~bzr-pqm/bzr/bzr.dev

2338.1.1 by John Arbash Meinel
(Arnaud Fontaine) Compatibility with vsftpd error messages.
1
# Copyright (C) 2005, 2006, 2007 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
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
"""Implementation of Transport over ftp.
17
18
Written by Daniel Silverstone <dsilvers@digital-scurf.org> with serious
19
cargo-culting from the sftp transport and the http transport.
20
21
It provides the ftp:// and aftp:// protocols where ftp:// is passive ftp
22
and aftp:// is active ftp. Most people will want passive ftp for traversing
23
NAT and other firewalls, so it's best to use it unless you explicitly want
24
active, in which case aftp:// will be your friend.
25
"""
26
27
from cStringIO import StringIO
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.
28
import errno
1185.36.4 by Daniel Silverstone
Add FTP transport
29
import ftplib
3021.1.2 by Vincent Ladeuil
Fix bug #164567 by catching connection errors.
30
import getpass
1530.1.11 by Robert Collins
Push the transport permutations list into each transport module allowing for automatic testing of new modules that are registered as transports.
31
import os
1551.15.2 by Aaron Bentley
FTP does not require atomic rename. Fixes #89436
32
import os.path
1185.36.4 by Daniel Silverstone
Add FTP transport
33
import urlparse
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
1185.72.13 by Matthieu Moy
Make ftp put atomic
37
import random
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
from warnings import warn
39
1948.1.7 by John Arbash Meinel
Fix FtpTransport so that it raises UnicodeError and skips the unicode path tests
40
from bzrlib import (
2900.2.5 by Vincent Ladeuil
ake ftp aware of authentication config.
41
    config,
1948.1.7 by John Arbash Meinel
Fix FtpTransport so that it raises UnicodeError and skips the unicode path tests
42
    errors,
1551.15.2 by Aaron Bentley
FTP does not require atomic rename. Fixes #89436
43
    osutils,
1948.1.7 by John Arbash Meinel
Fix FtpTransport so that it raises UnicodeError and skips the unicode path tests
44
    urlutils,
45
    )
46
from bzrlib.trace import mutter, warning
1707.3.4 by John Arbash Meinel
Moved most of sftp.split_url into a Transport function.
47
from bzrlib.transport import (
2671.3.6 by Robert Collins
Review feedback.
48
    AppendBasedFileStream,
2900.2.16 by Vincent Ladeuil
Make hhtp proxy aware of AuthenticationConfig (for password).
49
    ConnectedTransport,
2671.3.2 by Robert Collins
Start open_file_stream logic.
50
    _file_streams,
2900.2.16 by Vincent Ladeuil
Make hhtp proxy aware of AuthenticationConfig (for password).
51
    register_urlparse_netloc_protocol,
1707.3.4 by John Arbash Meinel
Moved most of sftp.split_url into a Transport function.
52
    Server,
53
    )
2413.1.1 by John Arbash Meinel
Implement TestCaseWithFTPServer using the new shiny Feature mechanism.
54
from bzrlib.transport.local import LocalURLServer
1707.3.24 by John Arbash Meinel
Add prompt for password for ftp.
55
import bzrlib.ui
1551.3.11 by Aaron Bentley
Merge from Robert
56
2900.2.16 by Vincent Ladeuil
Make hhtp proxy aware of AuthenticationConfig (for password).
57
58
register_urlparse_netloc_protocol('aftp')
1707.3.30 by John Arbash Meinel
Only load medusa if you are running the test suite.
59
1551.3.11 by Aaron Bentley
Merge from Robert
60
1707.3.25 by John Arbash Meinel
Setting some default exception classes, which can help a lot.
61
class FtpPathError(errors.PathError):
62
    """FTP failed for path: %(path)s%(extra)s"""
63
64
1185.36.4 by Daniel Silverstone
Add FTP transport
65
class FtpStatResult(object):
66
    def __init__(self, f, relpath):
67
        try:
68
            self.st_size = f.size(relpath)
69
            self.st_mode = stat.S_IFREG
70
        except ftplib.error_perm:
71
            pwd = f.pwd()
72
            try:
73
                f.cwd(relpath)
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."""
2485.8.23 by Vincent Ladeuil
Assert the accepted schemes for sftp and ftp.
92
        assert base.startswith('ftp://') or base.startswith('aftp://')
2485.8.59 by Vincent Ladeuil
Update from review comments.
93
        super(FtpTransport, self).__init__(base,
94
                                           _from_transport=_from_transport)
2485.8.25 by Vincent Ladeuil
Separate abspath from _remote_path, the intents are different.
95
        self._unqualified_scheme = 'ftp'
2485.8.22 by Vincent Ladeuil
Finish ftp refactoring. Test suite passing.
96
        if self._scheme == 'aftp':
97
            self.is_active = True
98
        else:
99
            self.is_active = False
1707.3.4 by John Arbash Meinel
Moved most of sftp.split_url into a Transport function.
100
1185.36.4 by Daniel Silverstone
Add FTP transport
101
    def _get_FTP(self):
102
        """Return the ftplib.FTP instance for this object."""
2485.8.33 by Vincent Ladeuil
ftp connection sharing refactored. Tests passing.
103
        # Ensures that a connection is established
104
        connection = self._get_connection()
105
        if connection is None:
106
            # First connection ever
107
            connection, credentials = self._create_connection()
108
            self._set_connection(connection, credentials)
109
        return connection
110
111
    def _create_connection(self, credentials=None):
112
        """Create a new connection with the provided credentials.
