~bzr-pqm/bzr/bzr.dev

2338.1.1 by John Arbash Meinel
(Arnaud Fontaine) Compatibility with vsftpd error messages.
1
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
1887.1.1 by Adeodato Simó
Do not separate paragraphs in the copyright statement with blank lines,
2
#
1185.36.4 by Daniel Silverstone
Add FTP transport
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
1887.1.1 by Adeodato Simó
Do not separate paragraphs in the copyright statement with blank lines,
7
#
1185.36.4 by Daniel Silverstone
Add FTP transport
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
1887.1.1 by Adeodato Simó
Do not separate paragraphs in the copyright statement with blank lines,
12
#
1185.36.4 by Daniel Silverstone
Add FTP transport
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
"""Implementation of Transport over ftp.
17
18
Written by Daniel Silverstone <dsilvers@digital-scurf.org> with serious
19
cargo-culting from the sftp transport and the http transport.
20
21
It provides the ftp:// and aftp:// protocols where ftp:// is passive ftp
22
and aftp:// is active ftp. Most people will want passive ftp for traversing
23
NAT and other firewalls, so it's best to use it unless you explicitly want
24
active, in which case aftp:// will be your friend.
25
"""
26
27
from cStringIO import StringIO
1707.3.1 by John Arbash Meinel
Have a FtpServer, but the tests fail
28
import asyncore
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.
29
import errno
1185.36.4 by Daniel Silverstone
Add FTP transport
30
import ftplib
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
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.
33
import urllib
1185.36.4 by Daniel Silverstone
Add FTP transport
34
import urlparse
2413.3.1 by John Arbash Meinel
Trap EBADF during shutdown of asyncore.loop()
35
import select
1185.36.4 by Daniel Silverstone
Add FTP transport
36
import stat
1707.3.1 by John Arbash Meinel
Have a FtpServer, but the tests fail
37
import threading
1185.72.14 by Matthieu Moy
Sleep between retries when the connection closes
38
import time
1185.72.13 by Matthieu Moy
Make ftp put atomic
39
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.
40
from warnings import warn
41
1948.1.7 by John Arbash Meinel
Fix FtpTransport so that it raises UnicodeError and skips the unicode path tests
42
from bzrlib import (
43
    errors,
1551.15.2 by Aaron Bentley
FTP does not require atomic rename. Fixes #89436
44
    osutils,
1948.1.7 by John Arbash Meinel
Fix FtpTransport so that it raises UnicodeError and skips the unicode path tests
45
    urlutils,
46
    )
47
from bzrlib.trace import mutter, warning
1707.3.4 by John Arbash Meinel
Moved most of sftp.split_url into a Transport function.
48
from bzrlib.transport import (
49
    Server,
50
    split_url,
1948.1.7 by John Arbash Meinel
Fix FtpTransport so that it raises UnicodeError and skips the unicode path tests
51
    Transport,
1707.3.4 by John Arbash Meinel
Moved most of sftp.split_url into a Transport function.
52
    )
2413.1.1 by John Arbash Meinel
Implement TestCaseWithFTPServer using the new shiny Feature mechanism.
53
from bzrlib.transport.local import LocalURLServer
1707.3.24 by John Arbash Meinel
Add prompt for password for ftp.
54
import bzrlib.ui
1551.3.11 by Aaron Bentley
Merge from Robert
55
1707.3.30 by John Arbash Meinel
Only load medusa if you are running the test suite.
56
_have_medusa = False
57
1551.3.11 by Aaron Bentley
Merge from Robert
58
1707.3.25 by John Arbash Meinel
Setting some default exception classes, which can help a lot.
59
class FtpPathError(errors.PathError):
60
    """FTP failed for path: %(path)s%(extra)s"""
61
62
1551.3.11 by Aaron Bentley
Merge from Robert
63
_FTP_cache = {}
1707.3.4 by John Arbash Meinel
Moved most of sftp.split_url into a Transport function.
64
def _find_FTP(hostname, port, username, password, is_active):
1551.3.11 by Aaron Bentley
Merge from Robert
65
    """Find an ftplib.FTP instance attached to this triplet."""
1707.3.4 by John Arbash Meinel
Moved most of sftp.split_url into a Transport function.
66
    key = (hostname, port, username, password, is_active)
67
    alt_key = (hostname, port, username, '********', is_active)
1551.3.11 by Aaron Bentley
Merge from Robert
68
    if key not in _FTP_cache:
1707.3.4 by John Arbash Meinel
Moved most of sftp.split_url into a Transport function.
69
        mutter("Constructing FTP instance against %r" % (alt_key,))
1707.3.5 by John Arbash Meinel
Adding port to ftp connections, and not logging the ftp password
70
        conn = ftplib.FTP()
71
72
        conn.connect(host=hostname, port=port)
1707.3.24 by John Arbash Meinel
Add prompt for password for ftp.
73
        if username and username != 'anonymous' and not password:
74
            password = bzrlib.ui.ui_factory.get_password(
75
                prompt='FTP %(user)s@%(host)s password',
76
                user=username, host=hostname)
1707.3.5 by John Arbash Meinel
Adding port to ftp connections, and not logging the ftp password
77
        conn.login(user=username, passwd=password)
78
        conn.set_pasv(not is_active)
79
80
        _FTP_cache[key] = conn
81
1551.3.11 by Aaron Bentley
Merge from Robert
82
    return _FTP_cache[key]    
83
84
1185.36.4 by Daniel Silverstone
Add FTP transport
85
class FtpStatResult(object):
86
    def __init__(self, f, relpath):
87
        try:
88
            self.st_size = f.size(relpath)
89
            self.st_mode = stat.S_IFREG
90
        except ftplib.error_perm:
91
            pwd = f.pwd()
92
            try:
93
                f.cwd(relpath)
94
                self.st_mode = stat.S_IFDIR
95
            finally:
96
                f.cwd(pwd)
97
98
1185.72.15 by Matthieu Moy
better error messages on ftp failures
99
_number_of_retries = 2
1185.72.14 by Matthieu Moy
Sleep between retries when the connection closes
100
_sleep_between_retries = 5
1185.72.12 by Matthieu Moy
made __number_of_retries global
101
1185.36.4 by Daniel Silverstone
Add FTP transport
102
class FtpTransport(Transport):
103
    """This is the transport agent for ftp:// access."""
104
105
    def __init__(self, base, _provided_instance=None):
