78 lines
2.7 KiB
Rust
78 lines
2.7 KiB
Rust
//! 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());
|
||
|
|
}
|
||
|
|
}
|