113
114
        :param credentials: The credentials needed to establish the connection.
115
116
        :return: The created connection and its associated credentials.
117
118
        The credentials are only the password as it may have been entered
119
        interactively by the user and may be different from the one provided
120
        in base url at transport creation time.
121
        """
122
        if credentials is None:
2900.2.15 by Vincent Ladeuil
AuthenticationConfig can be queried for logins too (first step).
123
            user, password = self._user, self._password
2485.8.33 by Vincent Ladeuil
ftp connection sharing refactored. Tests passing.
124
        else:
2900.2.15 by Vincent Ladeuil
AuthenticationConfig can be queried for logins too (first step).
125
            user, password = credentials
126
127
        auth = config.AuthenticationConfig()
128
        if user is None:
129
            user = auth.get_user('ftp', self._host, port=self._port)
130
            if user is None:
131
                # Default to local user
132
                user = getpass.getuser()
2485.8.33 by Vincent Ladeuil
ftp connection sharing refactored. Tests passing.
133
134
        mutter("Constructing FTP instance against %r" %
2900.2.15 by Vincent Ladeuil
AuthenticationConfig can be queried for logins too (first step).
135
               ((self._host, self._port, user, '********',
2485.8.33 by Vincent Ladeuil
ftp connection sharing refactored. Tests passing.
136
                self.is_active),))
137
        try:
138
            connection = ftplib.FTP()
139
            connection.connect(host=self._host, port=self._port)
2900.2.15 by Vincent Ladeuil
AuthenticationConfig can be queried for logins too (first step).
140
            if user and user != 'anonymous' and \
2790.1.1 by Vincent Ladeuil
Fix #137044 by prompting for a password if *none* is provided for ftp.
141
                    password is None: # '' is a valid password
2900.2.15 by Vincent Ladeuil
AuthenticationConfig can be queried for logins too (first step).
142
                password = auth.get_password('ftp', self._host, user,
2900.2.12 by Vincent Ladeuil
Since all schemes query AuthenticationConfig then prompt user, make that
143
                                             port=self._port)
2900.2.15 by Vincent Ladeuil
AuthenticationConfig can be queried for logins too (first step).
144
            connection.login(user=user, passwd=password)
2485.8.33 by Vincent Ladeuil
ftp connection sharing refactored. Tests passing.
145
            connection.set_pasv(not self.is_active)
3021.1.2 by Vincent Ladeuil
Fix bug #164567 by catching connection errors.
146
        except socket.error, e:
3021.1.4 by Vincent Ladeuil
Use the right execption.
147
            raise errors.SocketConnectionError(self._host, self._port,
148
                                               msg='Unable to connect to',
149
                                               orig_error= e)
2485.8.33 by Vincent Ladeuil
ftp connection sharing refactored. Tests passing.
150
        except ftplib.error_perm, e:
151
            raise errors.TransportError(msg="Error setting up connection:"
152
                                        " %s" % str(e), orig_error=e)
2900.2.15 by Vincent Ladeuil
AuthenticationConfig can be queried for logins too (first step).
153
        return connection, (user, password)
2485.8.33 by Vincent Ladeuil
ftp connection sharing refactored. Tests passing.
154
155
    def _reconnect(self):
156
        """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).
157
        credentials = self._get_credentials()
2485.8.33 by Vincent Ladeuil
ftp connection sharing refactored. Tests passing.
158
        connection, credentials = self._create_connection(credentials)
159
        self._set_connection(connection, credentials)
160
161
    def _translate_perm_error(self, err, path, extra=None,
162
                              unknown_exc=FtpPathError):
1707.3.19 by John Arbash Meinel
For some reason after RNTO we get a failure for has()
163
        """Try to translate an ftplib.error_perm exception.
164
165
        :param err: The error to translate into a bzr error
166
        :param path: The path which had problems
167
        :param extra: Extra information which can be included
168
        :param unknown_exc: If None, we will just raise the original exception
169
                    otherwise we raise unknown_exc(path, extra=extra)
170
        """
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.
171
        s = str(err).lower()
172
        if not extra:
173
            extra = str(err)
1707.3.25 by John Arbash Meinel
Setting some default exception classes, which can help a lot.
174
        else:
175
            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.
176
        if ('no such file' in s
1707.3.13 by John Arbash Meinel
Changing raise Error to raise errors.Error
177
            or 'could not open' in s
178
            or 'no such dir' in s
2338.1.1 by John Arbash Meinel
(Arnaud Fontaine) Compatibility with vsftpd error messages.
179
            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.
180
            or 'file doesn\'t exist' in s
2885.3.1 by Gary van der Merwe
Correctly detect a NoSuchFile when using a filezilla server.
181
            or 'file/directory not found' in s # filezilla server
1707.3.13 by John Arbash Meinel
Changing raise Error to raise errors.Error
182
            ):
183
            raise errors.NoSuchFile(path, extra=extra)
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.
184
        if ('file exists' in s):