106
        """Set the base path where files will be stored."""
107
        assert base.startswith('ftp://') or base.startswith('aftp://')
1707.3.4 by John Arbash Meinel
Moved most of sftp.split_url into a Transport function.
108
1185.36.4 by Daniel Silverstone
Add FTP transport
109
        self.is_active = base.startswith('aftp://')
110
        if self.is_active:
1707.3.4 by John Arbash Meinel
Moved most of sftp.split_url into a Transport function.
111
            # urlparse won't handle aftp://
1185.36.4 by Daniel Silverstone
Add FTP transport
112
            base = base[1:]
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.
113
        if not base.endswith('/'):
114
            base += '/'
1707.3.4 by John Arbash Meinel
Moved most of sftp.split_url into a Transport function.
115
        (self._proto, self._username,
116
            self._password, self._host,
117
            self._port, self._path) = split_url(base)
118
        base = self._unparse_url()
119
120
        super(FtpTransport, self).__init__(base)
1185.36.4 by Daniel Silverstone
Add FTP transport
121
        self._FTP_instance = _provided_instance
122
1707.3.4 by John Arbash Meinel
Moved most of sftp.split_url into a Transport function.
123
    def _unparse_url(self, path=None):
124
        if path is None:
125
            path = self._path
126
        path = urllib.quote(path)
127
        netloc = urllib.quote(self._host)
128
        if self._username is not None:
129
            netloc = '%s@%s' % (urllib.quote(self._username), netloc)
130
        if self._port is not None:
131
            netloc = '%s:%d' % (netloc, self._port)
1952.1.1 by John Arbash Meinel
Ghozzy: Fix Bzr's support of Active FTP (aftp://)
132
        proto = 'ftp'
133
        if self.is_active:
134
            proto = 'aftp'
135
        return urlparse.urlunparse((proto, netloc, path, '', '', ''))
1707.3.4 by John Arbash Meinel
Moved most of sftp.split_url into a Transport function.
136
1185.36.4 by Daniel Silverstone
Add FTP transport
137
    def _get_FTP(self):
138
        """Return the ftplib.FTP instance for this object."""
139
        if self._FTP_instance is not None:
140
            return self._FTP_instance
141
        
142
        try:
1707.3.4 by John Arbash Meinel
Moved most of sftp.split_url into a Transport function.
143
            self._FTP_instance = _find_FTP(self._host, self._port,
144
                                           self._username, self._password,
1551.3.11 by Aaron Bentley
Merge from Robert
145
                                           self.is_active)
1185.36.4 by Daniel Silverstone
Add FTP transport
146
            return self._FTP_instance
147
        except ftplib.error_perm, e:
1707.3.13 by John Arbash Meinel
Changing raise Error to raise errors.Error
148
            raise errors.TransportError(msg="Error setting up connection: %s"
1185.36.4 by Daniel Silverstone
Add FTP transport
149
                                    % str(e), orig_error=e)
150
1707.3.25 by John Arbash Meinel
Setting some default exception classes, which can help a lot.
151
    def _translate_perm_error(self, err, path, extra=None, unknown_exc=FtpPathError):
1707.3.19 by John Arbash Meinel
For some reason after RNTO we get a failure for has()
152
        """Try to translate an ftplib.error_perm exception.
153
154
        :param err: The error to translate into a bzr error
155
        :param path: The path which had problems
156
        :param extra: Extra information which can be included
157
        :param unknown_exc: If None, we will just raise the original exception
158
                    otherwise we raise unknown_exc(path, extra=extra)
159
        """
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.
160
        s = str(err).lower()
161
        if not extra:
162
            extra = str(err)
1707.3.25 by John Arbash Meinel
Setting some default exception classes, which can help a lot.
163
        else:
164
            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.
165
        if ('no such file' in s
1707.3.13 by John Arbash Meinel
Changing raise Error to raise errors.Error
166
            or 'could not open' in s
167
            or 'no such dir' in s
2338.1.1 by John Arbash Meinel
(Arnaud Fontaine) Compatibility with vsftpd error messages.
168
            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.
169
            or 'file doesn\'t exist' in s
1707.3.13 by John Arbash Meinel
Changing raise Error to raise errors.Error
170
            ):
171
            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.
172
        if ('file exists' in s):
1707.3.15 by John Arbash Meinel
Handle trying to list a file instead of a dir
173
            raise errors.FileExists(path, extra=extra)
174
        if ('not a directory' in s):
175
            raise errors.PathError(path, extra=extra)
176
1707.3.18 by John Arbash Meinel
Fix error handling for ftp.put()
177
        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()
178
179
        if unknown_exc:
180
            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.
181
        # TODO: jam 20060516 Consider re-raising the error wrapped in 
182
        #       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
183
        #       Also, 'sftp' has a generic 'Failure' mode, which we use failure_exc
184
        #       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.
185
        #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
186
        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.
187
1185.36.4 by Daniel Silverstone
Add FTP transport
188
    def should_cache(self):
189
        """Return True if the data pulled across should be cached locally.
190
        """
191
        return True
192
193
    def clone(self, offset=None):
194
        """Return a new FtpTransport with root at self.base + offset.
195
        """
196
        mutter("FTP clone")
197
        if offset is None:
198
            return FtpTransport(self.base, self._FTP_instance)
199
        else:
200
            return FtpTransport(self.abspath(offset), self._FTP_instance)
201
202
    def _abspath(self, relpath):
203
        assert isinstance(relpath, basestring)
1948.1.7 by John Arbash Meinel
Fix FtpTransport so that it raises UnicodeError and skips the unicode path tests
204
        relpath = urlutils.unescape(relpath)
1910.15.11 by Andrew Bennetts
Fix the FTP transport's handling of abspath('/')
205
        if relpath.startswith('/'):
206
            basepath = []
207
        else:
208
            basepath = self._path.split('/')
1185.36.4 by Daniel Silverstone
Add FTP transport
209
        if len(basepath) > 0 and basepath[-1] == '':
210
            basepath = basepath[:-1]
1910.15.11 by Andrew Bennetts
Fix the FTP transport's handling of abspath('/')
211
        for p in relpath.split('/'):
1185.36.4 by Daniel Silverstone
Add FTP transport
212
            if p == '..':
213
                if len(basepath) == 0:
214
                    # In most filesystems, a request for the parent
215
                    # of root, just returns root.
216
                    continue
