duplicity-team team mailing list archive
-
duplicity-team team
-
Mailing list archive
-
Message #00260
[Merge] lp:~mterry/duplicity/backend-log-codes into lp:duplicity
Michael Terry has proposed merging lp:~mterry/duplicity/backend-log-codes into lp:duplicity.
Requested reviews:
duplicity-team (duplicity-team)
Related bugs:
#578663 Use log codes for common backend errors
https://bugs.launchpad.net/bugs/578663
I've added some new error codes (and took the liberty of reserving a chunk of numbers for future backend codes):
backend_error = 50
backend_permission_denied = 51
backend_not_found = 52
backend_no_space = 53
I've converted two of the backends to use the new scheme, rather than throwing up BackendExceptions: localbackend and giobackend. Converting all backends would be nice, but I didn't consider it necessary.
Using a proper error message for these errors is better for the user because a simple error message is more friendly than an exception, and it's better for wrappers because they can display an intelligent message or change behavior based on the error.
The schema I came up with for the machine readable bit is:
ERROR <num> <operation> <file1> <file2>
Where num is the error code; operation is 'list', 'delete', 'put', or 'get'; and file2 is only provided for put and get. I have working code that consumes all this in a Deja Dup branch.
I've tested all four error paths in both backends. Seems to work fine.
--
https://code.launchpad.net/~mterry/duplicity/backend-log-codes/+merge/25373
Your team duplicity-team is requested to review the proposed merge of lp:~mterry/duplicity/backend-log-codes into lp:duplicity.
=== modified file 'duplicity/backends/giobackend.py'
--- duplicity/backends/giobackend.py 2010-02-09 01:00:37 +0000
+++ duplicity/backends/giobackend.py 2010-05-15 14:47:22 +0000
@@ -29,6 +29,7 @@
import duplicity.backend
from duplicity import log
from duplicity import globals
+from duplicity import util
from duplicity.errors import *
from duplicity.util import exception_traceback
@@ -88,10 +89,24 @@
% str(e), log.ErrorCode.connection_failed)
loop.quit()
+ def handle_error(self, e, op, file1=None, file2=None):
+ code = log.ErrorCode.backend_error
+ if isinstance(e, gio.Error):
+ if e.code == gio.ERROR_PERMISSION_DENIED:
+ code = log.ErrorCode.backend_permission_denied
+ elif e.code == gio.ERROR_NOT_FOUND:
+ code = log.ErrorCode.backend_not_found
+ elif e.code == gio.ERROR_NO_SPACE:
+ code = log.ErrorCode.backend_no_space
+ extra = ' '.join([util.escape(x) for x in [file1, file2] if x])
+ extra = ' '.join([op, extra])
+ log.FatalError(str(e), code, extra)
+
def copy_progress(self, *args, **kwargs):
pass
- def copy_file(self, source, target):
+ def copy_file(self, op, source, target):
+ exc = None
for n in range(1, globals.num_retries+1):
log.Info(_("Writing %s") % target.get_parse_name())
try:
@@ -103,8 +118,8 @@
% (target.get_parse_name(), n, e.__class__.__name__, str(e)))
log.Debug("Backtrace of previous error: %s"
% exception_traceback())
- raise BackendException(_("Could not copy %s to %s") % (source.get_parse_name(),
- target.get_parse_name()))
+ exc = e
+ self.handle_error(exc, op, source.get_parse_name(), target.get_parse_name())
def put(self, source_path, remote_filename = None):
"""Copy file to remote"""
@@ -112,19 +127,22 @@
remote_filename = source_path.get_filename()
source_file = gio.File(path=source_path.name)
target_file = self.remote_file.get_child_for_display_name(remote_filename)
- self.copy_file(source_file, target_file)
+ self.copy_file('put', source_file, target_file)
def get(self, filename, local_path):
"""Get file and put in local_path (Path object)"""
source_file = self.remote_file.get_child_for_display_name(filename)
target_file = gio.File(path=local_path.name)
- self.copy_file(source_file, target_file)
+ self.copy_file('get', source_file, target_file)
local_path.setdata()
def list(self):
"""List files in that directory"""
- enum = self.remote_file.enumerate_children(gio.FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME,
- gio.FILE_QUERY_INFO_NOFOLLOW_SYMLINKS)
+ try:
+ enum = self.remote_file.enumerate_children(gio.FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME,
+ gio.FILE_QUERY_INFO_NOFOLLOW_SYMLINKS)
+ except Exception, e:
+ self.handle_error(e, 'list', self.remote_file.get_parse_name())
files = []
try:
info = enum.next_file()
@@ -133,7 +151,7 @@
info = enum.next_file()
return files
except Exception, e:
- raise BackendException(str(e))
+ self.handle_error(e, 'list')
def delete(self, filename_list):
"""Delete all files in filename list"""
@@ -142,4 +160,4 @@
for filename in filename_list:
self.remote_file.get_child_for_display_name(filename).delete()
except Exception, e:
- raise BackendException(str(e))
+ self.handle_error(e, 'delete', self.remote_file.get_child_for_display_name(filename).get_parse_name())
=== modified file 'duplicity/backends/localbackend.py'
--- duplicity/backends/localbackend.py 2010-02-09 01:00:37 +0000
+++ duplicity/backends/localbackend.py 2010-05-15 14:47:22 +0000
@@ -21,10 +21,12 @@
import os
import types
+import errno
import duplicity.backend
from duplicity import log
from duplicity import path
+from duplicity import util
from duplicity.errors import *
class LocalBackend(duplicity.backend.Backend):
@@ -41,6 +43,19 @@
raise BackendException( "Bad file:// path syntax." )
self.remote_pathdir = path.Path(parsed_url.path[2:])
+ def handle_error(self, e, op, file1=None, file2=None):
+ code = log.ErrorCode.backend_error
+ if hasattr(e, 'errno'):
+ if e.errno == errno.EACCES:
+ code = log.ErrorCode.backend_permission_denied
+ elif e.errno == errno.ENOENT:
+ code = log.ErrorCode.backend_not_found
+ elif e.errno == errno.ENOSPC:
+ code = log.ErrorCode.backend_no_space
+ extra = ' '.join([util.escape(x) for x in [file1, file2] if x])
+ extra = ' '.join([op, extra])
+ log.FatalError(str(e), code, extra)
+
def put(self, source_path, remote_filename = None, rename = None):
"""If rename is set, try that first, copying if doesn't work"""
if not remote_filename:
@@ -52,14 +67,22 @@
source_path.rename(target_path)
except OSError:
pass
+ except Exception, e:
+ handle_error(e, 'put', source_path.name, target_path.name)
else:
return
- target_path.writefileobj(source_path.open("rb"))
+ try:
+ target_path.writefileobj(source_path.open("rb"))
+ except Exception, e:
+ self.handle_error(e, 'put', source_path.name, target_path.name)
def get(self, filename, local_path):
"""Get file and put in local_path (Path object)"""
source_path = self.remote_pathdir.append(filename)
- local_path.writefileobj(source_path.open("rb"))
+ try:
+ local_path.writefileobj(source_path.open("rb"))
+ except Exception, e:
+ self.handle_error(e, 'get', source_path.name, local_path.name)
def list(self):
"""List files in that directory"""
@@ -67,7 +90,10 @@
os.makedirs(self.remote_pathdir.base)
except:
pass
- return self.remote_pathdir.listdir()
+ try:
+ return self.remote_pathdir.listdir()
+ except Exception, e:
+ self.handle_error(e, 'list', self.remote_pathdir.name)
def delete(self, filename_list):
"""Delete all files in filename list"""
@@ -75,8 +101,8 @@
for filename in filename_list:
try:
self.remote_pathdir.append(filename).delete()
- except OSError, e:
- raise BackendException(str(e))
+ except Exception, e:
+ self.handle_error(e, 'delete', self.remote_pathdir.append(filename).name)
duplicity.backend.register_backend("file", LocalBackend)
=== modified file 'duplicity/log.py'
--- duplicity/log.py 2010-02-09 01:00:37 +0000
+++ duplicity/log.py 2010-05-15 14:47:22 +0000
@@ -181,6 +181,13 @@
gio_not_available = 40
par2_missing = 41
source_dir_mismatch = 42
+
+ # 50->69 reserved for backend errors
+ backend_error = 50
+ backend_permission_denied = 51
+ backend_not_found = 52
+ backend_no_space = 53
+
# Reserve 255 because it is used as an error code for gksu
def FatalError(s, code, extra=None):
Follow ups