1707.3.15 by John Arbash Meinel
Handle trying to list a file instead of a dir
185
            raise errors.FileExists(path, extra=extra)
186
        if ('not a directory' in s):
187
            raise errors.PathError(path, extra=extra)
188
1707.3.18 by John Arbash Meinel
Fix error handling for ftp.put()
189
        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()
190
191
        if unknown_exc:
192
            raise unknown_exc(path, extra=extra)
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
        # TODO: jam 20060516 Consider re-raising the error wrapped in 
194
        #       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
195
        #       Also, 'sftp' has a generic 'Failure' mode, which we use failure_exc
196
        #       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.
197
        #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
198
        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.
199
2485.8.22 by Vincent Ladeuil
Finish ftp refactoring. Test suite passing.
200
    def _remote_path(self, relpath):
1948.1.7 by John Arbash Meinel
Fix FtpTransport so that it raises UnicodeError and skips the unicode path tests
201
        # XXX: It seems that ftplib does not handle Unicode paths
2485.8.22 by Vincent Ladeuil
Finish ftp refactoring. Test suite passing.
202
        # at the same time, medusa won't handle utf8 paths So if
203
        # we .encode(utf8) here (see ConnectedTransport
204
        # implementation), then we get a Server failure.  while
205
        # if we use str(), we get a UnicodeError, and the test
206
        # suite just skips testing UnicodePaths.
207
        relative = str(urlutils.unescape(relpath))
208
        remote_path = self._combine_paths(self._path, relative)
209
        return remote_path
1185.36.4 by Daniel Silverstone
Add FTP transport
210
211
    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.
212
        """Does the target location exist?"""
213
        # FIXME jam 20060516 We *do* ask about directories in the test suite
214
        #       We don't seem to in the actual codebase
215
        # XXX: I assume we're never asked has(dirname) and thus I use
216
        # the FTP size command and assume that if it doesn't raise,
217
        # all is good.
2485.8.22 by Vincent Ladeuil
Finish ftp refactoring. Test suite passing.
218
        abspath = self._remote_path(relpath)
1185.36.4 by Daniel Silverstone
Add FTP transport
219
        try:
220
            f = self._get_FTP()
1707.3.19 by John Arbash Meinel
For some reason after RNTO we get a failure for has()
221
            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.
222
            s = f.size(abspath)
223
            mutter("FTP has: %s", abspath)
1185.36.4 by Daniel Silverstone
Add FTP transport
224
            return True
1707.3.16 by John Arbash Meinel
Overloading cmd_size and cmd_mkd so that the test suite can handle directories.
225
        except ftplib.error_perm, e:
226
            if ('is a directory' in str(e).lower()):
227
                mutter("FTP has dir: %s: %s", abspath, e)
228
                return True
229
            mutter("FTP has not: %s: %s", abspath, e)
1185.36.4 by Daniel Silverstone
Add FTP transport
230
            return False
231
2164.2.15 by Vincent Ladeuil
Http redirections are not followed by default. Do not use hints
232
    def get(self, relpath, decode=False, retries=0):
1185.36.4 by Daniel Silverstone
Add FTP transport
233
        """Get the file at the given relative path.
234
235
        :param relpath: The relative path to the file
1185.72.9 by Matthieu Moy
suggestions by jam and robertc
236
        :param retries: Number of retries after temporary failures so far
237
                        for this operation.
1185.36.4 by Daniel Silverstone
Add FTP transport
238
239
        We're meant to return a file-like object which bzr will
240
        then read from. For now we do this via the magic of StringIO
241
        """
1540.3.6 by Martin Pool
[merge] update from bzr.dev
242
        # TODO: decode should be deprecated
1185.36.4 by Daniel Silverstone
Add FTP transport
243
        try:
2485.8.22 by Vincent Ladeuil
Finish ftp refactoring. Test suite passing.
244
            mutter("FTP get: %s", self._remote_path(relpath))
1185.36.4 by Daniel Silverstone
Add FTP transport
245
            f = self._get_FTP()
246
            ret = StringIO()
2485.8.22 by Vincent Ladeuil
Finish ftp refactoring. Test suite passing.
247
            f.retrbinary('RETR '+self._remote_path(relpath), ret.write, 8192)
1185.36.4 by Daniel Silverstone
Add FTP transport
248
            ret.seek(0)
249
            return ret
250
        except ftplib.error_perm, e:
1707.3.13 by John Arbash Meinel
Changing raise Error to raise errors.Error
251
            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
252
        except ftplib.error_temp, e:
1185.72.15 by Matthieu Moy
better error messages on ftp failures
253
            if retries > _number_of_retries:
1707.3.13 by John Arbash Meinel
Changing raise Error to raise errors.Error
254
                raise errors.TransportError(msg="FTP temporary error during GET %s. Aborting."
1185.72.15 by Matthieu Moy
better error messages on ftp failures
255
                                     % self.abspath(relpath),
256
                                     orig_error=e)
1185.72.8 by Matthieu Moy
try to solve the problem of ftp servers closing the connection
257
            else:
1707.3.8 by John Arbash Meinel
[patch] Alexandre Saint: enable append support for ftp
258
                warning("FTP temporary error: %s. Retrying.", str(e))
