← Back to team overview

duplicity-team team mailing list archive

[Merge] lp:~iamthefij/duplicity/b2-python3 into lp:duplicity

 

Ian Fijolek has proposed merging lp:~iamthefij/duplicity/b2-python3 into lp:duplicity.

Requested reviews:
  duplicity-team (duplicity-team)

For more details, see:
https://code.launchpad.net/~iamthefij/duplicity/b2-python3/+merge/379251

Fix bytes/str error in b2 backend on Python 3
  
A nearly identical bug to #1863018 also affects the B2 backend. This is a very similar patch to the one just merged that fixes it.

The quote_plus function is removed as it no longer appears to be required. At least not with the latest b2 and in Python 3. When it is present, a FileNotFound exception is thrown because the / in the path gets double encoded.
-- 
Your team duplicity-team is requested to review the proposed merge of lp:~iamthefij/duplicity/b2-python3 into lp:duplicity.
=== modified file 'duplicity/backends/b2backend.py'
--- duplicity/backends/b2backend.py	2019-11-16 17:15:49 +0000
+++ duplicity/backends/b2backend.py	2020-02-15 08:26:57 +0000
@@ -32,9 +32,9 @@
 
 import duplicity.backend
 from duplicity.errors import BackendException, FatalBackendException
+from duplicity import util
 from duplicity import log
 from duplicity import progress
-from duplicity import util
 
 
 class B2ProgressListener(object):
@@ -65,24 +65,20 @@
         """
         duplicity.backend.Backend.__init__(self, parsed_url)
 
-        global DownloadDestLocalFile, FileVersionInfoFactory
-        try:  # try to import the new b2sdk if available
-            from b2sdk.api import B2Api
-            from b2sdk.account_info import InMemoryAccountInfo
-            from b2sdk.download_dest import DownloadDestLocalFile
-            from b2sdk.exception import NonExistentBucket
-            from b2sdk.file_version import FileVersionInfoFactory
+        # Import B2 API
+        try:
+            global b2
+            import b2
+            global b2sdk
+            import b2sdk
+            import b2.api
+            import b2.account_info
+            import b2.download_dest
+            import b2.file_version
         except ImportError:
-            try:  # fall back to import the old b2 client
-                from b2.api import B2Api
-                from b2.account_info import InMemoryAccountInfo
-                from b2.download_dest import DownloadDestLocalFile
-                from b2.exception import NonExistentBucket
-                from b2.file_version import FileVersionInfoFactory
-            except ImportError:
-                raise BackendException(u'B2 backend requires B2 Python SDK (pip install b2sdk)')
+            raise BackendException(u'B2 backend requires B2 Python APIs (pip install b2)')
 
-        self.service = B2Api(InMemoryAccountInfo())
+        self.service = b2.api.B2Api(b2.account_info.InMemoryAccountInfo())
         self.parsed_url.hostname = u'B2'
 
         account_id = parsed_url.username
@@ -104,32 +100,33 @@
         try:
             self.bucket = self.service.get_bucket_by_name(bucket_name)
             log.Log(u"Bucket found", log.INFO)
-        except NonExistentBucket:
+        except b2.exception.NonExistentBucket:
             try:
                 log.Log(u"Bucket not found, creating one", log.INFO)
                 self.bucket = self.service.create_bucket(bucket_name, u'allPrivate')
             except:
                 raise FatalBackendException(u"Bucket cannot be created")
 
+    def _build_remote_filepath(self, remote_filename):
+        u"""Build remote filepath"""
+        return self.path + util.fsdecode(remote_filename)
+
     def _get(self, remote_filename, local_path):
         u"""
         Download remote_filename to local_path
         """
-        log.Log(u"Get: %s -> %s" % (self.path + util.fsdecode(remote_filename),
-                                    util.fsdecode(local_path.name)),
-                log.INFO)
-        self.bucket.download_file_by_name(quote_plus(self.path + util.fsdecode(remote_filename), u'/'),
-                                          DownloadDestLocalFile(util.fsdecode(local_path.name)))
+        remote_filename = self._build_remote_filepath(remote_filename)
+        log.Log(u"Get: %s -> %s" % (remote_filename, local_path.name), log.INFO)
+        self.bucket.download_file_by_name(remote_filename,
+                                          b2.download_dest.DownloadDestLocalFile(local_path.name))
 
     def _put(self, source_path, remote_filename):
         u"""
         Copy source_path to remote_filename
         """
-        log.Log(u"Put: %s -> %s" % (util.fsdecode(source_path.name),
-                                    self.path + util.fsdecode(remote_filename)),
-                log.INFO)
-        self.bucket.upload_local_file(util.fsdecode(source_path.name),
-                                      quote_plus(self.path + util.fsdecode(remote_filename), u'/'),
+        remote_filename = self._build_remote_filepath(remote_filename)
+        log.Log(u"Put: %s -> %s" % (source_path.name, remote_filename), log.INFO)
+        self.bucket.upload_local_file(source_path.name, remote_filename,
                                       content_type=u'application/pgp-encrypted',
                                       progress_listener=B2ProgressListener())
 
@@ -144,23 +141,25 @@
         u"""
         Delete filename from remote server
         """
-        log.Log(u"Delete: %s" % self.path + util.fsdecode(filename), log.INFO)
-        file_version_info = self.file_info(quote_plus(self.path + util.fsdecode(filename), u'/'))
+        filename = self._build_remote_filepath(filename)
+        log.Log(u"Delete: %s" % filename, log.INFO)
+        file_version_info = self.file_info(filename)
         self.bucket.delete_file_version(file_version_info.id_, file_version_info.file_name)
 
     def _query(self, filename):
         u"""
         Get size info of filename
         """
-        log.Log(u"Query: %s" % self.path + util.fsdecode(filename), log.INFO)
-        file_version_info = self.file_info(quote_plus(self.path + util.fsdecode(filename), u'/'))
+        filename = self._build_remote_filepath(filename)
+        log.Log(u"Query: %s" % filename, log.INFO)
+        file_version_info = self.file_info(filename)
         return {u'size': file_version_info.size
                 if file_version_info is not None and file_version_info.size is not None else -1}
 
     def file_info(self, filename):
-        response = self.bucket.api.session.list_file_names(self.bucket.id_, filename, 1, None)
+        response = self.bucket.list_file_names(filename, 1)
         for entry in response[u'files']:
-            file_version_info = FileVersionInfoFactory.from_api_response(entry)
+            file_version_info = b2.file_version.FileVersionInfoFactory.from_api_response(entry)
             if file_version_info.file_name == filename:
                 return file_version_info
         raise BackendException(u'File not found')


Follow ups