#! /usr/bin/python3 import argparse import filecmp import os import shutil # Argument Parsing parser = argparse.ArgumentParser( prog="DataPuller", description="Check differences between folders and update files if needed", ) parser.add_argument("-i", "--input") parser.add_argument("-o", "--output") parser.add_argument("-q", "--quiet", action="store_true") parser.add_argument("-y", "--confirm", action="store_true") parser.add_argument("-d", "--delete", action="store_true") parser.add_argument("--dry-run", action="store_true") args = parser.parse_args() # Paths if not args.input: inpath = input("Source Path: ") else: inpath = args.input if not args.output: outpath = input("Target Path: ") else: outpath = args.output def get_files(path): file_info = [] if not os.path.exists(path): return file_info for folder, _, files in os.walk(path): for file in files: full_path = os.path.join(folder, file) rel_path = os.path.relpath(full_path, path) file_info.append(rel_path) return file_info def main(): startstring = " FOLDER-SYNC 1.0 " if not args.quiet: print( len(startstring) * "=" + "\n" + startstring + "\n" + len(startstring) * "=" ) # Test Folder availability if not os.path.exists(inpath): print(f"ERROR: The input folder ({inpath}) is unavailable or non-existant.") if not args.quiet: input("\nPress ENTER to exit...") exit(1) if not os.path.exists(outpath): print(f"ERROR: The output folder ({outpath}] is unavailable or non-existant.") if not args.quiet: input("\nPress ENTER to exit...") exit(1) print("Analyzing files. Please wait...\n") source = get_files(inpath) target = get_files(outpath) new = [] changed = [] deleted = [] # Check for file additions and changes for path in source: if path not in target: new.append(path) elif not filecmp.cmp(os.path.join(inpath, path), os.path.join(outpath, path)): changed.append(path) # Check for file deletions for path in target: if path not in source: deleted.append(path) if not new and not changed and not deleted: if not args.quiet: print("No changes found.") input("\nPress ENTER to exit...") exit(0) # Print overview if not args.quiet: print("Found the following changes:\n") if new: print("Added:") for i in new: print(f" + {i}") print() if changed: print("Changed:") for i in changed: print(f" ~ {i}") print() if deleted: print("Deleted:") for i in deleted: print(f" - {i}") print() # Copy and update files if not args.dry_run: if new or changed: if args.confirm: copy_confirm = "y" else: copy_confirm = ( input("Write new and changed files? (y/N): ").strip().lower() ) if copy_confirm in ["ja", "j", "yes", "y"]: if not args.quiet: print("\nCopying files...") for path in new + changed: source_path = os.path.join(inpath, path) target_path = os.path.join(outpath, path) # Create new folders if needed os.makedirs(os.path.dirname(target_path), exist_ok=True) # Copy file try: shutil.copy(source_path, target_path) if not args.quiet: print(f"Successfully copied {path}") except Exception as error: print(f"Failed to copy {path} with error {error}") if not args.confirm: if not input("Continue? (y/N): ") in [ "ja", "j", "yes", "y", ]: exit(1) elif not args.quiet: print("\nSkipping changes...") # Delete files if not args.dry_run: if deleted: alert = " ALERT: Some files only exist at the destination. " if not args.quiet: print(f"{len(alert) * '*'}\n{alert}\n{len(alert) * '*'}") if args.delete: delete_confirm = "y" else: delete_confirm = ( input("Delete files on the target? (y/N): ").strip().lower() ) if delete_confirm in ["ja", "j", "yes", "y"]: if not args.quiet: print("\nDeleting files...") for path in deleted: target_path = os.path.join(outpath, path) try: os.remove(target_path) if not args.quiet: print(f"Successfully deleted {path}") except Exception as error: print(f"Failed to delete {path} with error {error}") if not args.confirm: if not input("Continue? (y/N): ") in [ "ja", "j", "yes", "y", ]: exit(1) elif not args.quiet: print("\nSkipping deletion...") if not args.quiet: print("\nFolder sync finished!") input("\nPress ENTER to exit...") if __name__ == "__main__": main()