217
                basepath.pop()
218
            elif p == '.' or p == '':
219
                continue # No-op
220
            else:
221
                basepath.append(p)
222
        # Possibly, we could use urlparse.urljoin() here, but
223
        # I'm concerned about when it chooses to strip the last
224
        # portion of the path, and when it doesn't.
1948.1.7 by John Arbash Meinel
Fix FtpTransport so that it raises UnicodeError and skips the unicode path tests
225
226
        # XXX: It seems that ftplib does not handle Unicode paths
227
        # at the same time, medusa won't handle utf8 paths
228
        # So if we .encode(utf8) here, then we get a Server failure.
229
        # while if we use str(), we get a UnicodeError, and the test suite
230
        # just skips testing UnicodePaths.
231
        return str('/'.join(basepath) or '/')
1185.36.4 by Daniel Silverstone
Add FTP transport
232
    
233
    def abspath(self, relpath):
234
        """Return the full url to the given relative path.
235
        This can be supplied with a string or a list
236
        """
237
        path = self._abspath(relpath)
1707.3.4 by John Arbash Meinel
Moved most of sftp.split_url into a Transport function.
238
        return self._unparse_url(path)
1185.36.4 by Daniel Silverstone
Add FTP transport
239
240
    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.
241
        """Does the target location exist?"""
242
        # FIXME jam 20060516 We *do* ask about directories in the test suite
243
        #       We don't seem to in the actual codebase
244
        # XXX: I assume we're never asked has(dirname) and thus I use
245
        # the FTP size command and assume that if it doesn't raise,
246
        # all is good.
247
        abspath = self._abspath(relpath)
1185.36.4 by Daniel Silverstone
Add FTP transport
248
        try:
249
            f = self._get_FTP()
1707.3.19 by John Arbash Meinel
For some reason after RNTO we get a failure for has()
250
            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.
251
            s = f.size(abspath)
252
            mutter("FTP has: %s", abspath)
1185.36.4 by Daniel Silverstone
Add FTP transport
253
            return True
1707.3.16 by John Arbash Meinel
Overloading cmd_size and cmd_mkd so that the test suite can handle directories.
254
        except ftplib.error_perm, e:
255
            if ('is a directory' in str(e).lower()):
256
                mutter("FTP has dir: %s: %s", abspath, e)
257
                return True
258
            mutter("FTP has not: %s: %s", abspath, e)
1185.36.4 by Daniel Silverstone
Add FTP transport
259
            return False
260
2164.2.15 by Vincent Ladeuil
Http redirections are not followed by default. Do not use hints
261
    def get(self, relpath, decode=False, retries=0):
1185.36.4 by Daniel Silverstone
Add FTP transport
262
        """Get the file at the given relative path.
263
264
        :param relpath: The relative path to the file
1185.72.9 by Matthieu Moy
suggestions by jam and robertc
265
        :param retries: Number of retries after temporary failures so far
266
                        for this operation.
1185.36.4 by Daniel Silverstone
Add FTP transport
267
268
        We're meant to return a file-like object which bzr will
269
        then read from. For now we do this via the magic of StringIO
270
        """
1540.3.6 by Martin Pool
[merge] update from bzr.dev
271
        # TODO: decode should be deprecated
1185.36.4 by Daniel Silverstone
Add FTP transport
272
        try:
1707.3.8 by John Arbash Meinel
[patch] Alexandre Saint: enable append support for ftp
273
            mutter("FTP get: %s", self._abspath(relpath))
1185.36.4 by Daniel Silverstone
Add FTP transport
274
            f = self._get_FTP()
275
            ret = StringIO()
276
            f.retrbinary('RETR '+self._abspath(relpath), ret.write, 8192)
277
            ret.seek(0)
278
            return ret
279
        except ftplib.error_perm, e:
1707.3.13 by John Arbash Meinel
Changing raise Error to raise errors.Error
280
            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
281
        except ftplib.error_temp, e:
1185.72.15 by Matthieu Moy
better error messages on ftp failures
282
            if retries > _number_of_retries:
1707.3.13 by John Arbash Meinel
Changing raise Error to raise errors.Error
283
                raise errors.TransportError(msg="FTP temporary error during GET %s. Aborting."
1185.72.15 by Matthieu Moy
better error messages on ftp failures
284
                                     % self.abspath(relpath),
285
                                     orig_error=e)
1185.72.8 by Matthieu Moy
try to solve the problem of ftp servers closing the connection
286
            else:
1707.3.8 by John Arbash Meinel
[patch] Alexandre Saint: enable append support for ftp
287
                warning("FTP temporary error: %s. Retrying.", str(e))
1185.72.8 by Matthieu Moy
try to solve the problem of ftp servers closing the connection
288
                self._FTP_instance = None
289
                return self.get(relpath, decode, retries+1)
1185.72.15 by Matthieu Moy
better error messages on ftp failures
290
        except EOFError, e:
1185.72.9 by Matthieu Moy
suggestions by jam and robertc
291
            if retries > _number_of_retries:
1707.3.13 by John Arbash Meinel
Changing raise Error to raise errors.Error
292
                raise errors.TransportError("FTP control connection closed during GET %s."
1185.72.15 by Matthieu Moy
better error messages on ftp failures
293
                                     % self.abspath(relpath),
294
                                     orig_error=e)
1185.72.8 by Matthieu Moy
try to solve the problem of ftp servers closing the connection
295
            else:
1185.72.9 by Matthieu Moy
suggestions by jam and robertc
296
                warning("FTP control connection closed. Trying to reopen.")
1185.72.14 by Matthieu Moy
Sleep between retries when the connection closes
297
                time.sleep(_sleep_between_retries)
1185.72.8 by Matthieu Moy
try to solve the problem of ftp servers closing the connection
298
                self._FTP_instance = None
299
                return self.get(relpath, decode, retries+1)
1185.36.4 by Daniel Silverstone
Add FTP transport
300
1955.3.6 by John Arbash Meinel
Lots of deprecation warnings, but no errors
301
    def put_file(self, relpath, fp, mode=None, retries=0):
