104 lines
4.3 KiB
Python
104 lines
4.3 KiB
Python
from __future__ import annotations
|
|
from pathlib import Path
|
|
from datetime import datetime, UTC
|
|
import json, shutil
|
|
from .review_schema import WorkspaceMeta, WorkspaceRegistry
|
|
|
|
def utc_now() -> str:
|
|
return datetime.now(UTC).isoformat()
|
|
|
|
class WorkspaceManager:
|
|
def __init__(self, registry_path: str | Path, default_workspace_root: str | Path) -> None:
|
|
self.registry_path = Path(registry_path)
|
|
self.default_workspace_root = Path(default_workspace_root)
|
|
self.default_workspace_root.mkdir(parents=True, exist_ok=True)
|
|
|
|
def load_registry(self) -> WorkspaceRegistry:
|
|
if self.registry_path.exists():
|
|
return WorkspaceRegistry.model_validate(json.loads(self.registry_path.read_text(encoding="utf-8")))
|
|
return WorkspaceRegistry()
|
|
|
|
def save_registry(self, registry: WorkspaceRegistry) -> None:
|
|
self.registry_path.write_text(registry.model_dump_json(indent=2), encoding="utf-8")
|
|
|
|
def list_workspaces(self) -> WorkspaceRegistry:
|
|
return self.load_registry()
|
|
|
|
def create_workspace(self, workspace_id: str, title: str, notes: str = "") -> WorkspaceMeta:
|
|
registry = self.load_registry()
|
|
workspace_dir = self.default_workspace_root / workspace_id
|
|
workspace_dir.mkdir(parents=True, exist_ok=True)
|
|
draft_dir = workspace_dir / "draft_pack"
|
|
draft_dir.mkdir(parents=True, exist_ok=True)
|
|
if not (draft_dir / "pack.yaml").exists():
|
|
(draft_dir / "pack.yaml").write_text(
|
|
f"name: {workspace_id}\ndisplay_name: {title}\nversion: 0.1.0-draft\ndescription: Seed draft pack for workspace {workspace_id}\n",
|
|
encoding="utf-8"
|
|
)
|
|
if not (draft_dir / "concepts.yaml").exists():
|
|
(draft_dir / "concepts.yaml").write_text("concepts: []\n", encoding="utf-8")
|
|
|
|
meta = WorkspaceMeta(
|
|
workspace_id=workspace_id,
|
|
title=title,
|
|
path=str(workspace_dir),
|
|
created_at=utc_now(),
|
|
last_opened_at=utc_now(),
|
|
notes=notes,
|
|
)
|
|
registry.workspaces = [w for w in registry.workspaces if w.workspace_id != workspace_id] + [meta]
|
|
registry.recent_workspace_ids = [workspace_id] + [w for w in registry.recent_workspace_ids if w != workspace_id]
|
|
self.save_registry(registry)
|
|
return meta
|
|
|
|
def touch_recent(self, workspace_id: str) -> WorkspaceMeta | None:
|
|
registry = self.load_registry()
|
|
target = None
|
|
for ws in registry.workspaces:
|
|
if ws.workspace_id == workspace_id:
|
|
ws.last_opened_at = utc_now()
|
|
target = ws
|
|
break
|
|
if target is not None:
|
|
registry.recent_workspace_ids = [workspace_id] + [w for w in registry.recent_workspace_ids if w != workspace_id]
|
|
self.save_registry(registry)
|
|
return target
|
|
|
|
def get_workspace(self, workspace_id: str) -> WorkspaceMeta | None:
|
|
registry = self.load_registry()
|
|
for ws in registry.workspaces:
|
|
if ws.workspace_id == workspace_id:
|
|
return ws
|
|
return None
|
|
|
|
def import_draft_pack(self, source_dir: str | Path, workspace_id: str, title: str | None = None, notes: str = "") -> WorkspaceMeta:
|
|
source_dir = Path(source_dir)
|
|
if not source_dir.exists():
|
|
raise FileNotFoundError(f"Draft pack source does not exist: {source_dir}")
|
|
|
|
meta = self.get_workspace(workspace_id)
|
|
if meta is None:
|
|
meta = self.create_workspace(workspace_id, title or workspace_id, notes=notes)
|
|
else:
|
|
self.touch_recent(workspace_id)
|
|
|
|
workspace_dir = Path(meta.path)
|
|
target_draft = workspace_dir / "draft_pack"
|
|
if target_draft.exists():
|
|
shutil.rmtree(target_draft)
|
|
shutil.copytree(source_dir, target_draft)
|
|
|
|
registry = self.load_registry()
|
|
for ws in registry.workspaces:
|
|
if ws.workspace_id == workspace_id:
|
|
ws.last_opened_at = utc_now()
|
|
if title:
|
|
ws.title = title
|
|
if notes:
|
|
ws.notes = notes
|
|
meta = ws
|
|
break
|
|
registry.recent_workspace_ids = [workspace_id] + [w for w in registry.recent_workspace_ids if w != workspace_id]
|
|
self.save_registry(registry)
|
|
return meta
|