2485.8.33 by Vincent Ladeuil
ftp connection sharing refactored. Tests passing.
259
                self._reconnect()
1185.72.8 by Matthieu Moy
try to solve the problem of ftp servers closing the connection
260
                return self.get(relpath, decode, retries+1)
1185.72.15 by Matthieu Moy
better error messages on ftp failures
261
        except EOFError, e:
1185.72.9 by Matthieu Moy
suggestions by jam and robertc
262
            if retries > _number_of_retries:
1707.3.13 by John Arbash Meinel
Changing raise Error to raise errors.Error
263
                raise errors.TransportError("FTP control connection closed during GET %s."
1185.72.15 by Matthieu Moy
better error messages on ftp failures
264
                                     % self.abspath(relpath),
265
                                     orig_error=e)
1185.72.8 by Matthieu Moy
try to solve the problem of ftp servers closing the connection
266
            else:
1185.72.9 by Matthieu Moy
suggestions by jam and robertc
267
                warning("FTP control connection closed. Trying to reopen.")
1185.72.14 by Matthieu Moy
Sleep between retries when the connection closes
268
                time.sleep(_sleep_between_retries)
2485.8.33 by Vincent Ladeuil
ftp connection sharing refactored. Tests passing.
269
                self._reconnect()
1185.72.8 by Matthieu Moy
try to solve the problem of ftp servers closing the connection
270
                return self.get(relpath, decode, retries+1)
1185.36.4 by Daniel Silverstone
Add FTP transport
271
1955.3.6 by John Arbash Meinel
Lots of deprecation warnings, but no errors
272
    def put_file(self, relpath, fp, mode=None, retries=0):
1185.36.4 by Daniel Silverstone
Add FTP transport
273
        """Copy the file-like or string object into the location.
274
275
        :param relpath: Location to put the contents, relative to base.
1185.72.13 by Matthieu Moy
Make ftp put atomic
276
        :param fp:       File-like or string object.
1185.72.9 by Matthieu Moy
suggestions by jam and robertc
277
        :param retries: Number of retries after temporary failures so far
278
                        for this operation.
279
1551.15.2 by Aaron Bentley
FTP does not require atomic rename. Fixes #89436
280
        TODO: jam 20051215 ftp as a protocol seems to support chmod, but
281
        ftplib does not
1185.36.4 by Daniel Silverstone
Add FTP transport
282
        """
2485.8.22 by Vincent Ladeuil
Finish ftp refactoring. Test suite passing.
283
        abspath = self._remote_path(relpath)
1707.3.18 by John Arbash Meinel
Fix error handling for ftp.put()
284
        tmp_abspath = '%s.tmp.%.9f.%d.%d' % (abspath, time.time(),
1185.72.13 by Matthieu Moy
Make ftp put atomic
285
                        os.getpid(), random.randint(0,0x7FFFFFFF))
2745.5.2 by Robert Collins
* ``bzrlib.transport.Transport.put_file`` now returns the number of bytes
286
        bytes = None
1963.2.6 by Robey Pointer
pychecker is on crack; go back to using 'is None'.
287
        if getattr(fp, 'read', None) is None:
2745.5.2 by Robert Collins
* ``bzrlib.transport.Transport.put_file`` now returns the number of bytes
288
            # hand in a string IO
289
            bytes = fp
290
            fp = StringIO(bytes)
291
        else:
292
            # capture the byte count; .read() may be read only so
293
            # decorate it.
294
            class byte_counter(object):
295
                def __init__(self, fp):
296
                    self.fp = fp
297
                    self.counted_bytes = 0
298
                def read(self, count):
299
                    result = self.fp.read(count)
300
                    self.counted_bytes += len(result)
301
                    return result
302
            fp = byte_counter(fp)
1185.36.4 by Daniel Silverstone
Add FTP transport
303
        try:
1707.3.18 by John Arbash Meinel
Fix error handling for ftp.put()
304
            mutter("FTP put: %s", abspath)
1185.36.4 by Daniel Silverstone
Add FTP transport
305
            f = self._get_FTP()
1185.72.13 by Matthieu Moy
Make ftp put atomic
306
            try:
307
                f.storbinary('STOR '+tmp_abspath, fp)
1551.15.2 by Aaron Bentley
FTP does not require atomic rename. Fixes #89436
308
                self._rename_and_overwrite(tmp_abspath, abspath, f)
2745.5.2 by Robert Collins
* ``bzrlib.transport.Transport.put_file`` now returns the number of bytes
309
                if bytes is not None:
310
                    return len(bytes)
311
                else:
312
                    return fp.counted_bytes
1185.72.13 by Matthieu Moy
Make ftp put atomic
313
            except (ftplib.error_temp,EOFError), e:
1185.72.15 by Matthieu Moy
better error messages on ftp failures
314
                warning("Failure during ftp PUT. Deleting temporary file.")
1185.72.13 by Matthieu Moy
Make ftp put atomic
315
                try:
316
                    f.delete(tmp_abspath)
317
                except:
1707.3.8 by John Arbash Meinel
[patch] Alexandre Saint: enable append support for ftp
318
                    warning("Failed to delete temporary file on the"
319
                            " server.\nFile: %s", tmp_abspath)
