82
83
def __init__(self, base, _provided_instance=None):
83
84
"""Set the base path where files will be stored."""
84
85
assert base.startswith('ftp://') or base.startswith('aftp://')
87
self.is_active = base.startswith('aftp://')
89
# urlparse won't handle aftp://, delete the leading 'a'
90
# FIXME: This breaks even hopes of connection sharing
91
# (by reusing the url instead of true cloning) by
92
# modifying the the url coming from the user.
94
if not base.endswith('/'):
96
(self._proto, self._username,
97
self._password, self._host,
98
self._port, self._path) = split_url(base)
99
base = self._unparse_url()
85
101
super(FtpTransport, self).__init__(base)
86
if self._scheme == 'aftp':
87
self._unqualified_scheme = 'ftp'
90
self._unqualified_scheme = self._scheme
91
self.is_active = False
92
102
self._FTP_instance = _provided_instance
104
def _unparse_url(self, path=None):
107
path = urllib.quote(path)
108
netloc = urllib.quote(self._host)
109
if self._username is not None:
110
netloc = '%s@%s' % (urllib.quote(self._username), netloc)
111
if self._port is not None:
112
netloc = '%s:%d' % (netloc, self._port)
116
return urlparse.urlunparse((proto, netloc, path, '', '', ''))
94
118
def _get_FTP(self):
95
119
"""Return the ftplib.FTP instance for this object."""
96
120
if self._FTP_instance is None:
97
121
mutter("Constructing FTP instance against %r" %
98
((self._host, self._port, self._user, '********',
122
((self._host, self._port, self._username, '********',
99
123
self.is_active),))
101
125
connection = ftplib.FTP()
102
126
connection.connect(host=self._host, port=self._port)
103
if self._user and self._user != 'anonymous' and \
127
if self._username and self._username != 'anonymous' and \
104
128
not self._password:
105
129
self._password = bzrlib.ui.ui_factory.get_password(
106
130
prompt='FTP %(user)s@%(host)s password',
107
user=self._user, host=self._host)
108
connection.login(user=self._user, passwd=self._password)
131
user=self._username, host=self._host)
132
connection.login(user=self._username, passwd=self._password)
109
133
connection.set_pasv(not self.is_active)
110
134
except ftplib.error_perm, e:
111
135
raise errors.TransportError(msg="Error setting up connection:"
164
188
return FtpTransport(self.abspath(offset), self._FTP_instance)
166
def _remote_path(self, relpath):
190
def _abspath(self, relpath):
191
assert isinstance(relpath, basestring)
192
relpath = urlutils.unescape(relpath)
193
if relpath.startswith('/'):
196
basepath = self._path.split('/')
197
if len(basepath) > 0 and basepath[-1] == '':
198
basepath = basepath[:-1]
199
for p in relpath.split('/'):
201
if len(basepath) == 0:
202
# In most filesystems, a request for the parent
203
# of root, just returns root.
206
elif p == '.' or p == '':
210
# Possibly, we could use urlparse.urljoin() here, but
211
# I'm concerned about when it chooses to strip the last
212
# portion of the path, and when it doesn't.
167
214
# XXX: It seems that ftplib does not handle Unicode paths
168
# at the same time, medusa won't handle utf8 paths So if
169
# we .encode(utf8) here (see ConnectedTransport
170
# implementation), then we get a Server failure. while
171
# if we use str(), we get a UnicodeError, and the test
172
# suite just skips testing UnicodePaths.
173
relative = str(urlutils.unescape(relpath))
174
remote_path = self._combine_paths(self._path, relative)
215
# at the same time, medusa won't handle utf8 paths
216
# So if we .encode(utf8) here, then we get a Server failure.
217
# while if we use str(), we get a UnicodeError, and the test suite
218
# just skips testing UnicodePaths.
219
return str('/'.join(basepath) or '/')
221
def abspath(self, relpath):
222
"""Return the full url to the given relative path.
223
This can be supplied with a string or a list
225
path = self._abspath(relpath)
226
return self._unparse_url(path)
177
228
def has(self, relpath):
178
229
"""Does the target location exist?"""
208
259
# TODO: decode should be deprecated
210
mutter("FTP get: %s", self._remote_path(relpath))
261
mutter("FTP get: %s", self._abspath(relpath))
211
262
f = self._get_FTP()
213
f.retrbinary('RETR '+self._remote_path(relpath), ret.write, 8192)
264
f.retrbinary('RETR '+self._abspath(relpath), ret.write, 8192)
216
267
except ftplib.error_perm, e:
246
297
TODO: jam 20051215 ftp as a protocol seems to support chmod, but
249
abspath = self._remote_path(relpath)
300
abspath = self._abspath(relpath)
250
301
tmp_abspath = '%s.tmp.%.9f.%d.%d' % (abspath, time.time(),
251
302
os.getpid(), random.randint(0,0x7FFFFFFF))
252
303
if getattr(fp, 'read', None) is None:
363
414
mutter("FTP site chmod: setting permissions to %s on %s",
364
str(mode), self._remote_path(relpath))
415
str(mode), self._abspath(relpath))
365
416
ftp = self._get_FTP()
366
cmd = "SITE CHMOD %s %s" % (self._remote_path(relpath), str(mode))
417
cmd = "SITE CHMOD %s %s" % (self._abspath(relpath), str(mode))
368
419
except ftplib.error_perm, e:
369
420
# Command probably not available on this server
370
421
warning("FTP Could not set permissions to %s on %s. %s",
371
str(mode), self._remote_path(relpath), str(e))
422
str(mode), self._abspath(relpath), str(e))
373
424
# TODO: jam 20060516 I believe ftp allows you to tell an ftp server
374
425
# to copy something to another machine. And you may be able
376
427
# So implement a fancier 'copy()'
378
429
def rename(self, rel_from, rel_to):
379
abs_from = self._remote_path(rel_from)
380
abs_to = self._remote_path(rel_to)
430
abs_from = self._abspath(rel_from)
431
abs_to = self._abspath(rel_to)
381
432
mutter("FTP rename: %s => %s", abs_from, abs_to)
382
433
f = self._get_FTP()
383
434
return self._rename(abs_from, abs_to, f)
392
443
def move(self, rel_from, rel_to):
393
444
"""Move the item at rel_from to the location at rel_to"""
394
abs_from = self._remote_path(rel_from)
395
abs_to = self._remote_path(rel_to)
445
abs_from = self._abspath(rel_from)
446
abs_to = self._abspath(rel_to)
397
448
mutter("FTP mv: %s => %s", abs_from, abs_to)
398
449
f = self._get_FTP()