resolve live data on initial load

This commit is contained in:
2026-08-31 01:15:32 +08:00
parent c68f8f0bf7
commit 25eb21755f
10 changed files with 293 additions and 50 deletions
+101 -15
View File
@@ -2,6 +2,7 @@
UFA: User Focused App
*/
use std::collections::HashMap;
use std::sync::RwLock;
use tauri::{AppHandle, Manager};
use tauri_specta::Event;
@@ -27,7 +28,47 @@ pub struct FriendForegroundAppChanged {
}
#[derive(Default)]
pub struct ForegroundAppState(RwLock<AppMeta>);
pub struct ForegroundAppState(RwLock<HashMap<String, AppMeta>>);
impl ForegroundAppState {
fn update(&self, user_id: String, meta: AppMeta) -> Result<(), String> {
self.0
.write()
.map_err(|error| error.to_string())?
.insert(user_id, meta);
Ok(())
}
pub(crate) fn remove(&self, user_ids: &[String]) -> Result<(), String> {
let mut apps = self.0.write().map_err(|error| error.to_string())?;
apps.retain(|user_id, _| !user_ids.contains(user_id));
Ok(())
}
pub(crate) fn snapshot(&self) -> Result<HashMap<String, AppMeta>, String> {
self.0
.read()
.map(|apps| apps.clone())
.map_err(|error| error.to_string())
}
pub(crate) fn get(&self, user_id: &str) -> Result<Option<AppMeta>, String> {
self.0
.read()
.map(|apps| apps.get(user_id).cloned())
.map_err(|error| error.to_string())
}
}
fn current_app() -> AppMeta {
#[cfg(target_os = "macos")]
let meta = macos::get_active_app_metadata_macos();
#[cfg(target_os = "windows")]
let meta = windows::get_active_app_metadata_windows(None);
#[cfg(not(any(target_os = "macos", target_os = "windows")))]
let meta = AppMeta::default();
meta
}
/// Listens for changes in the active (foreground) application and calls the provided callback with metadata.
/// The implementation varies by platform: macOS uses NSWorkspace notifications, Windows uses WinEventHook.
@@ -66,26 +107,71 @@ where
/// and emits events to the Tauri app on changes.
/// Used for app to emit user foreground app to peers.
pub fn init(handle: &AppHandle) {
update_local_app(handle, current_app());
let handle = handle.clone();
init_listener(move |meta: AppMeta| {
let state = handle.state::<ForegroundAppState>();
let mut current = state.0.write().expect("Foreground App lock failed");
*current = meta.clone();
drop(current);
if let Err(error) = (ForegroundAppChanged { meta: meta.clone() }).emit(&handle) {
eprintln!("Failed to emit foreground app change: {error}");
}
handle
.state::<crate::network::Network>()
.send_live_data(crate::live_data::LiveData::ForegroundApp { meta });
});
init_listener(move |meta: AppMeta| update_local_app(&handle, meta));
}
pub(crate) fn emit_friend_app(handle: &AppHandle, friend_id: String, meta: AppMeta) {
if let Err(error) = handle
.state::<ForegroundAppState>()
.update(friend_id.clone(), meta.clone())
{
eprintln!("Failed to cache friend foreground app: {error}");
return;
}
if let Err(error) = (FriendForegroundAppChanged { friend_id, meta }).emit(handle) {
eprintln!("Failed to emit friend foreground app change: {error}");
}
}
fn update_local_app(handle: &AppHandle, meta: AppMeta) {
let user_id = handle
.state::<crate::keypair::AppKeypair>()
.public_key()
.to_owned();
if let Err(error) = handle
.state::<ForegroundAppState>()
.update(user_id, meta.clone())
{
eprintln!("Failed to cache foreground app: {error}");
return;
}
if let Err(error) = (ForegroundAppChanged { meta: meta.clone() }).emit(handle) {
eprintln!("Failed to emit foreground app change: {error}");
}
handle
.state::<crate::network::Network>()
.send_live_data(crate::live_data::LiveData::ForegroundApp { meta });
}
#[cfg(test)]
mod tests {
use super::*;
fn app(name: &str) -> AppMeta {
AppMeta {
local: Some(name.to_owned()),
unlocal: None,
ico: None,
}
}
#[test]
fn foreground_app_state_snapshots_and_removes_users() {
let state = ForegroundAppState::default();
state.update("local".to_owned(), app("Local")).unwrap();
state.update("friend".to_owned(), app("Friend")).unwrap();
assert_eq!(
state.get("friend").unwrap().unwrap().local.as_deref(),
Some("Friend")
);
state.remove(&["friend".to_owned()]).unwrap();
let snapshot = state.snapshot().unwrap();
assert_eq!(snapshot.len(), 1);
assert_eq!(snapshot["local"].local.as_deref(), Some("Local"));
}
}