From a89149fe34267ee064a9f667035975efa29f8f41 Mon Sep 17 00:00:00 2001 From: Shane Osbourne Date: Sat, 18 Apr 2026 16:57:33 +0100 Subject: [PATCH 01/10] monitors --- Cargo.lock | 76 +++---------- crates/bsnext_core/Cargo.toml | 1 - crates/bsnext_fs/Cargo.toml | 1 - crates/bsnext_monitor/Cargo.toml | 8 ++ .../examples/path_monitor_example.rs | 71 ++++++++++++ crates/bsnext_monitor/src/lib.rs | 16 +-- .../src}/path_monitor.rs | 104 +++++++----------- .../bsnext_monitor/src/path_monitor_meta.rs | 24 ++++ crates/bsnext_system/Cargo.toml | 1 + crates/bsnext_system/src/system.rs | 3 +- .../watchables/handle_fs_event_grouping.rs | 2 +- .../src/watchables/input_monitor.rs | 16 ++- crates/bsnext_system/src/watchables/mod.rs | 1 - .../src/watchables/monitor_path_watchables.rs | 17 ++- .../src/watchables/path_watchable.rs | 2 +- 15 files changed, 193 insertions(+), 150 deletions(-) create mode 100644 crates/bsnext_monitor/examples/path_monitor_example.rs rename crates/{bsnext_system/src/watchables => bsnext_monitor/src}/path_monitor.rs (90%) create mode 100644 crates/bsnext_monitor/src/path_monitor_meta.rs diff --git a/Cargo.lock b/Cargo.lock index 51247442..c5b43fcd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -234,7 +234,7 @@ checksum = "504e3947307ac8326a5437504c517c4b56716c9d98fac0028c2acc7ca47d70ae" dependencies = [ "async-trait", "axum-core", - "base64 0.22.1", + "base64", "bytes", "futures-util", "http", @@ -284,29 +284,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "axum-extra" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0be6ea09c9b96cb5076af0de2e383bd2bc0c18f827cf1967bdd353e0b910d733" -dependencies = [ - "axum", - "axum-core", - "bytes", - "futures-util", - "headers", - "http", - "http-body", - "http-body-util", - "mime", - "pin-project-lite", - "serde", - "tower 0.4.13", - "tower-layer", - "tower-service", - "tracing", -] - [[package]] name = "axum-server" version = "0.6.0" @@ -354,12 +331,6 @@ dependencies = [ "backtrace", ] -[[package]] -name = "base64" -version = "0.21.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" - [[package]] name = "base64" version = "0.22.1" @@ -500,7 +471,6 @@ dependencies = [ "actix-rt", "anyhow", "axum", - "axum-extra", "axum-server", "bsnext_client", "bsnext_dto", @@ -575,7 +545,6 @@ dependencies = [ "bsnext_tracing", "futures", "globset", - "kill_tree", "notify", "pin-project-lite", "thiserror 1.0.60", @@ -666,6 +635,16 @@ dependencies = [ [[package]] name = "bsnext_monitor" version = "0.26.0" +dependencies = [ + "actix", + "actix-rt", + "bsnext_fs", + "bsnext_input", + "globset", + "tokio", + "tokio-stream", + "tracing", +] [[package]] name = "bsnext_output" @@ -720,6 +699,7 @@ dependencies = [ "bsnext_input", "bsnext_js", "bsnext_md", + "bsnext_monitor", "bsnext_output", "bsnext_task", "bsnext_tracing", @@ -1455,30 +1435,6 @@ version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" -[[package]] -name = "headers" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "322106e6bd0cba2d5ead589ddb8150a13d7c4217cf80d7c4f682ca994ccc6aa9" -dependencies = [ - "base64 0.21.7", - "bytes", - "headers-core", - "http", - "httpdate", - "mime", - "sha1", -] - -[[package]] -name = "headers-core" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4" -dependencies = [ - "http", -] - [[package]] name = "heck" version = "0.5.0" @@ -2498,7 +2454,7 @@ version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42cf17e9a1800f5f396bc67d193dc9411b59012a5876445ef450d449881e1016" dependencies = [ - "base64 0.22.1", + "base64", "indexmap", "quick-xml", "serde", @@ -2715,7 +2671,7 @@ version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7d6d2a27d57148378eb5e111173f4276ad26340ecc5c49a4a2152167a2d6a37" dependencies = [ - "base64 0.22.1", + "base64", "bytes", "encoding_rs", "futures-channel", @@ -2824,7 +2780,7 @@ version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" dependencies = [ - "base64 0.22.1", + "base64", "rustls-pki-types", ] @@ -3526,7 +3482,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fec7c61a0695dc1887c1b53952990f3ad2e3a31453e1f49f10e75424943a93ec" dependencies = [ "async-trait", - "base64 0.22.1", + "base64", "bytes", "http", "http-body", diff --git a/crates/bsnext_core/Cargo.toml b/crates/bsnext_core/Cargo.toml index abd60aff..8220e3d8 100644 --- a/crates/bsnext_core/Cargo.toml +++ b/crates/bsnext_core/Cargo.toml @@ -21,7 +21,6 @@ bsnext_tracing = { path = "../bsnext_tracing" } bsnext_query = { path = "../bsnext_query" } axum-server = { version = "0.6.0", features = ["tls-rustls"] } -axum-extra = { version = "0.9.3", features = ["typed-header"] } hyper-tls = { version = "0.6.0" } hyper-util = { version = "0.1.1", features = ["client-legacy"] } diff --git a/crates/bsnext_fs/Cargo.toml b/crates/bsnext_fs/Cargo.toml index 8116a638..fddcb970 100644 --- a/crates/bsnext_fs/Cargo.toml +++ b/crates/bsnext_fs/Cargo.toml @@ -22,5 +22,4 @@ thiserror = { workspace = true } bsnext_tracing = { path = "../bsnext_tracing" } tracing = { workspace = true } tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } -kill_tree = { workspace = true } anyhow = { workspace = true } diff --git a/crates/bsnext_monitor/Cargo.toml b/crates/bsnext_monitor/Cargo.toml index 2a00da07..284a1665 100644 --- a/crates/bsnext_monitor/Cargo.toml +++ b/crates/bsnext_monitor/Cargo.toml @@ -4,3 +4,11 @@ version = "0.26.0" edition = "2024" [dependencies] +bsnext_fs = { path = "../bsnext_fs" } +bsnext_input = { path = "../bsnext_input" } +actix = { workspace = true } +actix-rt = { workspace = true } +tokio = { workspace = true } +tokio-stream = { workspace = true } +tracing = { workspace = true } +globset = { workspace = true } diff --git a/crates/bsnext_monitor/examples/path_monitor_example.rs b/crates/bsnext_monitor/examples/path_monitor_example.rs new file mode 100644 index 00000000..5e13abf3 --- /dev/null +++ b/crates/bsnext_monitor/examples/path_monitor_example.rs @@ -0,0 +1,71 @@ +use actix::{Actor, ResponseFuture}; +use actix_rt::System; +use bsnext_fs::{FsEvent, FsEventContext, FsEventGrouping}; +use bsnext_input::route::{DebounceDuration, Spec}; +use bsnext_monitor::path_monitor::PathMonitor; +use std::env::current_dir; +use std::process; +use std::time::Duration; + +fn main() { + let code = System::with_tokio_rt(|| { + // build system with a multi-thread tokio runtime. + tokio::runtime::Builder::new_multi_thread() + .worker_threads(1) + .enable_all() + .build() + .unwrap() + }) + .block_on(async_main()); + System::current().stop_with_code(code); + process::exit(code) +} + +async fn async_main() -> i32 { + struct Consumer; + + #[derive(actix::Message, Debug, Clone)] + #[rtype(result = "()")] + struct Ping; + + impl actix::Actor for Consumer { + type Context = actix::Context; + } + + impl actix::Handler for Consumer { + type Result = (); + + fn handle(&mut self, _msg: FsEventGrouping, _ctx: &mut Self::Context) -> Self::Result { + dbg!("got "); + } + } + + impl actix::Handler for Consumer { + type Result = ResponseFuture<()>; + + fn handle(&mut self, _msg: Ping, _ctx: &mut Self::Context) -> Self::Result { + Box::pin(async {}) + } + } + + let consumer = Consumer; + let actor = consumer.start(); + let reciever = actor.clone().recipient(); + let cwd = current_dir().unwrap(); + let fs_context = FsEventContext::default(); + let spec = Spec { + debounce: Some(DebounceDuration::Ms(0)), + ..Default::default() + }; + + let p = vec![]; + + let monitor = PathMonitor::new(reciever, Default::default(), cwd, fs_context, spec, p); + let monitor_actor = monitor.start(); + let _did_wait = actor.send(Ping).await; + let _sent = monitor_actor + .send(FsEvent::changed("/a", "a", fs_context.id())) + .await; + tokio::time::sleep(Duration::from_millis(310)).await; + 0 +} diff --git a/crates/bsnext_monitor/src/lib.rs b/crates/bsnext_monitor/src/lib.rs index b93cf3ff..d5ee48d3 100644 --- a/crates/bsnext_monitor/src/lib.rs +++ b/crates/bsnext_monitor/src/lib.rs @@ -1,14 +1,2 @@ -pub fn add(left: u64, right: u64) -> u64 { - left + right -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); - } -} +pub mod path_monitor; +pub mod path_monitor_meta; diff --git a/crates/bsnext_system/src/watchables/path_monitor.rs b/crates/bsnext_monitor/src/path_monitor.rs similarity index 90% rename from crates/bsnext_system/src/watchables/path_monitor.rs rename to crates/bsnext_monitor/src/path_monitor.rs index f54d7c9b..cb0a929f 100644 --- a/crates/bsnext_system/src/watchables/path_monitor.rs +++ b/crates/bsnext_monitor/src/path_monitor.rs @@ -1,4 +1,3 @@ -use crate::watchables::path_watchable::PathWatchable; use actix::{ActorContext, Addr, AsyncContext, Context, Handler, Recipient, StreamHandler}; use actix_rt::Arbiter; use bsnext_fs::actor::FsWatcher; @@ -11,7 +10,7 @@ use bsnext_fs::{ Debounce, FsEvent, FsEventContext, FsEventGrouping, FsEventKind, PathDescription, PathDescriptionOwned, }; -use bsnext_input::route::PathPattern; +use bsnext_input::route::{PathPattern, Spec}; use std::collections::BTreeSet; use std::path::{Path, PathBuf}; use tokio::sync::mpsc; @@ -24,8 +23,9 @@ pub struct PathMonitor { pub(crate) addrs: Vec>, pub(crate) recipient: Recipient, pub(crate) fs_ctx: FsEventContext, - pub(crate) path_watchable: PathWatchable, pub(crate) debounce: Debounce, + pub spec: Spec, + pub watch_paths: Vec, inner_sender: tokio::sync::mpsc::Sender, inner_receiver: Option>, } @@ -36,7 +36,8 @@ impl PathMonitor { debounce: Debounce, cwd: PathBuf, fs_ctx: FsEventContext, - path_watchable: PathWatchable, + spec: Spec, + watch_paths: Vec, ) -> Self { let (inner_sender, inner_receiver) = mpsc::channel::(1); Self { @@ -45,66 +46,19 @@ impl PathMonitor { cwd, addrs: vec![], fs_ctx, - path_watchable, + spec, + watch_paths, inner_sender, inner_receiver: Some(inner_receiver), } } } -#[derive(Debug, Clone)] -pub struct PathMonitorMeta { - #[allow(dead_code)] - pub cwd: PathBuf, - pub fs_ctx: FsEventContext, - #[allow(dead_code)] - pub path_watchable: PathWatchable, - pub debounce: Debounce, -} - -impl From<&PathMonitor> for PathMonitorMeta { - fn from(value: &PathMonitor) -> Self { - Self { - path_watchable: value.path_watchable.clone(), - cwd: value.cwd.clone(), - fs_ctx: value.fs_ctx, - debounce: value.debounce, - } - } -} - -impl StreamHandler for PathMonitor { - fn handle(&mut self, event: FsEvent, _ctx: &mut Context) { - self.recipient.do_send(FsEventGrouping::Singular(event)) - } -} - -impl StreamHandler> for PathMonitor { - fn handle(&mut self, events: Vec, _ctx: &mut Context) { - debug!(" └ got {} events to process", events.len()); - // let now = Instant::now(); - // let original_len = events.len(); - // let unique_len = unique.len(); - let unique = events.iter().collect::>(); - debug!(" └ {} unique event after converting to set", unique.len()); - debug!(" └ {:?}", unique); - let outgoing = unique - .into_iter() - .filter_map(|e| match &e.kind { - FsEventKind::Change(pd) => Some(pd.to_owned()), - _ => None, - }) - .collect::>(); - self.recipient - .do_send(FsEventGrouping::buffered_change(outgoing, self.fs_ctx)) - } -} - impl actix::Actor for PathMonitor { type Context = actix::Context; fn started(&mut self, ctx: &mut Self::Context) { - for single_path in &self.path_watchable.watch_paths() { + for single_path in &self.watch_paths { let as_str = single_path.to_string_lossy(); let PathAndFilter { path, filter_kind } = PathAndFilter::new(&as_str); @@ -112,8 +66,7 @@ impl actix::Actor for PathMonitor { let mut filters = filter_kind.into_iter().collect::>(); // additional filter from options? - let spec_opts = self.path_watchable.spec_opts(); - if let Some(filter) = &spec_opts.only { + if let Some(filter) = &self.spec.only { filters.push(filter.clone()); } @@ -121,25 +74,25 @@ impl actix::Actor for PathMonitor { let watcher = to_watcher( &self.cwd, Some(&PathPattern::List(filters)), - spec_opts.ignore.as_ref(), + self.spec.ignore.as_ref(), self.fs_ctx, ctx.address().recipient(), ); let watcher_addr = watcher.start(); - let watcher_addr_clone = watcher_addr.clone(); - self.addrs.push(watcher_addr); + self.addrs.push(watcher_addr.clone()); - watcher_addr_clone.do_send(RequestWatchPath { + watcher_addr.do_send(RequestWatchPath { path: path.to_path_buf(), }); } + let Some(receiver) = self.inner_receiver.take() else { panic!("impossible?") }; - let debounce = self.debounce; - match debounce { + + match self.debounce { Debounce::Trailing { duration } => { let stream = ReceiverStream::new(receiver).debounce(duration); >::add_stream(stream, ctx); @@ -152,6 +105,33 @@ impl actix::Actor for PathMonitor { } } +impl StreamHandler for PathMonitor { + fn handle(&mut self, event: FsEvent, _ctx: &mut Context) { + self.recipient.do_send(FsEventGrouping::Singular(event)) + } +} + +impl StreamHandler> for PathMonitor { + fn handle(&mut self, events: Vec, _ctx: &mut Context) { + debug!(" └ got {} events to process", events.len()); + // let now = Instant::now(); + // let original_len = events.len(); + // let unique_len = unique.len(); + let unique = events.iter().collect::>(); + debug!(" └ {} unique event after converting to set", unique.len()); + debug!(" └ {:?}", unique); + let outgoing = unique + .into_iter() + .filter_map(|e| match &e.kind { + FsEventKind::Change(pd) => Some(pd.to_owned()), + _ => None, + }) + .collect::>(); + self.recipient + .do_send(FsEventGrouping::buffered_change(outgoing, self.fs_ctx)) + } +} + struct PathAndFilter<'a> { path: &'a Path, filter_kind: Option, diff --git a/crates/bsnext_monitor/src/path_monitor_meta.rs b/crates/bsnext_monitor/src/path_monitor_meta.rs new file mode 100644 index 00000000..bceac4f2 --- /dev/null +++ b/crates/bsnext_monitor/src/path_monitor_meta.rs @@ -0,0 +1,24 @@ +use crate::path_monitor::PathMonitor; +use bsnext_fs::{Debounce, FsEventContext}; +use bsnext_input::route::Spec; +use std::path::PathBuf; + +#[derive(Debug, Clone)] +pub struct PathMonitorMeta { + #[allow(dead_code)] + pub cwd: PathBuf, + pub fs_ctx: FsEventContext, + pub spec: Spec, + pub debounce: Debounce, +} + +impl From<&PathMonitor> for PathMonitorMeta { + fn from(value: &PathMonitor) -> Self { + Self { + spec: value.spec.clone(), + cwd: value.cwd.clone(), + fs_ctx: value.fs_ctx, + debounce: value.debounce, + } + } +} diff --git a/crates/bsnext_system/Cargo.toml b/crates/bsnext_system/Cargo.toml index f0a0643c..911ffa2b 100644 --- a/crates/bsnext_system/Cargo.toml +++ b/crates/bsnext_system/Cargo.toml @@ -19,6 +19,7 @@ bsnext_dto = { path = "../bsnext_dto" } bsnext_example = { path = "../bsnext_example" } bsnext_output = { path = "../bsnext_output" } bsnext_task = { path = "../bsnext_task" } +bsnext_monitor = { path = "../bsnext_monitor" } rand = "0.8.5" kill_tree = { workspace = true } sqids = { workspace = true } diff --git a/crates/bsnext_system/src/system.rs b/crates/bsnext_system/src/system.rs index 2fa79376..06f9cef3 100644 --- a/crates/bsnext_system/src/system.rs +++ b/crates/bsnext_system/src/system.rs @@ -6,7 +6,6 @@ use crate::servers::ResolveServers; use crate::tasks::resolve::ResolveInitialTasks; use crate::tasks::task_spec::TaskSpec; use crate::watchables::input_monitor::InputMonitor; -use crate::watchables::path_monitor::{PathMonitor, PathMonitorMeta}; use crate::watchables::path_watchable::PathWatchable; use actix::{Actor, Addr, AsyncContext, ResponseFuture, Running}; use actix_rt::Arbiter; @@ -16,6 +15,8 @@ use bsnext_dto::internal::{AnyEvent, ChildResult, TaskReportAndTree}; use bsnext_dto::GetActiveServersResponse; use bsnext_input::startup::{StartupContext, TopLevelRunMode}; use bsnext_input::Input; +use bsnext_monitor::path_monitor::PathMonitor; +use bsnext_monitor::path_monitor_meta::PathMonitorMeta; use bsnext_task::task_trigger::{ExecTrigger, TaskTrigger, TaskTriggerSource}; use std::collections::HashMap; use std::path::PathBuf; diff --git a/crates/bsnext_system/src/watchables/handle_fs_event_grouping.rs b/crates/bsnext_system/src/watchables/handle_fs_event_grouping.rs index 9989f8ca..9eed873a 100644 --- a/crates/bsnext_system/src/watchables/handle_fs_event_grouping.rs +++ b/crates/bsnext_system/src/watchables/handle_fs_event_grouping.rs @@ -5,7 +5,6 @@ use crate::system::BsSystem; use crate::tasks::task_comms::TaskComms; use crate::tasks::task_spec::TaskSpec; use crate::tasks::Runnable; -use crate::watchables::path_monitor::PathMonitorMeta; use crate::watchables::path_watchable::PathWatchable; use actix::{Addr, AsyncContext}; use bsnext_core::servers_supervisor::file_changed_handler::FileChanged; @@ -18,6 +17,7 @@ use bsnext_fs::{ }; use bsnext_input::bs_live_built_in_task::BsLiveBuiltInTask; use bsnext_input::{Input, InputError, PathDefinition, PathDefs, PathError}; +use bsnext_monitor::path_monitor_meta::PathMonitorMeta; use bsnext_task::task_trigger::FsChangesTrigger; use tracing::{debug_span, info}; diff --git a/crates/bsnext_system/src/watchables/input_monitor.rs b/crates/bsnext_system/src/watchables/input_monitor.rs index 91552a14..d615e801 100644 --- a/crates/bsnext_system/src/watchables/input_monitor.rs +++ b/crates/bsnext_system/src/watchables/input_monitor.rs @@ -1,11 +1,12 @@ use crate::system::BsSystem; use crate::watchables::any_watchable::AnyWatchable; -use crate::watchables::path_monitor::{PathMonitor, PathMonitorMeta}; use crate::watchables::path_watchable::PathWatchable; use actix::{Actor, Addr, AsyncContext}; use bsnext_fs::{Debounce, FsEventContext}; use bsnext_input::route::Spec; use bsnext_input::InputCtx; +use bsnext_monitor::path_monitor::PathMonitor; +use bsnext_monitor::path_monitor_meta::PathMonitorMeta; use std::path::PathBuf; use std::time::Duration; @@ -37,13 +38,22 @@ impl actix::Handler for BsSystem { }; let ctx = FsEventContext::for_root(); + let paths = vec![msg.path.to_path_buf()]; let pw = PathWatchable::Any(AnyWatchable { - dirs: vec![msg.path.to_path_buf()], + dirs: paths.clone(), spec: Spec::default(), task_spec: None, }); - let input_path_monitor = PathMonitor::new(sys, debounce, self.cwd.clone(), ctx, pw); + let input_path_monitor = PathMonitor::new( + sys, + debounce, + self.cwd.clone(), + ctx, + pw.spec().clone(), + paths, + ); + let meta = PathMonitorMeta::from(&input_path_monitor); tracing::debug!("starting input monitor"); diff --git a/crates/bsnext_system/src/watchables/mod.rs b/crates/bsnext_system/src/watchables/mod.rs index b111a590..eb44e86d 100644 --- a/crates/bsnext_system/src/watchables/mod.rs +++ b/crates/bsnext_system/src/watchables/mod.rs @@ -12,7 +12,6 @@ pub mod any_watchable; mod handle_fs_event_grouping; pub mod input_monitor; pub mod monitor_path_watchables; -pub mod path_monitor; pub mod path_watchable; pub mod route_watchable; pub mod server_watchable; diff --git a/crates/bsnext_system/src/watchables/monitor_path_watchables.rs b/crates/bsnext_system/src/watchables/monitor_path_watchables.rs index b3319e6b..08491722 100644 --- a/crates/bsnext_system/src/watchables/monitor_path_watchables.rs +++ b/crates/bsnext_system/src/watchables/monitor_path_watchables.rs @@ -1,9 +1,10 @@ use crate::system::BsSystem; -use crate::watchables::path_monitor::{PathMonitor, PathMonitorMeta, StopPathMonitor}; use crate::watchables::path_watchable::PathWatchable; use actix::{Actor, Addr, AsyncContext}; use bsnext_fs::{Debounce, FsEventContext}; use bsnext_input::route::{DebounceDuration, Spec}; +use bsnext_monitor::path_monitor::{PathMonitor, StopPathMonitor}; +use bsnext_monitor::path_monitor_meta::PathMonitorMeta; use std::collections::BTreeSet; use std::path::PathBuf; use std::time::Duration; @@ -45,6 +46,11 @@ impl actix::Handler for BsSystem { let span = debug_span!("{}", index); let _guard = span.enter(); let watchable_hash = any_watchable.as_id(); + let paths = any_watchable + .watch_paths() + .into_iter() + .map(ToOwned::to_owned) + .collect::>(); tracing::trace!(?watchable_hash); let fs_ctx_id = match any_watchable { @@ -55,10 +61,10 @@ impl actix::Handler for BsSystem { tracing::trace!(?fs_ctx_id); let fs_ctx = FsEventContext::new(fs_ctx_id, watchable_hash); - let opts = any_watchable.spec_opts(); - tracing::trace!(?opts); + let spec = any_watchable.spec(); + tracing::trace!(?spec); - let duration = match opts { + let duration = match spec { Spec { debounce: Some(DebounceDuration::Ms(ms)), .. @@ -75,7 +81,8 @@ impl actix::Handler for BsSystem { debounce, msg.cwd.clone(), fs_ctx, - (*any_watchable).clone(), + spec.clone(), + paths, ); let meta = PathMonitorMeta::from(&monitor); diff --git a/crates/bsnext_system/src/watchables/path_watchable.rs b/crates/bsnext_system/src/watchables/path_watchable.rs index d2751d95..f62913bc 100644 --- a/crates/bsnext_system/src/watchables/path_watchable.rs +++ b/crates/bsnext_system/src/watchables/path_watchable.rs @@ -45,7 +45,7 @@ impl Display for PathWatchable { } impl PathWatchable { - pub fn spec_opts(&self) -> &Spec { + pub fn spec(&self) -> &Spec { match self { PathWatchable::Server(server) => &server.spec, PathWatchable::Route(route) => &route.spec, From 0f2242a36e5a6bdb49a28d523857b383c94972a5 Mon Sep 17 00:00:00 2001 From: Shane Osbourne Date: Sat, 18 Apr 2026 17:39:49 +0100 Subject: [PATCH 02/10] refactor --- crates/bsnext_monitor/src/lib.rs | 1 + .../src/watchables/any_watchable.rs | 7 +- crates/bsnext_monitor/src/watchables/mod.rs | 73 +++++++++++++++++++ .../src/watchables/path_watchable.rs | 9 --- .../src/watchables/route_watchable.rs | 7 +- .../src/watchables/server_watchable.rs | 19 +---- .../handle_fs_event_grouping.rs | 28 ++----- .../src/{watchables => }/input_monitor.rs | 5 +- crates/bsnext_system/src/lib.rs | 4 +- .../monitor_path_watchables.rs | 40 +++++++--- crates/bsnext_system/src/override_input.rs | 3 +- .../bsnext_system/src/start/start_system.rs | 9 ++- crates/bsnext_system/src/system.rs | 7 +- crates/bsnext_system/src/watchables/mod.rs | 73 ------------------- 14 files changed, 129 insertions(+), 156 deletions(-) rename crates/{bsnext_system => bsnext_monitor}/src/watchables/any_watchable.rs (71%) create mode 100644 crates/bsnext_monitor/src/watchables/mod.rs rename crates/{bsnext_system => bsnext_monitor}/src/watchables/path_watchable.rs (86%) rename crates/{bsnext_system => bsnext_monitor}/src/watchables/route_watchable.rs (93%) rename crates/{bsnext_system => bsnext_monitor}/src/watchables/server_watchable.rs (54%) rename crates/bsnext_system/src/{watchables => }/handle_fs_event_grouping.rs (89%) rename crates/bsnext_system/src/{watchables => }/input_monitor.rs (93%) rename crates/bsnext_system/src/{watchables => }/monitor_path_watchables.rs (78%) delete mode 100644 crates/bsnext_system/src/watchables/mod.rs diff --git a/crates/bsnext_monitor/src/lib.rs b/crates/bsnext_monitor/src/lib.rs index d5ee48d3..70ca9808 100644 --- a/crates/bsnext_monitor/src/lib.rs +++ b/crates/bsnext_monitor/src/lib.rs @@ -1,2 +1,3 @@ pub mod path_monitor; pub mod path_monitor_meta; +pub mod watchables; diff --git a/crates/bsnext_system/src/watchables/any_watchable.rs b/crates/bsnext_monitor/src/watchables/any_watchable.rs similarity index 71% rename from crates/bsnext_system/src/watchables/any_watchable.rs rename to crates/bsnext_monitor/src/watchables/any_watchable.rs index 1475cd6f..b8120c06 100644 --- a/crates/bsnext_system/src/watchables/any_watchable.rs +++ b/crates/bsnext_monitor/src/watchables/any_watchable.rs @@ -1,14 +1,11 @@ -use crate::tasks::task_spec::TaskSpec; -use crate::watchables::server_watchable::to_task_spec; -use bsnext_input::route::Spec; use bsnext_input::Input; +use bsnext_input::route::Spec; use std::path::PathBuf; #[derive(Debug, PartialEq, PartialOrd, Ord, Eq, Hash, Clone)] pub struct AnyWatchable { pub dirs: Vec, pub spec: Spec, - pub task_spec: Option, } pub fn to_any_watchables(input: &Input) -> Vec { @@ -16,13 +13,11 @@ pub fn to_any_watchables(input: &Input) -> Vec { .watchers .iter() .map(|watcher| { - let task_spec = watcher.spec.as_ref().and_then(to_task_spec); let path_bufs = watcher.dirs.as_pathbufs(); AnyWatchable { dirs: path_bufs, spec: watcher.spec.clone().unwrap_or_default(), - task_spec, } }) .collect() diff --git a/crates/bsnext_monitor/src/watchables/mod.rs b/crates/bsnext_monitor/src/watchables/mod.rs new file mode 100644 index 00000000..fb0aa523 --- /dev/null +++ b/crates/bsnext_monitor/src/watchables/mod.rs @@ -0,0 +1,73 @@ +use crate::watchables::any_watchable::to_any_watchables; +use crate::watchables::path_watchable::PathWatchable; +use crate::watchables::route_watchable::to_route_watchables; +use crate::watchables::server_watchable::to_server_watchables; +use actix::Recipient; +use bsnext_input::{InferWatchers, Input, WatchGlobalConfig}; +use std::path::PathBuf; +use tracing::debug; + +pub mod any_watchable; +pub mod path_watchable; +pub mod route_watchable; +pub mod server_watchable; + +#[derive(actix::Message)] +#[rtype(result = "()")] +pub struct MonitorPathWatchables { + pub watchables: Vec, + pub cwd: PathBuf, +} + +#[tracing::instrument(skip_all, name = "accept_watchables")] +pub fn accept_watchables(cwd: PathBuf, input: &Input, addr: Recipient) { + let route_watchables = to_route_watchables(input); + let server_watchables = to_server_watchables(input); + let any_watchables = to_any_watchables(input); + + let routes = route_watchables + .iter() + .map(|r| PathWatchable::Route(r.to_owned())); + + let servers = server_watchables + .iter() + .map(|w| PathWatchable::Server(w.to_owned())); + + let any = any_watchables + .iter() + .map(|w| PathWatchable::Any(w.to_owned())); + + let watchables: Vec<_> = match &input.config.watchers { + WatchGlobalConfig::Enabled { infer } => match infer { + InferWatchers::None => { + debug!("processing {} any watchables", any.len()); + any.collect() + } + InferWatchers::Routes => { + debug!("processing {} route watchables", routes.len()); + debug!("processing {} any watchables", any.len()); + routes.chain(any).collect() + } + InferWatchers::Servers => { + debug!("processing {} server watchables", servers.len()); + debug!("processing {} any watchables", any.len()); + servers.chain(any).collect() + } + InferWatchers::RoutesAndServers => { + debug!("processing {} route watchables", routes.len()); + debug!("processing {} server watchables", servers.len()); + debug!("processing {} any watchables", any.len()); + routes.chain(servers).chain(any).collect() + } + }, + WatchGlobalConfig::Disabled => vec![], + }; + + debug!( + "{} watchables to add, cwd: {}", + watchables.len(), + cwd.display() + ); + let msg = MonitorPathWatchables { watchables, cwd }; + addr.do_send(msg); +} diff --git a/crates/bsnext_system/src/watchables/path_watchable.rs b/crates/bsnext_monitor/src/watchables/path_watchable.rs similarity index 86% rename from crates/bsnext_system/src/watchables/path_watchable.rs rename to crates/bsnext_monitor/src/watchables/path_watchable.rs index f62913bc..234ea745 100644 --- a/crates/bsnext_system/src/watchables/path_watchable.rs +++ b/crates/bsnext_monitor/src/watchables/path_watchable.rs @@ -1,7 +1,6 @@ use std::fmt::{Display, Formatter}; use std::hash::{DefaultHasher, Hash, Hasher}; -use crate::tasks::task_spec::TaskSpec; use crate::watchables::any_watchable::AnyWatchable; use crate::watchables::route_watchable::RouteWatchable; use crate::watchables::server_watchable::ServerWatchable; @@ -65,12 +64,4 @@ impl PathWatchable { self.hash(&mut hasher); hasher.finish() } - - pub fn task_spec(&self) -> Option<&TaskSpec> { - match self { - PathWatchable::Server(server) => server.task_spec.as_ref(), - PathWatchable::Route(route) => route.task_spec.as_ref(), - PathWatchable::Any(any) => any.task_spec.as_ref(), - } - } } diff --git a/crates/bsnext_system/src/watchables/route_watchable.rs b/crates/bsnext_monitor/src/watchables/route_watchable.rs similarity index 93% rename from crates/bsnext_system/src/watchables/route_watchable.rs rename to crates/bsnext_monitor/src/watchables/route_watchable.rs index 857e57ff..30edf7e4 100644 --- a/crates/bsnext_system/src/watchables/route_watchable.rs +++ b/crates/bsnext_monitor/src/watchables/route_watchable.rs @@ -1,9 +1,7 @@ -use crate::tasks::task_spec::TaskSpec; -use crate::watchables::server_watchable::to_task_spec; +use bsnext_input::Input; use bsnext_input::route::{DirRoute, PathPattern, RawRoute, RouteKind, Spec, SseOpts}; use bsnext_input::server_config::ServerIdentity; use bsnext_input::watch_opts::WatchOpts; -use bsnext_input::Input; use std::path::{Path, PathBuf}; #[derive(Debug, PartialEq, PartialOrd, Ord, Eq, Hash, Clone)] @@ -12,7 +10,6 @@ pub struct RouteWatchable { pub route_path: String, pub dir: PathBuf, pub spec: Spec, - pub task_spec: Option, } pub fn to_route_watchables(input: &Input) -> Vec { @@ -49,7 +46,6 @@ pub fn to_route_watchables(input: &Input) -> Vec { // respect a given spec's 'debounce' (eg: if provided by user), otherwise try to use the global spec.debounce = spec.debounce.or(input.config.global_fs_debounce); - let run = to_task_spec(&spec); let route_path = r.path.as_str().to_owned(); let route_watchable = RouteWatchable { @@ -57,7 +53,6 @@ pub fn to_route_watchables(input: &Input) -> Vec { route_path, dir: pb, spec, - task_spec: run, }; tracing::trace!(?route_watchable); diff --git a/crates/bsnext_system/src/watchables/server_watchable.rs b/crates/bsnext_monitor/src/watchables/server_watchable.rs similarity index 54% rename from crates/bsnext_system/src/watchables/server_watchable.rs rename to crates/bsnext_monitor/src/watchables/server_watchable.rs index 8ad4db9a..29eae6f0 100644 --- a/crates/bsnext_system/src/watchables/server_watchable.rs +++ b/crates/bsnext_monitor/src/watchables/server_watchable.rs @@ -1,7 +1,6 @@ -use crate::tasks::task_spec::TaskSpec; +use bsnext_input::Input; use bsnext_input::route::Spec; use bsnext_input::server_config::ServerIdentity; -use bsnext_input::Input; use std::path::PathBuf; #[derive(Debug, PartialEq, PartialOrd, Ord, Eq, Hash, Clone)] @@ -9,7 +8,6 @@ pub struct ServerWatchable { pub server_identity: ServerIdentity, pub dirs: Vec, pub spec: Spec, - pub task_spec: Option, } pub fn to_server_watchables(input: &Input) -> Vec { @@ -18,29 +16,14 @@ pub fn to_server_watchables(input: &Input) -> Vec { .iter() .flat_map(|server_config| { server_config.watchers.iter().map(|watcher| { - let task_spec = watcher.spec.as_ref().and_then(to_task_spec); let path_bufs = watcher.dirs.as_pathbufs(); ServerWatchable { server_identity: server_config.identity.clone(), dirs: path_bufs, spec: watcher.spec.clone().unwrap_or_default(), - task_spec, } }) }) .collect() } - -/// Convert task items into a sequential execution configuration. -/// tl;dr: Forces tasks to run in sequential order rather than concurrently. -/// -/// Creates a runner that executes tasks strictly one after another to match user -/// expectations when defining task lists in declarative formats (yaml/json). -pub fn to_task_spec(spec: &Spec) -> Option { - // if the 'run' key was given, it's a list of steps. - let run = spec.run.as_ref()?; - - // otherwise, construct a runner - Some(TaskSpec::seq_from(run)) -} diff --git a/crates/bsnext_system/src/watchables/handle_fs_event_grouping.rs b/crates/bsnext_system/src/handle_fs_event_grouping.rs similarity index 89% rename from crates/bsnext_system/src/watchables/handle_fs_event_grouping.rs rename to crates/bsnext_system/src/handle_fs_event_grouping.rs index 9eed873a..737edcda 100644 --- a/crates/bsnext_system/src/watchables/handle_fs_event_grouping.rs +++ b/crates/bsnext_system/src/handle_fs_event_grouping.rs @@ -5,7 +5,6 @@ use crate::system::BsSystem; use crate::tasks::task_comms::TaskComms; use crate::tasks::task_spec::TaskSpec; use crate::tasks::Runnable; -use crate::watchables::path_watchable::PathWatchable; use actix::{Addr, AsyncContext}; use bsnext_core::servers_supervisor::file_changed_handler::FileChanged; use bsnext_dto::external_events::ExternalEventsDTO; @@ -91,12 +90,6 @@ impl BsSystem { .map(|(.., (_addr, meta))| meta) } } - fn path_watchable(&self, incoming: &FsEventContext) -> Option<&PathWatchable> { - self.any_monitors - .iter() - .find(|(.., (_path_monitor, PathMonitorMeta { ref fs_ctx, .. }))| fs_ctx == incoming) - .map(|(pw, ..)| pw) - } #[tracing::instrument(skip_all)] fn handle_buffered( &mut self, @@ -207,23 +200,16 @@ impl BsSystem { #[tracing::instrument(skip_all)] fn task_spec_for_fs_event(&self, fs_event_ctx: &FsEventContext) -> TaskSpec { - let Some(path_watchable) = self.path_watchable(fs_event_ctx) else { - tracing::error!("did not find a matching monitor"); - return TaskSpec::seq(&[]); - }; - - info!("matching monitor, path_watchable: {}", path_watchable); - info!("matching fs_event_ctx: {:?}", fs_event_ctx); - - let custom_task_spec = path_watchable.task_spec(); - if custom_task_spec.is_none() { - info!("no custom tasks given, NotifyServer + ExtEvent will be defaults"); - } - custom_task_spec.map(ToOwned::to_owned).unwrap_or_else(|| { + if let Some(spec) = self.specs.get(fs_event_ctx) { + info!("matching task_spec: {:?}", spec); + info!("matching fs_event_ctx: {:?}", fs_event_ctx); + spec.to_owned() + } else { + info!("creating a default task spec on the fly because fs_event_ctx didn't match"); TaskSpec::seq(&[ Runnable::BsLiveTask(BsLiveBuiltInTask::NotifyServer), Runnable::BsLiveTask(BsLiveBuiltInTask::PublishExternalEvent), ]) - }) + } } } diff --git a/crates/bsnext_system/src/watchables/input_monitor.rs b/crates/bsnext_system/src/input_monitor.rs similarity index 93% rename from crates/bsnext_system/src/watchables/input_monitor.rs rename to crates/bsnext_system/src/input_monitor.rs index d615e801..6cc42e77 100644 --- a/crates/bsnext_system/src/watchables/input_monitor.rs +++ b/crates/bsnext_system/src/input_monitor.rs @@ -1,12 +1,12 @@ use crate::system::BsSystem; -use crate::watchables::any_watchable::AnyWatchable; -use crate::watchables::path_watchable::PathWatchable; use actix::{Actor, Addr, AsyncContext}; use bsnext_fs::{Debounce, FsEventContext}; use bsnext_input::route::Spec; use bsnext_input::InputCtx; use bsnext_monitor::path_monitor::PathMonitor; use bsnext_monitor::path_monitor_meta::PathMonitorMeta; +use bsnext_monitor::watchables::any_watchable::AnyWatchable; +use bsnext_monitor::watchables::path_watchable::PathWatchable; use std::path::PathBuf; use std::time::Duration; @@ -42,7 +42,6 @@ impl actix::Handler for BsSystem { let pw = PathWatchable::Any(AnyWatchable { dirs: paths.clone(), spec: Spec::default(), - task_spec: None, }); let input_path_monitor = PathMonitor::new( diff --git a/crates/bsnext_system/src/lib.rs b/crates/bsnext_system/src/lib.rs index 7e0e4054..6ab29e7f 100644 --- a/crates/bsnext_system/src/lib.rs +++ b/crates/bsnext_system/src/lib.rs @@ -5,8 +5,11 @@ pub mod cli; mod export; mod external_event_sender; mod fs_task_tracker; +mod handle_fs_event_grouping; pub mod input_fs; +pub mod input_monitor; mod invoke_scope; +pub mod monitor_path_watchables; pub mod override_input; pub mod run; mod servers; @@ -14,4 +17,3 @@ pub mod start; mod system; pub mod tasks; pub mod watch; -pub mod watchables; diff --git a/crates/bsnext_system/src/watchables/monitor_path_watchables.rs b/crates/bsnext_system/src/monitor_path_watchables.rs similarity index 78% rename from crates/bsnext_system/src/watchables/monitor_path_watchables.rs rename to crates/bsnext_system/src/monitor_path_watchables.rs index 08491722..81126cfe 100644 --- a/crates/bsnext_system/src/watchables/monitor_path_watchables.rs +++ b/crates/bsnext_system/src/monitor_path_watchables.rs @@ -1,22 +1,16 @@ use crate::system::BsSystem; -use crate::watchables::path_watchable::PathWatchable; +use crate::tasks::task_spec::TaskSpec; use actix::{Actor, Addr, AsyncContext}; use bsnext_fs::{Debounce, FsEventContext}; use bsnext_input::route::{DebounceDuration, Spec}; use bsnext_monitor::path_monitor::{PathMonitor, StopPathMonitor}; use bsnext_monitor::path_monitor_meta::PathMonitorMeta; +use bsnext_monitor::watchables::path_watchable::PathWatchable; +use bsnext_monitor::watchables::MonitorPathWatchables; use std::collections::BTreeSet; -use std::path::PathBuf; use std::time::Duration; use tracing::{debug, debug_span}; -#[derive(actix::Message)] -#[rtype(result = "()")] -pub struct MonitorPathWatchables { - pub watchables: Vec, - pub cwd: PathBuf, -} - impl actix::Handler for BsSystem { type Result = (); @@ -116,11 +110,33 @@ struct InsertMonitor(PathWatchable, Addr, PathMonitorMeta); impl actix::Handler for BsSystem { type Result = (); - fn handle(&mut self, msg: InsertMonitor, _ctx: &mut Self::Context) -> Self::Result { + fn handle( + &mut self, + InsertMonitor(watchable, addr, meta): InsertMonitor, + _ctx: &mut Self::Context, + ) -> Self::Result { let span = debug_span!("InsertMonitor"); let _guard = span.enter(); - debug!("{}", msg.0); - self.any_monitors.insert(msg.0, (msg.1, msg.2)); + debug!("{}", watchable); + let fs_ctx = meta.fs_ctx; + let task_spec = to_task_spec(&meta.spec); + self.any_monitors.insert(watchable, (addr, meta)); + if let Some(spec) = task_spec { + self.specs.insert(fs_ctx, spec); + } debug!("+ Monitor count {}", self.any_monitors.len()); } } + +/// Convert task items into a sequential execution configuration. +/// tl;dr: Forces tasks to run in sequential order rather than concurrently. +/// +/// Creates a runner that executes tasks strictly one after another to match user +/// expectations when defining task lists in declarative formats (yaml/json). +pub fn to_task_spec(spec: &Spec) -> Option { + // if the 'run' key was given, it's a list of steps. + let run = spec.run.as_ref()?; + + // otherwise, construct a runner + Some(TaskSpec::seq_from(run)) +} diff --git a/crates/bsnext_system/src/override_input.rs b/crates/bsnext_system/src/override_input.rs index 55e9b75b..81bdd08b 100644 --- a/crates/bsnext_system/src/override_input.rs +++ b/crates/bsnext_system/src/override_input.rs @@ -5,6 +5,7 @@ use bsnext_dto::internal::{AnyEvent, ChildResult, ServerError}; use bsnext_dto::GetActiveServersResponse; use bsnext_input::startup::StartupContext; use bsnext_input::{Input, InputCtx}; +use bsnext_monitor::watchables::accept_watchables; use tracing::debug; #[derive(Debug, actix::Message)] @@ -35,7 +36,7 @@ impl actix::Handler for BsSystem { Err(err) => Err(ServerError::Unknown(err.to_string())), }; // todo: only process the override if valid? - actor.accept_watchables(&input_clone, addr); + accept_watchables(actor.cwd.clone(), &input_clone, addr.recipient()); actor.update_ctx(&input_clone, &start_ctx_clone); output }); diff --git a/crates/bsnext_system/src/start/start_system.rs b/crates/bsnext_system/src/start/start_system.rs index a07ea7a4..bdf28bc1 100644 --- a/crates/bsnext_system/src/start/start_system.rs +++ b/crates/bsnext_system/src/start/start_system.rs @@ -1,11 +1,11 @@ #![allow(clippy::result_large_err)] use crate::api::BsSystemApi; +use crate::input_monitor::MonitorInput; use crate::start::start_kind::StartKind; use crate::system::{ run_jobs, setup_jobs_only, setup_servers_only, BsSystem, RunDryOk, RunOk, SetupOk, SetupServersOk, }; -use crate::watchables::input_monitor::MonitorInput; use actix::{ Actor, ActorContext, ActorFutureExt, AsyncContext, Handler, ResponseActFuture, WrapFuture, }; @@ -13,6 +13,7 @@ use bsnext_dto::internal::{AnyEvent, ChildResult, InternalEvents}; use bsnext_dto::{DidStart, StartupError}; use bsnext_input::startup::{RunMode, SystemStart, SystemStartArgs}; use bsnext_input::InputCtx; +use bsnext_monitor::watchables::accept_watchables; use std::future::ready; use std::path::PathBuf; use tokio::sync::oneshot; @@ -79,7 +80,7 @@ impl Handler for BsSystem { input_ctx, }); // todo: where to better sequence these side-effects? - actor.accept_watchables(&input, addr); + accept_watchables(actor.cwd.clone(), &input, addr.recipient()); Ok(DidStart::Started(servers)) }, )) @@ -99,7 +100,7 @@ impl Handler for BsSystem { debug!("errored: {:?}", errored); return Err(StartupError::ServerError((*server_error).to_owned())); } - actor.accept_watchables(&res.input, addr); + accept_watchables(actor.cwd.clone(), &res.input, addr.recipient()); Ok(DidStart::Started(res.servers)) }, )) @@ -137,7 +138,7 @@ impl Handler for BsSystem { debug!("errored: {:?}", errored); return Err(StartupError::ServerError((*server_error).to_owned())); } - actor.accept_watchables(&res.input, addr); + accept_watchables(actor.cwd.clone(), &res.input, addr.recipient()); Ok(DidStart::Started(res.servers)) }, )) diff --git a/crates/bsnext_system/src/system.rs b/crates/bsnext_system/src/system.rs index 06f9cef3..016b93a6 100644 --- a/crates/bsnext_system/src/system.rs +++ b/crates/bsnext_system/src/system.rs @@ -1,22 +1,23 @@ use crate::capabilities::Capabilities; use crate::fs_task_tracker::FsTaskTracker; +use crate::input_monitor::InputMonitor; use crate::invoke_scope::{InvokeScope, Invoker}; use crate::run::resolve_spec::{InvokeRunTasks, ResolveSpec}; use crate::servers::ResolveServers; use crate::tasks::resolve::ResolveInitialTasks; use crate::tasks::task_spec::TaskSpec; -use crate::watchables::input_monitor::InputMonitor; -use crate::watchables::path_watchable::PathWatchable; use actix::{Actor, Addr, AsyncContext, ResponseFuture, Running}; use actix_rt::Arbiter; use bsnext_core::servers_supervisor::actor::ServersSupervisor; use bsnext_dto::external_events::{ExternalEventsDTO, TaskTreePreview, TaskTreeSummary}; use bsnext_dto::internal::{AnyEvent, ChildResult, TaskReportAndTree}; use bsnext_dto::GetActiveServersResponse; +use bsnext_fs::FsEventContext; use bsnext_input::startup::{StartupContext, TopLevelRunMode}; use bsnext_input::Input; use bsnext_monitor::path_monitor::PathMonitor; use bsnext_monitor::path_monitor_meta::PathMonitorMeta; +use bsnext_monitor::watchables::path_watchable::PathWatchable; use bsnext_task::task_trigger::{ExecTrigger, TaskTrigger, TaskTriggerSource}; use std::collections::HashMap; use std::path::PathBuf; @@ -31,6 +32,7 @@ pub struct BsSystem { servers_addr: Addr, any_event_sender: Sender, pub(crate) input_monitors: Option, + pub(crate) specs: HashMap, pub(crate) any_monitors: HashMap, PathMonitorMeta)>, pub(crate) fs_task_tracker: Addr, pub(crate) invoker_addr: Addr, @@ -96,6 +98,7 @@ impl BsSystem { fs_task_tracker, cwd, start_context, + specs: HashMap::default(), } } diff --git a/crates/bsnext_system/src/watchables/mod.rs b/crates/bsnext_system/src/watchables/mod.rs deleted file mode 100644 index eb44e86d..00000000 --- a/crates/bsnext_system/src/watchables/mod.rs +++ /dev/null @@ -1,73 +0,0 @@ -use crate::system::BsSystem; -use crate::watchables::any_watchable::to_any_watchables; -use crate::watchables::path_watchable::PathWatchable; -use crate::watchables::route_watchable::to_route_watchables; -use crate::watchables::server_watchable::to_server_watchables; -use actix::Addr; -use bsnext_input::{InferWatchers, Input, WatchGlobalConfig}; -use monitor_path_watchables::MonitorPathWatchables; -use tracing::debug; - -pub mod any_watchable; -mod handle_fs_event_grouping; -pub mod input_monitor; -pub mod monitor_path_watchables; -pub mod path_watchable; -pub mod route_watchable; -pub mod server_watchable; - -impl BsSystem { - #[tracing::instrument(skip_all, name = "BsSystem.accept_watchables")] - pub(crate) fn accept_watchables(&mut self, input: &Input, addr: Addr) { - let route_watchables = to_route_watchables(input); - let server_watchables = to_server_watchables(input); - let any_watchables = to_any_watchables(input); - - let routes = route_watchables - .iter() - .map(|r| PathWatchable::Route(r.to_owned())); - - let servers = server_watchables - .iter() - .map(|w| PathWatchable::Server(w.to_owned())); - - let any = any_watchables - .iter() - .map(|w| PathWatchable::Any(w.to_owned())); - - let watchables: Vec<_> = match &input.config.watchers { - WatchGlobalConfig::Enabled { infer } => match infer { - InferWatchers::None => { - debug!("processing {} any watchables", any.len()); - any.collect() - } - InferWatchers::Routes => { - debug!("processing {} route watchables", routes.len()); - debug!("processing {} any watchables", any.len()); - routes.chain(any).collect() - } - InferWatchers::Servers => { - debug!("processing {} server watchables", servers.len()); - debug!("processing {} any watchables", any.len()); - servers.chain(any).collect() - } - InferWatchers::RoutesAndServers => { - debug!("processing {} route watchables", routes.len()); - debug!("processing {} server watchables", servers.len()); - debug!("processing {} any watchables", any.len()); - routes.chain(servers).chain(any).collect() - } - }, - WatchGlobalConfig::Disabled => vec![], - }; - - let cwd = self.cwd.clone(); - debug!( - "{} watchables to add, cwd: {}", - watchables.len(), - cwd.display() - ); - let msg = MonitorPathWatchables { watchables, cwd }; - addr.do_send(msg); - } -} From 4a0863e8eee9d361465821009b82e501e669785b Mon Sep 17 00:00:00 2001 From: Shane Osbourne Date: Sun, 26 Apr 2026 22:48:41 +0100 Subject: [PATCH 03/10] refactor --- Cargo.lock | 1 + crates/bsnext_fs/examples/watch_cmd.rs | 2 - crates/bsnext_fs/src/lib.rs | 27 +----- crates/bsnext_input/Cargo.toml | 1 + crates/bsnext_input/src/input_test/mod.rs | 8 +- crates/bsnext_input/src/route.rs | 32 +++++++- crates/bsnext_input/src/watch_opt_test/mod.rs | 30 +++---- crates/bsnext_input/src/watch_opts.rs | 6 +- .../examples/path_monitor_example.rs | 7 +- crates/bsnext_monitor/src/lib.rs | 65 +++++++++++++++ .../src/monitor_path_watchables.rs | 82 +++++++------------ crates/bsnext_monitor/src/path_monitor.rs | 40 +++++---- .../bsnext_monitor/src/path_monitor_meta.rs | 6 +- .../src/watchables/any_watchable.rs | 6 +- crates/bsnext_monitor/src/watchables/mod.rs | 16 +++- .../src/watchables/path_watchable.rs | 10 +-- .../src/watchables/route_watchable.rs | 35 +++----- .../src/watchables/server_watchable.rs | 6 +- crates/bsnext_system/src/api.rs | 10 ++- .../src/handle_fs_event_grouping.rs | 66 ++++++++------- crates/bsnext_system/src/lib.rs | 5 +- crates/bsnext_system/src/monitor_any.rs | 66 +++++++++++++++ .../{input_monitor.rs => monitor_input.rs} | 4 +- crates/bsnext_system/src/mutable_state.rs | 3 + .../bsnext_system/src/start/start_system.rs | 15 ++-- crates/bsnext_system/src/system.rs | 16 ++-- crates/bsnext_system/src/tasks/task_spec.rs | 8 +- .../bsnext_system/src/watch/watch_sub_opts.rs | 6 +- 28 files changed, 357 insertions(+), 222 deletions(-) rename crates/{bsnext_system => bsnext_monitor}/src/monitor_path_watchables.rs (63%) create mode 100644 crates/bsnext_system/src/monitor_any.rs rename crates/bsnext_system/src/{input_monitor.rs => monitor_input.rs} (95%) create mode 100644 crates/bsnext_system/src/mutable_state.rs diff --git a/Cargo.lock b/Cargo.lock index c5b43fcd..123e2ffe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -593,6 +593,7 @@ name = "bsnext_input" version = "0.26.0" dependencies = [ "anyhow", + "bsnext_fs", "bsnext_fs_helpers", "bsnext_guards", "bsnext_resp", diff --git a/crates/bsnext_fs/examples/watch_cmd.rs b/crates/bsnext_fs/examples/watch_cmd.rs index ddece452..f2337b75 100644 --- a/crates/bsnext_fs/examples/watch_cmd.rs +++ b/crates/bsnext_fs/examples/watch_cmd.rs @@ -31,7 +31,6 @@ async fn main() -> anyhow::Result<()> { #[derive(Default)] struct Example { - run_count: usize, running: bool, cmd: Cmd, } @@ -40,7 +39,6 @@ impl Example { pub fn from_str>(cmd: A) -> anyhow::Result { Ok(Self { running: false, - run_count: 0, cmd: Cmd(OsString::try_from(cmd.as_ref())?), }) } diff --git a/crates/bsnext_fs/src/lib.rs b/crates/bsnext_fs/src/lib.rs index d29e6e1c..68573d3a 100644 --- a/crates/bsnext_fs/src/lib.rs +++ b/crates/bsnext_fs/src/lib.rs @@ -19,7 +19,7 @@ pub enum Debounce { impl Default for Debounce { fn default() -> Self { - Self::Trailing { + Self::Buffered { duration: Duration::from_millis(300), } } @@ -171,29 +171,10 @@ impl<'a> From<&'a PathDescription<'_>> for PathDescriptionOwned { } } -#[derive(actix::Message, Debug, Clone)] -#[rtype(result = "()")] -pub enum FsEventGrouping { - Singular(FsEvent), - BufferedChange(BufferedChangeEvent), -} - -impl FsEventGrouping { - pub fn buffered_change( - events: Vec, - fs_event_context: FsEventContext, - ) -> Self { - Self::BufferedChange(BufferedChangeEvent { - events, - fs_ctx: fs_event_context, - }) - } -} - #[derive(Debug, Clone)] pub struct BufferedChangeEvent { pub events: Vec, - pub fs_ctx: FsEventContext, + pub fs_event_ctx: FsEventContext, } impl BufferedChangeEvent { @@ -206,12 +187,12 @@ impl BufferedChangeEvent { .filter(|x| x.absolute != path) .map(ToOwned::to_owned) .collect(), - fs_ctx: self.fs_ctx, + fs_event_ctx: self.fs_event_ctx, } } else { Self { events: self.events, - fs_ctx: self.fs_ctx, + fs_event_ctx: self.fs_event_ctx, } } } diff --git a/crates/bsnext_input/Cargo.toml b/crates/bsnext_input/Cargo.toml index fb06af76..864f9546 100644 --- a/crates/bsnext_input/Cargo.toml +++ b/crates/bsnext_input/Cargo.toml @@ -8,6 +8,7 @@ edition = "2021" [dependencies] bsnext_resp = { path = "../bsnext_resp" } bsnext_fs_helpers = { path = "../bsnext_fs_helpers" } +bsnext_fs = { path = "../bsnext_fs" } bsnext_tracing = { path = "../bsnext_tracing" } bsnext_guards = { path = "../bsnext_guards" } shell-words = { version = "1.1.0" } diff --git a/crates/bsnext_input/src/input_test/mod.rs b/crates/bsnext_input/src/input_test/mod.rs index 32153f46..c8f172dd 100644 --- a/crates/bsnext_input/src/input_test/mod.rs +++ b/crates/bsnext_input/src/input_test/mod.rs @@ -1,6 +1,6 @@ use crate::route::{ CompressionOpts, CorsOpts, DebounceDuration, DelayKind, DelayOpts, MultiWatch, PathPattern, - Route, Spec, WatcherDirs, + Route, WatchSpec, WatcherDirs, }; use crate::watch_opts::WatchOpts; use crate::Input; @@ -213,7 +213,7 @@ fn test_deserialize_watch() { let c: Route = serde_yaml::from_str(input).unwrap(); assert_eq!( c.opts.watch, - WatchOpts::Spec(Spec { + WatchOpts::Spec(WatchSpec { debounce: Some(DebounceDuration::Ms(2000)), only: None, ignore: None, @@ -242,11 +242,11 @@ servers: vec![ MultiWatch { dirs: WatcherDirs::Single("./".to_string()), - spec: Some(Spec::default()) + spec: Some(WatchSpec::default()) }, MultiWatch { dirs: WatcherDirs::Single("./other".to_string()), - spec: Some(Spec { + spec: Some(WatchSpec { debounce: Some(DebounceDuration::Ms(2000)), only: Some(PathPattern::Extension { ext: "**/*.css".to_string() diff --git a/crates/bsnext_input/src/route.rs b/crates/bsnext_input/src/route.rs index a6fd12cd..0465cdac 100644 --- a/crates/bsnext_input/src/route.rs +++ b/crates/bsnext_input/src/route.rs @@ -3,6 +3,8 @@ use crate::path_def::PathDef; use crate::route_cli::RouteCli; use crate::watch_opts::WatchOpts; use crate::when_guard::{WhenBodyGuard, WhenGuard}; +use crate::InputConfig; +use bsnext_fs::Debounce; use bsnext_resp::cache_opts::CacheOpts; use bsnext_resp::inject_opts::InjectOpts; use matchit::InsertError; @@ -12,6 +14,7 @@ use std::hash::{Hash, Hasher}; use std::ops::Deref; use std::path::PathBuf; use std::str::FromStr; +use std::time::Duration; #[derive(Debug, PartialEq, Hash, Clone, serde::Deserialize, serde::Serialize)] pub struct Route { @@ -299,6 +302,16 @@ pub enum DebounceDuration { Ms(u64), } +impl From for Debounce { + fn from(debounce_duration: DebounceDuration) -> Self { + match debounce_duration { + DebounceDuration::Ms(ms) => Debounce::Buffered { + duration: Duration::from_millis(ms), + }, + } + } +} + #[derive( Debug, Ord, PartialOrd, PartialEq, Eq, Hash, Clone, serde::Deserialize, serde::Serialize, )] @@ -342,7 +355,7 @@ impl FromStr for PathPattern { serde::Deserialize, serde::Serialize, )] -pub struct Spec { +pub struct WatchSpec { pub debounce: Option, pub only: Option, pub ignore: Option, @@ -350,6 +363,21 @@ pub struct Spec { pub before: Option>, } +impl WatchSpec { + pub fn with_globals(mut self, config: &InputConfig) -> Self { + // respect a given spec's 'ignore' (eg: if provided by user), otherwise try to use + self.ignore = self.ignore.or_else(|| config.global_fs_ignore.to_owned()); + + // respect a given spec's 'only' (eg: if provided by user), otherwise try to use + self.only = self.only.or_else(|| config.global_fs_only.to_owned()); + + // respect a given spec's 'debounce' (eg: if provided by user), otherwise try to use the global + self.debounce = self.debounce.or(config.global_fs_debounce); + + self + } +} + #[derive( Debug, Default, @@ -540,7 +568,7 @@ impl Default for PrefixOpt { pub struct MultiWatch { pub dirs: WatcherDirs, #[serde(flatten)] - pub spec: Option, + pub spec: Option, } #[derive( diff --git a/crates/bsnext_input/src/watch_opt_test/mod.rs b/crates/bsnext_input/src/watch_opt_test/mod.rs index cf9210ea..a3a7fc8d 100644 --- a/crates/bsnext_input/src/watch_opt_test/mod.rs +++ b/crates/bsnext_input/src/watch_opt_test/mod.rs @@ -1,6 +1,6 @@ use crate::route::{ DebounceDuration, PathPattern, RunAll, RunAllOpts, RunOptItem, RunSeq, SeqOpts, ShRunOptItem, - Spec, + WatchSpec, }; use crate::watch_opts::WatchOpts; @@ -11,7 +11,7 @@ fn test_watch_opts_debounce() { ms: 200 only: "**/*.css" "#; - let expected = WatchOpts::Spec(Spec { + let expected = WatchOpts::Spec(WatchSpec { debounce: Some(DebounceDuration::Ms(200)), only: Some(PathPattern::StringDefault("**/*.css".into())), ignore: None, @@ -27,7 +27,7 @@ fn test_watch_opts_inline_filter() { let input = r#" only: "**/*.css" "#; - let expected = WatchOpts::Spec(Spec { + let expected = WatchOpts::Spec(WatchSpec { debounce: None, only: Some(PathPattern::StringDefault("**/*.css".into())), ignore: None, @@ -44,7 +44,7 @@ fn test_watch_opts_explicit_filter_ext() { only: ext: "css" "#; - let expected = WatchOpts::Spec(Spec { + let expected = WatchOpts::Spec(WatchSpec { debounce: None, only: Some(PathPattern::Extension { ext: "css".to_string(), @@ -62,7 +62,7 @@ fn test_watch_opts_explicit_filter_glob() { only: glob: "**/*.css" "#; - let expected = WatchOpts::Spec(Spec { + let expected = WatchOpts::Spec(WatchSpec { debounce: None, only: Some(PathPattern::Glob { glob: "**/*.css".into(), @@ -83,7 +83,7 @@ fn test_watch_opts_run_seq() { - sh: echo 2 - sh: echo 3 "#; - let expected = Spec { + let expected = WatchSpec { run: Some(vec![ RunOptItem::Sh(ShRunOptItem::new("echo 1")), RunOptItem::Sh(ShRunOptItem::new("echo 2")), @@ -91,7 +91,7 @@ fn test_watch_opts_run_seq() { ]), ..Default::default() }; - let actual: Spec = serde_yaml::from_str(input).unwrap(); + let actual: WatchSpec = serde_yaml::from_str(input).unwrap(); assert_eq!(expected, actual); } @@ -104,7 +104,7 @@ fn test_watch_opts_run_all() { - sh: echo 2 - sh: echo 3 "#; - let expected = Spec { + let expected = WatchSpec { run: Some(vec![RunOptItem::All(RunAll::new(vec![ RunOptItem::Sh(ShRunOptItem::new("echo 1")), RunOptItem::Sh(ShRunOptItem::new("echo 2")), @@ -112,7 +112,7 @@ fn test_watch_opts_run_all() { ]))]), ..Default::default() }; - let actual: Spec = serde_yaml::from_str(input).unwrap(); + let actual: WatchSpec = serde_yaml::from_str(input).unwrap(); assert_eq!(expected, actual); } @@ -126,7 +126,7 @@ fn test_watch_opts_run_all_nested() { - sh: echo 3 - sh: echo 4 "#; - let expected = Spec { + let expected = WatchSpec { run: Some(vec![ RunOptItem::Sh(ShRunOptItem::new("echo 1")), RunOptItem::All(RunAll::new(vec![ @@ -137,7 +137,7 @@ fn test_watch_opts_run_all_nested() { ]), ..Default::default() }; - let actual: Spec = serde_yaml::from_str(input).unwrap(); + let actual: WatchSpec = serde_yaml::from_str(input).unwrap(); assert_eq!(expected, actual); } @@ -153,7 +153,7 @@ fn test_watch_opts_run_all_max_concurrency() { opts: max: 10 "#; - let expected = Spec { + let expected = WatchSpec { run: Some(vec![ RunOptItem::Sh(ShRunOptItem::new("echo 1")), RunOptItem::All(RunAll::with_opts( @@ -167,7 +167,7 @@ fn test_watch_opts_run_all_max_concurrency() { ]), ..Default::default() }; - let actual: Spec = serde_yaml::from_str(input).unwrap(); + let actual: WatchSpec = serde_yaml::from_str(input).unwrap(); assert_eq!(actual, expected); } @@ -182,7 +182,7 @@ fn test_watch_opts_run_seq_exit() { opts: exit_on_fail: false "#; - let expected = Spec { + let expected = WatchSpec { run: Some(vec![RunOptItem::Seq(RunSeq::with_opts( vec![ RunOptItem::Sh(ShRunOptItem::new("echo 2")), @@ -195,6 +195,6 @@ fn test_watch_opts_run_seq_exit() { ))]), ..Default::default() }; - let actual: Spec = serde_yaml::from_str(input).unwrap(); + let actual: WatchSpec = serde_yaml::from_str(input).unwrap(); assert_eq!(actual, expected); } diff --git a/crates/bsnext_input/src/watch_opts.rs b/crates/bsnext_input/src/watch_opts.rs index 67b02716..2951932a 100644 --- a/crates/bsnext_input/src/watch_opts.rs +++ b/crates/bsnext_input/src/watch_opts.rs @@ -1,11 +1,11 @@ -use crate::route::Spec; +use crate::route::WatchSpec; #[derive(Debug, PartialEq, Hash, Clone, serde::Deserialize, serde::Serialize)] #[serde(untagged)] pub enum WatchOpts { Bool(bool), InlineGlob(String), - Spec(Spec), + Spec(WatchSpec), } impl Default for WatchOpts { @@ -18,7 +18,7 @@ impl WatchOpts { pub fn is_enabled(&self) -> bool { !matches!(self, WatchOpts::Bool(false)) } - pub fn spec(&self) -> Option<&Spec> { + pub fn spec(&self) -> Option<&WatchSpec> { match self { WatchOpts::Bool(_) => None, WatchOpts::InlineGlob(_) => None, diff --git a/crates/bsnext_monitor/examples/path_monitor_example.rs b/crates/bsnext_monitor/examples/path_monitor_example.rs index 5e13abf3..a8372099 100644 --- a/crates/bsnext_monitor/examples/path_monitor_example.rs +++ b/crates/bsnext_monitor/examples/path_monitor_example.rs @@ -1,7 +1,8 @@ use actix::{Actor, ResponseFuture}; use actix_rt::System; -use bsnext_fs::{FsEvent, FsEventContext, FsEventGrouping}; -use bsnext_input::route::{DebounceDuration, Spec}; +use bsnext_fs::{FsEvent, FsEventContext}; +use bsnext_input::route::{DebounceDuration, WatchSpec}; +use bsnext_monitor::FsEventGrouping; use bsnext_monitor::path_monitor::PathMonitor; use std::env::current_dir; use std::process; @@ -53,7 +54,7 @@ async fn async_main() -> i32 { let reciever = actor.clone().recipient(); let cwd = current_dir().unwrap(); let fs_context = FsEventContext::default(); - let spec = Spec { + let spec = WatchSpec { debounce: Some(DebounceDuration::Ms(0)), ..Default::default() }; diff --git a/crates/bsnext_monitor/src/lib.rs b/crates/bsnext_monitor/src/lib.rs index 70ca9808..8d78c3e0 100644 --- a/crates/bsnext_monitor/src/lib.rs +++ b/crates/bsnext_monitor/src/lib.rs @@ -1,3 +1,68 @@ +use crate::path_monitor::PathMonitor; +use crate::path_monitor_meta::PathMonitorMeta; +use crate::watchables::path_watchable::PathWatchable; +use actix::Addr; +use bsnext_fs::{BufferedChangeEvent, Debounce, FsEvent, FsEventContext, PathDescriptionOwned}; +use bsnext_input::route::WatchSpec; +use std::collections::HashMap; + +pub mod monitor_path_watchables; pub mod path_monitor; pub mod path_monitor_meta; pub mod watchables; + +#[derive(Debug, Default)] +pub struct Monitor { + any_monitors: HashMap, PathMonitorMeta)>, +} + +impl Monitor { + pub fn new() -> Self { + Self { + any_monitors: Default::default(), + } + } +} + +impl actix::Actor for Monitor { + type Context = actix::Context; +} + +#[derive(actix::Message, Debug, Clone)] +#[rtype(result = "()")] +pub struct FsEventGrouping { + pub watch_spec: WatchSpec, + pub debounce: Debounce, + pub group: Group, +} + +#[derive(Debug, Clone)] +pub enum Group { + Singular(FsEvent), + BufferedChange(BufferedChangeEvent), +} + +impl FsEventGrouping { + pub fn singular(evt: FsEvent, watch_spec: WatchSpec, debounce: Debounce) -> Self { + FsEventGrouping { + debounce, + group: Group::Singular(evt), + watch_spec, + } + } + pub fn buffered_change( + events: Vec, + fs_event_ctx: FsEventContext, + watch_spec: WatchSpec, + debounce: Debounce, + ) -> Self { + Self { + group: Group::BufferedChange(BufferedChangeEvent { + events, + fs_event_ctx, + }), + watch_spec, + debounce, + } + } +} diff --git a/crates/bsnext_system/src/monitor_path_watchables.rs b/crates/bsnext_monitor/src/monitor_path_watchables.rs similarity index 63% rename from crates/bsnext_system/src/monitor_path_watchables.rs rename to crates/bsnext_monitor/src/monitor_path_watchables.rs index 81126cfe..ded3875d 100644 --- a/crates/bsnext_system/src/monitor_path_watchables.rs +++ b/crates/bsnext_monitor/src/monitor_path_watchables.rs @@ -1,20 +1,29 @@ -use crate::system::BsSystem; -use crate::tasks::task_spec::TaskSpec; -use actix::{Actor, Addr, AsyncContext}; +use crate::Monitor; +use crate::path_monitor::{PathMonitor, StopPathMonitor}; +use crate::path_monitor_meta::PathMonitorMeta; +use crate::watchables::MonitorPathWatchables; +use crate::watchables::path_watchable::PathWatchable; +use actix::{Actor, Addr, AsyncContext, ResponseFuture}; use bsnext_fs::{Debounce, FsEventContext}; -use bsnext_input::route::{DebounceDuration, Spec}; -use bsnext_monitor::path_monitor::{PathMonitor, StopPathMonitor}; -use bsnext_monitor::path_monitor_meta::PathMonitorMeta; -use bsnext_monitor::watchables::path_watchable::PathWatchable; -use bsnext_monitor::watchables::MonitorPathWatchables; use std::collections::BTreeSet; -use std::time::Duration; use tracing::{debug, debug_span}; -impl actix::Handler for BsSystem { - type Result = (); +#[derive(actix::Message)] +#[rtype(result = "()")] +pub struct DropMonitor(pub PathWatchable); + +#[derive(actix::Message)] +#[rtype(result = "()")] +pub struct InsertMonitor( + pub PathWatchable, + pub Addr, + pub PathMonitorMeta, +); + +impl actix::Handler for Monitor { + type Result = ResponseFuture<()>; - #[tracing::instrument(skip_all, name = "MonitorPathWatchables->BsSystem")] + #[tracing::instrument(skip_all, name = "MonitorPathWatchables->Monitor")] fn handle(&mut self, msg: MonitorPathWatchables, ctx: &mut Self::Context) -> Self::Result { debug!("{}", file!()); @@ -58,20 +67,11 @@ impl actix::Handler for BsSystem { let spec = any_watchable.spec(); tracing::trace!(?spec); - let duration = match spec { - Spec { - debounce: Some(DebounceDuration::Ms(ms)), - .. - } => Duration::from_millis(*ms), - _ => Duration::from_millis(300), - }; - tracing::trace!(?duration); - - let debounce = Debounce::Buffered { duration }; + let debounce = spec.debounce.map(Debounce::from).unwrap_or_default(); tracing::trace!(?debounce); let monitor = PathMonitor::new( - ctx.address().recipient(), + msg.recipient.clone(), debounce, msg.cwd.clone(), fs_ctx, @@ -82,18 +82,16 @@ impl actix::Handler for BsSystem { let meta = PathMonitorMeta::from(&monitor); tracing::trace!(?meta); - let monitor = monitor.start(); + let monitor_addr = monitor.start(); - ctx.notify(InsertMonitor((*any_watchable).clone(), monitor, meta)) + ctx.notify(InsertMonitor((*any_watchable).clone(), monitor_addr, meta)) } + + Box::pin(async move {}) } } -#[derive(actix::Message)] -#[rtype(result = "()")] -struct DropMonitor(PathWatchable); - -impl actix::Handler for BsSystem { +impl actix::Handler for Monitor { type Result = (); fn handle(&mut self, msg: DropMonitor, _ctx: &mut Self::Context) -> Self::Result { @@ -103,11 +101,7 @@ impl actix::Handler for BsSystem { } } -#[derive(actix::Message)] -#[rtype(result = "()")] -struct InsertMonitor(PathWatchable, Addr, PathMonitorMeta); - -impl actix::Handler for BsSystem { +impl actix::Handler for Monitor { type Result = (); fn handle( @@ -118,25 +112,7 @@ impl actix::Handler for BsSystem { let span = debug_span!("InsertMonitor"); let _guard = span.enter(); debug!("{}", watchable); - let fs_ctx = meta.fs_ctx; - let task_spec = to_task_spec(&meta.spec); self.any_monitors.insert(watchable, (addr, meta)); - if let Some(spec) = task_spec { - self.specs.insert(fs_ctx, spec); - } debug!("+ Monitor count {}", self.any_monitors.len()); } } - -/// Convert task items into a sequential execution configuration. -/// tl;dr: Forces tasks to run in sequential order rather than concurrently. -/// -/// Creates a runner that executes tasks strictly one after another to match user -/// expectations when defining task lists in declarative formats (yaml/json). -pub fn to_task_spec(spec: &Spec) -> Option { - // if the 'run' key was given, it's a list of steps. - let run = spec.run.as_ref()?; - - // otherwise, construct a runner - Some(TaskSpec::seq_from(run)) -} diff --git a/crates/bsnext_monitor/src/path_monitor.rs b/crates/bsnext_monitor/src/path_monitor.rs index cb0a929f..594eeeb2 100644 --- a/crates/bsnext_monitor/src/path_monitor.rs +++ b/crates/bsnext_monitor/src/path_monitor.rs @@ -1,3 +1,4 @@ +use crate::FsEventGrouping; use actix::{ActorContext, Addr, AsyncContext, Context, Handler, Recipient, StreamHandler}; use actix_rt::Arbiter; use bsnext_fs::actor::FsWatcher; @@ -7,10 +8,9 @@ use bsnext_fs::stop_handler::StopWatcher; use bsnext_fs::stream::StreamOpsExt; use bsnext_fs::watch_path_handler::RequestWatchPath; use bsnext_fs::{ - Debounce, FsEvent, FsEventContext, FsEventGrouping, FsEventKind, PathDescription, - PathDescriptionOwned, + Debounce, FsEvent, FsEventContext, FsEventKind, PathDescription, PathDescriptionOwned, }; -use bsnext_input::route::{PathPattern, Spec}; +use bsnext_input::route::{PathPattern, WatchSpec}; use std::collections::BTreeSet; use std::path::{Path, PathBuf}; use tokio::sync::mpsc; @@ -24,7 +24,7 @@ pub struct PathMonitor { pub(crate) recipient: Recipient, pub(crate) fs_ctx: FsEventContext, pub(crate) debounce: Debounce, - pub spec: Spec, + pub watch_spec: WatchSpec, pub watch_paths: Vec, inner_sender: tokio::sync::mpsc::Sender, inner_receiver: Option>, @@ -32,21 +32,21 @@ pub struct PathMonitor { impl PathMonitor { pub fn new( - sys: Recipient, + recipient: Recipient, debounce: Debounce, cwd: PathBuf, fs_ctx: FsEventContext, - spec: Spec, + watch_spec: WatchSpec, watch_paths: Vec, ) -> Self { let (inner_sender, inner_receiver) = mpsc::channel::(1); Self { - recipient: sys, + recipient, debounce, cwd, addrs: vec![], fs_ctx, - spec, + watch_spec, watch_paths, inner_sender, inner_receiver: Some(inner_receiver), @@ -66,7 +66,7 @@ impl actix::Actor for PathMonitor { let mut filters = filter_kind.into_iter().collect::>(); // additional filter from options? - if let Some(filter) = &self.spec.only { + if let Some(filter) = &self.watch_spec.only { filters.push(filter.clone()); } @@ -74,7 +74,7 @@ impl actix::Actor for PathMonitor { let watcher = to_watcher( &self.cwd, Some(&PathPattern::List(filters)), - self.spec.ignore.as_ref(), + self.watch_spec.ignore.as_ref(), self.fs_ctx, ctx.address().recipient(), ); @@ -107,12 +107,18 @@ impl actix::Actor for PathMonitor { impl StreamHandler for PathMonitor { fn handle(&mut self, event: FsEvent, _ctx: &mut Context) { - self.recipient.do_send(FsEventGrouping::Singular(event)) + debug!("StreamHandler for PathMonitor"); + self.recipient.do_send(FsEventGrouping::singular( + event, + self.watch_spec.clone(), + self.debounce, + )) } } impl StreamHandler> for PathMonitor { fn handle(&mut self, events: Vec, _ctx: &mut Context) { + debug!("StreamHandler> for PathMonitor"); debug!(" └ got {} events to process", events.len()); // let now = Instant::now(); // let original_len = events.len(); @@ -127,8 +133,12 @@ impl StreamHandler> for PathMonitor { _ => None, }) .collect::>(); - self.recipient - .do_send(FsEventGrouping::buffered_change(outgoing, self.fs_ctx)) + self.recipient.do_send(FsEventGrouping::buffered_change( + outgoing, + self.fs_ctx, + self.watch_spec.clone(), + self.debounce, + )) } } @@ -296,7 +306,9 @@ impl Handler for PathMonitor { } _ => { // todo: any need to buffer these? - self.recipient.do_send(FsEventGrouping::Singular(msg)) + debug!("Sending some other event"); + let output = FsEventGrouping::singular(msg, self.watch_spec.clone(), self.debounce); + self.recipient.do_send(output) } } } diff --git a/crates/bsnext_monitor/src/path_monitor_meta.rs b/crates/bsnext_monitor/src/path_monitor_meta.rs index bceac4f2..59b182af 100644 --- a/crates/bsnext_monitor/src/path_monitor_meta.rs +++ b/crates/bsnext_monitor/src/path_monitor_meta.rs @@ -1,6 +1,6 @@ use crate::path_monitor::PathMonitor; use bsnext_fs::{Debounce, FsEventContext}; -use bsnext_input::route::Spec; +use bsnext_input::route::WatchSpec; use std::path::PathBuf; #[derive(Debug, Clone)] @@ -8,14 +8,14 @@ pub struct PathMonitorMeta { #[allow(dead_code)] pub cwd: PathBuf, pub fs_ctx: FsEventContext, - pub spec: Spec, + pub watch_spec: WatchSpec, pub debounce: Debounce, } impl From<&PathMonitor> for PathMonitorMeta { fn from(value: &PathMonitor) -> Self { Self { - spec: value.spec.clone(), + watch_spec: value.watch_spec.clone(), cwd: value.cwd.clone(), fs_ctx: value.fs_ctx, debounce: value.debounce, diff --git a/crates/bsnext_monitor/src/watchables/any_watchable.rs b/crates/bsnext_monitor/src/watchables/any_watchable.rs index b8120c06..16936294 100644 --- a/crates/bsnext_monitor/src/watchables/any_watchable.rs +++ b/crates/bsnext_monitor/src/watchables/any_watchable.rs @@ -1,11 +1,11 @@ use bsnext_input::Input; -use bsnext_input::route::Spec; +use bsnext_input::route::WatchSpec; use std::path::PathBuf; #[derive(Debug, PartialEq, PartialOrd, Ord, Eq, Hash, Clone)] pub struct AnyWatchable { pub dirs: Vec, - pub spec: Spec, + pub watch_spec: WatchSpec, } pub fn to_any_watchables(input: &Input) -> Vec { @@ -17,7 +17,7 @@ pub fn to_any_watchables(input: &Input) -> Vec { AnyWatchable { dirs: path_bufs, - spec: watcher.spec.clone().unwrap_or_default(), + watch_spec: watcher.spec.clone().unwrap_or_default(), } }) .collect() diff --git a/crates/bsnext_monitor/src/watchables/mod.rs b/crates/bsnext_monitor/src/watchables/mod.rs index fb0aa523..b71cb619 100644 --- a/crates/bsnext_monitor/src/watchables/mod.rs +++ b/crates/bsnext_monitor/src/watchables/mod.rs @@ -1,3 +1,4 @@ +use crate::FsEventGrouping; use crate::watchables::any_watchable::to_any_watchables; use crate::watchables::path_watchable::PathWatchable; use crate::watchables::route_watchable::to_route_watchables; @@ -17,10 +18,15 @@ pub mod server_watchable; pub struct MonitorPathWatchables { pub watchables: Vec, pub cwd: PathBuf, + pub recipient: Recipient, } #[tracing::instrument(skip_all, name = "accept_watchables")] -pub fn accept_watchables(cwd: PathBuf, input: &Input, addr: Recipient) { +pub fn accept_watchables( + cwd: PathBuf, + input: &Input, + recipient: Recipient, +) -> MonitorPathWatchables { let route_watchables = to_route_watchables(input); let server_watchables = to_server_watchables(input); let any_watchables = to_any_watchables(input); @@ -68,6 +74,10 @@ pub fn accept_watchables(cwd: PathBuf, input: &Input, addr: Recipient &Spec { + pub fn spec(&self) -> &WatchSpec { match self { - PathWatchable::Server(server) => &server.spec, - PathWatchable::Route(route) => &route.spec, - PathWatchable::Any(any) => &any.spec, + PathWatchable::Server(server) => &server.watch_spec, + PathWatchable::Route(route) => &route.watch_spec, + PathWatchable::Any(any) => &any.watch_spec, } } pub fn watch_paths(&self) -> Vec<&Path> { diff --git a/crates/bsnext_monitor/src/watchables/route_watchable.rs b/crates/bsnext_monitor/src/watchables/route_watchable.rs index 30edf7e4..64c3376d 100644 --- a/crates/bsnext_monitor/src/watchables/route_watchable.rs +++ b/crates/bsnext_monitor/src/watchables/route_watchable.rs @@ -1,5 +1,5 @@ use bsnext_input::Input; -use bsnext_input::route::{DirRoute, PathPattern, RawRoute, RouteKind, Spec, SseOpts}; +use bsnext_input::route::{DirRoute, PathPattern, RawRoute, RouteKind, SseOpts, WatchSpec}; use bsnext_input::server_config::ServerIdentity; use bsnext_input::watch_opts::WatchOpts; use std::path::{Path, PathBuf}; @@ -9,7 +9,7 @@ pub struct RouteWatchable { pub server_identity: ServerIdentity, pub route_path: String, pub dir: PathBuf, - pub spec: Spec, + pub watch_spec: WatchSpec, } pub fn to_route_watchables(input: &Input) -> Vec { @@ -20,9 +20,9 @@ pub fn to_route_watchables(input: &Input) -> Vec { server_config .routes .iter() - .filter(|r| r.opts.watch.is_enabled()) - .filter_map(|r| { - let output = match &r.kind { + .filter(|route| route.opts.watch.is_enabled()) + .filter_map(|route| { + let output = match &route.kind { RouteKind::Raw(route) => maybe_source_file(route).map(PathBuf::from), RouteKind::Dir(DirRoute { dir, .. }) => Some(PathBuf::from(dir)), RouteKind::Proxy(_) => None, @@ -32,27 +32,14 @@ pub fn to_route_watchables(input: &Input) -> Vec { let pb = output?; let identity = server_config.identity.clone(); - - let mut spec = to_spec(&r.opts.watch); - - // respect a given spec's 'ignore' (eg: if provided by user), otherwise try to use - spec.ignore = spec - .ignore - .or_else(|| input.config.global_fs_ignore.to_owned()); - - // respect a given spec's 'only' (eg: if provided by user), otherwise try to use - spec.only = spec.only.or_else(|| input.config.global_fs_only.to_owned()); - - // respect a given spec's 'debounce' (eg: if provided by user), otherwise try to use the global - spec.debounce = spec.debounce.or(input.config.global_fs_debounce); - - let route_path = r.path.as_str().to_owned(); + let watch_spec = to_watch_spec(&route.opts.watch).with_globals(&input.config); + let route_path = route.path.as_str().to_owned(); let route_watchable = RouteWatchable { server_identity: identity, route_path, dir: pb, - spec, + watch_spec, }; tracing::trace!(?route_watchable); @@ -73,11 +60,11 @@ pub fn maybe_source_file(raw_route: &RawRoute) -> Option<&Path> { } } -pub fn to_spec(wo: &WatchOpts) -> Spec { +pub fn to_watch_spec(wo: &WatchOpts) -> WatchSpec { match wo { WatchOpts::Bool(enabled) if !*enabled => unreachable!("should be handled..."), - WatchOpts::Bool(enabled) if *enabled => Spec::default(), - WatchOpts::InlineGlob(glob) => Spec { + WatchOpts::Bool(enabled) if *enabled => WatchSpec::default(), + WatchOpts::InlineGlob(glob) => WatchSpec { debounce: None, only: Some(PathPattern::Glob { glob: glob.to_string(), diff --git a/crates/bsnext_monitor/src/watchables/server_watchable.rs b/crates/bsnext_monitor/src/watchables/server_watchable.rs index 29eae6f0..1c2dc07f 100644 --- a/crates/bsnext_monitor/src/watchables/server_watchable.rs +++ b/crates/bsnext_monitor/src/watchables/server_watchable.rs @@ -1,5 +1,5 @@ use bsnext_input::Input; -use bsnext_input::route::Spec; +use bsnext_input::route::WatchSpec; use bsnext_input::server_config::ServerIdentity; use std::path::PathBuf; @@ -7,7 +7,7 @@ use std::path::PathBuf; pub struct ServerWatchable { pub server_identity: ServerIdentity, pub dirs: Vec, - pub spec: Spec, + pub watch_spec: WatchSpec, } pub fn to_server_watchables(input: &Input) -> Vec { @@ -21,7 +21,7 @@ pub fn to_server_watchables(input: &Input) -> Vec { ServerWatchable { server_identity: server_config.identity.clone(), dirs: path_bufs, - spec: watcher.spec.clone().unwrap_or_default(), + watch_spec: watcher.spec.clone().unwrap_or_default(), } }) }) diff --git a/crates/bsnext_system/src/api.rs b/crates/bsnext_system/src/api.rs index e9c375c0..17fcf883 100644 --- a/crates/bsnext_system/src/api.rs +++ b/crates/bsnext_system/src/api.rs @@ -4,7 +4,9 @@ use crate::system::BsSystem; use actix::Addr; use bsnext_dto::internal::ServerError; use bsnext_dto::ActiveServer; -use bsnext_fs::{FsEvent, FsEventGrouping}; +use bsnext_fs::{Debounce, FsEvent}; +use bsnext_input::route::WatchSpec; +use bsnext_monitor::FsEventGrouping; use tokio::sync::oneshot; #[derive(Debug)] @@ -43,7 +45,11 @@ impl BsSystemApi { } pub fn fs_event(&self, evt: FsEvent) { - self.sys_address.do_send(FsEventGrouping::Singular(evt)) + self.sys_address.do_send(FsEventGrouping::singular( + evt, + WatchSpec::default(), + Debounce::default(), + )) } pub async fn active_servers(&self) -> Result, ServerError> { diff --git a/crates/bsnext_system/src/handle_fs_event_grouping.rs b/crates/bsnext_system/src/handle_fs_event_grouping.rs index 737edcda..1c52f133 100644 --- a/crates/bsnext_system/src/handle_fs_event_grouping.rs +++ b/crates/bsnext_system/src/handle_fs_event_grouping.rs @@ -11,14 +11,15 @@ use bsnext_dto::external_events::ExternalEventsDTO; use bsnext_dto::internal::{AnyEvent, InternalEvents}; use bsnext_dto::{StoppedWatchingDTO, WatchingDTO}; use bsnext_fs::{ - BufferedChangeEvent, FsEvent, FsEventContext, FsEventGrouping, FsEventKind, PathAddedEvent, + BufferedChangeEvent, Debounce, FsEvent, FsEventContext, FsEventKind, PathAddedEvent, PathDescriptionOwned, PathEvent, }; use bsnext_input::bs_live_built_in_task::BsLiveBuiltInTask; +use bsnext_input::route::WatchSpec; use bsnext_input::{Input, InputError, PathDefinition, PathDefs, PathError}; -use bsnext_monitor::path_monitor_meta::PathMonitorMeta; +use bsnext_monitor::{FsEventGrouping, Group}; use bsnext_task::task_trigger::FsChangesTrigger; -use tracing::{debug_span, info}; +use tracing::{debug, debug_span, info}; impl actix::Handler for BsSystem { type Result = (); @@ -27,13 +28,20 @@ impl actix::Handler for BsSystem { let addr = ctx.address(); let span = debug_span!("Handler->FsEventGrouping->BsSystem"); let _guard = span.enter(); - let next = match msg { - FsEventGrouping::Singular(fs_event) => self.handle_fs_event(fs_event, addr), - FsEventGrouping::BufferedChange(buff) => { - if let Some((task_trigger, task_spec)) = self.handle_buffered(buff) { + let debounce = msg.debounce; + let watch_spec = msg.watch_spec; + let next = match msg.group { + Group::Singular(fs_event) => { + tracing::debug!("will handle single event"); + self.handle_fs_event(fs_event, addr, debounce) + } + Group::BufferedChange(buff) => { + if let Some((task_trigger, task_spec)) = self.handle_buffered(buff, watch_spec) { tracing::debug!("will trigger task runner"); self.fs_task_tracker .do_send(TriggerFsTask::new(task_spec, task_trigger)); + } else { + tracing::debug!("will NOT trigger task runner"); } None } @@ -46,7 +54,12 @@ impl actix::Handler for BsSystem { } impl BsSystem { - fn handle_fs_event(&mut self, fs_event: FsEvent, addr: Addr) -> Option { + fn handle_fs_event( + &mut self, + fs_event: FsEvent, + addr: Addr, + debounce: Debounce, + ) -> Option { match &fs_event.kind { FsEventKind::Change(ch) if fs_event.fs_event_ctx.is_root() => { tracing::info!("fs_event_ctx=root"); @@ -69,33 +82,18 @@ impl BsSystem { let evt = self.handle_any_change(&fs_event.fs_event_ctx, inner); Some(evt) } - FsEventKind::PathAdded(path) => { - let Some(pw) = self.monitor_meta(&fs_event.fs_event_ctx) else { - tracing::error!(evt=?fs_event, "missing monitor meta data"); - return None; - }; - self.handle_path_added(path, pw) - } + FsEventKind::PathAdded(path) => self.handle_path_added(path, &debounce), FsEventKind::PathRemoved(path) => self.handle_path_removed(path), FsEventKind::PathNotFoundError(pdo) => self.handle_path_not_found(pdo), } } - fn monitor_meta(&self, incoming: &FsEventContext) -> Option<&PathMonitorMeta> { - if incoming.is_root() { - self.input_monitors.as_ref().map(|m| &m.monitor_meta) - } else { - self.any_monitors - .iter() - .find(|(.., (_addr, PathMonitorMeta { ref fs_ctx, .. }))| fs_ctx == incoming) - .map(|(.., (_addr, meta))| meta) - } - } #[tracing::instrument(skip_all)] fn handle_buffered( &mut self, buf: BufferedChangeEvent, + watch_spec: WatchSpec, ) -> Option<(FsChangesTrigger, TaskSpec)> { - tracing::debug!(msg.event_count = buf.events.len(), msg.ctx = ?buf.fs_ctx, ?buf); + tracing::debug!(msg.event_count = buf.events.len(), msg.ctx = ?buf.fs_event_ctx, ?buf); let change = if let Some(mon) = &self.input_monitors { if let Some(fp) = mon.input_ctx.file_path() { @@ -121,8 +119,8 @@ impl BsSystem { .map(|evt| evt.absolute.to_owned()) .collect::>(); - let task_spec = self.task_spec_for_fs_event(&change.fs_ctx); - let trigger = FsChangesTrigger::new(paths, change.fs_ctx); + let task_spec = self.task_spec_for_fs_event(&watch_spec); + let trigger = FsChangesTrigger::new(paths, change.fs_event_ctx); Some((trigger, task_spec)) } @@ -171,9 +169,9 @@ impl BsSystem { Some(input), ) } - fn handle_path_added(&self, path: &PathAddedEvent, meta: &PathMonitorMeta) -> Option { + fn handle_path_added(&self, path: &PathAddedEvent, debounce: &Debounce) -> Option { Some(AnyEvent::External(ExternalEventsDTO::Watching( - WatchingDTO::from_path_buf(&path.path, meta.debounce), + WatchingDTO::from_path_buf(&path.path, *debounce), ))) } @@ -199,13 +197,13 @@ impl BsSystem { } #[tracing::instrument(skip_all)] - fn task_spec_for_fs_event(&self, fs_event_ctx: &FsEventContext) -> TaskSpec { - if let Some(spec) = self.specs.get(fs_event_ctx) { + fn task_spec_for_fs_event(&self, watch_spec: &WatchSpec) -> TaskSpec { + if let Some(spec) = TaskSpec::opt_from(watch_spec) { info!("matching task_spec: {:?}", spec); - info!("matching fs_event_ctx: {:?}", fs_event_ctx); + info!("matching fs_event_ctx: {:?}", watch_spec); spec.to_owned() } else { - info!("creating a default task spec on the fly because fs_event_ctx didn't match"); + debug!("creating a default task spec on the fly because fs_event_ctx didn't match any task specs"); TaskSpec::seq(&[ Runnable::BsLiveTask(BsLiveBuiltInTask::NotifyServer), Runnable::BsLiveTask(BsLiveBuiltInTask::PublishExternalEvent), diff --git a/crates/bsnext_system/src/lib.rs b/crates/bsnext_system/src/lib.rs index 6ab29e7f..827b9d0f 100644 --- a/crates/bsnext_system/src/lib.rs +++ b/crates/bsnext_system/src/lib.rs @@ -7,9 +7,10 @@ mod external_event_sender; mod fs_task_tracker; mod handle_fs_event_grouping; pub mod input_fs; -pub mod input_monitor; mod invoke_scope; -pub mod monitor_path_watchables; +pub mod monitor_any; +pub mod monitor_input; +pub mod mutable_state; pub mod override_input; pub mod run; mod servers; diff --git a/crates/bsnext_system/src/monitor_any.rs b/crates/bsnext_system/src/monitor_any.rs new file mode 100644 index 00000000..d35d6419 --- /dev/null +++ b/crates/bsnext_system/src/monitor_any.rs @@ -0,0 +1,66 @@ +use crate::system::BsSystem; +use actix::{ActorFutureExt, AsyncContext}; +use actix::{ResponseActFuture, WrapFuture}; +use bsnext_input::Input; +use bsnext_monitor::watchables::accept_watchables; +use tracing::debug_span; + +/// Message to monitor file system paths based on the current input configuration. +/// +/// This handler processes an `Input` configuration to determine which paths should be +/// monitored for file system changes. It: +/// - Converts the input into a collection of watchables based on the configured watch strategy +/// (routes, servers, or any custom watchers) +/// - Sends these watchables to the `Monitor` actor to create individual `PathMonitor` instances +/// - Registers the resulting task specifications with the system for later use in handling +/// file system events +/// +/// The monitoring configuration respects the `watchers` and `infer` settings in the input, +/// allowing for flexible control over which files and directories are watched. +#[derive(actix::Message)] +#[rtype(result = "()")] +pub struct MonitorAny { + input: Input, +} + +impl MonitorAny { + pub fn new(input: Input) -> Self { + Self { input } + } +} + +impl actix::Handler for BsSystem { + type Result = ResponseActFuture; + + fn handle(&mut self, msg: MonitorAny, ctx: &mut Self::Context) -> Self::Result { + let cwd = self.cwd.clone(); + let recip = ctx.address().recipient(); + let monitor = self.monitor.clone(); + + let future = async move { + let input = msg.input; + let msg = accept_watchables(cwd, &input, recip); + monitor.send(msg).await?; + Ok::<_, anyhow::Error>(()) + }; + + Box::pin(future.into_actor(self).map(|res, _actor, _ctx| match res { + Ok(..) => { + let span = debug_span!("MonitorAny -> InsertResults"); + let _g = span.entered(); + tracing::debug!("a monitor was added"); + // for (index, insert_result) in inner.into_iter().enumerate() { + // let task_spec = TaskSpec::opt_from(&insert_result.meta.watch_spec); + // let fs_ctx = insert_result.meta.fs_ctx; + // actor.insert_meta(insert_result.meta.fs_ctx, insert_result.meta); + // if let Some(spec) = task_spec { + // actor.insert_task_spec(fs_ctx, spec); + // } + // } + } + Err(err) => { + tracing::error!(?err); + } + })) + } +} diff --git a/crates/bsnext_system/src/input_monitor.rs b/crates/bsnext_system/src/monitor_input.rs similarity index 95% rename from crates/bsnext_system/src/input_monitor.rs rename to crates/bsnext_system/src/monitor_input.rs index 6cc42e77..9877b047 100644 --- a/crates/bsnext_system/src/input_monitor.rs +++ b/crates/bsnext_system/src/monitor_input.rs @@ -1,7 +1,7 @@ use crate::system::BsSystem; use actix::{Actor, Addr, AsyncContext}; use bsnext_fs::{Debounce, FsEventContext}; -use bsnext_input::route::Spec; +use bsnext_input::route::WatchSpec; use bsnext_input::InputCtx; use bsnext_monitor::path_monitor::PathMonitor; use bsnext_monitor::path_monitor_meta::PathMonitorMeta; @@ -41,7 +41,7 @@ impl actix::Handler for BsSystem { let paths = vec![msg.path.to_path_buf()]; let pw = PathWatchable::Any(AnyWatchable { dirs: paths.clone(), - spec: Spec::default(), + watch_spec: WatchSpec::default(), }); let input_path_monitor = PathMonitor::new( diff --git a/crates/bsnext_system/src/mutable_state.rs b/crates/bsnext_system/src/mutable_state.rs new file mode 100644 index 00000000..6cf57a50 --- /dev/null +++ b/crates/bsnext_system/src/mutable_state.rs @@ -0,0 +1,3 @@ +use crate::system::BsSystem; + +impl BsSystem {} diff --git a/crates/bsnext_system/src/start/start_system.rs b/crates/bsnext_system/src/start/start_system.rs index bdf28bc1..2519a0b4 100644 --- a/crates/bsnext_system/src/start/start_system.rs +++ b/crates/bsnext_system/src/start/start_system.rs @@ -1,6 +1,7 @@ #![allow(clippy::result_large_err)] use crate::api::BsSystemApi; -use crate::input_monitor::MonitorInput; +use crate::monitor_any::MonitorAny; +use crate::monitor_input::MonitorInput; use crate::start::start_kind::StartKind; use crate::system::{ run_jobs, setup_jobs_only, setup_servers_only, BsSystem, RunDryOk, RunOk, SetupOk, @@ -13,7 +14,6 @@ use bsnext_dto::internal::{AnyEvent, ChildResult, InternalEvents}; use bsnext_dto::{DidStart, StartupError}; use bsnext_input::startup::{RunMode, SystemStart, SystemStartArgs}; use bsnext_input::InputCtx; -use bsnext_monitor::watchables::accept_watchables; use std::future::ready; use std::path::PathBuf; use tokio::sync::oneshot; @@ -79,8 +79,7 @@ impl Handler for BsSystem { cwd: actor.cwd.clone(), input_ctx, }); - // todo: where to better sequence these side-effects? - accept_watchables(actor.cwd.clone(), &input, addr.recipient()); + ctx.notify(MonitorAny::new(input.clone())); Ok(DidStart::Started(servers)) }, )) @@ -92,7 +91,7 @@ impl Handler for BsSystem { let jobs = crate::system::setup_jobs(addr.clone(), input.clone()); Box::pin(jobs.into_actor(self).map( - move |res: Result, actor, _ctx| { + move |res: Result, _actor, ctx| { let res = res?; debug!("✅ setup jobs completed"); let errored = ChildResult::first_server_error(&res.child_results); @@ -100,7 +99,7 @@ impl Handler for BsSystem { debug!("errored: {:?}", errored); return Err(StartupError::ServerError((*server_error).to_owned())); } - accept_watchables(actor.cwd.clone(), &res.input, addr.recipient()); + ctx.notify(MonitorAny::new(res.input.clone())); Ok(DidStart::Started(res.servers)) }, )) @@ -130,7 +129,7 @@ impl Handler for BsSystem { }; Box::pin(jobs.into_actor(self).map( - move |res: Result, actor, _ctx| { + move |res: Result, _actor, ctx| { let res = res?; debug!("✅ setup jobs completed"); let errored = ChildResult::first_server_error(&res.child_results); @@ -138,7 +137,7 @@ impl Handler for BsSystem { debug!("errored: {:?}", errored); return Err(StartupError::ServerError((*server_error).to_owned())); } - accept_watchables(actor.cwd.clone(), &res.input, addr.recipient()); + ctx.notify(MonitorAny::new(res.input.clone())); Ok(DidStart::Started(res.servers)) }, )) diff --git a/crates/bsnext_system/src/system.rs b/crates/bsnext_system/src/system.rs index 016b93a6..30736005 100644 --- a/crates/bsnext_system/src/system.rs +++ b/crates/bsnext_system/src/system.rs @@ -1,7 +1,7 @@ use crate::capabilities::Capabilities; use crate::fs_task_tracker::FsTaskTracker; -use crate::input_monitor::InputMonitor; use crate::invoke_scope::{InvokeScope, Invoker}; +use crate::monitor_input::InputMonitor; use crate::run::resolve_spec::{InvokeRunTasks, ResolveSpec}; use crate::servers::ResolveServers; use crate::tasks::resolve::ResolveInitialTasks; @@ -12,14 +12,10 @@ use bsnext_core::servers_supervisor::actor::ServersSupervisor; use bsnext_dto::external_events::{ExternalEventsDTO, TaskTreePreview, TaskTreeSummary}; use bsnext_dto::internal::{AnyEvent, ChildResult, TaskReportAndTree}; use bsnext_dto::GetActiveServersResponse; -use bsnext_fs::FsEventContext; use bsnext_input::startup::{StartupContext, TopLevelRunMode}; use bsnext_input::Input; -use bsnext_monitor::path_monitor::PathMonitor; -use bsnext_monitor::path_monitor_meta::PathMonitorMeta; -use bsnext_monitor::watchables::path_watchable::PathWatchable; +use bsnext_monitor::Monitor; use bsnext_task::task_trigger::{ExecTrigger, TaskTrigger, TaskTriggerSource}; -use std::collections::HashMap; use std::path::PathBuf; use std::time::Duration; use tokio::sync::mpsc::Sender; @@ -32,9 +28,8 @@ pub struct BsSystem { servers_addr: Addr, any_event_sender: Sender, pub(crate) input_monitors: Option, - pub(crate) specs: HashMap, - pub(crate) any_monitors: HashMap, PathMonitorMeta)>, pub(crate) fs_task_tracker: Addr, + pub(crate) monitor: Addr, pub(crate) invoker_addr: Addr, pub(crate) cwd: PathBuf, pub(crate) start_context: StartupContext, @@ -87,18 +82,19 @@ impl BsSystem { ); let invoker_addr = invoker.start(); let fs_task_tracker = FsTaskTracker::new(invoker_addr.clone().recipient()).start(); + let monitor = Monitor::new(); + let monitor = monitor.start(); BsSystem { self_addr: None, capabilities_addr, servers_addr, any_event_sender, input_monitors: None, - any_monitors: Default::default(), + monitor, invoker_addr, fs_task_tracker, cwd, start_context, - specs: HashMap::default(), } } diff --git a/crates/bsnext_system/src/tasks/task_spec.rs b/crates/bsnext_system/src/tasks/task_spec.rs index 106847d3..c27b862e 100644 --- a/crates/bsnext_system/src/tasks/task_spec.rs +++ b/crates/bsnext_system/src/tasks/task_spec.rs @@ -4,7 +4,7 @@ use crate::tasks::{Node, Runnable, RunnableWithComms}; use actix::Addr; use bsnext_core::servers_supervisor::actor::ServersSupervisor; use bsnext_dto::archy::ArchyNode; -use bsnext_input::route::RunOptItem; +use bsnext_input::route::{RunOptItem, WatchSpec}; use bsnext_task::task_entry::TaskEntry; use bsnext_task::task_scope::TaskScope; use bsnext_task::{ @@ -33,6 +33,12 @@ pub struct TaskSpec { path: NodePath, } +impl TaskSpec { + pub fn opt_from(watch_spec: &WatchSpec) -> Option { + watch_spec.run.as_ref().map(|run| TaskSpec::seq_from(run)) + } +} + impl TaskSpec { pub fn path(&self) -> &NodePath { &self.path diff --git a/crates/bsnext_system/src/watch/watch_sub_opts.rs b/crates/bsnext_system/src/watch/watch_sub_opts.rs index f44f914c..8a889ca2 100644 --- a/crates/bsnext_system/src/watch/watch_sub_opts.rs +++ b/crates/bsnext_system/src/watch/watch_sub_opts.rs @@ -1,7 +1,7 @@ use crate::watch::watch_runner::WatchRunnerStr; use bsnext_input::route::{ - BeforeRunOptItem, DebounceDuration, MultiWatch, PathPattern, RunOptItem, ShRunOptItem, Spec, - WatcherDirs, + BeforeRunOptItem, DebounceDuration, MultiWatch, PathPattern, RunOptItem, ShRunOptItem, + WatchSpec, WatcherDirs, }; #[derive(Debug, Default, Clone, clap::Parser)] @@ -112,7 +112,7 @@ impl From for MultiWatch { .debounce .map(|duration| DebounceDuration::Ms(duration as u64)); - let spec = Spec { + let spec = WatchSpec { before: queue, run: run_opts, debounce, From 11bd594ee12880b224819070c224b24d22c589bb Mon Sep 17 00:00:00 2001 From: Shane Osbourne Date: Tue, 28 Apr 2026 21:08:02 +0100 Subject: [PATCH 04/10] remove monitor meta --- Cargo.lock | 1 + crates/bsnext_monitor/Cargo.toml | 1 + .../examples/path_monitor_example.rs | 55 ++++++++++++++----- crates/bsnext_monitor/src/lib.rs | 6 +- .../src/monitor_path_watchables.rs | 39 ++++++------- crates/bsnext_monitor/src/path_monitor.rs | 25 ++++++--- .../bsnext_monitor/src/path_monitor_meta.rs | 24 -------- crates/bsnext_system/src/monitor_input.rs | 21 ++----- 8 files changed, 86 insertions(+), 86 deletions(-) delete mode 100644 crates/bsnext_monitor/src/path_monitor_meta.rs diff --git a/Cargo.lock b/Cargo.lock index 123e2ffe..984b7035 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -639,6 +639,7 @@ version = "0.26.0" dependencies = [ "actix", "actix-rt", + "anyhow", "bsnext_fs", "bsnext_input", "globset", diff --git a/crates/bsnext_monitor/Cargo.toml b/crates/bsnext_monitor/Cargo.toml index 284a1665..e39d48c2 100644 --- a/crates/bsnext_monitor/Cargo.toml +++ b/crates/bsnext_monitor/Cargo.toml @@ -12,3 +12,4 @@ tokio = { workspace = true } tokio-stream = { workspace = true } tracing = { workspace = true } globset = { workspace = true } +anyhow = { workspace = true } diff --git a/crates/bsnext_monitor/examples/path_monitor_example.rs b/crates/bsnext_monitor/examples/path_monitor_example.rs index a8372099..df5ae13f 100644 --- a/crates/bsnext_monitor/examples/path_monitor_example.rs +++ b/crates/bsnext_monitor/examples/path_monitor_example.rs @@ -3,7 +3,7 @@ use actix_rt::System; use bsnext_fs::{FsEvent, FsEventContext}; use bsnext_input::route::{DebounceDuration, WatchSpec}; use bsnext_monitor::FsEventGrouping; -use bsnext_monitor::path_monitor::PathMonitor; +use bsnext_monitor::path_monitor::{PathMonitor, WatchPaths}; use std::env::current_dir; use std::process; use std::time::Duration; @@ -18,12 +18,24 @@ fn main() { .unwrap() }) .block_on(async_main()); - System::current().stop_with_code(code); - process::exit(code) + match code { + Ok(code) => { + System::current().stop_with_code(code); + process::exit(code) + } + Err(err) => { + eprintln!("{err}"); + System::current().stop_with_code(1); + process::exit(1) + } + } } -async fn async_main() -> i32 { - struct Consumer; +async fn async_main() -> anyhow::Result { + #[derive(Default)] + struct Consumer { + events: Vec, + } #[derive(actix::Message, Debug, Clone)] #[rtype(result = "()")] @@ -36,8 +48,8 @@ async fn async_main() -> i32 { impl actix::Handler for Consumer { type Result = (); - fn handle(&mut self, _msg: FsEventGrouping, _ctx: &mut Self::Context) -> Self::Result { - dbg!("got "); + fn handle(&mut self, msg: FsEventGrouping, _ctx: &mut Self::Context) -> Self::Result { + self.events.push(msg); } } @@ -49,24 +61,41 @@ async fn async_main() -> i32 { } } - let consumer = Consumer; + #[derive(actix::Message, Debug, Clone)] + #[rtype(result = "Vec")] + struct Read; + + impl actix::Handler for Consumer { + type Result = Vec; + + fn handle(&mut self, _msg: Read, _ctx: &mut Self::Context) -> Self::Result { + self.events.clone() + } + } + + let consumer = Consumer::default(); let actor = consumer.start(); let reciever = actor.clone().recipient(); let cwd = current_dir().unwrap(); let fs_context = FsEventContext::default(); + let debounce = bsnext_fs::Debounce::Buffered { + duration: Duration::from_millis(0), + }; let spec = WatchSpec { debounce: Some(DebounceDuration::Ms(0)), ..Default::default() }; - let p = vec![]; - - let monitor = PathMonitor::new(reciever, Default::default(), cwd, fs_context, spec, p); + let monitor = PathMonitor::new(reciever, debounce, cwd, fs_context, spec); let monitor_actor = monitor.start(); + monitor_actor.send(WatchPaths { paths: vec![] }).await?; let _did_wait = actor.send(Ping).await; let _sent = monitor_actor .send(FsEvent::changed("/a", "a", fs_context.id())) .await; - tokio::time::sleep(Duration::from_millis(310)).await; - 0 + let r = Read; + tokio::time::sleep(Duration::from_millis(10)).await; + let events = actor.send(r).await?; + assert_eq!(events.len(), 1); + Ok(0) } diff --git a/crates/bsnext_monitor/src/lib.rs b/crates/bsnext_monitor/src/lib.rs index 8d78c3e0..486ec706 100644 --- a/crates/bsnext_monitor/src/lib.rs +++ b/crates/bsnext_monitor/src/lib.rs @@ -1,5 +1,4 @@ use crate::path_monitor::PathMonitor; -use crate::path_monitor_meta::PathMonitorMeta; use crate::watchables::path_watchable::PathWatchable; use actix::Addr; use bsnext_fs::{BufferedChangeEvent, Debounce, FsEvent, FsEventContext, PathDescriptionOwned}; @@ -8,18 +7,17 @@ use std::collections::HashMap; pub mod monitor_path_watchables; pub mod path_monitor; -pub mod path_monitor_meta; pub mod watchables; #[derive(Debug, Default)] pub struct Monitor { - any_monitors: HashMap, PathMonitorMeta)>, + path_monitors: HashMap>, } impl Monitor { pub fn new() -> Self { Self { - any_monitors: Default::default(), + path_monitors: Default::default(), } } } diff --git a/crates/bsnext_monitor/src/monitor_path_watchables.rs b/crates/bsnext_monitor/src/monitor_path_watchables.rs index ded3875d..6b7aca29 100644 --- a/crates/bsnext_monitor/src/monitor_path_watchables.rs +++ b/crates/bsnext_monitor/src/monitor_path_watchables.rs @@ -1,11 +1,11 @@ use crate::Monitor; -use crate::path_monitor::{PathMonitor, StopPathMonitor}; -use crate::path_monitor_meta::PathMonitorMeta; +use crate::path_monitor::{PathMonitor, StopPathMonitor, WatchPaths}; use crate::watchables::MonitorPathWatchables; use crate::watchables::path_watchable::PathWatchable; use actix::{Actor, Addr, AsyncContext, ResponseFuture}; use bsnext_fs::{Debounce, FsEventContext}; use std::collections::BTreeSet; +use std::path::PathBuf; use tracing::{debug, debug_span}; #[derive(actix::Message)] @@ -14,11 +14,7 @@ pub struct DropMonitor(pub PathWatchable); #[derive(actix::Message)] #[rtype(result = "()")] -pub struct InsertMonitor( - pub PathWatchable, - pub Addr, - pub PathMonitorMeta, -); +pub struct InsertMonitor(pub PathWatchable, pub Addr, pub Vec); impl actix::Handler for Monitor { type Result = ResponseFuture<()>; @@ -27,7 +23,7 @@ impl actix::Handler for Monitor { fn handle(&mut self, msg: MonitorPathWatchables, ctx: &mut Self::Context) -> Self::Result { debug!("{}", file!()); - let existing = self.any_monitors.keys().collect::>(); + let existing = self.path_monitors.keys().collect::>(); let incoming = msg.watchables.iter().collect::>(); let in_both = incoming.intersection(&existing).collect::>(); @@ -39,7 +35,7 @@ impl actix::Handler for Monitor { debug!("{} monitors to add", to_add.len()); for any_watchable in to_remove { - if let Some((path_monitor_addr, _)) = self.any_monitors.get(any_watchable) { + if let Some(path_monitor_addr) = self.path_monitors.get(any_watchable) { path_monitor_addr.do_send(StopPathMonitor); ctx.notify(DropMonitor((*any_watchable).clone())) } @@ -70,21 +66,21 @@ impl actix::Handler for Monitor { let debounce = spec.debounce.map(Debounce::from).unwrap_or_default(); tracing::trace!(?debounce); - let monitor = PathMonitor::new( + let path_monitor = PathMonitor::new( msg.recipient.clone(), debounce, msg.cwd.clone(), fs_ctx, spec.clone(), - paths, ); - let meta = PathMonitorMeta::from(&monitor); - tracing::trace!(?meta); - - let monitor_addr = monitor.start(); + let path_monitor_addr = path_monitor.start(); - ctx.notify(InsertMonitor((*any_watchable).clone(), monitor_addr, meta)) + ctx.notify(InsertMonitor( + (*any_watchable).clone(), + path_monitor_addr, + paths, + )) } Box::pin(async move {}) @@ -96,8 +92,8 @@ impl actix::Handler for Monitor { fn handle(&mut self, msg: DropMonitor, _ctx: &mut Self::Context) -> Self::Result { tracing::trace!(watchable=?msg.0, "DropMonitor"); - self.any_monitors.remove(&msg.0); - tracing::trace!("dropped, Monitor count {}", self.any_monitors.len()); + self.path_monitors.remove(&msg.0); + tracing::trace!("dropped, Monitor count {}", self.path_monitors.len()); } } @@ -106,13 +102,14 @@ impl actix::Handler for Monitor { fn handle( &mut self, - InsertMonitor(watchable, addr, meta): InsertMonitor, + InsertMonitor(watchable, addr, paths): InsertMonitor, _ctx: &mut Self::Context, ) -> Self::Result { let span = debug_span!("InsertMonitor"); let _guard = span.enter(); debug!("{}", watchable); - self.any_monitors.insert(watchable, (addr, meta)); - debug!("+ Monitor count {}", self.any_monitors.len()); + self.path_monitors.insert(watchable, addr.clone()); + debug!("+ Monitor count {}", self.path_monitors.len()); + addr.do_send(WatchPaths { paths }); } } diff --git a/crates/bsnext_monitor/src/path_monitor.rs b/crates/bsnext_monitor/src/path_monitor.rs index 594eeeb2..c6764f4d 100644 --- a/crates/bsnext_monitor/src/path_monitor.rs +++ b/crates/bsnext_monitor/src/path_monitor.rs @@ -1,5 +1,5 @@ use crate::FsEventGrouping; -use actix::{ActorContext, Addr, AsyncContext, Context, Handler, Recipient, StreamHandler}; +use actix::{Actor, ActorContext, Addr, AsyncContext, Context, Handler, Recipient, StreamHandler}; use actix_rt::Arbiter; use bsnext_fs::actor::FsWatcher; use bsnext_fs::buffered_debounce::BufferedStreamOpsExt; @@ -20,12 +20,11 @@ use tracing::{debug, debug_span}; #[derive(Debug)] pub struct PathMonitor { pub(crate) cwd: PathBuf, - pub(crate) addrs: Vec>, - pub(crate) recipient: Recipient, pub(crate) fs_ctx: FsEventContext, pub(crate) debounce: Debounce, - pub watch_spec: WatchSpec, - pub watch_paths: Vec, + pub(crate) watch_spec: WatchSpec, + addrs: Vec>, + recipient: Recipient, inner_sender: tokio::sync::mpsc::Sender, inner_receiver: Option>, } @@ -37,7 +36,6 @@ impl PathMonitor { cwd: PathBuf, fs_ctx: FsEventContext, watch_spec: WatchSpec, - watch_paths: Vec, ) -> Self { let (inner_sender, inner_receiver) = mpsc::channel::(1); Self { @@ -47,7 +45,6 @@ impl PathMonitor { addrs: vec![], fs_ctx, watch_spec, - watch_paths, inner_sender, inner_receiver: Some(inner_receiver), } @@ -56,9 +53,19 @@ impl PathMonitor { impl actix::Actor for PathMonitor { type Context = actix::Context; +} + +#[derive(actix::Message, Debug, Clone)] +#[rtype(result = "()")] +pub struct WatchPaths { + pub paths: Vec, +} + +impl actix::Handler for PathMonitor { + type Result = (); - fn started(&mut self, ctx: &mut Self::Context) { - for single_path in &self.watch_paths { + fn handle(&mut self, msg: WatchPaths, ctx: &mut Self::Context) -> Self::Result { + for single_path in &msg.paths { let as_str = single_path.to_string_lossy(); let PathAndFilter { path, filter_kind } = PathAndFilter::new(&as_str); diff --git a/crates/bsnext_monitor/src/path_monitor_meta.rs b/crates/bsnext_monitor/src/path_monitor_meta.rs deleted file mode 100644 index 59b182af..00000000 --- a/crates/bsnext_monitor/src/path_monitor_meta.rs +++ /dev/null @@ -1,24 +0,0 @@ -use crate::path_monitor::PathMonitor; -use bsnext_fs::{Debounce, FsEventContext}; -use bsnext_input::route::WatchSpec; -use std::path::PathBuf; - -#[derive(Debug, Clone)] -pub struct PathMonitorMeta { - #[allow(dead_code)] - pub cwd: PathBuf, - pub fs_ctx: FsEventContext, - pub watch_spec: WatchSpec, - pub debounce: Debounce, -} - -impl From<&PathMonitor> for PathMonitorMeta { - fn from(value: &PathMonitor) -> Self { - Self { - watch_spec: value.watch_spec.clone(), - cwd: value.cwd.clone(), - fs_ctx: value.fs_ctx, - debounce: value.debounce, - } - } -} diff --git a/crates/bsnext_system/src/monitor_input.rs b/crates/bsnext_system/src/monitor_input.rs index 9877b047..fb0559c1 100644 --- a/crates/bsnext_system/src/monitor_input.rs +++ b/crates/bsnext_system/src/monitor_input.rs @@ -3,8 +3,7 @@ use actix::{Actor, Addr, AsyncContext}; use bsnext_fs::{Debounce, FsEventContext}; use bsnext_input::route::WatchSpec; use bsnext_input::InputCtx; -use bsnext_monitor::path_monitor::PathMonitor; -use bsnext_monitor::path_monitor_meta::PathMonitorMeta; +use bsnext_monitor::path_monitor::{PathMonitor, WatchPaths}; use bsnext_monitor::watchables::any_watchable::AnyWatchable; use bsnext_monitor::watchables::path_watchable::PathWatchable; use std::path::PathBuf; @@ -14,7 +13,6 @@ use std::time::Duration; pub struct InputMonitor { #[allow(dead_code)] pub addr: Addr, - pub monitor_meta: PathMonitorMeta, pub input_ctx: InputCtx, } @@ -44,16 +42,8 @@ impl actix::Handler for BsSystem { watch_spec: WatchSpec::default(), }); - let input_path_monitor = PathMonitor::new( - sys, - debounce, - self.cwd.clone(), - ctx, - pw.spec().clone(), - paths, - ); - - let meta = PathMonitorMeta::from(&input_path_monitor); + let input_path_monitor = + PathMonitor::new(sys, debounce, self.cwd.clone(), ctx, pw.spec().clone()); tracing::debug!("starting input monitor"); @@ -61,10 +51,11 @@ impl actix::Handler for BsSystem { let input_monitor = InputMonitor { input_ctx: msg.input_ctx.clone(), - addr: input_watcher_addr, - monitor_meta: meta, + addr: input_watcher_addr.clone(), }; + input_watcher_addr.do_send(WatchPaths { paths }); + self.input_monitors = Some(input_monitor); } } From 677d85cd218b5d8e5c0d5b4bdce1a0aac9ca02e6 Mon Sep 17 00:00:00 2001 From: Shane Osbourne Date: Tue, 28 Apr 2026 22:11:16 +0100 Subject: [PATCH 05/10] more cleaning --- crates/bsnext_input/src/input_fs.rs | 78 +++++++++++++++++++ crates/bsnext_input/src/lib.rs | 1 + .../examples/path_monitor_example.rs | 12 +-- crates/bsnext_monitor/src/lib.rs | 6 +- crates/bsnext_monitor/src/path_monitor.rs | 12 +-- crates/bsnext_monitor/src/watchables/mod.rs | 6 +- crates/bsnext_system/src/api.rs | 4 +- crates/bsnext_system/src/fs_task_tracker.rs | 23 +++--- .../src/handle_fs_event_grouping.rs | 6 +- crates/bsnext_system/src/input_fs.rs | 78 +------------------ crates/bsnext_system/src/lib.rs | 1 - crates/bsnext_system/src/mutable_state.rs | 3 - .../src/start/start_kind/run_from_input.rs | 3 +- 13 files changed, 116 insertions(+), 117 deletions(-) create mode 100644 crates/bsnext_input/src/input_fs.rs delete mode 100644 crates/bsnext_system/src/mutable_state.rs diff --git a/crates/bsnext_input/src/input_fs.rs b/crates/bsnext_input/src/input_fs.rs new file mode 100644 index 00000000..cd743187 --- /dev/null +++ b/crates/bsnext_input/src/input_fs.rs @@ -0,0 +1,78 @@ +use crate::InputError; +use std::path::{Path, PathBuf}; + +pub enum ResolvedInputOutcome { + /// You provided a path that couldn't be resolved + Missing { + user_input: String, + cwd: PathBuf, + absolute: PathBuf, + err: Box, + }, + + GivenPath { + user_input: String, + cwd: PathBuf, + absolute: PathBuf, + }, + + /// You provided no paths but a path was auto-resolved + Auto { + named: String, + cwd: PathBuf, + absolute: PathBuf, + }, + + /// You provided no paths and nothing was auto-resolved + Empty, +} + +impl ResolvedInputOutcome { + pub fn new>(cwd: PathBuf, inputs: &[T]) -> Self { + resolve_inputs(cwd, inputs) + } +} + +fn resolve_inputs>(cwd: PathBuf, inputs: &[T]) -> ResolvedInputOutcome { + let input_candidates = inputs + .iter() + .map(|path| { + let abs = cwd.join(path.as_ref()); + let exists = Path::exists(&abs); + (path.as_ref(), abs, exists) + }) + .collect::>(); + + if let Some((user_input, abs, exists)) = input_candidates.first() { + if !exists { + return ResolvedInputOutcome::Missing { + user_input: (*user_input).to_owned(), + absolute: (*abs).to_owned(), + cwd, + err: Box::new(InputError::MissingInputs), + }; + } + return ResolvedInputOutcome::GivenPath { + user_input: (*user_input).to_owned(), + absolute: (*abs).to_owned(), + cwd, + }; + } + + let auto_lookups = ["bslive.yml", "bslive.yaml", "bslive.md", "bslive.html"]; + + for named in auto_lookups { + let abs = cwd.join(named); + let exists = Path::exists(&abs); + if !exists { + continue; + } + return ResolvedInputOutcome::Auto { + named: (*named).to_owned(), + cwd, + absolute: (*abs).to_owned(), + }; + } + + ResolvedInputOutcome::Empty +} diff --git a/crates/bsnext_input/src/lib.rs b/crates/bsnext_input/src/lib.rs index 504107d5..c8b8a3bd 100644 --- a/crates/bsnext_input/src/lib.rs +++ b/crates/bsnext_input/src/lib.rs @@ -18,6 +18,7 @@ pub mod playground; pub mod route; pub mod bs_live_built_in_task; +pub mod input_fs; pub mod route_cli; pub mod route_manifest; pub mod server_config; diff --git a/crates/bsnext_monitor/examples/path_monitor_example.rs b/crates/bsnext_monitor/examples/path_monitor_example.rs index df5ae13f..347f6b1a 100644 --- a/crates/bsnext_monitor/examples/path_monitor_example.rs +++ b/crates/bsnext_monitor/examples/path_monitor_example.rs @@ -2,7 +2,7 @@ use actix::{Actor, ResponseFuture}; use actix_rt::System; use bsnext_fs::{FsEvent, FsEventContext}; use bsnext_input::route::{DebounceDuration, WatchSpec}; -use bsnext_monitor::FsEventGrouping; +use bsnext_monitor::FsGroup; use bsnext_monitor::path_monitor::{PathMonitor, WatchPaths}; use std::env::current_dir; use std::process; @@ -34,7 +34,7 @@ fn main() { async fn async_main() -> anyhow::Result { #[derive(Default)] struct Consumer { - events: Vec, + events: Vec, } #[derive(actix::Message, Debug, Clone)] @@ -45,10 +45,10 @@ async fn async_main() -> anyhow::Result { type Context = actix::Context; } - impl actix::Handler for Consumer { + impl actix::Handler for Consumer { type Result = (); - fn handle(&mut self, msg: FsEventGrouping, _ctx: &mut Self::Context) -> Self::Result { + fn handle(&mut self, msg: FsGroup, _ctx: &mut Self::Context) -> Self::Result { self.events.push(msg); } } @@ -62,11 +62,11 @@ async fn async_main() -> anyhow::Result { } #[derive(actix::Message, Debug, Clone)] - #[rtype(result = "Vec")] + #[rtype(result = "Vec")] struct Read; impl actix::Handler for Consumer { - type Result = Vec; + type Result = Vec; fn handle(&mut self, _msg: Read, _ctx: &mut Self::Context) -> Self::Result { self.events.clone() diff --git a/crates/bsnext_monitor/src/lib.rs b/crates/bsnext_monitor/src/lib.rs index 486ec706..503521f9 100644 --- a/crates/bsnext_monitor/src/lib.rs +++ b/crates/bsnext_monitor/src/lib.rs @@ -28,7 +28,7 @@ impl actix::Actor for Monitor { #[derive(actix::Message, Debug, Clone)] #[rtype(result = "()")] -pub struct FsEventGrouping { +pub struct FsGroup { pub watch_spec: WatchSpec, pub debounce: Debounce, pub group: Group, @@ -40,9 +40,9 @@ pub enum Group { BufferedChange(BufferedChangeEvent), } -impl FsEventGrouping { +impl FsGroup { pub fn singular(evt: FsEvent, watch_spec: WatchSpec, debounce: Debounce) -> Self { - FsEventGrouping { + FsGroup { debounce, group: Group::Singular(evt), watch_spec, diff --git a/crates/bsnext_monitor/src/path_monitor.rs b/crates/bsnext_monitor/src/path_monitor.rs index c6764f4d..ede969b2 100644 --- a/crates/bsnext_monitor/src/path_monitor.rs +++ b/crates/bsnext_monitor/src/path_monitor.rs @@ -1,4 +1,4 @@ -use crate::FsEventGrouping; +use crate::FsGroup; use actix::{Actor, ActorContext, Addr, AsyncContext, Context, Handler, Recipient, StreamHandler}; use actix_rt::Arbiter; use bsnext_fs::actor::FsWatcher; @@ -24,14 +24,14 @@ pub struct PathMonitor { pub(crate) debounce: Debounce, pub(crate) watch_spec: WatchSpec, addrs: Vec>, - recipient: Recipient, + recipient: Recipient, inner_sender: tokio::sync::mpsc::Sender, inner_receiver: Option>, } impl PathMonitor { pub fn new( - recipient: Recipient, + recipient: Recipient, debounce: Debounce, cwd: PathBuf, fs_ctx: FsEventContext, @@ -115,7 +115,7 @@ impl actix::Handler for PathMonitor { impl StreamHandler for PathMonitor { fn handle(&mut self, event: FsEvent, _ctx: &mut Context) { debug!("StreamHandler for PathMonitor"); - self.recipient.do_send(FsEventGrouping::singular( + self.recipient.do_send(FsGroup::singular( event, self.watch_spec.clone(), self.debounce, @@ -140,7 +140,7 @@ impl StreamHandler> for PathMonitor { _ => None, }) .collect::>(); - self.recipient.do_send(FsEventGrouping::buffered_change( + self.recipient.do_send(FsGroup::buffered_change( outgoing, self.fs_ctx, self.watch_spec.clone(), @@ -314,7 +314,7 @@ impl Handler for PathMonitor { _ => { // todo: any need to buffer these? debug!("Sending some other event"); - let output = FsEventGrouping::singular(msg, self.watch_spec.clone(), self.debounce); + let output = FsGroup::singular(msg, self.watch_spec.clone(), self.debounce); self.recipient.do_send(output) } } diff --git a/crates/bsnext_monitor/src/watchables/mod.rs b/crates/bsnext_monitor/src/watchables/mod.rs index b71cb619..9abce723 100644 --- a/crates/bsnext_monitor/src/watchables/mod.rs +++ b/crates/bsnext_monitor/src/watchables/mod.rs @@ -1,4 +1,4 @@ -use crate::FsEventGrouping; +use crate::FsGroup; use crate::watchables::any_watchable::to_any_watchables; use crate::watchables::path_watchable::PathWatchable; use crate::watchables::route_watchable::to_route_watchables; @@ -18,14 +18,14 @@ pub mod server_watchable; pub struct MonitorPathWatchables { pub watchables: Vec, pub cwd: PathBuf, - pub recipient: Recipient, + pub recipient: Recipient, } #[tracing::instrument(skip_all, name = "accept_watchables")] pub fn accept_watchables( cwd: PathBuf, input: &Input, - recipient: Recipient, + recipient: Recipient, ) -> MonitorPathWatchables { let route_watchables = to_route_watchables(input); let server_watchables = to_server_watchables(input); diff --git a/crates/bsnext_system/src/api.rs b/crates/bsnext_system/src/api.rs index 17fcf883..8045f085 100644 --- a/crates/bsnext_system/src/api.rs +++ b/crates/bsnext_system/src/api.rs @@ -6,7 +6,7 @@ use bsnext_dto::internal::ServerError; use bsnext_dto::ActiveServer; use bsnext_fs::{Debounce, FsEvent}; use bsnext_input::route::WatchSpec; -use bsnext_monitor::FsEventGrouping; +use bsnext_monitor::FsGroup; use tokio::sync::oneshot; #[derive(Debug)] @@ -45,7 +45,7 @@ impl BsSystemApi { } pub fn fs_event(&self, evt: FsEvent) { - self.sys_address.do_send(FsEventGrouping::singular( + self.sys_address.do_send(FsGroup::singular( evt, WatchSpec::default(), Debounce::default(), diff --git a/crates/bsnext_system/src/fs_task_tracker.rs b/crates/bsnext_system/src/fs_task_tracker.rs index cff8695e..9ba813e3 100644 --- a/crates/bsnext_system/src/fs_task_tracker.rs +++ b/crates/bsnext_system/src/fs_task_tracker.rs @@ -6,7 +6,6 @@ use bsnext_dto::internal::TaskReportAndTree; use bsnext_fs::FsEventContext; use bsnext_task::task_trigger::{FsChangesTrigger, TaskTrigger, TaskTriggerSource}; use std::collections::HashMap; -use tokio::sync::oneshot::Receiver; use tracing::Level; #[derive(Debug)] @@ -56,7 +55,17 @@ impl Handler for FsTaskTracker { let invoke_spec = InvokeScope::new(task_trigger, task_spec, tx); self.spec_invoker.do_send(invoke_spec); - Box::pin(run(rx).into_actor(self).map(move |_resp, actor, _ctx| { + let fut = async move { + let output = rx.await?; + if output.report.is_ok() { + tracing::debug!("✅TriggerFsTaskEvent triggered a invoke scope and succeeded") + } else { + tracing::debug!("❌ TriggerFsTaskEvent triggered a invoke scope and failed") + } + Ok::<_, anyhow::Error>(()) + }; + + Box::pin(fut.into_actor(self).map(move |_resp, actor, _ctx| { actor.task_spec_mapping.remove(&cloned_id); })) } @@ -83,16 +92,6 @@ fn debug_trigger(trigger: &FsChangesTrigger) { } } -async fn run(rx: Receiver) -> anyhow::Result<()> { - let output = rx.await?; - if output.report.is_ok() { - tracing::debug!("✅TriggerFsTaskEvent triggered a invoke scope and succeeded") - } else { - tracing::debug!("❌ TriggerFsTaskEvent triggered a invoke scope and failed") - } - Ok(()) -} - /// Message to trigger execution of a filesystem-watched task. /// /// This provides a simple binary concurrency guard: if a task is already running, diff --git a/crates/bsnext_system/src/handle_fs_event_grouping.rs b/crates/bsnext_system/src/handle_fs_event_grouping.rs index 1c52f133..d8623818 100644 --- a/crates/bsnext_system/src/handle_fs_event_grouping.rs +++ b/crates/bsnext_system/src/handle_fs_event_grouping.rs @@ -17,14 +17,14 @@ use bsnext_fs::{ use bsnext_input::bs_live_built_in_task::BsLiveBuiltInTask; use bsnext_input::route::WatchSpec; use bsnext_input::{Input, InputError, PathDefinition, PathDefs, PathError}; -use bsnext_monitor::{FsEventGrouping, Group}; +use bsnext_monitor::{FsGroup, Group}; use bsnext_task::task_trigger::FsChangesTrigger; use tracing::{debug, debug_span, info}; -impl actix::Handler for BsSystem { +impl actix::Handler for BsSystem { type Result = (); - fn handle(&mut self, msg: FsEventGrouping, ctx: &mut Self::Context) -> Self::Result { + fn handle(&mut self, msg: FsGroup, ctx: &mut Self::Context) -> Self::Result { let addr = ctx.address(); let span = debug_span!("Handler->FsEventGrouping->BsSystem"); let _guard = span.enter(); diff --git a/crates/bsnext_system/src/input_fs.rs b/crates/bsnext_system/src/input_fs.rs index fecb1a58..f249e05c 100644 --- a/crates/bsnext_system/src/input_fs.rs +++ b/crates/bsnext_system/src/input_fs.rs @@ -1,81 +1,5 @@ use bsnext_input::{Input, InputCreation, InputCtx, InputError}; -use std::path::{Path, PathBuf}; - -pub enum ResolvedInputOutcome { - /// You provided a path that couldn't be resolved - Missing { - user_input: String, - cwd: PathBuf, - absolute: PathBuf, - err: Box, - }, - - GivenPath { - user_input: String, - cwd: PathBuf, - absolute: PathBuf, - }, - - /// You provided no paths but a path was auto-resolved - Auto { - named: String, - cwd: PathBuf, - absolute: PathBuf, - }, - - /// You provided no paths and nothing was auto-resolved - Empty, -} - -impl ResolvedInputOutcome { - pub fn new>(cwd: PathBuf, inputs: &[T]) -> Self { - resolve_inputs(cwd, inputs) - } -} - -fn resolve_inputs>(cwd: PathBuf, inputs: &[T]) -> ResolvedInputOutcome { - let input_candidates = inputs - .iter() - .map(|path| { - let abs = cwd.join(path.as_ref()); - let exists = Path::exists(&abs); - (path.as_ref(), abs, exists) - }) - .collect::>(); - - if let Some((user_input, abs, exists)) = input_candidates.first() { - if !exists { - return ResolvedInputOutcome::Missing { - user_input: (*user_input).to_owned(), - absolute: (*abs).to_owned(), - cwd, - err: Box::new(InputError::MissingInputs), - }; - } - return ResolvedInputOutcome::GivenPath { - user_input: (*user_input).to_owned(), - absolute: (*abs).to_owned(), - cwd, - }; - } - - let auto_lookups = ["bslive.yml", "bslive.yaml", "bslive.md", "bslive.html"]; - - for named in auto_lookups { - let abs = cwd.join(named); - let exists = Path::exists(&abs); - if !exists { - continue; - } - return ResolvedInputOutcome::Auto { - named: (*named).to_owned(), - cwd, - absolute: (*abs).to_owned(), - }; - } - - ResolvedInputOutcome::Empty -} +use std::path::Path; pub fn from_input_path>(path: P, ctx: &InputCtx) -> Result> { match path.as_ref().extension().and_then(|x| x.to_str()) { diff --git a/crates/bsnext_system/src/lib.rs b/crates/bsnext_system/src/lib.rs index 827b9d0f..d3f069b0 100644 --- a/crates/bsnext_system/src/lib.rs +++ b/crates/bsnext_system/src/lib.rs @@ -10,7 +10,6 @@ pub mod input_fs; mod invoke_scope; pub mod monitor_any; pub mod monitor_input; -pub mod mutable_state; pub mod override_input; pub mod run; mod servers; diff --git a/crates/bsnext_system/src/mutable_state.rs b/crates/bsnext_system/src/mutable_state.rs deleted file mode 100644 index 6cf57a50..00000000 --- a/crates/bsnext_system/src/mutable_state.rs +++ /dev/null @@ -1,3 +0,0 @@ -use crate::system::BsSystem; - -impl BsSystem {} diff --git a/crates/bsnext_system/src/start/start_kind/run_from_input.rs b/crates/bsnext_system/src/start/start_kind/run_from_input.rs index 7a94f654..a3882976 100644 --- a/crates/bsnext_system/src/start/start_kind/run_from_input.rs +++ b/crates/bsnext_system/src/start/start_kind/run_from_input.rs @@ -1,4 +1,5 @@ -use crate::input_fs::{from_input_path, ResolvedInputOutcome}; +use crate::input_fs::from_input_path; +use bsnext_input::input_fs::ResolvedInputOutcome; use bsnext_input::startup::{ RunMode, StartupContext, SystemStart, SystemStartArgs, TopLevelRunMode, }; From 43bd08d0c8891af1d54585f6153deebe090f0afd Mon Sep 17 00:00:00 2001 From: Shane Osbourne Date: Mon, 4 May 2026 19:26:24 +0100 Subject: [PATCH 06/10] some cleanup --- Cargo.lock | 117 ------------------ Cargo.toml | 1 - bsnext/Cargo.toml | 1 - crates/bsnext_example/Cargo.toml | 12 -- crates/bsnext_example/src/basic.rs | 49 -------- crates/bsnext_example/src/lib.rs | 56 --------- crates/bsnext_example/src/lit.rs | 31 ----- crates/bsnext_example/src/md.rs | 20 --- crates/bsnext_example/src/playground.rs | 20 --- crates/bsnext_system/Cargo.toml | 3 - crates/bsnext_system/src/args.rs | 8 -- crates/bsnext_system/src/cli.rs | 32 +---- crates/bsnext_system/src/export/mod.rs | 48 ------- .../src/handle_fs_event_grouping.rs | 2 +- crates/bsnext_system/src/lib.rs | 1 - crates/bsnext_system/src/run/mod.rs | 85 +++++++++---- .../bsnext_system/src/start/start_command.rs | 58 ++++++++- crates/bsnext_system/src/start/start_kind.rs | 100 +-------------- .../start/start_kind/start_from_example.rs | 82 ------------ 19 files changed, 124 insertions(+), 602 deletions(-) delete mode 100644 crates/bsnext_example/Cargo.toml delete mode 100644 crates/bsnext_example/src/basic.rs delete mode 100644 crates/bsnext_example/src/lib.rs delete mode 100644 crates/bsnext_example/src/lit.rs delete mode 100644 crates/bsnext_example/src/md.rs delete mode 100644 crates/bsnext_example/src/playground.rs delete mode 100644 crates/bsnext_system/src/export/mod.rs delete mode 100644 crates/bsnext_system/src/start/start_kind/start_from_example.rs diff --git a/Cargo.lock b/Cargo.lock index 984b7035..e2842844 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -432,25 +432,11 @@ dependencies = [ "alloc-stdlib", ] -[[package]] -name = "bslive" -version = "0.26.0" -dependencies = [ - "actix-rt", - "bsnext_system", - "napi", - "napi-build", - "napi-derive", - "serde_json", - "tokio", -] - [[package]] name = "bsnext" version = "0.26.0" dependencies = [ "actix-rt", - "anyhow", "bsnext_system", "tokio", ] @@ -525,16 +511,6 @@ dependencies = [ "typeshare", ] -[[package]] -name = "bsnext_example" -version = "0.26.0" -dependencies = [ - "bsnext_html", - "bsnext_input", - "bsnext_md", - "clap", -] - [[package]] name = "bsnext_fs" version = "0.26.0" @@ -694,7 +670,6 @@ dependencies = [ "anyhow", "bsnext_core", "bsnext_dto", - "bsnext_example", "bsnext_fs", "bsnext_fs_helpers", "bsnext_html", @@ -712,9 +687,7 @@ dependencies = [ "globset", "insta", "kill_tree", - "rand 0.8.5", "reqwest", - "sqids", "tempfile", "tokio", "tokio-stream", @@ -909,15 +882,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "convert_case" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" -dependencies = [ - "unicode-segmentation", -] - [[package]] name = "core-foundation" version = "0.9.4" @@ -1000,16 +964,6 @@ dependencies = [ "syn", ] -[[package]] -name = "ctor" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edb49164822f3ee45b17acd4a208cfc1251410cf0cad9a833234c9890774dd9f" -dependencies = [ - "quote", - "syn", -] - [[package]] name = "darling" version = "0.20.11" @@ -1979,65 +1933,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "napi" -version = "2.16.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55740c4ae1d8696773c78fdafd5d0e5fe9bc9f1b071c7ba493ba5c413a9184f3" -dependencies = [ - "bitflags 2.5.0", - "ctor", - "napi-derive", - "napi-sys", - "once_cell", - "serde", - "serde_json", -] - -[[package]] -name = "napi-build" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03acbfa4f156a32188bfa09b86dc11a431b5725253fc1fc6f6df5bed273382c4" - -[[package]] -name = "napi-derive" -version = "2.16.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cbe2585d8ac223f7d34f13701434b9d5f4eb9c332cccce8dee57ea18ab8ab0c" -dependencies = [ - "cfg-if", - "convert_case", - "napi-derive-backend", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "napi-derive-backend" -version = "1.0.75" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1639aaa9eeb76e91c6ae66da8ce3e89e921cd3885e99ec85f4abacae72fc91bf" -dependencies = [ - "convert_case", - "once_cell", - "proc-macro2", - "quote", - "regex", - "semver", - "syn", -] - -[[package]] -name = "napi-sys" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "427802e8ec3a734331fec1035594a210ce1ff4dc5bc1950530920ab717964ea3" -dependencies = [ - "libloading", -] - [[package]] name = "native-tls" version = "0.2.11" @@ -2915,12 +2810,6 @@ dependencies = [ "smallvec", ] -[[package]] -name = "semver" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" - [[package]] name = "serde" version = "1.0.228" @@ -3813,12 +3702,6 @@ dependencies = [ "tinyvec", ] -[[package]] -name = "unicode-segmentation" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" - [[package]] name = "unicode-width" version = "0.1.13" diff --git a/Cargo.toml b/Cargo.toml index e7e843df..c3935f8a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,4 @@ workspace.members = [ - "bslive", "bsnext", "crates/*", ] diff --git a/bsnext/Cargo.toml b/bsnext/Cargo.toml index b4e07113..ca2a2aa2 100644 --- a/bsnext/Cargo.toml +++ b/bsnext/Cargo.toml @@ -7,5 +7,4 @@ edition = "2021" bsnext_system = { path = "../crates/bsnext_system" } actix-rt = { workspace = true } -anyhow = { workspace = true } tokio = { workspace = true } diff --git a/crates/bsnext_example/Cargo.toml b/crates/bsnext_example/Cargo.toml deleted file mode 100644 index afe4cc4c..00000000 --- a/crates/bsnext_example/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "bsnext_example" -version = "0.26.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -bsnext_input = { path = "../bsnext_input" } -bsnext_md = { path = "../bsnext_md" } -bsnext_html = { path = "../bsnext_html" } -clap = { workspace = true } diff --git a/crates/bsnext_example/src/basic.rs b/crates/bsnext_example/src/basic.rs deleted file mode 100644 index 9fa13276..00000000 --- a/crates/bsnext_example/src/basic.rs +++ /dev/null @@ -1,49 +0,0 @@ -use bsnext_input::route::{Route, RouteKind}; -use bsnext_input::server_config::ServerIdentity; -use bsnext_input::{ - server_config::{self}, - Input, InputSource, InputSourceKind, -}; - -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub struct BasicExample; - -impl InputSource for BasicExample { - fn into_input(self, identity: Option) -> InputSourceKind { - let server = server_config::ServerConfig { - identity: identity.unwrap_or_else(ServerIdentity::named), - routes: vec![ - Route { - path: "/".to_string().parse().unwrap(), - kind: RouteKind::new_html(include_str!( - "../../../examples/basic/public/index.html" - )), - ..Default::default() - }, - Route { - path: "/styles.css".to_string().parse().unwrap(), - kind: RouteKind::new_raw(include_str!( - "../../../examples/basic/public/styles.css" - )), - ..Default::default() - }, - Route { - path: "/script.js".to_string().parse().unwrap(), - kind: RouteKind::new_raw(include_str!( - "../../../examples/basic/public/script.js" - )), - ..Default::default() - }, - Route { - path: "/reset.css".to_string().parse().unwrap(), - kind: RouteKind::new_raw(include_str!( - "../../../examples/basic/public/reset.css" - )), - ..Default::default() - }, - ], - ..Default::default() - }; - InputSourceKind::Type(Input::from_server(server)) - } -} diff --git a/crates/bsnext_example/src/lib.rs b/crates/bsnext_example/src/lib.rs deleted file mode 100644 index d89fe2a4..00000000 --- a/crates/bsnext_example/src/lib.rs +++ /dev/null @@ -1,56 +0,0 @@ -use crate::basic::BasicExample; -use crate::lit::LitExample; -use crate::md::MdExample; -use crate::playground::PlaygroundExample; -use bsnext_input::server_config::ServerIdentity; -use bsnext_input::{InputSource, InputSourceKind}; - -pub mod basic; -pub mod lit; -pub mod md; - -pub mod playground; - -#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, clap::ValueEnum)] -pub enum Example { - Basic, - Lit, - Md, - #[default] - Playground, -} - -use bsnext_input::target::TargetKind; - -#[derive(Debug, Clone, clap::Parser)] -pub struct ExampleCommand { - /// Write input to disk - #[arg(long)] - pub target: Option, - - #[arg(long, value_enum, default_value_t)] - pub example: Example, - - /// create a temp folder for examples instead of using the current dir - #[arg(long, requires = "example")] - pub temp: bool, - - /// Override output folder (not compatible with 'temp') - #[arg(long, requires = "example", conflicts_with = "temp")] - pub dir: Option, - - /// create a temp folder for examples instead of using the current dir - #[arg(long, requires = "example", conflicts_with = "dir")] - pub name: Option, -} - -impl InputSource for Example { - fn into_input(self, identity: Option) -> InputSourceKind { - match self { - Example::Basic => BasicExample.into_input(identity), - Example::Lit => LitExample.into_input(identity), - Example::Md => MdExample.into_input(identity), - Example::Playground => PlaygroundExample.into_input(identity), - } - } -} diff --git a/crates/bsnext_example/src/lit.rs b/crates/bsnext_example/src/lit.rs deleted file mode 100644 index e9ea9b59..00000000 --- a/crates/bsnext_example/src/lit.rs +++ /dev/null @@ -1,31 +0,0 @@ -use bsnext_input::route::{Route, RouteKind}; -use bsnext_input::server_config::ServerIdentity; -use bsnext_input::{ - server_config::{self}, - Input, InputSource, InputSourceKind, -}; - -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub struct LitExample; - -impl InputSource for LitExample { - fn into_input(self, identity: Option) -> InputSourceKind { - let server = server_config::ServerConfig { - identity: identity.unwrap_or_else(ServerIdentity::named), - routes: vec![ - Route { - path: "/".to_string().parse().unwrap(), - kind: RouteKind::new_html(include_str!("../../../examples/lit/index.html")), - ..Default::default() - }, - Route { - path: "/lit.js".to_string().parse().unwrap(), - kind: RouteKind::new_raw(include_str!("../../../examples/lit/lit.js")), - ..Default::default() - }, - ], - ..Default::default() - }; - InputSourceKind::Type(Input::from_server(server)) - } -} diff --git a/crates/bsnext_example/src/md.rs b/crates/bsnext_example/src/md.rs deleted file mode 100644 index ace72504..00000000 --- a/crates/bsnext_example/src/md.rs +++ /dev/null @@ -1,20 +0,0 @@ -use bsnext_input::server_config::ServerIdentity; -use bsnext_input::{InputCreation, InputSource, InputSourceKind}; -use bsnext_md::md_fs::MdFs; - -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub struct MdExample; - -impl InputSource for MdExample { - fn into_input(self, identity: Option) -> InputSourceKind { - let input_str = include_str!("../../../examples/markdown/single.md"); - let mut input = - MdFs::from_input_str(input_str, &Default::default()).expect("example cannot fail?"); - let server = input - .servers - .first_mut() - .expect("example must have 1 server"); - server.identity = identity.unwrap_or_else(ServerIdentity::named); - InputSourceKind::Type(input) - } -} diff --git a/crates/bsnext_example/src/playground.rs b/crates/bsnext_example/src/playground.rs deleted file mode 100644 index ad92e500..00000000 --- a/crates/bsnext_example/src/playground.rs +++ /dev/null @@ -1,20 +0,0 @@ -use bsnext_html::HtmlFs; -use bsnext_input::server_config::ServerIdentity; -use bsnext_input::{InputCreation, InputCtx, InputSource, InputSourceKind}; - -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub struct PlaygroundExample; - -impl InputSource for PlaygroundExample { - fn into_input(self, identity: Option) -> InputSourceKind { - let input_str = include_str!("../../../examples/html/playground.html"); - let mut input = HtmlFs::from_input_str(input_str, &InputCtx::default()).unwrap(); - - // update the server identity if it was provided - if let (Some(server), Some(identity)) = (input.servers.get_mut(0), identity) { - server.identity = identity; - } - - InputSourceKind::Type(input) - } -} diff --git a/crates/bsnext_system/Cargo.toml b/crates/bsnext_system/Cargo.toml index 911ffa2b..09c9229e 100644 --- a/crates/bsnext_system/Cargo.toml +++ b/crates/bsnext_system/Cargo.toml @@ -16,13 +16,10 @@ bsnext_core = { path = "../bsnext_core" } bsnext_fs = { path = "../bsnext_fs" } bsnext_fs_helpers = { path = "../bsnext_fs_helpers" } bsnext_dto = { path = "../bsnext_dto" } -bsnext_example = { path = "../bsnext_example" } bsnext_output = { path = "../bsnext_output" } bsnext_task = { path = "../bsnext_task" } bsnext_monitor = { path = "../bsnext_monitor" } -rand = "0.8.5" kill_tree = { workspace = true } -sqids = { workspace = true } anyhow = { workspace = true } clap = { workspace = true } diff --git a/crates/bsnext_system/src/args.rs b/crates/bsnext_system/src/args.rs index c68393f8..c34c9d1d 100644 --- a/crates/bsnext_system/src/args.rs +++ b/crates/bsnext_system/src/args.rs @@ -2,9 +2,7 @@ use crate::run::RunCommand; use crate::start::start_command::StartCommand; use crate::watch::watch_sub_opts::WatchSubOpts; use crate::watch::WatchCommand; -use bsnext_core::export::ExportCommand; use bsnext_core::shared_args::{FsOpts, InputOpts, LoggingOpts}; -use bsnext_example::ExampleCommand; use bsnext_tracing::OutputFormat; // bslive route --path=/ --dir= @@ -46,7 +44,6 @@ impl Args { match &self.command { Some(SubCommands::Watch(WatchCommand { logging, .. })) => logging, Some(SubCommands::Start(StartCommand { logging, .. })) => logging, - Some(SubCommands::Export(ExportCommand { logging, .. })) => logging, Some(SubCommands::Run(RunCommand { logging, .. })) => logging, _ => &self.logging, } @@ -55,7 +52,6 @@ impl Args { match &self.command { Some(SubCommands::Watch(WatchCommand { format, .. })) => format.to_owned(), Some(SubCommands::Start(StartCommand { format, .. })) => format.to_owned(), - Some(SubCommands::Export(ExportCommand { format, .. })) => format.to_owned(), Some(SubCommands::Run(RunCommand { format, .. })) => format.to_owned(), _ => self.format, } @@ -66,10 +62,6 @@ impl Args { pub enum SubCommands { /// Start the services Start(StartCommand), - /// Export raw entries to files - Export(ExportCommand), - /// Run an example project - Example(ExampleCommand), /// Just use file watching Watch(WatchCommand), /// Just run tasks diff --git a/crates/bsnext_system/src/cli.rs b/crates/bsnext_system/src/cli.rs index fd55b11b..6a2e3b31 100644 --- a/crates/bsnext_system/src/cli.rs +++ b/crates/bsnext_system/src/cli.rs @@ -1,11 +1,9 @@ use crate::args::{Args, SubCommands}; -use crate::export::export_cmd; use crate::start; use crate::start::start_command::StartCommand; use crate::start::start_kind::start_from_inputs::StartFromInput; use crate::start::start_kind::StartKind; use crate::start::stdout_channel; -use bsnext_core::shared_args::LoggingOpts; use bsnext_input::route::MultiWatch; use bsnext_input::Input; use bsnext_output::OutputWriters; @@ -71,7 +69,7 @@ where OutputFormat::Json => OutputWriters::Json, }; - let command = args.command.unwrap_or_else(move || { + let sub_command = args.command.unwrap_or_else(move || { SubCommands::Start(StartCommand { cors: false, port: args.port, @@ -84,9 +82,9 @@ where }) }); - tracing::debug!("subcommand = {:?}", command); + tracing::debug!("subcommand = {:?}", sub_command); let _guard = debug_span!("parent").entered(); - let r = async_init(command, writer, logging, format, args_c, cwd).await; + let r = async_init(sub_command, writer, args_c, cwd).await; drop(_guard); drop(tracing_guard); r @@ -95,32 +93,12 @@ where async fn async_init( command: SubCommands, writer: OutputWriters, - logging: LoggingOpts, - format: OutputFormat, args: Args, cwd: PathBuf, ) -> Result<(), anyhow::Error> { match command { - SubCommands::Export(cmd) => { - let start_cmd = StartCommand { - cors: false, - port: None, - trailing: cmd.trailing.clone(), - proxies: vec![], - watch_sub_opts: Default::default(), - logging, - format, - no_watch: true, - }; - let cwd = PathBuf::from(current_dir().unwrap().to_string_lossy().to_string()); - let result = export_cmd(&cwd, &args.fs_opts, &args.input_opts, &cmd, &start_cmd).await; - bsnext_output::stdout::completion_writer(writer, result) - } - SubCommands::Example(example) => { - todo!("{:?}", example); - } SubCommands::Start(start) => { - let start_kind = StartKind::from_args(&args.fs_opts, &args.input_opts, &start); + let start_kind = start.as_start_kind(&args.fs_opts, &args.input_opts); start_stdout_wrapper(start_kind, cwd, writer).await } SubCommands::Watch(watch) => { @@ -131,7 +109,7 @@ async fn async_init( start_stdout_wrapper(start_kind, cwd, writer).await } SubCommands::Run(run) => { - let start_kind = StartKind::from_run_args(&args.fs_opts, &args.input_opts, run); + let start_kind = run.as_start_kind(&args.input_opts); start_stdout_wrapper(start_kind, cwd, writer) .instrument(debug_span!("SubCommands::Run").or_current()) .await diff --git a/crates/bsnext_system/src/export/mod.rs b/crates/bsnext_system/src/export/mod.rs deleted file mode 100644 index cfc40601..00000000 --- a/crates/bsnext_system/src/export/mod.rs +++ /dev/null @@ -1,48 +0,0 @@ -use crate::start::start_command::StartCommand; -use crate::start::start_kind::StartKind; -use bsnext_core::export::{export_one_server, ExportCommand}; -use bsnext_core::shared_args::{FsOpts, InputOpts}; -use bsnext_fs_helpers::WriteMode; -use bsnext_input::startup::{StartupContext, SystemStart, SystemStartArgs}; -use bsnext_output::OutputWriterTrait; -use std::path::PathBuf; - -pub async fn export_cmd( - cwd: &PathBuf, - fs_opts: &FsOpts, - input_opts: &InputOpts, - cmd: &ExportCommand, - start_command: &StartCommand, -) -> Result, impl OutputWriterTrait> { - let ctx = StartupContext::from_cwd(Some(cwd)); - tracing::debug!("StartupContext: {:?}", ctx); - - let start_kind = StartKind::from_args(fs_opts, input_opts, start_command).resolve_input(&ctx); - - match start_kind { - Err(e) => todo!( - "handle errors here, eg: when the startup was invalid {:?}", - e - ), - Ok(SystemStartArgs::InputOnly { input: _ }) => todo!("handle InputOnly?"), - Ok(SystemStartArgs::PathWithInput { path: _, input }) if input.servers.len() == 1 => { - let first = &input.servers[0]; - - let fs_write_mode = if fs_opts.force { - WriteMode::Override - } else { - WriteMode::Safe - }; - - export_one_server(cwd, first.clone(), cmd, fs_write_mode).await - } - Ok(SystemStartArgs::PathWithInput { path: _, input: _ }) => { - // let first = - // let _result = export_one_server(cwd, cmd).await; - todo!("handle more than 1 server for export?") - } - Ok(SystemStartArgs::PathWithInvalidInput { .. }) => todo!("handle PathWithInvalidInput?"), - Ok(SystemStartArgs::RunOnly { .. }) => todo!("handle PathWithInvalidInput?"), - Ok(SystemStartArgs::InputOnlyDeferred { .. }) => todo!("not yet..."), - } -} diff --git a/crates/bsnext_system/src/handle_fs_event_grouping.rs b/crates/bsnext_system/src/handle_fs_event_grouping.rs index d8623818..9b2a755e 100644 --- a/crates/bsnext_system/src/handle_fs_event_grouping.rs +++ b/crates/bsnext_system/src/handle_fs_event_grouping.rs @@ -151,7 +151,7 @@ impl BsSystem { let ctx = self .input_monitors .as_ref() - .map(|x| x.input_ctx.clone()) + .map(|input_monitor| input_monitor.input_ctx.clone()) .unwrap_or_default(); let input = from_input_path(&inner.absolute, &ctx); diff --git a/crates/bsnext_system/src/lib.rs b/crates/bsnext_system/src/lib.rs index d3f069b0..9cb86d17 100644 --- a/crates/bsnext_system/src/lib.rs +++ b/crates/bsnext_system/src/lib.rs @@ -2,7 +2,6 @@ mod api; pub mod args; pub mod capabilities; pub mod cli; -mod export; mod external_event_sender; mod fs_task_tracker; mod handle_fs_event_grouping; diff --git a/crates/bsnext_system/src/run/mod.rs b/crates/bsnext_system/src/run/mod.rs index f89d9fc9..7076e978 100644 --- a/crates/bsnext_system/src/run/mod.rs +++ b/crates/bsnext_system/src/run/mod.rs @@ -1,7 +1,10 @@ pub mod resolve_spec; -use bsnext_core::shared_args::LoggingOpts; +use crate::start::start_kind::run_from_input::RunFromInputPaths; +use crate::start::start_kind::StartKind; +use bsnext_core::shared_args::{InputOpts, LoggingOpts}; use bsnext_input::route::{RunAll, RunOptItem, RunSeq, ShRunOptItem}; +use bsnext_input::startup::{RunMode, TopLevelRunMode}; use bsnext_input::Input; use bsnext_tracing::OutputFormat; @@ -37,35 +40,67 @@ pub struct RunCommand { } impl RunCommand { - #[tracing::instrument] - pub fn as_input(&self) -> Input { - let mut input = Input::default(); - let mut list_of_commands: Vec = vec![]; + pub fn as_start_kind(&self, input_opts: &InputOpts) -> StartKind { + let from_cmd = as_input(self); - { - for (index, sh) in self.sh_commands.iter().enumerate() { - tracing::info!(index = index, sh = sh, name = "None", prefix = "None"); - list_of_commands.push(RunOptItem::Sh(ShRunOptItem { - sh: sh.clone(), - name: None, - prefix: None, - })); - } - } - if list_of_commands.is_empty() { - return input; + tracing::debug!(self.trailing = ?self.trailing); + tracing::debug!(self.sh_commands = ?self.sh_commands); + tracing::debug!(self.all = ?self.all); + + let named = if self.trailing.is_empty() { + vec!["default".to_string()] + } else { + self.trailing.to_owned() }; - let mut items = vec![]; - if self.all { - let run_all = RunAll::new(list_of_commands); - items.push(RunOptItem::All(run_all)); + + // dry takes precedence + let run_mode = if self.dry { + RunMode::Dry } else { - let run_seq = RunSeq::new(list_of_commands); - items.push(RunOptItem::Seq(run_seq)); + RunMode::Exec { + preview: self.preview, + summary: self.summary, + } + }; + let top_level = TopLevelRunMode::Seq; + StartKind::Run(RunFromInputPaths::new( + from_cmd, + input_opts.input.clone(), + named, + run_mode, + top_level, + )) + } +} + +#[tracing::instrument] +fn as_input(run: &RunCommand) -> Input { + let mut input = Input::default(); + let mut list_of_commands: Vec = vec![]; + + { + for (index, sh) in run.sh_commands.iter().enumerate() { + tracing::info!(index = index, sh = sh, name = "None", prefix = "None"); + list_of_commands.push(RunOptItem::Sh(ShRunOptItem { + sh: sh.clone(), + name: None, + prefix: None, + })); } - input.run.insert("default".to_string(), items); - input } + if list_of_commands.is_empty() { + return input; + }; + let mut items = vec![]; + if run.all { + let run_all = RunAll::new(list_of_commands); + items.push(RunOptItem::All(run_all)); + } else { + let run_seq = RunSeq::new(list_of_commands); + items.push(RunOptItem::Seq(run_seq)); + } + input.run.insert("default".to_string(), items); + input } #[cfg(test)] diff --git a/crates/bsnext_system/src/start/start_command.rs b/crates/bsnext_system/src/start/start_command.rs index 6b312a17..099849de 100644 --- a/crates/bsnext_system/src/start/start_command.rs +++ b/crates/bsnext_system/src/start/start_command.rs @@ -1,5 +1,11 @@ +use crate::start::start_kind::start_from_inputs::{StartFromInput, StartFromInputPaths}; +use crate::start::start_kind::start_from_paths::StartFromPaths; +use crate::start::start_kind::StartKind; use crate::watch::watch_sub_opts::WatchSubOpts; -use bsnext_core::shared_args::LoggingOpts; +use bsnext_core::shared_args::{FsOpts, InputOpts, LoggingOpts}; +use bsnext_input::route::{CorsOpts, Opts, Route}; +use bsnext_input::server_config::{ServerConfig, ServerIdentity}; +use bsnext_input::Input; use bsnext_tracing::OutputFormat; #[derive(Debug, Default, Clone, clap::Parser)] @@ -34,3 +40,53 @@ pub struct StartCommand { #[clap(flatten)] pub watch_sub_opts: WatchSubOpts, } + +impl StartCommand { + pub fn as_start_kind(&self, fs_opts: &FsOpts, input_opts: &InputOpts) -> StartKind { + // todo: make the addition of a proxy + route opts easier? + if !self.trailing.is_empty() { + tracing::debug!( + "{} trailing, {} inputs", + self.trailing.len(), + input_opts.input.len() + ); + return StartKind::FromPaths(StartFromPaths { + paths: self.trailing.clone(), + write_input: fs_opts.write, + port: self.port, + force: fs_opts.force, + watch_sub_opts: self.watch_sub_opts.clone(), + route_opts: Opts { + cors: self.cors.then_some(CorsOpts::Cors(true)), + ..Default::default() + }, + no_watch: self.no_watch, + }); + } + + tracing::debug!("0 trailing, {} inputs", input_opts.input.len()); + if input_opts.input.is_empty() && !self.proxies.is_empty() { + tracing::debug!("input was empty, but had proxies"); + let first_proxy = self.proxies.first().expect("guarded first proxy"); + let r = Route::proxy(first_proxy); + let id = ServerIdentity::from_port_or_named(self.port).unwrap_or_else(|_e| { + tracing::error!("A problem occurred with the port?"); + ServerIdentity::named() + }); + let ser = ServerConfig::from_route(r, id); + let input = Input::from_server(ser); + StartKind::FromInput(StartFromInput { input }) + } else { + tracing::debug!( + input_len = input_opts.input.len(), + proxes = self.proxies.len(), + "neither inputs nor proxies were present" + ); + StartKind::FromInputPaths(StartFromInputPaths { + input_paths: input_opts.input.clone(), + port: self.port, + no_watch: self.no_watch, + }) + } + } +} diff --git a/crates/bsnext_system/src/start/start_kind.rs b/crates/bsnext_system/src/start/start_kind.rs index 1cba1b3e..a85e5992 100644 --- a/crates/bsnext_system/src/start/start_kind.rs +++ b/crates/bsnext_system/src/start/start_kind.rs @@ -1,23 +1,14 @@ -use crate::run::RunCommand; -use crate::start::start_command::StartCommand; use crate::start::start_kind::run_from_input::RunFromInputPaths; -use crate::start::start_kind::start_from_example::StartFromExample; use crate::start::start_kind::start_from_inputs::{StartFromInput, StartFromInputPaths}; use crate::start::start_kind::start_from_paths::StartFromPaths; -use bsnext_core::shared_args::{FsOpts, InputOpts}; use bsnext_fs_helpers::{fs_write_str, FsWriteError, WriteMode}; -use bsnext_input::route::{CorsOpts, Opts, Route}; -use bsnext_input::server_config::{ServerConfig, ServerIdentity}; -use bsnext_input::startup::{ - RunMode, StartupContext, SystemStart, SystemStartArgs, TopLevelRunMode, -}; +use bsnext_input::startup::{StartupContext, SystemStart, SystemStartArgs}; use bsnext_input::target::TargetKind; use bsnext_input::InputWriter; use bsnext_input::{Input, InputError}; use std::path::{Path, PathBuf}; pub mod run_from_input; -pub mod start_from_example; pub mod start_from_inputs; pub mod start_from_paths; @@ -27,102 +18,13 @@ pub enum StartKind { Run(RunFromInputPaths), FromInput(StartFromInput), FromInputPaths(StartFromInputPaths), - FromExample(StartFromExample), FromPaths(StartFromPaths), } -impl StartKind { - #[tracing::instrument(name = "from_args")] - pub fn from_args(fs_opts: &FsOpts, input_opts: &InputOpts, cmd: &StartCommand) -> Self { - // todo: make the addition of a proxy + route opts easier? - if !cmd.trailing.is_empty() { - tracing::debug!( - "{} trailing, {} inputs", - cmd.trailing.len(), - input_opts.input.len() - ); - return StartKind::FromPaths(StartFromPaths { - paths: cmd.trailing.clone(), - write_input: fs_opts.write, - port: cmd.port, - force: fs_opts.force, - watch_sub_opts: cmd.watch_sub_opts.clone(), - route_opts: Opts { - cors: cmd.cors.then_some(CorsOpts::Cors(true)), - ..Default::default() - }, - no_watch: cmd.no_watch, - }); - } - - tracing::debug!("0 trailing, {} inputs", input_opts.input.len()); - if input_opts.input.is_empty() && !cmd.proxies.is_empty() { - tracing::debug!("input was empty, but had proxies"); - let first_proxy = cmd.proxies.first().expect("guarded first proxy"); - let r = Route::proxy(first_proxy); - let id = ServerIdentity::from_port_or_named(cmd.port).unwrap_or_else(|_e| { - tracing::error!("A problem occurred with the port?"); - ServerIdentity::named() - }); - let ser = ServerConfig::from_route(r, id); - let input = Input::from_server(ser); - StartKind::FromInput(StartFromInput { input }) - } else { - tracing::debug!( - input_len = input_opts.input.len(), - proxes = cmd.proxies.len(), - "neither inputs nor proxies were present" - ); - StartKind::FromInputPaths(StartFromInputPaths { - input_paths: input_opts.input.clone(), - port: cmd.port, - no_watch: cmd.no_watch, - }) - } - } - pub fn from_input(input: Input) -> Self { - Self::FromInput(StartFromInput { input }) - } - - #[tracing::instrument(skip_all)] - pub fn from_run_args(_fs_opts: &FsOpts, input_opts: &InputOpts, run_cmd: RunCommand) -> Self { - let from_cmd = run_cmd.as_input(); - - tracing::debug!(run_cmd.trailing = ?run_cmd.trailing); - tracing::debug!(run_cmd.sh_commands = ?run_cmd.sh_commands); - tracing::debug!(run_cmd.all = ?run_cmd.all); - - let named = if run_cmd.trailing.is_empty() { - vec!["default".to_string()] - } else { - run_cmd.trailing - }; - - // dry takes precedence - let run_mode = if run_cmd.dry { - RunMode::Dry - } else { - RunMode::Exec { - preview: run_cmd.preview, - summary: run_cmd.summary, - } - }; - let top_level = TopLevelRunMode::Seq; - StartKind::Run(RunFromInputPaths::new( - from_cmd, - input_opts.input.clone(), - named, - run_mode, - top_level, - )) - } -} - impl SystemStart for StartKind { fn resolve_input(&self, ctx: &StartupContext) -> Result> { match self { StartKind::FromInputPaths(from_inputs) => from_inputs.resolve_input(ctx), - StartKind::FromExample(from_example) => from_example.resolve_input(ctx), StartKind::FromPaths(from_trailing_args) => from_trailing_args.resolve_input(ctx), StartKind::FromInput(from_inputs) => from_inputs.resolve_input(ctx), StartKind::Run(run_from_input) => run_from_input.resolve_input(ctx), diff --git a/crates/bsnext_system/src/start/start_kind/start_from_example.rs b/crates/bsnext_system/src/start/start_kind/start_from_example.rs deleted file mode 100644 index 985f0c2f..00000000 --- a/crates/bsnext_system/src/start/start_kind/start_from_example.rs +++ /dev/null @@ -1,82 +0,0 @@ -use crate::start::start_kind::fs_write_input; -use bsnext_example::Example; -use bsnext_fs_helpers::WriteMode; -use bsnext_input::server_config::ServerIdentity; -use bsnext_input::startup::{StartupContext, SystemStart, SystemStartArgs}; -use bsnext_input::target::TargetKind; -use bsnext_input::{rand_word, InputError, InputSource, InputSourceKind}; - -#[derive(Debug)] -pub struct StartFromExample { - pub example: Example, - pub write_input: bool, - pub target_kind: TargetKind, - pub port: Option, - pub temp: bool, - pub force: bool, - pub name: Option, - pub dir: Option, -} - -impl SystemStart for StartFromExample { - fn resolve_input(&self, ctx: &StartupContext) -> Result> { - // todo: mimic this for other kinds of startup - basically allow 'port' to be given and respected - let identity = - ServerIdentity::from_port_or_named(self.port).map_err(|e| Box::new(e.into()))?; - let input_source_kind = self.example.into_input(Some(identity)); - - let write_mode = if self.force { - WriteMode::Override - } else { - WriteMode::Safe - }; - - let target_dir = if self.temp { - let temp_dir = std::env::temp_dir(); - let name = self.name.clone(); - let word = name.unwrap_or_else(rand_word); - let num = rand::random::(); - let next_dir = temp_dir.join(format!("bslive-{word}-{num}")); - bsnext_fs_helpers::create_dir_and_cd(&next_dir, &write_mode)? - } else if let Some(dir) = &self.dir { - let next_dir = ctx.cwd.join(dir); - bsnext_fs_helpers::create_dir_and_cd(&next_dir, &write_mode)? - } else { - ctx.cwd.to_path_buf() - }; - - if !self.write_input { - tracing::info!("NOT writing input..."); - return match input_source_kind { - InputSourceKind::Type(input) => Ok(SystemStartArgs::InputOnly { input }), - InputSourceKind::File { input, .. } => Ok(SystemStartArgs::InputOnly { input }), - }; - } - - tracing::info!( - "will write to {} because write_input was true.", - target_dir.display() - ); - - let (path, input) = match input_source_kind { - InputSourceKind::Type(input) => { - let path = - fs_write_input(&target_dir, &input, self.target_kind.clone(), &write_mode) - .map_err(|e| Box::new(e.into()))?; - (path, input) - } - InputSourceKind::File { src_file, input } => { - let path = bsnext_fs_helpers::fs_write_str( - &target_dir, - src_file.path(), - src_file.content(), - &write_mode, - ) - .map_err(|e| Box::new(e.into()))?; - (path, input) - } - }; - - Ok(SystemStartArgs::PathWithInput { path, input }) - } -} From d65105bacb890ea6798cc6187b075012fde1ba7f Mon Sep 17 00:00:00 2001 From: Shane Osbourne Date: Sun, 28 Jun 2026 11:48:41 +0100 Subject: [PATCH 07/10] cleanup --- crates/bsnext_fs/src/actor.rs | 4 +- crates/bsnext_fs/src/filter.rs | 33 ++++++++- crates/bsnext_fs/src/watch_path_handler.rs | 1 + crates/bsnext_monitor/src/path_monitor.rs | 74 +++++++++++++++---- .../bsnext_system/examples/override_input.rs | 14 ++-- .../bsnext_system/examples/without_stdout.rs | 14 ++-- crates/bsnext_system/src/run/mod.rs | 11 --- .../src/start/start_kind/start_from_inputs.rs | 1 - .../bsnext_system/tests/manual_fs_events.rs | 19 ++--- 9 files changed, 113 insertions(+), 58 deletions(-) diff --git a/crates/bsnext_fs/src/actor.rs b/crates/bsnext_fs/src/actor.rs index 4815694e..ef6f8866 100644 --- a/crates/bsnext_fs/src/actor.rs +++ b/crates/bsnext_fs/src/actor.rs @@ -50,11 +50,11 @@ impl FsWatcher { } pub fn with_filter(&mut self, f: Filter) { - tracing::debug!("adding filter {:?}", f); + tracing::debug!("adding filter {}", f); self.filters.push(f) } pub fn with_ignore(&mut self, f: Filter) { - tracing::debug!("adding ignore {:?}", f); + tracing::debug!("adding ignore {}", f); self.ignore.push(f) } diff --git a/crates/bsnext_fs/src/filter.rs b/crates/bsnext_fs/src/filter.rs index 2ad2d505..b0325064 100644 --- a/crates/bsnext_fs/src/filter.rs +++ b/crates/bsnext_fs/src/filter.rs @@ -1,4 +1,5 @@ use crate::PathDescription; +use std::fmt::{Display, Formatter}; #[derive(Debug, Clone)] pub enum Filter { @@ -15,10 +16,36 @@ pub enum Filter { any: String, }, } + +impl Display for Filter { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let str = match self { + Filter::None => String::from("Filter::None"), + Filter::Extension { ext } => format!("Filter::Extension({})", ext), + Filter::Glob { + raw, + scope, + glob: _, + } => format!("Filter::Glob Raw({}) Scope({})", raw, scope), + Filter::Any { any } => format!("Filter::Any({})", any), + }; + write!(f, "{}", str) + } +} + #[derive(Debug, Clone)] pub enum FilterScope { - Abs, - Rel, + Absolute, + Relative, +} + +impl Display for FilterScope { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + FilterScope::Absolute => write!(f, "FilterScope::Absolute"), + FilterScope::Relative => write!(f, "FilterScope::Relative"), + } + } } impl PathFilter for Filter { @@ -33,7 +60,7 @@ impl PathFilter for Filter { } Filter::Glob { glob, scope, raw } => { let target = match (scope, pd.relative) { - (FilterScope::Rel, Some(rel)) => rel, + (FilterScope::Relative, Some(rel)) => rel, _ => pd.absolute, }; let compare = target.to_string_lossy().to_string(); diff --git a/crates/bsnext_fs/src/watch_path_handler.rs b/crates/bsnext_fs/src/watch_path_handler.rs index ccfc591d..0ccd8589 100644 --- a/crates/bsnext_fs/src/watch_path_handler.rs +++ b/crates/bsnext_fs/src/watch_path_handler.rs @@ -19,6 +19,7 @@ impl Handler for FsWatcher { todo!("Can this ever be reached?"); }; + tracing::debug!(?self.cwd, ?msg.path, "will watch msg.path"); match watcher.watch(&msg.path, RecursiveMode::Recursive) { Ok(_) => { // tracing::debug!(path = ?msg.path, "👀 watching! {} receivers", self.receivers.len()); diff --git a/crates/bsnext_monitor/src/path_monitor.rs b/crates/bsnext_monitor/src/path_monitor.rs index ede969b2..8c17e3a8 100644 --- a/crates/bsnext_monitor/src/path_monitor.rs +++ b/crates/bsnext_monitor/src/path_monitor.rs @@ -67,7 +67,9 @@ impl actix::Handler for PathMonitor { fn handle(&mut self, msg: WatchPaths, ctx: &mut Self::Context) -> Self::Result { for single_path in &msg.paths { let as_str = single_path.to_string_lossy(); + tracing::debug!(?as_str, "before split"); let PathAndFilter { path, filter_kind } = PathAndFilter::new(&as_str); + tracing::debug!(?path, ?filter_kind, "will split"); // create a filter list, first using the optional filter given above let mut filters = filter_kind.into_iter().collect::>(); @@ -90,9 +92,9 @@ impl actix::Handler for PathMonitor { self.addrs.push(watcher_addr.clone()); - watcher_addr.do_send(RequestWatchPath { - path: path.to_path_buf(), - }); + let pb = path.to_path_buf(); + tracing::debug!(?pb, ?path, "as pb"); + watcher_addr.do_send(RequestWatchPath { path: pb }); } let Some(receiver) = self.inner_receiver.take() else { @@ -156,20 +158,62 @@ struct PathAndFilter<'a> { impl<'a> PathAndFilter<'a> { pub fn new(p: &'a str) -> Self { - if let Some((before, ..)) = p.split_once("*") { - PathAndFilter { + match p.split_once("*") { + // for cases like '**/*.toml' or '*.css' + Some(("", ..)) => PathAndFilter { + path: Path::new("."), + filter_kind: Some(PathPattern::Glob { + glob: p.to_string(), + }), + }, + Some((before, ..)) => PathAndFilter { path: Path::new(before), filter_kind: Some(PathPattern::Glob { glob: p.to_string(), }), - } - } else { - PathAndFilter { + }, + None => PathAndFilter { path: Path::new(p), filter_kind: None, - } + }, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_path_and_filter_with_glob() { + let input = "abc/*.css"; + let result = PathAndFilter::new(input); + + assert_eq!(result.path, Path::new("abc/")); + assert!(matches!(result.filter_kind, Some(PathPattern::Glob { .. }))); + + if let Some(PathPattern::Glob { glob }) = result.filter_kind { + assert_eq!(glob, "abc/*.css"); } } + + #[test] + fn test_path_and_filter_without_glob() { + let input = "abc/style.css"; + let result = PathAndFilter::new(input); + + assert_eq!(result.path, Path::new("abc/style.css")); + assert!(result.filter_kind.is_none()); + } + + #[test] + fn test_glob_without_path() { + let input = "**/*.toml"; + let result = PathAndFilter::new(input); + + assert_eq!(result.path, Path::new(".")); + // assert!(result.filter_kind.is_none()); + } } impl PathFilter for PathAndFilter<'_> { @@ -203,12 +247,12 @@ fn convert(fk: &PathPattern) -> Vec { Ok(pattern) if is_abs => vec![Filter::Glob { glob: pattern, raw: string_default.to_string(), - scope: FilterScope::Abs, + scope: FilterScope::Absolute, }], Ok(pattern) => vec![Filter::Glob { glob: pattern, raw: string_default.to_string(), - scope: FilterScope::Rel, + scope: FilterScope::Relative, }], Err(e) => { tracing::error!("could not use glob {:?}", string_default); @@ -236,12 +280,12 @@ fn convert(fk: &PathPattern) -> Vec { Ok(pattern) if is_abs => vec![Filter::Glob { glob: pattern, raw: glob.to_string(), - scope: FilterScope::Abs, + scope: FilterScope::Absolute, }], Ok(pattern) => vec![Filter::Glob { glob: pattern, raw: glob.to_string(), - scope: FilterScope::Rel, + scope: FilterScope::Relative, }], Err(e) => { tracing::error!("could not use glob '{:?}'", glob); @@ -333,14 +377,14 @@ fn to_watcher( if let Some(filter_kind) = &filter { let filters = convert(filter_kind); for filter in filters { - debug!(filter = ?filter, "append filter"); + debug!(filter = %filter, "append filter"); watcher.with_filter(filter); } } if let Some(ignore_filter_kind) = &ignore { let ignores = convert(ignore_filter_kind); for ignore in ignores { - debug!(ignore = ?ignore, "with ignore"); + debug!(ignore = %ignore, "with ignore"); watcher.with_ignore(ignore); } } diff --git a/crates/bsnext_system/examples/override_input.rs b/crates/bsnext_system/examples/override_input.rs index 9cc1ede6..c620051a 100644 --- a/crates/bsnext_system/examples/override_input.rs +++ b/crates/bsnext_system/examples/override_input.rs @@ -1,6 +1,5 @@ -use bsnext_core::shared_args::{FsOpts, InputOpts}; use bsnext_dto::internal::AnyEvent; -use bsnext_system::start::start_command::StartCommand; +use bsnext_system::start::start_kind::start_from_inputs::StartFromInputPaths; use bsnext_system::start::start_kind::StartKind; use bsnext_system::start::start_system::start_system; use std::fs; @@ -27,21 +26,20 @@ servers: let tmp_dir = tempfile::tempdir().unwrap(); let index_file = tmp_dir.path().join("bslive.yaml"); + let index_file_str = index_file.to_string_lossy().to_string(); fs::write(&index_file, input).expect("can write?"); let cwd = PathBuf::from(tmp_dir.path()); - let start = StartCommand { - cors: false, + let start = StartFromInputPaths { + input_paths: vec![index_file_str], port: None, - trailing: vec![], - proxies: vec![], - ..Default::default() + no_watch: false, }; let (events_sender, _) = mpsc::channel::(1); - let start_kind = StartKind::from_args(&FsOpts::default(), &InputOpts::default(), &start); + let start_kind = StartKind::FromInputPaths(start); let api = start_system(cwd, start_kind, events_sender) .await .map_err(|e| anyhow::anyhow!("{:?}", e))?; diff --git a/crates/bsnext_system/examples/without_stdout.rs b/crates/bsnext_system/examples/without_stdout.rs index 12244089..80ff2573 100644 --- a/crates/bsnext_system/examples/without_stdout.rs +++ b/crates/bsnext_system/examples/without_stdout.rs @@ -1,6 +1,7 @@ use bsnext_core::shared_args::{FsOpts, InputOpts}; use bsnext_dto::internal::AnyEvent; use bsnext_system::start::start_command::StartCommand; +use bsnext_system::start::start_kind::start_from_paths::StartFromPaths; use bsnext_system::start::start_kind::StartKind; use bsnext_system::start::start_system::start_system; use std::fs; @@ -16,19 +17,18 @@ pub async fn main() -> Result<(), anyhow::Error> { let cwd = PathBuf::from(tmp_dir.path()); let as_str = cwd.to_string_lossy().to_string(); - let start = StartCommand { - cors: false, + let start = StartFromPaths { port: None, - trailing: vec![as_str], - proxies: vec![], - logging: Default::default(), - format: Default::default(), + force: false, + paths: vec![as_str], watch_sub_opts: Default::default(), no_watch: false, + write_input: false, + route_opts: Default::default(), }; let (events_sender, mut events_receiver) = mpsc::channel::(1); - let start_kind = StartKind::from_args(&FsOpts::default(), &InputOpts::default(), &start); + let start_kind = StartKind::FromPaths(start); let api = start_system(cwd, start_kind, events_sender) .await .map_err(|e| anyhow::anyhow!("{:?}", e))?; diff --git a/crates/bsnext_system/src/run/mod.rs b/crates/bsnext_system/src/run/mod.rs index 7076e978..4c84608c 100644 --- a/crates/bsnext_system/src/run/mod.rs +++ b/crates/bsnext_system/src/run/mod.rs @@ -122,15 +122,4 @@ mod test { assert_eq!(run_cmd.preview, true); Ok(()) } - - #[test] - fn input() -> anyhow::Result<()> { - let run_cmd = RunCommand::try_parse_from(vec!["COMMAND_NAME", "--sh", "def"])?; - let as_input = run_cmd.as_input(); - let s = vec![RunOptItem::Seq(RunSeq::new(vec![RunOptItem::Sh( - ShRunOptItem::new("def"), - )]))]; - assert_eq!(as_input.run.get("default"), Some(&s)); - Ok(()) - } } diff --git a/crates/bsnext_system/src/start/start_kind/start_from_inputs.rs b/crates/bsnext_system/src/start/start_kind/start_from_inputs.rs index 36bd4ed0..007a63ee 100644 --- a/crates/bsnext_system/src/start/start_kind/start_from_inputs.rs +++ b/crates/bsnext_system/src/start/start_kind/start_from_inputs.rs @@ -5,7 +5,6 @@ use bsnext_input::{Input, InputArgs, InputCtx, InputError, WatchGlobalConfig}; use std::path::{Path, PathBuf}; #[derive(Debug)] -// todo: must ensure StartFromInputPaths pub struct StartFromInputPaths { pub input_paths: Vec, pub port: Option, diff --git a/crates/bsnext_system/tests/manual_fs_events.rs b/crates/bsnext_system/tests/manual_fs_events.rs index 6a8d1a79..0ed44d5b 100644 --- a/crates/bsnext_system/tests/manual_fs_events.rs +++ b/crates/bsnext_system/tests/manual_fs_events.rs @@ -1,8 +1,7 @@ -use bsnext_core::shared_args::{FsOpts, InputOpts}; use bsnext_dto::external_events::ExternalEventsDTO; use bsnext_dto::internal::AnyEvent; use bsnext_fs::FsEvent; -use bsnext_system::start::start_command::StartCommand; +use bsnext_system::start::start_kind::start_from_inputs::StartFromInputPaths; use bsnext_system::start::start_kind::StartKind; use bsnext_system::start::start_system::start_system; use futures_util::future::join; @@ -33,21 +32,19 @@ servers: raw: "body { background: cyan }" "#; fs::write(&index_file, input).expect("can write?"); + let index_file_str = index_file.to_string_lossy().to_string(); let cwd = PathBuf::from(tmp_dir.path()); let (events_sender, events_receiver) = mpsc::channel::(2); - let start = StartCommand { - cors: false, + + let start = StartFromInputPaths { + no_watch: true, + input_paths: vec![index_file_str], port: None, - proxies: vec![], - trailing: vec![], - logging: Default::default(), - format: Default::default(), - watch_sub_opts: Default::default(), - no_watch: false, }; - let start_kind = StartKind::from_args(&FsOpts::default(), &InputOpts::default(), &start); + + let start_kind = StartKind::FromInputPaths(start); let api = start_system(cwd, start_kind, events_sender) .await .map_err(|e| anyhow::anyhow!("{:?}", e))?; From 58efcc6a208e5d3086b076bffde101124d9fb55f Mon Sep 17 00:00:00 2001 From: Shane Osbourne Date: Sun, 28 Jun 2026 11:58:28 +0100 Subject: [PATCH 08/10] renames --- Cargo.lock | 20 +++++++++---------- .../Cargo.toml | 2 +- .../examples/path_monitor_example.rs | 4 ++-- .../src/lib.rs | 0 .../src/monitor_path_watchables.rs | 0 .../src/path_monitor.rs | 0 .../src/watchables/any_watchable.rs | 0 .../src/watchables/mod.rs | 0 .../src/watchables/path_watchable.rs | 0 .../src/watchables/route_watchable.rs | 0 .../src/watchables/server_watchable.rs | 0 crates/bsnext_system/Cargo.toml | 2 +- crates/bsnext_system/src/api.rs | 2 +- .../src/handle_fs_event_grouping.rs | 2 +- crates/bsnext_system/src/monitor_any.rs | 2 +- crates/bsnext_system/src/monitor_input.rs | 6 +++--- crates/bsnext_system/src/override_input.rs | 2 +- crates/bsnext_system/src/system.rs | 2 +- 18 files changed, 22 insertions(+), 22 deletions(-) rename crates/{bsnext_monitor => bsnext_path_monitor}/Cargo.toml (92%) rename crates/{bsnext_monitor => bsnext_path_monitor}/examples/path_monitor_example.rs (96%) rename crates/{bsnext_monitor => bsnext_path_monitor}/src/lib.rs (100%) rename crates/{bsnext_monitor => bsnext_path_monitor}/src/monitor_path_watchables.rs (100%) rename crates/{bsnext_monitor => bsnext_path_monitor}/src/path_monitor.rs (100%) rename crates/{bsnext_monitor => bsnext_path_monitor}/src/watchables/any_watchable.rs (100%) rename crates/{bsnext_monitor => bsnext_path_monitor}/src/watchables/mod.rs (100%) rename crates/{bsnext_monitor => bsnext_path_monitor}/src/watchables/path_watchable.rs (100%) rename crates/{bsnext_monitor => bsnext_path_monitor}/src/watchables/route_watchable.rs (100%) rename crates/{bsnext_monitor => bsnext_path_monitor}/src/watchables/server_watchable.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index e2842844..054ee605 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -610,25 +610,25 @@ dependencies = [ ] [[package]] -name = "bsnext_monitor" +name = "bsnext_output" version = "0.26.0" dependencies = [ - "actix", - "actix-rt", "anyhow", - "bsnext_fs", - "bsnext_input", - "globset", - "tokio", - "tokio-stream", "tracing", ] [[package]] -name = "bsnext_output" +name = "bsnext_path_monitor" version = "0.26.0" dependencies = [ + "actix", + "actix-rt", "anyhow", + "bsnext_fs", + "bsnext_input", + "globset", + "tokio", + "tokio-stream", "tracing", ] @@ -676,8 +676,8 @@ dependencies = [ "bsnext_input", "bsnext_js", "bsnext_md", - "bsnext_monitor", "bsnext_output", + "bsnext_path_monitor", "bsnext_task", "bsnext_tracing", "bsnext_yaml", diff --git a/crates/bsnext_monitor/Cargo.toml b/crates/bsnext_path_monitor/Cargo.toml similarity index 92% rename from crates/bsnext_monitor/Cargo.toml rename to crates/bsnext_path_monitor/Cargo.toml index e39d48c2..a0a1a758 100644 --- a/crates/bsnext_monitor/Cargo.toml +++ b/crates/bsnext_path_monitor/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "bsnext_monitor" +name = "bsnext_path_monitor" version = "0.26.0" edition = "2024" diff --git a/crates/bsnext_monitor/examples/path_monitor_example.rs b/crates/bsnext_path_monitor/examples/path_monitor_example.rs similarity index 96% rename from crates/bsnext_monitor/examples/path_monitor_example.rs rename to crates/bsnext_path_monitor/examples/path_monitor_example.rs index 347f6b1a..ea58dc1b 100644 --- a/crates/bsnext_monitor/examples/path_monitor_example.rs +++ b/crates/bsnext_path_monitor/examples/path_monitor_example.rs @@ -2,8 +2,8 @@ use actix::{Actor, ResponseFuture}; use actix_rt::System; use bsnext_fs::{FsEvent, FsEventContext}; use bsnext_input::route::{DebounceDuration, WatchSpec}; -use bsnext_monitor::FsGroup; -use bsnext_monitor::path_monitor::{PathMonitor, WatchPaths}; +use bsnext_path_monitor::FsGroup; +use bsnext_path_monitor::path_monitor::{PathMonitor, WatchPaths}; use std::env::current_dir; use std::process; use std::time::Duration; diff --git a/crates/bsnext_monitor/src/lib.rs b/crates/bsnext_path_monitor/src/lib.rs similarity index 100% rename from crates/bsnext_monitor/src/lib.rs rename to crates/bsnext_path_monitor/src/lib.rs diff --git a/crates/bsnext_monitor/src/monitor_path_watchables.rs b/crates/bsnext_path_monitor/src/monitor_path_watchables.rs similarity index 100% rename from crates/bsnext_monitor/src/monitor_path_watchables.rs rename to crates/bsnext_path_monitor/src/monitor_path_watchables.rs diff --git a/crates/bsnext_monitor/src/path_monitor.rs b/crates/bsnext_path_monitor/src/path_monitor.rs similarity index 100% rename from crates/bsnext_monitor/src/path_monitor.rs rename to crates/bsnext_path_monitor/src/path_monitor.rs diff --git a/crates/bsnext_monitor/src/watchables/any_watchable.rs b/crates/bsnext_path_monitor/src/watchables/any_watchable.rs similarity index 100% rename from crates/bsnext_monitor/src/watchables/any_watchable.rs rename to crates/bsnext_path_monitor/src/watchables/any_watchable.rs diff --git a/crates/bsnext_monitor/src/watchables/mod.rs b/crates/bsnext_path_monitor/src/watchables/mod.rs similarity index 100% rename from crates/bsnext_monitor/src/watchables/mod.rs rename to crates/bsnext_path_monitor/src/watchables/mod.rs diff --git a/crates/bsnext_monitor/src/watchables/path_watchable.rs b/crates/bsnext_path_monitor/src/watchables/path_watchable.rs similarity index 100% rename from crates/bsnext_monitor/src/watchables/path_watchable.rs rename to crates/bsnext_path_monitor/src/watchables/path_watchable.rs diff --git a/crates/bsnext_monitor/src/watchables/route_watchable.rs b/crates/bsnext_path_monitor/src/watchables/route_watchable.rs similarity index 100% rename from crates/bsnext_monitor/src/watchables/route_watchable.rs rename to crates/bsnext_path_monitor/src/watchables/route_watchable.rs diff --git a/crates/bsnext_monitor/src/watchables/server_watchable.rs b/crates/bsnext_path_monitor/src/watchables/server_watchable.rs similarity index 100% rename from crates/bsnext_monitor/src/watchables/server_watchable.rs rename to crates/bsnext_path_monitor/src/watchables/server_watchable.rs diff --git a/crates/bsnext_system/Cargo.toml b/crates/bsnext_system/Cargo.toml index 09c9229e..e152eaa8 100644 --- a/crates/bsnext_system/Cargo.toml +++ b/crates/bsnext_system/Cargo.toml @@ -18,7 +18,7 @@ bsnext_fs_helpers = { path = "../bsnext_fs_helpers" } bsnext_dto = { path = "../bsnext_dto" } bsnext_output = { path = "../bsnext_output" } bsnext_task = { path = "../bsnext_task" } -bsnext_monitor = { path = "../bsnext_monitor" } +bsnext_path_monitor = { path = "../bsnext_path_monitor" } kill_tree = { workspace = true } anyhow = { workspace = true } diff --git a/crates/bsnext_system/src/api.rs b/crates/bsnext_system/src/api.rs index 8045f085..2cf0d258 100644 --- a/crates/bsnext_system/src/api.rs +++ b/crates/bsnext_system/src/api.rs @@ -6,7 +6,7 @@ use bsnext_dto::internal::ServerError; use bsnext_dto::ActiveServer; use bsnext_fs::{Debounce, FsEvent}; use bsnext_input::route::WatchSpec; -use bsnext_monitor::FsGroup; +use bsnext_path_monitor::FsGroup; use tokio::sync::oneshot; #[derive(Debug)] diff --git a/crates/bsnext_system/src/handle_fs_event_grouping.rs b/crates/bsnext_system/src/handle_fs_event_grouping.rs index 9b2a755e..a31af58f 100644 --- a/crates/bsnext_system/src/handle_fs_event_grouping.rs +++ b/crates/bsnext_system/src/handle_fs_event_grouping.rs @@ -17,7 +17,7 @@ use bsnext_fs::{ use bsnext_input::bs_live_built_in_task::BsLiveBuiltInTask; use bsnext_input::route::WatchSpec; use bsnext_input::{Input, InputError, PathDefinition, PathDefs, PathError}; -use bsnext_monitor::{FsGroup, Group}; +use bsnext_path_monitor::{FsGroup, Group}; use bsnext_task::task_trigger::FsChangesTrigger; use tracing::{debug, debug_span, info}; diff --git a/crates/bsnext_system/src/monitor_any.rs b/crates/bsnext_system/src/monitor_any.rs index d35d6419..065ac87a 100644 --- a/crates/bsnext_system/src/monitor_any.rs +++ b/crates/bsnext_system/src/monitor_any.rs @@ -2,7 +2,7 @@ use crate::system::BsSystem; use actix::{ActorFutureExt, AsyncContext}; use actix::{ResponseActFuture, WrapFuture}; use bsnext_input::Input; -use bsnext_monitor::watchables::accept_watchables; +use bsnext_path_monitor::watchables::accept_watchables; use tracing::debug_span; /// Message to monitor file system paths based on the current input configuration. diff --git a/crates/bsnext_system/src/monitor_input.rs b/crates/bsnext_system/src/monitor_input.rs index fb0559c1..32fb2105 100644 --- a/crates/bsnext_system/src/monitor_input.rs +++ b/crates/bsnext_system/src/monitor_input.rs @@ -3,9 +3,9 @@ use actix::{Actor, Addr, AsyncContext}; use bsnext_fs::{Debounce, FsEventContext}; use bsnext_input::route::WatchSpec; use bsnext_input::InputCtx; -use bsnext_monitor::path_monitor::{PathMonitor, WatchPaths}; -use bsnext_monitor::watchables::any_watchable::AnyWatchable; -use bsnext_monitor::watchables::path_watchable::PathWatchable; +use bsnext_path_monitor::path_monitor::{PathMonitor, WatchPaths}; +use bsnext_path_monitor::watchables::any_watchable::AnyWatchable; +use bsnext_path_monitor::watchables::path_watchable::PathWatchable; use std::path::PathBuf; use std::time::Duration; diff --git a/crates/bsnext_system/src/override_input.rs b/crates/bsnext_system/src/override_input.rs index 81bdd08b..064e00a2 100644 --- a/crates/bsnext_system/src/override_input.rs +++ b/crates/bsnext_system/src/override_input.rs @@ -5,7 +5,7 @@ use bsnext_dto::internal::{AnyEvent, ChildResult, ServerError}; use bsnext_dto::GetActiveServersResponse; use bsnext_input::startup::StartupContext; use bsnext_input::{Input, InputCtx}; -use bsnext_monitor::watchables::accept_watchables; +use bsnext_path_monitor::watchables::accept_watchables; use tracing::debug; #[derive(Debug, actix::Message)] diff --git a/crates/bsnext_system/src/system.rs b/crates/bsnext_system/src/system.rs index 30736005..af020ed4 100644 --- a/crates/bsnext_system/src/system.rs +++ b/crates/bsnext_system/src/system.rs @@ -14,7 +14,7 @@ use bsnext_dto::internal::{AnyEvent, ChildResult, TaskReportAndTree}; use bsnext_dto::GetActiveServersResponse; use bsnext_input::startup::{StartupContext, TopLevelRunMode}; use bsnext_input::Input; -use bsnext_monitor::Monitor; +use bsnext_path_monitor::Monitor; use bsnext_task::task_trigger::{ExecTrigger, TaskTrigger, TaskTriggerSource}; use std::path::PathBuf; use std::time::Duration; From b65c7b3e26944d049279707750c32f852a697078 Mon Sep 17 00:00:00 2001 From: Shane Osbourne Date: Sun, 28 Jun 2026 12:02:21 +0100 Subject: [PATCH 09/10] fix build --- Cargo.lock | 103 +++++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + 2 files changed, 104 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 054ee605..3773df6d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -432,6 +432,19 @@ dependencies = [ "alloc-stdlib", ] +[[package]] +name = "bslive" +version = "0.26.0" +dependencies = [ + "actix-rt", + "bsnext_system", + "napi", + "napi-build", + "napi-derive", + "serde_json", + "tokio", +] + [[package]] name = "bsnext" version = "0.26.0" @@ -882,6 +895,15 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -964,6 +986,16 @@ dependencies = [ "syn", ] +[[package]] +name = "ctor" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" +dependencies = [ + "quote", + "syn", +] + [[package]] name = "darling" version = "0.20.11" @@ -1933,6 +1965,65 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "napi" +version = "2.16.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55740c4ae1d8696773c78fdafd5d0e5fe9bc9f1b071c7ba493ba5c413a9184f3" +dependencies = [ + "bitflags 2.5.0", + "ctor", + "napi-derive", + "napi-sys", + "once_cell", + "serde", + "serde_json", +] + +[[package]] +name = "napi-build" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9c366d2c8c60b86fa632df75f745509b52f9128f91a6bad4c796e44abb505e1" + +[[package]] +name = "napi-derive" +version = "2.16.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cbe2585d8ac223f7d34f13701434b9d5f4eb9c332cccce8dee57ea18ab8ab0c" +dependencies = [ + "cfg-if", + "convert_case", + "napi-derive-backend", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "napi-derive-backend" +version = "1.0.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1639aaa9eeb76e91c6ae66da8ce3e89e921cd3885e99ec85f4abacae72fc91bf" +dependencies = [ + "convert_case", + "once_cell", + "proc-macro2", + "quote", + "regex", + "semver", + "syn", +] + +[[package]] +name = "napi-sys" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "427802e8ec3a734331fec1035594a210ce1ff4dc5bc1950530920ab717964ea3" +dependencies = [ + "libloading", +] + [[package]] name = "native-tls" version = "0.2.11" @@ -2810,6 +2901,12 @@ dependencies = [ "smallvec", ] +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + [[package]] name = "serde" version = "1.0.228" @@ -3702,6 +3799,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-segmentation" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" + [[package]] name = "unicode-width" version = "0.1.13" diff --git a/Cargo.toml b/Cargo.toml index c3935f8a..e7e843df 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,4 +1,5 @@ workspace.members = [ + "bslive", "bsnext", "crates/*", ] From 84b1053368376c72b9f96711f7956839e7a6d74f Mon Sep 17 00:00:00 2001 From: Shane Osbourne Date: Sun, 5 Jul 2026 23:08:11 +0100 Subject: [PATCH 10/10] more clean --- crates/bsnext_input/src/input_test/mod.rs | 4 +- crates/bsnext_input/src/route.rs | 83 ++++++++- crates/bsnext_input/src/watch_opt_test/mod.rs | 48 +++++- .../examples/path_monitor_example.rs | 79 ++++----- crates/bsnext_path_monitor/src/lib.rs | 32 +--- .../src/path_and_filter.rs | 124 ++++++++++++++ .../bsnext_path_monitor/src/path_monitor.rs | 158 ++---------------- .../src/watch_paths_msg.rs | 7 + .../bsnext_path_monitor/src/watchables/mod.rs | 83 --------- crates/bsnext_system/src/api.rs | 4 +- .../src/handle_fs_event_grouping.rs | 6 +- crates/bsnext_system/src/lib.rs | 2 + crates/bsnext_system/src/monitor_any.rs | 6 +- crates/bsnext_system/src/monitor_input.rs | 13 +- crates/bsnext_system/src/override_input.rs | 6 +- .../src/path_monitors/mod.rs} | 31 +++- crates/bsnext_system/src/system.rs | 8 +- .../bsnext_system/src/watch/watch_sub_opts.rs | 6 +- .../src/watchables/any_watchable.rs | 2 +- crates/bsnext_system/src/watchables/mod.rs | 80 +++++++++ .../src/watchables/path_watchable.rs | 0 .../src/watchables/route_watchable.rs | 2 +- .../src/watchables/server_watchable.rs | 2 +- 23 files changed, 457 insertions(+), 329 deletions(-) create mode 100644 crates/bsnext_path_monitor/src/path_and_filter.rs create mode 100644 crates/bsnext_path_monitor/src/watch_paths_msg.rs delete mode 100644 crates/bsnext_path_monitor/src/watchables/mod.rs rename crates/{bsnext_path_monitor/src/monitor_path_watchables.rs => bsnext_system/src/path_monitors/mod.rs} (85%) rename crates/{bsnext_path_monitor => bsnext_system}/src/watchables/any_watchable.rs (100%) create mode 100644 crates/bsnext_system/src/watchables/mod.rs rename crates/{bsnext_path_monitor => bsnext_system}/src/watchables/path_watchable.rs (100%) rename crates/{bsnext_path_monitor => bsnext_system}/src/watchables/route_watchable.rs (100%) rename crates/{bsnext_path_monitor => bsnext_system}/src/watchables/server_watchable.rs (100%) diff --git a/crates/bsnext_input/src/input_test/mod.rs b/crates/bsnext_input/src/input_test/mod.rs index c8f172dd..1690b324 100644 --- a/crates/bsnext_input/src/input_test/mod.rs +++ b/crates/bsnext_input/src/input_test/mod.rs @@ -214,7 +214,7 @@ fn test_deserialize_watch() { assert_eq!( c.opts.watch, WatchOpts::Spec(WatchSpec { - debounce: Some(DebounceDuration::Ms(2000)), + debounce: Some(DebounceDuration::Ms { ms: 2000 }), only: None, ignore: None, run: None, @@ -247,7 +247,7 @@ servers: MultiWatch { dirs: WatcherDirs::Single("./other".to_string()), spec: Some(WatchSpec { - debounce: Some(DebounceDuration::Ms(2000)), + debounce: Some(DebounceDuration::Ms { ms: 2000 }), only: Some(PathPattern::Extension { ext: "**/*.css".to_string() }), diff --git a/crates/bsnext_input/src/route.rs b/crates/bsnext_input/src/route.rs index 0465cdac..01e40320 100644 --- a/crates/bsnext_input/src/route.rs +++ b/crates/bsnext_input/src/route.rs @@ -297,21 +297,98 @@ pub enum DelayKind { #[derive( Debug, Ord, PartialOrd, PartialEq, Eq, Hash, Clone, Copy, serde::Deserialize, serde::Serialize, )] +#[serde(untagged)] pub enum DebounceDuration { - #[serde(rename = "ms")] - Ms(u64), + MsStrategy { ms: u64, strategy: Strategy }, + Ms { ms: u64 }, +} + +#[derive( + Debug, Ord, PartialOrd, PartialEq, Eq, Hash, Clone, Copy, serde::Deserialize, serde::Serialize, +)] +pub enum Strategy { + #[serde(rename = "buffered")] + Buffered, + #[serde(rename = "trailing")] + Trailing, } impl From for Debounce { fn from(debounce_duration: DebounceDuration) -> Self { match debounce_duration { - DebounceDuration::Ms(ms) => Debounce::Buffered { + DebounceDuration::MsStrategy { + ms, + strategy: Strategy::Buffered, + } => Debounce::Buffered { + duration: Duration::from_millis(ms), + }, + DebounceDuration::Ms { ms } => Debounce::Buffered { + duration: Duration::from_millis(ms), + }, + DebounceDuration::MsStrategy { + ms, + strategy: Strategy::Trailing, + } => Debounce::Trailing { duration: Duration::from_millis(ms), }, } } } +#[cfg(test)] +mod debounce_duration_tests { + use super::*; + use serde::{de, Deserialize, Deserializer}; + + #[test] + fn test_debounce_duration_ms_trailing() { + #[derive(serde::Deserialize, Debug)] + struct A { + a: DebounceDuration, + } + let yaml = r#" +a: + ms: 12 + strategy: ase +"#; + let result: A = serde_yaml::from_str(yaml).unwrap(); + dbg!(result); + + // assert_eq!(result, DebounceDuration::MsTrailing(250)); + } + + // #[test] + // fn test_debounce_duration_ms_buffered() { + // let yaml = r#" + // ms_buffered: 500 + // "#; + // let result: DebounceDuration = serde_yaml::from_str(yaml).unwrap(); + // assert_eq!(result, DebounceDuration::MsBuffered(500)); + // } + // + // #[test] + // fn test_debounce_duration_converts_to_buffered() { + // let debounce: Debounce = DebounceDuration::Ms(100).into(); + // assert_eq!( + // debounce, + // Debounce::Buffered { + // duration: Duration::from_millis(100) + // } + // ); + // } + // + // #[test] + // fn test_debounce_duration_converts_to_trailing() { + // let debounce: Debounce = DebounceDuration::MsTrailing(150).into(); + // assert_eq!( + // debounce, + // Debounce::Trailing { + // duration: Duration::from_millis(150) + // } + // ); + // } +} + #[derive( Debug, Ord, PartialOrd, PartialEq, Eq, Hash, Clone, serde::Deserialize, serde::Serialize, )] diff --git a/crates/bsnext_input/src/watch_opt_test/mod.rs b/crates/bsnext_input/src/watch_opt_test/mod.rs index a3a7fc8d..7f3dc4ad 100644 --- a/crates/bsnext_input/src/watch_opt_test/mod.rs +++ b/crates/bsnext_input/src/watch_opt_test/mod.rs @@ -1,6 +1,6 @@ use crate::route::{ DebounceDuration, PathPattern, RunAll, RunAllOpts, RunOptItem, RunSeq, SeqOpts, ShRunOptItem, - WatchSpec, + Strategy, WatchSpec, }; use crate::watch_opts::WatchOpts; @@ -12,7 +12,51 @@ fn test_watch_opts_debounce() { only: "**/*.css" "#; let expected = WatchOpts::Spec(WatchSpec { - debounce: Some(DebounceDuration::Ms(200)), + debounce: Some(DebounceDuration::Ms { ms: 200 }), + only: Some(PathPattern::StringDefault("**/*.css".into())), + ignore: None, + run: None, + before: None, + }); + let actual: WatchOpts = serde_yaml::from_str(input).unwrap(); + assert_eq!(actual, expected); +} + +#[test] +fn test_watch_opts_debounce_with_trailing() { + let input = r#" + debounce: + ms: 200 + strategy: trailing + only: "**/*.css" + "#; + let expected = WatchOpts::Spec(WatchSpec { + debounce: Some(DebounceDuration::MsStrategy { + ms: 200, + strategy: Strategy::Trailing, + }), + only: Some(PathPattern::StringDefault("**/*.css".into())), + ignore: None, + run: None, + before: None, + }); + let actual: WatchOpts = serde_yaml::from_str(input).unwrap(); + assert_eq!(actual, expected); +} + +#[test] +fn test_watch_opts_debounce_with_buffered() { + let input = r#" + debounce: + ms: 200 + strategy: buffered + only: "**/*.css" + "#; + let expected = WatchOpts::Spec(WatchSpec { + debounce: Some(DebounceDuration::MsStrategy { + ms: 200, + strategy: Strategy::Buffered, + }), only: Some(PathPattern::StringDefault("**/*.css".into())), ignore: None, run: None, diff --git a/crates/bsnext_path_monitor/examples/path_monitor_example.rs b/crates/bsnext_path_monitor/examples/path_monitor_example.rs index ea58dc1b..853abd14 100644 --- a/crates/bsnext_path_monitor/examples/path_monitor_example.rs +++ b/crates/bsnext_path_monitor/examples/path_monitor_example.rs @@ -2,8 +2,9 @@ use actix::{Actor, ResponseFuture}; use actix_rt::System; use bsnext_fs::{FsEvent, FsEventContext}; use bsnext_input::route::{DebounceDuration, WatchSpec}; -use bsnext_path_monitor::FsGroup; -use bsnext_path_monitor::path_monitor::{PathMonitor, WatchPaths}; +use bsnext_path_monitor::PathMonitorEvent; +use bsnext_path_monitor::path_monitor::PathMonitor; +use bsnext_path_monitor::watch_paths_msg::WatchPaths; use std::env::current_dir; use std::process; use std::time::Duration; @@ -31,62 +32,61 @@ fn main() { } } -async fn async_main() -> anyhow::Result { - #[derive(Default)] - struct Consumer { - events: Vec, - } +#[derive(Default)] +struct Consumer { + events: Vec, +} - #[derive(actix::Message, Debug, Clone)] - #[rtype(result = "()")] - struct Ping; +#[derive(actix::Message, Debug, Clone)] +#[rtype(result = "()")] +struct Ping; - impl actix::Actor for Consumer { - type Context = actix::Context; - } +impl actix::Actor for Consumer { + type Context = actix::Context; +} - impl actix::Handler for Consumer { - type Result = (); +impl actix::Handler for Consumer { + type Result = (); - fn handle(&mut self, msg: FsGroup, _ctx: &mut Self::Context) -> Self::Result { - self.events.push(msg); - } + fn handle(&mut self, msg: PathMonitorEvent, _ctx: &mut Self::Context) -> Self::Result { + self.events.push(msg); } +} - impl actix::Handler for Consumer { - type Result = ResponseFuture<()>; +impl actix::Handler for Consumer { + type Result = ResponseFuture<()>; - fn handle(&mut self, _msg: Ping, _ctx: &mut Self::Context) -> Self::Result { - Box::pin(async {}) - } + fn handle(&mut self, _msg: Ping, _ctx: &mut Self::Context) -> Self::Result { + Box::pin(async {}) } +} - #[derive(actix::Message, Debug, Clone)] - #[rtype(result = "Vec")] - struct Read; +#[derive(actix::Message, Debug, Clone)] +#[rtype(result = "Vec")] +struct Read; - impl actix::Handler for Consumer { - type Result = Vec; +impl actix::Handler for Consumer { + type Result = Vec; - fn handle(&mut self, _msg: Read, _ctx: &mut Self::Context) -> Self::Result { - self.events.clone() - } + fn handle(&mut self, _msg: Read, _ctx: &mut Self::Context) -> Self::Result { + self.events.clone() } +} +async fn async_main() -> anyhow::Result { let consumer = Consumer::default(); let actor = consumer.start(); - let reciever = actor.clone().recipient(); + let receiver = actor.clone().recipient(); let cwd = current_dir().unwrap(); let fs_context = FsEventContext::default(); - let debounce = bsnext_fs::Debounce::Buffered { - duration: Duration::from_millis(0), - }; + let debounce_duration = DebounceDuration::Ms { ms: 0 }; + let debounce = bsnext_fs::Debounce::from(debounce_duration); let spec = WatchSpec { - debounce: Some(DebounceDuration::Ms(0)), - ..Default::default() + debounce: Some(debounce_duration), + ..WatchSpec::default() }; - let monitor = PathMonitor::new(reciever, debounce, cwd, fs_context, spec); + let monitor = PathMonitor::new(receiver, debounce, cwd, fs_context, spec); let monitor_actor = monitor.start(); monitor_actor.send(WatchPaths { paths: vec![] }).await?; let _did_wait = actor.send(Ping).await; @@ -95,7 +95,8 @@ async fn async_main() -> anyhow::Result { .await; let r = Read; tokio::time::sleep(Duration::from_millis(10)).await; - let events = actor.send(r).await?; + let events: Vec<_> = actor.send(r).await?; + dbg!(&events); assert_eq!(events.len(), 1); Ok(0) } diff --git a/crates/bsnext_path_monitor/src/lib.rs b/crates/bsnext_path_monitor/src/lib.rs index 503521f9..ee00ab08 100644 --- a/crates/bsnext_path_monitor/src/lib.rs +++ b/crates/bsnext_path_monitor/src/lib.rs @@ -1,34 +1,12 @@ -use crate::path_monitor::PathMonitor; -use crate::watchables::path_watchable::PathWatchable; -use actix::Addr; use bsnext_fs::{BufferedChangeEvent, Debounce, FsEvent, FsEventContext, PathDescriptionOwned}; use bsnext_input::route::WatchSpec; -use std::collections::HashMap; - -pub mod monitor_path_watchables; +pub mod path_and_filter; pub mod path_monitor; -pub mod watchables; - -#[derive(Debug, Default)] -pub struct Monitor { - path_monitors: HashMap>, -} - -impl Monitor { - pub fn new() -> Self { - Self { - path_monitors: Default::default(), - } - } -} - -impl actix::Actor for Monitor { - type Context = actix::Context; -} +pub mod watch_paths_msg; #[derive(actix::Message, Debug, Clone)] #[rtype(result = "()")] -pub struct FsGroup { +pub struct PathMonitorEvent { pub watch_spec: WatchSpec, pub debounce: Debounce, pub group: Group, @@ -40,9 +18,9 @@ pub enum Group { BufferedChange(BufferedChangeEvent), } -impl FsGroup { +impl PathMonitorEvent { pub fn singular(evt: FsEvent, watch_spec: WatchSpec, debounce: Debounce) -> Self { - FsGroup { + PathMonitorEvent { debounce, group: Group::Singular(evt), watch_spec, diff --git a/crates/bsnext_path_monitor/src/path_and_filter.rs b/crates/bsnext_path_monitor/src/path_and_filter.rs new file mode 100644 index 00000000..1cc3d76d --- /dev/null +++ b/crates/bsnext_path_monitor/src/path_and_filter.rs @@ -0,0 +1,124 @@ +use crate::path_monitor; +use bsnext_fs::PathDescription; +use bsnext_fs::filter::PathFilter; +use bsnext_input::route::PathPattern; +use std::path::Path; + +pub(crate) struct PathAndFilter<'a> { + pub(crate) path: &'a Path, + pub(crate) filter_kind: Option, +} + +impl<'a> PathAndFilter<'a> { + pub fn new(p: &'a str) -> Self { + match p.split_once("*") { + // for cases like '**/*.toml' or '*.css' + Some(("", ..)) => PathAndFilter { + path: Path::new("."), + filter_kind: Some(PathPattern::Glob { + glob: p.to_string(), + }), + }, + Some((before, ..)) => PathAndFilter { + path: Path::new(before), + filter_kind: Some(PathPattern::Glob { + glob: p.to_string(), + }), + }, + None => PathAndFilter { + path: Path::new(p), + filter_kind: None, + }, + } + } +} + +impl PathFilter for PathAndFilter<'_> { + fn any(&self, pd: &PathDescription) -> bool { + match &self.filter_kind { + None => { + if self.path == pd.absolute { + return true; + } + pd.relative.map(|rel| rel == self.path).unwrap_or(false) + } + Some(filter) => { + let filters = path_monitor::pattern_to_filter_list(filter); + filters.iter().any(|x| x.any(pd)) + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_path_and_filter_with_glob() { + let input = "abc/*.css"; + let result = PathAndFilter::new(input); + + assert_eq!(result.path, Path::new("abc/")); + assert!(matches!(result.filter_kind, Some(PathPattern::Glob { .. }))); + + if let Some(PathPattern::Glob { glob }) = result.filter_kind { + assert_eq!(glob, "abc/*.css"); + } + } + + #[test] + fn test_path_and_filter_without_glob() { + let input = "abc/style.css"; + let result = PathAndFilter::new(input); + + assert_eq!(result.path, Path::new("abc/style.css")); + assert!(result.filter_kind.is_none()); + } + + #[test] + fn test_glob_without_path() { + let input = "**/*.toml"; + let result = PathAndFilter::new(input); + + assert_eq!(result.path, Path::new(".")); + // assert!(result.filter_kind.is_none()); + } + #[test] + fn test_e2e_filtering() { + use bsnext_fs::{Abs, Cwd}; + let abs = Abs("/user/shakyshane/abc/style.css"); + let cwd = Cwd("/user/shakyshane"); + let dirs = [ + (&abs, &cwd, "abc/*.css", true), + (&abs, &cwd, "**/*.css", true), + (&abs, &cwd, "def/*.css", false), + (&abs, &cwd, "abc/style.css", true), + (&abs, &cwd, "/user/shakyshane/abc/style.css", true), + (&abs, &cwd, "/user/shakyshane/abc/*.css", true), + (&abs, &cwd, "/user/shakyshane/abc/*.{css,txt}", true), + (&abs, &cwd, "/user/shakyshane/abc/*.{txt}", false), + (&abs, &cwd, "/user/shakyshane/**/*.css", true), + (&abs, &cwd, "/user/shakyshane/*.css", false), + (&abs, &cwd, "**/abc/*.css", true), + (&abs, &cwd, "**/def/*.css", false), + (&abs, &cwd, "abc/**/*.css", true), + (&abs, &cwd, "def/**/*.css", false), + (&abs, &cwd, "*.css", false), + (&abs, &cwd, "style.css", false), + (&abs, &cwd, "abc/s*.css", true), + (&abs, &cwd, "abc/style.*", true), + (&abs, &cwd, "*/style.css", true), + ]; + for (abs, cwd, dir, expected) in dirs { + let change = PathDescription::from_cwd(abs, cwd); + let v = PathAndFilter::new(dir); + let actual = v.any(&change); + assert_eq!( + actual, expected, + "dir was: {}, result should be {}", + dir, expected + ); + } + } +} diff --git a/crates/bsnext_path_monitor/src/path_monitor.rs b/crates/bsnext_path_monitor/src/path_monitor.rs index 8c17e3a8..4a73ab0b 100644 --- a/crates/bsnext_path_monitor/src/path_monitor.rs +++ b/crates/bsnext_path_monitor/src/path_monitor.rs @@ -1,15 +1,15 @@ -use crate::FsGroup; +use crate::PathMonitorEvent; +use crate::path_and_filter::PathAndFilter; +use crate::watch_paths_msg::WatchPaths; use actix::{Actor, ActorContext, Addr, AsyncContext, Context, Handler, Recipient, StreamHandler}; use actix_rt::Arbiter; use bsnext_fs::actor::FsWatcher; use bsnext_fs::buffered_debounce::BufferedStreamOpsExt; -use bsnext_fs::filter::{Filter, FilterScope, PathFilter}; +use bsnext_fs::filter::{Filter, FilterScope}; use bsnext_fs::stop_handler::StopWatcher; use bsnext_fs::stream::StreamOpsExt; use bsnext_fs::watch_path_handler::RequestWatchPath; -use bsnext_fs::{ - Debounce, FsEvent, FsEventContext, FsEventKind, PathDescription, PathDescriptionOwned, -}; +use bsnext_fs::{Debounce, FsEvent, FsEventContext, FsEventKind, PathDescriptionOwned}; use bsnext_input::route::{PathPattern, WatchSpec}; use std::collections::BTreeSet; use std::path::{Path, PathBuf}; @@ -24,14 +24,14 @@ pub struct PathMonitor { pub(crate) debounce: Debounce, pub(crate) watch_spec: WatchSpec, addrs: Vec>, - recipient: Recipient, + recipient: Recipient, inner_sender: tokio::sync::mpsc::Sender, inner_receiver: Option>, } impl PathMonitor { pub fn new( - recipient: Recipient, + recipient: Recipient, debounce: Debounce, cwd: PathBuf, fs_ctx: FsEventContext, @@ -55,12 +55,6 @@ impl actix::Actor for PathMonitor { type Context = actix::Context; } -#[derive(actix::Message, Debug, Clone)] -#[rtype(result = "()")] -pub struct WatchPaths { - pub paths: Vec, -} - impl actix::Handler for PathMonitor { type Result = (); @@ -117,7 +111,7 @@ impl actix::Handler for PathMonitor { impl StreamHandler for PathMonitor { fn handle(&mut self, event: FsEvent, _ctx: &mut Context) { debug!("StreamHandler for PathMonitor"); - self.recipient.do_send(FsGroup::singular( + self.recipient.do_send(PathMonitorEvent::singular( event, self.watch_spec.clone(), self.debounce, @@ -142,7 +136,7 @@ impl StreamHandler> for PathMonitor { _ => None, }) .collect::>(); - self.recipient.do_send(FsGroup::buffered_change( + self.recipient.do_send(PathMonitorEvent::buffered_change( outgoing, self.fs_ctx, self.watch_spec.clone(), @@ -151,89 +145,7 @@ impl StreamHandler> for PathMonitor { } } -struct PathAndFilter<'a> { - path: &'a Path, - filter_kind: Option, -} - -impl<'a> PathAndFilter<'a> { - pub fn new(p: &'a str) -> Self { - match p.split_once("*") { - // for cases like '**/*.toml' or '*.css' - Some(("", ..)) => PathAndFilter { - path: Path::new("."), - filter_kind: Some(PathPattern::Glob { - glob: p.to_string(), - }), - }, - Some((before, ..)) => PathAndFilter { - path: Path::new(before), - filter_kind: Some(PathPattern::Glob { - glob: p.to_string(), - }), - }, - None => PathAndFilter { - path: Path::new(p), - filter_kind: None, - }, - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_path_and_filter_with_glob() { - let input = "abc/*.css"; - let result = PathAndFilter::new(input); - - assert_eq!(result.path, Path::new("abc/")); - assert!(matches!(result.filter_kind, Some(PathPattern::Glob { .. }))); - - if let Some(PathPattern::Glob { glob }) = result.filter_kind { - assert_eq!(glob, "abc/*.css"); - } - } - - #[test] - fn test_path_and_filter_without_glob() { - let input = "abc/style.css"; - let result = PathAndFilter::new(input); - - assert_eq!(result.path, Path::new("abc/style.css")); - assert!(result.filter_kind.is_none()); - } - - #[test] - fn test_glob_without_path() { - let input = "**/*.toml"; - let result = PathAndFilter::new(input); - - assert_eq!(result.path, Path::new(".")); - // assert!(result.filter_kind.is_none()); - } -} - -impl PathFilter for PathAndFilter<'_> { - fn any(&self, pd: &PathDescription) -> bool { - match &self.filter_kind { - None => { - if self.path == pd.absolute { - return true; - } - pd.relative.map(|rel| rel == self.path).unwrap_or(false) - } - Some(filter) => { - let filters = convert(filter); - filters.iter().any(|x| x.any(pd)) - } - } - } -} - -fn convert(fk: &PathPattern) -> Vec { +pub(crate) fn pattern_to_filter_list(fk: &PathPattern) -> Vec { match fk { PathPattern::StringDefault(string_default) => { if string_default.contains("*") { @@ -294,51 +206,16 @@ fn convert(fk: &PathPattern) -> Vec { } } } - PathPattern::List(items) => items.iter().flat_map(convert).collect::>(), + PathPattern::List(items) => items + .iter() + .flat_map(pattern_to_filter_list) + .collect::>(), PathPattern::Any { any } => vec![Filter::Any { any: any.to_string(), }], } } -#[test] -fn test_e2e_filtering() { - use bsnext_fs::{Abs, Cwd}; - let abs = Abs("/user/shakyshane/abc/style.css"); - let cwd = Cwd("/user/shakyshane"); - let dirs = [ - (&abs, &cwd, "abc/*.css", true), - (&abs, &cwd, "**/*.css", true), - (&abs, &cwd, "def/*.css", false), - (&abs, &cwd, "abc/style.css", true), - (&abs, &cwd, "/user/shakyshane/abc/style.css", true), - (&abs, &cwd, "/user/shakyshane/abc/*.css", true), - (&abs, &cwd, "/user/shakyshane/abc/*.{css,txt}", true), - (&abs, &cwd, "/user/shakyshane/abc/*.{txt}", false), - (&abs, &cwd, "/user/shakyshane/**/*.css", true), - (&abs, &cwd, "/user/shakyshane/*.css", false), - (&abs, &cwd, "**/abc/*.css", true), - (&abs, &cwd, "**/def/*.css", false), - (&abs, &cwd, "abc/**/*.css", true), - (&abs, &cwd, "def/**/*.css", false), - (&abs, &cwd, "*.css", false), - (&abs, &cwd, "style.css", false), - (&abs, &cwd, "abc/s*.css", true), - (&abs, &cwd, "abc/style.*", true), - (&abs, &cwd, "*/style.css", true), - ]; - for (abs, cwd, dir, expected) in dirs { - let change = PathDescription::from_cwd(abs, cwd); - let v = PathAndFilter::new(dir); - let actual = v.any(&change); - assert_eq!( - actual, expected, - "dir was: {}, result should be {}", - dir, expected - ); - } -} - impl Handler for PathMonitor { type Result = (); fn handle(&mut self, msg: FsEvent, _ctx: &mut Self::Context) -> Self::Result { @@ -358,7 +235,8 @@ impl Handler for PathMonitor { _ => { // todo: any need to buffer these? debug!("Sending some other event"); - let output = FsGroup::singular(msg, self.watch_spec.clone(), self.debounce); + let output = + PathMonitorEvent::singular(msg, self.watch_spec.clone(), self.debounce); self.recipient.do_send(output) } } @@ -375,14 +253,14 @@ fn to_watcher( let mut watcher = FsWatcher::new(cwd, fs_ctx, receiver); if let Some(filter_kind) = &filter { - let filters = convert(filter_kind); + let filters = pattern_to_filter_list(filter_kind); for filter in filters { debug!(filter = %filter, "append filter"); watcher.with_filter(filter); } } if let Some(ignore_filter_kind) = &ignore { - let ignores = convert(ignore_filter_kind); + let ignores = pattern_to_filter_list(ignore_filter_kind); for ignore in ignores { debug!(ignore = %ignore, "with ignore"); watcher.with_ignore(ignore); diff --git a/crates/bsnext_path_monitor/src/watch_paths_msg.rs b/crates/bsnext_path_monitor/src/watch_paths_msg.rs new file mode 100644 index 00000000..2efe27f8 --- /dev/null +++ b/crates/bsnext_path_monitor/src/watch_paths_msg.rs @@ -0,0 +1,7 @@ +use std::path::PathBuf; + +#[derive(actix::Message, Debug, Clone)] +#[rtype(result = "()")] +pub struct WatchPaths { + pub paths: Vec, +} diff --git a/crates/bsnext_path_monitor/src/watchables/mod.rs b/crates/bsnext_path_monitor/src/watchables/mod.rs deleted file mode 100644 index 9abce723..00000000 --- a/crates/bsnext_path_monitor/src/watchables/mod.rs +++ /dev/null @@ -1,83 +0,0 @@ -use crate::FsGroup; -use crate::watchables::any_watchable::to_any_watchables; -use crate::watchables::path_watchable::PathWatchable; -use crate::watchables::route_watchable::to_route_watchables; -use crate::watchables::server_watchable::to_server_watchables; -use actix::Recipient; -use bsnext_input::{InferWatchers, Input, WatchGlobalConfig}; -use std::path::PathBuf; -use tracing::debug; - -pub mod any_watchable; -pub mod path_watchable; -pub mod route_watchable; -pub mod server_watchable; - -#[derive(actix::Message)] -#[rtype(result = "()")] -pub struct MonitorPathWatchables { - pub watchables: Vec, - pub cwd: PathBuf, - pub recipient: Recipient, -} - -#[tracing::instrument(skip_all, name = "accept_watchables")] -pub fn accept_watchables( - cwd: PathBuf, - input: &Input, - recipient: Recipient, -) -> MonitorPathWatchables { - let route_watchables = to_route_watchables(input); - let server_watchables = to_server_watchables(input); - let any_watchables = to_any_watchables(input); - - let routes = route_watchables - .iter() - .map(|r| PathWatchable::Route(r.to_owned())); - - let servers = server_watchables - .iter() - .map(|w| PathWatchable::Server(w.to_owned())); - - let any = any_watchables - .iter() - .map(|w| PathWatchable::Any(w.to_owned())); - - let watchables: Vec<_> = match &input.config.watchers { - WatchGlobalConfig::Enabled { infer } => match infer { - InferWatchers::None => { - debug!("processing {} any watchables", any.len()); - any.collect() - } - InferWatchers::Routes => { - debug!("processing {} route watchables", routes.len()); - debug!("processing {} any watchables", any.len()); - routes.chain(any).collect() - } - InferWatchers::Servers => { - debug!("processing {} server watchables", servers.len()); - debug!("processing {} any watchables", any.len()); - servers.chain(any).collect() - } - InferWatchers::RoutesAndServers => { - debug!("processing {} route watchables", routes.len()); - debug!("processing {} server watchables", servers.len()); - debug!("processing {} any watchables", any.len()); - routes.chain(servers).chain(any).collect() - } - }, - WatchGlobalConfig::Disabled => vec![], - }; - - debug!( - "{} watchables to add, cwd: {}", - watchables.len(), - cwd.display() - ); - - MonitorPathWatchables { - watchables, - cwd, - recipient, - } -} diff --git a/crates/bsnext_system/src/api.rs b/crates/bsnext_system/src/api.rs index 2cf0d258..cee7b802 100644 --- a/crates/bsnext_system/src/api.rs +++ b/crates/bsnext_system/src/api.rs @@ -6,7 +6,7 @@ use bsnext_dto::internal::ServerError; use bsnext_dto::ActiveServer; use bsnext_fs::{Debounce, FsEvent}; use bsnext_input::route::WatchSpec; -use bsnext_path_monitor::FsGroup; +use bsnext_path_monitor::PathMonitorEvent; use tokio::sync::oneshot; #[derive(Debug)] @@ -45,7 +45,7 @@ impl BsSystemApi { } pub fn fs_event(&self, evt: FsEvent) { - self.sys_address.do_send(FsGroup::singular( + self.sys_address.do_send(PathMonitorEvent::singular( evt, WatchSpec::default(), Debounce::default(), diff --git a/crates/bsnext_system/src/handle_fs_event_grouping.rs b/crates/bsnext_system/src/handle_fs_event_grouping.rs index a31af58f..fb7cddb8 100644 --- a/crates/bsnext_system/src/handle_fs_event_grouping.rs +++ b/crates/bsnext_system/src/handle_fs_event_grouping.rs @@ -17,14 +17,14 @@ use bsnext_fs::{ use bsnext_input::bs_live_built_in_task::BsLiveBuiltInTask; use bsnext_input::route::WatchSpec; use bsnext_input::{Input, InputError, PathDefinition, PathDefs, PathError}; -use bsnext_path_monitor::{FsGroup, Group}; +use bsnext_path_monitor::{Group, PathMonitorEvent}; use bsnext_task::task_trigger::FsChangesTrigger; use tracing::{debug, debug_span, info}; -impl actix::Handler for BsSystem { +impl actix::Handler for BsSystem { type Result = (); - fn handle(&mut self, msg: FsGroup, ctx: &mut Self::Context) -> Self::Result { + fn handle(&mut self, msg: PathMonitorEvent, ctx: &mut Self::Context) -> Self::Result { let addr = ctx.address(); let span = debug_span!("Handler->FsEventGrouping->BsSystem"); let _guard = span.enter(); diff --git a/crates/bsnext_system/src/lib.rs b/crates/bsnext_system/src/lib.rs index 9cb86d17..b67a8259 100644 --- a/crates/bsnext_system/src/lib.rs +++ b/crates/bsnext_system/src/lib.rs @@ -10,9 +10,11 @@ mod invoke_scope; pub mod monitor_any; pub mod monitor_input; pub mod override_input; +pub mod path_monitors; pub mod run; mod servers; pub mod start; mod system; pub mod tasks; pub mod watch; +pub mod watchables; diff --git a/crates/bsnext_system/src/monitor_any.rs b/crates/bsnext_system/src/monitor_any.rs index 065ac87a..6efc170a 100644 --- a/crates/bsnext_system/src/monitor_any.rs +++ b/crates/bsnext_system/src/monitor_any.rs @@ -1,8 +1,8 @@ use crate::system::BsSystem; +use crate::watchables::MonitorPathWatchables; use actix::{ActorFutureExt, AsyncContext}; use actix::{ResponseActFuture, WrapFuture}; use bsnext_input::Input; -use bsnext_path_monitor::watchables::accept_watchables; use tracing::debug_span; /// Message to monitor file system paths based on the current input configuration. @@ -35,11 +35,11 @@ impl actix::Handler for BsSystem { fn handle(&mut self, msg: MonitorAny, ctx: &mut Self::Context) -> Self::Result { let cwd = self.cwd.clone(); let recip = ctx.address().recipient(); - let monitor = self.monitor.clone(); + let monitor = self.path_monitors.clone(); let future = async move { let input = msg.input; - let msg = accept_watchables(cwd, &input, recip); + let msg = MonitorPathWatchables::new(cwd, &input, recip); monitor.send(msg).await?; Ok::<_, anyhow::Error>(()) }; diff --git a/crates/bsnext_system/src/monitor_input.rs b/crates/bsnext_system/src/monitor_input.rs index 32fb2105..84588a6b 100644 --- a/crates/bsnext_system/src/monitor_input.rs +++ b/crates/bsnext_system/src/monitor_input.rs @@ -1,11 +1,12 @@ use crate::system::BsSystem; +use crate::watchables::any_watchable::AnyWatchable; +use crate::watchables::path_watchable::PathWatchable; use actix::{Actor, Addr, AsyncContext}; use bsnext_fs::{Debounce, FsEventContext}; use bsnext_input::route::WatchSpec; use bsnext_input::InputCtx; -use bsnext_path_monitor::path_monitor::{PathMonitor, WatchPaths}; -use bsnext_path_monitor::watchables::any_watchable::AnyWatchable; -use bsnext_path_monitor::watchables::path_watchable::PathWatchable; +use bsnext_path_monitor::path_monitor::PathMonitor; +use bsnext_path_monitor::watch_paths_msg::WatchPaths; use std::path::PathBuf; use std::time::Duration; @@ -47,14 +48,14 @@ impl actix::Handler for BsSystem { tracing::debug!("starting input monitor"); - let input_watcher_addr = input_path_monitor.start(); + let path_monitor_addr = input_path_monitor.start(); let input_monitor = InputMonitor { input_ctx: msg.input_ctx.clone(), - addr: input_watcher_addr.clone(), + addr: path_monitor_addr.clone(), }; - input_watcher_addr.do_send(WatchPaths { paths }); + path_monitor_addr.do_send(WatchPaths { paths }); self.input_monitors = Some(input_monitor); } diff --git a/crates/bsnext_system/src/override_input.rs b/crates/bsnext_system/src/override_input.rs index 064e00a2..290a506a 100644 --- a/crates/bsnext_system/src/override_input.rs +++ b/crates/bsnext_system/src/override_input.rs @@ -1,11 +1,11 @@ use crate::servers::ResolveServers; use crate::system::BsSystem; +use crate::watchables::MonitorPathWatchables; use actix::{ActorFutureExt, AsyncContext, ResponseActFuture, WrapFuture}; use bsnext_dto::internal::{AnyEvent, ChildResult, ServerError}; use bsnext_dto::GetActiveServersResponse; use bsnext_input::startup::StartupContext; use bsnext_input::{Input, InputCtx}; -use bsnext_path_monitor::watchables::accept_watchables; use tracing::debug; #[derive(Debug, actix::Message)] @@ -36,7 +36,9 @@ impl actix::Handler for BsSystem { Err(err) => Err(ServerError::Unknown(err.to_string())), }; // todo: only process the override if valid? - accept_watchables(actor.cwd.clone(), &input_clone, addr.recipient()); + let msg = + MonitorPathWatchables::new(actor.cwd.clone(), &input_clone, addr.recipient()); + actor.path_monitors.do_send(msg); actor.update_ctx(&input_clone, &start_ctx_clone); output }); diff --git a/crates/bsnext_path_monitor/src/monitor_path_watchables.rs b/crates/bsnext_system/src/path_monitors/mod.rs similarity index 85% rename from crates/bsnext_path_monitor/src/monitor_path_watchables.rs rename to crates/bsnext_system/src/path_monitors/mod.rs index 6b7aca29..5af433fa 100644 --- a/crates/bsnext_path_monitor/src/monitor_path_watchables.rs +++ b/crates/bsnext_system/src/path_monitors/mod.rs @@ -1,13 +1,30 @@ -use crate::Monitor; -use crate::path_monitor::{PathMonitor, StopPathMonitor, WatchPaths}; -use crate::watchables::MonitorPathWatchables; use crate::watchables::path_watchable::PathWatchable; +use crate::watchables::MonitorPathWatchables; use actix::{Actor, Addr, AsyncContext, ResponseFuture}; use bsnext_fs::{Debounce, FsEventContext}; -use std::collections::BTreeSet; +use bsnext_path_monitor::path_monitor::{PathMonitor, StopPathMonitor}; +use bsnext_path_monitor::watch_paths_msg::WatchPaths; +use std::collections::{BTreeSet, HashMap}; use std::path::PathBuf; use tracing::{debug, debug_span}; +#[derive(Debug, Default)] +pub struct PathMonitors { + path_monitors: HashMap>, +} + +impl PathMonitors { + pub fn new() -> Self { + Self { + path_monitors: Default::default(), + } + } +} + +impl actix::Actor for PathMonitors { + type Context = actix::Context; +} + #[derive(actix::Message)] #[rtype(result = "()")] pub struct DropMonitor(pub PathWatchable); @@ -16,7 +33,7 @@ pub struct DropMonitor(pub PathWatchable); #[rtype(result = "()")] pub struct InsertMonitor(pub PathWatchable, pub Addr, pub Vec); -impl actix::Handler for Monitor { +impl actix::Handler for PathMonitors { type Result = ResponseFuture<()>; #[tracing::instrument(skip_all, name = "MonitorPathWatchables->Monitor")] @@ -87,7 +104,7 @@ impl actix::Handler for Monitor { } } -impl actix::Handler for Monitor { +impl actix::Handler for PathMonitors { type Result = (); fn handle(&mut self, msg: DropMonitor, _ctx: &mut Self::Context) -> Self::Result { @@ -97,7 +114,7 @@ impl actix::Handler for Monitor { } } -impl actix::Handler for Monitor { +impl actix::Handler for PathMonitors { type Result = (); fn handle( diff --git a/crates/bsnext_system/src/system.rs b/crates/bsnext_system/src/system.rs index af020ed4..a6ce171c 100644 --- a/crates/bsnext_system/src/system.rs +++ b/crates/bsnext_system/src/system.rs @@ -2,6 +2,7 @@ use crate::capabilities::Capabilities; use crate::fs_task_tracker::FsTaskTracker; use crate::invoke_scope::{InvokeScope, Invoker}; use crate::monitor_input::InputMonitor; +use crate::path_monitors::PathMonitors; use crate::run::resolve_spec::{InvokeRunTasks, ResolveSpec}; use crate::servers::ResolveServers; use crate::tasks::resolve::ResolveInitialTasks; @@ -14,7 +15,6 @@ use bsnext_dto::internal::{AnyEvent, ChildResult, TaskReportAndTree}; use bsnext_dto::GetActiveServersResponse; use bsnext_input::startup::{StartupContext, TopLevelRunMode}; use bsnext_input::Input; -use bsnext_path_monitor::Monitor; use bsnext_task::task_trigger::{ExecTrigger, TaskTrigger, TaskTriggerSource}; use std::path::PathBuf; use std::time::Duration; @@ -29,7 +29,7 @@ pub struct BsSystem { any_event_sender: Sender, pub(crate) input_monitors: Option, pub(crate) fs_task_tracker: Addr, - pub(crate) monitor: Addr, + pub(crate) path_monitors: Addr, pub(crate) invoker_addr: Addr, pub(crate) cwd: PathBuf, pub(crate) start_context: StartupContext, @@ -82,7 +82,7 @@ impl BsSystem { ); let invoker_addr = invoker.start(); let fs_task_tracker = FsTaskTracker::new(invoker_addr.clone().recipient()).start(); - let monitor = Monitor::new(); + let monitor = PathMonitors::new(); let monitor = monitor.start(); BsSystem { self_addr: None, @@ -90,7 +90,7 @@ impl BsSystem { servers_addr, any_event_sender, input_monitors: None, - monitor, + path_monitors: monitor, invoker_addr, fs_task_tracker, cwd, diff --git a/crates/bsnext_system/src/watch/watch_sub_opts.rs b/crates/bsnext_system/src/watch/watch_sub_opts.rs index 8a889ca2..755eecac 100644 --- a/crates/bsnext_system/src/watch/watch_sub_opts.rs +++ b/crates/bsnext_system/src/watch/watch_sub_opts.rs @@ -108,9 +108,9 @@ impl From for MultiWatch { PathPattern::List(value.only) }); - let debounce = value - .debounce - .map(|duration| DebounceDuration::Ms(duration as u64)); + let debounce = value.debounce.map(|duration| DebounceDuration::Ms { + ms: (duration as u64), + }); let spec = WatchSpec { before: queue, diff --git a/crates/bsnext_path_monitor/src/watchables/any_watchable.rs b/crates/bsnext_system/src/watchables/any_watchable.rs similarity index 100% rename from crates/bsnext_path_monitor/src/watchables/any_watchable.rs rename to crates/bsnext_system/src/watchables/any_watchable.rs index 16936294..2cdb8559 100644 --- a/crates/bsnext_path_monitor/src/watchables/any_watchable.rs +++ b/crates/bsnext_system/src/watchables/any_watchable.rs @@ -1,5 +1,5 @@ -use bsnext_input::Input; use bsnext_input::route::WatchSpec; +use bsnext_input::Input; use std::path::PathBuf; #[derive(Debug, PartialEq, PartialOrd, Ord, Eq, Hash, Clone)] diff --git a/crates/bsnext_system/src/watchables/mod.rs b/crates/bsnext_system/src/watchables/mod.rs new file mode 100644 index 00000000..5132919d --- /dev/null +++ b/crates/bsnext_system/src/watchables/mod.rs @@ -0,0 +1,80 @@ +use crate::watchables::any_watchable::to_any_watchables; +use crate::watchables::path_watchable::PathWatchable; +use crate::watchables::route_watchable::to_route_watchables; +use crate::watchables::server_watchable::to_server_watchables; +use actix::Recipient; +use bsnext_input::{InferWatchers, Input, WatchGlobalConfig}; +use bsnext_path_monitor::PathMonitorEvent; +use std::path::PathBuf; +use tracing::debug; + +pub mod any_watchable; +pub mod path_watchable; +pub mod route_watchable; +pub mod server_watchable; + +#[derive(actix::Message)] +#[rtype(result = "()")] +pub struct MonitorPathWatchables { + pub watchables: Vec, + pub cwd: PathBuf, + pub recipient: Recipient, +} + +impl MonitorPathWatchables { + pub fn new(cwd: PathBuf, input: &Input, recipient: Recipient) -> Self { + let route_watchables = to_route_watchables(input); + let server_watchables = to_server_watchables(input); + let any_watchables = to_any_watchables(input); + + let routes = route_watchables + .iter() + .map(|r| PathWatchable::Route(r.to_owned())); + + let servers = server_watchables + .iter() + .map(|w| PathWatchable::Server(w.to_owned())); + + let any = any_watchables + .iter() + .map(|w| PathWatchable::Any(w.to_owned())); + + let watchables: Vec<_> = match &input.config.watchers { + WatchGlobalConfig::Enabled { infer } => match infer { + InferWatchers::None => { + debug!("processing {} any watchables", any.len()); + any.collect() + } + InferWatchers::Routes => { + debug!("processing {} route watchables", routes.len()); + debug!("processing {} any watchables", any.len()); + routes.chain(any).collect() + } + InferWatchers::Servers => { + debug!("processing {} server watchables", servers.len()); + debug!("processing {} any watchables", any.len()); + servers.chain(any).collect() + } + InferWatchers::RoutesAndServers => { + debug!("processing {} route watchables", routes.len()); + debug!("processing {} server watchables", servers.len()); + debug!("processing {} any watchables", any.len()); + routes.chain(servers).chain(any).collect() + } + }, + WatchGlobalConfig::Disabled => vec![], + }; + + debug!( + "{} watchables to add, cwd: {}", + watchables.len(), + cwd.display() + ); + + MonitorPathWatchables { + watchables, + cwd, + recipient, + } + } +} diff --git a/crates/bsnext_path_monitor/src/watchables/path_watchable.rs b/crates/bsnext_system/src/watchables/path_watchable.rs similarity index 100% rename from crates/bsnext_path_monitor/src/watchables/path_watchable.rs rename to crates/bsnext_system/src/watchables/path_watchable.rs diff --git a/crates/bsnext_path_monitor/src/watchables/route_watchable.rs b/crates/bsnext_system/src/watchables/route_watchable.rs similarity index 100% rename from crates/bsnext_path_monitor/src/watchables/route_watchable.rs rename to crates/bsnext_system/src/watchables/route_watchable.rs index 64c3376d..9ea30464 100644 --- a/crates/bsnext_path_monitor/src/watchables/route_watchable.rs +++ b/crates/bsnext_system/src/watchables/route_watchable.rs @@ -1,7 +1,7 @@ -use bsnext_input::Input; use bsnext_input::route::{DirRoute, PathPattern, RawRoute, RouteKind, SseOpts, WatchSpec}; use bsnext_input::server_config::ServerIdentity; use bsnext_input::watch_opts::WatchOpts; +use bsnext_input::Input; use std::path::{Path, PathBuf}; #[derive(Debug, PartialEq, PartialOrd, Ord, Eq, Hash, Clone)] diff --git a/crates/bsnext_path_monitor/src/watchables/server_watchable.rs b/crates/bsnext_system/src/watchables/server_watchable.rs similarity index 100% rename from crates/bsnext_path_monitor/src/watchables/server_watchable.rs rename to crates/bsnext_system/src/watchables/server_watchable.rs index 1c2dc07f..652691ac 100644 --- a/crates/bsnext_path_monitor/src/watchables/server_watchable.rs +++ b/crates/bsnext_system/src/watchables/server_watchable.rs @@ -1,6 +1,6 @@ -use bsnext_input::Input; use bsnext_input::route::WatchSpec; use bsnext_input::server_config::ServerIdentity; +use bsnext_input::Input; use std::path::PathBuf; #[derive(Debug, PartialEq, PartialOrd, Ord, Eq, Hash, Clone)]