/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
1707.3.13 by John Arbash Meinel
Changing raise Error to raise errors.Error
183
            ):
184
            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.
185
        if ('file exists' in s):
1707.3.15 by John Arbash Meinel
Handle trying to list a file instead of a dir
186
            raise errors.FileExists(path, extra=extra)
187
        if ('not a directory' in s):
188
            raise errors.PathError(path, extra=extra)
189
1707.3.18 by John Arbash Meinel
Fix error handling for ftp.put()
190
        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()
191
192
        if unknown_exc:
193
            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.
194
        # TODO: jam 20060516 Consider re-raising the error wrapped in 
195
        #       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
196
        #       Also, 'sftp' has a generic 'Failure' mode, which we use failure_exc
197
        #       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.
198
        #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
199
        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.
200
2485.8.22 by Vincent Ladeuil
Finish ftp refactoring. Test suite passing.
201
    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
202
        # XXX: It seems that ftplib does not handle Unicode paths
2485.8.22 by Vincent Ladeuil
Finish ftp refactoring. Test suite passing.
203
        # at the same time, medusa won't handle utf8 paths So if
204
        # we .encode(utf8) here (see ConnectedTransport
205
        # implementation), then we get a Server failure.  while
206
        # if we use str(), we get a UnicodeError, and the test
207
        # suite just skips testing UnicodePaths.
208
        relative = str(urlutils.unescape(relpath))
209
        remote_path = self._combine_paths(self._path, relative)
210
        return remote_path
1185.36.4 by Daniel Silverstone
Add FTP transport
211
212
    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.
213
        """Does the target location exist?"""
214
        # FIXME jam 20060516 We *do* ask about directories in the test suite
215
        #       We don't seem to in the actual codebase
216
        # XXX: I assume we're never asked has(dirname) and thus I use
217
        # the FTP size command and assume that if it doesn't raise,
218
        # all is good.
2485.8.22 by Vincent Ladeuil
Finish ftp refactoring. Test suite passing.
219
        abspath = self._remote_path(relpath)
1185.36.4 by Daniel Silverstone
Add FTP transport
220
        try:
221
            f = self._get_FTP()
1707.3.19 by John Arbash Meinel
For some reason after RNTO we get a failure for has()
222
            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.
223
            s = f.size(abspath)
224
            mutter("FTP has: %s", abspath)
1185.36.4 by Daniel Silverstone
Add FTP transport
225
            return True
1707.3.16 by John Arbash Meinel
Overloading cmd_size and cmd_mkd so that the test suite can handle directories.
226
        except ftplib.error_perm, e:
227
            if ('is a directory' in str(e).lower()):
228
                mutter("FTP has dir: %s: %s", abspath, e)
229
                return True
230
            mutter("FTP has not: %s: %s", abspath, e)
1185.36.4 by Daniel Silverstone
Add FTP transport
231
            return False
232
2164.2.15 by Vincent Ladeuil
Http redirections are not followed by default. Do not use hints
233
    def get(self, relpath, decode=False, retries=0):
1185.36.4 by Daniel Silverstone
Add FTP transport
234
        """Get the file at the given relative path.
235
236
        :param relpath: The relative path to the file
1185.72.9 by Matthieu Moy
suggestions by jam and robertc
237
        :param retries: Number of retries after temporary failures so far
238
                        for this operation.
1185.36.4 by Daniel Silverstone
Add FTP transport
239
240
        We're meant to return a file-like object which bzr will
241
        then read from. For now we do this via the magic of StringIO
242
        """
1540.3.6 by Martin Pool
[merge] update from bzr.dev
243
        # TODO: decode should be deprecated
1185.36.4 by Daniel Silverstone
Add FTP transport
244
        try:
2485.8.22 by Vincent Ladeuil
Finish ftp refactoring. Test suite passing.
245
            mutter("FTP get: %s", self._remote_path(relpath))
