~bzr-pqm/bzr/bzr.dev

4763.2.4 by John Arbash Meinel
merge bzr.2.1 in preparation for NEWS entry.
1
# Copyright (C) 2005-2010 Canonical Ltd
1887.1.1 by Adeodato Simó
Do not separate paragraphs in the copyright statement with blank lines,
2
#
1185.36.4 by Daniel Silverstone
Add FTP transport
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
1887.1.1 by Adeodato Simó
Do not separate paragraphs in the copyright statement with blank lines,
7
#
1185.36.4 by Daniel Silverstone
Add FTP transport
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
1887.1.1 by Adeodato Simó
Do not separate paragraphs in the copyright statement with blank lines,
12
#
1185.36.4 by Daniel Silverstone
Add FTP transport
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
4183.7.1 by Sabin Iacob
update FSF mailing address
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
5060.3.1 by Martin Pool
Handle "Directory not empty" from ftp as DirectoryNotEmpty.
16
1185.36.4 by Daniel Silverstone
Add FTP transport
17
"""Implementation of Transport over ftp.
18
19
Written by Daniel Silverstone <dsilvers@digital-scurf.org> with serious
20
cargo-culting from the sftp transport and the http transport.
21
22
It provides the ftp:// and aftp:// protocols where ftp:// is passive ftp
23
and aftp:// is active ftp. Most people will want passive ftp for traversing
24
NAT and other firewalls, so it's best to use it unless you explicitly want
25
active, in which case aftp:// will be your friend.
26
"""
27
28
from cStringIO import StringIO
29
import ftplib
3021.1.2 by Vincent Ladeuil
Fix bug #164567 by catching connection errors.
30
import getpass
1530.1.11 by Robert Collins
Push the transport permutations list into each transport module allowing for automatic testing of new modules that are registered as transports.
31
import os
3508.1.15 by Vincent Ladeuil
Tweak chmod bits output for easier debug.
32
import random
3021.1.2 by Vincent Ladeuil
Fix bug #164567 by catching connection errors.
33
import socket
1185.36.4 by Daniel Silverstone
Add FTP transport
34
import stat
1185.72.14 by Matthieu Moy
Sleep between retries when the connection closes
35
import time
1530.1.11 by Robert Collins
Push the transport permutations list into each transport module allowing for automatic testing of new modules that are registered as transports.
36
1948.1.7 by John Arbash Meinel
Fix FtpTransport so that it raises UnicodeError and skips the unicode path tests
37
from bzrlib import (
2900.2.5 by Vincent Ladeuil
ake ftp aware of authentication config.
38
    config,
1948.1.7 by John Arbash Meinel
Fix FtpTransport so that it raises UnicodeError and skips the unicode path tests
39
    errors,
1551.15.2 by Aaron Bentley
FTP does not require atomic rename. Fixes #89436
40
    osutils,
1948.1.7 by John Arbash Meinel
Fix FtpTransport so that it raises UnicodeError and skips the unicode path tests
41
    urlutils,
42
    )
5390.2.1 by Alexander Belchenko
`decode` parameter to get() method in FtpTransport and GioTransport classes is deprecated.
43
from bzrlib.symbol_versioning import (
44
    DEPRECATED_PARAMETER,
45
    deprecated_in,
46
    deprecated_passed,
47
    warn,
48
    )
1948.1.7 by John Arbash Meinel
Fix FtpTransport so that it raises UnicodeError and skips the unicode path tests
49
from bzrlib.trace import mutter, warning
1707.3.4 by John Arbash Meinel
Moved most of sftp.split_url into a Transport function.
50
from bzrlib.transport import (
2671.3.6 by Robert Collins
Review feedback.
51
    AppendBasedFileStream,
2900.2.16 by Vincent Ladeuil
Make hhtp proxy aware of AuthenticationConfig (for password).
52
    ConnectedTransport,
2671.3.2 by Robert Collins
Start open_file_stream logic.
53
    _file_streams,
2900.2.16 by Vincent Ladeuil
Make hhtp proxy aware of AuthenticationConfig (for password).
54
    register_urlparse_netloc_protocol,
1707.3.4 by John Arbash Meinel
Moved most of sftp.split_url into a Transport function.
55
    Server,
56
    )
1551.3.11 by Aaron Bentley
Merge from Robert
57
2900.2.16 by Vincent Ladeuil
Make hhtp proxy aware of AuthenticationConfig (for password).
58
59
register_urlparse_netloc_protocol('aftp')
1707.3.30 by John Arbash Meinel
Only load medusa if you are running the test suite.
60
1551.3.11 by Aaron Bentley
Merge from Robert
61
1707.3.25 by John Arbash Meinel
Setting some default exception classes, which can help a lot.
62
class FtpPathError(errors.PathError):
63
    """FTP failed for path: %(path)s%(extra)s"""
64
65
1185.36.4 by Daniel Silverstone
Add FTP transport
66
class FtpStatResult(object):
3508.1.4 by Vincent Ladeuil
Ensure that ftp transport are always in binary mode.
67
68
    def __init__(self, f, abspath):
1185.36.4 by Daniel Silverstone
Add FTP transport
69
        try:
3508.1.4 by Vincent Ladeuil
Ensure that ftp transport are always in binary mode.
70
            self.st_size = f.size(abspath)
1185.36.4 by Daniel Silverstone
Add FTP transport
71
            self.st_mode = stat.S_IFREG
72
        except ftplib.error_perm:
73
            pwd = f.pwd()
74
            try:
3508.1.4 by Vincent Ladeuil
Ensure that ftp transport are always in binary mode.
75
                f.cwd(abspath)
1185.36.4 by Daniel Silverstone
Add FTP transport
76
                self.st_mode = stat.S_IFDIR
77
            finally:
78
                f.cwd(pwd)
79
80
1185.72.15 by Matthieu Moy
better error messages on ftp failures
81
_number_of_retries = 2
1185.72.14 by Matthieu Moy
Sleep between retries when the connection closes
82
_sleep_between_retries = 5
1185.72.12 by Matthieu Moy
made __number_of_retries global
83
2485.8.46 by Vincent Ladeuil
Add some remarks about current limitations in connection sharing.
84
# FIXME: there are inconsistencies in the way temporary errors are
85
# handled. Sometimes we reconnect, sometimes we raise an exception. Care should
86
# be taken to analyze the implications for write operations (read operations
2485.8.59 by Vincent Ladeuil
Update from review comments.
87
# are safe to retry). Overall even some read operations are never
88
# retried. --vila 20070720 (Bug #127164)
2485.8.16 by Vincent Ladeuil
Create a new, empty, ConnectedTransport class.
89
class FtpTransport(ConnectedTransport):
1185.36.4 by Daniel Silverstone
Add FTP transport
90
    """This is the transport agent for ftp:// access."""
