← Back to team overview

duplicity-team team mailing list archive

[MERGE] Corects remote path to have one single slash

 

Greetings,

I found that I was not able to interact with Godaddy's ftp-based storage with duplicity. Looking further, found the the put() and get() methods in ftpbackend were stripping all the starting slashes from the remote path. This reflects a python api change, where the call is now lstrip(s, n). I did not change the call, because in my experience, expecting the right number of slashes is unreliable. Rather, I added '/' to the sprintf formatting strings.

Yours,

Jed Reynolds
# Bazaar merge directive format 2 (Bazaar 0.90)
# revision_id: jed@xxxxxxxxxxxxxx-20121003141903-5m3jc8lkkb68qxy1
# target_branch: bzr+ssh://bazaar.launchpad.net/+branch/duplicity/
# testament_sha1: c13abf28d343e9b1f471d16b7cb7f9e561d448af
# timestamp: 2012-10-03 07:22:48 -0700
# base_revision_id: michael.terry@xxxxxxxxxxxxx-20121002221843-\
#   ql9em3gu3ug0uk6b
# 
# Begin patch
=== modified file 'duplicity/backends/ftpbackend.py'
--- duplicity/backends/ftpbackend.py	2011-06-17 06:21:42 +0000
+++ duplicity/backends/ftpbackend.py	2012-10-03 14:19:03 +0000
@@ -29,96 +29,96 @@
 from duplicity import tempdir
 
 class FTPBackend(duplicity.backend.Backend):
