~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/ftp.py

  • Committer: Vincent Ladeuil
  • Date: 2007-06-01 07:25:50 UTC
  • mto: (2485.8.44 bzr.connection.sharing)
  • mto: This revision was merged to the branch mainline in revision 2646.
  • Revision ID: v.ladeuil+lp@free.fr-20070601072550-oku4t5llxk4invum
Finish ftp refactoring. Test suite passing.

* bzrlib/transport/ftp.py:
(FtpTransport.__init__): Simplified.
(FtpTransport._unparse_url): Deleted.
(FtpTransport._abspath): Deleted and reimplemented as a
specialisation of _remote_path. The heart is: we don't support
Unicode paths :-/
(FtpTransport.abspath): Deleted.

Show diffs side-by-side

added added

removed removed

Lines of Context:
47
47
from bzrlib.trace import mutter, warning
48
48
from bzrlib.transport import (
49
49
    Server,
50
 
    split_url,
51
50
    ConnectedTransport,
52
51
    )
53
52
from bzrlib.transport.local import LocalURLServer
82
81
 
83
82
    def __init__(self, base, _provided_instance=None):
84
83
        """Set the base path where files will be stored."""
85
 
        assert base.startswith('ftp://') or base.startswith('aftp://')
86
 
 
87
 
        self.is_active = base.startswith('aftp://')
88
 
        if self.is_active:
89
 
            # urlparse won't handle aftp://, delete the leading 'a'
90
 
            # FIXME: This breaks even hopes of connection sharing
91
 
            # (by reusing the url instead of true cloning) by
92
 
            # modifying the the url coming from the user.
93
 
            base = base[1:]
94
 
        if not base.endswith('/'):
95
 
            base += '/'
96
 
        (self._proto, self._username,
97
 
            self._password, self._host,
98
 
            self._port, self._path) = split_url(base)
99
 
        base = self._unparse_url()
100
 
 
101
84
        super(FtpTransport, self).__init__(base)
 
85
        if self._scheme == 'aftp':
 
86
            self._unqualified_scheme = 'ftp'
 
87
            self.is_active = True
 
88
        else:
 
89
            self._unqualified_scheme = self._scheme
 
90
            self.is_active = False
102
91
        self._FTP_instance = _provided_instance
103
92
 
104
 
    def _unparse_url(self, path=None):
105
 
        if path is None:
106
 
            path = self._path
107
 
        path = urllib.quote(path)
108
 
        netloc = urllib.quote(self._host)
109
 
        if self._username is not None:
110
 
            netloc = '%s@%s' % (urllib.quote(self._username), netloc)
111
 
        if self._port is not None:
112
 
            netloc = '%s:%d' % (netloc, self._port)
113
 
        proto = 'ftp'
114
 
        if self.is_active:
115
 
            proto = 'aftp'
116
 
        return urlparse.urlunparse((proto, netloc, path, '', '', ''))
117
 
 
118
93
    def _get_FTP(self):
119
94
        """Return the ftplib.FTP instance for this object."""
120
95
        if self._FTP_instance is None:
121
96
            mutter("Constructing FTP instance against %r" %
122
 
                   ((self._host, self._port, self._username, '********',
 
97
                   ((self._host, self._port, self._user, '********',
123
98
                    self.is_active),))
124
99
            try:
125
100
                connection = ftplib.FTP()
126
101
                connection.connect(host=self._host, port=self._port)
127
 
                if self._username and self._username != 'anonymous' and \
 
102
                if self._user and self._user != 'anonymous' and \
128
103
                        not self._password:
129
104
                    self._password = bzrlib.ui.ui_factory.get_password(
130
105
                        prompt='FTP %(user)s@%(host)s password',
131
 
                        user=self._username, host=self._host)
132
 
                connection.login(user=self._username, passwd=self._password)
 
106
                        user=self._user, host=self._host)
 
107
                connection.login(user=self._user, passwd=self._password)
133
108
                connection.set_pasv(not self.is_active)
134
109
            except ftplib.error_perm, e:
135
110
                raise errors.TransportError(msg="Error setting up connection:"
187
162
        else:
188
163
            return FtpTransport(self.abspath(offset), self._FTP_instance)
189
164
 
190
 
    def _abspath(self, relpath):
191
 
        assert isinstance(relpath, basestring)
192
 
        relpath = urlutils.unescape(relpath)
193
 
        if relpath.startswith('/'):
194
 
            basepath = []
195
 
        else:
196
 
            basepath = self._path.split('/')
197
 
        if len(basepath) > 0 and basepath[-1] == '':
198
 
            basepath = basepath[:-1]
199
 
        for p in relpath.split('/'):
200
 
            if p == '..':
201
 
                if len(basepath) == 0:
202
 
                    # In most filesystems, a request for the parent
203
 
                    # of root, just returns root.
204
 
                    continue
205
 
                basepath.pop()
206
 
            elif p == '.' or p == '':
207
 
                continue # No-op
208
 
            else:
209
 
                basepath.append(p)
210
 
        # Possibly, we could use urlparse.urljoin() here, but
211
 
        # I'm concerned about when it chooses to strip the last
212
 
        # portion of the path, and when it doesn't.
213
 
 
 
165
    def _remote_path(self, relpath):
214
166
        # XXX: It seems that ftplib does not handle Unicode paths
215
 
        # at the same time, medusa won't handle utf8 paths
216
 
        # So if we .encode(utf8) here, then we get a Server failure.
217
 
        # while if we use str(), we get a UnicodeError, and the test suite
218
 
        # just skips testing UnicodePaths.
219
 
        return str('/'.join(basepath) or '/')
220
 
    
221
 
    def abspath(self, relpath):
222
 
        """Return the full url to the given relative path.
223
 
        This can be supplied with a string or a list
224
 
        """
225
 
        path = self._abspath(relpath)
226
 
        return self._unparse_url(path)
 
167
        # at the same time, medusa won't handle utf8 paths So if
 
168
        # we .encode(utf8) here (see ConnectedTransport
 
169
        # implementation), then we get a Server failure.  while
 
170
        # if we use str(), we get a UnicodeError, and the test
 
171
        # suite just skips testing UnicodePaths.
 
172
        relative = str(urlutils.unescape(relpath))
 
173
        remote_path = self._combine_paths(self._path, relative)
 
174
        return remote_path
227
175
 
228
176
    def has(self, relpath):
229
177
        """Does the target location exist?"""
232
180
        # XXX: I assume we're never asked has(dirname) and thus I use
233
181
        # the FTP size command and assume that if it doesn't raise,
234
182
        # all is good.
235
 
        abspath = self._abspath(relpath)
 
183
        abspath = self._remote_path(relpath)
236
184
        try:
237
185
            f = self._get_FTP()
238
186
            mutter('FTP has check: %s => %s', relpath, abspath)
258
206
        """
259
207
        # TODO: decode should be deprecated
260
208
        try:
261
 
            mutter("FTP get: %s", self._abspath(relpath))
 
209
            mutter("FTP get: %s", self._remote_path(relpath))
262
210
            f = self._get_FTP()
263
211
            ret = StringIO()
264
 
            f.retrbinary('RETR '+self._abspath(relpath), ret.write, 8192)
 
212
            f.retrbinary('RETR '+self._remote_path(relpath), ret.write, 8192)
265
213
            ret.seek(0)
266
214
            return ret
267
215
        except ftplib.error_perm, e:
297
245
        TODO: jam 20051215 ftp as a protocol seems to support chmod, but
298
246
        ftplib does not
299
247
        """
300
 
        abspath = self._abspath(relpath)
 
248
        abspath = self._remote_path(relpath)
301
249
        tmp_abspath = '%s.tmp.%.9f.%d.%d' % (abspath, time.time(),
302
250
                        os.getpid(), random.randint(0,0x7FFFFFFF))
303
251
        if getattr(fp, 'read', None) is None:
339
287
 
340
288
    def mkdir(self, relpath, mode=None):
341
289
        """Create a directory at the given path."""
342
 
        abspath = self._abspath(relpath)
 
290
        abspath = self._remote_path(relpath)
343
291
        try:
344
292
            mutter("FTP mkd: %s", abspath)
345
293
            f = self._get_FTP()
350
298
 
351
299
    def rmdir(self, rel_path):
352
300
        """Delete the directory at rel_path"""
353
 
        abspath = self._abspath(rel_path)
 
301
        abspath = self._remote_path(rel_path)
354
302
        try:
355
303
            mutter("FTP rmd: %s", abspath)
356
304
            f = self._get_FTP()
362
310
        """Append the text in the file-like object into the final
363
311
        location.
364
312
        """
365
 
        abspath = self._abspath(relpath)
 
313
        abspath = self._remote_path(relpath)
366
314
        if self.has(relpath):
367
315
            ftp = self._get_FTP()
368
316
            result = ftp.size(abspath)
381
329
        number of retries is exceeded.
382
330
        """
383
331
        try:
384
 
            abspath = self._abspath(relpath)
 
332
            abspath = self._remote_path(relpath)
385
333
            mutter("FTP appe (try %d) to %s", retries, abspath)
386
334
            ftp = self._get_FTP()
387
335
            ftp.voidcmd("TYPE I")
412
360
        """
413
361
        try:
414
362
            mutter("FTP site chmod: setting permissions to %s on %s",
415
 
                str(mode), self._abspath(relpath))
 
363
                str(mode), self._remote_path(relpath))
416
364
            ftp = self._get_FTP()
417
 
            cmd = "SITE CHMOD %s %s" % (self._abspath(relpath), str(mode))
 
365
            cmd = "SITE CHMOD %s %s" % (self._remote_path(relpath), str(mode))
418
366
            ftp.sendcmd(cmd)
419
367
        except ftplib.error_perm, e:
420
368
            # Command probably not available on this server
421
369
            warning("FTP Could not set permissions to %s on %s. %s",
422
 
                    str(mode), self._abspath(relpath), str(e))
 
370
                    str(mode), self._remote_path(relpath), str(e))