1185.72.13 by Matthieu Moy
Make ftp put atomic
320
                    raise e
321
                raise
1185.36.4 by Daniel Silverstone
Add FTP transport
322
        except ftplib.error_perm, e:
2570.1.1 by John Arbash Meinel
(John Arbash Meinel) A couple small updates for pushing over ftp.
323
            self._translate_perm_error(e, abspath, extra='could not store',
324
                                       unknown_exc=errors.NoSuchFile)
1185.72.8 by Matthieu Moy
try to solve the problem of ftp servers closing the connection
325
        except ftplib.error_temp, e:
1185.72.9 by Matthieu Moy
suggestions by jam and robertc
326
            if retries > _number_of_retries:
1707.3.13 by John Arbash Meinel
Changing raise Error to raise errors.Error
327
                raise errors.TransportError("FTP temporary error during PUT %s. Aborting."
1185.72.15 by Matthieu Moy
better error messages on ftp failures
328
                                     % self.abspath(relpath), orig_error=e)
1185.72.8 by Matthieu Moy
try to solve the problem of ftp servers closing the connection
329
            else:
1707.3.8 by John Arbash Meinel
[patch] Alexandre Saint: enable append support for ftp
330
                warning("FTP temporary error: %s. Retrying.", str(e))
2485.8.33 by Vincent Ladeuil
ftp connection sharing refactored. Tests passing.
331
                self._reconnect()
1955.3.6 by John Arbash Meinel
Lots of deprecation warnings, but no errors
332
                self.put_file(relpath, fp, mode, retries+1)
1185.72.9 by Matthieu Moy
suggestions by jam and robertc
333
        except EOFError:
1185.72.10 by Matthieu Moy
One 1 -> _number_of_retries was missing
334
            if retries > _number_of_retries:
1707.3.13 by John Arbash Meinel
Changing raise Error to raise errors.Error
335
                raise errors.TransportError("FTP control connection closed during PUT %s."
1185.72.15 by Matthieu Moy
better error messages on ftp failures
336
                                     % self.abspath(relpath), orig_error=e)
1185.72.8 by Matthieu Moy
try to solve the problem of ftp servers closing the connection
337
            else:
1185.72.14 by Matthieu Moy
Sleep between retries when the connection closes
338
                warning("FTP control connection closed. Trying to reopen.")
339
                time.sleep(_sleep_between_retries)
2485.8.33 by Vincent Ladeuil
ftp connection sharing refactored. Tests passing.
340
                self._reconnect()
1955.3.6 by John Arbash Meinel
Lots of deprecation warnings, but no errors
341
                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
342
1185.58.2 by John Arbash Meinel
Added mode to the appropriate transport functions, and tests to make sure they work.
343
    def mkdir(self, relpath, mode=None):
1185.36.4 by Daniel Silverstone
Add FTP transport
344
        """Create a directory at the given path."""
2485.8.22 by Vincent Ladeuil
Finish ftp refactoring. Test suite passing.
345
        abspath = self._remote_path(relpath)
1185.36.4 by Daniel Silverstone
Add FTP transport
346
        try:
1707.3.14 by John Arbash Meinel
Cleanup mkdir and rmdir error handling.
347
            mutter("FTP mkd: %s", abspath)
1185.36.4 by Daniel Silverstone
Add FTP transport
348
            f = self._get_FTP()
1707.3.14 by John Arbash Meinel
Cleanup mkdir and rmdir error handling.
349
            f.mkd(abspath)
1185.36.4 by Daniel Silverstone
Add FTP transport
350
        except ftplib.error_perm, e:
1707.3.25 by John Arbash Meinel
Setting some default exception classes, which can help a lot.
351
            self._translate_perm_error(e, abspath,
352
                unknown_exc=errors.FileExists)
1185.36.4 by Daniel Silverstone
Add FTP transport
353
2671.3.9 by Robert Collins
Review feedback and fix VFat emulated transports to not claim to have unix permissions.
354
    def open_write_stream(self, relpath, mode=None):
355
        """See Transport.open_write_stream."""
2671.3.6 by Robert Collins
Review feedback.
356
        self.put_bytes(relpath, "", mode)
357
        result = AppendBasedFileStream(self, relpath)
358
        _file_streams[self.abspath(relpath)] = result
359
        return result
2671.3.2 by Robert Collins
Start open_file_stream logic.
360
2671.3.1 by Robert Collins
* New method ``bzrlib.transport.Transport.get_recommended_page_size``.
361
    def recommended_page_size(self):
362
        """See Transport.recommended_page_size().
363
364
        For FTP we suggest a large page size to reduce the overhead
365
        introduced by latency.
366
        """
367
        return 64 * 1024
368
1658.1.1 by Martin Pool
Fix FTP push with metadir format (Alexandre Saint)
369
    def rmdir(self, rel_path):
370
        """Delete the directory at rel_path"""
2485.8.22 by Vincent Ladeuil
Finish ftp refactoring. Test suite passing.
371
        abspath = self._remote_path(rel_path)
1658.1.1 by Martin Pool
Fix FTP push with metadir format (Alexandre Saint)
372
        try:
1707.3.14 by John Arbash Meinel
Cleanup mkdir and rmdir error handling.
373
            mutter("FTP rmd: %s", abspath)
