/brz/remove-bazaar

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