423
371
 
424
372
    # TODO: jam 20060516 I believe ftp allows you to tell an ftp server
425
373
    #       to copy something to another machine. And you may be able
427
375
    #       So implement a fancier 'copy()'
428
376
 
429
377
    def rename(self, rel_from, rel_to):
430
 
        abs_from = self._abspath(rel_from)
431
 
        abs_to = self._abspath(rel_to)
 
378
        abs_from = self._remote_path(rel_from)
 
379
        abs_to = self._remote_path(rel_to)
432
380
        mutter("FTP rename: %s => %s", abs_from, abs_to)
433
381
        f = self._get_FTP()
434
382
        return self._rename(abs_from, abs_to, f)
442
390
 
443
391
    def move(self, rel_from, rel_to):
444
392
        """Move the item at rel_from to the location at rel_to"""
445
 
        abs_from = self._abspath(rel_from)
446
 
        abs_to = self._abspath(rel_to)
 
393
        abs_from = self._remote_path(rel_from)
 
394
        abs_to = self._remote_path(rel_to)
447
395
        try:
448
396
            mutter("FTP mv: %s => %s", abs_from, abs_to)
449
397
            f = self._get_FTP()
464
412
 
465
413
    def delete(self, relpath):
466
414
        """Delete the item at relpath"""
467
 
        abspath = self._abspath(relpath)
 
415
        abspath = self._remote_path(relpath)
468
416
        f = self._get_FTP()
469
417
        self._delete(abspath, f)
470
418
 
482
430
 
483
431
    def list_dir(self, relpath):
484
432
        """See Transport.list_dir."""
485
 
        basepath = self._abspath(relpath)
 
433
        basepath = self._remote_path(relpath)
486
434
        mutter("FTP nlst: %s", basepath)
487
435
        f = self._get_FTP()
488
436
        try:
515
463
 
516
464
    def stat(self, relpath):
517
465
        """Return the stat information for a file."""
518
 
        abspath = self._abspath(relpath)
 
466
        abspath = self._remote_path(relpath)
519
467
        try:
520
468
            mutter("FTP stat: %s", abspath)
521
469
            f = self._get_FTP()