origin_platform/
memory_watcher.rs1use crate::watcher::{BUFFER_SIZE, WatchHandle, WorkspaceChange, WorkspaceWatcher};
6use crate::workspace::WorkspaceRoot;
7use async_trait::async_trait;
8use origin_domain::Result;
9use std::collections::HashMap;
10use std::sync::Mutex;
11use tokio::sync::broadcast;
12
13#[derive(Debug, Default)]
15pub struct MemoryWorkspaceWatcher {
16 senders: Mutex<HashMap<WorkspaceRoot, broadcast::Sender<WorkspaceChange>>>,
17}
18
19impl MemoryWorkspaceWatcher {
20 pub fn new() -> Self {
21 Self::default()
22 }
23
24 pub fn emit(&self, root: &WorkspaceRoot, change: WorkspaceChange) -> usize {
27 let senders = self.senders.lock().expect("poisoned");
28 match senders.get(root) {
29 Some(sender) => sender.send(change).unwrap_or(0),
30 None => 0,
31 }
32 }
33}
34
35#[async_trait]
36impl WorkspaceWatcher for MemoryWorkspaceWatcher {
37 async fn watch(&self, root: &WorkspaceRoot) -> Result<WatchHandle> {
38 let mut senders = self.senders.lock().expect("poisoned");
39 let sender = match senders.get(root) {
40 Some(existing) => existing.clone(),
41 None => {
42 let (tx, _) = broadcast::channel(BUFFER_SIZE);
43 senders.insert(root.clone(), tx.clone());
44 tx
45 }
46 };
47
48 Ok(WatchHandle::new(sender.subscribe()))
49 }
50}
51
52#[cfg(test)]
53mod tests {
54 use super::*;
55 use std::path::Path;
56
57 #[tokio::test]
58 async fn watching_twice_returns_different_handles_same_channel() {
59 let watcher = MemoryWorkspaceWatcher::new();
60 let root = WorkspaceRoot::new(Path::new("/repo").to_path_buf()).unwrap();
61
62 let mut handle_a = watcher.watch(&root).await.unwrap();
63 let mut handle_b = watcher.watch(&root).await.unwrap();
64
65 watcher.emit(&root, WorkspaceChange::modified("README.md"));
66
67 assert_eq!(handle_a.recv().await.unwrap().path(), "README.md");
68 assert_eq!(handle_b.recv().await.unwrap().path(), "README.md");
69 }
70
71 #[tokio::test]
72 async fn different_roots_get_different_channels() {
73 let watcher = MemoryWorkspaceWatcher::new();
74 let root_a = WorkspaceRoot::new(Path::new("/a").to_path_buf()).unwrap();
75 let root_b = WorkspaceRoot::new(Path::new("/b").to_path_buf()).unwrap();
76
77 let mut handle_a = watcher.watch(&root_a).await.unwrap();
78 let mut handle_b = watcher.watch(&root_b).await.unwrap();
79
80 watcher.emit(&root_a, WorkspaceChange::modified("a.txt"));
82 watcher.emit(&root_b, WorkspaceChange::modified("b.txt"));
84
85 let a_event = handle_a.try_recv().unwrap();
86 let b_event = handle_b.try_recv().unwrap();
87
88 assert_eq!(a_event.path(), "a.txt");
89 assert_eq!(b_event.path(), "b.txt");
90
91 assert!(handle_a.try_recv().is_err());
93 }
94
95 #[tokio::test]
96 async fn emitting_to_an_unwatched_root_delivers_to_no_one() {
97 let watcher = MemoryWorkspaceWatcher::new();
98 let root = WorkspaceRoot::new(Path::new("/unwatched").to_path_buf()).unwrap();
99
100 let delivered = watcher.emit(&root, WorkspaceChange::created("nope.txt"));
101 assert_eq!(delivered, 0);
102 }
103}