1185.36.4 by Daniel Silverstone
Add FTP transport
246
            f = self._get_FTP()
247
            ret = StringIO()
2485.8.22 by Vincent Ladeuil
Finish ftp refactoring. Test suite passing.
248
            f.retrbinary('RETR '+self._remote_path(relpath), ret.write, 8192)
1185.36.4 by Daniel Silverstone
Add FTP transport
249
            ret.seek(0)
250
            return ret
251
        except ftplib.error_perm, e:
1707.3.13 by John Arbash Meinel
Changing raise Error to raise errors.Error
252
            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
253
        except ftplib.error_temp, e:
1185.72.15 by Matthieu Moy
better error messages on ftp failures
254
            if retries > _number_of_retries:
1707.3.13 by John Arbash Meinel
Changing raise Error to raise errors.Error
255
                raise errors.TransportError(msg="FTP temporary error during GET %s. Aborting."
1185.72.15 by Matthieu Moy
better error messages on ftp failures
256
                                     % self.abspath(relpath),
257
                                     orig_error=e)
1185.72.8 by Matthieu Moy
try to solve the problem of ftp servers closing the connection
258
            else:
1707.3.8 by John Arbash Meinel
[patch] Alexandre Saint: enable append support for ftp
259
                warning("FTP temporary error: %s. Retrying.", str(e))
2485.8.33 by Vincent Ladeuil
ftp connection sharing refactored. Tests passing.
260
                self._reconnect()
1185.72.8 by Matthieu Moy
try to solve the problem of ftp servers closing the connection
261
                return self.get(relpath, decode, retries+1)
1185.72.15 by Matthieu Moy
better error messages on ftp failures
262
        except EOFError, e:
1185.72.9 by Matthieu Moy
suggestions by jam and robertc
263
            if retries > _number_of_retries:
1707.3.13 by John Arbash Meinel
Changing raise Error to raise errors.Error
264
                raise errors.TransportError("FTP control connection closed during GET %s."
1185.72.15 by Matthieu Moy
better error messages on ftp failures
265
                                     % self.abspath(relpath),
266
                                     orig_error=e)
1185.72.8 by Matthieu Moy
try to solve the problem of ftp servers closing the connection
267
            else:
1185.72.9 by Matthieu Moy
suggestions by jam and robertc
268
                warning("FTP control connection closed. Trying to reopen.")
1185.72.14 by Matthieu Moy
Sleep between retries when the connection closes
269
                time.sleep(_sleep_between_retries)
2485.8.33 by Vincent Ladeuil
ftp connection sharing refactored. Tests passing.
270
                self._reconnect()
1185.72.8 by Matthieu Moy
try to solve the problem of ftp servers closing the connection
271
                return self.get(relpath, decode, retries+1)
1185.36.4 by Daniel Silverstone
Add FTP transport
272
1955.3.6 by John Arbash Meinel
Lots of deprecation warnings, but no errors
273
    def put_file(self, relpath, fp, mode=None, retries=0):
1185.36.4 by Daniel Silverstone
Add FTP transport
274
        """Copy the file-like or string object into the location.
275
276
        :param relpath: Location to put the contents, relative to base.
1185.72.13 by Matthieu Moy
Make ftp put atomic
277
        :param fp:       File-like or string object.
1185.72.9 by Matthieu Moy
suggestions by jam and robertc
278
        :param retries: Number of retries after temporary failures so far
279
                        for this operation.
280
1551.15.2 by Aaron Bentley
FTP does not require atomic rename. Fixes #89436
281
        TODO: jam 20051215 ftp as a protocol seems to support chmod, but
282
        ftplib does not
1185.36.4 by Daniel Silverstone
Add FTP transport
283
        """
2485.8.22 by Vincent Ladeuil
Finish ftp refactoring. Test suite passing.
284
        abspath = self._remote_path(relpath)
