commit c184c0c247305b56f353cdc30841d926125d6bae
parent 4e47ad1a9b2c119d92f8bb4a53bc29521535b68e
Author: triesap <tyson@radroots.org>
Date: Sun, 22 Mar 2026 01:14:47 +0000
core: add revision-aware offline geocoder diagnostics
- extend offline geocoder unavailable state with release-safe platform and asset revision context
- export platform and stamped revision details through the shared offline geocoder diagnostic contract without leaking raw debug paths
- attach the new diagnostic context across desktop ios android and web geocoder initialization paths
- log one-line unavailable events on desktop and ios to match the existing android geocoder failure reporting
Diffstat:
12 files changed, 289 insertions(+), 81 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
@@ -2885,6 +2885,7 @@ dependencies = [
"eframe",
"egui",
"image",
+ "log",
"objc2-foundation 0.3.2",
"radroots-app-apple-security",
"radroots-app-core",
@@ -2900,6 +2901,7 @@ name = "radroots-app-ios"
version = "0.1.0"
dependencies = [
"eframe",
+ "log",
"radroots-app-apple-security",
"radroots-app-core",
"radroots-geocoder",
diff --git a/crates/android/src/lib.rs b/crates/android/src/lib.rs
@@ -204,6 +204,7 @@ impl AndroidBackend {
let offline_geocoder = offline_geocoder::AndroidOfflineGeocoder::from_state(
RadrootsOfflineGeocoderState::unavailable(
radroots_app_core::RadrootsOfflineGeocoderUnavailableKind::MissingBuildAsset,
+ radroots_app_core::RadrootsOfflineGeocoderPlatform::Android,
"android offline geocoder initialization is only wired on android targets",
),
);
diff --git a/crates/android/src/offline_geocoder.rs b/crates/android/src/offline_geocoder.rs
@@ -1,6 +1,9 @@
#![cfg_attr(not(target_os = "android"), allow(dead_code))]
-use radroots_app_core::{RadrootsOfflineGeocoderState, RadrootsOfflineGeocoderUnavailableKind};
+use radroots_app_core::{
+ RadrootsOfflineGeocoderPlatform, RadrootsOfflineGeocoderState,
+ RadrootsOfflineGeocoderUnavailableKind,
+};
#[cfg(target_os = "android")]
use radroots_geocoder::Geocoder;
use std::path::Path;
@@ -60,6 +63,7 @@ impl AndroidOfflineGeocoder {
.unwrap_or_else(|_| {
RadrootsOfflineGeocoderState::unavailable(
RadrootsOfflineGeocoderUnavailableKind::InternalError,
+ RadrootsOfflineGeocoderPlatform::Android,
"android offline geocoder state lock poisoned",
)
})
@@ -78,19 +82,44 @@ impl AndroidOfflineGeocoder {
fn initialize_offline_geocoder() -> RadrootsOfflineGeocoderState {
match initialize_offline_geocoder_inner() {
Ok(()) => RadrootsOfflineGeocoderState::Ready,
- Err((kind, debug_message)) => {
- RadrootsOfflineGeocoderState::unavailable(kind, debug_message)
- }
+ Err((kind, asset_revision, debug_message)) => match asset_revision {
+ Some(asset_revision) => RadrootsOfflineGeocoderState::unavailable_with_revision(
+ kind,
+ RadrootsOfflineGeocoderPlatform::Android,
+ asset_revision,
+ debug_message,
+ ),
+ None => RadrootsOfflineGeocoderState::unavailable(
+ kind,
+ RadrootsOfflineGeocoderPlatform::Android,
+ debug_message,
+ ),
+ },
}
}
#[cfg(target_os = "android")]
-fn initialize_offline_geocoder_inner()
--> Result<(), (RadrootsOfflineGeocoderUnavailableKind, String)> {
- let staged_path = stage_offline_geocoder_asset()?;
+fn initialize_offline_geocoder_inner() -> Result<
+ (),
+ (
+ RadrootsOfflineGeocoderUnavailableKind,
+ Option<String>,
+ String,
+ ),
+> {
+ let staged_path = stage_offline_geocoder_asset()
+ .map_err(|(kind, debug_message)| (kind, None, debug_message))?;
+ let asset_revision = staged_asset_revision(staged_path.as_str()).map_err(|debug_message| {
+ (
+ RadrootsOfflineGeocoderUnavailableKind::InternalError,
+ None,
+ debug_message,
+ )
+ })?;
Geocoder::open_path(staged_path.as_str()).map_err(|source| {
(
RadrootsOfflineGeocoderUnavailableKind::InitializationFailed,
+ Some(asset_revision.clone()),
format!("failed to open staged android geocoder db: {source}"),
)
})?;
@@ -304,6 +333,24 @@ fn prune_stale_revisions(staged_path: &str) -> Result<(), String> {
Ok(())
}
+fn staged_asset_revision(staged_path: &str) -> Result<String, String> {
+ let staged_path = Path::new(staged_path);
+ let Some(active_revision_dir) = staged_path.parent() else {
+ return Err("android staged geocoder path did not have a revision directory".to_owned());
+ };
+ let Some(active_revision) = active_revision_dir.file_name() else {
+ return Err("android staged geocoder revision directory did not have a name".to_owned());
+ };
+ let revision = active_revision.to_string_lossy();
+ if revision.len() != 64 || !revision.bytes().all(|byte| byte.is_ascii_hexdigit()) {
+ return Err(
+ "android staged geocoder revision directory name was not a sha256 hex revision"
+ .to_owned(),
+ );
+ }
+ Ok(revision.into_owned())
+}
+
#[cfg(test)]
mod tests {
use super::*;
@@ -313,6 +360,7 @@ mod tests {
fn missing_asset_maps_to_build_unavailable_message() {
let state = RadrootsOfflineGeocoderState::unavailable(
RadrootsOfflineGeocoderUnavailableKind::MissingBuildAsset,
+ RadrootsOfflineGeocoderPlatform::Android,
"android bundled geocoder asset missing at assets/geocoder/geonames.db",
);
@@ -320,6 +368,8 @@ mod tests {
state,
RadrootsOfflineGeocoderState::Unavailable {
kind: RadrootsOfflineGeocoderUnavailableKind::MissingBuildAsset,
+ platform: RadrootsOfflineGeocoderPlatform::Android,
+ asset_revision: None,
debug_message:
"android bundled geocoder asset missing at assets/geocoder/geonames.db"
.to_owned(),
@@ -328,6 +378,17 @@ mod tests {
}
#[test]
+ fn staged_asset_revision_reads_sha256_directory_name() {
+ let revision = "6ca5f1a324de02922d40b1ff33eedf3a5a133c978de921eee5130a0c7876079c";
+ let staged_path = format!("/tmp/radroots/android/geocoder/{revision}/geonames.db");
+
+ assert_eq!(
+ staged_asset_revision(staged_path.as_str()).unwrap(),
+ revision
+ );
+ }
+
+ #[test]
fn prune_stale_revisions_keeps_active_revision_only() {
let temp_root = std::env::temp_dir().join(format!(
"radroots-android-geocoder-prune-test-{}",
diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs
@@ -9,8 +9,8 @@ mod offline_geocoder;
pub const APP_NAME: &str = "Rad Roots";
pub use offline_geocoder::{
- RadrootsOfflineGeocoderDiagnostic, RadrootsOfflineGeocoderState,
- RadrootsOfflineGeocoderUnavailableKind,
+ RadrootsOfflineGeocoderDiagnostic, RadrootsOfflineGeocoderPlatform,
+ RadrootsOfflineGeocoderState, RadrootsOfflineGeocoderUnavailableKind,
};
#[derive(Debug, Clone, PartialEq, Eq)]
@@ -298,6 +298,11 @@ impl RadrootsApp {
if let Some(diagnostic) = state.diagnostic() {
ui.label(diagnostic.technical_message);
ui.add_space(6.0);
+ ui.monospace(format!("platform: {}", diagnostic.platform_code));
+ ui.monospace(format!(
+ "asset revision: {}",
+ diagnostic.asset_revision.as_deref().unwrap_or("unknown")
+ ));
ui.monospace(format!("diagnostic code: {}", diagnostic.code));
if ui.button("Copy Offline Geocoder Diagnostic").clicked() {
ui.ctx().copy_text(diagnostic.export_text());
@@ -1169,6 +1174,7 @@ mod tests {
RadrootsOfflineGeocoderState::Initializing,
vec![Ok(Some(RadrootsOfflineGeocoderState::unavailable(
RadrootsOfflineGeocoderUnavailableKind::InitializationFailed,
+ RadrootsOfflineGeocoderPlatform::Desktop,
"failed to open staged geocoder db",
)))],
),
@@ -1180,6 +1186,7 @@ mod tests {
app.offline_geocoder_state,
Some(RadrootsOfflineGeocoderState::unavailable(
RadrootsOfflineGeocoderUnavailableKind::InitializationFailed,
+ RadrootsOfflineGeocoderPlatform::Desktop,
"failed to open staged geocoder db",
))
);
@@ -1200,6 +1207,8 @@ mod tests {
.as_ref()
.and_then(RadrootsOfflineGeocoderState::diagnostic)
.unwrap();
+ assert_eq!(diagnostic.platform_code, "desktop");
+ assert_eq!(diagnostic.asset_revision, None);
assert_eq!(diagnostic.code, "initialization_failed");
assert!(
!diagnostic
diff --git a/crates/core/src/offline_geocoder.rs b/crates/core/src/offline_geocoder.rs
@@ -1,4 +1,23 @@
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum RadrootsOfflineGeocoderPlatform {
+ Desktop,
+ Ios,
+ Android,
+ Web,
+}
+
+impl RadrootsOfflineGeocoderPlatform {
+ pub fn code(self) -> &'static str {
+ match self {
+ Self::Desktop => "desktop",
+ Self::Ios => "ios",
+ Self::Android => "android",
+ Self::Web => "web",
+ }
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RadrootsOfflineGeocoderUnavailableKind {
MissingBuildAsset,
InitializationFailed,
@@ -40,6 +59,8 @@ impl RadrootsOfflineGeocoderUnavailableKind {
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RadrootsOfflineGeocoderDiagnostic {
+ pub platform_code: &'static str,
+ pub asset_revision: Option<String>,
pub code: &'static str,
pub summary_label: &'static str,
pub user_message: &'static str,
@@ -49,8 +70,13 @@ pub struct RadrootsOfflineGeocoderDiagnostic {
impl RadrootsOfflineGeocoderDiagnostic {
pub fn export_text(&self) -> String {
format!(
- "offline geocoder diagnostic\ncode: {}\nstatus: {}\nuser: {}\ntechnical: {}",
- self.code, self.summary_label, self.user_message, self.technical_message
+ "offline geocoder diagnostic\nplatform: {}\nasset_revision: {}\ncode: {}\nstatus: {}\nuser: {}\ntechnical: {}",
+ self.platform_code,
+ self.asset_revision.as_deref().unwrap_or("unknown"),
+ self.code,
+ self.summary_label,
+ self.user_message,
+ self.technical_message
)
}
}
@@ -61,6 +87,8 @@ pub enum RadrootsOfflineGeocoderState {
Ready,
Unavailable {
kind: RadrootsOfflineGeocoderUnavailableKind,
+ platform: RadrootsOfflineGeocoderPlatform,
+ asset_revision: Option<String>,
debug_message: String,
},
}
@@ -68,10 +96,27 @@ pub enum RadrootsOfflineGeocoderState {
impl RadrootsOfflineGeocoderState {
pub fn unavailable(
kind: RadrootsOfflineGeocoderUnavailableKind,
+ platform: RadrootsOfflineGeocoderPlatform,
debug_message: impl Into<String>,
) -> Self {
Self::Unavailable {
kind,
+ platform,
+ asset_revision: None,
+ debug_message: debug_message.into(),
+ }
+ }
+
+ pub fn unavailable_with_revision(
+ kind: RadrootsOfflineGeocoderUnavailableKind,
+ platform: RadrootsOfflineGeocoderPlatform,
+ asset_revision: impl Into<String>,
+ debug_message: impl Into<String>,
+ ) -> Self {
+ Self::Unavailable {
+ kind,
+ platform,
+ asset_revision: Some(asset_revision.into()),
debug_message: debug_message.into(),
}
}
@@ -85,7 +130,14 @@ impl RadrootsOfflineGeocoderState {
pub fn diagnostic(&self) -> Option<RadrootsOfflineGeocoderDiagnostic> {
match self {
- Self::Unavailable { kind, .. } => Some(RadrootsOfflineGeocoderDiagnostic {
+ Self::Unavailable {
+ kind,
+ platform,
+ asset_revision,
+ ..
+ } => Some(RadrootsOfflineGeocoderDiagnostic {
+ platform_code: platform.code(),
+ asset_revision: asset_revision.clone(),
code: kind.code(),
summary_label: self.summary_label(),
user_message: kind.user_message(),
@@ -103,6 +155,20 @@ impl RadrootsOfflineGeocoderState {
}
}
+ pub fn platform(&self) -> Option<RadrootsOfflineGeocoderPlatform> {
+ match self {
+ Self::Unavailable { platform, .. } => Some(*platform),
+ Self::Initializing | Self::Ready => None,
+ }
+ }
+
+ pub fn asset_revision(&self) -> Option<&str> {
+ match self {
+ Self::Unavailable { asset_revision, .. } => asset_revision.as_deref(),
+ Self::Initializing | Self::Ready => None,
+ }
+ }
+
pub fn technical_message(&self) -> Option<&'static str> {
match self {
Self::Unavailable { kind, .. } => Some(kind.technical_message()),
@@ -126,10 +192,13 @@ mod tests {
fn unavailable_state_exposes_release_safe_diagnostic() {
let state = RadrootsOfflineGeocoderState::unavailable(
RadrootsOfflineGeocoderUnavailableKind::InitializationFailed,
+ RadrootsOfflineGeocoderPlatform::Desktop,
"failed to open staged geocoder db: /tmp/geonames.db",
);
let diagnostic = state.diagnostic().unwrap();
+ assert_eq!(diagnostic.platform_code, "desktop");
+ assert_eq!(diagnostic.asset_revision, None);
assert_eq!(diagnostic.code, "initialization_failed");
assert_eq!(diagnostic.summary_label, "Offline geocoder unavailable");
assert_eq!(
@@ -142,4 +211,27 @@ mod tests {
);
assert!(!diagnostic.export_text().contains("/tmp/geonames.db"));
}
+
+ #[test]
+ fn unavailable_state_with_revision_exports_release_safe_platform_context() {
+ let state = RadrootsOfflineGeocoderState::unavailable_with_revision(
+ RadrootsOfflineGeocoderUnavailableKind::InitializationFailed,
+ RadrootsOfflineGeocoderPlatform::Android,
+ "6ca5f1a324de02922d40b1ff33eedf3a5a133c978de921eee5130a0c7876079c",
+ "failed to open staged android geocoder db: /data/user/0/org.radroots.app.android/files/geocoder.db",
+ );
+ let diagnostic = state.diagnostic().unwrap();
+ let export_text = diagnostic.export_text();
+
+ assert_eq!(diagnostic.platform_code, "android");
+ assert_eq!(
+ diagnostic.asset_revision.as_deref(),
+ Some("6ca5f1a324de02922d40b1ff33eedf3a5a133c978de921eee5130a0c7876079c")
+ );
+ assert!(export_text.contains("platform: android"));
+ assert!(export_text.contains(
+ "asset_revision: 6ca5f1a324de02922d40b1ff33eedf3a5a133c978de921eee5130a0c7876079c"
+ ));
+ assert!(!export_text.contains("/data/user/0/org.radroots.app.android/files/geocoder.db"));
+ }
}
diff --git a/crates/desktop/Cargo.toml b/crates/desktop/Cargo.toml
@@ -19,6 +19,7 @@ directories.workspace = true
eframe = { workspace = true, features = ["wgpu", "wayland", "x11"] }
egui.workspace = true
image.workspace = true
+log.workspace = true
radroots-app-core = { path = "../core" }
radroots-geocoder.workspace = true
radroots-nostr-accounts.workspace = true
diff --git a/crates/desktop/src/main.rs b/crates/desktop/src/main.rs
@@ -10,8 +10,8 @@ use radroots_app_apple_security::{
};
use radroots_app_core::{
APP_NAME, HomeActionKind, HomeActionResult, HomeActionState, IdentityGateState,
- ImportActionState, RadrootsApp, RadrootsAppBackend, RadrootsOfflineGeocoderState,
- RadrootsOfflineGeocoderUnavailableKind, SetupActionState,
+ ImportActionState, RadrootsApp, RadrootsAppBackend, RadrootsOfflineGeocoderPlatform,
+ RadrootsOfflineGeocoderState, RadrootsOfflineGeocoderUnavailableKind, SetupActionState,
};
#[cfg(target_os = "macos")]
use radroots_identity::RadrootsIdentity;
@@ -64,21 +64,22 @@ impl DesktopBackend {
#[cfg(target_os = "macos")]
let offline_geocoder = match Self::app_data_root() {
Ok(app_data_root) => DesktopOfflineGeocoder::start(app_data_root),
- Err(debug_message) => DesktopOfflineGeocoder::from_state(
- RadrootsOfflineGeocoderState::unavailable(
+ Err(debug_message) => {
+ DesktopOfflineGeocoder::from_state(RadrootsOfflineGeocoderState::unavailable(
RadrootsOfflineGeocoderUnavailableKind::InternalError,
+ RadrootsOfflineGeocoderPlatform::Desktop,
debug_message,
- ),
- ),
+ ))
+ }
};
#[cfg(not(target_os = "macos"))]
- let offline_geocoder = DesktopOfflineGeocoder::from_state(
- RadrootsOfflineGeocoderState::unavailable(
+ let offline_geocoder =
+ DesktopOfflineGeocoder::from_state(RadrootsOfflineGeocoderState::unavailable(
RadrootsOfflineGeocoderUnavailableKind::MissingBuildAsset,
+ RadrootsOfflineGeocoderPlatform::Desktop,
"desktop offline geocoder initialization is only wired for macos",
- ),
- );
+ ));
Self { offline_geocoder }
}
diff --git a/crates/desktop/src/offline_geocoder.rs b/crates/desktop/src/offline_geocoder.rs
@@ -1,4 +1,7 @@
-use radroots_app_core::{RadrootsOfflineGeocoderState, RadrootsOfflineGeocoderUnavailableKind};
+use radroots_app_core::{
+ RadrootsOfflineGeocoderPlatform, RadrootsOfflineGeocoderState,
+ RadrootsOfflineGeocoderUnavailableKind,
+};
use radroots_geocoder::Geocoder;
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicBool, Ordering};
@@ -28,6 +31,9 @@ impl DesktopOfflineGeocoder {
let changed = Arc::clone(&tracker.changed);
std::thread::spawn(move || {
let state = initialize_offline_geocoder(app_data_root.as_path());
+ if let RadrootsOfflineGeocoderState::Unavailable { debug_message, .. } = &state {
+ log::warn!("desktop offline geocoder unavailable: {debug_message}");
+ }
if let Ok(mut slot) = current.lock() {
*slot = state;
changed.store(true, Ordering::Release);
@@ -44,6 +50,7 @@ impl DesktopOfflineGeocoder {
.unwrap_or_else(|_| {
RadrootsOfflineGeocoderState::unavailable(
RadrootsOfflineGeocoderUnavailableKind::InternalError,
+ RadrootsOfflineGeocoderPlatform::Desktop,
"desktop offline geocoder state lock poisoned",
)
})
@@ -59,49 +66,58 @@ impl DesktopOfflineGeocoder {
}
fn initialize_offline_geocoder(app_data_root: &Path) -> RadrootsOfflineGeocoderState {
- match initialize_offline_geocoder_inner(app_data_root) {
- Ok(()) => RadrootsOfflineGeocoderState::Ready,
- Err((kind, debug_message)) => {
- RadrootsOfflineGeocoderState::unavailable(kind, debug_message)
- }
- }
-}
-
-fn initialize_offline_geocoder_inner(
- app_data_root: &Path,
-) -> Result<(), (RadrootsOfflineGeocoderUnavailableKind, String)> {
let source_path = runtime_asset_path().map_err(|debug_message| {
- (
+ RadrootsOfflineGeocoderState::unavailable(
RadrootsOfflineGeocoderUnavailableKind::InternalError,
+ RadrootsOfflineGeocoderPlatform::Desktop,
debug_message,
)
- })?;
+ });
+ let source_path = match source_path {
+ Ok(source_path) => source_path,
+ Err(state) => return state,
+ };
if !source_path.is_file() {
- return Err((
+ return RadrootsOfflineGeocoderState::unavailable(
RadrootsOfflineGeocoderUnavailableKind::MissingBuildAsset,
+ RadrootsOfflineGeocoderPlatform::Desktop,
format!(
"desktop bundled geocoder asset missing at {}",
source_path.display()
),
- ));
+ );
}
- let revision = runtime_asset_revision(source_path.parent().unwrap_or_else(|| Path::new(".")))?;
+ let revision =
+ match runtime_asset_revision(source_path.parent().unwrap_or_else(|| Path::new("."))) {
+ Ok(revision) => revision,
+ Err((kind, debug_message)) => {
+ return RadrootsOfflineGeocoderState::unavailable(
+ kind,
+ RadrootsOfflineGeocoderPlatform::Desktop,
+ debug_message,
+ );
+ }
+ };
let staged_path = staged_db_path(app_data_root, revision.as_str());
- stage_runtime_asset(source_path.as_path(), staged_path.as_path()).map_err(|debug_message| {
- (
+ if let Err(debug_message) = stage_runtime_asset(source_path.as_path(), staged_path.as_path()) {
+ return RadrootsOfflineGeocoderState::unavailable_with_revision(
RadrootsOfflineGeocoderUnavailableKind::InitializationFailed,
+ RadrootsOfflineGeocoderPlatform::Desktop,
+ revision,
debug_message,
- )
- })?;
- Geocoder::open_path(staged_path.as_path()).map_err(|source| {
- (
+ );
+ }
+ if let Err(source) = Geocoder::open_path(staged_path.as_path()) {
+ return RadrootsOfflineGeocoderState::unavailable_with_revision(
RadrootsOfflineGeocoderUnavailableKind::InitializationFailed,
+ RadrootsOfflineGeocoderPlatform::Desktop,
+ revision,
format!("failed to open staged geocoder db: {source}"),
- )
- })?;
+ );
+ }
let _ = prune_stale_revisions(staged_geocoder_root(app_data_root), revision.as_str());
- Ok(())
+ RadrootsOfflineGeocoderState::Ready
}
fn runtime_asset_path() -> Result<PathBuf, String> {
@@ -239,6 +255,7 @@ mod tests {
fn missing_asset_maps_to_build_unavailable_message() {
let state = RadrootsOfflineGeocoderState::unavailable(
RadrootsOfflineGeocoderUnavailableKind::MissingBuildAsset,
+ RadrootsOfflineGeocoderPlatform::Desktop,
"desktop bundled geocoder asset missing at /tmp/geonames.db",
);
@@ -246,6 +263,8 @@ mod tests {
state,
RadrootsOfflineGeocoderState::Unavailable {
kind: RadrootsOfflineGeocoderUnavailableKind::MissingBuildAsset,
+ platform: RadrootsOfflineGeocoderPlatform::Desktop,
+ asset_revision: None,
debug_message: "desktop bundled geocoder asset missing at /tmp/geonames.db"
.to_owned(),
}
diff --git a/crates/ios/Cargo.toml b/crates/ios/Cargo.toml
@@ -16,6 +16,7 @@ crate-type = ["staticlib", "rlib"]
[dependencies]
eframe = { workspace = true, features = ["wgpu"] }
+log.workspace = true
radroots-app-apple-security.workspace = true
radroots-app-core = { path = "../core" }
radroots-geocoder.workspace = true
diff --git a/crates/ios/src/lib.rs b/crates/ios/src/lib.rs
@@ -7,8 +7,8 @@ use radroots_app_core::IdentityGateState;
#[cfg(target_os = "ios")]
use radroots_app_core::{
APP_NAME, HomeActionKind, HomeActionResult, HomeActionState, ImportActionState,
- PasteActionState, RadrootsApp, RadrootsAppBackend, RadrootsOfflineGeocoderState,
- RadrootsOfflineGeocoderUnavailableKind, SetupActionState,
+ PasteActionState, RadrootsApp, RadrootsAppBackend, RadrootsOfflineGeocoderPlatform,
+ RadrootsOfflineGeocoderState, RadrootsOfflineGeocoderUnavailableKind, SetupActionState,
};
#[cfg(any(target_os = "ios", test))]
use radroots_identity::RadrootsIdentity;
@@ -48,6 +48,7 @@ impl IosBackend {
Err(debug_message) => offline_geocoder::IosOfflineGeocoder::from_state(
RadrootsOfflineGeocoderState::unavailable(
RadrootsOfflineGeocoderUnavailableKind::InternalError,
+ RadrootsOfflineGeocoderPlatform::Ios,
debug_message,
),
),
diff --git a/crates/ios/src/offline_geocoder.rs b/crates/ios/src/offline_geocoder.rs
@@ -1,6 +1,9 @@
#![cfg_attr(not(target_os = "ios"), allow(dead_code))]
-use radroots_app_core::{RadrootsOfflineGeocoderState, RadrootsOfflineGeocoderUnavailableKind};
+use radroots_app_core::{
+ RadrootsOfflineGeocoderPlatform, RadrootsOfflineGeocoderState,
+ RadrootsOfflineGeocoderUnavailableKind,
+};
use radroots_geocoder::Geocoder;
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicBool, Ordering};
@@ -30,6 +33,9 @@ impl IosOfflineGeocoder {
std::thread::spawn(move || {
let state = initialize_offline_geocoder(app_data_root.as_path());
+ if let RadrootsOfflineGeocoderState::Unavailable { debug_message, .. } = &state {
+ log::warn!("ios offline geocoder unavailable: {debug_message}");
+ }
if let Ok(mut slot) = current.lock() {
*slot = state;
changed.store(true, Ordering::Release);
@@ -46,6 +52,7 @@ impl IosOfflineGeocoder {
.unwrap_or_else(|_| {
RadrootsOfflineGeocoderState::unavailable(
RadrootsOfflineGeocoderUnavailableKind::InternalError,
+ RadrootsOfflineGeocoderPlatform::Ios,
"ios offline geocoder state lock poisoned",
)
})
@@ -61,49 +68,58 @@ impl IosOfflineGeocoder {
}
fn initialize_offline_geocoder(app_data_root: &Path) -> RadrootsOfflineGeocoderState {
- match initialize_offline_geocoder_inner(app_data_root) {
- Ok(()) => RadrootsOfflineGeocoderState::Ready,
- Err((kind, debug_message)) => {
- RadrootsOfflineGeocoderState::unavailable(kind, debug_message)
- }
- }
-}
-
-fn initialize_offline_geocoder_inner(
- app_data_root: &Path,
-) -> Result<(), (RadrootsOfflineGeocoderUnavailableKind, String)> {
let source_path = bundled_asset_path().map_err(|debug_message| {
- (
+ RadrootsOfflineGeocoderState::unavailable(
RadrootsOfflineGeocoderUnavailableKind::InternalError,
+ RadrootsOfflineGeocoderPlatform::Ios,
debug_message,
)
- })?;
+ });
+ let source_path = match source_path {
+ Ok(source_path) => source_path,
+ Err(state) => return state,
+ };
if !source_path.is_file() {
- return Err((
+ return RadrootsOfflineGeocoderState::unavailable(
RadrootsOfflineGeocoderUnavailableKind::MissingBuildAsset,
+ RadrootsOfflineGeocoderPlatform::Ios,
format!(
"ios bundled geocoder asset missing at {}",
source_path.display()
),
- ));
+ );
}
- let revision = bundled_asset_revision(source_path.parent().unwrap_or_else(|| Path::new(".")))?;
+ let revision =
+ match bundled_asset_revision(source_path.parent().unwrap_or_else(|| Path::new("."))) {
+ Ok(revision) => revision,
+ Err((kind, debug_message)) => {
+ return RadrootsOfflineGeocoderState::unavailable(
+ kind,
+ RadrootsOfflineGeocoderPlatform::Ios,
+ debug_message,
+ );
+ }
+ };
let staged_path = staged_db_path(app_data_root, revision.as_str());
- stage_bundled_asset(source_path.as_path(), staged_path.as_path()).map_err(|debug_message| {
- (
+ if let Err(debug_message) = stage_bundled_asset(source_path.as_path(), staged_path.as_path()) {
+ return RadrootsOfflineGeocoderState::unavailable_with_revision(
RadrootsOfflineGeocoderUnavailableKind::InitializationFailed,
+ RadrootsOfflineGeocoderPlatform::Ios,
+ revision,
debug_message,
- )
- })?;
- Geocoder::open_path(staged_path.as_path()).map_err(|source| {
- (
+ );
+ }
+ if let Err(source) = Geocoder::open_path(staged_path.as_path()) {
+ return RadrootsOfflineGeocoderState::unavailable_with_revision(
RadrootsOfflineGeocoderUnavailableKind::InitializationFailed,
+ RadrootsOfflineGeocoderPlatform::Ios,
+ revision,
format!("failed to open staged ios geocoder db: {source}"),
- )
- })?;
+ );
+ }
let _ = prune_stale_revisions(staged_geocoder_root(app_data_root), revision.as_str());
- Ok(())
+ RadrootsOfflineGeocoderState::Ready
}
fn bundled_asset_path() -> Result<PathBuf, String> {
@@ -242,6 +258,7 @@ mod tests {
fn missing_asset_maps_to_build_unavailable_message() {
let state = RadrootsOfflineGeocoderState::unavailable(
RadrootsOfflineGeocoderUnavailableKind::MissingBuildAsset,
+ RadrootsOfflineGeocoderPlatform::Ios,
"ios bundled geocoder asset missing at /tmp/geonames.db",
);
@@ -249,6 +266,8 @@ mod tests {
state,
RadrootsOfflineGeocoderState::Unavailable {
kind: RadrootsOfflineGeocoderUnavailableKind::MissingBuildAsset,
+ platform: RadrootsOfflineGeocoderPlatform::Ios,
+ asset_revision: None,
debug_message: "ios bundled geocoder asset missing at /tmp/geonames.db".to_owned(),
}
);
diff --git a/crates/web/src/lib.rs b/crates/web/src/lib.rs
@@ -13,20 +13,22 @@ use nostr::nips::nip19::ToBech32;
use nostr::signer::NostrSigner;
#[cfg(target_arch = "wasm32")]
use nostr_browser_signer::{BrowserSigner, Error as BrowserSignerError};
-#[cfg(any(target_arch = "wasm32", test))]
-use radroots_app_core::{
- RadrootsOfflineGeocoderState, RadrootsOfflineGeocoderUnavailableKind,
-};
#[cfg(target_arch = "wasm32")]
use radroots_app_core::{
HomeActionKind, HomeActionResult, HomeActionState, IdentityGateState, RadrootsApp,
RadrootsAppBackend, SetupActionState,
};
+#[cfg(any(target_arch = "wasm32", test))]
+use radroots_app_core::{
+ RadrootsOfflineGeocoderPlatform, RadrootsOfflineGeocoderState,
+ RadrootsOfflineGeocoderUnavailableKind,
+};
#[cfg(any(target_arch = "wasm32", test))]
fn offline_geocoder_unavailable_state() -> RadrootsOfflineGeocoderState {
RadrootsOfflineGeocoderState::unavailable(
RadrootsOfflineGeocoderUnavailableKind::MissingBuildAsset,
+ RadrootsOfflineGeocoderPlatform::Web,
"radroots-geocoder currently depends on rusqlite and is not wired for wasm runtime initialization.",
)
}
@@ -92,7 +94,6 @@ impl WebBackend {
state.pending_result = None;
IdentityGateState::Missing
}
-
}
#[cfg(target_arch = "wasm32")]