Package the models on every platform, not just the phone

The Android bundling landed the weights under that platform's asset directory,
which was the wrong home the moment a second packager wanted them. `makepkg -si`
produced a desktop install with no model at all — the same "no face model is
installed" the phone used to show, for the same reason: nothing put the files
anywhere the app looks.

So `models/face/` at the root is the one copy, and both packagers read it:
assemble-apk.sh bundles it as APK assets, and the PKGBUILD installs it to
/usr/share/darkroom/models. Both refuse an LFS pointer rather than shipping a
130-byte file that fails inside the graph loader on a user's machine.

`face_models` now searches three places, most specific first: the account's own
directory, the shared user directory, then $XDG_DATA_DIRS. So a packaged pair is
found automatically and a pair the user placed by hand still outranks it — which
is what keeps a deliberate choice of weights from being overridden by an
upgrade.

$XDG_DATA_DIRS rather than a hard-coded /usr/share: that is the variable a
distribution, a prefix install or a Nix-style store already sets to say where
its data went, and its documented default is exactly the two paths that would
otherwise have been hard-coded. Empty on Android, which has no such directories
— there the APK's copy is unpacked into the shared user directory instead,
because an asset inside a package is not a path anything can read from.

Verified: the APK still carries both models at assets/models/, the PKGBUILD
parses and installs from the new path, 467 tests pass.

Includes the pkgver 0.6.0 → 0.7.0 bump that was already sitting uncommitted in
the working tree.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-27 18:05:23 +02:00
co-authored by Claude Opus 5
parent 725f7bf77f
commit 2d95807542
9 changed files with 101 additions and 45 deletions
+38 -5
View File
@@ -2934,17 +2934,50 @@ pub fn shared_face_models_dir() -> PathBuf {
/// `tools/fix-face-model-shapes.sh` has to run over the originals first,
/// because tract cannot parse either graph with a dynamic input.
///
/// The per-account directory wins over the shared one so a library can be
/// pinned to its own weights — a re-index after a model swap is a `model_id`
/// change, and someone mid-migration needs the old pair to stay put for one
/// account without holding back the other.
/// Searched in three places, most specific first:
///
/// 1. **The account's own directory.** A library can be pinned to its own
/// weights — a model swap is a `model_id` change and a re-index, and someone
/// mid-migration needs one account's pair to stay put without holding the
/// other back.
/// 2. **The shared user directory.** Where a user drops a pair by hand, and
/// where the Android entry point unpacks the copy the APK carries.
/// 3. **The system directories.** Where a package installs them — the Arch
/// package puts the pair in `/usr/share/darkroom/models`. Last, so anything
/// the user placed themselves outranks what the package shipped.
pub fn face_models(server: &str, user_id: &str) -> Option<(PathBuf, PathBuf)> {
fn pair(dir: PathBuf) -> Option<(PathBuf, PathBuf)> {
let detector = dir.join("scrfd_500m_640.onnx");
let embedder = dir.join("arcface_mbf_b1.onnx");
(detector.is_file() && embedder.is_file()).then_some((detector, embedder))
}
pair(face_models_dir(server, user_id)).or_else(|| pair(shared_face_models_dir()))
pair(face_models_dir(server, user_id))
.or_else(|| pair(shared_face_models_dir()))
.or_else(|| system_face_models_dirs().into_iter().find_map(pair))
}
/// Where a *package* may have installed the models.
///
/// `$XDG_DATA_DIRS` rather than a hard-coded `/usr/share`, because that is the
/// variable a distribution, a prefix install, or a Nix-style store sets to say
/// where its data went, and the default it falls back to is exactly the pair of
/// paths that would otherwise have been hard-coded.
///
/// Empty on Android, which has no such directories: there the APK's copy is
/// unpacked into the shared user directory instead, because an asset inside a
/// package is not a path anything can read from (ARCH §6.9).
fn system_face_models_dirs() -> Vec<PathBuf> {
if cfg!(target_os = "android") {
return Vec::new();
}
let dirs = std::env::var("XDG_DATA_DIRS")
.ok()
.filter(|v| !v.is_empty())
.unwrap_or_else(|| "/usr/local/share:/usr/share".into());
dirs.split(':')
.filter(|d| !d.is_empty())
.map(|d| PathBuf::from(d).join("darkroom").join("models"))
.collect()
}
/// One grid cell's data, read from the catalog.