Skip to main content

oxihuman_core/
signal_handler.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Named signal handler registry for hooking OS-style signals or custom events.
6
7use std::collections::HashMap;
8
9/// A registered handler entry.
10#[allow(dead_code)]
11#[derive(Debug, Clone)]
12pub struct HandlerEntry {
13    pub name: String,
14    pub signal: String,
15    pub priority: i32,
16    pub enabled: bool,
17    pub call_count: u64,
18}
19
20/// Registry of named signal handlers.
21#[allow(dead_code)]
22pub struct SignalHandler {
23    handlers: HashMap<String, HandlerEntry>,
24    dispatch_log: Vec<(String, u64)>,
25    total_dispatched: u64,
26}
27
28#[allow(dead_code)]
29impl SignalHandler {
30    pub fn new() -> Self {
31        Self {
32            handlers: HashMap::new(),
33            dispatch_log: Vec::new(),
34            total_dispatched: 0,
35        }
36    }
37
38    /// Register a handler for a signal.
39    pub fn register(&mut self, name: &str, signal: &str, priority: i32) {
40        self.handlers.insert(
41            name.to_string(),
42            HandlerEntry {
43                name: name.to_string(),
44                signal: signal.to_string(),
45                priority,
46                enabled: true,
47                call_count: 0,
48            },
49        );
50    }
51
52    /// Unregister a handler.
53    pub fn unregister(&mut self, name: &str) -> bool {
54        self.handlers.remove(name).is_some()
55    }
56
57    /// Enable or disable a handler.
58    pub fn set_enabled(&mut self, name: &str, enabled: bool) -> bool {
59        if let Some(h) = self.handlers.get_mut(name) {
60            h.enabled = enabled;
61            true
62        } else {
63            false
64        }
65    }
66
67    /// Dispatch a signal; calls all enabled handlers sorted by priority desc.
68    pub fn dispatch(&mut self, signal: &str, timestamp: u64) -> usize {
69        self.total_dispatched += 1;
70        self.dispatch_log.push((signal.to_string(), timestamp));
71        let mut count = 0;
72        let names: Vec<String> = self.handlers.keys().cloned().collect();
73        let mut sorted: Vec<(i32, String)> = names
74            .iter()
75            .filter_map(|n| {
76                let h = &self.handlers[n];
77                if h.signal == signal && h.enabled {
78                    Some((h.priority, n.clone()))
79                } else {
80                    None
81                }
82            })
83            .collect();
84        sorted.sort_by(|a, b| b.0.cmp(&a.0));
85        for (_, name) in sorted {
86            if let Some(h) = self.handlers.get_mut(&name) {
87                h.call_count += 1;
88                count += 1;
89            }
90        }
91        count
92    }
93
94    pub fn get(&self, name: &str) -> Option<&HandlerEntry> {
95        self.handlers.get(name)
96    }
97
98    pub fn handler_count(&self) -> usize {
99        self.handlers.len()
100    }
101
102    pub fn total_dispatched(&self) -> u64 {
103        self.total_dispatched
104    }
105
106    pub fn dispatch_log_len(&self) -> usize {
107        self.dispatch_log.len()
108    }
109
110    pub fn clear_log(&mut self) {
111        self.dispatch_log.clear();
112    }
113
114    pub fn clear_all(&mut self) {
115        self.handlers.clear();
116        self.dispatch_log.clear();
117        self.total_dispatched = 0;
118    }
119
120    pub fn is_empty(&self) -> bool {
121        self.handlers.is_empty()
122    }
123}
124
125impl Default for SignalHandler {
126    fn default() -> Self {
127        Self::new()
128    }
129}
130
131pub fn new_signal_handler() -> SignalHandler {
132    SignalHandler::new()
133}
134
135#[cfg(test)]
136mod tests {
137    use super::*;
138
139    #[test]
140    fn register_and_dispatch() {
141        let mut sh = new_signal_handler();
142        sh.register("h1", "resize", 0);
143        let count = sh.dispatch("resize", 100);
144        assert_eq!(count, 1);
145    }
146
147    #[test]
148    fn unregister_removes() {
149        let mut sh = new_signal_handler();
150        sh.register("h1", "exit", 0);
151        assert!(sh.unregister("h1"));
152        assert_eq!(sh.dispatch("exit", 0), 0);
153    }
154
155    #[test]
156    fn disabled_handler_not_called() {
157        let mut sh = new_signal_handler();
158        sh.register("h1", "pause", 5);
159        sh.set_enabled("h1", false);
160        assert_eq!(sh.dispatch("pause", 1), 0);
161    }
162
163    #[test]
164    fn re_enable_handler() {
165        let mut sh = new_signal_handler();
166        sh.register("h1", "resume", 1);
167        sh.set_enabled("h1", false);
168        sh.set_enabled("h1", true);
169        assert_eq!(sh.dispatch("resume", 1), 1);
170    }
171
172    #[test]
173    fn call_count_increments() {
174        let mut sh = new_signal_handler();
175        sh.register("h1", "tick", 0);
176        sh.dispatch("tick", 1);
177        sh.dispatch("tick", 2);
178        assert_eq!(sh.get("h1").expect("should succeed").call_count, 2);
179    }
180
181    #[test]
182    fn total_dispatched_increments() {
183        let mut sh = new_signal_handler();
184        sh.dispatch("x", 0);
185        sh.dispatch("x", 1);
186        assert_eq!(sh.total_dispatched(), 2);
187    }
188
189    #[test]
190    fn handler_count() {
191        let mut sh = new_signal_handler();
192        sh.register("a", "s", 0);
193        sh.register("b", "s", 0);
194        assert_eq!(sh.handler_count(), 2);
195    }
196
197    #[test]
198    fn wrong_signal_not_dispatched() {
199        let mut sh = new_signal_handler();
200        sh.register("h1", "usr1", 0);
201        assert_eq!(sh.dispatch("usr2", 0), 0);
202    }
203
204    #[test]
205    fn clear_all() {
206        let mut sh = new_signal_handler();
207        sh.register("h1", "s", 0);
208        sh.dispatch("s", 0);
209        sh.clear_all();
210        assert!(sh.is_empty());
211        assert_eq!(sh.total_dispatched(), 0);
212    }
213
214    #[test]
215    fn clear_log() {
216        let mut sh = new_signal_handler();
217        sh.dispatch("s", 1);
218        assert_eq!(sh.dispatch_log_len(), 1);
219        sh.clear_log();
220        assert_eq!(sh.dispatch_log_len(), 0);
221    }
222}