diff options
Diffstat (limited to 'app/backends/audiocpp/models.py')
| -rw-r--r-- | app/backends/audiocpp/models.py | 90 |
1 files changed, 35 insertions, 55 deletions
diff --git a/app/backends/audiocpp/models.py b/app/backends/audiocpp/models.py index 16c61e9..d150f62 100644 --- a/app/backends/audiocpp/models.py +++ b/app/backends/audiocpp/models.py @@ -16,10 +16,6 @@ from . import catalog as _catalog # runner kills it and reports exit 124 (see run_console_subprocess). DOWNLOAD_STALL_TIMEOUT = 300 -# Spec sanitizing lives with the catalog (the wizard's catalog view applies -# the same in-memory repair; the download path materializes it through its -# sanitized specs copy). The alias keeps this module's historical name. -_sanitize_model_spec = _catalog.sanitize_model_spec def _installed_display_names(audiocpp_dir: Path, model_entries: Optional[List[dict]], @@ -255,52 +251,21 @@ def _manager_supports_progress(manager: Path) -> bool: and _manager_supports_flag(manager, "--cancel-file")) -def _sanitize_model_spec(spec: dict) -> bool: - """Repair dot ``strip_prefix`` packages in SPEC, in place. - - A package's ``strip_prefix`` is stripped from the front of every file - path to get the local layout, so it only works when every file is - listed under that prefix (``<prefix>/<file>``). A dot prefix ("." or - "./") is meant for files written ``./<file>``; when the package instead - lists repo-root files bare (``model.gguf``), the manager rejects the - whole package ("file path does not start with strip_prefix '.': ...") - and nothing can be downloaded. Root-level files need no prefix at all - (upstream specs like minimax_music3.json store ""), so dropping the dot - prefix is the safe repair. Prefixes naming a real directory are left - alone — the correct remote paths cannot be guessed. Returns True when - SPEC changed. - """ - changed = False - for package in spec.get("packages") or []: - if not isinstance(package, dict): - continue - prefix = str(package.get("strip_prefix") or "").rstrip("/") - if prefix not in (".", ".."): - continue - files = package.get("files") - if not isinstance(files, list) or not files: - continue - if all(isinstance(item, str) and item.startswith(prefix + "/") - for item in files): - continue - package["strip_prefix"] = "" - changed = True - return changed - - def _prepare_specs_dir(audiocpp_dir: Path) -> Optional[Path]: - """Return a temp specs dir with dot ``strip_prefix`` entries repaired. + """Return a temp specs dir with broken ``strip_prefix`` entries repaired. audio.cpp's model manager accepts ``--specs-dir``, so a checkout whose specs carry a broken ``strip_prefix`` can be installed from a sanitized copy without modifying the checkout. Every ``model_specs/*.json`` is - copied; the ones needing a repair are rewritten via - ``_sanitize_model_spec`` (specs that fail to parse are copied verbatim - so the manager reports them exactly as it would upstream). Returns None - when no spec needed a repair (or the specs directory is missing or - unreadable) — the caller then runs against the checkout's own specs. - The caller owns the returned directory and removes it when the installs - are done. + copied; the ones needing a repair are rewritten via the catalog's + ``sanitize_model_spec`` (both upstream spec bug classes: dot prefixes + and missing prefixes on $gguf-rooted single-GGUF packages — glm_tts/ + outetts shipped like that). Specs that fail to parse are copied + verbatim so the manager reports them exactly as it would upstream. + Returns None when no spec needed a repair (or the specs directory is + missing or unreadable) — the caller then runs against the checkout's + own specs. The caller owns the returned directory and removes it when + the installs are done. """ specs_dir = audiocpp_dir / "model_specs" try: @@ -321,7 +286,7 @@ def _prepare_specs_dir(audiocpp_dir: Path) -> Optional[Path]: except ValueError: payloads.append((spec_path.name, text)) continue - if isinstance(spec, dict) and _sanitize_model_spec(spec): + if isinstance(spec, dict) and _catalog.sanitize_model_spec(spec): sanitized = True text = json.dumps(spec, indent=2, ensure_ascii=False) + "\n" payloads.append((spec_path.name, text)) @@ -707,12 +672,15 @@ def delete_model_files(server_json: Path, entries: List[dict]) -> int: Each entry's ``rel`` is resolved exactly like the server resolves it (relative against ``server_json``'s directory; absolute paths honored), - then removed as a directory tree or a single file. Missing entries are - ignored. Returns the number of paths removed. Used by the wizard's + then removed as a directory tree or a single file. Paths that escape + the checkout (``..`` segments, or an absolute path outside the + checkout's tree) are refused rather than deleted — the value comes + from a user-editable server.json. Missing entries are ignored. + Returns the number of paths removed. Used by the wizard's "Delete unused models?" step — the regenerated server.json already only lists the kept models, so no entry cleanup is needed here. """ - base = server_json.parent + base = server_json.parent.resolve() removed = 0 for item in entries: rel = item.get("rel") @@ -720,16 +688,28 @@ def delete_model_files(server_json: Path, entries: List[dict]) -> int: continue path = Path(rel) if Path(rel).is_absolute() else base / rel try: - if not path.exists(): + resolved = path.resolve() + except OSError: + continue + if base not in resolved.parents and resolved != base: + print(f"[WARNING] Refusing to remove {path}: outside the " + "audio.cpp checkout") + continue + if resolved == base: + print(f"[WARNING] Refusing to remove {path}: it is the " + "checkout directory itself") + continue + try: + if not resolved.exists(): continue - if path.is_dir(): - shutil.rmtree(path, ignore_errors=True) + if resolved.is_dir(): + shutil.rmtree(resolved, ignore_errors=True) else: - path.unlink() + resolved.unlink() except OSError as exc: - print(f"[WARNING] Could not remove {path}: {exc}") + print(f"[WARNING] Could not remove {resolved}: {exc}") continue - print(f"[OK] Removed unused model {path}") + print(f"[OK] Removed unused model {resolved}") removed += 1 return removed |
