#!/usr/bin/env python

from __future__ import print_function

import os
import sys
import time
import fnmatch
import pprint
import traceback
import links_and_nodes as ln

class fs_sync_host_base(object):
    def get_size_and_mtime(self, fn, required=False):
        # and mode and type and symlink_target
        if type(fn) == list:
            fn = ",".join(fn)
            infos = self.get_tree_info(fn, 0)
            sizes_and_mtimes = []
            for info in infos:
                if info["error_message"]:
                    if required:
                        raise Exception(info["error_message"])
                    sizes_and_mtimes.append(None)
                else:
                    sizes_and_mtimes.append((info["size"], info["mtime"], info["mode"], info["type"], info.get("symlink_target", "")))
            return sizes_and_mtimes
        resp = self.get_tree_info(fn, 0)
        info = resp[0]
        if info["error_message"]:
            if required:
                raise Exception(info["error_message"])
            return None
        return info["size"], info["mtime"], info["mode"], info["type"], info.get("symlink_target", "")

    def get_entries_below_dir(self, dn, recursive=True, empty_if_not_existing=False):
        infos = self.get_tree_info(dn, 1000 if recursive else 0)
        if infos[0]["error_message"]:
            if empty_if_not_existing and "No such file or directory" in infos[0]["error_message"]:
                return []
            raise Exception(infos[0]["error_message"])
        if infos[0]["type"] != "d":
            raise Exception("%r is not a directory: %r" % (dn, infos[0]))
        entries = []
        def add_entries(infos, prepend=""):
            for info in infos:
                if info["error_message"]:
                    continue # ignore
                entries.append((prepend + info["entry"], info["type"]))
                if recursive and info["childs"]:
                    add_entries(info["childs"], prepend + info["entry"] + "/")
        add_entries(infos[0]["childs"])
        return entries

class fs_sync_host(ln.services_wrapper, fs_sync_host_base):
    def __init__(self, clnt, prefix):
        self.name = prefix
        ln.services_wrapper.__init__(self, clnt, prefix)
        self.wrap_service("read_from_file", "ln/file_services2/read_from_file") # other end might send data as char
        self.wrap_service("write_file", "ln/file_services2/write_file") # we will send data as bytes
        self.wrap_service("get_tree_info", "ln/file_services/get_tree_info", method_name="_get_tree_info")
        self.wrap_service("get_tree_info2", "ln/file_services2/get_tree_info2", method_name="_get_tree_info2")
        self.wrap_service("chown", "ln/file_services/chown")
        self.wrap_service("clock_settime", "ln/file_services/clock_settime")
        self.wrap_service("put_symlink", "ln/file_services/put_symlink")
        self.wrap_service("unlink", "ln/file_services2/unlink")
        self.wrap_service("rmdir", "ln/file_services2/rmdir")
        self.assume_have_get_tree_info2 = True

    def get_tree_info(self, pathnames, maxdepth):
        if self.assume_have_get_tree_info2:
            try:
                return self._get_tree_info2(pathnames, maxdepth)
            except:
                if "no provider registered" in str(sys.exc_info()[1]):
                    self.assume_have_get_tree_info2 = False
                else:
                    raise
        return self._get_tree_info(pathnames, maxdepth)

class fs_sync_localhost(fs_sync_host_base):
    # localhost implementation of ffile services
    def __init__(self):
        self.name = "localhost"

    def get_tree_info(self, fn, maxdepth):
        fns = fn.split(",")
        ret = []
        for fn in fns:
            info = dict(
                error_message="",
                size=0,
                mtime=0
            )
            try:
                sbuf = os.lstat(fn)
                info["size"] = int(sbuf.st_size)
                info["mtime"] = int(sbuf.st_mtime)
                info["mode"] = int(sbuf.st_mode)

                info["symlink_target"] = ""
                if os.path.islink(fn):
                    info["type"] = "l"
                    info["symlink_target"] = os.readlink(fn)
                    info["size"] = 0
                    info["mtime"] = 0
                    info["mode"] = 0
                elif os.path.isdir(fn):
                    info["type"] = "d"
                elif os.path.isfile(fn):
                    info["type"] = "f"
                else:
                    info["type"] = "\0"
            except:
                info["error_message"] = str(sys.exc_info()[1])
            ret.append(info)
        return ret

    def read_from_file(self, fn, offset, length):
        with open(fn, "rb") as fp:
            fp.seek(offset)
            data = fp.read(length)
        return data

