1
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
1
# Copyright (C) 2005, 2006, 2007, 2008, 2009 Canonical Ltd
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
"""Implementation of Transport over ftp.
18
18
Written by Daniel Silverstone <dsilvers@digital-scurf.org> with serious
65
65
class FtpStatResult(object):
66
def __init__(self, f, relpath):
67
def __init__(self, f, abspath):
68
self.st_size = f.size(relpath)
69
self.st_size = f.size(abspath)
69
70
self.st_mode = stat.S_IFREG
70
71
except ftplib.error_perm:
74
75
self.st_mode = stat.S_IFDIR
146
147
connection.login(user=user, passwd=password)
147
148
connection.set_pasv(not self.is_active)
149
# binary mode is the default
150
connection.voidcmd('TYPE I')
148
151
except socket.error, e:
149
152
raise errors.SocketConnectionError(self._host, self._port,
150
153
msg='Unable to connect to',
197
200
raise unknown_exc(path, extra=extra)
198
# TODO: jam 20060516 Consider re-raising the error wrapped in
201
# TODO: jam 20060516 Consider re-raising the error wrapped in
199
202
# something like TransportError, but this loses the traceback
200
203
# Also, 'sftp' has a generic 'Failure' mode, which we use failure_exc
201
204
# to handle. Consider doing something like that here.
402
405
def _try_append(self, relpath, text, mode=None, retries=0):
403
406
"""Try repeatedly to append the given text to the file at relpath.
405
408
This is a recursive function. On errors, it will be called until the
406
409
number of retries is exceeded.
409
412
abspath = self._remote_path(relpath)
410
413
mutter("FTP appe (try %d) to %s", retries, abspath)
411
414
ftp = self._get_FTP()
412
ftp.voidcmd("TYPE I")
413
415
cmd = "APPE %s" % abspath
414
416
conn = ftp.transfercmd(cmd)
415
417
conn.sendall(text)
439
441
mutter("FTP site chmod: setting permissions to %s on %s",
440
str(mode), self._remote_path(relpath))
442
oct(mode), self._remote_path(relpath))
441
443
ftp = self._get_FTP()
442
444
cmd = "SITE CHMOD %s %s" % (oct(mode),
443
445
self._remote_path(relpath))
445
447
except ftplib.error_perm, e:
446
448
# Command probably not available on this server
447
449
warning("FTP Could not set permissions to %s on %s. %s",
448
str(mode), self._remote_path(relpath), str(e))
450
oct(mode), self._remote_path(relpath), str(e))
450
452
# TODO: jam 20060516 I believe ftp allows you to tell an ftp server
451
453
# to copy something to another machine. And you may be able
476
478
self._rename_and_overwrite(abs_from, abs_to, f)
477
479
except ftplib.error_perm, e:
478
480
self._translate_perm_error(e, abs_from,
479
extra='unable to rename to %r' % (rel_to,),
481
extra='unable to rename to %r' % (rel_to,),
480
482
unknown_exc=errors.PathError)
482
484
def _rename_and_overwrite(self, abs_from, abs_to, f):
517
519
mutter("FTP nlst: %s", basepath)
518
520
f = self._get_FTP()
520
paths = f.nlst(basepath)
521
except ftplib.error_perm, e:
522
self._translate_perm_error(e, relpath, extra='error with list_dir')
523
except ftplib.error_temp, e:
524
# xs4all's ftp server raises a 450 temp error when listing an empty
525
# directory. Check for that and just return an empty list in that
526
# case. See bug #215522
527
if str(e).lower().startswith('450 no files found'):
528
mutter('FTP Server returned "%s" for nlst.'
529
' Assuming it means empty directory',
523
paths = f.nlst(basepath)
524
except ftplib.error_perm, e:
525
self._translate_perm_error(e, relpath,
526
extra='error with list_dir')
527
except ftplib.error_temp, e:
528
# xs4all's ftp server raises a 450 temp error when listing an
529
# empty directory. Check for that and just return an empty list
530
# in that case. See bug #215522
531
if str(e).lower().startswith('450 no files found'):
532
mutter('FTP Server returned "%s" for nlst.'
533
' Assuming it means empty directory',
538
# Restore binary mode as nlst switch to ascii mode to retrieve file
533
542
# If FTP.nlst returns paths prefixed by relpath, strip 'em
534
543
if paths and paths[0].startswith(basepath):
535
544
entries = [path[len(basepath)+1:] for path in paths]
589
598
def get_test_permutations():
590
599
"""Return the permutations to be used in testing."""
591
from bzrlib import tests
592
if tests.FTPServerFeature.available():
593
from bzrlib.tests import ftp_server
594
return [(FtpTransport, ftp_server.FTPServer)]
596
# Dummy server to have the test suite report the number of tests
597
# needing that feature. We raise UnavailableFeature from methods before
598
# the test server is being used. Doing so in the setUp method has bad
599
# side-effects (tearDown is never called).
600
class UnavailableFTPServer(object):
602
def setUp(self, vfs_server=None):
609
raise tests.UnavailableFeature(tests.FTPServerFeature)
611
def get_bogus_url(self):
612
raise tests.UnavailableFeature(tests.FTPServerFeature)
614
return [(FtpTransport, UnavailableFTPServer)]
600
from bzrlib.tests import ftp_server
601
return [(FtpTransport, ftp_server.FTPTestServer)]