1707.3.18 by John Arbash Meinel
Fix error handling for ftp.put()
285
        tmp_abspath = '%s.tmp.%.9f.%d.%d' % (abspath, time.time(),
1185.72.13 by Matthieu Moy
Make ftp put atomic
286
                        os.getpid(), random.randint(0,0x7FFFFFFF))
2745.5.2 by Robert Collins
* ``bzrlib.transport.Transport.put_file`` now returns the number of bytes
287
        bytes = None
1963.2.6 by Robey Pointer
pychecker is on crack; go back to using 'is None'.
288
        if getattr(fp, 'read', None) is None:
2745.5.2 by Robert Collins
* ``bzrlib.transport.Transport.put_file`` now returns the number of bytes
289
            # hand in a string IO
290
            bytes = fp
291
            fp = StringIO(bytes)
292
        else:
293
            # capture the byte count; .read() may be read only so
294
            # decorate it.
295
            class byte_counter(object):
296
                def __init__(self, fp):
297
                    self.fp = fp
298
                    self.counted_bytes = 0
299
                def read(self, count):
300
                    result = self.fp.read(count)
301
                    self.counted_bytes += len(result)
302
                    return result
303
            fp = byte_counter(fp)
1185.36.4 by Daniel Silverstone
Add FTP transport
304
        try:
1707.3.18 by John Arbash Meinel
Fix error handling for ftp.put()
305
            mutter("FTP put: %s", abspath)
1185.36.4 by Daniel Silverstone
Add FTP transport
306
            f = self._get_FTP()
1185.72.13 by Matthieu Moy
Make ftp put atomic
307
            try:
308
                f.storbinary('STOR '+tmp_abspath, fp)
1551.15.2 by Aaron Bentley
FTP does not require atomic rename. Fixes #89436
309
                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
310
                if bytes is not None:
311
                    return len(bytes)
312
                else:
313
                    return fp.counted_bytes
1185.72.13 by Matthieu Moy
Make ftp put atomic
314
            except (ftplib.error_temp,EOFError), e:
1185.72.15 by Matthieu Moy
better error messages on ftp failures
315
                warning("Failure during ftp PUT. Deleting temporary file.")
1185.72.13 by Matthieu Moy
Make ftp put atomic
316
                try:
317
                    f.delete(tmp_abspath)
318
                except:
1707.3.8 by John Arbash Meinel
[patch] Alexandre Saint: enable append support for ftp
319
                    warning("Failed to delete temporary file on the"
320
                            " server.\nFile: %s", tmp_abspath)
1185.72.13 by Matthieu Moy
Make ftp put atomic
321
                    raise e
322
                raise
1185.36.4 by Daniel Silverstone
Add FTP transport
323
        except ftplib.error_perm, e:
2570.1.1 by John Arbash Meinel
(John Arbash Meinel) A couple small updates for pushing over ftp.
324
            self._translate_perm_error(e, abspath, extra='could not store',
325
                                       unknown_exc=errors.NoSuchFile)
1185.72.8 by Matthieu Moy
try to solve the problem of ftp servers closing the connection
326
        except ftplib.error_temp, e:
1185.72.9 by Matthieu Moy
suggestions by jam and robertc
327
            if retries > _number_of_retries:
1707.3.13 by John Arbash Meinel
Changing raise Error to raise errors.Error
328
                raise errors.TransportError("FTP temporary error during PUT %s. Aborting."
1185.72.15 by Matthieu Moy
better error messages on ftp failures
329
                                     % self.abspath(relpath), orig_error=e)
1185.72.8 by Matthieu Moy
try to solve the problem of ftp servers closing the connection
330
            else:
1707.3.8 by John Arbash Meinel
[patch] Alexandre Saint: enable append support for ftp
331
                warning("FTP temporary error: %s. Retrying.", str(e))
2485.8.33 by Vincent Ladeuil
ftp connection sharing refactored. Tests passing.
332
                self._reconnect()
