~bzr-pqm/bzr/bzr.dev

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