diff --git a/DataPuller.py b/DataPuller.py old mode 100755 new mode 100644 index 1a08567..ab9e887 --- a/DataPuller.py +++ b/DataPuller.py @@ -1,157 +1,175 @@ import os +import argparse import shutil +import filecmp -# --- Hier die Pfade definieren --- -QUELL_ORDNER = input("Quellordner: ") -ZIEL_ORDNER = input("Zielordner: ") -# --------------------------------- +# 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("--dryrun", action="store_true") -def hole_datei_infos(verzeichnis): - """Sammelt alle Dateien mit ihrer Größe und dem Änderungsdatum.""" - datei_infos = {} - if not os.path.exists(verzeichnis): - return datei_infos +args = parser.parse_args() - for ordner_pfad, _, dateien in os.walk(verzeichnis): - for datei in dateien: - voller_pfad = os.path.join(ordner_pfad, datei) - # Relativen Pfad ermitteln (z. B. "unterordner\dokument.docx") - relativer_pfad = os.path.relpath(voller_pfad, verzeichnis) +# 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) try: - stat = os.stat(voller_pfad) - datei_infos[relativer_pfad] = { - "groesse": stat.st_size, - "aenderungsdatum": stat.st_mtime, + stat = os.stat(full_path) + file_info[rel_path] = { + "size": stat.st_size, + "modified": stat.st_mtime, } except Exception: pass - return datei_infos - + return file_info def main(): - print("=" * 50) - print(" USB-STICK SYNCHRONISATION") - print("=" * 50) + startstring = " FOLDER-SYNC 1.0 " + if not args.quiet: + print(len(startstring)*"=" + "\n" + startstring + "\n" + len(startstring)*"=") - # Prüfen, ob Laufwerke da sind - if not os.path.exists(QUELL_ORDNER): - print(f"Fehler: Das Schul-Laufwerk ({QUELL_ORDNER}) ist nicht erreichbar!") - input("\nDrücke Enter zum Beenden...") - return + # 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) - ziel_laufwerk = os.path.splitdrive(ZIEL_ORDNER)[0] + "\\" - if not os.path.exists(ziel_laufwerk): - print( - f"Fehler: Laufwerk {ziel_laufwerk} nicht gefunden. Ist der USB-Stick eingesteckt?" - ) - input("\nDrücke Enter zum Beenden...") - return + 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("Analysiere Dateien. Bitte warten...\n") + print("Analyzing files. Please wait...\n") - quelle = hole_datei_infos(QUELL_ORDNER) - ziel = hole_datei_infos(ZIEL_ORDNER) + source = get_files(inpath) + target = get_files(outpath) - neu = [] - geaendert = [] - geloescht = [] + new = [] + changed = [] + deleted = [] - # Prüfen, was auf P: neu oder geändert wurde - for pfad, info in quelle.items(): - if pfad not in ziel: - neu.append(pfad) - else: - # Wir prüfen die Dateigröße und das Datum (2 Sekunden Toleranz für USB-Sticks) - if ( - info["groesse"] != ziel[pfad]["groesse"] - or abs(info["aenderungsdatum"] - ziel[pfad]["aenderungsdatum"]) > 2 - ): - geaendert.append(pfad) + # Check for file additions and changes + for path, info in source.items(): + 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) - # Prüfen, was von P: gelöscht wurde und auf dem Stick auch weg kann - for pfad in ziel: - if pfad not in quelle: - geloescht.append(pfad) + # Check for file deletions + for path in target: + if path not in source: + deleted.append(path) - # Wenn alles aktuell ist - if not neu and not geaendert and not geloescht: - print( - "Alles ist bereits auf dem neuesten Stand! Keine Änderungen erforderlich." - ) - input("\nDrücke Enter zum Beenden...") - return + if not new and not changed and not deleted: + if not args.quiet: + print("No changes found.") + input("\nPress ENTER to exit...") + exit(0) - # Übersicht ausgeben - print("Folgende Änderungen wurden gefunden:\n") - if neu: - print("NEUE DATEIEN (werden auf den Stick kopiert):") - for d in neu: - print(f" + {d}") - print() - if geaendert: - print("GEÄNDERTE DATEIEN (Inhalt hat sich geändert, werden aktualisiert):") - for d in geaendert: - print(f" ~ {d}") - print() - if geloescht: - print("GELÖSCHTE DATEIEN (sind nicht mehr auf dem Schul-Laufwerk):") - for d in geloescht: - print(f" - {d}") - print() + # 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}") - # --- SCHRITT 1: KOPIEREN & AKTUALISIEREN --- - if neu or geaendert: - antwort_kopieren = ( - input("Sollen die NEUEN und GEÄNDERTEN Dateien kopiert werden? (ja/nein): ") - .strip() - .lower() - ) - if antwort_kopieren in ["ja", "j", "yes", "y"]: - print("\nKopiere Dateien...") - for pfad in neu + geaendert: - quell_pfad = os.path.join(QUELL_ORDNER, pfad) - ziel_pfad = os.path.join(ZIEL_ORDNER, pfad) + # Copy and update files + if not args.dryrun: + 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) - # Ordnerstruktur auf dem Stick erstellen, falls ein neuer Unterordner da ist - os.makedirs(os.path.dirname(ziel_pfad), exist_ok=True) + # Create new folders if needed + os.makedirs(os.path.dirname(target_path), exist_ok=True) - # Datei kopieren - shutil.copy2(quell_pfad, ziel_pfad) - print(f"Erfolgreich kopiert: {pfad}") - else: - print("\nKopieren übersprungen.") + # 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...") - # --- SCHRITT 2: LÖSCHEN --- - if geloescht: - print("\n" + "!" * 55) - print(" ACHTUNG: Einige Dateien existieren nur auf dem Stick.") - print("!" * 55) - antwort_loeschen = ( - input( - "Sollen die GELÖSCHTEN Dateien nun auch vom Stick ENTFERNT werden? (ja/nein): " - ) - .strip() - .lower() - ) + # Delete files + if not args.dryrun: + 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 antwort_loeschen in ["ja", "j", "yes", "y"]: - print("\nLösche Dateien...") - for pfad in geloescht: - ziel_pfad = os.path.join(ZIEL_ORDNER, pfad) - try: - os.remove(ziel_pfad) - print(f"Erfolgreich gelöscht: {pfad}") - except Exception as e: - print(f"Fehler beim Löschen von {pfad}: {e}") - else: - print( - "\nLöschen übersprungen. Die Dateien bleiben als Backup auf dem Stick erhalten." - ) + if not args.quiet: + print("\nFolder sync finished!") + input("\nPress ENTER to exit...") - print("\nSynchronisation abgeschlossen!") - input("\nDrücke Enter zum Beenden...") if __name__ == "__main__": - main() + main() \ No newline at end of file diff --git a/DataPullerLinux.py b/DataPullerLinux.py deleted file mode 100644 index ab9e887..0000000 --- a/DataPullerLinux.py +++ /dev/null @@ -1,175 +0,0 @@ -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') -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("--dryrun", 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) - try: - stat = os.stat(full_path) - file_info[rel_path] = { - "size": stat.st_size, - "modified": stat.st_mtime, - } - except Exception: - pass - 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, info in source.items(): - 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}") - if deleted: - print("Deleted:") - for i in deleted: - print(f" - {i}") - - # Copy and update files - if not args.dryrun: - 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.dryrun: - 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() \ No newline at end of file