1185.36.4 by Daniel Silverstone
Add FTP transport
302
        """Copy the file-like or string object into the location.
303
304
        :param relpath: Location to put the contents, relative to base.
1185.72.13 by Matthieu Moy
Make ftp put atomic
305
        :param fp:       File-like or string object.
1185.72.9 by Matthieu Moy
suggestions by jam and robertc
306
        :param retries: Number of retries after temporary failures so far
307
                        for this operation.
308
1551.15.2 by Aaron Bentley
FTP does not require atomic rename. Fixes #89436
309
        TODO: jam 20051215 ftp as a protocol seems to support chmod, but
310
        ftplib does not
1185.36.4 by Daniel Silverstone
Add FTP transport
311
        """
1707.3.18 by John Arbash Meinel
Fix error handling for ftp.put()
312
        abspath = self._abspath(relpath)
313
        tmp_abspath = '%s.tmp.%.9f.%d.%d' % (abspath, time.time(),
1185.72.13 by Matthieu Moy
Make ftp put atomic
314
                        os.getpid(), random.randint(0,0x7FFFFFFF))
1963.2.6 by Robey Pointer
pychecker is on crack; go back to using 'is None'.
315
        if getattr(fp, 'read', None) is None:
1185.36.4 by Daniel Silverstone
Add FTP transport
316
            fp = StringIO(fp)
317
        try:
1707.3.18 by John Arbash Meinel
Fix error handling for ftp.put()
318
            mutter("FTP put: %s", abspath)
1185.36.4 by Daniel Silverstone
Add FTP transport
319
            f = self._get_FTP()
1185.72.13 by Matthieu Moy
Make ftp put atomic
320
            try:
321
                f.storbinary('STOR '+tmp_abspath, fp)
1551.15.2 by Aaron Bentley
FTP does not require atomic rename. Fixes #89436
322
                self._rename_and_overwrite(tmp_abspath, abspath, f)
1185.72.13 by Matthieu Moy
Make ftp put atomic
323
            except (ftplib.error_temp,EOFError), e:
1185.72.15 by Matthieu Moy
better error messages on ftp failures
324
                warning("Failure during ftp PUT. Deleting temporary file.")
1185.72.13 by Matthieu Moy
Make ftp put atomic
325
                try:
326
                    f.delete(tmp_abspath)
327
                except:
1707.3.8 by John Arbash Meinel
[patch] Alexandre Saint: enable append support for ftp
328
                    warning("Failed to delete temporary file on the"
329
                            " server.\nFile: %s", tmp_abspath)
1185.72.13 by Matthieu Moy
Make ftp put atomic
330
                    raise e
331
                raise
1185.36.4 by Daniel Silverstone
Add FTP transport
332
        except ftplib.error_perm, e:
2570.1.1 by John Arbash Meinel
(John Arbash Meinel) A couple small updates for pushing over ftp.
333
            self._translate_perm_error(e, abspath, extra='could not store',
334
                                       unknown_exc=errors.NoSuchFile)
1185.72.8 by Matthieu Moy
try to solve the problem of ftp servers closing the connection
335
        except ftplib.error_temp, e:
1185.72.9 by Matthieu Moy
suggestions by jam and robertc
336
            if retries > _number_of_retries:
1707.3.13 by John Arbash Meinel
Changing raise Error to raise errors.Error
337
                raise errors.TransportError("FTP temporary error during PUT %s. Aborting."
1185.72.15 by Matthieu Moy
better error messages on ftp failures
338
                                     % self.abspath(relpath), orig_error=e)
1185.72.8 by Matthieu Moy
try to solve the problem of ftp servers closing the connection
339
            else:
1707.3.8 by John Arbash Meinel
[patch] Alexandre Saint: enable append support for ftp
340
                warning("FTP temporary error: %s. Retrying.", str(e))
1185.72.8 by Matthieu Moy
try to solve the problem of ftp servers closing the connection
341
                self._FTP_instance = None
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.9 by Matthieu Moy
suggestions by jam and robertc
343
        except EOFError:
1185.72.10 by Matthieu Moy
One 1 -> _number_of_retries was missing
344
            if retries > _number_of_retries:
1707.3.13 by John Arbash Meinel
Changing raise Error to raise errors.Error
345
                raise errors.TransportError("FTP control connection closed during PUT %s."
1185.72.15 by Matthieu Moy
better error messages on ftp failures
346
                                     % self.abspath(relpath), orig_error=e)
1185.72.8 by Matthieu Moy
try to solve the problem of ftp servers closing the connection
347
            else:
1185.72.14 by Matthieu Moy
Sleep between retries when the connection closes
348
                warning("FTP control connection closed. Trying to reopen.")
349
                time.sleep(_sleep_between_retries)
1185.72.8 by Matthieu Moy
try to solve the problem of ftp servers closing the connection
350
                self._FTP_instance = None
1955.3.6 by John Arbash Meinel
Lots of deprecation warnings, but no errors
351
                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
352
1185.58.2 by John Arbash Meinel
Added mode to the appropriate transport functions, and tests to make sure they work.
353
    def mkdir(self, relpath, mode=None):
1185.36.4 by Daniel Silverstone
Add FTP transport
354
        """Create a directory at the given path."""
1707.3.14 by John Arbash Meinel
Cleanup mkdir and rmdir error handling.
355
        abspath = self._abspath(relpath)
1185.36.4 by Daniel Silverstone
Add FTP transport
356
        try:
1707.3.14 by John Arbash Meinel
Cleanup mkdir and rmdir error handling.
357
            mutter("FTP mkd: %s", abspath)
1185.36.4 by Daniel Silverstone
Add FTP transport
358
            f = self._get_FTP()
1707.3.14 by John Arbash Meinel
Cleanup mkdir and rmdir error handling.
359
            f.mkd(abspath)
1185.36.4 by Daniel Silverstone
Add FTP transport
360
        except ftplib.error_perm, e:
1707.3.25 by John Arbash Meinel
Setting some default exception classes, which can help a lot.
361
            self._translate_perm_error(e, abspath,
362
                unknown_exc=errors.FileExists)
1185.36.4 by Daniel Silverstone
Add FTP transport
363
1658.1.1 by Martin Pool
Fix FTP push with metadir format (Alexandre Saint)
364
    def rmdir(self, rel_path):
365
        """Delete the directory at rel_path"""
1707.3.14 by John Arbash Meinel
Cleanup mkdir and rmdir error handling.
366
        abspath = self._abspath(rel_path)
1658.1.1 by Martin Pool
Fix FTP push with metadir format (Alexandre Saint)
367
        try:
1707.3.14 by John Arbash Meinel
Cleanup mkdir and rmdir error handling.
368
            mutter("FTP rmd: %s", abspath)
