From 26d027a44eca90f2456f435bc7f7a894a3e9a460 Mon Sep 17 00:00:00 2001 From: jenstandstad Date: Mon, 4 May 2026 20:31:37 +0200 Subject: [PATCH] Adding cache so we can sync via server --- gnommo/cache.py | 33 +++++++++++++++++++ gnommo/cli.py | 85 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 118 insertions(+) diff --git a/gnommo/cache.py b/gnommo/cache.py index 0a3e3b6..36347bd 100644 --- a/gnommo/cache.py +++ b/gnommo/cache.py @@ -85,6 +85,39 @@ def resolve_with_cache( return local_path, False +def load_server_config() -> Optional[dict]: + """Load server rsync config from ~/.gnommo.conf. + + Expected config: + [server] + host = 76.13.144.52 + user = root + path = /gnommo/project + + Returns: + Dict with keys host, user, path (and optionally port), or None. + """ + config_path = Path.home() / ".gnommo.conf" + if not config_path.exists(): + return None + + config = configparser.ConfigParser() + config.read(config_path) + + if not config.has_section("server"): + return None + + host = config.get("server", "host", fallback=None) + user = config.get("server", "user", fallback="root") + path = config.get("server", "path", fallback="/gnommo/project") + port = config.get("server", "port", fallback="22") + + if not host: + return None + + return {"host": host, "user": user, "path": path, "port": port} + + def is_cache_configured() -> bool: """Check if cache is configured (for status messages).""" return load_cache_config() is not None diff --git a/gnommo/cli.py b/gnommo/cli.py index 41bd8a1..c40de8e 100644 --- a/gnommo/cli.py +++ b/gnommo/cli.py @@ -96,6 +96,7 @@ Examples: "description", "archive", "load", + "sync", "extract-audio", "master", "push", @@ -197,6 +198,11 @@ Examples: action="store_true", help="For transcode: compress _processed.mov files (with alpha) using HEVC+alpha instead of narration files", ) + parser.add_argument( + "--down", + action="store_true", + help="For sync: download from server to local (default is upload)", + ) parser.add_argument( "--alpha-quality", type=float, @@ -272,6 +278,8 @@ Examples: return cmd_archive(project_path, args.verbose, args.dry_run) elif action == "load": return cmd_load(project_path, args.verbose, args.dry_run) + elif action == "sync": + return cmd_sync(project_path, args.verbose, args.dry_run, args.down) elif action == "extract-audio": return cmd_extract_audio( project_path, args.verbose, args.segment, args.channel, args.combined @@ -3086,6 +3094,83 @@ def cmd_load(project_path: Path, verbose: bool, dry_run: bool) -> int: return 0 +def cmd_sync(project_path: Path, verbose: bool, dry_run: bool, download: bool) -> int: + """Sync project files to/from the remote server via rsync over SSH.""" + from .cache import load_server_config + + direction = "Downloading from" if download else "Uploading to" + print(f"{direction} server: {project_path.name}") + + server = load_server_config() + if server is None: + print("Error: Server not configured. Add to ~/.gnommo.conf:") + print(" [server]") + print(" host = 76.13.144.52") + print(" user = root") + print(" path = /gnommo/project") + return 1 + + remote = f"{server['user']}@{server['host']}:{server['path']}/{project_path.name}/" + local = f"{project_path}/" + + if download: + src, dest = remote, local + else: + src, dest = local, remote + + print(f" Source: {src}") + print(f" Destination: {dest}") + + # Ensure destination directory exists + if not dry_run: + if download: + project_path.mkdir(parents=True, exist_ok=True) + else: + remote_dir = f"{server['path']}/{project_path.name}" + ssh_cmd = [ + "ssh", "-p", server["port"], + f"{server['user']}@{server['host']}", + f"mkdir -p {remote_dir}", + ] + if verbose: + print(f" Creating remote dir: {remote_dir}") + result = subprocess.run(ssh_cmd) + if result.returncode != 0: + print(f"Error: could not create remote directory {remote_dir}") + return 1 + + rsync_cmd = [ + "rsync", + "-av", + "--progress", + "-e", f"ssh -p {server['port']}", + "--exclude=*.py", + "--exclude=__pycache__", + "--exclude=.git", + "--exclude=.DS_Store", + src, + dest, + ] + + if dry_run: + rsync_cmd.insert(1, "--dry-run") + print("\n [DRY RUN] Would execute:") + print(f" {' '.join(rsync_cmd)}") + else: + print("\n Syncing files...") + + if verbose: + print(f" Command: {' '.join(rsync_cmd)}") + + result = subprocess.run(rsync_cmd) + if result.returncode != 0: + print(f"Error: rsync failed with code {result.returncode}") + return 1 + + print("\nDone.") + return 0 + + # ============================================================================= # Extract Audio Command # =============================================================================