1955.3.6 by John Arbash Meinel
Lots of deprecation warnings, but no errors
333
                self.put_file(relpath, fp, mode, retries+1)
1185.72.9 by Matthieu Moy
suggestions by jam and robertc
334
        except EOFError:
1185.72.10 by Matthieu Moy
One 1 -> _number_of_retries was missing
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 control connection closed during PUT %s."
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:
1185.72.14 by Matthieu Moy
Sleep between retries when the connection closes
339
                warning("FTP control connection closed. Trying to reopen.")
340
                time.sleep(_sleep_between_retries)
2485.8.33 by Vincent Ladeuil
ftp connection sharing refactored. Tests passing.
341
                self._reconnect()
1955.3.6 by John Arbash Meinel
Lots of deprecation warnings, but no errors
342
                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
343
1185.58.2 by John Arbash Meinel
Added mode to the appropriate transport functions, and tests to make sure they work.
344
    def mkdir(self, relpath, mode=None):
1185.36.4 by Daniel Silverstone
Add FTP transport
345
        """Create a directory at the given path."""
2485.8.22 by Vincent Ladeuil
Finish ftp refactoring. Test suite passing.
346
        abspath = self._remote_path(relpath)
1185.36.4 by Daniel Silverstone
Add FTP transport
347
        try:
1707.3.14 by John Arbash Meinel
Cleanup mkdir and rmdir error handling.
348
            mutter("FTP mkd: %s", abspath)
1185.36.4 by Daniel Silverstone
Add FTP transport
349
            f = self._get_FTP()
1707.3.14 by John Arbash Meinel
Cleanup mkdir and rmdir error handling.
350
            f.mkd(abspath)
1185.36.4 by Daniel Silverstone
Add FTP transport
351
        except ftplib.error_perm, e:
1707.3.25 by John Arbash Meinel
Setting some default exception classes, which can help a lot.
352
            self._translate_perm_error(e, abspath,
353
                unknown_exc=errors.FileExists)
1185.36.4 by Daniel Silverstone
Add FTP transport
354
2671.3.9 by Robert Collins
Review feedback and fix VFat emulated transports to not claim to have unix permissions.
355
    def open_write_stream(self, relpath, mode=None):
356
        """See Transport.open_write_stream."""
2671.3.6 by Robert Collins
Review feedback.
357
        self.put_bytes(relpath, "", mode)
358
        result = AppendBasedFileStream(self, relpath)
359
        _file_streams[self.abspath(relpath)] = result
360
        return result
2671.3.2 by Robert Collins
Start open_file_stream logic.
361
2671.3.1 by Robert Collins
* New method ``bzrlib.transport.Transport.get_recommended_page_size``.
362
    def recommended_page_size(self):
363
        """See Transport.recommended_page_size().
364
365
        For FTP we suggest a large page size to reduce the overhead
366
        introduced by latency.
367
        """
368
        return 64 * 1024
369
1658.1.1 by Martin Pool
Fix FTP push with metadir format (Alexandre Saint)
370
    def rmdir(self, rel_path):
371
        """Delete the directory at rel_path"""
2485.8.22 by Vincent Ladeuil
Finish ftp refactoring. Test suite passing.
372
        abspath = self._remote_path(rel_path)
1658.1.1 by Martin Pool
Fix FTP push with metadir format (Alexandre Saint)
373
        try:
1707.3.14 by John Arbash Meinel
Cleanup mkdir and rmdir error handling.
374
            mutter("FTP rmd: %s", abspath)
1551.15.4 by Aaron Bentley
Revert now-unnecessary changes
375
            f = self._get_FTP()
1707.3.14 by John Arbash Meinel
Cleanup mkdir and rmdir error handling.
376
            f.rmd(abspath)
1658.1.1 by Martin Pool
Fix FTP push with metadir format (Alexandre Saint)
377
        except ftplib.error_perm, e:
1551.15.4 by Aaron Bentley
Revert now-unnecessary changes
378
            self._translate_perm_error(e, abspath, unknown_exc=errors.PathError)
1658.1.1 by Martin Pool
Fix FTP push with metadir format (Alexandre Saint)
379
1955.3.15 by John Arbash Meinel
Deprecate 'Transport.append' in favor of Transport.append_file or Transport.append_bytes
380
    def append_file(self, relpath, f, mode=None):
1185.36.4 by Daniel Silverstone
Add FTP transport
381
        """Append the text in the file-like object into the final
382
        location.
383
        """
2485.8.22 by Vincent Ladeuil
Finish ftp refactoring. Test suite passing.
384
        abspath = self._remote_path(relpath)
1707.3.8 by John Arbash Meinel
[patch] Alexandre Saint: enable append support for ftp
385
        if self.has(relpath):
386
            ftp = self._get_FTP()
1707.3.19 by John Arbash Meinel
For some reason after RNTO we get a failure for has()
387
            result = ftp.size(abspath)
1707.3.8 by John Arbash Meinel
[patch] Alexandre Saint: enable append support for ftp
388
        else:
389
            result = 0
390
1707.3.19 by John Arbash Meinel
For some reason after RNTO we get a failure for has()
391
        mutter("FTP appe to %s", abspath)
1707.3.8 by John Arbash Meinel
[patch] Alexandre Saint: enable append support for ftp
392
        self._try_append(relpath, f.read(), mode)
393
394
        return result
395
396
    def _try_append(self, relpath, text, mode=None, retries=0):
397
        """Try repeatedly to append the given text to the file at relpath.
398
        
399
        This is a recursive function. On errors, it will be called until the
400
        number of retries is exceeded.
401
        """
402
        try:
2485.8.22 by Vincent Ladeuil
Finish ftp refactoring. Test suite passing.
403
            abspath = self._remote_path(relpath)
1707.3.8 by John Arbash Meinel
[patch] Alexandre Saint: enable append support for ftp
404
            mutter("FTP appe (try %d) to %s", retries, abspath)
405
            ftp = self._get_FTP()
406
            ftp.voidcmd("TYPE I")
407
            cmd = "APPE %s" % abspath
408
            conn = ftp.transfercmd(cmd)
409
            conn.sendall(text)
410
            conn.close()
1707.3.25 by John Arbash Meinel
Setting some default exception classes, which can help a lot.
411
            if mode:
1707.3.8 by John Arbash Meinel
[patch] Alexandre Saint: enable append support for ftp
412
                self._setmode(relpath, mode)
413
            ftp.getresp()
414
        except ftplib.error_perm, e:
1707.3.26 by John Arbash Meinel
Added a default exception for append
415
            self._translate_perm_error(e, abspath, extra='error appending',
416
                unknown_exc=errors.NoSuchFile)
1707.3.8 by John Arbash Meinel
[patch] Alexandre Saint: enable append support for ftp
417
        except ftplib.error_temp, e:
418
            if retries > _number_of_retries:
1707.3.13 by John Arbash Meinel
Changing raise Error to raise errors.Error
419
                raise errors.TransportError("FTP temporary error during APPEND %s." \
1707.3.8 by John Arbash Meinel
[patch] Alexandre Saint: enable append support for ftp
420
                        "Aborting." % abspath, orig_error=e)
421
            else:
422
                warning("FTP temporary error: %s. Retrying.", str(e))
2485.8.33 by Vincent Ladeuil
ftp connection sharing refactored. Tests passing.
423
                self._reconnect()
1707.3.8 by John Arbash Meinel
[patch] Alexandre Saint: enable append support for ftp
424
                self._try_append(relpath, text, mode, retries+1)
425
426
    def _setmode(self, relpath, mode):
427
        """Set permissions on a path.
428
429
        Only set permissions if the FTP server supports the 'SITE CHMOD'
430
        extension.
431
        """
432
        try:
433
            mutter("FTP site chmod: setting permissions to %s on %s",
2485.8.22 by Vincent Ladeuil
Finish ftp refactoring. Test suite passing.
434
                str(mode), self._remote_path(relpath))
1707.3.8 by John Arbash Meinel
[patch] Alexandre Saint: enable append support for ftp
435
            ftp = self._get_FTP()
2485.8.22 by Vincent Ladeuil
Finish ftp refactoring. Test suite passing.
436
            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
437
            ftp.sendcmd(cmd)
438
        except ftplib.error_perm, e:
439
            # Command probably not available on this server
440
            warning("FTP Could not set permissions to %s on %s. %s",
2485.8.22 by Vincent Ladeuil
Finish ftp refactoring. Test suite passing.
441
                    str(mode), self._remote_path(relpath), str(e))
1185.36.4 by Daniel Silverstone
Add FTP transport
442
1707.3.11 by John Arbash Meinel
fixing more tests.
443
    # TODO: jam 20060516 I believe ftp allows you to tell an ftp server
444
    #       to copy something to another machine. And you may be able
445
    #       to give it its own address as the 'to' location.
446
    #       So implement a fancier 'copy()'
1185.36.4 by Daniel Silverstone
Add FTP transport
447
1551.15.3 by Aaron Bentley
Separate FtpTransport.rename from FtpTransport.move
448
    def rename(self, rel_from, rel_to):
2485.8.22 by Vincent Ladeuil
Finish ftp refactoring. Test suite passing.
449
        abs_from = self._remote_path(rel_from)
450
        abs_to = self._remote_path(rel_to)
1551.15.3 by Aaron Bentley
Separate FtpTransport.rename from FtpTransport.move
451
        mutter("FTP rename: %s => %s", abs_from, abs_to)
452
        f = self._get_FTP()
453
        return self._rename(abs_from, abs_to, f)
454
455
    def _rename(self, abs_from, abs_to, f):
456
        try:
457
            f.rename(abs_from, abs_to)
458
        except ftplib.error_perm, e:
459
            self._translate_perm_error(e, abs_from,
460
                ': unable to rename to %r' % (abs_to))
461
1185.36.4 by Daniel Silverstone
Add FTP transport
462
    def move(self, rel_from, rel_to):
463
        """Move the item at rel_from to the location at rel_to"""
2485.8.22 by Vincent Ladeuil
Finish ftp refactoring. Test suite passing.
464
        abs_from = self._remote_path(rel_from)
465
        abs_to = self._remote_path(rel_to)
1185.36.4 by Daniel Silverstone
Add FTP transport
466
        try:
1707.3.19 by John Arbash Meinel
For some reason after RNTO we get a failure for has()
467
            mutter("FTP mv: %s => %s", abs_from, abs_to)
1185.36.4 by Daniel Silverstone
Add FTP transport
468
            f = self._get_FTP()
1551.15.2 by Aaron Bentley
FTP does not require atomic rename. Fixes #89436
469
            self._rename_and_overwrite(abs_from, abs_to, f)
1185.36.4 by Daniel Silverstone
Add FTP transport
470
        except ftplib.error_perm, e:
1707.3.19 by John Arbash Meinel
For some reason after RNTO we get a failure for has()
471
            self._translate_perm_error(e, abs_from,
472
                extra='unable to rename to %r' % (rel_to,), 
473
                unknown_exc=errors.PathError)
1185.36.4 by Daniel Silverstone
Add FTP transport
474
1551.15.2 by Aaron Bentley
FTP does not require atomic rename. Fixes #89436
475
    def _rename_and_overwrite(self, abs_from, abs_to, f):
476
        """Do a fancy rename on the remote server.
477
478
        Using the implementation provided by osutils.
479
        """