1551.15.4 by Aaron Bentley
Revert now-unnecessary changes
369
            f = self._get_FTP()
1707.3.14 by John Arbash Meinel
Cleanup mkdir and rmdir error handling.
370
            f.rmd(abspath)
1658.1.1 by Martin Pool
Fix FTP push with metadir format (Alexandre Saint)
371
        except ftplib.error_perm, e:
1551.15.4 by Aaron Bentley
Revert now-unnecessary changes
372
            self._translate_perm_error(e, abspath, unknown_exc=errors.PathError)
1658.1.1 by Martin Pool
Fix FTP push with metadir format (Alexandre Saint)
373
1955.3.15 by John Arbash Meinel
Deprecate 'Transport.append' in favor of Transport.append_file or Transport.append_bytes
374
    def append_file(self, relpath, f, mode=None):
1185.36.4 by Daniel Silverstone
Add FTP transport
375
        """Append the text in the file-like object into the final
376
        location.
377
        """
1707.3.19 by John Arbash Meinel
For some reason after RNTO we get a failure for has()
378
        abspath = self._abspath(relpath)
1707.3.8 by John Arbash Meinel
[patch] Alexandre Saint: enable append support for ftp
379
        if self.has(relpath):
380
            ftp = self._get_FTP()
1707.3.19 by John Arbash Meinel
For some reason after RNTO we get a failure for has()
381
            result = ftp.size(abspath)
1707.3.8 by John Arbash Meinel
[patch] Alexandre Saint: enable append support for ftp
382
        else:
383
            result = 0
384
1707.3.19 by John Arbash Meinel
For some reason after RNTO we get a failure for has()
385
        mutter("FTP appe to %s", abspath)
1707.3.8 by John Arbash Meinel
[patch] Alexandre Saint: enable append support for ftp
386
        self._try_append(relpath, f.read(), mode)
387
388
        return result
389
390
    def _try_append(self, relpath, text, mode=None, retries=0):
391
        """Try repeatedly to append the given text to the file at relpath.
392
        
393
        This is a recursive function. On errors, it will be called until the
394
        number of retries is exceeded.
395
        """
396
        try:
397
            abspath = self._abspath(relpath)
398
            mutter("FTP appe (try %d) to %s", retries, abspath)
399
            ftp = self._get_FTP()
400
            ftp.voidcmd("TYPE I")
401
            cmd = "APPE %s" % abspath
402
            conn = ftp.transfercmd(cmd)
403
            conn.sendall(text)
404
            conn.close()
1707.3.25 by John Arbash Meinel
Setting some default exception classes, which can help a lot.
405
            if mode:
1707.3.8 by John Arbash Meinel
[patch] Alexandre Saint: enable append support for ftp
406
                self._setmode(relpath, mode)
407
            ftp.getresp()
408
        except ftplib.error_perm, e:
1707.3.26 by John Arbash Meinel
Added a default exception for append
409
            self._translate_perm_error(e, abspath, extra='error appending',
410
                unknown_exc=errors.NoSuchFile)
1707.3.8 by John Arbash Meinel
[patch] Alexandre Saint: enable append support for ftp
411
        except ftplib.error_temp, e:
412
            if retries > _number_of_retries:
1707.3.13 by John Arbash Meinel
Changing raise Error to raise errors.Error
413
                raise errors.TransportError("FTP temporary error during APPEND %s." \
1707.3.8 by John Arbash Meinel
[patch] Alexandre Saint: enable append support for ftp
414
                        "Aborting." % abspath, orig_error=e)
415
            else:
416
                warning("FTP temporary error: %s. Retrying.", str(e))
417
                self._FTP_instance = None
418
                self._try_append(relpath, text, mode, retries+1)
419
420
    def _setmode(self, relpath, mode):
421
        """Set permissions on a path.
422
423
        Only set permissions if the FTP server supports the 'SITE CHMOD'
424
        extension.
425
        """
426
        try:
427
            mutter("FTP site chmod: setting permissions to %s on %s",
428
                str(mode), self._abspath(relpath))
429
            ftp = self._get_FTP()
430
            cmd = "SITE CHMOD %s %s" % (self._abspath(relpath), str(mode))
431
            ftp.sendcmd(cmd)
432
        except ftplib.error_perm, e:
433
            # Command probably not available on this server
434
            warning("FTP Could not set permissions to %s on %s. %s",
435
                    str(mode), self._abspath(relpath), str(e))
1185.36.4 by Daniel Silverstone
Add FTP transport
436
1707.3.11 by John Arbash Meinel
fixing more tests.
437
    # TODO: jam 20060516 I believe ftp allows you to tell an ftp server
438
    #       to copy something to another machine. And you may be able
439
    #       to give it its own address as the 'to' location.
440
    #       So implement a fancier 'copy()'
1185.36.4 by Daniel Silverstone
Add FTP transport
441
1551.15.3 by Aaron Bentley
Separate FtpTransport.rename from FtpTransport.move
442
    def rename(self, rel_from, rel_to):
443
        abs_from = self._abspath(rel_from)
444
        abs_to = self._abspath(rel_to)
445
        mutter("FTP rename: %s => %s", abs_from, abs_to)
446
        f = self._get_FTP()
447
        return self._rename(abs_from, abs_to, f)
448
449
    def _rename(self, abs_from, abs_to, f):
450
        try:
451
            f.rename(abs_from, abs_to)
452
        except ftplib.error_perm, e:
453
            self._translate_perm_error(e, abs_from,
454
                ': unable to rename to %r' % (abs_to))
455
1185.36.4 by Daniel Silverstone
Add FTP transport
456
    def move(self, rel_from, rel_to):
457
        """Move the item at rel_from to the location at rel_to"""
1707.3.19 by John Arbash Meinel
For some reason after RNTO we get a failure for has()
458
        abs_from = self._abspath(rel_from)
459
        abs_to = self._abspath(rel_to)
1185.36.4 by Daniel Silverstone
Add FTP transport
460
        try:
1707.3.19 by John Arbash Meinel
For some reason after RNTO we get a failure for has()
461
            mutter("FTP mv: %s => %s", abs_from, abs_to)
1185.36.4 by Daniel Silverstone
Add FTP transport
462
            f = self._get_FTP()
1551.15.2 by Aaron Bentley
FTP does not require atomic rename. Fixes #89436
463
            self._rename_and_overwrite(abs_from, abs_to, f)
