#!/usr/bin/env python import os import sys import shutil import platform # copy_dot_hidden_enable = False # delete_if_not_on_src_enable = True CMD_RMTREE = "rm -Rf" CMD_RM = "rm -f" CMD_COMMENT = "# " CMD_CP = "cp" if platform.system() == "Windows": CMD_RMTREE = "rd /S /Q" # /S: Delete all files and subdirectories and the directory itself. # /Q: Do not ask on global wildcard. CMD_RM = "del /Q /F" # /Q: Do not confirm on wildcard. # /F: Force deleting read-only files. CMD_COMMENT = "REM " CMD_CP = "COPY" def path_join_all(names): result = names[0] for i in range(1, len(names)): result = os.path.join(result, names[i]) return result def trim_branch(src, dst, dot_hidden=True): ''' Explore dst non-recursively and delete files and subdirectories recursively that are not present on src. Keyword arguments: dot_hidden -- Operate on files and directories even if they are hidden by starting with '.'. ''' for sub_name in os.listdir(dst): src_sub_path = os.path.join(src, sub_name) dst_sub_path = os.path.join(dst, sub_name) if not dot_hidden: if sub_name.startswith("."): continue if not os.path.exists(src_sub_path): if os.path.isfile(dst_sub_path): print("{} \"{}\"...".format(CMD_RM, dst_sub_path)) os.remove(dst_sub_path) else: print("{} \"{}\"...".format(CMD_RMTREE, dst_sub_path)) shutil.rmtree(dst_sub_path) def update_tree(src, dst, level=0, do_trim=False, dot_hidden=False): ''' Creates dst if not present, then copies everything from src to dst recursively. Keyword arguments: do_trim -- Delete files and directories from dst that are not on src. dot_hidden -- Copy files and directories even if hidden by starting with '.'. ''' folder_path = src if level <= 1: print(CMD_COMMENT + " "*level + "synchronizing with \"{}\"" "".format(dst)) if not os.path.isdir(dst): os.makedirs(dst) else: if do_trim: trim_branch(src, dst, dot_hidden=dot_hidden) if os.path.isdir(folder_path): for sub_name in os.listdir(folder_path): sub_path = os.path.join(folder_path, sub_name) dst_sub_path = os.path.join(dst, sub_name) allow_copy = True if not dot_hidden: allow_copy = not sub_name.startswith(".") if allow_copy and os.path.isdir(sub_path): update_tree(sub_path, dst_sub_path, level+1) if allow_copy and os.path.isfile(sub_path): try: shutil.copyfile(sub_path, dst_sub_path) except PermissionError: print(CMD_COMMENT + "PermissionError:") print(CMD_COMMENT + " {} \"{}\" \"{}\"" "".format(CMD_CP, sub_path, dst_sub_path)) pass USAGE = ''' Syntax: forwardfilesync.py [options] --hidden Process files & folders even if named starting with '.'. --delete Delete files & folders on the destination if not in source. ''' def usage(): print(USAGE) def main(): flags = {} flags["hidden"] = False flags["delete"] = False if len(sys.argv) == 3: pass elif (len(sys.argv) == 4) and (sys.argv[3][:2] == "--"): name = sys.argv[3][2:] if name not in flags: usage() print("Error: The syntax is invalid. Expected:") print("forwardfilesync.py ") print("forwardfilesync.py --hidden") exit(1) flags[name] = True else: usage() print("Error: The syntax is invalid. Expected:") print("forwardfilesync.py ") print("forwardfilesync.py --hidden --delete") exit(1) src = sys.argv[1] dst = sys.argv[2] update_tree( src, dst, do_trim=flags["delete"] is True, dot_hidden=flags["hidden"] is True, ) print(CMD_COMMENT + "Done.") if __name__ == "__main__": main()