Show
Ignore:
Timestamp:
2007-06-03 18:24:33 (3 years ago)
Author:
Stefan Schwarzer <sschwarzer@…>
Branch:
default
convert_revision:
svn:778c30c8-61e0-0310-89d4-fe2f97a467b2/trunk@711
Message:
Preliminary, incomplete version of directory tree synchronization.
Files:
1 modified

Legend:

Unmodified
Added
Removed
  • ftp_sync.py

    r674 r679  
    3232# $Id$ 
    3333 
     34""" 
     35Tools for syncing combinations of local and remote directories. 
     36 
     37*** WARNING: This is an unfinished in-development version! 
     38""" 
     39 
     40# Sync combinations: 
     41# - remote -> local (download) 
     42# - local -> remote (upload) 
     43# - remote -> remote 
     44# - local -> local (perhaps implicitly possible due to design, but not targeted) 
     45 
     46import os 
     47import shutil 
     48 
     49from ftputil import FTPHost 
     50import ftp_error 
     51 
     52__all__ = ['FTPHost', 'LocalHost', 'Syncer'] 
     53 
     54 
     55# 64 KB 
     56CHUNK_SIZE = 64*1024 
     57 
     58 
     59class LocalHost(object): 
     60    def open(self, path, mode): 
     61        """ 
     62        Return a Python file object for file name `path`, opened in 
     63        mode `mode`. 
     64        """ 
     65        # this is the built-in `open` function, not `os.open`! 
     66        return open(path, mode) 
     67 
     68    def __getattr__(self, attr): 
     69        return getattr(os, attr) 
     70 
     71 
     72class Syncer(object): 
     73    def __init__(self, source, target): 
     74        """ 
     75        Init the `FTPSyncer` instance. 
     76         
     77        Each of `source` and `target` is either an `FTPHost` or a 
     78        `LocalHost` object. The source and target directories, resp. 
     79        have to be set with the `chdir` command before passing them 
     80        in. The semantics is so that the items under the source 
     81        directory will show up under the target directory after the 
     82        synchronization (unless there's an error). 
     83        """ 
     84        self._source = source 
     85        self._target = target 
     86 
     87    def _mkdir(self, target_dir): 
     88        """ 
     89        Try to create the target directory `target_dir`. If it already 
     90        exists, don't do anything. If the directory is present but 
     91        it's actually a file, raise a `SyncError`. 
     92        """ 
     93        #TODO handle setting of target mtime according to source mtime 
     94        #  (beware of rootdir anomalies; try to handle them as well) 
     95        print "Making", target_dir 
     96        if self._target.path.isfile(target_dir): 
     97            raise ftp_error.SyncError("target dir '%s' is actually a file" % 
     98                                      target_dir) 
     99        if not self._target.path.isdir(target_dir): 
     100            self._target.mkdir(target_dir) 
     101 
     102    def _sync_file(self, source_file, target_file): 
     103        #TODO handle `IOError`s 
     104        #TODO handle conditional copy 
     105        #TODO handle setting of target mtime according to source mtime 
     106        #  (beware of rootdir anomalies; try to handle them as well) 
     107        print "Syncing", source_file, "->", target_file 
     108        source = self._source.open(source_file, "rb") 
     109        try: 
     110            target = self._target.open(target_file, "wb") 
     111            try: 
     112                shutil.copyfileobj(source, target, length=CHUNK_SIZE) 
     113            finally: 
     114                target.close() 
     115        finally: 
     116            source.close() 
     117 
     118    def sync(self, source_dir, target_dir): 
     119        """ 
     120        Synchronize the source and the target by updating the target 
     121        to match the source as far as possible. 
     122 
     123        Current limitations: 
     124        - _don't_ delete items which are on the target path but not on the 
     125          source path 
     126        - files are always copied, the modification timestamps are not 
     127          compared 
     128        - all files are copied in binary mode, never in ASCII/text mode 
     129        - incomplete error handling 
     130        """ 
     131        source_dir = self._source.path.abspath(source_dir) 
     132        target_dir = self._target.path.abspath(target_dir) 
     133        self._mkdir(target_dir) 
     134        for dirpath, dirnames, filenames in self._source.walk(source_dir): 
     135            for dirname in dirnames: 
     136                inner_source_dir = self._source.path.join(dirpath, dirname) 
     137                inner_target_dir = inner_source_dir.replace(source_dir, 
     138                                                            target_dir, 1) 
     139                self._mkdir(inner_target_dir) 
     140            for filename in filenames: 
     141                source_file = self._source.path.join(dirpath, filename) 
     142                target_file = source_file.replace(source_dir, target_dir, 1) 
     143                self._sync_file(source_file, target_file) 
     144