1551.15.3 by Aaron Bentley
Separate FtpTransport.rename from FtpTransport.move
480
        osutils.fancy_rename(abs_from, abs_to,
1551.15.4 by Aaron Bentley
Revert now-unnecessary changes
481
            rename_func=lambda p1, p2: self._rename(p1, p2, f),
482
            unlink_func=lambda p: self._delete(p, f))
1551.15.2 by Aaron Bentley
FTP does not require atomic rename. Fixes #89436
483
1185.36.4 by Daniel Silverstone
Add FTP transport
484
    def delete(self, relpath):
485
        """Delete the item at relpath"""
2485.8.22 by Vincent Ladeuil
Finish ftp refactoring. Test suite passing.
486
        abspath = self._remote_path(relpath)
1551.15.2 by Aaron Bentley
FTP does not require atomic rename. Fixes #89436
487
        f = self._get_FTP()
488
        self._delete(abspath, f)
489
490
    def _delete(self, abspath, f):
1185.36.4 by Daniel Silverstone
Add FTP transport
491
        try:
1707.3.11 by John Arbash Meinel
fixing more tests.
492
            mutter("FTP rm: %s", abspath)
493
            f.delete(abspath)
1185.36.4 by Daniel Silverstone
Add FTP transport
494
        except ftplib.error_perm, e:
1707.3.25 by John Arbash Meinel
Setting some default exception classes, which can help a lot.
495
            self._translate_perm_error(e, abspath, 'error deleting',
496
                unknown_exc=errors.NoSuchFile)
1185.36.4 by Daniel Silverstone
Add FTP transport
497
2634.1.1 by Robert Collins
(robertc) Reinstate the accidentally backed out external_url patch.
498
    def external_url(self):
499
        """See bzrlib.transport.Transport.external_url."""
500
        # FTP URL's are externally usable.
501
        return self.base
502
1185.36.4 by Daniel Silverstone
Add FTP transport
503
    def listable(self):
504
        """See Transport.listable."""
505
        return True
506
507
    def list_dir(self, relpath):
508
        """See Transport.list_dir."""
2485.8.22 by Vincent Ladeuil
Finish ftp refactoring. Test suite passing.
509
        basepath = self._remote_path(relpath)
1959.2.5 by John Arbash Meinel
Small fixes in ftp.list_dir()
510
        mutter("FTP nlst: %s", basepath)
1959.2.1 by John Arbash Meinel
David Allouche: Make transports return escaped paths
511
        f = self._get_FTP()
1185.36.4 by Daniel Silverstone
Add FTP transport
512
        try:
1707.3.8 by John Arbash Meinel
[patch] Alexandre Saint: enable append support for ftp
513
            paths = f.nlst(basepath)
1185.36.4 by Daniel Silverstone
Add FTP transport
514
        except ftplib.error_perm, e:
1707.3.13 by John Arbash Meinel
Changing raise Error to raise errors.Error
515
            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
516
        # If FTP.nlst returns paths prefixed by relpath, strip 'em
517
        if paths and paths[0].startswith(basepath):
518
            entries = [path[len(basepath)+1:] for path in paths]
519
        else:
520
            entries = paths
521
        # Remove . and .. if present
1959.2.5 by John Arbash Meinel
Small fixes in ftp.list_dir()
522
        return [urlutils.escape(entry) for entry in entries
523
                if entry not in ('.', '..')]
1185.36.4 by Daniel Silverstone
Add FTP transport
524
525
    def iter_files_recursive(self):
526
        """See Transport.iter_files_recursive.
527
528
        This is cargo-culted from the SFTP transport"""
529
        mutter("FTP iter_files_recursive")
530
        queue = list(self.list_dir("."))
531
        while queue:
1959.2.1 by John Arbash Meinel
David Allouche: Make transports return escaped paths
532
            relpath = queue.pop(0)
1185.36.4 by Daniel Silverstone
Add FTP transport
533
            st = self.stat(relpath)
534
            if stat.S_ISDIR(st.st_mode):
535
                for i, basename in enumerate(self.list_dir(relpath)):