91
2485.8.59 by Vincent Ladeuil
Update from review comments.
92
    def __init__(self, base, _from_transport=None):
1185.36.4 by Daniel Silverstone
Add FTP transport
93
        """Set the base path where files will be stored."""
3376.2.4 by Martin Pool
Remove every assert statement from bzrlib!
94
        if not (base.startswith('ftp://') or base.startswith('aftp://')):
95
            raise ValueError(base)
2485.8.59 by Vincent Ladeuil
Update from review comments.
96
        super(FtpTransport, self).__init__(base,
97
                                           _from_transport=_from_transport)
2485.8.25 by Vincent Ladeuil
Separate abspath from _remote_path, the intents are different.
98
        self._unqualified_scheme = 'ftp'
2485.8.22 by Vincent Ladeuil
Finish ftp refactoring. Test suite passing.
99
        if self._scheme == 'aftp':
100
            self.is_active = True
101
        else:
102
            self.is_active = False
4725.3.1 by Vincent Ladeuil
Fix test failure and clean up ftp APPE.
103
104
        # Most modern FTP servers support the APPE command. If ours doesn't, we
105
        # (re)set this flag accordingly later.
4537.1.1 by Nils Durner
fall back to GET & PUT if APPE doesn't work
106
        self._has_append = True
1707.3.4 by John Arbash Meinel
Moved most of sftp.split_url into a Transport function.
107
1185.36.4 by Daniel Silverstone
Add FTP transport
108
    def _get_FTP(self):
109
        """Return the ftplib.FTP instance for this object."""
2485.8.33 by Vincent Ladeuil
ftp connection sharing refactored. Tests passing.
110
        # Ensures that a connection is established
111
        connection = self._get_connection()
112
        if connection is None:
113
            # First connection ever
114
            connection, credentials = self._create_connection()
115
            self._set_connection(connection, credentials)
116
        return connection
117
4725.3.3 by Vincent Ladeuil
Fix test failure at the root without cleaning up ftp APPE.
118
    connection_class = ftplib.FTP
119
2485.8.33 by Vincent Ladeuil
ftp connection sharing refactored. Tests passing.
120
    def _create_connection(self, credentials=None):
121
        """Create a new connection with the provided credentials.
122
123
        :param credentials: The credentials needed to establish the connection.
124
125
        :return: The created connection and its associated credentials.
126
3777.1.1 by Aaron Bentley
Use auth.conf for sftp
127
        The input credentials are only the password as it may have been
128
        entered interactively by the user and may be different from the one
129
        provided in base url at transport creation time.  The returned
130
        credentials are username, password.
2485.8.33 by Vincent Ladeuil
ftp connection sharing refactored. Tests passing.
131
        """
132
        if credentials is None:
2900.2.15 by Vincent Ladeuil
AuthenticationConfig can be queried for logins too (first step).
133
            user, password = self._user, self._password
2485.8.33 by Vincent Ladeuil
ftp connection sharing refactored. Tests passing.
134
        else:
2900.2.15 by Vincent Ladeuil
AuthenticationConfig can be queried for logins too (first step).
135
            user, password = credentials
136
137
        auth = config.AuthenticationConfig()
138
        if user is None:
4304.2.1 by Vincent Ladeuil
Fix bug #367726 by reverting some default user handling introduced
139
            user = auth.get_user('ftp', self._host, port=self._port,
140
                                 default=getpass.getuser())
2485.8.33 by Vincent Ladeuil
ftp connection sharing refactored. Tests passing.
141
        mutter("Constructing FTP instance against %r" %
2900.2.15 by Vincent Ladeuil
AuthenticationConfig can be queried for logins too (first step).
142
               ((self._host, self._port, user, '********',
2485.8.33 by Vincent Ladeuil
ftp connection sharing refactored. Tests passing.
143
                self.is_active),))
144
        try:
4725.3.3 by Vincent Ladeuil
Fix test failure at the root without cleaning up ftp APPE.
145
            connection = self.connection_class()
2485.8.33 by Vincent Ladeuil
ftp connection sharing refactored. Tests passing.
146
            connection.connect(host=self._host, port=self._port)
4725.3.3 by Vincent Ladeuil
Fix test failure at the root without cleaning up ftp APPE.
147
            self._login(connection, auth, user, password)
2485.8.33 by Vincent Ladeuil
ftp connection sharing refactored. Tests passing.
148
            connection.set_pasv(not self.is_active)
3508.1.4 by Vincent Ladeuil
Ensure that ftp transport are always in binary mode.
149
            # binary mode is the default
150
            connection.voidcmd('TYPE I')
3021.1.2 by Vincent Ladeuil
Fix bug #164567 by catching connection errors.
151
        except socket.error, e:
3021.1.4 by Vincent Ladeuil
Use the right execption.
152
            raise errors.SocketConnectionError(self._host, self._port,
153
                                               msg='Unable to connect to',
154
                                               orig_error= e)
2485.8.33 by Vincent Ladeuil
ftp connection sharing refactored. Tests passing.
155
        except ftplib.error_perm, e:
156
            raise errors.TransportError(msg="Error setting up connection:"
157
                                        " %s" % str(e), orig_error=e)
2900.2.15 by Vincent Ladeuil
AuthenticationConfig can be queried for logins too (first step).
158
        return connection, (user, password)
2485.8.33 by Vincent Ladeuil
ftp connection sharing refactored. Tests passing.
159
4725.3.3 by Vincent Ladeuil
Fix test failure at the root without cleaning up ftp APPE.
160
    def _login(self, connection, auth, user, password):
161
        # '' is a valid password
162
        if user and user != 'anonymous' and password is None:
163
            password = auth.get_password('ftp', self._host,
164
                                         user, port=self._port)
165
        connection.login(user=user, passwd=password)
166
2485.8.33 by Vincent Ladeuil
ftp connection sharing refactored. Tests passing.
167
    def _reconnect(self):
168
        """Create a new connection with the previously used credentials"""
2917.1.2 by Vincent Ladeuil
Fix typo in ftp.py making the reconnection fail on temporary errors (154259).
169
        credentials = self._get_credentials()
2485.8.33 by Vincent Ladeuil
ftp connection sharing refactored. Tests passing.
170
        connection, credentials = self._create_connection(credentials)
171
        self._set_connection(connection, credentials)
172
5247.2.12 by Vincent Ladeuil
Ensure that all transports close their underlying connection.
173
    def disconnect(self):
174
        connection = self._get_connection()
175
        if connection is not None:
176
            connection.close()
