Changeset 679:f4405b7674a3


Ignore:
Timestamp:
Jun 3, 2007, 8:24:33 PM (13 years ago)
Author:
Stefan Schwarzer <sschwarzer@…>
Branch:
default
Convert:
svn:778c30c8-61e0-0310-89d4-fe2f97a467b2/trunk@711
Message:
Preliminary, incomplete version of directory tree synchronization.
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • _test_ftp_sync.py

    r676 r679  
    3232# $Id$
    3333
     34import os
     35import shutil
     36import sys
     37import unittest
    3438
     39TEST_ROOT = "/home/schwa/sd/python/ftputil"
     40sys.path.insert(0, os.path.dirname(TEST_ROOT))
     41
     42from ftputil import ftp_sync
     43
     44
     45class TestLocalToLocal(unittest.TestCase):
     46    def setUp(self):
     47        target_dir = os.path.join(TEST_ROOT, "test_target")
     48        shutil.rmtree(target_dir)
     49        os.mkdir(target_dir)
     50
     51    def test_sync_empty_dir(self):
     52        source = ftp_sync.LocalHost()
     53        target = ftp_sync.LocalHost()
     54        syncer = ftp_sync.Syncer(source, target)
     55        source_dir = os.path.join(TEST_ROOT, "test_empty")
     56        target_dir = os.path.join(TEST_ROOT, "test_target")
     57        syncer.sync(source_dir, target_dir)
     58
     59
     60if __name__ == '__main__':
     61    unittest.main()
     62
  • 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
Note: See TracChangeset for help on using the changeset viewer.