class fs_sync(object):
    def __init__(self, clnt=None):
        if "-help" in sys.argv or "--help" in sys.argv or "-h" in sys.argv:
            self.usage()
            sys.exit(0)
        if clnt is None:
            self.clnt = ln.client(sys.argv[0], sys.argv)
        else:
            self.clnt = clnt
        self.hosts = {}

    def get_human_readable(self, v):
        units = ["B", "kB", "MB", "GB", "TB"]
        unit_step = 1000
        start = 3/4.
        need_decimals = False
        while v > (start*unit_step):
            if len(units) == 1:
                break
            del units[0]
            v /= float(unit_step)
            need_decimals = True
        if need_decimals:
            return "%.3f%s" % (v, units[0])
        return "%.0f%s" % (v, units[0])
        
    def usage(self):
        print("""
links_and_nodes file_services client by Florian Schmidt (2015-04-16)

%s [-ln_manager HOST:PORT] [-v]* [-loop] 
    [-sync-file SRC_SPEC DST_SPEC [-chown USERNAME] ]*
    [-sync-files-from FILEPATH]*
    [-sync-files-from-to FILEPATH DST_NODE]*
    [-sync-dir SRC_DIRSPEC DST_DIRSPEC [-r] [-include-pattern PATTERN]* [-exclude-pattern PATTERN]*
               [-exclude-patterns-from-file FILEPATH]* [-multi-dirs-sep SEPARATOR]  [-use-real-path]
               [-chown USERNAME] [-prepend_dst_base] [-delete]
    ]*
    [-sync-time NODE_NAME [-systohc] ]

options followed by * can be repeated multiple times.

 -v     
        be verbose (can be repeated up to 3 times)

-dry
        dry-run - do not transfer or remove files/directories, only print actions.

 -loop  
        do not exit. wait for the user to press enter and then run again

 -sync-file SRC_SPEC DST_SPEC
        sync a single file from SRC_SPEC to DST_SPEC where those 
        specs are of the form
          LOCAL_SPEC  := FILEPATH
          REMOTE_SPEC := NODE_NAME:FILEPATH
                      
          DST_SPEC := LOCAL_SPEC | REMOTE_SPEC | NODE_NAME: | NODE_NAME:PATH/
          SRC_SPEC := LOCAL_SPEC | REMOTE_SPEC

        -chown USERNAME
           change username of created files on remote host

-sync-files-from FILEPATH
        read the named textfile specified by FILEPATH and interpret 
        each line as
          SRC_SPEC DST_SPEC
        that is: they are sparated by a single space " "
        (empty lines and lines starting with # are ignored)

-sync-files-from-to FILEPATH DST_NODE
        read the named textfile specified by FILEPATH and interpret each
        line as a single SRC_SPEC. for each line the same DST_SPEC is 
        used:
          DST_SPEC := DST_NODE:
        which will sync only towards a single node and will use the same 
        filenames from the SRC_SPEC.

-sync-dir SRC_DIRSPEC DST_DIRSPEC
        sync a single directory with its contents from SRC_DIRSPEC to DST_DIRSPEC with
          SRC_DIRSPEC := LOCAL_DIRSPEC
          DST_DIRSPEC := LOCAL_DIRSPEC | REMOTE_DIRSPEC | NODE_NAME:

          LOCAL_DIRSPEC  := DIRPATH
          REMOTE_DIRSPEC := NODE_NAME:DIRPATH

        target directories are created as needed.

        specify -r to recurse into sub-directories.

        -chown USERNAME
           change username of created files on remote host

        -prepend_dst_base
           use full DIRPATH from SRC_DIRSPEC to create entries below DST_DIRSPEC
           (`fs_sync /home/some/file target:/from_remote` creates remote file `/from_remote/home/some/file`)

        -delete
           delete remote files and directories in DST_DIRSPEC which don't exist in SRC_DIRSPEC

-sync-time NODE_NAME
        sync local time to remote node, optionally calling hwclock -systohc

""" % sys.argv[0])
        

    def run(self):
        jobs = []
        args = self.clnt.get_remaining_args()[1:]
        skip = 0
        had_error = False
        do_loop = False
        sync_dir_opts = None
        sync_file_opts = None
        sync_time_opts = None
        self.verbosity = 0
        self.dry = False
        for i, arg in enumerate(args):
            if skip:
                skip -= 1
                continue
            if arg == "-loop":
                do_loop = True
                continue
            if arg.startswith("-v"):
                self.verbosity += arg.count("v")
                continue
            if arg == "-sync-file":
                src_file, dst_file = args[i + 1], args[i + 2]
                skip = 2
                sync_file_opts = []
                jobs.append(("sync_file", src_file, dst_file, None, sync_file_opts))
                continue
            if arg == "-sync-files-from":
                src_file = args[i + 1]
                skip = 1
                jobs.append(("sync_files_from", src_file))
                continue
            if arg == "-sync-files-from-to":
                src_file, target_node = args[i + 1], args[i + 2]
                skip = 2
                jobs.append(("sync_files_from_to", src_file, target_node))
                continue
            if arg == "-sync-dir":
                sync_dir_opts = []
                src_dir, dst_dir = args[i + 1], args[i + 2]
                skip = 2
                jobs.append(("sync_dir", src_dir, dst_dir, sync_dir_opts))
                continue
            if arg == "-sync-time":
                sync_time_opts = []
                dst_node = args[i + 1]
                skip = 1
                jobs.append(("sync_time", dst_node, sync_time_opts))
                continue
            if arg == "-systohc":
                skip = 0
                if sync_time_opts is not None:
                    sync_time_opts.append("systohc")
                continue
            if arg == "-r":
                sync_dir_opts.append("recursive")
                continue
            if arg == "-dry":
                self.dry = True
                continue
            if arg == "-prepend_dst_base":
                sync_dir_opts.append("prepend_dst_base")
                continue
            if arg == "-include-pattern":
                pattern = args[i + 1]
                skip = 1
                sync_dir_opts.append(("include_pattern", pattern))
                continue
            if arg == "-chown":
                username = args[i + 1]
                skip = 1
                if sync_dir_opts is not None:
                    sync_dir_opts.append(("chown", username))
                if sync_file_opts is not None:
                    sync_file_opts.append(("chown", username))
                continue
            if arg == "-delete":
                if sync_dir_opts is not None:
                    sync_dir_opts.append("delete")
                continue
            if arg == "-exclude-pattern":
                pattern = args[i + 1]
                skip = 1
                sync_dir_opts.append(("exclude_pattern", pattern))
                continue
            if arg == "-exclude-patterns-from-file":
                fn = args[i + 1]
                skip = 1
                sync_dir_opts.append(("exclude_patterns_file", fn))
                continue
            if arg == "-multi-dirs-sep":
                separator = args[i + 1]
                skip = 1
                sync_dir_opts.append(("multi_dirs_separator", separator))
                continue
            if arg == "-use-real-path":
                sync_dir_opts.append("use_real_path")
                continue
            print("unknown argument: %r" % arg)
            had_error = True
        if had_error:
            print("...stopping because of invalid command line arguments")
            sys.exit(0)
        while True:
            start = time.time()
            self.bytes_transferred = 0
            self.deleted_files = 0
            self.deleted_directories = 0
            for job in jobs:
                method = job[0]
                args = job[1:]
                getattr(self, method)(*args)
            runtime = time.time() - start
            info = ["runtime: %.1fs" % runtime]
            if self.bytes_transferred:
                info.append("%s transferred, %s/s" % (
                    self.get_human_readable(self.bytes_transferred),
                    self.get_human_readable(self.bytes_transferred / runtime)))
            if self.deleted_files:
                info.append("%d deleted files" % self.deleted_files)
            if self.deleted_directories:
                info.append("%d deleted directories" % self.deleted_directories)
            if len(info) == 1:
                info.append("all up-to-date")
            print(", ".join(info))

            if not do_loop:
                break
            print("loop mode. press enter to start again!")
            input()

    def get_host(self, name):
        if name in self.hosts:
            return self.hosts[name]
        if name == "localhost":
            host = fs_sync_localhost()
        else:
            host = fs_sync_host(self.clnt, name)
        self.hosts[name] = host
        return host

    def parse_spec(self, spec):
        if ":" in spec:
            host_name, fn = spec.split(":", 1)
        else:
            host_name = "localhost"
            fn = spec
        return self.get_host(host_name), fn
        
    def sync_files_from(self, list_file):
        jobs = []
        with open(list_file, "r") as fp:
            list_file_dir = os.path.dirname(list_file)
            for i, line in enumerate(fp):
                line = line.strip()
                if not line:
                    continue
                if line.startswith("#"):
                    continue
                try:
                    src, dst = line.split(" ", 1)
                except:
                    raise Exception("%s:%d invalid line: %r\nexpect line to be in format: SRC-SPEC DST-SPEC" % (
                        list_file, i + 1, line))
                jobs.append((src.strip(), dst.strip()))
        for src, dst in jobs:
            self.sync_file(src, dst, base_dir=list_file_dir)

    def make_abs(self, what, base_dir):
        if not base_dir:
            return what
        if what[0] == "/":
            return what
        if what[:2] == "./":
            return os.path.join(base_dir, what[2:])
        return os.path.join(base_dir, what)

    def sync_files_from_to(self, list_file, to_node):
        jobs = []
        with open(list_file, "r") as fp:
            list_file_dir = os.path.dirname(list_file)
            for i, line in enumerate(fp):
                line = line.strip()
                if not line:
                    continue
                if line.startswith("#"):
                    continue
                jobs.append(line)
        dst_host, dst = self.parse_spec("%s:" % to_node)
        files_per_host = dict()
        if self.verbosity >= 1: print("get file attributes...")
        for src_spec in jobs:
            #self.sync_file(src, "%s:" % to_node, base_dir=list_file_dir)
            src_host, src = self.parse_spec(src_spec)
            src = self.make_abs(src, list_file_dir)
            if src_host not in files_per_host:
                files_per_host[src_host] = []
            files_per_host[src_host].append(src)
        for src_host, files in files_per_host.items():
            src_attrs = src_host.get_size_and_mtime(files, required=True)
            dst_attrs = dst_host.get_size_and_mtime(files)
            for fn, src_attr, dst_attr in zip(files, src_attrs, dst_attrs):
                self._sync_file(
                    src_host, fn, src_attr,
                    dst_host, fn, dst_attr)

    def sync_file(self, src_spec, dst_spec, base_dir=None, opts=None):
        if self.verbosity >= 1: print("get file attributes...")

        src_host, src = self.parse_spec(src_spec)
        dst_host, dst = self.parse_spec(dst_spec)

        if dst.endswith("/"):
            dst = os.path.join(dst, os.path.basename(src))
        if not dst:
            dst = src

        src = self.make_abs(src, base_dir)
        dst = self.make_abs(dst, base_dir)

        chown = None
        if opts:
            for opt in opts:
                if type(opt) == tuple:
                    if opt[0] == "chown":
                        chown = opt[1]
        self._sync_file(
            src_host, src, src_host.get_size_and_mtime(src, required=True),
            dst_host, dst, dst_host.get_size_and_mtime(dst),
            chown=chown)

    def _sync_file(self, src_host, src, src_attrs, dst_host, dst, dst_attrs, chown=None):
        if self.verbosity >= 1: 
            if src_host.name != "localhost":
                print("%s:%s" % (src_host, src))
            else:
                print(src)
        if src_attrs == dst_attrs:
            if self.verbosity >= 2: print("  file is up-to-date")
            return
        if not dst_attrs:
            if self.verbosity >= 2: print("  file does not exist at target")
        else:
            size_delta = dst_attrs[0] - src_attrs[0]
            ts_delta = dst_attrs[1] - src_attrs[1]
            if self.verbosity >= 2:
                if size_delta > 0:
                    print("  file at target is %d bytes larger2" % size_delta)
                elif size_delta < 0:
                    print("  file at target is %d bytes smaller2" % -size_delta)
                if ts_delta > 0:
                    print("  file at target is %d seconds newer2 (src %r dst %d)" % (ts_delta, src_attrs[1], dst_attrs[1]))
                elif ts_delta < 0:
                    print("  file at target is %d seconds older2" % -ts_delta)
                if dst_attrs[2] != src_attrs[2]:
                    print("source mode is %04o, destination mode is %04o" % (src_attrs[2], dst_attrs[2]))
                if dst_attrs[3] != src_attrs[3]:
                    print("source type is %r, destination type is %r, assume_have_get_tree_info2: %s" % (src_attrs[3], dst_attrs[3], dst_host.assume_have_get_tree_info2))
                if dst_attrs[4] != src_attrs[4]:
                    print("source symlink_target is %r, destination is %r" % (src_attrs[4], dst_attrs[4]))
        if self.verbosity < 1:
            if src_host.name != "localhost":
                print("%s:%s" % (src_host, src))
            else:
                print(src)
        
        if self.verbosity > 0 and self.verbosity < 2: 
            print(" -> changed!")
        
        if self.dry:
            return

        if dst_host.assume_have_get_tree_info2 and src_attrs[3] == "l":
            target = src_attrs[4]
            if self.verbosity >= 3: print("  put symlink targetting %r..." % (target, ))
            dst_host.put_symlink(dst, target)
        else:
            if self.verbosity >= 3: print("  read... %s" % self.get_human_readable(src_attrs[0]))
            data = src_host.read_from_file(src, 0, src_attrs[0])
            if self.verbosity >= 3: print("  write...")
            dst_host.write_file(dst, data, src_attrs[1], src_attrs[2])
            self.bytes_transferred += len(data)
        if chown:
            try:
                dst_host.chown(dst, chown)
            except:
                if "no provider registered" in str(sys.exc_info()[1]):
                    print("chown to %r not supported by file_services binary..." % chown)
                else:
                    raise


    def sync_dir(self, src, dst_spec, opts):
        recursive = "recursive" in opts
        use_real_path = "use_real_path" in opts
        prepend_dst_base = "prepend_dst_base" in opts
        delete = "delete" in opts
        include_patterns = []
        exclude_patterns = []
        multi_dirs_separator = None
        chown = None
        for opt in opts:
            if type(opt) == tuple:
                if opt[0] == "include_pattern":
                    include_patterns.append(opt[1])
                elif opt[0] == "exclude_pattern":
                    exclude_patterns.append(opt[1])
                elif opt[0] == "exclude_patterns_file":
                    with open(opt[1]) as fp:
                        for line in fp:
                            line = line.strip()
                            if not line or line.startswith("#"):
                                continue
                            exclude_patterns.append(line)
                elif opt[0] == "multi_dirs_separator":
                    multi_dirs_separator = opt[1]
                elif opt[0] == "chown":
                    chown = opt[1]
        
        if multi_dirs_separator is not None:
            src_dirs = src.strip(multi_dirs_separator).split(multi_dirs_separator)
            remove = ['""']
            for item in remove:
                if item in src_dirs:
                    del src_dirs[src_dirs.index(item)]
        else:
            src_dirs = [src]
        if use_real_path:
            src_dirs = [os.path.realpath(path) for path in src_dirs]
        src_host = self.get_host("localhost")
        
        base_dir = os.getcwd()
        src_dirs = [self.make_abs(src, base_dir).rstrip("/") for src in src_dirs]

        if self.verbosity >= 3: 
            if len(src_dirs) == 1:
                print("sync_dir %r" % src_dirs[0])
            else:
                print("sync_dirs")
                for src in src_dirs:
                    print(" %r" % src)
            print("  include patterns: %s" % (", ".join(include_patterns)))
            print("  exclude patterns: %s" % (", ".join(exclude_patterns)))
            print("  recursive: %s" % recursive)
            print("  dry: %s" % self.dry)
            print("  chown: %s" % chown)
            print("  delete: %s" % delete)

        if self.dry and self.verbosity < 2:
            self.verbosity = 2
                    
        def append_jobs(src, jobs, src_dirs, src_base):
            #print "list dir %r" % src
            for e in os.listdir(src):
                if e in (".", ".."):
                    continue
                p = os.path.join(src, e)
                isdir = os.path.isdir(p)
                if not isdir and include_patterns:
                    for pattern in include_patterns:
                        if fnmatch.fnmatch(e, pattern):
                            break
                    else:
                        if self.verbosity >= 3: print("skip because not included: %r" % (e))
                        continue # skip file, not in include patterns!
                if exclude_patterns:
                    skip = False
                    for pattern in exclude_patterns:
                        if "/" in pattern:
                            if fnmatch.fnmatch(p, pattern):
                                skip = True
                                break
                        else:
                            #print (e, pattern)
                            if fnmatch.fnmatch(e, pattern):
                                skip = True
                                break
                    if skip:
                        if self.verbosity >= 3: print("skip because of exclude %r: %r" % (pattern, e))
                        continue # skip file, in exclude patterns!
                if isdir:
                    if recursive:
                        src_dirs.append(p[len(src_base) + 1:])
                        append_jobs(p, jobs, src_dirs, src_base)
                elif not os.access(p, os.R_OK):
                    print("can not read %r" % p)
                else:
                    jobs.append(p)
        src_files = []
        dirs_in_src_dirs = [] # those are not copied, but also not deleted
        for src in src_dirs:
            append_jobs(src, src_files, dirs_in_src_dirs, src)

        dst_host, dst = self.parse_spec(dst_spec)
        dst = dst.strip()
        if dst:
            # change base path!
            dst_files = []
            for p in src_files:
                if not prepend_dst_base:
                    for src in src_dirs:
                        if p.startswith(src + os.sep):
                            base_fn = p[len(src) + 1:]
                            break
                    else:
                        raise Exception("unable to find source dir for source file %r" % p)
                    if base_fn.startswith("/"):
                        base_fn = base_fn[1:]
                    new_fn = os.path.join(dst, base_fn)
                else:
                    base_fn = p
                    if base_fn.startswith("./"):
                        base_fn = base_fn[2:]
                    elif base_fn[0] == "/":
                        base_fn = base_fn[1:]
                    new_fn = os.path.join(dst, base_fn)
                dst_files.append(new_fn)
        else:
            dst_files = src_files

        if src_files:
            try:
                src_attrs = src_host.get_size_and_mtime(src_files, required=True)
            except:
                raise Exception("could not list source files:\n%s\nfiles:\n%s" % (
                    traceback.format_exc(),
                    ",\n".join(src_files)))
            dst_attrs = dst_host.get_size_and_mtime(dst_files)
            for src_fn, dst_fn, src_attr, dst_attr in zip(src_files, dst_files, src_attrs, dst_attrs):
                self._sync_file(
                    src_host, src_fn, src_attr,
                    dst_host, dst_fn, dst_attr,
                    chown=chown)

        if not delete:
            return

        if dst.endswith(os.sep):
            dst = dst[:-1]
        if not dst:
            if len(src_dirs) > 1:
                raise Exception("`-delete` not supported with empty dst-dir and multiple source directories!")
            dst = src_dirs[0]
            if dst.endswith(os.sep):
                dst = dst[:-1]
        # are there files to delete?
        prepend = dst + os.sep

        expected_files = set() # list of relative filenames below dst
        for dst_fn in dst_files:
            efn = dst_fn[len(dst) + 1:]
            expected_files.add(efn)

        directories_to_delete = []
        files_to_delete = []
        for entry, entry_type in dst_host.get_entries_below_dir(dst, recursive=recursive, empty_if_not_existing=True):
            if self.verbosity > 2:
                print("  delete found remote enrty %r type %s" % (entry, entry_type))
            is_in_to_be_deleted_directory = False
            for dtd in directories_to_delete:
                if entry.startswith(dtd + os.sep):
                    is_in_to_be_deleted_directory = True
                    break
            if is_in_to_be_deleted_directory:
                continue

            if entry_type == "d":
                if not recursive:
                    if self.verbosity > 2:
                        print("    not running recursively, no deleting remote subdirs")
                    continue
                if entry in dirs_in_src_dirs:
                    if self.verbosity > 2:
                        print("    that directory exist in source dirs")
                    continue
                search = entry + os.sep
                for efn in expected_files:
                    if efn.startswith(search):
                        if self.verbosity > 2:
                            print("    that directory is used by source file %r" % efn)
                        break
                else:
                    if self.verbosity > 0:
                        print(" directory %r does not exist in sources -> delete!" % entry)
                    directories_to_delete.append(entry)
                    continue
                continue
            if entry in expected_files:
                if self.verbosity > 2:
                    print("    that file is in use")
                continue
            if self.verbosity > 0:
                print(" entry %r does not exist in sources -> delete!" % entry)
            files_to_delete.append(entry)
        if not self.dry:
            for fn in files_to_delete:
                dst_host.unlink(prepend + fn)
                self.deleted_files += 1
            for dn in directories_to_delete:
                dst_host.rmdir(prepend + dn, 1)
                self.deleted_directories += 1

    def sync_time(self, dst, opts):
        dst_host = self.get_host(dst)        
        if "systohc" in opts:
            systohc = 1
        else:
            systohc = 0
        t = time.time()
        tv_sec = int(t)
        tv_nsec = int((t - tv_sec) * 1e9)
        dst_host.clock_settime(0, tv_sec, tv_nsec, systohc)

if __name__ == "__main__":
    s = fs_sync()
    s.run()
