176 lines
6.1 KiB
Python
176 lines
6.1 KiB
Python
"""Migrate repo-local runtime data to configured external roots."""
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import asyncio
|
|
import shutil
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
|
|
PROJECT_ROOT = Path(__file__).resolve().parents[1]
|
|
BACKEND_DIR = PROJECT_ROOT / "backend"
|
|
LEGACY_RUNTIME = BACKEND_DIR / "runtime"
|
|
|
|
if str(PROJECT_ROOT) not in sys.path:
|
|
sys.path.insert(0, str(PROJECT_ROOT))
|
|
|
|
from backend.app.config import settings # noqa: E402
|
|
from backend.app import database # noqa: E402
|
|
from backend.app.services.sbas_insar_catalog_service import sbas_insar_catalog_service # noqa: E402
|
|
from backend.app.services.sbas_insar_production_service import sbas_insar_production_service # noqa: E402
|
|
|
|
|
|
def _norm(path: Path | str) -> Path:
|
|
return Path(path).expanduser().resolve()
|
|
|
|
|
|
def _is_under(path: Path, parent: Path) -> bool:
|
|
try:
|
|
path.relative_to(parent)
|
|
return True
|
|
except ValueError:
|
|
return False
|
|
|
|
|
|
def _measure(path: Path) -> tuple[int, int]:
|
|
if not path.exists():
|
|
return 0, 0
|
|
if path.is_file():
|
|
return path.stat().st_size, 1
|
|
size = 0
|
|
files = 0
|
|
for item in path.rglob("*"):
|
|
if item.is_file():
|
|
try:
|
|
size += item.stat().st_size
|
|
files += 1
|
|
except OSError:
|
|
continue
|
|
return size, files
|
|
|
|
|
|
def _gb(size: int) -> str:
|
|
return f"{size / (1024 ** 3):.3f} GB"
|
|
|
|
|
|
def _copy_tree(source: Path, target: Path, *, delete_source: bool) -> dict[str, object]:
|
|
if not source.exists():
|
|
return {"source": str(source), "target": str(target), "exists": False, "copied": False}
|
|
if _norm(target) == _norm(source):
|
|
return {"source": str(source), "target": str(target), "exists": True, "copied": False, "reason": "same_path"}
|
|
if _is_under(_norm(target), _norm(source)):
|
|
raise ValueError(f"refusing to copy {source} into itself: {target}")
|
|
target.parent.mkdir(parents=True, exist_ok=True)
|
|
if target.exists():
|
|
raise FileExistsError(f"target already exists: {target}")
|
|
shutil.copytree(source, target)
|
|
size, files = _measure(target)
|
|
if delete_source:
|
|
shutil.rmtree(source)
|
|
return {
|
|
"source": str(source),
|
|
"target": str(target),
|
|
"exists": True,
|
|
"copied": True,
|
|
"deleted_source": bool(delete_source),
|
|
"size": _gb(size),
|
|
"files": files,
|
|
}
|
|
|
|
|
|
def migrate_pyint_dem(*, apply: bool, delete_source: bool) -> dict[str, object]:
|
|
source = LEGACY_RUNTIME / "pyint_dem"
|
|
target = Path(settings.PYINT_DEM_ROOT)
|
|
size, files = _measure(source)
|
|
plan = {
|
|
"source": str(source),
|
|
"target": str(target),
|
|
"source_exists": source.exists(),
|
|
"source_size": _gb(size),
|
|
"source_files": files,
|
|
"apply": apply,
|
|
"delete_source": delete_source,
|
|
}
|
|
if not apply:
|
|
return plan
|
|
return {**plan, "result": _copy_tree(source, target, delete_source=delete_source)}
|
|
|
|
|
|
def sync_sbas_products(*, apply: bool) -> dict[str, object]:
|
|
legacy_root = LEGACY_RUNTIME / "sbas_insar_production" / "runs"
|
|
configured_work_root = Path(settings.GAMMA_SBAS_WORK_ROOT) / "runs"
|
|
roots = []
|
|
for root in (legacy_root, configured_work_root):
|
|
if root.is_dir() and root not in roots:
|
|
roots.append(root)
|
|
run_ids = sorted(
|
|
{
|
|
path.parent.name
|
|
for root in roots
|
|
for path in root.glob("*/run_manifest.json")
|
|
}
|
|
)
|
|
plan: dict[str, object] = {
|
|
"source_roots": [str(root) for root in roots],
|
|
"product_root": str(Path(settings.GAMMA_SBAS_PRODUCT_ROOT) / "runs"),
|
|
"run_ids": run_ids,
|
|
"apply": apply,
|
|
}
|
|
if not apply:
|
|
return plan
|
|
synced = []
|
|
original_root = sbas_insar_production_service.production_root
|
|
try:
|
|
for run_id in run_ids:
|
|
selected_root = next((root for root in roots if (root / run_id / "run_manifest.json").is_file()), None)
|
|
if selected_root is None:
|
|
continue
|
|
sbas_insar_production_service.production_root = selected_root.parent
|
|
synced.append(sbas_insar_production_service.sync_product_package(run_id))
|
|
finally:
|
|
sbas_insar_production_service.production_root = original_root
|
|
plan["synced"] = synced
|
|
return plan
|
|
|
|
|
|
async def rebuild_sbas_catalog() -> dict[str, object]:
|
|
if database.AsyncSessionLocal is None:
|
|
database.init_db(settings.DATABASE_URL)
|
|
if database.AsyncSessionLocal is None:
|
|
raise RuntimeError("Database session factory is not initialized")
|
|
async with database.AsyncSessionLocal() as db:
|
|
return await sbas_insar_catalog_service.rebuild_catalog(db, full_rebuild=True)
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("--apply", action="store_true", help="perform the migration; default only prints the plan")
|
|
parser.add_argument("--move-pyint-dem", action="store_true", help="copy legacy pyint_dem to PYINT_DEM_ROOT")
|
|
parser.add_argument("--delete-legacy-pyint-dem", action="store_true", help="delete legacy pyint_dem after copy")
|
|
parser.add_argument("--sync-sbas-products", action="store_true", help="sync lightweight SBAS products into GAMMA_SBAS_PRODUCT_ROOT")
|
|
parser.add_argument("--rebuild-sbas-catalog", action="store_true", help="rebuild SBAS result catalog after syncing products")
|
|
args = parser.parse_args()
|
|
|
|
actions: dict[str, object] = {}
|
|
if args.move_pyint_dem:
|
|
actions["pyint_dem"] = migrate_pyint_dem(apply=args.apply, delete_source=args.delete_legacy_pyint_dem)
|
|
if args.sync_sbas_products:
|
|
actions["sbas_products"] = sync_sbas_products(apply=args.apply)
|
|
if args.rebuild_sbas_catalog:
|
|
if args.apply:
|
|
actions["sbas_catalog"] = asyncio.run(rebuild_sbas_catalog())
|
|
else:
|
|
actions["sbas_catalog"] = {"apply": False, "action": "rebuild_sbas_catalog"}
|
|
if not actions:
|
|
actions["message"] = "no actions selected"
|
|
|
|
import json
|
|
|
|
print(json.dumps(actions, indent=2, ensure_ascii=False, default=str))
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|