536
                    queue.insert(i, relpath+"/"+basename)
537
            else:
538
                yield relpath
539
540
    def stat(self, relpath):
1707.3.13 by John Arbash Meinel
Changing raise Error to raise errors.Error
541
        """Return the stat information for a file."""
2485.8.22 by Vincent Ladeuil
Finish ftp refactoring. Test suite passing.
542
        abspath = self._remote_path(relpath)
1185.36.4 by Daniel Silverstone
Add FTP transport
543
        try:
1707.3.13 by John Arbash Meinel
Changing raise Error to raise errors.Error
544
            mutter("FTP stat: %s", abspath)
1185.36.4 by Daniel Silverstone
Add FTP transport
545
            f = self._get_FTP()
1707.3.13 by John Arbash Meinel
Changing raise Error to raise errors.Error
546
            return FtpStatResult(f, abspath)
1185.36.4 by Daniel Silverstone
Add FTP transport
547
        except ftplib.error_perm, e:
1707.3.13 by John Arbash Meinel
Changing raise Error to raise errors.Error
548
            self._translate_perm_error(e, abspath, extra='error w/ stat')
1185.36.4 by Daniel Silverstone
Add FTP transport
549
550
    def lock_read(self, relpath):
551
        """Lock the given file for shared (read) access.
552
        :return: A lock object, which should be passed to Transport.unlock()
553
        """
554
        # The old RemoteBranch ignore lock for reading, so we will
555
        # continue that tradition and return a bogus lock object.
556
        class BogusLock(object):
557
            def __init__(self, path):
558
                self.path = path
559
            def unlock(self):
560
                pass
561
        return BogusLock(relpath)
562
563
    def lock_write(self, relpath):
564
        """Lock the given file for exclusive (write) access.
565
        WARNING: many transports do not support this, so trying avoid using it
566
567
        :return: A lock object, which should be passed to Transport.unlock()
568
        """
569
        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.
570
571
572
def get_test_permutations():
573
    """Return the permutations to be used in testing."""
2917.3.1 by Vincent Ladeuil
Separate transport from test server.
574
    from bzrlib import tests
575
    if tests.FTPServerFeature.available():
2949.3.2 by Vincent Ladeuil
Rename FTPServer.py to ftp_server.py.
576
        from bzrlib.tests import ftp_server
577
        return [(FtpTransport, ftp_server.FTPServer)]
2917.3.1 by Vincent Ladeuil
Separate transport from test server.
578
    else:
2917.3.2 by Vincent Ladeuil
Prepare for fixing bug #157752 (so close to a palindrome...)
579
        # 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
580
        # needing that feature. We raise UnavailableFeature from methods before
581
        # the test server is being used. Doing so in the setUp method has bad
582
        # side-effects (tearDown is never called).
2949.3.2 by Vincent Ladeuil
Rename FTPServer.py to ftp_server.py.
583
        class UnavailableFTPServer(object):
3221.5.1 by Vincent Ladeuil
Fix bug #137823 by raising UnavailableFeature *after* the fake ftp server
584
2917.3.2 by Vincent Ladeuil
Prepare for fixing bug #157752 (so close to a palindrome...)
585
            def setUp(self):
3221.5.1 by Vincent Ladeuil
Fix bug #137823 by raising UnavailableFeature *after* the fake ftp server
586
                pass
587
588
            def tearDown(self):
589
                pass
590
591
            def get_url(self):
592
                raise tests.UnavailableFeature(tests.FTPServerFeature)
593
594
            def get_bogus_url(self):
2917.3.2 by Vincent Ladeuil
Prepare for fixing bug #157752 (so close to a palindrome...)
595
                raise tests.UnavailableFeature(tests.FTPServerFeature)
596
2949.3.2 by Vincent Ladeuil
Rename FTPServer.py to ftp_server.py.
597
        return [(FtpTransport, UnavailableFTPServer)]