83
82
def __init__(self, base, _provided_instance=None):
84
83
"""Set the base path where files will be stored."""
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()
101
84
super(FtpTransport, self).__init__(base)
85
if self._scheme == 'aftp':
86
self._unqualified_scheme = 'ftp'
89
self._unqualified_scheme = self._scheme
90
self.is_active = False
102
91
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, '', '', ''))
118
93
def _get_FTP(self):
119
94
"""Return the ftplib.FTP instance for this object."""
120
95
if self._FTP_instance is None:
121
96
mutter("Constructing FTP instance against %r" %
122
((self._host, self._port, self._username, '********',
97
((self._host, self._port, self._user, '********',
123
98
self.is_active),))
125
100
connection = ftplib.FTP()
126
101
connection.connect(host=self._host, port=self._port)
127
if self._username and self._username != 'anonymous' and \
102
if self._user and self._user != 'anonymous' and \
128
103
not self._password:
129
104
self._password = bzrlib.ui.ui_factory.get_password(
130
105
prompt='FTP %(user)s@%(host)s password',
131
user=self._username, host=self._host)
132
connection.login(user=self._username, passwd=self._password)
106
user=self._user, host=self._host)
107
connection.login(user=self._user, passwd=self._password)
133
108
connection.set_pasv(not self.is_active)
134
109
except ftplib.error_perm, e:
135
110
raise errors.TransportError(msg="Error setting up connection:"
188
163
return FtpTransport(self.abspath(offset), self._FTP_instance)
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.
165
def _remote_path(self, relpath):
214
166
# XXX: It seems that ftplib does not handle Unicode paths
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)
167
# at the same time, medusa won't handle utf8 paths So if
168
# we .encode(utf8) here (see ConnectedTransport
169
# implementation), then we get a Server failure. while
170
# if we use str(), we get a UnicodeError, and the test
171
# suite just skips testing UnicodePaths.
172
relative = str(urlutils.unescape(relpath))
173
remote_path = self._combine_paths(self._path, relative)
228
176
def has(self, relpath):
229
177
"""Does the target location exist?"""
259
207
# TODO: decode should be deprecated
261
mutter("FTP get: %s", self._abspath(relpath))
209
mutter("FTP get: %s", self._remote_path(relpath))
262
210
f = self._get_FTP()
264
f.retrbinary('RETR '+self._abspath(relpath), ret.write, 8192)
212
f.retrbinary('RETR '+self._remote_path(relpath), ret.write, 8192)
267
215
except ftplib.error_perm, e:
297
245
TODO: jam 20051215 ftp as a protocol seems to support chmod, but
300
abspath = self._abspath(relpath)
248
abspath = self._remote_path(relpath)
301
249
tmp_abspath = '%s.tmp.%.9f.%d.%d' % (abspath, time.time(),
302
250
os.getpid(), random.randint(0,0x7FFFFFFF))
303
251
if getattr(fp, 'read', None) is None:
414
362
mutter("FTP site chmod: setting permissions to %s on %s",
415
str(mode), self._abspath(relpath))
363
str(mode), self._remote_path(relpath))
416
364
ftp = self._get_FTP()
417
cmd = "SITE CHMOD %s %s" % (self._abspath(relpath), str(mode))
365
cmd = "SITE CHMOD %s %s" % (self._remote_path(relpath), str(mode))
419
367
except ftplib.error_perm, e:
420
368
# Command probably not available on this server
421
369
warning("FTP Could not set permissions to %s on %s. %s",
422
str(mode), self._abspath(relpath), str(e))
370
str(mode), self._remote_path(relpath), str(e))
424
372
# TODO: jam 20060516 I believe ftp allows you to tell an ftp server
425
373
# to copy something to another machine. And you may be able
427
375
# So implement a fancier 'copy()'
429
377
def rename(self, rel_from, rel_to):
430
abs_from = self._abspath(rel_from)
431
abs_to = self._abspath(rel_to)
378
abs_from = self._remote_path(rel_from)
379
abs_to = self._remote_path(rel_to)
432
380
mutter("FTP rename: %s => %s", abs_from, abs_to)
433
381
f = self._get_FTP()
434
382
return self._rename(abs_from, abs_to, f)
443
391
def move(self, rel_from, rel_to):
444
392
"""Move the item at rel_from to the location at rel_to"""
445
abs_from = self._abspath(rel_from)
446
abs_to = self._abspath(rel_to)
393
abs_from = self._remote_path(rel_from)
394
abs_to = self._remote_path(rel_to)
448
396
mutter("FTP mv: %s => %s", abs_from, abs_to)
449
397
f = self._get_FTP()