aboutsummaryrefslogtreecommitdiff
path: root/app/backends/audiocpp/models.py
diff options
context:
space:
mode:
Diffstat (limited to 'app/backends/audiocpp/models.py')
-rw-r--r--app/backends/audiocpp/models.py90
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