1185.36.4 by Daniel Silverstone
Add FTP transport
464
        except ftplib.error_perm, e:
1707.3.19 by John Arbash Meinel
For some reason after RNTO we get a failure for has()
465
            self._translate_perm_error(e, abs_from,
466
                extra='unable to rename to %r' % (rel_to,), 
467
                unknown_exc=errors.PathError)
1185.36.4 by Daniel Silverstone
Add FTP transport
468
1551.15.2 by Aaron Bentley
FTP does not require atomic rename. Fixes #89436
469
    def _rename_and_overwrite(self, abs_from, abs_to, f):
470
        """Do a fancy rename on the remote server.
471
472
        Using the implementation provided by osutils.
473
        """
1551.15.3 by Aaron Bentley
Separate FtpTransport.rename from FtpTransport.move
474
        osutils.fancy_rename(abs_from, abs_to,
1551.15.4 by Aaron Bentley
Revert now-unnecessary changes
475
            rename_func=lambda p1, p2: self._rename(p1, p2, f),
476
            unlink_func=lambda p: self._delete(p, f))
1551.15.2 by Aaron Bentley
FTP does not require atomic rename. Fixes #89436
477
1185.36.4 by Daniel Silverstone
Add FTP transport
478
    def delete(self, relpath):
479
        """Delete the item at relpath"""
1707.3.11 by John Arbash Meinel
fixing more tests.
480
        abspath = self._abspath(relpath)
1551.15.2 by Aaron Bentley
FTP does not require atomic rename. Fixes #89436
481
        f = self._get_FTP()
482
        self._delete(abspath, f)
483
484
    def _delete(self, abspath, f):
1185.36.4 by Daniel Silverstone
Add FTP transport
485
        try:
1707.3.11 by John Arbash Meinel
fixing more tests.
486
            mutter("FTP rm: %s", abspath)
487
            f.delete(abspath)
1185.36.4 by Daniel Silverstone
Add FTP transport
488
        except ftplib.error_perm, e:
1707.3.25 by John Arbash Meinel
Setting some default exception classes, which can help a lot.
489
            self._translate_perm_error(e, abspath, 'error deleting',
490
                unknown_exc=errors.NoSuchFile)
1185.36.4 by Daniel Silverstone
Add FTP transport
491
492
    def listable(self):
493
        """See Transport.listable."""
494
        return True
495
496
    def list_dir(self, relpath):
497
        """See Transport.list_dir."""
1959.2.5 by John Arbash Meinel
Small fixes in ftp.list_dir()
498
        basepath = self._abspath(relpath)
499
        mutter("FTP nlst: %s", basepath)
1959.2.1 by John Arbash Meinel
David Allouche: Make transports return escaped paths
500
        f = self._get_FTP()
1185.36.4 by Daniel Silverstone
Add FTP transport
501
        try:
1707.3.8 by John Arbash Meinel
[patch] Alexandre Saint: enable append support for ftp
502
            paths = f.nlst(basepath)
1185.36.4 by Daniel Silverstone
Add FTP transport
503
        except ftplib.error_perm, e:
1707.3.13 by John Arbash Meinel
Changing raise Error to raise errors.Error
504
            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
505
        # If FTP.nlst returns paths prefixed by relpath, strip 'em
506
        if paths and paths[0].startswith(basepath):
507
            entries = [path[len(basepath)+1:] for path in paths]
508
        else:
509
            entries = paths
510
        # Remove . and .. if present
1959.2.5 by John Arbash Meinel
Small fixes in ftp.list_dir()
511
        return [urlutils.escape(entry) for entry in entries
512
                if entry not in ('.', '..')]
1185.36.4 by Daniel Silverstone
Add FTP transport
513
514
    def iter_files_recursive(self):
515
        """See Transport.iter_files_recursive.
516
517
        This is cargo-culted from the SFTP transport"""
518
        mutter("FTP iter_files_recursive")
519
        queue = list(self.list_dir("."))
520
        while queue:
1959.2.1 by John Arbash Meinel
David Allouche: Make transports return escaped paths
521
            relpath = queue.pop(0)
1185.36.4 by Daniel Silverstone
Add FTP transport
522
            st = self.stat(relpath)
523
            if stat.S_ISDIR(st.st_mode):
524
                for i, basename in enumerate(self.list_dir(relpath)):
525
                    queue.insert(i, relpath+"/"+basename)
526
            else:
527
                yield relpath
528
529
    def stat(self, relpath):
1707.3.13 by John Arbash Meinel
Changing raise Error to raise errors.Error
530
        """Return the stat information for a file."""
531
        abspath = self._abspath(relpath)
1185.36.4 by Daniel Silverstone
Add FTP transport
532
        try:
1707.3.13 by John Arbash Meinel
Changing raise Error to raise errors.Error
533
            mutter("FTP stat: %s", abspath)
1185.36.4 by Daniel Silverstone
Add FTP transport
534
            f = self._get_FTP()
1707.3.13 by John Arbash Meinel
Changing raise Error to raise errors.Error
535
            return FtpStatResult(f, abspath)
1185.36.4 by Daniel Silverstone
Add FTP transport
536
        except ftplib.error_perm, e:
1707.3.13 by John Arbash Meinel
Changing raise Error to raise errors.Error
537
            self._translate_perm_error(e, abspath, extra='error w/ stat')
1185.36.4 by Daniel Silverstone
Add FTP transport
538
539
    def lock_read(self, relpath):
540
        """Lock the given file for shared (read) access.
541
        :return: A lock object, which should be passed to Transport.unlock()
542
        """
543
        # The old RemoteBranch ignore lock for reading, so we will
544
        # continue that tradition and return a bogus lock object.
545
        class BogusLock(object):
546
            def __init__(self, path):
547
                self.path = path
548
            def unlock(self):
549
                pass
550
        return BogusLock(relpath)
551
552
    def lock_write(self, relpath):
553
        """Lock the given file for exclusive (write) access.
554
        WARNING: many transports do not support this, so trying avoid using it
555
556
        :return: A lock object, which should be passed to Transport.unlock()
557
        """
558
        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.
559
560
1707.3.30 by John Arbash Meinel
Only load medusa if you are running the test suite.
561
class FtpServer(Server):
2052.6.1 by Robert Collins
``Transport.get`` has had its interface made more clear for ease of use.
562
    """Common code for FTP server facilities."""
