diff --git a/crates/shell/src/app_storage/lifecycle.rs b/crates/shell/src/app_storage/lifecycle.rs index d7c8c06e..2c5db2e5 100644 --- a/crates/shell/src/app_storage/lifecycle.rs +++ b/crates/shell/src/app_storage/lifecycle.rs @@ -62,12 +62,12 @@ pub fn register_native_specs(storage: &Storage) { } /// A script app's `manifest.json` under App Hub's data root: an installed -/// app's `//bundle/`, or the newest unpacked build of a system app +/// app's bundle ([`crate::apps::installed_bundle`]), or the newest unpacked build of a system app /// (`/.system///`). `None` before either exists (a system /// app the Card runner has not unpacked yet). pub fn script_manifest(root: &Path, manifest_id: &str) -> Option { super::validate_app_id(manifest_id).ok()?; - let installed = root.join(manifest_id).join("bundle").join("manifest.json"); + let installed = crate::apps::installed_bundle(root, manifest_id).join("manifest.json"); let path = if installed.is_file() { installed } else { diff --git a/crates/shell/src/apps.rs b/crates/shell/src/apps.rs index 21957c97..ba597546 100644 --- a/crates/shell/src/apps.rs +++ b/crates/shell/src/apps.rs @@ -24,7 +24,7 @@ use makepad_app_module::AppModule; use std::collections::HashMap; -use std::path::Path; +use std::path::{Path, PathBuf}; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Hosting { @@ -317,7 +317,7 @@ fn read_script_agent_apps(root: &Path) -> Vec { } } for app in octosense_app_hub_app::installed_apps(&root) { - if let Some(a) = script_agent_app(&root.join(&app.id).join("bundle").join("manifest.json"), &app.id, &app.name) { + if let Some(a) = script_agent_app(&installed_bundle(&root, &app.id).join("manifest.json"), &app.id, &app.name) { out.push(a); } } @@ -661,6 +661,20 @@ impl AppRegistry { } } +/// An installed store app's bundle under App Hub's apps root. App Hub keeps +/// it outside the app's storage, in `/.bundles//bundle` +/// (`octosense_app_hub::installed_bundle_dir`); a device whose App Hub has +/// not moved it yet still has it in `//bundle`. The new place +/// wins when both exist. +pub(crate) fn installed_bundle(root: &Path, app_id: &str) -> PathBuf { + let current = root.join(".bundles").join(app_id).join("bundle"); + if current.is_dir() { + current + } else { + root.join(app_id).join("bundle") + } +} + #[cfg(test)] mod tests { /// The assistant pane's own process is a registry row (F10 starts it @@ -674,6 +688,23 @@ mod tests { assert!(crate::shell::launcher::apps().iter().all(|a| a.id != "apps.aichat" && a.id != "aichat")); } + /// App Hub keeps an installed bundle in `/.bundles//bundle` + /// (outside the app's storage); a device App Hub has not updated yet + /// still has it in `//bundle`. Either is found, the new one + /// first. + #[test] + fn an_installed_bundle_is_found_in_either_layout() { + let root = std::env::temp_dir().join(format!("shell-installed-bundle-{}", std::process::id())); + let _ = std::fs::remove_dir_all(&root); + let legacy = root.join("dev.example.app/bundle"); + std::fs::create_dir_all(&legacy).unwrap(); + assert_eq!(installed_bundle(&root, "dev.example.app"), legacy); + let current = root.join(".bundles/dev.example.app/bundle"); + std::fs::create_dir_all(¤t).unwrap(); + assert_eq!(installed_bundle(&root, "dev.example.app"), current, "the new layout wins"); + assert_eq!(installed_bundle(&root, "dev.example.none"), root.join("dev.example.none/bundle")); + let _ = std::fs::remove_dir_all(&root); + } use super::*; /// ADR 0004 ยง3: no script app takes a native app's id or namespace. diff --git a/crates/shell/src/host_tools/script_apps.rs b/crates/shell/src/host_tools/script_apps.rs index 61c293cc..56558760 100644 --- a/crates/shell/src/host_tools/script_apps.rs +++ b/crates/shell/src/host_tools/script_apps.rs @@ -149,7 +149,7 @@ fn admitted_bundle(app: &str) -> Result<(PathBuf, PathBuf), String> { let root = octosense_appstore::data_root_if_set().ok_or("App Hub has no apps root yet")?; let bundle = match octosense_appstore::system::system_app(app) { Some(system) => octosense_appstore::system::prepare(&root, &system)?.0, - None => root.join(app).join("bundle"), + None => crate::apps::installed_bundle(&root, app), }; Ok((root, bundle)) }