1551.15.4 by Aaron Bentley
Revert now-unnecessary changes
374
            f = self._get_FTP()
1707.3.14 by John Arbash Meinel
Cleanup mkdir and rmdir error handling.
375
            f.rmd(abspath)
1658.1.1 by Martin Pool
Fix FTP push with metadir format (Alexandre Saint)
376
        except ftplib.error_perm, e:
1551.15.4 by Aaron Bentley
Revert now-unnecessary changes
377
            self._translate_perm_error(e, abspath, unknown_exc=errors.PathError)
1658.1.1 by Martin Pool
Fix FTP push with metadir format (Alexandre Saint)
378
1955.3.15 by John Arbash Meinel
Deprecate 'Transport.append' in favor of Transport.append_file or Transport.append_bytes
379
    def append_file(self, relpath, f, mode=None):
1185.36.4 by Daniel Silverstone
Add FTP transport
380
        """Append the text in the file-like object into the final
381
        location.
382
        """
2485.8.22 by Vincent Ladeuil
Finish ftp refactoring. Test suite passing.
383
        abspath = self._remote_path(relpath)
1707.3.8 by John Arbash Meinel
[patch] Alexandre Saint: enable append support for ftp
384
        if self.has(relpath):
385
            ftp = self._get_FTP()
1707.3.19 by John Arbash Meinel
For some reason after RNTO we get a failure for has()
386
            result = ftp.size(abspath)
1707.3.8 by John Arbash Meinel
[patch] Alexandre Saint: enable append support for ftp
387
        else:
388
            result = 0
389
1707.3.19 by John Arbash Meinel
For some reason after RNTO we get a failure for has()
390
        mutter("FTP appe to %s", abspath)
1707.3.8 by John Arbash Meinel
[patch] Alexandre Saint: enable append support for ftp
391
        self._try_append(relpath, f.read(), mode)
392
393
        return result
394
395
    def _try_append(self, relpath, text, mode=None, retries=0):
396
        """Try repeatedly to append the given text to the file at relpath.
397
        
398
        This is a recursive function. On errors, it will be called until the
399
        number of retries is exceeded.
400
        """
401
        try:
2485.8.22 by Vincent Ladeuil
Finish ftp refactoring. Test suite passing.
402
            abspath = self._remote_path(relpath)
1707.3.8 by John Arbash Meinel
[patch] Alexandre Saint: enable append support for ftp
403
            mutter("FTP appe (try %d) to %s", retries, abspath)
404
            ftp = self._get_FTP()
405
            ftp.voidcmd("TYPE I")
406
            cmd = "APPE %s" % abspath
407
            conn = ftp.transfercmd(cmd)
408
            conn.sendall(text)
409
            conn.close()
1707.3.25 by John Arbash Meinel
Setting some default exception classes, which can help a lot.
410
            if mode:
1707.3.8 by John Arbash Meinel
[patch] Alexandre Saint: enable append support for ftp
411
                self._setmode(relpath, mode)
412
            ftp.getresp()
413
        except ftplib.error_perm, e:
1707.3.26 by John Arbash Meinel
Added a default exception for append
414
            self._translate_perm_error(e, abspath, extra='error appending',
415
                unknown_exc=errors.NoSuchFile)
1707.3.8 by John Arbash Meinel
[patch] Alexandre Saint: enable append support for ftp
416
        except ftplib.error_temp, e:
417
            if retries > _number_of_retries:
1707.3.13 by John Arbash Meinel
Changing raise Error to raise errors.Error
418
                raise errors.TransportError("FTP temporary error during APPEND %s." \
1707.3.8 by John Arbash Meinel
[patch] Alexandre Saint: enable append support for ftp
419
                        "Aborting." % abspath, orig_error=e)
420
            else:
421
                warning("FTP temporary error: %s. Retrying.", str(e))
2485.8.33 by Vincent Ladeuil
ftp connection sharing refactored. Tests passing.
422
                self._reconnect()
1707.3.8 by John Arbash Meinel
[patch] Alexandre Saint: enable append support for ftp
423
                self._try_append(relpath, text, mode, retries+1)
424
425
    def _setmode(self, relpath, mode):
426
        """Set permissions on a path.
427
428
        Only set permissions if the FTP server supports the 'SITE CHMOD'
429
        extension.
430
        """
431
        try:
432
            mutter("FTP site chmod: setting permissions to %s on %s",
2485.8.22 by Vincent Ladeuil
Finish ftp refactoring. Test suite passing.
433
                str(mode), self._remote_path(relpath))
1707.3.8 by John Arbash Meinel
[patch] Alexandre Saint: enable append support for ftp
434
            ftp = self._get_FTP()
2485.8.22 by Vincent Ladeuil
Finish ftp refactoring. Test suite passing.
435
            cmd = "SITE CHMOD %s %s" % (self._remote_path(relpath), str(mode))
1707.3.8 by John Arbash Meinel
[patch] Alexandre Saint: enable append support for ftp
436
            ftp.sendcmd(cmd)
437
        except ftplib.error_perm, e:
438
            # Command probably not available on this server