177
5060.3.1 by Martin Pool
Handle "Directory not empty" from ftp as DirectoryNotEmpty.
178
    def _translate_ftp_error(self, err, path, extra=None,
2485.8.33 by Vincent Ladeuil
ftp connection sharing refactored. Tests passing.
179
                              unknown_exc=FtpPathError):
5060.3.1 by Martin Pool
Handle "Directory not empty" from ftp as DirectoryNotEmpty.
180
        """Try to translate an ftplib exception to a bzrlib exception.
1707.3.19 by John Arbash Meinel
For some reason after RNTO we get a failure for has()
181
182
        :param err: The error to translate into a bzr error
183
        :param path: The path which had problems
184
        :param extra: Extra information which can be included
185
        :param unknown_exc: If None, we will just raise the original exception
186
                    otherwise we raise unknown_exc(path, extra=extra)
187
        """
5060.3.1 by Martin Pool
Handle "Directory not empty" from ftp as DirectoryNotEmpty.
188
        # ftp error numbers are very generic, like "451: Requested action aborted,
189
        # local error in processing" so unfortunately we have to match by
190
        # strings.
1707.3.10 by John Arbash Meinel
Getting the append() tests to pass, and adding better error translation. These are probably dependent on the ftp test server, but for now it works.
191
        s = str(err).lower()
192
        if not extra:
193
            extra = str(err)
1707.3.25 by John Arbash Meinel
Setting some default exception classes, which can help a lot.
194
        else:
195
            extra += ': ' + str(err)
