tauri-plugin-deeprespond-im 1.0.0-alpha

深应智能 IM 的 Tauri 插件:本地存储、安全存储、窗口间的锁与消息、系统事件、通知、角标、文件
//! 已连接的页面(引擎页和各界面窗口):每个 WebView 的 JavaScript 在第一个命令 hello 中交来一个 Channel,
//! 插件经它把事件(频道消息、锁被抢走、系统事件、通知点击、传输进度……)按顺序交给这个页面。
//! 页面重新加载后再次 hello,旧的实例作废,它持有的锁、事务等由调用方释放。

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,
    /// 每次 hello 递增:区分同一窗口重新加载前后的页面。
    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()
    }

    /// 这个窗口当前的实例是不是 id(命令中带来的实例过期时拒绝)。
    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 {
    /// 测试用:事件交给 sink(为 None 时丢弃)。
    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
    }
}