/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/transport/ftp.py

[merge] update from bzr.dev

Show diffs side-by-side

added added

removed removed

Lines of Context:
24
24
active, in which case aftp:// will be your friend.
25
25
"""
26
26
 
27
 
from bzrlib.transport import Transport
28
 
 
29
 
from bzrlib.errors import (TransportNotPossible, TransportError,
30
 
                           NoSuchFile, FileExists)
31
 
 
32
 
import os, errno
33
27
from cStringIO import StringIO
 
28
import errno
34
29
import ftplib
 
30
import os
 
31
import urllib
35
32
import urlparse
36
 
import urllib
37
33
import stat
38
 
 
39
 
from bzrlib.branch import Branch
40
 
from bzrlib.trace import mutter
 
34
import time
 
35
import random
 
36
from warnings import warn
 
37
 
 
38
 
 
39
from bzrlib.transport import Transport
 
40
from bzrlib.errors import (TransportNotPossible, TransportError,
 
41
                           NoSuchFile, FileExists)
 
42
from bzrlib.trace import mutter, warning
 
43
 
 
44
 
 
45
_FTP_cache = {}
 
46
def _find_FTP(hostname, username, password, is_active):
 
47
    """Find an ftplib.FTP instance attached to this triplet."""
 
48
    key = "%s|%s|%s|%s" % (hostname, username, password, is_active)
 
49
    if key not in _FTP_cache:
 
50
        mutter("Constructing FTP instance against %r" % key)
 
51
        _FTP_cache[key] = ftplib.FTP(hostname, username, password)
 
52
        _FTP_cache[key].set_pasv(not is_active)
 
53
    return _FTP_cache[key]    
 
54
 
 
55
 
 
56
class FtpTransportError(TransportError):
 
57
    pass
41
58
 
42
59
 
43
60
class FtpStatResult(object):
54
71
                f.cwd(pwd)
55
72
 
56
73
 
 
74
_number_of_retries = 2
 
75
_sleep_between_retries = 5
 
76
 
57
77
class FtpTransport(Transport):
58
78
    """This is the transport agent for ftp:// access."""
59
79
 
69
89
            self._query, self._fragment) = urlparse.urlparse(self.base)
70
90
        self._FTP_instance = _provided_instance
71
91
 
72
 
 
73
92
    def _get_FTP(self):
74
93
        """Return the ftplib.FTP instance for this object."""
75
94
        if self._FTP_instance is not None:
84
103
            if ':' in username:
85
104
                username, password = username.split(":", 1)
86
105
 
87
 
            mutter("Constructing FTP instance")
88
 
            self._FTP_instance = ftplib.FTP(hostname, username, password)
89
 
            self._FTP_instance.set_pasv(not self.is_active)
 
106
            self._FTP_instance = _find_FTP(hostname, username, password,
 
107
                                           self.is_active)
90
108
            return self._FTP_instance
91
109
        except ftplib.error_perm, e:
92
110
            raise TransportError(msg="Error setting up connection: %s"
161
179
            mutter("FTP has not: %s" % self._abspath(relpath))
162
180
            return False
163
181
 
164
 
    def get(self, relpath):
 
182
    def get(self, relpath, decode=False, retries=0):
165
183
        """Get the file at the given relative path.
166
184
 
167
185
        :param relpath: The relative path to the file
 
186
        :param retries: Number of retries after temporary failures so far
 
187
                        for this operation.
168
188
 
169
189
        We're meant to return a file-like object which bzr will
170
190
        then read from. For now we do this via the magic of StringIO
171
191
        """
 
192
        # TODO: decode should be deprecated
172
193
        try:
173
194
            mutter("FTP get: %s" % self._abspath(relpath))
174
195
            f = self._get_FTP()
177
198
            ret.seek(0)
178
199
            return ret
179
200
        except ftplib.error_perm, e:
180
 
            raise NoSuchFile(self.abspath(relpath), extra=extra)
 
201
            raise NoSuchFile(self.abspath(relpath), extra=str(e))
 
202
        except ftplib.error_temp, e:
 
203
            if retries > _number_of_retries:
 
204
                raise TransportError(msg="FTP temporary error during GET %s. Aborting."
 
205
                                     % self.abspath(relpath),
 
206
                                     orig_error=e)
 
207
            else:
 
208
                warning("FTP temporary error: %s. Retrying." % str(e))
 
209
                self._FTP_instance = None
 
210
                return self.get(relpath, decode, retries+1)
 
211
        except EOFError, e:
 
212
            if retries > _number_of_retries:
 
213
                raise TransportError("FTP control connection closed during GET %s."
 
214
                                     % self.abspath(relpath),
 
215
                                     orig_error=e)
 
216
            else:
 
217
                warning("FTP control connection closed. Trying to reopen.")
 
218
                time.sleep(_sleep_between_retries)
 
219
                self._FTP_instance = None
 
220
                return self.get(relpath, decode, retries+1)
181
221
 
182
 
    def put(self, relpath, fp, mode=None):
 
222
    def put(self, relpath, fp, mode=None, retries=0):
183
223
        """Copy the file-like or string object into the location.
184
224
 
185
225
        :param relpath: Location to put the contents, relative to base.
186
 
        :param f:       File-like or string object.
187
 
        TODO: jam 20051215 This should be an atomic put, not overwritting files in place
 
226
        :param fp:       File-like or string object.
 
227
        :param retries: Number of retries after temporary failures so far
 
228
                        for this operation.
 
229
 
188
230
        TODO: jam 20051215 ftp as a protocol seems to support chmod, but ftplib does not
189
231
        """
 
232
        tmp_abspath = '%s.tmp.%.9f.%d.%d' % (self._abspath(relpath), time.time(),
 
233
                        os.getpid(), random.randint(0,0x7FFFFFFF))
190
234
        if not hasattr(fp, 'read'):
191
235
            fp = StringIO(fp)
192
236
        try:
193
237
            mutter("FTP put: %s" % self._abspath(relpath))
194
238
            f = self._get_FTP()
195
 
            f.storbinary('STOR '+self._abspath(relpath), fp, 8192)
 
239
            try:
 
240
                f.storbinary('STOR '+tmp_abspath, fp)
 
241
                f.rename(tmp_abspath, self._abspath(relpath))
 
242
            except (ftplib.error_temp,EOFError), e:
 
243
                warning("Failure during ftp PUT. Deleting temporary file.")
 
244
                try:
 
245
                    f.delete(tmp_abspath)
 
246
                except:
 
247
                    warning("Failed to delete temporary file on the server.\nFile: %s"
 
248
                            % tmp_abspath)
 
249
                    raise e
 
250
                raise
196
251
        except ftplib.error_perm, e:
197
 
            raise TransportError(orig_error=e)
 
252
            if "no such file" in str(e).lower():
 
253
                raise NoSuchFile("Error storing %s: %s"
 
254
                                 % (self.abspath(relpath), str(e)), extra=e)
 
255
            else:
 
256
                raise FtpTransportError(orig_error=e)
 
257
        except ftplib.error_temp, e:
 
258
            if retries > _number_of_retries:
 
259
                raise TransportError("FTP temporary error during PUT %s. Aborting."
 
260
                                     % self.abspath(relpath), orig_error=e)
 
261
            else:
 
262
                warning("FTP temporary error: %s. Retrying." % str(e))
 
263
                self._FTP_instance = None
 
264
                self.put(relpath, fp, mode, retries+1)
 
265
        except EOFError:
 
266
            if retries > _number_of_retries:
 
267
                raise TransportError("FTP control connection closed during PUT %s."
 
268
                                     % self.abspath(relpath), orig_error=e)
 
269
            else:
 
270
                warning("FTP control connection closed. Trying to reopen.")
 
271
                time.sleep(_sleep_between_retries)
 
272
                self._FTP_instance = None
 
273
                self.put(relpath, fp, mode, retries+1)
 
274
 
198
275
 
199
276
    def mkdir(self, relpath, mode=None):
200
277
        """Create a directory at the given path."""
282
359
            f = self._get_FTP()
283
360
            return FtpStatResult(f, self._abspath(relpath))
284
361
        except ftplib.error_perm, e:
285
 
            raise TransportError(orig_error=e)
 
362
            if "no such file" in str(e).lower():
 
363
                raise NoSuchFile("Error storing %s: %s"
 
364
                                 % (self.abspath(relpath), str(e)), extra=e)
 
365
            else:
 
366
                raise FtpTransportError(orig_error=e)
286
367
 
287
368
    def lock_read(self, relpath):
288
369
        """Lock the given file for shared (read) access.
304
385
        :return: A lock object, which should be passed to Transport.unlock()
305
386
        """
306
387
        return self.lock_read(relpath)
 
388
 
 
389
 
 
390
def get_test_permutations():
 
391
    """Return the permutations to be used in testing."""
 
392
    warn("There are no FTP transport provider tests yet.")
 
393
    return []