-    """Connect to remote store using File Transfer Protocol"""
-    def __init__(self, parsed_url):
-        duplicity.backend.Backend.__init__(self, parsed_url)
-
-        # we expect an error return, so go low-level and ignore it
-        try:
-            p = os.popen("ncftpls -v")
-            fout = p.read()
-            ret = p.close()
-        except Exception:
-            pass
-        # the expected error is 8 in the high-byte and some output
-        if ret != 0x0800 or not fout:
-            log.FatalError("NcFTP not found:  Please install NcFTP version 3.1.9 or later",
-                           log.ErrorCode.ftp_ncftp_missing)
-
-        # version is the second word of the first line
-        version = fout.split('\n')[0].split()[1]
-        if version < "3.1.9":
-            log.FatalError("NcFTP too old:  Duplicity requires NcFTP version 3.1.9,"
-                           "3.2.1 or later.  Version 3.2.0 will not work properly.",
-                           log.ErrorCode.ftp_ncftp_too_old)
-        elif version == "3.2.0":
-            log.Warn("NcFTP (ncftpput) version 3.2.0 may fail with duplicity.\n"
-                     "see: http://www.ncftpd.com/ncftp/doc/changelog.html\n";
-                     "If you have trouble, please upgrade to 3.2.1 or later",
-                     log.WarningCode.ftp_ncftp_v320)
-        log.Notice("NcFTP version is %s" % version)
-
-        self.parsed_url = parsed_url
-
-        self.url_string = duplicity.backend.strip_auth_from_url(self.parsed_url)
-
-        # This squelches the "file not found" result from ncftpls when
-        # the ftp backend looks for a collection that does not exist.
-        # version 3.2.2 has error code 5, 1280 is some legacy value
-        self.popen_persist_breaks[ 'ncftpls' ] = [ 5, 1280 ]
-
-        # Use an explicit directory name.
-        if self.url_string[-1] != '/':
-            self.url_string += '/'
-
-        self.password = self.get_password()
-
-        if globals.ftp_connection == 'regular':
-            self.conn_opt = '-E'
-        else:
-            self.conn_opt = '-F'
-
-        self.tempfile, self.tempname = tempdir.default().mkstemp()
-        os.write(self.tempfile, "host %s\n" % self.parsed_url.hostname)
-        os.write(self.tempfile, "user %s\n" % self.parsed_url.username)
-        os.write(self.tempfile, "pass %s\n" % self.password)
-        os.close(self.tempfile)
-        self.flags = "-f %s %s -t %s -o useCLNT=0,useHELP_SITE=0 " % \
-            (self.tempname, self.conn_opt, globals.timeout)
-        if parsed_url.port != None and parsed_url.port != 21:
-            self.flags += " -P '%s'" % (parsed_url.port)
-
-    def put(self, source_path, remote_filename = None):
-        """Transfer source_path to remote_filename"""
-        if not remote_filename:
-            remote_filename = source_path.get_filename()
-        remote_path = os.path.join(urllib.unquote(self.parsed_url.path.lstrip('/')), remote_filename).rstrip()
-        commandline = "ncftpput %s -m -V -C '%s' '%s'" % \
-            (self.flags, source_path.name, remote_path)
-        self.run_command_persist(commandline)
-
-    def get(self, remote_filename, local_path):
-        """Get remote filename, saving it to local_path"""
-        remote_path = os.path.join(urllib.unquote(self.parsed_url.path), remote_filename).rstrip()
-        commandline = "ncftpget %s -V -C '%s' '%s' '%s'" % \
-            (self.flags, self.parsed_url.hostname, remote_path.lstrip('/'), local_path.name)
-        self.run_command_persist(commandline)
-        local_path.setdata()
-
-    def list(self):
-        """List files in directory"""
-        # Do a long listing to avoid connection reset
-        commandline = "ncftpls %s -l '%s'" % (self.flags, self.url_string)
-        l = self.popen_persist(commandline).split('\n')
-        l = filter(lambda x: x, l)
-        # Look for our files as the last element of a long list line
-        return [x.split()[-1] for x in l if not x.startswith("total ")]
-
-    def delete(self, filename_list):
-        """Delete files in filename_list"""
-        for filename in filename_list:
-            commandline = "ncftpls %s -l -X 'DELE %s' '%s'" % \
-                (self.flags, filename, self.url_string)
-            self.popen_persist(commandline)
+	"""Connect to remote store using File Transfer Protocol"""
+	def __init__(self, parsed_url):
+		duplicity.backend.Backend.__init__(self, parsed_url)
+
+		# we expect an error return, so go low-level and ignore it
+		try:
+			p = os.popen("ncftpls -v")
+			fout = p.read()
+			ret = p.close()
+		except Exception:
+			pass
+		# the expected error is 8 in the high-byte and some output
+		if ret != 0x0800 or not fout:
+			log.FatalError("NcFTP not found:  Please install NcFTP version 3.1.9 or later",
+						   log.ErrorCode.ftp_ncftp_missing)
+
+		# version is the second word of the first line
+		version = fout.split('\n')[0].split()[1]
+		if version < "3.1.9":
+			log.FatalError("NcFTP too old:  Duplicity requires NcFTP version 3.1.9,"
+						   "3.2.1 or later.  Version 3.2.0 will not work properly.",
+						   log.ErrorCode.ftp_ncftp_too_old)
+		elif version == "3.2.0":
+			log.Warn("NcFTP (ncftpput) version 3.2.0 may fail with duplicity.\n"
+					 "see: http://www.ncftpd.com/ncftp/doc/changelog.html\n";
+					 "If you have trouble, please upgrade to 3.2.1 or later",
+					 log.WarningCode.ftp_ncftp_v320)
+		log.Notice("NcFTP version is %s" % version)
+
+		self.parsed_url = parsed_url
+
+		self.url_string = duplicity.backend.strip_auth_from_url(self.parsed_url)
+
+		# This squelches the "file not found" result from ncftpls when
+		# the ftp backend looks for a collection that does not exist.
+		# version 3.2.2 has error code 5, 1280 is some legacy value
+		self.popen_persist_breaks[ 'ncftpls' ] = [ 5, 1280 ]
+
+		# Use an explicit directory name.
+		if self.url_string[-1] != '/':
+			self.url_string += '/'
+
+		self.password = self.get_password()
+
+		if globals.ftp_connection == 'regular':
+			self.conn_opt = '-E'
+		else:
+			self.conn_opt = '-F'
+
+		self.tempfile, self.tempname = tempdir.default().mkstemp()
+		os.write(self.tempfile, "host %s\n" % self.parsed_url.hostname)
+		os.write(self.tempfile, "user %s\n" % self.parsed_url.username)
+		os.write(self.tempfile, "pass %s\n" % self.password)
+		os.close(self.tempfile)
+		self.flags = "-f %s %s -t %s -o useCLNT=0,useHELP_SITE=0 " % \
+			(self.tempname, self.conn_opt, globals.timeout)
+		if parsed_url.port != None and parsed_url.port != 21:
+			self.flags += " -P '%s'" % (parsed_url.port)
+
+	def put(self, source_path, remote_filename = None):
+		"""Transfer source_path to remote_filename"""
+		if not remote_filename:
+			remote_filename = source_path.get_filename()
+		remote_path = os.path.join(urllib.unquote(self.parsed_url.path), remote_filename).rstrip()
+		commandline = "ncftpput %s -m -V -C '%s' '/%s'" % \
+			(self.flags, source_path.name, remote_path.lstrip('/'))
+		self.run_command_persist(commandline)
+
+	def get(self, remote_filename, local_path):
+		"""Get remote filename, saving it to local_path"""
+		remote_path = os.path.join(urllib.unquote(self.parsed_url.path), remote_filename).rstrip()
+		commandline = "ncftpget %s -V -C '%s' '/%s' '%s'" % \
+			(self.flags, self.parsed_url.hostname, remote_path.lstrip('/'), local_path.name)
+		self.run_command_persist(commandline)
+		local_path.setdata()
+
+	def list(self):
+		"""List files in directory"""
+		# Do a long listing to avoid connection reset
+		commandline = "ncftpls %s -l '%s'" % (self.flags, self.url_string)
+		l = self.popen_persist(commandline).split('\n')
+		l = filter(lambda x: x, l)
+		# Look for our files as the last element of a long list line
+		return [x.split()[-1] for x in l if not x.startswith("total ")]
+
+	def delete(self, filename_list):
+		"""Delete files in filename_list"""
+		for filename in filename_list:
+			commandline = "ncftpls %s -l -X 'DELE %s' '%s'" % \
+				(self.flags, filename, self.url_string)
+			self.popen_persist(commandline)
 
 duplicity.backend.register_backend("ftp", FTPBackend)

# Begin bundle
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
HBPPT26dpYo/8XckU4UJDCVqQ4A=

Follow ups