/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

  • Committer: Aaron Bentley
  • Date: 2006-04-07 22:46:52 UTC
  • mfrom: (1645 +trunk)
  • mto: This revision was merged to the branch mainline in revision 1727.
  • Revision ID: aaron.bentley@utoronto.ca-20060407224652-4925bc3735b926f8
Merged latest 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, decode=False):
 
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):
 
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.
 
226
        :param fp:       File-like or string object.
 
227
        :param retries: Number of retries after temporary failures so far
 
228
                        for this operation.
 
229
 
 
230
        TODO: jam 20051215 ftp as a protocol seems to support chmod, but ftplib does not
187
231
        """
 
232
        tmp_abspath = '%s.tmp.%.9f.%d.%d' % (self._abspath(relpath), time.time(),
 
233
                        os.getpid(), random.randint(0,0x7FFFFFFF))
188
234
        if not hasattr(fp, 'read'):
189
235
            fp = StringIO(fp)
190
236
        try:
191
237
            mutter("FTP put: %s" % self._abspath(relpath))
192
238
            f = self._get_FTP()
193
 
            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
194
251
        except ftplib.error_perm, e:
195
 
            raise TransportError(orig_error=e)
196
 
 
197
 
    def mkdir(self, relpath):
 
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
 
 
275
 
 
276
    def mkdir(self, relpath, mode=None):
198
277
        """Create a directory at the given path."""
199
278
        try:
200
279
            mutter("FTP mkd: %s" % self._abspath(relpath))
280
359
            f = self._get_FTP()
281
360
            return FtpStatResult(f, self._abspath(relpath))
282
361
        except ftplib.error_perm, e:
283
 
            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)
284
367
 
285
368
    def lock_read(self, relpath):
286
369
        """Lock the given file for shared (read) access.
302
385
        :return: A lock object, which should be passed to Transport.unlock()
303
386
        """
304
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 []