Prevent duplicate desktop model sessions and record calibration evidence
This commit is contained in:
1 parent
8498c5401c
commit
fb8b45dcc9
7 files changed
+206
-10
No files matched your search
Generated
+18
@@ -3814,11 +3814,13 @@ dependencies = [
|
||||
"searchi-service",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2 0.10.9",
|
||||
"tauri",
|
||||
"tauri-build",
|
||||
"tauri-plugin-dialog",
|
||||
"tauri-plugin-global-shortcut",
|
||||
"tauri-plugin-opener",
|
||||
"tauri-plugin-single-instance",
|
||||
"tempfile",
|
||||
"uuid",
|
||||
"window-vibrancy",
|
||||
@@ -4586,6 +4588,22 @@ dependencies = [
|
||||
"zbus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-single-instance"
|
||||
version = "2.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c142ed88deee222bd2d979269d35c73b6c1c0f6ebd5b79b4ff80066fcad6af1"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tauri",
|
||||
"thiserror 2.0.21",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"windows-sys 0.61.2",
|
||||
"zbus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-runtime"
|
||||
version = "2.12.1"
|
||||
|
||||
@@ -22,6 +22,7 @@ tauri-plugin-opener = "2"
|
||||
tauri-plugin-global-shortcut = "2"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
sha2 = "0.10"
|
||||
tempfile = "3"
|
||||
searchi-core = { path = "../../../crates/searchi-core" }
|
||||
searchi-service = { path = "../../../crates/searchi-service" }
|
||||
@@ -30,6 +31,7 @@ searchi-media = { path = "../../../crates/searchi-media" }
|
||||
searchi-ai = { path = "../../../crates/searchi-ai" }
|
||||
uuid = { version = "1", features = ["v4"] }
|
||||
image = { version = "0.25", default-features = false, features = ["jpeg", "png", "webp", "bmp"] }
|
||||
tauri-plugin-single-instance = "2"
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
window-vibrancy = "=0.8.1"
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
//! Official single-instance transport, scoped to an explicitly selected profile.
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::{ffi::OsString, io, path::PathBuf};
|
||||
use tauri::{Emitter, Manager};
|
||||
|
||||
pub fn profile_identifier(base: &str, profile: Option<OsString>) -> io::Result<String> {
|
||||
let Some(profile) = profile else {
|
||||
return Ok(base.to_owned());
|
||||
};
|
||||
let path = crate::data_directory::resolve(PathBuf::new(), Some(profile))?;
|
||||
// Resolve the existing ancestor without creating a first-use profile.
|
||||
let canonical = crate::data_directory::canonical_exclusion(&path)?;
|
||||
let mut digest = Sha256::new();
|
||||
#[cfg(windows)]
|
||||
{
|
||||
use std::os::windows::ffi::OsStrExt;
|
||||
for unit in canonical.as_os_str().encode_wide() {
|
||||
digest.update(unit.to_le_bytes());
|
||||
}
|
||||
}
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
digest.update(canonical.as_os_str().as_bytes());
|
||||
}
|
||||
Ok(format!("{base}.profile{:x}", digest.finalize()))
|
||||
}
|
||||
|
||||
pub fn focus_search(app: &tauri::AppHandle) {
|
||||
if let Some(window) = app.get_webview_window("main") {
|
||||
let result = window
|
||||
.show()
|
||||
.and_then(|_| window.unminimize())
|
||||
.and_then(|_| window.set_focus());
|
||||
if let Err(error) = result {
|
||||
eprintln!("Could not focus searchi: {error}");
|
||||
}
|
||||
let _ = window.emit("focus-search", ());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
#[test]
|
||||
fn default_identity_is_unchanged_and_invalid_profile_rejected() {
|
||||
assert_eq!(
|
||||
profile_identifier("dev.searchi.desktop", None).unwrap(),
|
||||
"dev.searchi.desktop"
|
||||
);
|
||||
assert!(profile_identifier("dev.searchi.desktop", Some("relative".into())).is_err());
|
||||
}
|
||||
#[test]
|
||||
fn canonical_profiles_are_stable_distinct_and_do_not_expose_paths() {
|
||||
let root = tempfile::tempdir().unwrap();
|
||||
let first = root.path().join("Gemäß_日本_😀");
|
||||
let second = root.path().join("other");
|
||||
let id = profile_identifier("dev.searchi.desktop", Some(first.clone().into_os_string()))
|
||||
.unwrap();
|
||||
assert!(!first.exists());
|
||||
std::fs::create_dir(&first).unwrap();
|
||||
assert_eq!(
|
||||
id,
|
||||
profile_identifier(
|
||||
"dev.searchi.desktop",
|
||||
Some(first.join(".").into_os_string())
|
||||
)
|
||||
.unwrap()
|
||||
);
|
||||
assert_ne!(
|
||||
id,
|
||||
profile_identifier("dev.searchi.desktop", Some(second.into_os_string())).unwrap()
|
||||
);
|
||||
assert!(!id.contains("Gemäß"));
|
||||
assert_eq!(id.len(), "dev.searchi.desktop.profile".len() + 64);
|
||||
assert!(!root.path().join("other").exists());
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ compile_error!("Release desktop builds require --features custom-protocol to emb
|
||||
mod ai_commands;
|
||||
mod appearance;
|
||||
mod data_directory;
|
||||
mod instance;
|
||||
mod model_download;
|
||||
mod semantic_commands;
|
||||
mod settings;
|
||||
@@ -545,16 +546,22 @@ fn start_folder_watching(
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let mut context = tauri::generate_context!();
|
||||
context.config_mut().identifier = instance::profile_identifier(
|
||||
&context.config().identifier,
|
||||
std::env::var_os("SEARCHI_DATA_DIR"),
|
||||
)
|
||||
.expect("searchi profile identity could not be resolved");
|
||||
tauri::Builder::default()
|
||||
// Detect duplicates before any model, database, watcher or other plugin starts.
|
||||
.plugin(tauri_plugin_single_instance::init(|app, _args, _cwd| {
|
||||
instance::focus_search(app);
|
||||
}))
|
||||
.plugin(tauri_plugin_dialog::init())
|
||||
.plugin(tauri_plugin_opener::init())
|
||||
.plugin(tauri_plugin_global_shortcut::Builder::new().with_handler(|app, _, event| {
|
||||
if event.state == ShortcutState::Pressed {
|
||||
if let Some(window) = app.get_webview_window("main") {
|
||||
let result = window.show().and_then(|_| window.unminimize()).and_then(|_| window.set_focus());
|
||||
if let Err(error) = result { eprintln!("Could not focus Searchi: {error}"); }
|
||||
let _ = window.emit("focus-search", ());
|
||||
}
|
||||
instance::focus_search(app);
|
||||
}
|
||||
}).build())
|
||||
.setup(|app| {
|
||||
@@ -687,7 +694,7 @@ fn main() {
|
||||
hybrid_search,
|
||||
hybrid_search_measured
|
||||
])
|
||||
.build(tauri::generate_context!())
|
||||
.build(context)
|
||||
.expect("Searchi desktop could not start")
|
||||
.run(|app, event| {
|
||||
if matches!(event, tauri::RunEvent::Exit) {
|
||||
|
||||
Reference in new issue
Block a user