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