~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/ftp.py

  • Committer: Robert Collins
  • Date: 2007-07-04 08:08:13 UTC
  • mfrom: (2572 +trunk)
  • mto: This revision was merged to the branch mainline in revision 2587.
  • Revision ID: robertc@robertcollins.net-20070704080813-wzebx0r88fvwj5rq
Merge bzr.dev.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005 Canonical Ltd
 
1
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
29
29
import errno
30
30
import ftplib
31
31
import os
 
32
import os.path
32
33
import urllib
33
34
import urlparse
 
35
import select
34
36
import stat
35
37
import threading
36
38
import time
39
41
 
40
42
from bzrlib import (
41
43
    errors,
 
44
    osutils,
42
45
    urlutils,
43
46
    )
44
47
from bzrlib.trace import mutter, warning
47
50
    split_url,
48
51
    Transport,
49
52
    )
 
53
from bzrlib.transport.local import LocalURLServer
50
54
import bzrlib.ui
51
55
 
52
56
_have_medusa = False
161
165
        if ('no such file' in s
162
166
            or 'could not open' in s
163
167
            or 'no such dir' in s
 
168
            or 'could not create file' in s # vsftpd
 
169
            or 'file doesn\'t exist' in s
164
170
            ):
165
171
            raise errors.NoSuchFile(path, extra=extra)
166
172
        if ('file exists' in s):
300
306
        :param retries: Number of retries after temporary failures so far
301
307
                        for this operation.
302
308
 
303
 
        TODO: jam 20051215 ftp as a protocol seems to support chmod, but ftplib does not
 
309
        TODO: jam 20051215 ftp as a protocol seems to support chmod, but
 
310
        ftplib does not
304
311
        """
305
312
        abspath = self._abspath(relpath)
306
313
        tmp_abspath = '%s.tmp.%.9f.%d.%d' % (abspath, time.time(),
312
319
            f = self._get_FTP()
313
320
            try:
314
321
                f.storbinary('STOR '+tmp_abspath, fp)
315
 
                f.rename(tmp_abspath, abspath)
 
322
                self._rename_and_overwrite(tmp_abspath, abspath, f)
316
323
            except (ftplib.error_temp,EOFError), e:
317
324
                warning("Failure during ftp PUT. Deleting temporary file.")
318
325
                try:
323
330
                    raise e
324
331
                raise
325
332
        except ftplib.error_perm, e:
326
 
            self._translate_perm_error(e, abspath, extra='could not store')
 
333
            self._translate_perm_error(e, abspath, extra='could not store',
 
334
                                       unknown_exc=errors.NoSuchFile)
327
335
        except ftplib.error_temp, e:
328
336
            if retries > _number_of_retries:
329
337
                raise errors.TransportError("FTP temporary error during PUT %s. Aborting."
431
439
    #       to give it its own address as the 'to' location.
432
440
    #       So implement a fancier 'copy()'
433
441
 
 
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
 
434
456
    def move(self, rel_from, rel_to):
435
457
        """Move the item at rel_from to the location at rel_to"""
436
458
        abs_from = self._abspath(rel_from)
438
460
        try:
439
461
            mutter("FTP mv: %s => %s", abs_from, abs_to)
440
462
            f = self._get_FTP()
441
 
            f.rename(abs_from, abs_to)
 
463
            self._rename_and_overwrite(abs_from, abs_to, f)
442
464
        except ftplib.error_perm, e:
443
465
            self._translate_perm_error(e, abs_from,
444
466
                extra='unable to rename to %r' % (rel_to,), 
445
467
                unknown_exc=errors.PathError)
446
468
 
447
 
    rename = move
 
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
        """
 
474
        osutils.fancy_rename(abs_from, abs_to,
 
475
            rename_func=lambda p1, p2: self._rename(p1, p2, f),
 
476
            unlink_func=lambda p: self._delete(p, f))
448
477
 
449
478
    def delete(self, relpath):
450
479
        """Delete the item at relpath"""
451
480
        abspath = self._abspath(relpath)
 
481
        f = self._get_FTP()
 
482
        self._delete(abspath, f)
 
483
 
 
484
    def _delete(self, abspath, f):
452
485
        try:
453
486
            mutter("FTP rm: %s", abspath)
454
 
            f = self._get_FTP()
455
487
            f.delete(abspath)
456
488
        except ftplib.error_perm, e:
457
489
            self._translate_perm_error(e, abspath, 'error deleting',
549
581
        """This is used by medusa.ftp_server to log connections, etc."""
550
582
        self.logs.append(message)
551
583
 
552
 
    def setUp(self):
553
 
 
 
584
    def setUp(self, vfs_server=None):
554
585
        if not _have_medusa:
555
586
            raise RuntimeError('Must have medusa to run the FtpServer')
556
587
 
 
588
        assert vfs_server is None or isinstance(vfs_server, LocalURLServer), \
 
589
            "FtpServer currently assumes local transport, got %s" % vfs_server
 
590
 
557
591
        self._root = os.getcwdu()
558
592
        self._ftp_server = _ftp_server(
559
593
            authorizer=_test_authorizer(root=self._root),
565
599
        self._port = self._ftp_server.getsockname()[1]
566
600
        # Don't let it loop forever, or handle an infinite number of requests.
567
601
        # In this case it will run for 100s, or 1000 requests
568
 
        self._async_thread = threading.Thread(target=asyncore.loop,
 
602
        self._async_thread = threading.Thread(
 
603
                target=FtpServer._asyncore_loop_ignore_EBADF,
569
604
                kwargs={'timeout':0.1, 'count':1000})
570
605
        self._async_thread.setDaemon(True)
571
606
        self._async_thread.start()
577
612
        asyncore.close_all()
578
613
        self._async_thread.join()
579
614
 
 
615
    @staticmethod
 
616
    def _asyncore_loop_ignore_EBADF(*args, **kwargs):
 
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
        """
 
622
        try:
 
623
            asyncore.loop(*args, **kwargs)
 
624
        except select.error, e:
 
625
            if e.args[0] != errno.EBADF:
 
626
                raise
 
627
 
580
628
 
581
629
_ftp_channel = None
582
630
_ftp_server = None
647
695
            pfrom = self.filesystem.translate(self._renaming)
648
696
            self._renaming = None
649
697
            pto = self.filesystem.translate(line[1])
 
698
            if os.path.exists(pto):
 
699
                self.respond('550 RNTO failed: file exists')
 
700
                return
650
701
            try:
651
702
                os.rename(pfrom, pto)
652
703
            except (IOError, OSError), e:
653
704
                # TODO: jam 20060516 return custom responses based on
654
705
                #       why the command failed
655
 
                self.respond('550 RNTO failed: %s' % (e,))
 
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))
656
710
            except:
657
711
                self.respond('550 RNTO failed')
658
712
                # For a test server, we will go ahead and just die
690
744
                    self.filesystem.mkdir (path)
691
745
                    self.respond ('257 MKD command successful.')
692
746
                except (IOError, OSError), e:
693
 
                    self.respond ('550 error creating directory: %s' % (e,))
 
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))
694
752
                except:
695
753
                    self.respond ('550 error creating directory.')
696
754