~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/ftp.py

  • Committer: Vincent Ladeuil
  • Date: 2007-05-31 17:56:56 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-20070531175656-uwy1n9l8x3im9evb
PathNotChild inherits from PathError, not BzrError.

* bzrlib/errors.py:
(PathNotChild): Really a PathError.

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,
50
51
    ConnectedTransport,
51
52
    )
52
53
from bzrlib.transport.local import LocalURLServer
82
83
    def __init__(self, base, _provided_instance=None):
83
84
        """Set the base path where files will be stored."""
84
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
 
85
101
        super(FtpTransport, self).__init__(base)
86
 
        if self._scheme == 'aftp':
87
 
            self._unqualified_scheme = 'ftp'
88
 
            self.is_active = True
89
 
        else:
90
 
            self._unqualified_scheme = self._scheme
91
 
            self.is_active = False
92
102
        self._FTP_instance = _provided_instance
93
103
 
 
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
 
94
118
    def _get_FTP(self):
95
119
        """Return the ftplib.FTP instance for this object."""
96
120
        if self._FTP_instance is None:
97
121
            mutter("Constructing FTP instance against %r" %
98
 
                   ((self._host, self._port, self._user, '********',
 
122
                   ((self._host, self._port, self._username, '********',
99
123
                    self.is_active),))
100
124
            try:
101
125
                connection = ftplib.FTP()
102
126
                connection.connect(host=self._host, port=self._port)
103
 
                if self._user and self._user != 'anonymous' and \
 
127
                if self._username and self._username != 'anonymous' and \
104
128
                        not self._password:
105
129
                    self._password = bzrlib.ui.ui_factory.get_password(
106
130
                        prompt='FTP %(user)s@%(host)s password',
107
 
                        user=self._user, host=self._host)
108
 
                connection.login(user=self._user, passwd=self._password)
 
131
                        user=self._username, host=self._host)
 
132
                connection.login(user=self._username, passwd=self._password)
109
133
                connection.set_pasv(not self.is_active)
110
134
            except ftplib.error_perm, e:
111
135
                raise errors.TransportError(msg="Error setting up connection:"
163
187
        else:
164
188
            return FtpTransport(self.abspath(offset), self._FTP_instance)
165
189
 
166
 
    def _remote_path(self, relpath):
 
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
 
167
214
        # XXX: It seems that ftplib does not handle Unicode paths
168
 
        # at the same time, medusa won't handle utf8 paths So if
169
 
        # we .encode(utf8) here (see ConnectedTransport
170
 
        # implementation), then we get a Server failure.  while
171
 
        # if we use str(), we get a UnicodeError, and the test
172
 
        # suite just skips testing UnicodePaths.
173
 
        relative = str(urlutils.unescape(relpath))
174
 
        remote_path = self._combine_paths(self._path, relative)
175
 
        return remote_path
 
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)
176
227
 
177
228
    def has(self, relpath):
178
229
        """Does the target location exist?"""
181
232
        # XXX: I assume we're never asked has(dirname) and thus I use
182
233
        # the FTP size command and assume that if it doesn't raise,
183
234
        # all is good.
184
 
        abspath = self._remote_path(relpath)
 
235
        abspath = self._abspath(relpath)
185
236
        try:
186
237
            f = self._get_FTP()
187
238
            mutter('FTP has check: %s => %s', relpath, abspath)
207
258
        """
208
259
        # TODO: decode should be deprecated
209
260
        try:
210
 
            mutter("FTP get: %s", self._remote_path(relpath))
 
261
            mutter("FTP get: %s", self._abspath(relpath))
211
262
            f = self._get_FTP()
212
263
            ret = StringIO()
213
 
            f.retrbinary('RETR '+self._remote_path(relpath), ret.write, 8192)
 
264
            f.retrbinary('RETR '+self._abspath(relpath), ret.write, 8192)
214
265
            ret.seek(0)
215
266
            return ret
216
267
        except ftplib.error_perm, e:
246
297
        TODO: jam 20051215 ftp as a protocol seems to support chmod, but
247
298
        ftplib does not
248
299
        """
249
 
        abspath = self._remote_path(relpath)
 
300
        abspath = self._abspath(relpath)
250
301
        tmp_abspath = '%s.tmp.%.9f.%d.%d' % (abspath, time.time(),
251
302
                        os.getpid(), random.randint(0,0x7FFFFFFF))
252
303
        if getattr(fp, 'read', None) is None:
288
339
 
289
340
    def mkdir(self, relpath, mode=None):
290
341
        """Create a directory at the given path."""
291
 
        abspath = self._remote_path(relpath)
 
342
        abspath = self._abspath(relpath)
292
343
        try:
293
344
            mutter("FTP mkd: %s", abspath)
294
345
            f = self._get_FTP()
299
350
 
300
351
    def rmdir(self, rel_path):
301
352
        """Delete the directory at rel_path"""
302
 
        abspath = self._remote_path(rel_path)
 
353
        abspath = self._abspath(rel_path)
303
354
        try:
304
355
            mutter("FTP rmd: %s", abspath)
305
356
            f = self._get_FTP()
311
362
        """Append the text in the file-like object into the final
312
363
        location.
313
364
        """
314
 
        abspath = self._remote_path(relpath)
 
365
        abspath = self._abspath(relpath)
315
366
        if self.has(relpath):
316
367
            ftp = self._get_FTP()
317
368
            result = ftp.size(abspath)
330
381
        number of retries is exceeded.
331
382
        """
332
383
        try:
333
 
            abspath = self._remote_path(relpath)
 
384
            abspath = self._abspath(relpath)
334
385
            mutter("FTP appe (try %d) to %s", retries, abspath)
335
386
            ftp = self._get_FTP()
336
387
            ftp.voidcmd("TYPE I")
361
412
        """
362
413
        try:
363
414
            mutter("FTP site chmod: setting permissions to %s on %s",
364
 
                str(mode), self._remote_path(relpath))
 
415
                str(mode), self._abspath(relpath))
