/brz/remove-bazaar

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