diff --git a/rattail/exceptions.py b/rattail/exceptions.py index 6c4c8d37f72a800a1da17fc7efcd2717910f0842..ab045316b57e58fa65c623ba1158206fe9a59710 100644 --- a/rattail/exceptions.py +++ b/rattail/exceptions.py @@ -2,7 +2,7 @@ ################################################################################ # # Rattail -- Retail Software Framework -# Copyright © 2010-2014 Lance Edgar +# Copyright © 2010-2015 Lance Edgar # # This file is part of Rattail. # @@ -61,6 +61,32 @@ class RecipientsNotFound(ConfigurationError): "(or 'default.to') in the [rattail.mail] section.".format(self.key)) +class FileOperationError(RattailError): + """ + Generic exception for file operation failures. + """ + + +class PathNotFound(FileOperationError): + """ + Raised when "path not found" errors are encountered within the + :func:`rattail.files.locking_copy_test()` function. The purpose of this is + to normalize these errors to a single type, since the file monitor retry + mechanism will fail if two distinct exceptions are encountered during its + processing attempts. + """ + def __init__(self, original_error): + self.original_error = original_error + + def __str__(self): + return unicode(self).encode('utf_8') + + def __unicode__(self): + return '{0}: {1}'.format( + self.original_error.__class__.__name__, + self.original_error) + + class BatchError(RattailError): """ Base class for all batch-related errors. diff --git a/rattail/files.py b/rattail/files.py index 59b962454dbac577e60cdafe9a26df2c9550e436..4956aca487bea5f069fdb487845baad304e79b75 100644 --- a/rattail/files.py +++ b/rattail/files.py @@ -39,6 +39,8 @@ from datetime import datetime import pkg_resources +from rattail.exceptions import PathNotFound + def count_lines(path, encoding=None): """ @@ -135,16 +137,32 @@ def locking_copy_test(src, destdir): dst = os.path.join(destdir, fn) lockdir = '{0}.lock'.format(dst) + # Note that we normalize two separate errors to PathNotFound below. This + # is because the file monitor retry mechanism will fail if two distinct + # error types are encountered. Since this function needs to be somewhat + # robust in the face of network problems, raising a common error type will + # give us a better chance at being retried (if so configured). + # Attempt to create the lock dir. We ignore "file exists" error here since # it's possible this function may be called mutliple times as part of a # filemon retry strategy. try: os.mkdir(lockdir) except OSError as error: + if error.errno == errno.ENOENT: + raise PathNotFound(error) if error.errno != errno.EEXIST: raise - shutil.copy(src, dst) + # Attempt to copy the file. + try: + shutil.copy(src, dst) + except IOError as error: + if error.errno == errno.ENOENT: + raise PathNotFound(error) + raise + + # Attempt to remove the lock dir. os.rmdir(lockdir)