Changeset 845:a665b9c86548

Show
Ignore:
Timestamp:
2010-02-11 08:50:49 (6 months ago)
Author:
Stefan Schwarzer <sschwarzer@…>
Branch:
default
Message:
Preliminary upload/download refactoring. Some download tests fail.
Files:
1 modified

Legend:

Unmodified
Added
Removed
  • ftputil.py

    r843 r845  
    9393 
    9494 
     95class _TransferredFile(object): 
     96    """ 
     97    Represent a file on the local or remote side which is to 
     98    be transferred or is already transferred. 
     99    """ 
     100 
     101    def __init__(self, host, name, mode): 
     102        self._host = host 
     103        self._is_local = (host is None) 
     104        if self._is_local: 
     105            self._path = os.path 
     106        else: 
     107            self._path = host.path 
     108        self.name = self._path.abspath(name) 
     109        self.mode = mode 
     110 
     111    def exists(self): 
     112        return self._path.exists(self.name) 
     113 
     114    def mtime(self): 
     115        mtime_ = self._path.getmtime(self.name) 
     116        if not self._is_local: 
     117            # convert to client time zone; see definition of time 
     118            #  shift in docstring of `FTPHost.set_time_shift` 
     119            mtime_ -= self._host.time_shift() 
     120        return mtime_ 
     121 
     122    def fobj(self): 
     123        if self._is_local: 
     124            return open(self.name, self.mode) 
     125        else: 
     126            return self._host.file(self.name, self.mode) 
     127 
     128 
    95129##################################################################### 
    96130# `FTPHost` class with several methods similar to those of `os` 
     
    395429 
    396430    # 
    397     # operations based on file-like objects (rather high-level) 
     431    # operations based on file-like objects (rather high-level), 
     432    #  like upload and download 
    398433    # 
    399434    def copyfileobj(self, source, target, length=64*1024): 
     
    409444    def __get_modes(self, mode): 
    410445        """Return modes for source and target file.""" 
     446        #XXX use dictionary? 
     447        # invalid mode values are handled when a file object is made 
    411448        if mode == 'b': 
    412449            return 'rb', 'wb' 
     
    414451            return 'r', 'w' 
    415452 
    416     def __copy_file(self, source, target, mode, source_open, target_open): 
    417         """ 
    418         Copy a file from source to target. Which of both is a local 
    419         or a remote file, respectively, is determined by the arguments. 
     453    def __copy_file(self, source_file, target_file, conditional): 
     454        """ 
     455        Copy a file from `source_file` to `target_file`. 
     456         
     457        Both of these are `_TransferredFile` objects. Which of them is 
     458        a local or a remote file, respectively, is determined by the 
     459        arguments. If `conditional` is true, the file is only copied 
     460        if the target doesn't exist or is older than the source. If 
     461        `conditional` is false, the file is copied unconditionally. 
     462        """ 
     463        if conditional: 
     464            # evaluate condition: the target file either doesn't exist or is 
     465            #  older than the source file; use >= in the comparison, that is 
     466            #  if in doubt (due to imprecise timestamps) transfer 
     467            #FIXME we probably need a more complex comparison, depending 
     468            #  on the precision of the timestamp on the server! 
     469            condition = not target_file.exists() or \ 
     470                        source_file.mtime() > target_file.mtime() 
     471            if not condition: 
     472                # we didn't transfer 
     473                return False 
     474        source_fobj = source_file.fobj() 
     475        try: 
     476            target_fobj = target_file.fobj() 
     477            try: 
     478                self.copyfileobj(source_fobj, target_fobj) 
     479            finally: 
     480                target_fobj.close() 
     481        finally: 
     482            source_fobj.close() 
     483        # transfer accomplished 
     484        return True 
     485 
     486    def _upload(self, source, target, mode, conditional): 
     487        """ 
     488        Upload from `source` to `target` which are `_TransferredFile` 
     489        objects. The string `mode` may be "" or "b". If `conditional` 
     490        is true, check if file should be copied at all. See the 
     491        docstring of `__copy_file` for more. 
    420492        """ 
    421493        source_mode, target_mode = self.__get_modes(mode) 
    422         source = source_open(source, source_mode) 
    423         try: 
    424             target = target_open(target, target_mode) 
    425             try: 
    426                 self.copyfileobj(source, target) 
    427             finally: 
    428                 target.close() 
    429         finally: 
    430             source.close() 
    431  
    432     def upload(self, source, target, mode=''): 
    433         """ 
    434         Upload a file from the local source (name) to the remote 
    435         target (name). The argument mode is an empty string or 'a' for 
    436         text copies, or 'b' for binary copies. 
    437         """ 
    438         self.__copy_file(source, target, mode, open, self.file) 
     494        source_file = _TransferredFile(None, source, source_mode) 
     495        target_file = _TransferredFile(self, target, target_mode) 
    439496        # the path in the stat cache is implicitly invalidated when 
    440497        #  the file is opened on the remote host 
     498        return self.__copy_file(source_file, target_file, conditional) 
     499 
     500    def upload(self, source, target, mode=''): 
     501        #FIXME should we support mode "a" at all? We don't support 
     502        #  appending! 
     503        """ 
     504        Upload a file from the local source (name) to the remote 
     505        target (name). The argument `mode` is an empty string or 'a' for 
     506        text copies, or 'b' for binary copies. 
     507        """ 
     508        self._upload(source, target, mode, conditional=False) 
     509 
     510    def upload_if_newer(self, source, target, mode=''): 
     511        """ 
     512        Upload a file only if it's newer than the target on the 
     513        remote host or if the target file does not exist. See the 
     514        method `upload` for the meaning of the parameters. 
     515 
     516        If an upload was necessary, return `True`, else return 
     517        `False`. 
     518        """ 
     519        return self._upload(source, target, mode, conditional=True) 
     520 
     521    def _download(self, source, target, mode, conditional): 
     522        """ 
     523        Download from `source` to `target` which are `_TransferredFile` 
     524        objects. The string `mode` may be "" or "b". If `conditional` 
     525        is true, check if file should be copied at all. See the 
     526        docstring of `__copy_file` for more. 
     527        """ 
     528        source_mode, target_mode = self.__get_modes(mode) 
     529        source_file = _TransferredFile(None, source, source_mode) 
     530        target_file = _TransferredFile(self, target, target_mode) 
     531        return self.__copy_file(source_file, target_file, conditional) 
    441532 
    442533    def download(self, source, target, mode=''): 
     534        #FIXME should we support mode "a" at all? We don't support 
     535        #  appending! 
    443536        """ 
    444537        Download a file from the remote source (name) to the local 
     
    446539        text copies, or 'b' for binary copies. 
    447540        """ 
    448         self.__copy_file(source, target, mode, self.file, open) 
    449  
    450     #XXX the use of the `copy_method` seems less-than-ideal 
    451     #  factoring; can we handle it in another way? 
    452  
    453     def __copy_file_if_newer(self, source, target, mode, 
    454       source_mtime, target_mtime, target_exists, copy_method): 
    455         """ 
    456         Copy a source file only if it's newer than the target. The 
    457         direction of the copy operation is determined by the 
    458         arguments. See methods `upload_if_newer` and 
    459         `download_if_newer` for examples. 
    460  
    461         If the copy was necessary, return `True`, else return `False`. 
    462         """ 
    463         source_timestamp = source_mtime(source) 
    464         if target_exists(target): 
    465             target_timestamp = target_mtime(target) 
    466         else: 
    467             # every timestamp is newer than this one 
    468             target_timestamp = 0.0 
    469         if source_timestamp > target_timestamp: 
    470             copy_method(source, target, mode) 
    471             return True 
    472         else: 
    473             return False 
    474  
    475     def __shifted_local_mtime(self, file_name): 
    476         """ 
    477         Return last modification of a local file, corrected with 
    478         respect to the time shift between client and server. 
    479         """ 
    480         local_mtime = os.path.getmtime(file_name) 
    481         # transform to server time 
    482         return local_mtime + self.time_shift() 
    483  
    484     def upload_if_newer(self, source, target, mode=''): 
    485         """ 
    486         Upload a file only if it's newer than the target on the 
    487         remote host or if the target file does not exist. See the 
    488         method `upload` for the meaning of the parameters. 
    489  
    490         If an upload was necessary, return `True`, else return 
    491         `False`. 
    492         """ 
    493         return self.__copy_file_if_newer(source, target, mode, 
    494           self.__shifted_local_mtime, self.path.getmtime, 
    495           self.path.exists, self.upload) 
     541        self._download(source, target, mode, conditional=False) 
    496542 
    497543    def download_if_newer(self, source, target, mode=''): 
     
    504550        `False`. 
    505551        """ 
    506         return self.__copy_file_if_newer(source, target, mode, 
    507           self.path.getmtime, self.__shifted_local_mtime, 
    508           os.path.exists, self.download) 
     552        return self._download(source, target, mode, conditional=True) 
    509553 
    510554    #