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