365
416
            ftp = self._get_FTP()
366
 
            cmd = "SITE CHMOD %s %s" % (self._remote_path(relpath), str(mode))
 
417
            cmd = "SITE CHMOD %s %s" % (self._abspath(relpath), str(mode))
367
418
            ftp.sendcmd(cmd)
368
419
        except ftplib.error_perm, e:
369
420
            # Command probably not available on this server
370
421
            warning("FTP Could not set permissions to %s on %s. %s",
371
 
                    str(mode), self._remote_path(relpath), str(e))
 
422
                    str(mode), self._abspath(relpath), str(e))
372
423
 
373
424
    # TODO: jam 20060516 I believe ftp allows you to tell an ftp server
374
425
    #       to copy something to another machine. And you may be able
376
427
    #       So implement a fancier 'copy()'
377
428
 
378
429
    def rename(self, rel_from, rel_to):
379
 
        abs_from = self._remote_path(rel_from)
380
 
        abs_to = self._remote_path(rel_to)
 
430
        abs_from = self._abspath(rel_from)
 
431
        abs_to = self._abspath(rel_to)
381
432
        mutter("FTP rename: %s => %s", abs_from, abs_to)
382
433
        f = self._get_FTP()
383
434
        return self._rename(abs_from, abs_to, f)
391
442
 
392
443
    def move(self, rel_from, rel_to):
393
444
        """Move the item at rel_from to the location at rel_to"""
394
 
        abs_from = self._remote_path(rel_from)
395
 
        abs_to = self._remote_path(rel_to)
 
445
        abs_from = self._abspath(rel_from)
 
446
        abs_to = self._abspath(rel_to)
396
447
        try:
397
448
            mutter("FTP mv: %s => %s", abs_from, abs_to)
398
449
            f = self._get_FTP()
413
464
 
414
465
    def delete(self, relpath):
415
466
        """Delete the item at relpath"""
416
 
        abspath = self._remote_path(relpath)
 
467
        abspath = self._abspath(relpath)
417
468
        f = self._get_FTP()
418
469
        self._delete(abspath, f)
419
470
 
431
482
 
432
483
    def list_dir(self, relpath):
433
484
        """See Transport.list_dir."""
434
 
        basepath = self._remote_path(relpath)
 
485
        basepath = self._abspath(relpath)
435
486
        mutter("FTP nlst: %s", basepath)
436
487
        f = self._get_FTP()
437
488
        try:
464
515
 
465
516
    def stat(self, relpath):
466
517
        """Return the stat information for a file."""
467
 
        abspath = self._remote_path(relpath)
 
518
        abspath = self._abspath(relpath)
468
519
        try:
469
520
            mutter("FTP stat: %s", abspath)
470
521
            f = self._get_FTP()