439
            warning("FTP Could not set permissions to %s on %s. %s",
2485.8.22 by Vincent Ladeuil
Finish ftp refactoring. Test suite passing.
440
                    str(mode), self._remote_path(relpath), str(e))
1185.36.4 by Daniel Silverstone
Add FTP transport
441
1707.3.11 by John Arbash Meinel
fixing more tests.
442
    # TODO: jam 20060516 I believe ftp allows you to tell an ftp server
443
    #       to copy something to another machine. And you may be able
444
    #       to give it its own address as the 'to' location.
445
    #       So implement a fancier 'copy()'
1185.36.4 by Daniel Silverstone
Add FTP transport
446
1551.15.3 by Aaron Bentley
Separate FtpTransport.rename from FtpTransport.move
447
    def rename(self, rel_from, rel_to):
2485.8.22 by Vincent Ladeuil
Finish ftp refactoring. Test suite passing.
448
        abs_from = self._remote_path(rel_from)
449
        abs_to = self._remote_path(rel_to)
1551.15.3 by Aaron Bentley
Separate FtpTransport.rename from FtpTransport.move
450
        mutter("FTP rename: %s => %s", abs_from, abs_to)
451
        f = self._get_FTP()
452
        return self._rename(abs_from, abs_to, f)
453
454
    def _rename(self, abs_from, abs_to, f):
455
        try:
456
            f.rename(abs_from, abs_to)
457
        except ftplib.error_perm, e:
458
            self._translate_perm_error(e, abs_from,
459
                ': unable to rename to %r' % (abs_to))
460
1185.36.4 by Daniel Silverstone
Add FTP transport
461
    def move(self, rel_from, rel_to):
462
        """Move the item at rel_from to the location at rel_to"""
2485.8.22 by Vincent Ladeuil
Finish ftp refactoring. Test suite passing.
463
        abs_from = self._remote_path(rel_from)
464
        abs_to = self._remote_path(rel_to)
1185.36.4 by Daniel Silverstone
Add FTP transport
465
        try:
1707.3.19 by John Arbash Meinel
For some reason after RNTO we get a failure for has()
466
            mutter("FTP mv: %s => %s", abs_from, abs_to)
1185.36.4 by Daniel Silverstone
Add FTP transport
467
            f = self._get_FTP()
1551.15.2 by Aaron Bentley
FTP does not require atomic rename. Fixes #89436
468
            self._rename_and_overwrite(abs_from, abs_to, f)
1185.36.4 by Daniel Silverstone
Add FTP transport
469
        except ftplib.error_perm, e:
1707.3.19 by John Arbash Meinel
For some reason after RNTO we get a failure for has()
470
            self._translate_perm_error(e, abs_from,
471
                extra='unable to rename to %r' % (rel_to,), 
472
                unknown_exc=errors.PathError)
1185.36.4 by Daniel Silverstone
Add FTP transport
473
1551.15.2 by Aaron Bentley
FTP does not require atomic rename. Fixes #89436
474
    def _rename_and_overwrite(self, abs_from, abs_to, f):
475
        """Do a fancy rename on the remote server.
476
477
        Using the implementation provided by osutils.
478
        """
1551.15.3 by Aaron Bentley
Separate FtpTransport.rename from FtpTransport.move
479
        osutils.fancy_rename(abs_from, abs_to,
1551.15.4 by Aaron Bentley
Revert now-unnecessary changes
480
            rename_func=lambda p1, p2: self._rename(p1, p2, f),
481
            unlink_func=lambda p: self._delete(p, f))
1551.15.2 by Aaron Bentley
FTP does not require atomic rename. Fixes #89436
482
1185.36.4 by Daniel Silverstone
Add FTP transport
483
    def delete(self, relpath):
484
        """Delete the item at relpath"""
2485.8.22 by Vincent Ladeuil
Finish ftp refactoring. Test suite passing.
485
        abspath = self._remote_path(relpath)
1551.15.2 by Aaron Bentley
FTP does not require atomic rename. Fixes #89436
486
        f = self._get_FTP()
487
        self._delete(abspath, f)
488
489
    def _delete(self, abspath, f):
1185.36.4 by Daniel Silverstone
Add FTP transport
490
        try:
1707.3.11 by John Arbash Meinel
fixing more tests.
491
            mutter("FTP rm: %s", abspath)
492
            f.delete(abspath)
1185.36.4 by Daniel Silverstone
Add FTP transport
493
        except ftplib.error_perm, e:
1707.3.25 by John Arbash Meinel
Setting some default exception classes, which can help a lot.
494
            self._translate_perm_error(e, abspath, 'error deleting',
495
                unknown_exc=errors.NoSuchFile)
1185.36.4 by Daniel Silverstone
Add FTP transport
496
2634.1.1 by Robert Collins
(robertc) Reinstate the accidentally backed out external_url patch.
497
    def external_url(self):
498
        """See bzrlib.transport.Transport.external_url."""
499
        # FTP URL's are externally usable.
500
        return self.base
501
1185.36.4 by Daniel Silverstone
Add FTP transport
502
    def listable(self):
503
        """See Transport.listable."""
504
        return True
505
506
    def list_dir(self, relpath):
507
        """See Transport.list_dir."""
2485.8.22 by Vincent Ladeuil
Finish ftp refactoring. Test suite passing.
508
        basepath = self._remote_path(relpath)
