Changeset 212

Show
Ignore:
Timestamp:
2003-03-15 21:22:31 (6 years ago)
Author:
schwa
Message:
FTPHost: Added support for synchronizing local times of client and server.
Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/_test_ftputil.py

    r206 r212  
    1 # Copyright (C) 2002, Stefan Schwarzer 
     1# Copyright (C) 2003, Stefan Schwarzer 
    22# All rights reserved. 
    33# 
     
    3030# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
    3131 
    32 # $Id: _test_ftputil.py,v 1.63 2003/03/15 18:46:17 schwa Exp $ 
     32# $Id: _test_ftputil.py,v 1.64 2003/03/15 21:22:31 schwa Exp $ 
    3333 
    3434import unittest 
     
    9393    mock_file_content = binary_data() 
    9494 
     95class TimeShiftMockSession(_mock_ftplib.MockSession): 
     96    current_dir = '/login/dir' 
     97 
     98    def delete(self, file_name): 
     99        pass 
     100 
    95101 
    96102# 
     
    99105class FailingUploadAndDownloadFTPHost(ftputil.FTPHost): 
    100106    def upload(self, source, target, mode=''): 
    101         assert 0, "FTPHost.upload should not have been called" 
     107        assert 0, "`FTPHost.upload` should not have been called" 
    102108 
    103109    def download(self, source, target, mode=''): 
    104         assert 0, "FTPHost.download should not have been called" 
     110        assert 0, "`FTPHost.download` should not have been called" 
     111 
     112class TimeShiftFTPHost(ftputil.FTPHost): 
     113    class _Path: 
     114        def set_mtime(self, mtime): 
     115            self._mtime = mtime 
     116        def getmtime(self, file_name): 
     117            return self._mtime 
     118 
     119    def __init__(self, *args, **kwargs): 
     120        ftputil.FTPHost.__init__(self, *args, **kwargs) 
     121        self.path = self._Path() 
    105122 
    106123 
     
    555572 
    556573 
     574class TestTimeShift(unittest.TestCase): 
     575    def test_rounded_time_shift(self): 
     576        """Test if time shift is rounded correctly.""" 
     577        host = ftp_host_factory(session_factory=TimeShiftMockSession) 
     578        # use private bound method 
     579        rounded_time_shift = host._FTPHost__rounded_time_shift 
     580        # original value, expected result 
     581        test_data = [ 
     582          (0, 0), (0.1, 0), (-0.1, 0), (1500, 0), (-1500, 0), 
     583          (1800, 3600), (-1800, -3600), (2000, 3600), (-2000, -3600), 
     584          (5*3600-100, 5*3600), (-5*3600+100, -5*3600) ] 
     585        for time_shift, expected_time_shift in test_data: 
     586            calculated_time_shift = rounded_time_shift(time_shift) 
     587            self.assertEqual(calculated_time_shift, expected_time_shift) 
     588 
     589    def test_assert_valid_time_shift(self): 
     590        """Test time shift sanity checks.""" 
     591        host = ftp_host_factory(session_factory=TimeShiftMockSession) 
     592        # use private bound method 
     593        assert_time_shift = host._FTPHost__assert_valid_time_shift 
     594        # valid time shifts 
     595        test_data = [23*3600, -23*3600, 3600+30, -3600+30] 
     596        for time_shift in test_data: 
     597            self.failUnless( assert_time_shift(time_shift) is None ) 
     598        # invalid time shift (exceeds one day) 
     599        self.assertRaises(ftputil.TimeShiftError, assert_time_shift, 25*3600) 
     600        self.assertRaises(ftputil.TimeShiftError, assert_time_shift, -25*3600) 
     601        # invalid time shift (deviation from full hours unacceptable) 
     602        self.assertRaises(ftputil.TimeShiftError, assert_time_shift, 10*60) 
     603        self.assertRaises(ftputil.TimeShiftError, assert_time_shift, 
     604                          -3600-10*60) 
     605         
     606    def test_synchronize_time(self): 
     607        """Test time synchronization with server.""" 
     608        host = ftp_host_factory(ftp_host_class=TimeShiftFTPHost, 
     609                                session_factory=TimeShiftMockSession) 
     610        # valid time shift 
     611        host.path.set_mtime( time.time() + 3630 ) 
     612        host.synchronize_time() 
     613        self.assertEqual( host.time_shift(), 3600 ) 
     614        # invalid time shift 
     615        host.path.set_mtime( time.time() + 3600+10*60 ) 
     616        self.assertRaises(ftputil.TimeShiftError, host.synchronize_time) 
     617 
     618 
    557619if __name__ == '__main__': 
    558620    unittest.main() 
  • trunk/ftputil.py

    r210 r212  
    3030# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
    3131 
    32 # $Id: ftputil.py,v 1.102 2003/03/15 18:57:22 schwa Exp $ 
     32# $Id: ftputil.py,v 1.103 2003/03/15 21:22:31 schwa Exp $ 
    3333 
    3434""" 
     
    128128 
    129129class RootDirError(FTPError): pass 
     130class TimeShiftError(FTPError): pass 
    130131 
    131132class FTPOSError(FTPError, OSError): pass 
     
    392393        #  servers 
    393394        self.curdir, self.pardir, self.sep = '.', '..', '/' 
     395        # set default time shift (used in `upload_if_newer` and 
     396        #  `download_if_newer`) 
     397        self.set_time_shift(0.0) 
    394398        # check if we have a Microsoft ROBIN server 
    395399        try: 
     
    481485            # we don't want warnings if the constructor did fail 
    482486            pass 
     487 
     488    # 
     489    # time shift adjustment between client (i. e. us) and server 
     490    # 
     491    def set_time_shift(self, time_shift): 
     492        """ 
     493        Set the time shift value (i. e. the time difference between 
     494        client and server) for this `FTPHost` object. By definition, 
     495        the time shift value is positive if the local time of the 
     496        server is greater than the local time of the client. The 
     497        time shift is measured in seconds. 
     498        """ 
     499        self._time_shift = time_shift 
     500 
     501    def time_shift(self): 
     502        """ 
     503        Return the time shift between FTP server and client. See the 
     504        docstring of `set_time_shift` for more on this value. 
     505        """ 
     506        return self._time_shift 
     507 
     508    def __rounded_time_shift(self, time_shift): 
     509        """ 
     510        Return the given time shift in seconds, but rounded to 
     511        full hours. The argument is also assumed to be given in 
     512        seconds. 
     513        """ 
     514        minute = 60.0 
     515        hour = 60 * minute 
     516        # avoid division by zero below 
     517        if time_shift == 0: 
     518            return 0.0 
     519        # use a positive value for rounding 
     520        absolute_time_shift = abs(time_shift) 
     521        signum = time_shift / absolute_time_shift 
     522        # round it to hours; this code should also work for later Python 
     523        #  versions because of the explicit `int` 
     524        absolute_rounded_time_shift = \ 
     525          int( (absolute_time_shift+30*minute) / hour) * hour 
     526        # return with correct sign 
     527        return signum * absolute_rounded_time_shift 
     528 
     529    def __assert_valid_time_shift(self, time_shift): 
     530        """ 
     531        Perform sanity checks on the time shift value (given in 
     532        seconds). If the value fails, raise a `TimeShiftError`, 
     533        else simply return `None`. 
     534        """ 
     535        minute = 60.0 
     536        hour = 60 * minute 
     537        absolute_rounded_time_shift = \ 
     538          abs( self.__rounded_time_shift(time_shift) ) 
     539        # test 1: fail if the absolute time shift is greater than 
     540        #  a full day (24 hours) 
     541        if absolute_rounded_time_shift > 24 * hour: 
     542            raise TimeShiftError( 
     543                  "time shift (%.2f s) > 1 day" % time_shift) 
     544        # test 2: fail if the deviation between given time shift and 
     545        #  full hours is greater than a certain limit (e. g. five minutes) 
     546        maximum_deviation = 5 * minute 
     547        if abs( time_shift - self.__rounded_time_shift(time_shift) ) > \ 
     548           maximum_deviation: 
     549            raise TimeShiftError( 
     550                  "time shift (%.2f s) deviates more than %d s from full hours" 
     551                  % (time_shift, maximum_deviation) ) 
     552 
     553    def synchronize_time(self): 
     554        """ 
     555        Synchronize the local times of FTP client and server. This 
     556        is necessary to let `upload_if_newer` and `download_if_newer` 
     557        work correctly. 
     558 
     559        This implementation of `synchronize_time` requires all of the 
     560        following: 
     561        - The connection between server and client is established. 
     562        - The client has write access to the directory that is 
     563          current when `synchronize_time` is called. 
     564        - That directory isn't the root directory of the FTP server. 
     565 
     566        The usual usage pattern of `synchronize_time` is to call it 
     567        directly after the connection is established. (As can be 
     568        concluded from the points above, this requires write access 
     569        to the login directory.) 
     570 
     571        If `synchronize_time` fails, it raises a `TimeShiftError`. 
     572        """ 
     573        helper_file_name = "_ftputil_sync_" 
     574        # open a dummy file for writing in the current directory 
     575        #  on the FTP host, then close it 
     576        try: 
     577            file_ = self.file(helper_file_name, 'w') 
     578            file_.close() 
     579            # get the modification time of the new file 
     580            try: 
     581                modification_time = self.path.getmtime(helper_file_name) 
     582            except RootDirError: 
     583                raise TimeShiftError("can't use root directory for temp file") 
     584        finally: 
     585            # remove the just written file 
     586            self.unlink(helper_file_name) 
     587        # calculate the difference between server and client 
     588        time_shift = modification_time - time.time() 
     589        # do some sanity checks 
     590        self.__assert_valid_time_shift(time_shift) 
     591        # if tests passed, store the time difference as time shift value 
     592        self.set_time_shift( self.__rounded_time_shift(time_shift) ) 
    483593 
    484594    #