use std::collections::HashMap;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, Mutex};
use serde_json::Value;
use tauri::ipc::Channel;
pub const ENGINE_LABEL: &str = "deeprespond-im-engine";
pub struct Instance {
pub label: String,
pub id: u64,
pub engine: bool,
channel: Channel<Value>,
}
impl Instance {
pub fn send(&self, event: Value) {
if let Err(err) = self.channel.send(event) {
log::debug!("向页面 {} 发送事件失败:{err}", self.label);
}
}
}
#[derive(Default)]
pub struct Hub {
instances: Mutex<HashMap<String, Arc<Instance>>>,
next: AtomicU64,
}
impl Hub {
pub fn attach(&self, label: &str, channel: Channel<Value>) -> (Arc<Instance>, Option<Arc<Instance>>) {
let id = self.next.fetch_add(1, Ordering::Relaxed) + 1;
let instance = Arc::new(Instance { label: label.to_string(), id, engine: label == ENGINE_LABEL, channel });
let old = self.instances.lock().unwrap().insert(label.to_string(), instance.clone());
(instance, old)
}
pub fn detach(&self, label: &str) -> Option<Arc<Instance>> {
self.instances.lock().unwrap().remove(label)
}
pub fn get(&self, label: &str) -> Option<Arc<Instance>> {
self.instances.lock().unwrap().get(label).cloned()
}
pub fn is_current(&self, label: &str, id: u64) -> bool {
self.instances.lock().unwrap().get(label).is_some_and(|i| i.id == id)
}
pub fn windows(&self) -> Vec<Arc<Instance>> {
self.instances.lock().unwrap().values().filter(|i| !i.engine).cloned().collect()
}
pub fn all(&self) -> Vec<Arc<Instance>> {
self.instances.lock().unwrap().values().cloned().collect()
}
pub fn send(&self, label: &str, event: Value) -> bool {
match self.get(label) {
Some(instance) => {
instance.send(event);
true
}
None => false,
}
}
pub fn send_engine(&self, event: Value) -> bool {
self.send(ENGINE_LABEL, event)
}
pub fn broadcast(&self, event: Value) {
for instance in self.all() {
instance.send(event.clone());
}
}
pub fn broadcast_windows(&self, event: Value) {
for instance in self.windows() {
instance.send(event.clone());
}
}
}
#[cfg(test)]
impl Instance {
pub fn for_test(label: &str, id: u64) -> Self {
Self { label: label.to_string(), id, engine: label == ENGINE_LABEL, channel: Channel::new(|_| Ok(())) }
}
pub fn with_sink(label: &str, id: u64, sink: std::sync::Arc<Mutex<Vec<Value>>>) -> Self {
let channel = Channel::new(move |body| {
if let tauri::ipc::InvokeResponseBody::Json(text) = body {
sink.lock().unwrap().push(serde_json::from_str(&text).unwrap_or(Value::Null));
}
Ok(())
});
Self { label: label.to_string(), id, engine: label == ENGINE_LABEL, channel }
}
}
#[cfg(test)]
impl Hub {
pub fn insert_for_test(&self, instance: Instance) -> Arc<Instance> {
let instance = Arc::new(instance);
self.instances.lock().unwrap().insert(instance.label.clone(), instance.clone());
instance
}
}