1959.2.5 by John Arbash Meinel
Small fixes in ftp.list_dir()
509
        mutter("FTP nlst: %s", basepath)
1959.2.1 by John Arbash Meinel
David Allouche: Make transports return escaped paths
510
        f = self._get_FTP()
1185.36.4 by Daniel Silverstone
Add FTP transport
511
        try:
1707.3.8 by John Arbash Meinel
[patch] Alexandre Saint: enable append support for ftp
512
            paths = f.nlst(basepath)
1185.36.4 by Daniel Silverstone
Add FTP transport
513
        except ftplib.error_perm, e:
1707.3.13 by John Arbash Meinel
Changing raise Error to raise errors.Error
514
            self._translate_perm_error(e, relpath, extra='error with list_dir')
1959.2.1 by John Arbash Meinel
David Allouche: Make transports return escaped paths
515
        # If FTP.nlst returns paths prefixed by relpath, strip 'em
516
        if paths and paths[0].startswith(basepath):
517
            entries = [path[len(basepath)+1:] for path in paths]
518
        else:
519
            entries = paths
520
        # Remove . and .. if present
1959.2.5 by John Arbash Meinel
Small fixes in ftp.list_dir()
521
        return [urlutils.escape(entry) for entry in entries
522
                if entry not in ('.', '..')]
1185.36.4 by Daniel Silverstone
Add FTP transport
523
524
    def iter_files_recursive(self):
525
        """See Transport.iter_files_recursive.
526
527
        This is cargo-culted from the SFTP transport"""
528
        mutter("FTP iter_files_recursive")
529
        queue = list(self.list_dir("."))
530
        while queue:
1959.2.1 by John Arbash Meinel
David Allouche: Make transports return escaped paths
531
            relpath = queue.pop(0)
1185.36.4 by Daniel Silverstone
Add FTP transport
532
            st = self.stat(relpath)
533
            if stat.S_ISDIR(st.st_mode):
534
                for i, basename in enumerate(self.list_dir(relpath)):
535
                    queue.insert(i, relpath+"/"+basename)
536
            else:
537
                yield relpath
538
539
    def stat(self, relpath):
1707.3.13 by John Arbash Meinel
Changing raise Error to raise errors.Error
540
        """Return the stat information for a file."""
2485.8.22 by Vincent Ladeuil
Finish ftp refactoring. Test suite passing.
541
        abspath = self._remote_path(relpath)
1185.36.4 by Daniel Silverstone
Add FTP transport
542
        try:
1707.3.13 by John Arbash Meinel
Changing raise Error to raise errors.Error
543
            mutter("FTP stat: %s", abspath)
1185.36.4 by Daniel Silverstone
Add FTP transport
544
            f = self._get_FTP()
1707.3.13 by John Arbash Meinel
Changing raise Error to raise errors.Error
545
            return FtpStatResult(f, abspath)
1185.36.4 by Daniel Silverstone
Add FTP transport
546
        except ftplib.error_perm, e:
1707.3.13 by John Arbash Meinel
Changing raise Error to raise errors.Error
547
            self._translate_perm_error(e, abspath, extra='error w/ stat')
1185.36.4 by Daniel Silverstone
Add FTP transport
548
549
    def lock_read(self, relpath):
550
        """Lock the given file for shared (read) access.
551
        :return: A lock object, which should be passed to Transport.unlock()
552
        """
553
        # The old RemoteBranch ignore lock for reading, so we will
554
        # continue that tradition and return a bogus lock object.
555
        class BogusLock(object):
556
            def __init__(self, path):
557
                self.path = path
558
            def unlock(self):
559
                pass
560
        return BogusLock(relpath)
561
562
    def lock_write(self, relpath):
563
        """Lock the given file for exclusive (write) access.
564
        WARNING: many transports do not support this, so trying avoid using it
565
566
        :return: A lock object, which should be passed to Transport.unlock()
567
        """
568
        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.
569
570
571
def get_test_permutations():
572
    """Return the permutations to be used in testing."""
2917.3.1 by Vincent Ladeuil
Separate transport from test server.
573
    from bzrlib import tests
574
    if tests.FTPServerFeature.available():
2949.3.2 by Vincent Ladeuil
Rename FTPServer.py to ftp_server.py.
575
        from bzrlib.tests import ftp_server
576
        return [(FtpTransport, ftp_server.FTPServer)]
2917.3.1 by Vincent Ladeuil
Separate transport from test server.
577
    else:
2917.3.2 by Vincent Ladeuil
Prepare for fixing bug #157752 (so close to a palindrome...)
578
        # Dummy server to have the test suite report the number of tests
579
        # needing that feature.
2949.3.2 by Vincent Ladeuil
Rename FTPServer.py to ftp_server.py.
580
        class UnavailableFTPServer(object):
2917.3.2 by Vincent Ladeuil
Prepare for fixing bug #157752 (so close to a palindrome...)
581
            def setUp(self):
582
                raise tests.UnavailableFeature(tests.FTPServerFeature)
583
2949.3.2 by Vincent Ladeuil
Rename FTPServer.py to ftp_server.py.
584
        return [(FtpTransport, UnavailableFTPServer)]