app

Local-first trade for farms and co-ops
git clone https://radroots.dev/git/app.git
Log | Files | Refs | README | LICENSE

commit 243886c1de9d624db38ea04b51f72c6e47bfffe2
parent 9c804bf0200c8fa3680720ec2881ba3e1e1a4bea
Author: triesap <tyson@radroots.org>
Date:   Sun, 24 May 2026 08:39:53 +0000

core: centralize shared local events paths

Diffstat:
Mcrates/launchers/desktop/src/runtime.rs | 56+++++++++++++++-----------------------------------------
Mcrates/shared/core/src/lib.rs | 4+++-
Mcrates/shared/core/src/paths.rs | 87++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
3 files changed, 104 insertions(+), 43 deletions(-)

diff --git a/crates/launchers/desktop/src/runtime.rs b/crates/launchers/desktop/src/runtime.rs @@ -9,7 +9,8 @@ use chrono::{Duration, Utc}; use radroots_app_core::{ AppBuildIdentity, AppDesktopRuntimePaths, AppRuntimeCapture, AppRuntimeMode, AppRuntimePathsError, AppRuntimeSnapshot, AppSharedAccountsPaths, PackDayExportWriteError, - prepare_pack_day_export_bundle_at_data_root, write_prepared_pack_day_export_bundle, + prepare_pack_day_export_bundle_at_data_root, + shared_local_events_database_path_from_shared_accounts, write_prepared_pack_day_export_bundle, }; use radroots_app_models::{ ActiveSurface, AppActivityContext, AppActivityKind, AppIdentityProjection, AppStartupGate, @@ -83,8 +84,6 @@ use crate::remote_signer::{ }; const APP_DATABASE_FILE_NAME: &str = "app.sqlite3"; -const SHARED_LOCAL_EVENTS_DIR: &str = "local_events"; -const SHARED_LOCAL_EVENTS_DB_FILE_NAME: &str = "local_events.sqlite"; const SYNC_TRANSPORT_UNAVAILABLE_MESSAGE: &str = "remote sync transport is not configured"; #[derive(Debug, Default)] @@ -955,7 +954,7 @@ impl DesktopAppRuntimeState { } let database_path = paths.app.data.join(APP_DATABASE_FILE_NAME); let sqlite_store = AppSqliteStore::open(DatabaseTarget::Path(database_path.clone()))?; - let shared_local_events_database_path = shared_local_events_database_path(&paths)?; + let shared_local_events_database_path = paths.shared_local_events_database_path()?; let _ = sqlite_store .import_shared_local_events_from_path(shared_local_events_database_path.as_path())?; let database_schema_version = sqlite_store.schema_version()?; @@ -3917,35 +3916,6 @@ enum DesktopAppRuntimeBootstrapError { Sqlite(#[from] AppSqliteError), #[error(transparent)] State(#[from] AppStateStoreError), - #[error("desktop app data root must be nested under the Radroots data root")] - SharedLocalEventsPath, -} - -fn shared_local_events_database_path( - paths: &AppDesktopRuntimePaths, -) -> Result<PathBuf, DesktopAppRuntimeBootstrapError> { - let data_root = paths - .app - .data - .parent() - .and_then(|apps_root| apps_root.parent()) - .ok_or(DesktopAppRuntimeBootstrapError::SharedLocalEventsPath)?; - Ok(data_root - .join("shared") - .join(SHARED_LOCAL_EVENTS_DIR) - .join(SHARED_LOCAL_EVENTS_DB_FILE_NAME)) -} - -fn shared_local_events_database_path_from_shared_accounts( - paths: &AppSharedAccountsPaths, -) -> Option<PathBuf> { - Some( - paths - .data_root - .parent()? - .join(SHARED_LOCAL_EVENTS_DIR) - .join(SHARED_LOCAL_EVENTS_DB_FILE_NAME), - ) } fn current_runtime_time_ms() -> Result<i64, AppSqliteError> { @@ -6066,8 +6036,9 @@ mod tests { .generate_local_account(Some("Buyer".to_owned())) .expect("account should generate") ); - let database_path = - super::shared_local_events_database_path(&paths).expect("shared local events path"); + let database_path = paths + .shared_local_events_database_path() + .expect("shared local events path"); if let Some(parent) = database_path.parent() { fs::create_dir_all(parent).expect("shared local events parent directory"); } @@ -11180,8 +11151,9 @@ mod tests { } fn append_cli_local_listing_records(paths: &AppDesktopRuntimePaths, account_id: &str) { - let database_path = - super::shared_local_events_database_path(paths).expect("shared local events path"); + let database_path = paths + .shared_local_events_database_path() + .expect("shared local events path"); if let Some(parent) = database_path.parent() { fs::create_dir_all(parent).expect("shared local events directory should create"); } @@ -11278,8 +11250,9 @@ mod tests { title: &str, created_at_ms: i64, ) { - let database_path = - super::shared_local_events_database_path(paths).expect("shared local events path"); + let database_path = paths + .shared_local_events_database_path() + .expect("shared local events path"); if let Some(parent) = database_path.parent() { fs::create_dir_all(parent).expect("shared local events directory should create"); } @@ -11467,8 +11440,9 @@ mod tests { } fn shared_local_event_records(paths: &AppDesktopRuntimePaths) -> Vec<LocalEventRecord> { - let database_path = - super::shared_local_events_database_path(paths).expect("shared local events path"); + let database_path = paths + .shared_local_events_database_path() + .expect("shared local events path"); let executor = SqliteExecutor::open(database_path.as_path()).expect("open shared local events db"); let store = LocalEventsStore::new(executor); diff --git a/crates/shared/core/src/lib.rs b/crates/shared/core/src/lib.rs @@ -23,7 +23,9 @@ pub use paths::{ AppSharedAccountsPaths, AppSharedIdentityPaths, SHARED_ACCOUNTS_NAMESPACE, SHARED_ACCOUNTS_NAMESPACE_KIND, SHARED_ACCOUNTS_NAMESPACE_VALUE, SHARED_ACCOUNTS_STORE_FILE_NAME, SHARED_IDENTITIES_NAMESPACE, SHARED_IDENTITIES_NAMESPACE_KIND, - SHARED_IDENTITIES_NAMESPACE_VALUE, SHARED_IDENTITY_FILE_NAME, + SHARED_IDENTITIES_NAMESPACE_VALUE, SHARED_IDENTITY_FILE_NAME, SHARED_LOCAL_EVENTS_DB_FILE_NAME, + SHARED_LOCAL_EVENTS_NAMESPACE, SHARED_LOCAL_EVENTS_NAMESPACE_KIND, + SHARED_LOCAL_EVENTS_NAMESPACE_VALUE, shared_local_events_database_path_from_shared_accounts, }; pub use runtime::{ APP_DEFAULT_NOSTR_RELAY_URL_ENV, APP_HOST_PLATFORM, APP_ID, APP_LOCAL_LOG_ROOT_ENV, APP_NAME, diff --git a/crates/shared/core/src/paths.rs b/crates/shared/core/src/paths.rs @@ -17,6 +17,10 @@ pub const SHARED_IDENTITIES_NAMESPACE_KIND: &str = "shared"; pub const SHARED_IDENTITIES_NAMESPACE_VALUE: &str = "identities"; pub const SHARED_IDENTITIES_NAMESPACE: &str = "shared/identities"; pub const SHARED_IDENTITY_FILE_NAME: &str = "default.json"; +pub const SHARED_LOCAL_EVENTS_NAMESPACE_KIND: &str = "shared"; +pub const SHARED_LOCAL_EVENTS_NAMESPACE_VALUE: &str = "local_events"; +pub const SHARED_LOCAL_EVENTS_NAMESPACE: &str = "shared/local_events"; +pub const SHARED_LOCAL_EVENTS_DB_FILE_NAME: &str = "local_events.sqlite"; pub const APP_PATHS_PROFILE_ENV: &str = "RADROOTS_APP_PATHS_PROFILE"; pub const APP_PATHS_REPO_LOCAL_ROOT_ENV: &str = "RADROOTS_APP_PATHS_REPO_LOCAL_ROOT"; @@ -74,7 +78,7 @@ impl AppRuntimeHostEnvironment { appdata_dir: read_env("APPDATA").map(PathBuf::from), localappdata_dir: read_env("LOCALAPPDATA").map(PathBuf::from), paths_profile: read_env(APP_PATHS_PROFILE_ENV) - .and_then(|value| value.into_string().ok()), + .map(|value| value.to_string_lossy().into_owned()), repo_local_root: read_env(APP_PATHS_REPO_LOCAL_ROOT_ENV).map(PathBuf::from), } } @@ -109,6 +113,12 @@ pub struct AppDesktopRuntimePaths { pub shared_identity: AppSharedIdentityPaths, } +impl AppSharedAccountsPaths { + pub fn shared_local_events_database_path(&self) -> Option<PathBuf> { + shared_local_events_database_path_from_shared_accounts(self) + } +} + impl AppRuntimeRoots { pub fn current_desktop() -> Result<Self, AppRuntimePathsError> { AppDesktopRuntimePaths::current_desktop().map(|paths| paths.app) @@ -182,6 +192,36 @@ impl AppDesktopRuntimePaths { }, }) } + + pub fn shared_local_events_database_path(&self) -> Result<PathBuf, AppRuntimePathsError> { + let data_root = self + .app + .data + .parent() + .and_then(|apps_root| apps_root.parent()) + .ok_or(AppRuntimePathsError::SharedLocalEventsPath)?; + + Ok(shared_local_events_database_path_from_data_root(data_root)) + } +} + +pub fn shared_local_events_database_path_from_shared_accounts( + paths: &AppSharedAccountsPaths, +) -> Option<PathBuf> { + Some( + paths + .data_root + .parent()? + .join(SHARED_LOCAL_EVENTS_NAMESPACE_VALUE) + .join(SHARED_LOCAL_EVENTS_DB_FILE_NAME), + ) +} + +fn shared_local_events_database_path_from_data_root(data_root: &Path) -> PathBuf { + data_root + .join(SHARED_LOCAL_EVENTS_NAMESPACE_KIND) + .join(SHARED_LOCAL_EVENTS_NAMESPACE_VALUE) + .join(SHARED_LOCAL_EVENTS_DB_FILE_NAME) } fn resolve_desktop_base_roots( @@ -268,6 +308,7 @@ pub enum AppRuntimePathsError { EmptyRepoLocalRoot, UnsupportedPathProfile { value: String }, UnsupportedPlatform { platform: AppRuntimePlatform }, + SharedLocalEventsPath, } impl fmt::Display for AppRuntimePathsError { @@ -299,6 +340,8 @@ impl fmt::Display for AppRuntimePathsError { "desktop runtime roots are unsupported on {}", platform.label() ), + Self::SharedLocalEventsPath => formatter + .write_str("desktop app data root must be nested under the Radroots data root"), } } } @@ -314,6 +357,7 @@ mod tests { AppDesktopRuntimePaths, AppRuntimeHostEnvironment, AppRuntimePathsError, AppRuntimePlatform, AppRuntimeRoots, SHARED_ACCOUNTS_NAMESPACE, SHARED_ACCOUNTS_STORE_FILE_NAME, SHARED_IDENTITIES_NAMESPACE, SHARED_IDENTITY_FILE_NAME, + SHARED_LOCAL_EVENTS_DB_FILE_NAME, SHARED_LOCAL_EVENTS_NAMESPACE, }; #[test] @@ -355,6 +399,14 @@ mod tests { .join(SHARED_IDENTITIES_NAMESPACE) .join(SHARED_IDENTITY_FILE_NAME) ); + assert_eq!( + paths + .shared_local_events_database_path() + .expect("shared local events path"), + PathBuf::from("/Users/treesap/.radroots/data") + .join(SHARED_LOCAL_EVENTS_NAMESPACE) + .join(SHARED_LOCAL_EVENTS_DB_FILE_NAME) + ); } #[test] @@ -435,6 +487,15 @@ mod tests { PathBuf::from("/repo/infra/local/runtime/radroots/secrets/shared/identities") .join(SHARED_IDENTITY_FILE_NAME) ); + assert_eq!( + paths + .shared_accounts + .shared_local_events_database_path() + .expect("shared local events path"), + PathBuf::from("/repo/infra/local/runtime/radroots/data") + .join(SHARED_LOCAL_EVENTS_NAMESPACE) + .join(SHARED_LOCAL_EVENTS_DB_FILE_NAME) + ); } #[test] @@ -491,6 +552,30 @@ mod tests { ); } + #[cfg(unix)] + #[test] + fn malformed_env_profile_fails_closed() { + use std::os::unix::ffi::OsStringExt; + + let env = BTreeMap::from([( + APP_PATHS_PROFILE_ENV, + OsString::from_vec(vec![0xff, b'd', b'e', b'v']), + )]); + let err = AppRuntimeRoots::for_desktop( + AppRuntimePlatform::Macos, + AppRuntimeHostEnvironment::from_env_reader(|name| env.get(name).cloned()), + ) + .expect_err("malformed configured profile should fail closed"); + + match err { + AppRuntimePathsError::UnsupportedPathProfile { value } => { + assert!(value.contains('\u{fffd}')); + assert!(value.ends_with("dev")); + } + unexpected => panic!("unexpected malformed profile error: {unexpected:?}"), + } + } + #[test] fn desktop_runtime_roots_require_home_dir_on_unix() { let err = AppRuntimeRoots::for_desktop(