use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::mpsc::Sender;
use std::sync::{Mutex, Once, PoisonError};
use crate::Waker;
struct Route {
key: u64,
prefix: String,
tx: Sender<String>,
waker: Waker,
}
static ROUTES: Mutex<Vec<Route>> = Mutex::new(Vec::new());
static NEXT: AtomicU64 = AtomicU64::new(1);
static INSTALL: Once = Once::new();
pub(crate) struct Subscription(u64);
impl Drop for Subscription {
fn drop(&mut self) {
ROUTES.lock().unwrap_or_else(PoisonError::into_inner).retain(|r| r.key != self.0);
}
}
pub(crate) fn owner<'a>(prefixes: impl IntoIterator<Item = &'a str>, id: &str) -> Option<usize> {
let mut best: Option<(usize, usize)> = None;
for (i, p) in prefixes.into_iter().enumerate() {
if id.starts_with(p) && best.is_none_or(|(_, len)| p.len() >= len) {
best = Some((i, p.len()));
}
}
best.map(|(i, _)| i)
}
fn dispatch(id: &str) {
let routes = ROUTES.lock().unwrap_or_else(PoisonError::into_inner);
let Some(r) = owner(routes.iter().map(|r| r.prefix.as_str()), id).and_then(|i| routes.get(i)) else { return };
let rest = id.get(r.prefix.len()..).unwrap_or_default().to_string();
let (tx, waker) = (r.tx.clone(), r.waker.clone());
drop(routes);
if tx.send(rest).is_ok() {
waker();
}
}
pub(crate) fn subscribe(prefix: impl Into<String>, tx: Sender<String>, waker: Waker) -> Subscription {
INSTALL.call_once(|| {
muda::MenuEvent::set_event_handler(Some(|e: muda::MenuEvent| {
let _ = std::panic::catch_unwind(|| dispatch(&e.id().0));
}));
});
let key = NEXT.fetch_add(1, Ordering::Relaxed);
ROUTES.lock().unwrap_or_else(PoisonError::into_inner).push(Route { key, prefix: prefix.into(), tx, waker });
Subscription(key)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_longest_prefix_owns_an_id_and_the_newest_wins_a_tie() {
assert_eq!(owner(["", "\u{1}tray/a/"], "\u{1}tray/a/quit"), Some(1));
assert_eq!(owner(["", "\u{1}tray/a/"], "save"), Some(0));
assert_eq!(owner(["", ""], "save"), Some(1));
assert_eq!(owner(["\u{1}tray/a/"], "save"), None);
assert_eq!(owner([], "save"), None);
}
#[test]
fn deliveries_strip_the_prefix_and_stop_on_drop() {
let (tx, rx) = std::sync::mpsc::channel();
let sub = subscribe("\u{1}test-route/", tx, crate::no_waker());
dispatch("\u{1}test-route/open");
assert_eq!(rx.try_recv().ok().as_deref(), Some("open"));
drop(sub);
dispatch("\u{1}test-route/open");
assert!(rx.try_recv().is_err());
}
}