557
553
return self.lock_read(relpath)
560
class FtpServer(Server):
561
"""Common code for FTP server facilities."""
565
self._ftp_server = None
567
self._async_thread = None
572
"""Calculate an ftp url to this server."""
573
return 'ftp://foo:bar@localhost:%d/' % (self._port)
575
# def get_bogus_url(self):
576
# """Return a URL which cannot be connected to."""
577
# return 'ftp://127.0.0.1:1'
579
def log(self, message):
580
"""This is used by medusa.ftp_server to log connections, etc."""
581
self.logs.append(message)
583
def setUp(self, vfs_server=None):
585
raise RuntimeError('Must have medusa to run the FtpServer')
587
assert vfs_server is None or isinstance(vfs_server, LocalURLServer), \
588
"FtpServer currently assumes local transport, got %s" % vfs_server
590
self._root = os.getcwdu()
591
self._ftp_server = _ftp_server(
592
authorizer=_test_authorizer(root=self._root),
594
port=0, # bind to a random port
596
logger_object=self # Use FtpServer.log() for messages
598
self._port = self._ftp_server.getsockname()[1]
599
# Don't let it loop forever, or handle an infinite number of requests.
600
# In this case it will run for 1000s, or 10000 requests
601
self._async_thread = threading.Thread(
602
target=FtpServer._asyncore_loop_ignore_EBADF,
603
kwargs={'timeout':0.1, 'count':10000})
604
self._async_thread.setDaemon(True)
605
self._async_thread.start()
608
"""See bzrlib.transport.Server.tearDown."""
609
self._ftp_server.close()
611
self._async_thread.join()
614
def _asyncore_loop_ignore_EBADF(*args, **kwargs):
615
"""Ignore EBADF during server shutdown.
617
We close the socket to get the server to shutdown, but this causes
618
select.select() to raise EBADF.
621
asyncore.loop(*args, **kwargs)
622
# FIXME: If we reach that point, we should raise an exception
623
# explaining that the 'count' parameter in setUp is too low or
624
# testers may wonder why their test just sits there waiting for a
625
# server that is already dead. Note that if the tester waits too
626
# long under pdb the server will also die.
627
except select.error, e:
628
if e.args[0] != errno.EBADF:
634
_test_authorizer = None
638
global _have_medusa, _ftp_channel, _ftp_server, _test_authorizer
641
import medusa.filesys
642
import medusa.ftp_server
648
class test_authorizer(object):
649
"""A custom Authorizer object for running the test suite.
651
The reason we cannot use dummy_authorizer, is because it sets the
652
channel to readonly, which we don't always want to do.
655
def __init__(self, root):
657
# If secured_user is set secured_password will be checked
658
self.secured_user = None
659
self.secured_password = None
661
def authorize(self, channel, username, password):
662
"""Return (success, reply_string, filesystem)"""
664
return 0, 'No Medusa.', None
666
channel.persona = -1, -1
667
if username == 'anonymous':
668
channel.read_only = 1
670
channel.read_only = 0
672
# Check secured_user if set
673
if (self.secured_user is not None
674
and username == self.secured_user
675
and password != self.secured_password):
676
return 0, 'Password invalid.', None
678
return 1, 'OK.', medusa.filesys.os_filesystem(self.root)
681
class ftp_channel(medusa.ftp_server.ftp_channel):
682
"""Customized ftp channel"""
684
def log(self, message):
685
"""Redirect logging requests."""
686
mutter('_ftp_channel: %s', message)
688
def log_info(self, message, type='info'):
689
"""Redirect logging requests."""
690
mutter('_ftp_channel %s: %s', type, message)
692
def cmd_rnfr(self, line):
693
"""Prepare for renaming a file."""
694
self._renaming = line[1]
695
self.respond('350 Ready for RNTO')
696
# TODO: jam 20060516 in testing, the ftp server seems to
697
# check that the file already exists, or it sends
698
# 550 RNFR command failed
700
def cmd_rnto(self, line):
701
"""Rename a file based on the target given.
703
rnto must be called after calling rnfr.
705
if not self._renaming:
706
self.respond('503 RNFR required first.')
707
pfrom = self.filesystem.translate(self._renaming)
708
self._renaming = None
709
pto = self.filesystem.translate(line[1])
710
if os.path.exists(pto):
711
self.respond('550 RNTO failed: file exists')
714
os.rename(pfrom, pto)
715
except (IOError, OSError), e:
716
# TODO: jam 20060516 return custom responses based on
717
# why the command failed
718
# (bialix 20070418) str(e) on Python 2.5 @ Windows
719
# sometimes don't provide expected error message;
720
# so we obtain such message via os.strerror()
721
self.respond('550 RNTO failed: %s' % os.strerror(e.errno))
723
self.respond('550 RNTO failed')
724
# For a test server, we will go ahead and just die
727
self.respond('250 Rename successful.')
729
def cmd_size(self, line):
730
"""Return the size of a file
732
This is overloaded to help the test suite determine if the
733
target is a directory.
736
if not self.filesystem.isfile(filename):
737
if self.filesystem.isdir(filename):
738
self.respond('550 "%s" is a directory' % (filename,))
740
self.respond('550 "%s" is not a file' % (filename,))
742
self.respond('213 %d'
743
% (self.filesystem.stat(filename)[stat.ST_SIZE]),)
745
def cmd_mkd(self, line):
746
"""Create a directory.
748
Overloaded because default implementation does not distinguish
749
*why* it cannot make a directory.
752
self.command_not_understood(''.join(line))
756
self.filesystem.mkdir (path)
757
self.respond ('257 MKD command successful.')
758
except (IOError, OSError), e:
759
# (bialix 20070418) str(e) on Python 2.5 @ Windows
760
# sometimes don't provide expected error message;
761
# so we obtain such message via os.strerror()
762
self.respond ('550 error creating directory: %s' %
763
os.strerror(e.errno))
765
self.respond ('550 error creating directory.')
768
class ftp_server(medusa.ftp_server.ftp_server):
769
"""Customize the behavior of the Medusa ftp_server.
771
There are a few warts on the ftp_server, based on how it expects
775
ftp_channel_class = ftp_channel
777
def __init__(self, *args, **kwargs):
778
mutter('Initializing _ftp_server: %r, %r', args, kwargs)
779
medusa.ftp_server.ftp_server.__init__(self, *args, **kwargs)
781
def log(self, message):
782
"""Redirect logging requests."""
783
mutter('_ftp_server: %s', message)
785
def log_info(self, message, type='info'):
786
"""Override the asyncore.log_info so we don't stipple the screen."""
787
mutter('_ftp_server %s: %s', type, message)
789
_test_authorizer = test_authorizer
790
_ftp_channel = ftp_channel
791
_ftp_server = ftp_server
796
556
def get_test_permutations():
797
557
"""Return the permutations to be used in testing."""
798
if not _setup_medusa():
799
warn("You must install medusa (http://www.amk.ca/python/code/medusa.html) for FTP tests")
558
from bzrlib import tests
559
if tests.FTPServerFeature.available():
560
from bzrlib.tests import FTPServer
561
return [(FtpTransport, FTPServer.FTPServer)]
802
return [(FtpTransport, FtpServer)]