1707.3.30 by John Arbash Meinel
Only load medusa if you are running the test suite.
563
564
    def __init__(self):
565
        self._root = None
566
        self._ftp_server = None
567
        self._port = None
568
        self._async_thread = None
569
        # ftp server logs
570
        self.logs = []
571
572
    def get_url(self):
573
        """Calculate an ftp url to this server."""
574
        return 'ftp://foo:bar@localhost:%d/' % (self._port)
575
576
#    def get_bogus_url(self):
577
#        """Return a URL which cannot be connected to."""
578
#        return 'ftp://127.0.0.1:1'
579
580
    def log(self, message):
581
        """This is used by medusa.ftp_server to log connections, etc."""
582
        self.logs.append(message)
583
2413.1.1 by John Arbash Meinel
Implement TestCaseWithFTPServer using the new shiny Feature mechanism.
584
    def setUp(self, vfs_server=None):
1707.3.1 by John Arbash Meinel
Have a FtpServer, but the tests fail
585
        if not _have_medusa:
1707.3.30 by John Arbash Meinel
Only load medusa if you are running the test suite.
586
            raise RuntimeError('Must have medusa to run the FtpServer')
587
2413.1.1 by John Arbash Meinel
Implement TestCaseWithFTPServer using the new shiny Feature mechanism.
588
        assert vfs_server is None or isinstance(vfs_server, LocalURLServer), \
589
            "FtpServer currently assumes local transport, got %s" % vfs_server
590
1707.3.30 by John Arbash Meinel
Only load medusa if you are running the test suite.
591
        self._root = os.getcwdu()
592
        self._ftp_server = _ftp_server(
593
            authorizer=_test_authorizer(root=self._root),
594
            ip='localhost',
595
            port=0, # bind to a random port
596
            resolver=None,
597
            logger_object=self # Use FtpServer.log() for messages
598
            )
599
        self._port = self._ftp_server.getsockname()[1]
600
        # Don't let it loop forever, or handle an infinite number of requests.
601
        # In this case it will run for 100s, or 1000 requests
2413.3.1 by John Arbash Meinel
Trap EBADF during shutdown of asyncore.loop()
602
        self._async_thread = threading.Thread(
603
                target=FtpServer._asyncore_loop_ignore_EBADF,
1707.3.30 by John Arbash Meinel
Only load medusa if you are running the test suite.
604
                kwargs={'timeout':0.1, 'count':1000})
605
        self._async_thread.setDaemon(True)
606
        self._async_thread.start()
607
608
    def tearDown(self):
609
        """See bzrlib.transport.Server.tearDown."""
610
        # have asyncore release the channel
611
        self._ftp_server.del_channel()
612
        asyncore.close_all()
613
        self._async_thread.join()
614
2413.3.1 by John Arbash Meinel
Trap EBADF during shutdown of asyncore.loop()
615
    @staticmethod
616
    def _asyncore_loop_ignore_EBADF(*args, **kwargs):
2413.3.2 by John Arbash Meinel
cleanup comment (from Martin)
617
        """Ignore EBADF during server shutdown.
618
619
        We close the socket to get the server to shutdown, but this causes
620
        select.select() to raise EBADF.
621
        """
2413.3.1 by John Arbash Meinel
Trap EBADF during shutdown of asyncore.loop()
622
        try:
623
            asyncore.loop(*args, **kwargs)
624
        except select.error, e:
625
            if e.args[0] != errno.EBADF:
626
                raise
627
1707.3.30 by John Arbash Meinel
Only load medusa if you are running the test suite.
628
629
_ftp_channel = None
630
_ftp_server = None
631
_test_authorizer = None
632
633
634
def _setup_medusa():
635
    global _have_medusa, _ftp_channel, _ftp_server, _test_authorizer
636
    try:
637
        import medusa
638
        import medusa.filesys
639
        import medusa.ftp_server
640
    except ImportError:
641
        return False
642
643
    _have_medusa = True
644
645
    class test_authorizer(object):
646
        """A custom Authorizer object for running the test suite.
647
648
        The reason we cannot use dummy_authorizer, is because it sets the
649
        channel to readonly, which we don't always want to do.
650
        """
651
652
        def __init__(self, root):
653
            self.root = root
654
655
        def authorize(self, channel, username, password):
656
            """Return (success, reply_string, filesystem)"""
657
            if not _have_medusa:
658
                return 0, 'No Medusa.', None
659
660
            channel.persona = -1, -1
661
            if username == 'anonymous':
662
                channel.read_only = 1
663
            else:
664
                channel.read_only = 0
665
666
            return 1, 'OK.', medusa.filesys.os_filesystem(self.root)
667
668
669
    class ftp_channel(medusa.ftp_server.ftp_channel):
1707.3.7 by John Arbash Meinel
Creating child of ftp_channel to support renaming, need to add appending.
670
        """Customized ftp channel"""
671
1707.3.19 by John Arbash Meinel
For some reason after RNTO we get a failure for has()
672
        def log(self, message):
673
            """Redirect logging requests."""
674
            mutter('_ftp_channel: %s', message)
675
            
1707.3.7 by John Arbash Meinel
Creating child of ftp_channel to support renaming, need to add appending.
676
        def log_info(self, message, type='info'):
677
            """Redirect logging requests."""
678
            mutter('_ftp_channel %s: %s', type, message)
679
            
680
        def cmd_rnfr(self, line):
681
            """Prepare for renaming a file."""
682
            self._renaming = line[1]
683
            self.respond('350 Ready for RNTO')
684
            # TODO: jam 20060516 in testing, the ftp server seems to
685
            #       check that the file already exists, or it sends
686
            #       550 RNFR command failed
687
688
        def cmd_rnto(self, line):
689
            """Rename a file based on the target given.
690
691
            rnto must be called after calling rnfr.
692
            """
693
            if not self._renaming:
694
                self.respond('503 RNFR required first.')
695
            pfrom = self.filesystem.translate(self._renaming)
1707.3.19 by John Arbash Meinel
For some reason after RNTO we get a failure for has()
696
            self._renaming = None
1707.3.7 by John Arbash Meinel
Creating child of ftp_channel to support renaming, need to add appending.
697
            pto = self.filesystem.translate(line[1])
1551.15.2 by Aaron Bentley
FTP does not require atomic rename. Fixes #89436
698
            if os.path.exists(pto):
