Improvements to the up / down
This commit is contained in:
+150
-7
@@ -57,6 +57,17 @@ def _build_manifest(project_path: Path) -> list[str]:
|
|||||||
if (project_path / fixed).exists():
|
if (project_path / fixed).exists():
|
||||||
files.add(fixed)
|
files.add(fixed)
|
||||||
|
|
||||||
|
# Keynote presentation(s)
|
||||||
|
for key_file in project_path.glob("*.key"):
|
||||||
|
files.add(key_file.name)
|
||||||
|
|
||||||
|
# Rendered output (mp4 + srt — not low-res previews or transcripts)
|
||||||
|
out_dir = project_path / "out"
|
||||||
|
if out_dir.is_dir():
|
||||||
|
for f in out_dir.iterdir():
|
||||||
|
if f.is_file() and f.suffix in (".mp4", ".srt"):
|
||||||
|
files.add(str(f.relative_to(project_path)))
|
||||||
|
|
||||||
# Manuscript (may be at a non-standard path)
|
# Manuscript (may be at a non-standard path)
|
||||||
manuscript_rel = project.get("manuscript", "manuscript.txt")
|
manuscript_rel = project.get("manuscript", "manuscript.txt")
|
||||||
if (project_path / manuscript_rel).exists():
|
if (project_path / manuscript_rel).exists():
|
||||||
@@ -131,6 +142,90 @@ def _build_manifest(project_path: Path) -> list[str]:
|
|||||||
return sorted(files)
|
return sorted(files)
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Shared assets manifest
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def _find_shared_assets_root(project_path: Path) -> Optional[Path]:
|
||||||
|
"""Return the local shared_assets directory, or None if not found."""
|
||||||
|
candidate = project_path.parent / "shared_assets"
|
||||||
|
if candidate.is_dir():
|
||||||
|
return candidate
|
||||||
|
try:
|
||||||
|
from .cache import load_assets_config
|
||||||
|
assets = load_assets_config()
|
||||||
|
if assets:
|
||||||
|
candidate = assets / "shared_assets"
|
||||||
|
if candidate.is_dir():
|
||||||
|
return candidate
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _build_shared_manifest(project_path: Path) -> tuple:
|
||||||
|
"""Return (shared_assets_root, sorted list of root-relative paths) for shared files used by this project.
|
||||||
|
|
||||||
|
Layout conventions discovered from the actual filesystem:
|
||||||
|
- Shared videos: shared_assets/{source_file} (e.g. Logo6sec.mov, pexels/14923961.mp4)
|
||||||
|
- Shared audio: shared_assets/media/audio/{file}
|
||||||
|
"""
|
||||||
|
shared_root = _find_shared_assets_root(project_path)
|
||||||
|
if shared_root is None:
|
||||||
|
return None, []
|
||||||
|
|
||||||
|
files: set[str] = set()
|
||||||
|
|
||||||
|
# shared_assets/videos.json so the render pipeline can read metadata
|
||||||
|
if (shared_root / "videos.json").exists():
|
||||||
|
files.add("videos.json")
|
||||||
|
|
||||||
|
project: dict = {}
|
||||||
|
proj_json = project_path / "project.json"
|
||||||
|
if proj_json.exists():
|
||||||
|
try:
|
||||||
|
project = json.loads(proj_json.read_text(encoding="utf-8"))
|
||||||
|
except (json.JSONDecodeError, OSError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Shared video source files
|
||||||
|
videos_rel = project.get("videos", "media/videos/videos.json")
|
||||||
|
videos_json = project_path / videos_rel
|
||||||
|
if videos_json.exists():
|
||||||
|
try:
|
||||||
|
data = json.loads(videos_json.read_text(encoding="utf-8"))
|
||||||
|
for entry in data.values():
|
||||||
|
if not entry.get("is_shared"):
|
||||||
|
continue
|
||||||
|
src = entry.get("source_file")
|
||||||
|
if not src:
|
||||||
|
continue
|
||||||
|
# source_file is relative to shared_assets root
|
||||||
|
if (shared_root / src).exists():
|
||||||
|
files.add(src)
|
||||||
|
except (json.JSONDecodeError, OSError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Shared audio source files (live under media/audio/ within shared_assets)
|
||||||
|
audio_json = project_path / "media" / "audio" / "audio.json"
|
||||||
|
if audio_json.exists():
|
||||||
|
try:
|
||||||
|
data = json.loads(audio_json.read_text(encoding="utf-8"))
|
||||||
|
for entry in data.values():
|
||||||
|
if not entry.get("is_shared"):
|
||||||
|
continue
|
||||||
|
src = entry.get("file")
|
||||||
|
if not src:
|
||||||
|
continue
|
||||||
|
rel = f"media/audio/{src}"
|
||||||
|
if (shared_root / rel).exists():
|
||||||
|
files.add(rel)
|
||||||
|
except (json.JSONDecodeError, OSError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
return shared_root, sorted(files)
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Commit log helpers
|
# Commit log helpers
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -203,15 +298,22 @@ def cmd_up(project_path: Path, verbose: bool, dry_run: bool) -> int:
|
|||||||
for f in manifest:
|
for f in manifest:
|
||||||
print(f" {f}")
|
print(f" {f}")
|
||||||
|
|
||||||
|
# Build shared assets manifest
|
||||||
|
shared_root, shared_files = _build_shared_manifest(project_path)
|
||||||
|
remote_shared = f"{server['path']}/shared_assets"
|
||||||
|
|
||||||
if dry_run:
|
if dry_run:
|
||||||
print("\n[DRY RUN] Would push:")
|
print("\n[DRY RUN] Would push project files:")
|
||||||
for f in manifest:
|
for f in manifest:
|
||||||
print(f" {f}")
|
print(f" {f}")
|
||||||
|
if shared_files:
|
||||||
|
print(f"\n[DRY RUN] Would push shared assets ({len(shared_files)} files):")
|
||||||
|
for f in shared_files:
|
||||||
|
print(f" {f}")
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
# Ensure remote dir exists
|
# Pass 1: project files
|
||||||
subprocess.run([*ssh_cmd, f"mkdir -p {remote_project}"], check=True)
|
subprocess.run([*ssh_cmd, f"mkdir -p {remote_project}"], check=True)
|
||||||
|
|
||||||
rsync_cmd = [
|
rsync_cmd = [
|
||||||
"rsync", "-av", "--progress",
|
"rsync", "-av", "--progress",
|
||||||
"--files-from=-",
|
"--files-from=-",
|
||||||
@@ -221,10 +323,26 @@ def cmd_up(project_path: Path, verbose: bool, dry_run: bool) -> int:
|
|||||||
]
|
]
|
||||||
result = subprocess.run(rsync_cmd, input="\n".join(manifest), text=True)
|
result = subprocess.run(rsync_cmd, input="\n".join(manifest), text=True)
|
||||||
if result.returncode != 0:
|
if result.returncode != 0:
|
||||||
print(f"Error: rsync failed (code {result.returncode})")
|
print(f"Error: rsync failed for project files (code {result.returncode})")
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
print(f"\nDone. Pushed {total} files.")
|
# Pass 2: shared assets referenced by this project
|
||||||
|
if shared_files and shared_root:
|
||||||
|
print(f"\nPushing shared assets ({len(shared_files)} files)...")
|
||||||
|
subprocess.run([*ssh_cmd, f"mkdir -p {remote_shared}"], check=True)
|
||||||
|
rsync_shared = [
|
||||||
|
"rsync", "-av", "--progress",
|
||||||
|
"--files-from=-",
|
||||||
|
"-e", f"ssh -p {server['port']}",
|
||||||
|
f"{shared_root}/",
|
||||||
|
f"{server['user']}@{server['host']}:{remote_shared}/",
|
||||||
|
]
|
||||||
|
result = subprocess.run(rsync_shared, input="\n".join(shared_files), text=True)
|
||||||
|
if result.returncode != 0:
|
||||||
|
print(f"Error: rsync failed for shared assets (code {result.returncode})")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
print(f"\nDone. Pushed {total} project files, {len(shared_files)} shared assets.")
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
@@ -267,18 +385,43 @@ def cmd_down(project_path: Path, verbose: bool, dry_run: bool) -> int:
|
|||||||
f"{project_path}/",
|
f"{project_path}/",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
remote_shared = f"{server['path']}/shared_assets"
|
||||||
|
local_shared = project_path.parent / "shared_assets"
|
||||||
|
rsync_shared_cmd = [
|
||||||
|
"rsync", "-av", "--progress",
|
||||||
|
"-e", f"ssh -p {server['port']}",
|
||||||
|
f"{server['user']}@{server['host']}:{remote_shared}/",
|
||||||
|
f"{local_shared}/",
|
||||||
|
]
|
||||||
|
|
||||||
if dry_run:
|
if dry_run:
|
||||||
rsync_cmd.insert(1, "--dry-run")
|
rsync_cmd.insert(1, "--dry-run")
|
||||||
print("\n[DRY RUN] Would execute:")
|
print("\n[DRY RUN] Would pull project:")
|
||||||
print(f" {' '.join(rsync_cmd)}")
|
print(f" {' '.join(rsync_cmd)}")
|
||||||
|
print("\n[DRY RUN] Would pull shared assets:")
|
||||||
|
print(f" {' '.join(rsync_shared_cmd)}")
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
if verbose:
|
if verbose:
|
||||||
print(f" {' '.join(rsync_cmd)}")
|
print(f" {' '.join(rsync_cmd)}")
|
||||||
|
|
||||||
|
# Pass 1: project files
|
||||||
result = subprocess.run(rsync_cmd)
|
result = subprocess.run(rsync_cmd)
|
||||||
if result.returncode != 0:
|
if result.returncode != 0:
|
||||||
print(f"Error: rsync failed (code {result.returncode})")
|
print(f"Error: rsync failed for project files (code {result.returncode})")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
# Pass 2: shared assets (only if server has any)
|
||||||
|
check = subprocess.run(
|
||||||
|
[*ssh_cmd, f"test -d {remote_shared} && echo ok"],
|
||||||
|
capture_output=True, text=True,
|
||||||
|
)
|
||||||
|
if check.stdout.strip() == "ok":
|
||||||
|
print("\nPulling shared assets...")
|
||||||
|
local_shared.mkdir(parents=True, exist_ok=True)
|
||||||
|
result = subprocess.run(rsync_shared_cmd)
|
||||||
|
if result.returncode != 0:
|
||||||
|
print(f"Error: rsync failed for shared assets (code {result.returncode})")
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
print("\nDone.")
|
print("\nDone.")
|
||||||
|
|||||||
Reference in New Issue
Block a user