1707.3.10 by John Arbash Meinel
Getting the append() tests to pass, and adding better error translation. These are probably dependent on the ftp test server, but for now it works.
196
        if ('no such file' in s
1707.3.13 by John Arbash Meinel
Changing raise Error to raise errors.Error
197
            or 'could not open' in s
198
            or 'no such dir' in s
2338.1.1 by John Arbash Meinel
(Arnaud Fontaine) Compatibility with vsftpd error messages.
199
            or 'could not create file' in s # vsftpd
2570.1.1 by John Arbash Meinel
(John Arbash Meinel) A couple small updates for pushing over ftp.
200
            or 'file doesn\'t exist' in s
3315.1.1 by Christian Tschabuschnig
support vsftp (#129786)
201
            or 'rnfr command failed.' in s # vsftpd RNFR reply if file not found
2885.3.1 by Gary van der Merwe
Correctly detect a NoSuchFile when using a filezilla server.
202
            or 'file/directory not found' in s # filezilla server
3382.1.1 by Ian Clatworthy
commit now works with Microsoft FTP (Andreas Deininger)
203
            # Microsoft FTP-Service RNFR reply if file not found
204
            or (s.startswith('550 ') and 'unable to rename to' in extra)
5377.3.1 by Martin Pool
Better FTP warning messages and response parsing
205
            # if containing directory doesn't exist, suggested by
5552.1.4 by Vincent Ladeuil
Fix some new edge references.
206
            # <https://bugs.launchpad.net/bzr/+bug/224373>
5377.3.1 by Martin Pool
Better FTP warning messages and response parsing
207
            or (s.startswith('550 ') and "can't find folder" in s)
1707.3.13 by John Arbash Meinel
Changing raise Error to raise errors.Error
208
            ):
209
            raise errors.NoSuchFile(path, extra=extra)
5060.3.1 by Martin Pool
Handle "Directory not empty" from ftp as DirectoryNotEmpty.
210
        elif ('file exists' in s):
1707.3.15 by John Arbash Meinel
Handle trying to list a file instead of a dir
211
            raise errors.FileExists(path, extra=extra)
5060.3.1 by Martin Pool
Handle "Directory not empty" from ftp as DirectoryNotEmpty.
212
        elif ('not a directory' in s):
1707.3.15 by John Arbash Meinel
Handle trying to list a file instead of a dir
213
            raise errors.PathError(path, extra=extra)
5060.3.1 by Martin Pool
Handle "Directory not empty" from ftp as DirectoryNotEmpty.
214
        elif 'directory not empty' in s:
215
            raise errors.DirectoryNotEmpty(path, extra=extra)
1707.3.15 by John Arbash Meinel
Handle trying to list a file instead of a dir
216
1707.3.18 by John Arbash Meinel
Fix error handling for ftp.put()
217
        mutter('unable to understand error for path: %s: %s', path, err)
1707.3.19 by John Arbash Meinel
For some reason after RNTO we get a failure for has()
218
219
        if unknown_exc:
220
            raise unknown_exc(path, extra=extra)
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
221
        # TODO: jam 20060516 Consider re-raising the error wrapped in
1707.3.10 by John Arbash Meinel
Getting the append() tests to pass, and adding better error translation. These are probably dependent on the ftp test server, but for now it works.
222
        #       something like TransportError, but this loses the traceback
1707.3.15 by John Arbash Meinel
Handle trying to list a file instead of a dir
223
        #       Also, 'sftp' has a generic 'Failure' mode, which we use failure_exc
224
        #       to handle. Consider doing something like that here.
1707.3.10 by John Arbash Meinel
Getting the append() tests to pass, and adding better error translation. These are probably dependent on the ftp test server, but for now it works.
225
        #raise TransportError(msg='Error for path: %s' % (path,), orig_error=e)
1707.3.15 by John Arbash Meinel
Handle trying to list a file instead of a dir
226
        raise
1707.3.10 by John Arbash Meinel
Getting the append() tests to pass, and adding better error translation. These are probably dependent on the ftp test server, but for now it works.
227
1185.36.4 by Daniel Silverstone
Add FTP transport
228
    def has(self, relpath):
1707.3.16 by John Arbash Meinel
Overloading cmd_size and cmd_mkd so that the test suite can handle directories.
229
        """Does the target location exist?"""
230
        # FIXME jam 20060516 We *do* ask about directories in the test suite
231
        #       We don't seem to in the actual codebase
232
        # XXX: I assume we're never asked has(dirname) and thus I use
233
        # the FTP size command and assume that if it doesn't raise,
234
        # all is good.
2485.8.22 by Vincent Ladeuil
Finish ftp refactoring. Test suite passing.
235
        abspath = self._remote_path(relpath)
1185.36.4 by Daniel Silverstone
Add FTP transport
236
        try:
237
            f = self._get_FTP()
1707.3.19 by John Arbash Meinel
For some reason after RNTO we get a failure for has()
238
            mutter('FTP has check: %s => %s', relpath, abspath)
1707.3.16 by John Arbash Meinel
Overloading cmd_size and cmd_mkd so that the test suite can handle directories.
239
            s = f.size(abspath)
240
            mutter("FTP has: %s", abspath)
1185.36.4 by Daniel Silverstone
Add FTP transport
241
            return True
1707.3.16 by John Arbash Meinel
Overloading cmd_size and cmd_mkd so that the test suite can handle directories.
242
        except ftplib.error_perm, e:
243
            if ('is a directory' in str(e).lower()):
244
                mutter("FTP has dir: %s: %s", abspath, e)
245
                return True
246
            mutter("FTP has not: %s: %s", abspath, e)
1185.36.4 by Daniel Silverstone
Add FTP transport
247
            return False
248
5390.2.1 by Alexander Belchenko
`decode` parameter to get() method in FtpTransport and GioTransport classes is deprecated.
249
    def get(self, relpath, decode=DEPRECATED_PARAMETER, retries=0):
1185.36.4 by Daniel Silverstone
Add FTP transport
250
        """Get the file at the given relative path.
251
252
        :param relpath: The relative path to the file
1185.72.9 by Matthieu Moy
suggestions by jam and robertc
253
        :param retries: Number of retries after temporary failures so far
254
                        for this operation.
1185.36.4 by Daniel Silverstone
Add FTP transport
255
256
        We're meant to return a file-like object which bzr will
257
        then read from. For now we do this via the magic of StringIO
258
        """
5390.2.1 by Alexander Belchenko
`decode` parameter to get() method in FtpTransport and GioTransport classes is deprecated.
259
        if deprecated_passed(decode):
260
            warn(deprecated_in((2,3,0)) %
261
                 '"decode" parameter to FtpTransport.get()',
262
                 DeprecationWarning, stacklevel=2)
1185.36.4 by Daniel Silverstone
Add FTP transport
263
        try:
2485.8.22 by Vincent Ladeuil
Finish ftp refactoring. Test suite passing.
264
            mutter("FTP get: %s", self._remote_path(relpath))
1185.36.4 by Daniel Silverstone
Add FTP transport
265
            f = self._get_FTP()
266
            ret = StringIO()
2485.8.22 by Vincent Ladeuil
Finish ftp refactoring. Test suite passing.
267
            f.retrbinary('RETR '+self._remote_path(relpath), ret.write, 8192)
1185.36.4 by Daniel Silverstone
Add FTP transport
268
            ret.seek(0)
269
            return ret
270
        except ftplib.error_perm, e:
1707.3.13 by John Arbash Meinel
Changing raise Error to raise errors.Error
271
            raise errors.NoSuchFile(self.abspath(relpath), extra=str(e))
1185.72.8 by Matthieu Moy
try to solve the problem of ftp servers closing the connection
272
        except ftplib.error_temp, e:
1185.72.15 by Matthieu Moy
better error messages on ftp failures
273
            if retries > _number_of_retries:
1707.3.13 by John Arbash Meinel
Changing raise Error to raise errors.Error
274
                raise errors.TransportError(msg="FTP temporary error during GET %s. Aborting."
1185.72.15 by Matthieu Moy
better error messages on ftp failures
275
                                     % self.abspath(relpath),
276
                                     orig_error=e)
1185.72.8 by Matthieu Moy
try to solve the problem of ftp servers closing the connection
277
            else:
1707.3.8 by John Arbash Meinel
[patch] Alexandre Saint: enable append support for ftp
278
                warning("FTP temporary error: %s. Retrying.", str(e))
2485.8.33 by Vincent Ladeuil
ftp connection sharing refactored. Tests passing.
279
                self._reconnect()
1185.72.8 by Matthieu Moy
try to solve the problem of ftp servers closing the connection
280
                return self.get(relpath, decode, retries+1)
1185.72.15 by Matthieu Moy
better error messages on ftp failures
281
        except EOFError, e:
1185.72.9 by Matthieu Moy
suggestions by jam and robertc
282
            if retries > _number_of_retries:
1707.3.13 by John Arbash Meinel
Changing raise Error to raise errors.Error
283
                raise errors.TransportError("FTP control connection closed during GET %s."
1185.72.15 by Matthieu Moy
better error messages on ftp failures
284
                                     % self.abspath(relpath),
285
                                     orig_error=e)
1185.72.8 by Matthieu Moy
try to solve the problem of ftp servers closing the connection
286
            else:
1185.72.9 by Matthieu Moy
suggestions by jam and robertc
287
                warning("FTP control connection closed. Trying to reopen.")
1185.72.14 by Matthieu Moy
Sleep between retries when the connection closes
288
                time.sleep(_sleep_between_retries)
2485.8.33 by Vincent Ladeuil
ftp connection sharing refactored. Tests passing.
289
                self._reconnect()
1185.72.8 by Matthieu Moy
try to solve the problem of ftp servers closing the connection
290
                return self.get(relpath, decode, retries+1)
1185.36.4 by Daniel Silverstone
Add FTP transport
291
1955.3.6 by John Arbash Meinel
Lots of deprecation warnings, but no errors
292
    def put_file(self, relpath, fp, mode=None, retries=0):
1185.36.4 by Daniel Silverstone
Add FTP transport
293
        """Copy the file-like or string object into the location.
294
295
        :param relpath: Location to put the contents, relative to base.
1185.72.13 by Matthieu Moy
Make ftp put atomic
296
        :param fp:       File-like or string object.
1185.72.9 by Matthieu Moy
suggestions by jam and robertc
297
        :param retries: Number of retries after temporary failures so far
298
                        for this operation.
299
1551.15.2 by Aaron Bentley
FTP does not require atomic rename. Fixes #89436
300
        TODO: jam 20051215 ftp as a protocol seems to support chmod, but
301
        ftplib does not
1185.36.4 by Daniel Silverstone
Add FTP transport
302
        """
2485.8.22 by Vincent Ladeuil
Finish ftp refactoring. Test suite passing.
303
        abspath = self._remote_path(relpath)
1707.3.18 by John Arbash Meinel
Fix error handling for ftp.put()
304
        tmp_abspath = '%s.tmp.%.9f.%d.%d' % (abspath, time.time(),
1185.72.13 by Matthieu Moy
Make ftp put atomic
305
                        os.getpid(), random.randint(0,0x7FFFFFFF))
2745.5.2 by Robert Collins
* ``bzrlib.transport.Transport.put_file`` now returns the number of bytes
306
        bytes = None
1963.2.6 by Robey Pointer
pychecker is on crack; go back to using 'is None'.
307
        if getattr(fp, 'read', None) is None:
2745.5.2 by Robert Collins
* ``bzrlib.transport.Transport.put_file`` now returns the number of bytes
308
            # hand in a string IO
309
            bytes = fp
310
            fp = StringIO(bytes)
311
        else:
312
            # capture the byte count; .read() may be read only so
313
            # decorate it.
314
            class byte_counter(object):
315
                def __init__(self, fp):
316
                    self.fp = fp
317
                    self.counted_bytes = 0
318
                def read(self, count):
319
                    result = self.fp.read(count)
320
                    self.counted_bytes += len(result)
321
                    return result
322
            fp = byte_counter(fp)
1185.36.4 by Daniel Silverstone
Add FTP transport
323
        try:
1707.3.18 by John Arbash Meinel
Fix error handling for ftp.put()
324
            mutter("FTP put: %s", abspath)
1185.36.4 by Daniel Silverstone
Add FTP transport
325
            f = self._get_FTP()
1185.72.13 by Matthieu Moy
Make ftp put atomic
326
            try:
327
                f.storbinary('STOR '+tmp_abspath, fp)
1551.15.2 by Aaron Bentley
FTP does not require atomic rename. Fixes #89436
328
                self._rename_and_overwrite(tmp_abspath, abspath, f)
3508.1.1 by Vincent Ladeuil
Fix ftp transport so that it handles the 'mode' parameter when provided.
329
                self._setmode(relpath, mode)
2745.5.2 by Robert Collins
* ``bzrlib.transport.Transport.put_file`` now returns the number of bytes
330
                if bytes is not None:
331
                    return len(bytes)
332
                else:
333
                    return fp.counted_bytes
5377.3.1 by Martin Pool
Better FTP warning messages and response parsing
334
            except (ftplib.error_temp, EOFError), e:
335
                warning("Failure during ftp PUT of %s: %s. Deleting temporary file."
336
                    % (tmp_abspath, e, ))
1185.72.13 by Matthieu Moy
Make ftp put atomic
337
                try:
338
                    f.delete(tmp_abspath)
339
                except:
1707.3.8 by John Arbash Meinel
[patch] Alexandre Saint: enable append support for ftp
340
                    warning("Failed to delete temporary file on the"
341
                            " server.\nFile: %s", tmp_abspath)
1185.72.13 by Matthieu Moy
Make ftp put atomic
342
                    raise e
343
                raise
1185.36.4 by Daniel Silverstone
Add FTP transport
344
        except ftplib.error_perm, e:
5060.3.1 by Martin Pool
Handle "Directory not empty" from ftp as DirectoryNotEmpty.
345
            self._translate_ftp_error(e, abspath, extra='could not store',
2570.1.1 by John Arbash Meinel
(John Arbash Meinel) A couple small updates for pushing over ftp.
346
                                       unknown_exc=errors.NoSuchFile)
1185.72.8 by Matthieu Moy
try to solve the problem of ftp servers closing the connection
347
        except ftplib.error_temp, e:
1185.72.9 by Matthieu Moy
suggestions by jam and robertc
348
            if retries > _number_of_retries:
5377.3.1 by Martin Pool
Better FTP warning messages and response parsing
349
                raise errors.TransportError(
350
                    "FTP temporary error during PUT %s: %s. Aborting."
351
                    % (self.abspath(relpath), e), orig_error=e)
1185.72.8 by Matthieu Moy
try to solve the problem of ftp servers closing the connection
352
            else:
1707.3.8 by John Arbash Meinel
[patch] Alexandre Saint: enable append support for ftp
353
                warning("FTP temporary error: %s. Retrying.", str(e))
2485.8.33 by Vincent Ladeuil
ftp connection sharing refactored. Tests passing.
354
                self._reconnect()
1955.3.6 by John Arbash Meinel
Lots of deprecation warnings, but no errors
355
                self.put_file(relpath, fp, mode, retries+1)
1185.72.9 by Matthieu Moy
suggestions by jam and robertc
356
        except EOFError:
1185.72.10 by Matthieu Moy
One 1 -> _number_of_retries was missing
357
            if retries > _number_of_retries:
1707.3.13 by John Arbash Meinel
Changing raise Error to raise errors.Error
358
                raise errors.TransportError("FTP control connection closed during PUT %s."
1185.72.15 by Matthieu Moy
better error messages on ftp failures
359
                                     % self.abspath(relpath), orig_error=e)
1185.72.8 by Matthieu Moy
try to solve the problem of ftp servers closing the connection
360
            else:
1185.72.14 by Matthieu Moy
Sleep between retries when the connection closes
361
                warning("FTP control connection closed. Trying to reopen.")
362
                time.sleep(_sleep_between_retries)
2485.8.33 by Vincent Ladeuil
ftp connection sharing refactored. Tests passing.
363
                self._reconnect()
1955.3.6 by John Arbash Meinel
Lots of deprecation warnings, but no errors
364
                self.put_file(relpath, fp, mode, retries+1)
1185.72.8 by Matthieu Moy
try to solve the problem of ftp servers closing the connection
365
1185.58.2 by John Arbash Meinel
Added mode to the appropriate transport functions, and tests to make sure they work.
366
    def mkdir(self, relpath, mode=None):
1185.36.4 by Daniel Silverstone
Add FTP transport
367
        """Create a directory at the given path."""
2485.8.22 by Vincent Ladeuil
Finish ftp refactoring. Test suite passing.
368
        abspath = self._remote_path(relpath)
1185.36.4 by Daniel Silverstone
Add FTP transport
369
        try:
1707.3.14 by John Arbash Meinel
Cleanup mkdir and rmdir error handling.
370
            mutter("FTP mkd: %s", abspath)
1185.36.4 by Daniel Silverstone
Add FTP transport
371
            f = self._get_FTP()
5050.23.2 by Martin Pool
Narrower try/except scope for ftp reply 250 from mkd
372
            try:
373
                f.mkd(abspath)
374
            except ftplib.error_reply, e:
375
                # <https://bugs.launchpad.net/bzr/+bug/224373> Microsoft FTP
376
                # server returns "250 Directory created." which is kind of
377
                # reasonable, 250 meaning "requested file action OK", but not what
378
                # Python's ftplib expects.
379
                if e[0][:3] == '250':
380
                    pass
381
                else:
382
                    raise
3508.1.1 by Vincent Ladeuil
Fix ftp transport so that it handles the 'mode' parameter when provided.
383
            self._setmode(relpath, mode)
1185.36.4 by Daniel Silverstone
Add FTP transport
384
        except ftplib.error_perm, e:
5060.3.1 by Martin Pool
Handle "Directory not empty" from ftp as DirectoryNotEmpty.
385
            self._translate_ftp_error(e, abspath,
1707.3.25 by John Arbash Meinel
Setting some default exception classes, which can help a lot.
386
                unknown_exc=errors.FileExists)
1185.36.4 by Daniel Silverstone
Add FTP transport
387
2671.3.9 by Robert Collins
Review feedback and fix VFat emulated transports to not claim to have unix permissions.
388
    def open_write_stream(self, relpath, mode=None):
389
        """See Transport.open_write_stream."""
2671.3.6 by Robert Collins
Review feedback.
390
        self.put_bytes(relpath, "", mode)
391
        result = AppendBasedFileStream(self, relpath)
392
        _file_streams[self.abspath(relpath)] = result
393
        return result
2671.3.2 by Robert Collins
Start open_file_stream logic.
394
2671.3.1 by Robert Collins
* New method ``bzrlib.transport.Transport.get_recommended_page_size``.
395
    def recommended_page_size(self):
396
        """See Transport.recommended_page_size().
397
398
        For FTP we suggest a large page size to reduce the overhead
399
        introduced by latency.
400
        """
401
        return 64 * 1024
402
1658.1.1 by Martin Pool
Fix FTP push with metadir format (Alexandre Saint)
403
    def rmdir(self, rel_path):
404
        """Delete the directory at rel_path"""
2485.8.22 by Vincent Ladeuil
Finish ftp refactoring. Test suite passing.
405
        abspath = self._remote_path(rel_path)
1658.1.1 by Martin Pool
Fix FTP push with metadir format (Alexandre Saint)
406
        try:
1707.3.14 by John Arbash Meinel
Cleanup mkdir and rmdir error handling.
407
            mutter("FTP rmd: %s", abspath)
1551.15.4 by Aaron Bentley
Revert now-unnecessary changes
408
            f = self._get_FTP()
1707.3.14 by John Arbash Meinel
Cleanup mkdir and rmdir error handling.
409
            f.rmd(abspath)
1658.1.1 by Martin Pool
Fix FTP push with metadir format (Alexandre Saint)
410
        except ftplib.error_perm, e:
5060.3.1 by Martin Pool
Handle "Directory not empty" from ftp as DirectoryNotEmpty.
411
            self._translate_ftp_error(e, abspath, unknown_exc=errors.PathError)
1658.1.1 by Martin Pool
Fix FTP push with metadir format (Alexandre Saint)
412
1955.3.15 by John Arbash Meinel
Deprecate 'Transport.append' in favor of Transport.append_file or Transport.append_bytes
413
    def append_file(self, relpath, f, mode=None):
1185.36.4 by Daniel Silverstone
Add FTP transport
414
        """Append the text in the file-like object into the final
415
        location.
416
        """
4725.3.3 by Vincent Ladeuil
Fix test failure at the root without cleaning up ftp APPE.
417
        text = f.read()
2485.8.22 by Vincent Ladeuil
Finish ftp refactoring. Test suite passing.
418
        abspath = self._remote_path(relpath)
1707.3.8 by John Arbash Meinel
[patch] Alexandre Saint: enable append support for ftp
419
        if self.has(relpath):
420
            ftp = self._get_FTP()
1707.3.19 by John Arbash Meinel
For some reason after RNTO we get a failure for has()
421
            result = ftp.size(abspath)
1707.3.8 by John Arbash Meinel
[patch] Alexandre Saint: enable append support for ftp
422
        else:
423
            result = 0
424
4537.1.1 by Nils Durner
fall back to GET & PUT if APPE doesn't work
425
        if self._has_append:
426
            mutter("FTP appe to %s", abspath)
4725.3.3 by Vincent Ladeuil
Fix test failure at the root without cleaning up ftp APPE.
427
            self._try_append(relpath, text, mode)
4537.1.1 by Nils Durner
fall back to GET & PUT if APPE doesn't work
428
        else:
4725.3.3 by Vincent Ladeuil
Fix test failure at the root without cleaning up ftp APPE.
429
            self._fallback_append(relpath, text, mode)
1707.3.8 by John Arbash Meinel
[patch] Alexandre Saint: enable append support for ftp
430
431
        return result
432
4725.3.3 by Vincent Ladeuil
Fix test failure at the root without cleaning up ftp APPE.
433
    def _try_append(self, relpath, text, mode=None, retries=0):
1707.3.8 by John Arbash Meinel
[patch] Alexandre Saint: enable append support for ftp
434
        """Try repeatedly to append the given text to the file at relpath.
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
435
1707.3.8 by John Arbash Meinel
[patch] Alexandre Saint: enable append support for ftp
436
        This is a recursive function. On errors, it will be called until the
437
        number of retries is exceeded.
438
        """
439
        try:
2485.8.22 by Vincent Ladeuil
Finish ftp refactoring. Test suite passing.
440
            abspath = self._remote_path(relpath)
1707.3.8 by John Arbash Meinel
[patch] Alexandre Saint: enable append support for ftp
441
            mutter("FTP appe (try %d) to %s", retries, abspath)
442
            ftp = self._get_FTP()
4725.3.3 by Vincent Ladeuil
Fix test failure at the root without cleaning up ftp APPE.
443
            cmd = "APPE %s" % abspath
444
            conn = ftp.transfercmd(cmd)
445
            conn.sendall(text)
446
            conn.close()
3508.1.1 by Vincent Ladeuil
Fix ftp transport so that it handles the 'mode' parameter when provided.
447
            self._setmode(relpath, mode)
4725.3.3 by Vincent Ladeuil
Fix test failure at the root without cleaning up ftp APPE.
448
            ftp.getresp()
1707.3.8 by John Arbash Meinel
[patch] Alexandre Saint: enable append support for ftp
449
        except ftplib.error_perm, e:
4537.1.4 by Nils Durner
use the fallback mechanism only if the server really lacks the APPE command
450
            # Check whether the command is not supported (reply code 502)
451
            if str(e).startswith('502 '):
4725.3.3 by Vincent Ladeuil
Fix test failure at the root without cleaning up ftp APPE.
452
                warning("FTP server does not support file appending natively. "
453
                        "Performance may be severely degraded! (%s)", e)
4537.1.4 by Nils Durner
use the fallback mechanism only if the server really lacks the APPE command
454
                self._has_append = False
4725.3.3 by Vincent Ladeuil
Fix test failure at the root without cleaning up ftp APPE.
455
                self._fallback_append(relpath, text, mode)
4537.1.4 by Nils Durner
use the fallback mechanism only if the server really lacks the APPE command
456
            else:
5060.3.1 by Martin Pool
Handle "Directory not empty" from ftp as DirectoryNotEmpty.
457
                self._translate_ftp_error(e, abspath, extra='error appending',
4537.1.4 by Nils Durner
use the fallback mechanism only if the server really lacks the APPE command
458
                    unknown_exc=errors.NoSuchFile)
1707.3.8 by John Arbash Meinel
[patch] Alexandre Saint: enable append support for ftp
459
        except ftplib.error_temp, e:
460
            if retries > _number_of_retries:
4725.3.1 by Vincent Ladeuil
Fix test failure and clean up ftp APPE.
461
                raise errors.TransportError(
462
                    "FTP temporary error during APPEND %s. Aborting."
463
                    % abspath, orig_error=e)
1707.3.8 by John Arbash Meinel
[patch] Alexandre Saint: enable append support for ftp
464
            else:
465
                warning("FTP temporary error: %s. Retrying.", str(e))
2485.8.33 by Vincent Ladeuil
ftp connection sharing refactored. Tests passing.
466
                self._reconnect()
4725.3.3 by Vincent Ladeuil
Fix test failure at the root without cleaning up ftp APPE.
467
                self._try_append(relpath, text, mode, retries+1)
1707.3.8 by John Arbash Meinel
[patch] Alexandre Saint: enable append support for ftp
468
4725.3.3 by Vincent Ladeuil
Fix test failure at the root without cleaning up ftp APPE.
469
    def _fallback_append(self, relpath, text, mode = None):
4537.1.1 by Nils Durner
fall back to GET & PUT if APPE doesn't work
470
        remote = self.get(relpath)
4725.3.1 by Vincent Ladeuil
Fix test failure and clean up ftp APPE.
471
        remote.seek(0, os.SEEK_END)
4725.3.3 by Vincent Ladeuil
Fix test failure at the root without cleaning up ftp APPE.
472
        remote.write(text)
4725.3.1 by Vincent Ladeuil
Fix test failure and clean up ftp APPE.
473
        remote.seek(0)
4537.1.1 by Nils Durner
fall back to GET & PUT if APPE doesn't work
474
        return self.put_file(relpath, remote, mode)
475
1707.3.8 by John Arbash Meinel
[patch] Alexandre Saint: enable append support for ftp
476
    def _setmode(self, relpath, mode):
477
        """Set permissions on a path.
478
479
        Only set permissions if the FTP server supports the 'SITE CHMOD'
480
        extension.
481
        """
3508.1.1 by Vincent Ladeuil
Fix ftp transport so that it handles the 'mode' parameter when provided.
482
        if mode:
483
            try:
484
                mutter("FTP site chmod: setting permissions to %s on %s",
3508.1.10 by Vincent Ladeuil
Start supporting pyftpdlib as an ftp test server.
485
                       oct(mode), self._remote_path(relpath))
3508.1.1 by Vincent Ladeuil
Fix ftp transport so that it handles the 'mode' parameter when provided.
486
                ftp = self._get_FTP()
487
                cmd = "SITE CHMOD %s %s" % (oct(mode),
488
                                            self._remote_path(relpath))
489
                ftp.sendcmd(cmd)
490
            except ftplib.error_perm, e:
491
                # Command probably not available on this server
492
                warning("FTP Could not set permissions to %s on %s. %s",
3508.1.10 by Vincent Ladeuil
Start supporting pyftpdlib as an ftp test server.
493
                        oct(mode), self._remote_path(relpath), str(e))
1185.36.4 by Daniel Silverstone
Add FTP transport
494
1707.3.11 by John Arbash Meinel
fixing more tests.
495
    # TODO: jam 20060516 I believe ftp allows you to tell an ftp server
496
    #       to copy something to another machine. And you may be able
497
    #       to give it its own address as the 'to' location.
498
    #       So implement a fancier 'copy()'
1185.36.4 by Daniel Silverstone
Add FTP transport
499
1551.15.3 by Aaron Bentley
Separate FtpTransport.rename from FtpTransport.move
500
    def rename(self, rel_from, rel_to):
2485.8.22 by Vincent Ladeuil
Finish ftp refactoring. Test suite passing.
501
        abs_from = self._remote_path(rel_from)
502
        abs_to = self._remote_path(rel_to)
1551.15.3 by Aaron Bentley
Separate FtpTransport.rename from FtpTransport.move
503
        mutter("FTP rename: %s => %s", abs_from, abs_to)
504
        f = self._get_FTP()
505
        return self._rename(abs_from, abs_to, f)
506
507
    def _rename(self, abs_from, abs_to, f):
508
        try:
509
            f.rename(abs_from, abs_to)
5060.3.2 by Martin Pool
Handle 451 indicating directory not empty from ftp servers
510
        except (ftplib.error_temp, ftplib.error_perm), e:
5060.3.1 by Martin Pool
Handle "Directory not empty" from ftp as DirectoryNotEmpty.
511
            self._translate_ftp_error(e, abs_from,
1551.15.3 by Aaron Bentley
Separate FtpTransport.rename from FtpTransport.move
512
                ': unable to rename to %r' % (abs_to))
513
1185.36.4 by Daniel Silverstone
Add FTP transport
514
    def move(self, rel_from, rel_to):
515
        """Move the item at rel_from to the location at rel_to"""
2485.8.22 by Vincent Ladeuil
Finish ftp refactoring. Test suite passing.
516
        abs_from = self._remote_path(rel_from)
517
        abs_to = self._remote_path(rel_to)
1185.36.4 by Daniel Silverstone
Add FTP transport
518
        try:
1707.3.19 by John Arbash Meinel
For some reason after RNTO we get a failure for has()
519
            mutter("FTP mv: %s => %s", abs_from, abs_to)
1185.36.4 by Daniel Silverstone
Add FTP transport
520
            f = self._get_FTP()
1551.15.2 by Aaron Bentley
FTP does not require atomic rename. Fixes #89436
521
            self._rename_and_overwrite(abs_from, abs_to, f)
1185.36.4 by Daniel Silverstone
Add FTP transport
522
        except ftplib.error_perm, e:
5060.3.1 by Martin Pool
Handle "Directory not empty" from ftp as DirectoryNotEmpty.
523
            self._translate_ftp_error(e, abs_from,
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
524
                extra='unable to rename to %r' % (rel_to,),
1707.3.19 by John Arbash Meinel
For some reason after RNTO we get a failure for has()
525
                unknown_exc=errors.PathError)
1185.36.4 by Daniel Silverstone
Add FTP transport
526
1551.15.2 by Aaron Bentley
FTP does not require atomic rename. Fixes #89436
527
    def _rename_and_overwrite(self, abs_from, abs_to, f):
528
        """Do a fancy rename on the remote server.
529
530
        Using the implementation provided by osutils.
531
        """
1551.15.3 by Aaron Bentley
Separate FtpTransport.rename from FtpTransport.move
532
        osutils.fancy_rename(abs_from, abs_to,
1551.15.4 by Aaron Bentley
Revert now-unnecessary changes
533
            rename_func=lambda p1, p2: self._rename(p1, p2, f),
534
            unlink_func=lambda p: self._delete(p, f))
1551.15.2 by Aaron Bentley
FTP does not require atomic rename. Fixes #89436
535
1185.36.4 by Daniel Silverstone
Add FTP transport
536
    def delete(self, relpath):
537
        """Delete the item at relpath"""
2485.8.22 by Vincent Ladeuil
Finish ftp refactoring. Test suite passing.
538
        abspath = self._remote_path(relpath)
1551.15.2 by Aaron Bentley
FTP does not require atomic rename. Fixes #89436
539
        f = self._get_FTP()
540
        self._delete(abspath, f)
541
542
    def _delete(self, abspath, f):
1185.36.4 by Daniel Silverstone
Add FTP transport
543
        try:
1707.3.11 by John Arbash Meinel
fixing more tests.
544
            mutter("FTP rm: %s", abspath)
545
            f.delete(abspath)
1185.36.4 by Daniel Silverstone
Add FTP transport
546
        except ftplib.error_perm, e:
5060.3.1 by Martin Pool
Handle "Directory not empty" from ftp as DirectoryNotEmpty.
547
            self._translate_ftp_error(e, abspath, 'error deleting',
1707.3.25 by John Arbash Meinel
Setting some default exception classes, which can help a lot.
548
                unknown_exc=errors.NoSuchFile)
1185.36.4 by Daniel Silverstone
Add FTP transport
549
2634.1.1 by Robert Collins
(robertc) Reinstate the accidentally backed out external_url patch.
550
    def external_url(self):
551
        """See bzrlib.transport.Transport.external_url."""
552
        # FTP URL's are externally usable.
553
        return self.base
554
1185.36.4 by Daniel Silverstone
Add FTP transport
555
    def listable(self):
556
        """See Transport.listable."""
557
        return True
558
559
    def list_dir(self, relpath):
560
        """See Transport.list_dir."""
2485.8.22 by Vincent Ladeuil
Finish ftp refactoring. Test suite passing.
561
        basepath = self._remote_path(relpath)
1959.2.5 by John Arbash Meinel
Small fixes in ftp.list_dir()
562
        mutter("FTP nlst: %s", basepath)
1959.2.1 by John Arbash Meinel
David Allouche: Make transports return escaped paths
563
        f = self._get_FTP()
1185.36.4 by Daniel Silverstone
Add FTP transport
564
        try:
3508.1.6 by Vincent Ladeuil
Fix python-2.4 compatibility.
565
            try:
566
                paths = f.nlst(basepath)
567
            except ftplib.error_perm, e:
5060.3.1 by Martin Pool
Handle "Directory not empty" from ftp as DirectoryNotEmpty.
568
                self._translate_ftp_error(e, relpath,
3508.1.6 by Vincent Ladeuil
Fix python-2.4 compatibility.
569
                                           extra='error with list_dir')
570
            except ftplib.error_temp, e:
571
                # xs4all's ftp server raises a 450 temp error when listing an
572
                # empty directory. Check for that and just return an empty list
573
                # in that case. See bug #215522
574
                if str(e).lower().startswith('450 no files found'):
575
                    mutter('FTP Server returned "%s" for nlst.'
576
                           ' Assuming it means empty directory',
577
                           str(e))
578
                    return []
579
                raise
3508.1.4 by Vincent Ladeuil
Ensure that ftp transport are always in binary mode.
580
        finally:
581
            # Restore binary mode as nlst switch to ascii mode to retrieve file
582
            # list
583
            f.voidcmd('TYPE I')
584
1959.2.1 by John Arbash Meinel
David Allouche: Make transports return escaped paths
585
        # If FTP.nlst returns paths prefixed by relpath, strip 'em
586
        if paths and paths[0].startswith(basepath):
587
            entries = [path[len(basepath)+1:] for path in paths]
588
        else:
589
            entries = paths
590
        # Remove . and .. if present
1959.2.5 by John Arbash Meinel
Small fixes in ftp.list_dir()
591
        return [urlutils.escape(entry) for entry in entries
592
                if entry not in ('.', '..')]
1185.36.4 by Daniel Silverstone
Add FTP transport
593
594
    def iter_files_recursive(self):
595
        """See Transport.iter_files_recursive.
596
597
        This is cargo-culted from the SFTP transport"""
598
        mutter("FTP iter_files_recursive")
599
        queue = list(self.list_dir("."))
600
        while queue:
1959.2.1 by John Arbash Meinel
David Allouche: Make transports return escaped paths
601
            relpath = queue.pop(0)
1185.36.4 by Daniel Silverstone
Add FTP transport
602
            st = self.stat(relpath)
603
            if stat.S_ISDIR(st.st_mode):
604
                for i, basename in enumerate(self.list_dir(relpath)):
605
                    queue.insert(i, relpath+"/"+basename)
606
            else:
607
                yield relpath
608
609
    def stat(self, relpath):
1707.3.13 by John Arbash Meinel
Changing raise Error to raise errors.Error
610
        """Return the stat information for a file."""
2485.8.22 by Vincent Ladeuil
Finish ftp refactoring. Test suite passing.
611
        abspath = self._remote_path(relpath)
1185.36.4 by Daniel Silverstone
Add FTP transport
612
        try:
1707.3.13 by John Arbash Meinel
Changing raise Error to raise errors.Error
613
            mutter("FTP stat: %s", abspath)
1185.36.4 by Daniel Silverstone
Add FTP transport
614
            f = self._get_FTP()
1707.3.13 by John Arbash Meinel
Changing raise Error to raise errors.Error
615
            return FtpStatResult(f, abspath)
1185.36.4 by Daniel Silverstone
Add FTP transport
616
        except ftplib.error_perm, e:
5060.3.1 by Martin Pool
Handle "Directory not empty" from ftp as DirectoryNotEmpty.
617
            self._translate_ftp_error(e, abspath, extra='error w/ stat')
1185.36.4 by Daniel Silverstone
Add FTP transport
618
619
    def lock_read(self, relpath):
620
        """Lock the given file for shared (read) access.
621
        :return: A lock object, which should be passed to Transport.unlock()
622
        """
623
        # The old RemoteBranch ignore lock for reading, so we will
624
        # continue that tradition and return a bogus lock object.
625
        class BogusLock(object):
626
            def __init__(self, path):
627
                self.path = path
628
            def unlock(self):
629
                pass
630
        return BogusLock(relpath)
631
632
    def lock_write(self, relpath):
633
        """Lock the given file for exclusive (write) access.
634
        WARNING: many transports do not support this, so trying avoid using it
635
636
        :return: A lock object, which should be passed to Transport.unlock()
637
        """
638
        return self.lock_read(relpath)
1530.1.11 by Robert Collins
Push the transport permutations list into each transport module allowing for automatic testing of new modules that are registered as transports.
639
640
641
def get_test_permutations():
642
    """Return the permutations to be used in testing."""
3508.1.7 by Vincent Ladeuil
Prepare test framework for pyftpdlib injection.
643
    from bzrlib.tests import ftp_server
3508.1.23 by Vincent Ladeuil
Fix as per Martin's review.
644
    return [(FtpTransport, ftp_server.FTPTestServer)]