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
56 #[tokio::test]
57 async fn watching_twice_returns_different_handles_same_channel() {
58 let watcher = MemoryWorkspaceWatcher::new();
59 let root = WorkspaceRoot::new(std::env::temp_dir().join("repo")).unwrap();
60
61 let mut handle_a = watcher.watch(&root).await.unwrap();
62 let mut handle_b = watcher.watch(&root).await.unwrap();
63
64 watcher.emit(&root, WorkspaceChange::modified("README.md"));
65
66 assert_eq!(handle_a.recv().await.unwrap().path(), "README.md");
67 assert_eq!(handle_b.recv().await.unwrap().path(), "README.md");
68 }
69
70 #[tokio::test]
71 async fn different_roots_get_different_channels() {
72 let watcher = MemoryWorkspaceWatcher::new();
73 let root_a = WorkspaceRoot::new(std::env::temp_dir().join("a")).unwrap();
74 let root_b = WorkspaceRoot::new(std::env::temp_dir().join("b")).unwrap();
75
76 let mut handle_a = watcher.watch(&root_a).await.unwrap();
77 let mut handle_b = watcher.watch(&root_b).await.unwrap();
78
79 watcher.emit(&root_a, WorkspaceChange::modified("a.txt"));
81 watcher.emit(&root_b, WorkspaceChange::modified("b.txt"));
83
84 let a_event = handle_a.try_recv().unwrap();
85 let b_event = handle_b.try_recv().unwrap();
86
87 assert_eq!(a_event.path(), "a.txt");
88 assert_eq!(b_event.path(), "b.txt");
89
90 assert!(handle_a.try_recv().is_err());
92 }
93
94 #[tokio::test]
95 async fn emitting_to_an_unwatched_root_delivers_to_no_one() {
96 let watcher = MemoryWorkspaceWatcher::new();
97 let root = WorkspaceRoot::new(std::env::temp_dir().join("unwatched")).unwrap();
98
99 let delivered = watcher.emit(&root, WorkspaceChange::created("nope.txt"));
100 assert_eq!(delivered, 0);
101 }
102}