Skip to main content

nagisa_core/
enabled.rs

1//! 命令/事件启用/禁用状态(让 `TriggerMeta`/`PluginMeta` 的 `can_disable`/`default_enable`
2//! 在运行期生效;分层:插件总开关 + 触发器子开关,总闸 OFF ⇒ 内部全部 OFF)。
3//!
4//! 提供 `enable()`/`disable()` 与按会话(群)开关。一条命令对某会话默认
5//! 启用与否取决于其 `default_enable`;可被全局覆盖或按 `Peer` 覆盖。`can_disable=false`
6//! 的命令永远启用(在 dispatch 处直接短路,不查本表)。
7//!
8//! 运行期可变:内部 `RwLock`。通常作为共享状态注入(`App::new` 自动注册一个空表),
9//! 业务侧用 `State<EnabledSet>` 取句柄做 `/enable`、`/disable` 之类管理命令。
10use crate::plugin::SwitchKey;
11use nagisa_types::id::Peer;
12use serde::{Deserialize, Serialize};
13use std::collections::HashMap;
14use std::sync::{Mutex, RwLock};
15
16#[derive(Default)]
17struct Inner {
18    /// 命令名 → 全局启用覆盖。
19    global: HashMap<String, bool>,
20    /// (命令名, 会话) → 启用覆盖(优先于全局)。
21    per_peer: HashMap<(String, Peer), bool>,
22}
23
24/// 所有覆盖的可序列化快照。`per_peer` 用扁平列表(绕开「map 键非字符串」的序列化难题)。
25#[derive(Clone, Default, Serialize, Deserialize)]
26pub struct EnabledOverrides {
27    pub global: HashMap<String, bool>,
28    pub per_peer: Vec<(String, Peer, bool)>,
29}
30
31type ChangeFn = dyn Fn(&str, Option<Peer>, bool) + Send + Sync;
32
33/// 命令启用状态表。廉价共享(内部锁),运行期可改。
34pub struct EnabledSet {
35    inner: RwLock<Inner>,
36    on_change: Mutex<Option<Box<ChangeFn>>>,
37}
38
39impl EnabledSet {
40    pub fn new() -> Self {
41        Self { inner: RwLock::new(Inner::default()), on_change: Mutex::new(None) }
42    }
43
44    /// 设置某命令的启用状态:`peer=None` 为全局覆盖,`Some(p)` 为按会话覆盖。
45    /// 在释放写锁后触发 `on_change` 回调。
46    ///
47    /// 同时接受纯字符串键与类型化的 [`SwitchKey`](`#[command]`/`#[event]` 宏产出的 `<FN>_KEY`
48    /// 常量),故触发器键无需手敲字符串。
49    pub fn set(&self, key: impl Into<SwitchKey>, peer: Option<Peer>, enabled: bool) {
50        let name = key.into().resolve();
51        {
52            let mut g = self.inner.write().unwrap_or_else(|e| e.into_inner());
53            match peer {
54                None => {
55                    g.global.insert(name.to_string(), enabled);
56                }
57                Some(p) => {
58                    g.per_peer.insert((name.to_string(), p), enabled);
59                }
60            }
61        }
62        self.fire(name, peer, enabled);
63    }
64
65    /// 清除某命令的覆盖,回到 `default_enable`:`peer=None` 清全局,`Some` 清该会话。
66    pub fn reset(&self, key: impl Into<SwitchKey>, peer: Option<Peer>) {
67        let name = key.into().resolve();
68        let mut g = self.inner.write().unwrap_or_else(|e| e.into_inner());
69        match peer {
70            None => {
71                g.global.remove(name);
72            }
73            Some(p) => {
74                g.per_peer.remove(&(name.to_string(), p));
75            }
76        }
77    }
78
79    /// 在某作用域解析一个键:按会话覆盖 > 全局覆盖 > `default`。
80    fn key_on(&self, key: &str, default: bool, peer: Option<Peer>) -> bool {
81        let g = self.inner.read().unwrap_or_else(|e| e.into_inner());
82        if let Some(p) = peer {
83            if let Some(v) = g.per_peer.get(&(key.to_string(), p)) {
84                return *v;
85            }
86        }
87        if let Some(v) = g.global.get(key) {
88            return *v;
89        }
90        default
91    }
92
93    /// 分层的触发器判定:
94    /// 1. 触发器 `can_disable=false` → 恒开(豁免,无视总开关);
95    /// 2. 插件总开关 OFF(且插件 can_disable)→ 关(一刀压平);
96    /// 3. 否则看触发器自身的开关。
97    #[allow(clippy::too_many_arguments)]
98    pub fn is_enabled_keyed(
99        &self,
100        plugin_key: &str,
101        trigger_key: &str,
102        plugin_default: bool,
103        plugin_can_disable: bool,
104        trigger_default: bool,
105        trigger_can_disable: bool,
106        peer: Option<Peer>,
107    ) -> bool {
108        if !trigger_can_disable {
109            return true;
110        }
111        if plugin_can_disable && !self.key_on(plugin_key, plugin_default, peer) {
112            return false;
113        }
114        self.key_on(trigger_key, trigger_default, peer)
115    }
116
117    /// 单键查询(无插件层适用处用它)。
118    pub fn is_enabled(&self, name: &str, default_enable: bool, peer: Option<Peer>) -> bool {
119        self.key_on(name, default_enable, peer)
120    }
121
122    /// 注册一个在每次 `set` 调用后(锁释放后)触发的回调。只能注册一个,再次调用替换前一个。
123    /// `reset` 不触发回调(它是本地回退,不是需持久化的变更)。
124    pub fn on_change<F>(&self, f: F)
125    where
126        F: Fn(&str, Option<Peer>, bool) + Send + Sync + 'static,
127    {
128        *self.on_change.lock().unwrap_or_else(|e| e.into_inner()) = Some(Box::new(f));
129    }
130
131    fn fire(&self, key: &str, peer: Option<Peer>, enabled: bool) {
132        if let Some(cb) = self.on_change.lock().unwrap_or_else(|e| e.into_inner()).as_ref() {
133            cb(key, peer, enabled);
134        }
135    }
136
137    /// 把当前所有覆盖快照成一个可序列化结构。
138    pub fn snapshot(&self) -> EnabledOverrides {
139        let g = self.inner.read().unwrap_or_else(|e| e.into_inner());
140        EnabledOverrides {
141            global: g.global.clone(),
142            per_peer: g.per_peer.iter().map(|((k, p), v)| (k.clone(), *p, *v)).collect(),
143        }
144    }
145
146    /// 从先前快照的结构恢复覆盖(替换当前状态)。
147    pub fn restore(&self, ov: EnabledOverrides) {
148        let mut g = self.inner.write().unwrap_or_else(|e| e.into_inner());
149        g.global = ov.global;
150        g.per_peer = ov.per_peer.into_iter().map(|(k, p, v)| ((k, p), v)).collect();
151    }
152}
153
154impl Default for EnabledSet {
155    fn default() -> Self {
156        Self::new()
157    }
158}