699
                self.respond('550 RNTO failed: file exists')
700
                return
1707.3.7 by John Arbash Meinel
Creating child of ftp_channel to support renaming, need to add appending.
701
            try:
702
                os.rename(pfrom, pto)
1707.3.19 by John Arbash Meinel
For some reason after RNTO we get a failure for has()
703
            except (IOError, OSError), e:
1707.3.7 by John Arbash Meinel
Creating child of ftp_channel to support renaming, need to add appending.
704
                # TODO: jam 20060516 return custom responses based on
705
                #       why the command failed
2423.2.2 by Alexander Belchenko
Explicit error messages for test FTP server (Python 2.5 @ win32 compatibility)
706
                # (bialix 20070418) str(e) on Python 2.5 @ Windows
707
                # sometimes don't provide expected error message;
708
                # so we obtain such message via os.strerror()
709
                self.respond('550 RNTO failed: %s' % os.strerror(e.errno))
1707.3.7 by John Arbash Meinel
Creating child of ftp_channel to support renaming, need to add appending.
710
            except:
711
                self.respond('550 RNTO failed')
712
                # For a test server, we will go ahead and just die
713
                raise
1707.3.20 by John Arbash Meinel
Found the problem, was sending 2 responses
714
            else:
715
                self.respond('250 Rename successful.')
1707.3.7 by John Arbash Meinel
Creating child of ftp_channel to support renaming, need to add appending.
716
1707.3.16 by John Arbash Meinel
Overloading cmd_size and cmd_mkd so that the test suite can handle directories.
717
        def cmd_size(self, line):
718
            """Return the size of a file
719
720
            This is overloaded to help the test suite determine if the 
721
            target is a directory.
722
            """
723
            filename = line[1]
724
            if not self.filesystem.isfile(filename):
725
                if self.filesystem.isdir(filename):
726
                    self.respond('550 "%s" is a directory' % (filename,))
727
                else:
728
                    self.respond('550 "%s" is not a file' % (filename,))
729
            else:
730
                self.respond('213 %d' 
1707.3.17 by John Arbash Meinel
Fix errors in cmd_size
731
                    % (self.filesystem.stat(filename)[stat.ST_SIZE]),)
1707.3.16 by John Arbash Meinel
Overloading cmd_size and cmd_mkd so that the test suite can handle directories.
732
733
        def cmd_mkd(self, line):
734
            """Create a directory.
735
736
            Overloaded because default implementation does not distinguish
737
            *why* it cannot make a directory.
738
            """
739
            if len (line) != 2:
1773.4.1 by Martin Pool
Add pyflakes makefile target; fix many warnings
740
                self.command_not_understood(''.join(line))
1707.3.16 by John Arbash Meinel
Overloading cmd_size and cmd_mkd so that the test suite can handle directories.
741
            else:
742
                path = line[1]
743
                try:
744
                    self.filesystem.mkdir (path)
745
                    self.respond ('257 MKD command successful.')
746
                except (IOError, OSError), e:
2423.2.2 by Alexander Belchenko
Explicit error messages for test FTP server (Python 2.5 @ win32 compatibility)
747
                    # (bialix 20070418) str(e) on Python 2.5 @ Windows
748
                    # sometimes don't provide expected error message;
749
                    # so we obtain such message via os.strerror()
750
                    self.respond ('550 error creating directory: %s' %
751
                                  os.strerror(e.errno))
1707.3.16 by John Arbash Meinel
Overloading cmd_size and cmd_mkd so that the test suite can handle directories.
752
                except:
753
                    self.respond ('550 error creating directory.')
754
1707.3.7 by John Arbash Meinel
Creating child of ftp_channel to support renaming, need to add appending.
755
1707.3.30 by John Arbash Meinel
Only load medusa if you are running the test suite.
756
    class ftp_server(medusa.ftp_server.ftp_server):
1707.3.2 by John Arbash Meinel
Removing stipple from ftp tests.
757
        """Customize the behavior of the Medusa ftp_server.
758
759
        There are a few warts on the ftp_server, based on how it expects
760
        to be used.
761
        """
1707.3.7 by John Arbash Meinel
Creating child of ftp_channel to support renaming, need to add appending.
762
        _renaming = None
1707.3.30 by John Arbash Meinel
Only load medusa if you are running the test suite.
763
        ftp_channel_class = ftp_channel
1707.3.7 by John Arbash Meinel
Creating child of ftp_channel to support renaming, need to add appending.
764
765
        def __init__(self, *args, **kwargs):
766
            mutter('Initializing _ftp_server: %r, %r', args, kwargs)
767
            medusa.ftp_server.ftp_server.__init__(self, *args, **kwargs)
1707.3.2 by John Arbash Meinel
Removing stipple from ftp tests.
768
1707.3.19 by John Arbash Meinel
For some reason after RNTO we get a failure for has()
769
        def log(self, message):
770
            """Redirect logging requests."""
1707.3.30 by John Arbash Meinel
Only load medusa if you are running the test suite.
771
            mutter('_ftp_server: %s', message)
1707.3.19 by John Arbash Meinel
For some reason after RNTO we get a failure for has()
772
1707.3.2 by John Arbash Meinel
Removing stipple from ftp tests.
773
        def log_info(self, message, type='info'):
774
            """Override the asyncore.log_info so we don't stipple the screen."""
1707.3.7 by John Arbash Meinel
Creating child of ftp_channel to support renaming, need to add appending.
775
            mutter('_ftp_server %s: %s', type, message)
1707.3.2 by John Arbash Meinel
Removing stipple from ftp tests.
776
1707.3.30 by John Arbash Meinel
Only load medusa if you are running the test suite.
777
    _test_authorizer = test_authorizer
778
    _ftp_channel = ftp_channel
779
    _ftp_server = ftp_server
780
781
    return True
1707.3.1 by John Arbash Meinel
Have a FtpServer, but the tests fail
782
783
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.
784
def get_test_permutations():
785
    """Return the permutations to be used in testing."""
1707.3.30 by John Arbash Meinel
Only load medusa if you are running the test suite.
786
    if not _setup_medusa():
1707.3.1 by John Arbash Meinel
Have a FtpServer, but the tests fail
787
        warn("You must install medusa (http://www.amk.ca/python/code/medusa.html) for FTP tests")
788
        return []
789
    else:
790
        return [(FtpTransport, FtpServer)]