@browsersync/bslive 0.0.5 → 0.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) hide show
  1. package/Cargo.toml +32 -18
  2. package/bin.js +6 -0
  3. package/bslive/Cargo.toml +35 -0
  4. package/{build.rs → bslive/build.rs} +1 -1
  5. package/bslive/src/lib.rs +130 -0
  6. package/bsnext/Cargo.toml +21 -0
  7. package/bsnext/src/main.rs +110 -0
  8. package/crates/bsnext_client/Cargo.toml +8 -0
  9. package/crates/bsnext_client/build.rs +53 -0
  10. package/crates/bsnext_client/dist/index.js +6493 -0
  11. package/crates/bsnext_client/generated/dto.ts +116 -0
  12. package/crates/bsnext_client/generated/schema.ts +187 -0
  13. package/crates/bsnext_client/index.html +14 -0
  14. package/crates/bsnext_client/package-lock.json +2059 -0
  15. package/crates/bsnext_client/package.json +25 -0
  16. package/crates/bsnext_client/src/lib.rs +1 -0
  17. package/crates/bsnext_client/style.css +3 -0
  18. package/crates/bsnext_client/ts/console.ts +25 -0
  19. package/crates/bsnext_client/ts/index.ts +73 -0
  20. package/crates/bsnext_client/tsconfig.json +16 -0
  21. package/crates/bsnext_core/Cargo.toml +43 -0
  22. package/crates/bsnext_core/src/dir_loader.rs +230 -0
  23. package/crates/bsnext_core/src/dto.rs +281 -0
  24. package/crates/bsnext_core/src/handlers/mod.rs +1 -0
  25. package/crates/bsnext_core/src/handlers/proxy.rs +95 -0
  26. package/crates/bsnext_core/src/lib.rs +11 -0
  27. package/crates/bsnext_core/src/meta/mod.rs +5 -0
  28. package/crates/bsnext_core/src/not_found/mod.rs +2 -0
  29. package/crates/bsnext_core/src/not_found/not_found.html +20 -0
  30. package/crates/bsnext_core/src/not_found/not_found_service.rs +41 -0
  31. package/crates/bsnext_core/src/not_found/route_list.rs +49 -0
  32. package/crates/bsnext_core/src/panic_handler.rs +32 -0
  33. package/crates/bsnext_core/src/raw_loader.rs +226 -0
  34. package/crates/bsnext_core/src/server/actor.rs +92 -0
  35. package/crates/bsnext_core/src/server/error.rs +41 -0
  36. package/crates/bsnext_core/src/server/handler_change.rs +85 -0
  37. package/crates/bsnext_core/src/server/handler_listen.rs +157 -0
  38. package/crates/bsnext_core/src/server/handler_patch.rs +42 -0
  39. package/crates/bsnext_core/src/server/handler_routes_updated.rs +27 -0
  40. package/crates/bsnext_core/src/server/handler_stop.rs +38 -0
  41. package/crates/bsnext_core/src/server/mod.rs +10 -0
  42. package/crates/bsnext_core/src/server/router/mod.rs +112 -0
  43. package/crates/bsnext_core/src/server/router/tests.rs +204 -0
  44. package/crates/bsnext_core/src/server/signals.rs +11 -0
  45. package/crates/bsnext_core/src/server/state.rs +19 -0
  46. package/crates/bsnext_core/src/servers_supervisor/actor.rs +199 -0
  47. package/crates/bsnext_core/src/servers_supervisor/file_changed_handler.rs +41 -0
  48. package/crates/bsnext_core/src/servers_supervisor/get_servers_handler.rs +23 -0
  49. package/crates/bsnext_core/src/servers_supervisor/input_changed_handler.rs +21 -0
  50. package/crates/bsnext_core/src/servers_supervisor/mod.rs +6 -0
  51. package/crates/bsnext_core/src/servers_supervisor/start_handler.rs +82 -0
  52. package/crates/bsnext_core/src/servers_supervisor/stop_handler.rs +26 -0
  53. package/crates/bsnext_core/src/ws/mod.rs +164 -0
  54. package/crates/bsnext_example/Cargo.toml +10 -0
  55. package/crates/bsnext_example/src/basic.rs +51 -0
  56. package/crates/bsnext_example/src/lib.rs +26 -0
  57. package/crates/bsnext_example/src/lit.rs +37 -0
  58. package/crates/bsnext_example/src/md.rs +18 -0
  59. package/crates/bsnext_fs/Cargo.toml +22 -0
  60. package/crates/bsnext_fs/src/actor.rs +122 -0
  61. package/crates/bsnext_fs/src/buffered_debounce.rs +166 -0
  62. package/crates/bsnext_fs/src/filter.rs +30 -0
  63. package/crates/bsnext_fs/src/inner_fs_event_handler.rs +94 -0
  64. package/crates/bsnext_fs/src/lib.rs +141 -0
  65. package/crates/bsnext_fs/src/remove_path_handler.rs +46 -0
  66. package/crates/bsnext_fs/src/stop_handler.rs +15 -0
  67. package/crates/bsnext_fs/src/stream.rs +167 -0
  68. package/crates/bsnext_fs/src/test/mod.rs +213 -0
  69. package/crates/bsnext_fs/src/watch_path_handler.rs +67 -0
  70. package/crates/bsnext_fs/src/watcher.rs +348 -0
  71. package/crates/bsnext_input/Cargo.toml +22 -0
  72. package/crates/bsnext_input/src/input_test/mod.rs +151 -0
  73. package/crates/bsnext_input/src/lib.rs +153 -0
  74. package/crates/bsnext_input/src/md.rs +541 -0
  75. package/crates/bsnext_input/src/paths.rs +64 -0
  76. package/crates/bsnext_input/src/route.rs +185 -0
  77. package/crates/bsnext_input/src/route_manifest.rs +186 -0
  78. package/crates/bsnext_input/src/server_config.rs +88 -0
  79. package/crates/bsnext_input/src/target.rs +7 -0
  80. package/crates/bsnext_input/src/watch_opt_test/mod.rs +68 -0
  81. package/crates/bsnext_input/src/yml.rs +1 -0
  82. package/crates/bsnext_output/Cargo.toml +16 -0
  83. package/crates/bsnext_output/src/json.rs +11 -0
  84. package/crates/bsnext_output/src/lib.rs +25 -0
  85. package/crates/bsnext_output/src/pretty.rs +147 -0
  86. package/crates/bsnext_resp/Cargo.toml +13 -0
  87. package/crates/bsnext_resp/src/js/snippet.html +1 -0
  88. package/crates/bsnext_resp/src/js/ws.js +1 -0
  89. package/crates/bsnext_resp/src/lib.rs +79 -0
  90. package/crates/bsnext_system/Cargo.toml +29 -0
  91. package/crates/bsnext_system/src/args.rs +43 -0
  92. package/crates/bsnext_system/src/lib.rs +227 -0
  93. package/crates/bsnext_system/src/monitor.rs +241 -0
  94. package/crates/bsnext_system/src/monitor_any_watchables.rs +127 -0
  95. package/crates/bsnext_system/src/start_kind/snapshots/bsnext_system__start_kind__start_from_paths__test__test-2.snap +11 -0
  96. package/crates/bsnext_system/src/start_kind/snapshots/bsnext_system__start_kind__start_from_paths__test__test.snap +29 -0
  97. package/crates/bsnext_system/src/start_kind/start_from_example.rs +49 -0
  98. package/crates/bsnext_system/src/start_kind/start_from_inputs.rs +67 -0
  99. package/crates/bsnext_system/src/start_kind/start_from_paths.rs +51 -0
  100. package/crates/bsnext_system/src/start_kind.rs +56 -0
  101. package/crates/bsnext_system/src/startup.rs +51 -0
  102. package/crates/bsnext_tracing/Cargo.toml +10 -0
  103. package/crates/bsnext_tracing/src/lib.rs +89 -0
  104. package/examples/basic/input.yml +5 -0
  105. package/examples/basic/public/index.html +15 -0
  106. package/examples/basic/public/reset.css +52 -0
  107. package/examples/basic/public/script.js +1 -0
  108. package/examples/basic/public/styles.css +3 -0
  109. package/examples/kitchen-sink/a-file.md +1 -0
  110. package/examples/kitchen-sink/api/1.json +1 -0
  111. package/examples/kitchen-sink/api/2.json +3 -0
  112. package/examples/kitchen-sink/app.js +1 -0
  113. package/examples/kitchen-sink/input.html +185 -0
  114. package/examples/kitchen-sink/input.yml +133 -0
  115. package/examples/kitchen-sink/styles-2.css +3 -0
  116. package/examples/lit/index.html +21 -0
  117. package/examples/lit/input.yml +5 -0
  118. package/examples/lit/lit.js +82 -0
  119. package/examples/lit/package-lock.json +62 -0
  120. package/examples/lit/package.json +15 -0
  121. package/examples/md-single/frontmatter.md +35 -0
  122. package/examples/md-single/md-single.md +35 -0
  123. package/examples/openai/.nvm +1 -0
  124. package/examples/openai/build.mjs +21 -0
  125. package/examples/openai/index.html +13 -0
  126. package/examples/openai/input.yml +59 -0
  127. package/examples/openai/package-lock.json +720 -0
  128. package/examples/openai/package.json +21 -0
  129. package/examples/openai/src/index.js +21 -0
  130. package/examples/openai/sse/01.txt +8 -0
  131. package/examples/proxy-simple/input.yml +9 -0
  132. package/examples/single/input.yaml +26 -0
  133. package/index.d.ts +3 -1
  134. package/index.js +3 -2
  135. package/package.json +22 -19
  136. package/run.sh +6 -0
  137. package/src/lib.rs +0 -9
