/brz/remove-bazaar

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