DataPuller/DataPuller.py

214 lines
6.4 KiB
Python
Executable file

#! /usr/bin/python3
import os
import argparse
import shutil
import filecmp
# Argument Parsing
parser = argparse.ArgumentParser(
prog="DataPuller",
description="Check differences between folders and update files if needed",
)
parser.add_argument("-i", "--input", help="Path to input folder")
parser.add_argument("-o", "--output", help="Path to output folder")
parser.add_argument(
"-q",
"--quiet",
action="store_true",
help="Suppress non-essential commandline output",
)
parser.add_argument(
"-y", "--confirm", action="store_true", help="Auto-confirm user queries"
)
parser.add_argument(
"-d", "--delete", action="store_true", help="Auto-confirm file deletion"
)
parser.add_argument(
"--dry-run",
action="store_true",
help="List affected files without copying or deleting",
)
parser.add_argument(
"-s",
"--shallow-diff",
action="store_true",
help="Use filecmp.cmp() shallow mode for file diff (faster, but might miss changes or generate false positives)",
)
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 0.1.3 "
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),
shallow=args.shallow_diff,
):
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}")
if deleted:
print("Deleted:")
for i in deleted:
print(f" - {i}")
# 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:
if not args.dry_run:
print("\nFolder sync finished!")
else:
print("\nDry run complete!")
input("\nPress ENTER to exit...")
if __name__ == "__main__":
main()