@@ -0,0 +1,167 @@
1
+ use core::pin::Pin;
2
+ use core::task::{Context, Poll};
3
+ use std::fmt::Debug;
4
+ use std::time::Duration;
5
+
6
+ use futures::{Future, Stream};
7
+ use pin_project_lite::pin_project;
8
+
9
+ use tokio::time::{Instant, Sleep};
10
+
11
+ use tracing::trace;
12
+
13
+ pin_project! {
14
+ #[must_use = "streams do nothing unless polled"]
15
+ pub struct Debounce<St: Stream> {
16
+ #[pin]
17
+ dropped_count: usize,
18
+ #[pin]
19
+ value: St,
20
+ #[pin]
21
+ delay: Sleep,
22
+ #[pin]
23
+ debounce_time: Duration,
24
+ #[pin]
25
+ last_state: Option<St::Item>,
26
+ #[pin]
27
+ child_ended: bool
28
+ }
29
+ }
30
+
31
+ pub trait StreamOpsExt: Stream {
32
+ fn debounce(self, debounce_time: Duration) -> Debounce<Self>
33
+ where
34
+ Self: Sized + Unpin,
35
+ {
36
+ Debounce::new(self, debounce_time)
37
+ }
38
+ }
39
+
40
+ impl<T: ?Sized> StreamOpsExt for T where T: Stream {}
41
+
42
+ impl<St> Debounce<St>
43
+ where
44
+ St: Stream + Unpin,
45
+ {
46
+ #[allow(dead_code)]
47
+ fn new(stream: St, debounce_time: Duration) -> Debounce<St> {
48
+ Debounce {
49
+ value: stream,
50
+ dropped_count: 0,
51
+ delay: tokio::time::sleep(debounce_time),
52
+ debounce_time,
53
+ last_state: None,
54
+ child_ended: false,
55
+ }
56
+ }
57
+ }
58
+
59
+ impl<St, Item> Stream for Debounce<St>
60
+ where
61
+ St: Stream<Item = Item>,
62
+ Item: Clone + Unpin + Debug + 'static,
63
+ {
64
+ type Item = St::Item;
65
+ fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
66
+ let mut me = self.project();
67
+ trace!("+ polled!");
68
+
69
+ match me.value.poll_next(cx) {
70
+ Poll::Ready(Some(v)) => {
71
+ trace!("recorded a child value");
72
+ *me.last_state = Some(v);
73
+ *me.dropped_count += 1;
74
+
75
+ trace!("resetting the deadline to be `debounce_time` from `now`");
76
+ let dur = *me.debounce_time;
77
+ me.delay.as_mut().reset(Instant::now() + dur);
78
+
79
+ trace!("wake to ensure we're polled again");
80
+ cx.waker().wake_by_ref();
81
+ return Poll::Pending;
82
+ }
83
+ Poll::Ready(None) => {
84
+ trace!("child ended, nothing more to do?");
85
+ *me.child_ended = true;
86
+ }
87
+ Poll::Pending => {
88
+ trace!("child was pending, nothing to do");
89
+ }
90
+ }
91
+
92
+ match me.delay.poll(cx) {
93
+ Poll::Ready(_) => {
94
+ trace!("timer elapsed");
95
+ match (*me.last_state).clone() {
96
+ Some(v) => {
97
+ trace!("buffered {} events", me.dropped_count);
98
+ *me.last_state = None;
99
+ *me.dropped_count = 0;
100
+ trace!("sending value");
101
+ Poll::Ready(Some(v))
102
+ }
103
+ None => {
104
+ if *me.child_ended {
105
+ Poll::Ready(None)
106
+ } else {
107
+ Poll::Pending
108
+ }
109
+ }
110
+ }
111
+ }
112
+ Poll::Pending => Poll::Pending,
113
+ }
114
+ }
115
+ }
116
+
117
+ #[cfg(test)]
118
+ mod test {
119
+ use super::*;
120
+ use tokio::sync::mpsc;
121
+ use tokio::time::sleep;
122
+ use tokio::time::Instant;
123
+ use tokio_stream::wrappers::ReceiverStream;
124
+ use tokio_stream::StreamExt;
125
+ #[tokio::test]
126
+ async fn test_stream() {
127
+ let (tx, rx) = mpsc::channel::<&str>(10);
128
+ let handle = tokio::spawn(async move {
129
+ let events = ["A", "B", "C", "D", "E", "F"];
130
+
131
+ // 6 events all happening together
132
+ for evt in events {
133
+ tx.send(evt).await.unwrap();
134
+ }
135
+
136
+ // a gap in events, just under the debounce duration
137
+ sleep(Duration::from_millis(50)).await;
138
+
139
+ tx.send("G").await.unwrap();
140
+ tx.send("H").await.unwrap();
141
+ tx.send("I").await.unwrap();
142
+ tx.send("J").await.unwrap();
143
+
144
+ // drop the sender to complete the stream
145
+ drop(tx);
146
+ });
147
+
148
+ let start_time = Instant::now();
149
+ let stream = Box::pin(
150
+ ReceiverStream::new(rx)
151
+ .debounce(Duration::from_millis(100))
152
+ .collect::<Vec<_>>(),
153
+ );
154
+
155
+ let results = stream.await;
156
+ let end_time = Instant::now();
157
+ let total_duration = end_time
158
+ .checked_duration_since(start_time)
159
+ .expect("checked");
160
+
161
+ println!("{:?}", results);
162
+ println!("duration: {:?}", total_duration);
163
+
164
+ assert!(handle.await.is_ok());
165
+ assert_eq!(vec!["J"], results);
166
+ }
167
+ }
@@ -0,0 +1,213 @@
1
+ use crate::actor::FsWatcher;
2
+ use crate::watch_path_handler::RequestWatchPath;
3
+ use crate::{Debounce, FsEvent, FsEventKind};
4
+ use actix::{Actor, Addr};
5
+ use std::fs::File;
6
+ use std::io::Write;
7
+ use std::path::{Path, PathBuf};
8
+ use std::time::Duration;
9
+ use tempfile::TempDir;
10
+
11
+ use crate::filter::Filter;
12
+ use tokio::time::sleep;
13
+
14
+ struct A {
15
+ events: Vec<FsEvent>,
16
+ }
17
+
18
+ impl Actor for A {
19
+ type Context = actix::Context<Self>;
20
+ }
21
+ #[derive(actix::Message)]
22
+ #[rtype(result = "Vec<FsEvent>")]
23
+ struct GetEvents;
24
+
25
+ impl actix::Handler<GetEvents> for A {
26
+ type Result = Vec<FsEvent>;
27
+
28
+ fn handle(&mut self, _msg: GetEvents, _ctx: &mut Self::Context) -> Self::Result {
29
+ self.events.clone()
30
+ }
31
+ }
32
+ impl actix::Handler<FsEvent> for A {
33
+ type Result = ();
34
+
35
+ fn handle(&mut self, msg: FsEvent, _ctx: &mut Self::Context) -> Self::Result {
36
+ self.events.push(msg);
37
+ }
38
+ }
39
+
40
+ fn create_file(base: &Path, name: &str) -> PathBuf {
41
+ let file_path = base.join(name);
42
+ let mut file = File::create(&file_path).expect("create file");
43
+ file.write_all(name.as_bytes())
44
+ .expect(format!("write {name}").as_str());
45
+ file_path
46
+ }
47
+
48
+ struct TestCase {
49
+ addr: Addr<FsWatcher>,
50
+ recip_addr: Addr<A>,
51
+ dir: PathBuf,
52
+ #[allow(dead_code)]
53
+ tmp_dir: TempDir,
54
+ }
55
+
56
+ impl TestCase {
57
+ pub fn new(debounce: Debounce, filter: Option<Filter>) -> Self {
58
+ let tmp_dir = tempfile::tempdir().unwrap();
59
+ let mut fs = FsWatcher::new(tmp_dir.path(), 0);
60
+ fs.with_debounce(debounce);
61
+ if let Some(filter) = filter {
62
+ fs.with_filter(filter);
63
+ }
64
+ let addr = fs.start();
65
+ let a = A { events: vec![] };
66
+ let recip_addr = a.start();
67
+ Self {
68
+ recip_addr: recip_addr.clone(),
69
+ addr: addr.clone(),
70
+ dir: tmp_dir.path().to_path_buf(),
71
+ tmp_dir,
72
+ }
73
+ }
74
+
75
+ async fn watch(&self) {
76
+ let r = RequestWatchPath {
77
+ recipients: vec![self.recip_addr.clone().recipient()],
78
+ path: self.dir.to_path_buf(),
79
+ };
80
+
81
+ let _ = self.addr.send(r).await;
82
+ }
83
+
84
+ async fn write_file(&self, p: &str) {
85
+ create_file(self.dir.as_path(), p);
86
+ }
87
+
88
+ async fn get_events_after(&self, d: Duration) -> Vec<FsEvent> {
89
+ sleep(d).await;
90
+ let events = self.recip_addr.send(GetEvents).await.unwrap();
91
+ events
92
+ }
93
+ async fn change_events_after(&self, d: Duration) -> (Vec<FsEvent>, Vec<FsEvent>, usize) {
94
+ sleep(d).await;
95
+ let events = self.recip_addr.send(GetEvents).await.unwrap();
96
+ let len = events.len();
97
+ let (change_events, other_events) = events
98
+ .into_iter()
99
+ .partition(|e| matches!(e.kind, FsEventKind::Change(..)));
100
+ (change_events, other_events, len)
101
+ }
102
+ async fn buffered_change_after(&self, d: Duration) -> (Vec<FsEvent>, Vec<FsEvent>, usize) {
103
+ sleep(d).await;
104
+ let events = self.recip_addr.send(GetEvents).await.unwrap();
105
+ let len = events.len();
106
+ let (change_events, other_events) = events
107
+ .into_iter()
108
+ .partition(|e| matches!(e.kind, FsEventKind::ChangeBuffered(..)));
109
+ (change_events, other_events, len)
110
+ }
111
+
112
+ fn file_names(events: Vec<FsEvent>) -> Vec<String> {
113
+ events
114
+ .into_iter()
115
+ .flat_map(|evt| match evt.kind {
116
+ FsEventKind::Change(_) => vec![],
117
+ FsEventKind::ChangeBuffered(buf) => buf
118
+ .events
119
+ .iter()
120
+ .map(|p| p.absolute.to_path_buf())
121
+ .collect(),
122
+ FsEventKind::PathAdded(_) => vec![],
123
+ FsEventKind::PathRemoved(_) => vec![],
124
+ })
125
+ .map(|pb| pb.file_name().unwrap().to_string_lossy().to_string())
126
+ .collect()
127
+ }
128
+ }
129
+
130
+ async fn test_single_file_impl() -> Result<(), Box<dyn std::error::Error>> {
131
+ let tc = TestCase::new(Debounce::trailing_ms(10), None);
132
+ tc.watch().await;
133
+ tc.write_file("test_file.txt").await;
134
+ let events = tc.get_events_after(Duration::from_millis(300)).await;
135
+ assert_eq!(events.len(), 2);
136
+ assert_eq!(
137
+ matches!(events.get(0).unwrap().kind, FsEventKind::PathAdded(..)),
138
+ true
139
+ );
140
+ assert_eq!(
141
+ matches!(events.get(1).unwrap().kind, FsEventKind::Change(..)),
142
+ true
143
+ );
144
+ Ok(())
145
+ }
146
+
147
+ #[actix_rt::test]
148
+ async fn test_single_file() -> Result<(), Box<dyn std::error::Error>> {
149
+ test_single_file_impl().await
150
+ }
151
+
152
+ async fn test_trailing_drops_impl() -> Result<(), Box<dyn std::error::Error>> {
153
+ let tc = TestCase::new(Debounce::trailing_ms(10), None);
154
+ tc.watch().await;
155
+ tc.write_file("test_file.txt").await;
156
+ tc.write_file("test_file.css").await;
157
+ let (change, other, _total_count) = tc.change_events_after(Duration::from_millis(500)).await;
158
+ assert_eq!(change.len(), 1, "Should be a single change event");
159
+ assert_eq!(other.len(), 1, "Should be 2 in total");
160
+ Ok(())
161
+ }
162
+
163
+ #[actix_rt::test]
164
+ async fn test_trailing_drops() -> Result<(), Box<dyn std::error::Error>> {
165
+ test_trailing_drops_impl().await
166
+ }
167
+
168
+ async fn test_buffer_impl() -> Result<(), Box<dyn std::error::Error>> {
169
+ let tc = TestCase::new(Debounce::buffered_ms(10), None);
170
+ tc.watch().await;
171
+ tc.write_file("test_file.txt").await;
172
+ tc.write_file("test_file.css").await;
173
+ let (change, other, total_count) = tc.buffered_change_after(Duration::from_millis(500)).await;
174
+ assert_eq!(change.len(), 1, "Should be 1 change event (buffered)");
175
+ assert_eq!(other.len(), 1, "Should be 1 other event (path added)");
176
+ assert_eq!(total_count, 2, "Should be 2 in total");
177
+
178
+ let names = TestCase::file_names(change);
179
+ assert!(names.contains(&"test_file.txt".to_string()));
180
+ assert!(names.contains(&"test_file.css".to_string()));
181
+ Ok(())
182
+ }
183
+
184
+ #[actix_rt::test]
185
+ async fn test_buffer() -> Result<(), Box<dyn std::error::Error>> {
186
+ test_buffer_impl().await
187
+ }
188
+
189
+ async fn test_buffer_filter_impl() -> Result<(), Box<dyn std::error::Error>> {
190
+ let tc = TestCase::new(
191
+ Debounce::buffered_ms(10),
192
+ Some(Filter::Extension {
193
+ ext: "css".to_string(),
194
+ }),
195
+ );
196
+ tc.watch().await;
197
+ tc.write_file("test_file.txt").await;
198
+ tc.write_file("test_file.css").await;
199
+ let (change, other, total_count) = tc.buffered_change_after(Duration::from_millis(500)).await;
200
+ assert_eq!(change.len(), 1, "Should be 1 change event (buffered)");
201
+ assert_eq!(other.len(), 1, "Should be 1 other event (path added)");
202
+ assert_eq!(total_count, 2, "Should be 2 in total");
203
+
204
+ let names = TestCase::file_names(change);
205
+ assert_eq!(names.len(), 1);
206
+ assert!(names.contains(&"test_file.css".to_string()));
207
+ Ok(())
208
+ }
209
+
210
+ #[actix_rt::test]
211
+ async fn test_buffer_filter() -> Result<(), Box<dyn std::error::Error>> {
212
+ test_buffer_filter_impl().await
213
+ }
@@ -0,0 +1,67 @@
1
+ use crate::actor::FsWatcher;
2
+ use crate::{FsEvent, FsEventKind, FsWatchError, PathAddedEvent};
3
+ use actix::{ActorContext, Handler, Recipient};
4
+ use notify::{RecursiveMode, Watcher};
5
+ use std::path::PathBuf;
6
+
7
+ #[derive(actix::Message)]
8
+ #[rtype(result = "Result<(), FsWatchError>")]
9
+ pub struct RequestWatchPath {
10
+ pub recipients: Vec<Recipient<FsEvent>>,
11
+ pub path: PathBuf,
12
+ }
13
+
14
+ impl Handler<RequestWatchPath> for FsWatcher {
15
+ type Result = Result<(), FsWatchError>;
16
+
17
+ // todo: ensure this isn't sent for every input change
18
+ fn handle(&mut self, msg: RequestWatchPath, _ctx: &mut Self::Context) -> Self::Result {
19
+ tracing::trace!(path = ?msg.path, "-> WatchPath");
20
+ if let Some(watcher) = self.watcher.as_mut() {
21
+ match watcher.watch(&msg.path, RecursiveMode::Recursive) {
22
+ Ok(_) => {
23
+ let new_recipients = msg
24
+ .recipients
25
+ .into_iter()
26
+ .filter(|r| !self.receivers.contains(r))
27
+ .collect::<Vec<_>>();
28
+ self.receivers.extend(new_recipients);
29
+
30
+ tracing::debug!(path = ?msg.path, "👀 watching! {} receivers", self.receivers.len());
31
+ tracing::debug!(?self.cwd);
32
+ tracing::debug!(?self.receivers);
33
+
34
+ let matched = msg.path == self.cwd;
35
+
36
+ let relative = if matched {
37
+ msg.path.clone()
38
+ } else {
39
+ match msg.path.strip_prefix(&self.cwd) {
40
+ Ok(stripped) => stripped.to_path_buf(),
41
+ Err(e) => {
42
+ tracing::debug!(?e, "could not extract the CWD from a path");
43
+ msg.path.clone()
44
+ }
45
+ }
46
+ };
47
+ for recip in &self.receivers {
48
+ let evt = FsEventKind::PathAdded(PathAddedEvent {
49
+ path: relative.clone(),
50
+ debounce: self.debounce,
51
+ });
52
+ recip.do_send(FsEvent {
53
+ kind: evt,
54
+ ctx: self.ctx.clone(),
55
+ })
56
+ }
57
+ }
58
+ Err(err) => {
59
+ tracing::error!("cannot watch: {}", err);
60
+ _ctx.stop();
61
+ return Err(FsWatchError::Watcher(err));
62
+ }
63
+ }
64
+ }
65
+ Ok(())
66
+ }
67
+ }