diff --git a/Cargo.lock b/Cargo.lock index 51247442..3773df6d 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" @@ -479,7 +450,6 @@ name = "bsnext" version = "0.26.0" dependencies = [ "actix-rt", - "anyhow", "bsnext_system", "tokio", ] @@ -500,7 +470,6 @@ dependencies = [ "actix-rt", "anyhow", "axum", - "axum-extra", "axum-server", "bsnext_client", "bsnext_dto", @@ -555,16 +524,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" @@ -575,7 +534,6 @@ dependencies = [ "bsnext_tracing", "futures", "globset", - "kill_tree", "notify", "pin-project-lite", "thiserror 1.0.60", @@ -624,6 +582,7 @@ name = "bsnext_input" version = "0.26.0" dependencies = [ "anyhow", + "bsnext_fs", "bsnext_fs_helpers", "bsnext_guards", "bsnext_resp", @@ -664,14 +623,25 @@ dependencies = [ ] [[package]] -name = "bsnext_monitor" +name = "bsnext_output" version = "0.26.0" +dependencies = [ + "anyhow", + "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", ] @@ -713,7 +683,6 @@ dependencies = [ "anyhow", "bsnext_core", "bsnext_dto", - "bsnext_example", "bsnext_fs", "bsnext_fs_helpers", "bsnext_html", @@ -721,6 +690,7 @@ dependencies = [ "bsnext_js", "bsnext_md", "bsnext_output", + "bsnext_path_monitor", "bsnext_task", "bsnext_tracing", "bsnext_yaml", @@ -730,9 +700,7 @@ dependencies = [ "globset", "insta", "kill_tree", - "rand 0.8.5", "reqwest", - "sqids", "tempfile", "tokio", "tokio-stream", @@ -1020,9 +988,9 @@ dependencies = [ [[package]] name = "ctor" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edb49164822f3ee45b17acd4a208cfc1251410cf0cad9a833234c9890774dd9f" +checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" dependencies = [ "quote", "syn", @@ -1455,30 +1423,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" @@ -2038,9 +1982,9 @@ dependencies = [ [[package]] name = "napi-build" -version = "2.2.0" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03acbfa4f156a32188bfa09b86dc11a431b5725253fc1fc6f6df5bed273382c4" +checksum = "c9c366d2c8c60b86fa632df75f745509b52f9128f91a6bad4c796e44abb505e1" [[package]] name = "napi-derive" @@ -2498,7 +2442,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 +2659,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 +2768,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", ] @@ -2959,9 +2903,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.22" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" [[package]] name = "serde" @@ -3526,7 +3470,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", @@ -3857,9 +3801,9 @@ dependencies = [ [[package]] name = "unicode-segmentation" -version = "1.11.0" +version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" [[package]] name = "unicode-width" 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_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_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_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_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/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/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_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_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_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/input_test/mod.rs b/crates/bsnext_input/src/input_test/mod.rs index 32153f46..1690b324 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,8 +213,8 @@ fn test_deserialize_watch() { let c: Route = serde_yaml::from_str(input).unwrap(); assert_eq!( c.opts.watch, - WatchOpts::Spec(Spec { - debounce: Some(DebounceDuration::Ms(2000)), + WatchOpts::Spec(WatchSpec { + debounce: Some(DebounceDuration::Ms { ms: 2000 }), only: None, ignore: None, run: None, @@ -242,12 +242,12 @@ 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 { - debounce: Some(DebounceDuration::Ms(2000)), + spec: Some(WatchSpec { + debounce: Some(DebounceDuration::Ms { ms: 2000 }), only: Some(PathPattern::Extension { ext: "**/*.css".to_string() }), 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_input/src/route.rs b/crates/bsnext_input/src/route.rs index a6fd12cd..01e40320 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 { @@ -294,9 +297,96 @@ 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::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( @@ -342,7 +432,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 +440,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 +645,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..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, - Spec, + Strategy, WatchSpec, }; use crate::watch_opts::WatchOpts; @@ -11,8 +11,52 @@ fn test_watch_opts_debounce() { ms: 200 only: "**/*.css" "#; - let expected = WatchOpts::Spec(Spec { - debounce: Some(DebounceDuration::Ms(200)), + let expected = WatchOpts::Spec(WatchSpec { + 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, @@ -27,7 +71,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 +88,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 +106,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 +127,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 +135,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 +148,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 +156,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 +170,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 +181,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 +197,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 +211,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 +226,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 +239,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/Cargo.toml b/crates/bsnext_monitor/Cargo.toml deleted file mode 100644 index 2a00da07..00000000 --- a/crates/bsnext_monitor/Cargo.toml +++ /dev/null @@ -1,6 +0,0 @@ -[package] -name = "bsnext_monitor" -version = "0.26.0" -edition = "2024" - -[dependencies] diff --git a/crates/bsnext_monitor/src/lib.rs b/crates/bsnext_monitor/src/lib.rs deleted file mode 100644 index b93cf3ff..00000000 --- a/crates/bsnext_monitor/src/lib.rs +++ /dev/null @@ -1,14 +0,0 @@ -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); - } -} diff --git a/crates/bsnext_path_monitor/Cargo.toml b/crates/bsnext_path_monitor/Cargo.toml new file mode 100644 index 00000000..a0a1a758 --- /dev/null +++ b/crates/bsnext_path_monitor/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "bsnext_path_monitor" +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 } +anyhow = { workspace = true } diff --git a/crates/bsnext_path_monitor/examples/path_monitor_example.rs b/crates/bsnext_path_monitor/examples/path_monitor_example.rs new file mode 100644 index 00000000..853abd14 --- /dev/null +++ b/crates/bsnext_path_monitor/examples/path_monitor_example.rs @@ -0,0 +1,102 @@ +use actix::{Actor, ResponseFuture}; +use actix_rt::System; +use bsnext_fs::{FsEvent, FsEventContext}; +use bsnext_input::route::{DebounceDuration, WatchSpec}; +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; + +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()); + 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) + } + } +} + +#[derive(Default)] +struct Consumer { + events: Vec, +} + +#[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: PathMonitorEvent, _ctx: &mut Self::Context) -> Self::Result { + self.events.push(msg); + } +} + +impl actix::Handler for Consumer { + type Result = ResponseFuture<()>; + + 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; + +impl actix::Handler for Consumer { + type Result = Vec; + + 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 receiver = actor.clone().recipient(); + let cwd = current_dir().unwrap(); + let fs_context = FsEventContext::default(); + let debounce_duration = DebounceDuration::Ms { ms: 0 }; + let debounce = bsnext_fs::Debounce::from(debounce_duration); + let spec = WatchSpec { + debounce: Some(debounce_duration), + ..WatchSpec::default() + }; + + 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; + let _sent = monitor_actor + .send(FsEvent::changed("/a", "a", fs_context.id())) + .await; + let r = Read; + tokio::time::sleep(Duration::from_millis(10)).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 new file mode 100644 index 00000000..ee00ab08 --- /dev/null +++ b/crates/bsnext_path_monitor/src/lib.rs @@ -0,0 +1,44 @@ +use bsnext_fs::{BufferedChangeEvent, Debounce, FsEvent, FsEventContext, PathDescriptionOwned}; +use bsnext_input::route::WatchSpec; +pub mod path_and_filter; +pub mod path_monitor; +pub mod watch_paths_msg; + +#[derive(actix::Message, Debug, Clone)] +#[rtype(result = "()")] +pub struct PathMonitorEvent { + pub watch_spec: WatchSpec, + pub debounce: Debounce, + pub group: Group, +} + +#[derive(Debug, Clone)] +pub enum Group { + Singular(FsEvent), + BufferedChange(BufferedChangeEvent), +} + +impl PathMonitorEvent { + pub fn singular(evt: FsEvent, watch_spec: WatchSpec, debounce: Debounce) -> Self { + PathMonitorEvent { + 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_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_system/src/watchables/path_monitor.rs b/crates/bsnext_path_monitor/src/path_monitor.rs similarity index 59% rename from crates/bsnext_system/src/watchables/path_monitor.rs rename to crates/bsnext_path_monitor/src/path_monitor.rs index f54d7c9b..4a73ab0b 100644 --- a/crates/bsnext_system/src/watchables/path_monitor.rs +++ b/crates/bsnext_path_monitor/src/path_monitor.rs @@ -1,17 +1,16 @@ -use crate::watchables::path_watchable::PathWatchable; -use actix::{ActorContext, Addr, AsyncContext, Context, Handler, Recipient, StreamHandler}; +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, FsEventGrouping, FsEventKind, PathDescription, - PathDescriptionOwned, -}; -use bsnext_input::route::PathPattern; +use bsnext_fs::{Debounce, FsEvent, FsEventContext, FsEventKind, PathDescriptionOwned}; +use bsnext_input::route::{PathPattern, WatchSpec}; use std::collections::BTreeSet; use std::path::{Path, PathBuf}; use tokio::sync::mpsc; @@ -21,99 +20,56 @@ 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) path_watchable: PathWatchable, pub(crate) debounce: Debounce, + pub(crate) watch_spec: WatchSpec, + addrs: Vec>, + recipient: Recipient, inner_sender: tokio::sync::mpsc::Sender, inner_receiver: Option>, } impl PathMonitor { pub fn new( - sys: Recipient, + recipient: Recipient, debounce: Debounce, cwd: PathBuf, fs_ctx: FsEventContext, - path_watchable: PathWatchable, + watch_spec: WatchSpec, ) -> Self { let (inner_sender, inner_receiver) = mpsc::channel::(1); Self { - recipient: sys, + recipient, debounce, cwd, addrs: vec![], fs_ctx, - path_watchable, + watch_spec, 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; +} + +impl actix::Handler for PathMonitor { + type Result = (); - fn started(&mut self, ctx: &mut Self::Context) { - for single_path in &self.path_watchable.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(); + 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::>(); // additional filter from options? - let spec_opts = self.path_watchable.spec_opts(); - if let Some(filter) = &spec_opts.only { + if let Some(filter) = &self.watch_spec.only { filters.push(filter.clone()); } @@ -121,25 +77,25 @@ impl actix::Actor for PathMonitor { let watcher = to_watcher( &self.cwd, Some(&PathPattern::List(filters)), - spec_opts.ignore.as_ref(), + self.watch_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 { - 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 { 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,47 +108,44 @@ impl actix::Actor for PathMonitor { } } -struct PathAndFilter<'a> { - path: &'a Path, - filter_kind: Option, -} - -impl<'a> PathAndFilter<'a> { - pub fn new(p: &'a str) -> Self { - if let Some((before, ..)) = p.split_once("*") { - PathAndFilter { - path: Path::new(before), - filter_kind: Some(PathPattern::Glob { - glob: p.to_string(), - }), - } - } else { - PathAndFilter { - path: Path::new(p), - filter_kind: None, - } - } +impl StreamHandler for PathMonitor { + fn handle(&mut self, event: FsEvent, _ctx: &mut Context) { + debug!("StreamHandler for PathMonitor"); + self.recipient.do_send(PathMonitorEvent::singular( + event, + self.watch_spec.clone(), + self.debounce, + )) } } -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)) - } - } +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(); + // 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(PathMonitorEvent::buffered_change( + outgoing, + self.fs_ctx, + self.watch_spec.clone(), + self.debounce, + )) } } -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("*") { @@ -206,12 +159,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); @@ -239,12 +192,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); @@ -253,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 { @@ -316,7 +234,10 @@ impl Handler for PathMonitor { } _ => { // todo: any need to buffer these? - self.recipient.do_send(FsEventGrouping::Singular(msg)) + debug!("Sending some other event"); + let output = + PathMonitorEvent::singular(msg, self.watch_spec.clone(), self.debounce); + self.recipient.do_send(output) } } } @@ -332,16 +253,16 @@ 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"); + 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"); + 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_system/Cargo.toml b/crates/bsnext_system/Cargo.toml index f0a0643c..e152eaa8 100644 --- a/crates/bsnext_system/Cargo.toml +++ b/crates/bsnext_system/Cargo.toml @@ -16,12 +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" } -rand = "0.8.5" +bsnext_path_monitor = { path = "../bsnext_path_monitor" } kill_tree = { workspace = true } -sqids = { workspace = true } anyhow = { workspace = true } clap = { workspace = true } 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/api.rs b/crates/bsnext_system/src/api.rs index e9c375c0..cee7b802 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_path_monitor::PathMonitorEvent; 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(PathMonitorEvent::singular( + evt, + WatchSpec::default(), + Debounce::default(), + )) } pub async fn active_servers(&self) -> Result, ServerError> { 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/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/watchables/handle_fs_event_grouping.rs b/crates/bsnext_system/src/handle_fs_event_grouping.rs similarity index 69% rename from crates/bsnext_system/src/watchables/handle_fs_event_grouping.rs rename to crates/bsnext_system/src/handle_fs_event_grouping.rs index 9989f8ca..fb7cddb8 100644 --- a/crates/bsnext_system/src/watchables/handle_fs_event_grouping.rs +++ b/crates/bsnext_system/src/handle_fs_event_grouping.rs @@ -5,36 +5,43 @@ 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; 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_path_monitor::{Group, PathMonitorEvent}; use bsnext_task::task_trigger::FsChangesTrigger; -use tracing::{debug_span, info}; +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: PathMonitorEvent, ctx: &mut Self::Context) -> Self::Result { 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 } @@ -47,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"); @@ -70,39 +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) - } - } - 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, 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() { @@ -128,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)) } @@ -160,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); @@ -178,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), ))) } @@ -206,24 +197,17 @@ 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(|| { + 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: {:?}", watch_spec); + spec.to_owned() + } else { + 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/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 7e0e4054..b67a8259 100644 --- a/crates/bsnext_system/src/lib.rs +++ b/crates/bsnext_system/src/lib.rs @@ -2,12 +2,15 @@ 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; pub mod input_fs; 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; diff --git a/crates/bsnext_system/src/monitor_any.rs b/crates/bsnext_system/src/monitor_any.rs new file mode 100644 index 00000000..6efc170a --- /dev/null +++ b/crates/bsnext_system/src/monitor_any.rs @@ -0,0 +1,66 @@ +use crate::system::BsSystem; +use crate::watchables::MonitorPathWatchables; +use actix::{ActorFutureExt, AsyncContext}; +use actix::{ResponseActFuture, WrapFuture}; +use bsnext_input::Input; +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.path_monitors.clone(); + + let future = async move { + let input = msg.input; + let msg = MonitorPathWatchables::new(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/watchables/input_monitor.rs b/crates/bsnext_system/src/monitor_input.rs similarity index 70% rename from crates/bsnext_system/src/watchables/input_monitor.rs rename to crates/bsnext_system/src/monitor_input.rs index 91552a14..84588a6b 100644 --- a/crates/bsnext_system/src/watchables/input_monitor.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_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::route::WatchSpec; use bsnext_input::InputCtx; +use bsnext_path_monitor::path_monitor::PathMonitor; +use bsnext_path_monitor::watch_paths_msg::WatchPaths; use std::path::PathBuf; use std::time::Duration; @@ -13,7 +14,6 @@ use std::time::Duration; pub struct InputMonitor { #[allow(dead_code)] pub addr: Addr, - pub monitor_meta: PathMonitorMeta, pub input_ctx: InputCtx, } @@ -37,25 +37,26 @@ 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()], - spec: Spec::default(), - task_spec: None, + dirs: paths.clone(), + watch_spec: WatchSpec::default(), }); - let input_path_monitor = PathMonitor::new(sys, debounce, self.cwd.clone(), ctx, pw); - 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"); - 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, - monitor_meta: meta, + addr: path_monitor_addr.clone(), }; + 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 55e9b75b..290a506a 100644 --- a/crates/bsnext_system/src/override_input.rs +++ b/crates/bsnext_system/src/override_input.rs @@ -1,5 +1,6 @@ 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; @@ -35,7 +36,9 @@ 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); + 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_system/src/watchables/monitor_path_watchables.rs b/crates/bsnext_system/src/path_monitors/mod.rs similarity index 50% rename from crates/bsnext_system/src/watchables/monitor_path_watchables.rs rename to crates/bsnext_system/src/path_monitors/mod.rs index b3319e6b..5af433fa 100644 --- a/crates/bsnext_system/src/watchables/monitor_path_watchables.rs +++ b/crates/bsnext_system/src/path_monitors/mod.rs @@ -1,29 +1,46 @@ -use crate::system::BsSystem; -use crate::watchables::path_monitor::{PathMonitor, PathMonitorMeta, StopPathMonitor}; use crate::watchables::path_watchable::PathWatchable; -use actix::{Actor, Addr, AsyncContext}; +use crate::watchables::MonitorPathWatchables; +use actix::{Actor, Addr, AsyncContext, ResponseFuture}; use bsnext_fs::{Debounce, FsEventContext}; -use bsnext_input::route::{DebounceDuration, Spec}; -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 std::time::Duration; 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 MonitorPathWatchables { - pub watchables: Vec, - pub cwd: PathBuf, -} +pub struct DropMonitor(pub PathWatchable); -impl actix::Handler for BsSystem { - type Result = (); +#[derive(actix::Message)] +#[rtype(result = "()")] +pub struct InsertMonitor(pub PathWatchable, pub Addr, pub Vec); - #[tracing::instrument(skip_all, name = "MonitorPathWatchables->BsSystem")] +impl actix::Handler for PathMonitors { + type Result = ResponseFuture<()>; + + #[tracing::instrument(skip_all, name = "MonitorPathWatchables->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::>(); @@ -35,7 +52,7 @@ impl actix::Handler for BsSystem { 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())) } @@ -45,6 +62,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,65 +77,56 @@ 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 duration = match opts { - Spec { - debounce: Some(DebounceDuration::Ms(ms)), - .. - } => Duration::from_millis(*ms), - _ => Duration::from_millis(300), - }; - tracing::trace!(?duration); + let spec = any_watchable.spec(); + tracing::trace!(?spec); - 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(), + let path_monitor = PathMonitor::new( + msg.recipient.clone(), debounce, msg.cwd.clone(), fs_ctx, - (*any_watchable).clone(), + spec.clone(), ); - let meta = PathMonitorMeta::from(&monitor); - tracing::trace!(?meta); + let path_monitor_addr = path_monitor.start(); - let monitor = monitor.start(); - - ctx.notify(InsertMonitor((*any_watchable).clone(), monitor, meta)) + ctx.notify(InsertMonitor( + (*any_watchable).clone(), + path_monitor_addr, + paths, + )) } + + Box::pin(async move {}) } } -#[derive(actix::Message)] -#[rtype(result = "()")] -struct DropMonitor(PathWatchable); - -impl actix::Handler for BsSystem { +impl actix::Handler for PathMonitors { type Result = (); 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()); } } -#[derive(actix::Message)] -#[rtype(result = "()")] -struct InsertMonitor(PathWatchable, Addr, PathMonitorMeta); - -impl actix::Handler for BsSystem { +impl actix::Handler for PathMonitors { type Result = (); - fn handle(&mut self, msg: InsertMonitor, _ctx: &mut Self::Context) -> Self::Result { + fn handle( + &mut self, + InsertMonitor(watchable, addr, paths): 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!("+ Monitor count {}", self.any_monitors.len()); + debug!("{}", watchable); + self.path_monitors.insert(watchable, addr.clone()); + debug!("+ Monitor count {}", self.path_monitors.len()); + addr.do_send(WatchPaths { paths }); } } diff --git a/crates/bsnext_system/src/run/mod.rs b/crates/bsnext_system/src/run/mod.rs index f89d9fc9..4c84608c 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)] @@ -87,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_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/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, }; 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 }) - } -} 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/src/start/start_system.rs b/crates/bsnext_system/src/start/start_system.rs index a07ea7a4..2519a0b4 100644 --- a/crates/bsnext_system/src/start/start_system.rs +++ b/crates/bsnext_system/src/start/start_system.rs @@ -1,11 +1,12 @@ #![allow(clippy::result_large_err)] use crate::api::BsSystemApi; +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, SetupServersOk, }; -use crate::watchables::input_monitor::MonitorInput; use actix::{ Actor, ActorContext, ActorFutureExt, AsyncContext, Handler, ResponseActFuture, WrapFuture, }; @@ -78,8 +79,7 @@ impl Handler for BsSystem { cwd: actor.cwd.clone(), input_ctx, }); - // todo: where to better sequence these side-effects? - actor.accept_watchables(&input, addr); + ctx.notify(MonitorAny::new(input.clone())); Ok(DidStart::Started(servers)) }, )) @@ -91,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); @@ -99,7 +99,7 @@ impl Handler for BsSystem { debug!("errored: {:?}", errored); return Err(StartupError::ServerError((*server_error).to_owned())); } - actor.accept_watchables(&res.input, addr); + ctx.notify(MonitorAny::new(res.input.clone())); Ok(DidStart::Started(res.servers)) }, )) @@ -129,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); @@ -137,7 +137,7 @@ impl Handler for BsSystem { debug!("errored: {:?}", errored); return Err(StartupError::ServerError((*server_error).to_owned())); } - actor.accept_watchables(&res.input, addr); + 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 2fa79376..a6ce171c 100644 --- a/crates/bsnext_system/src/system.rs +++ b/crates/bsnext_system/src/system.rs @@ -1,13 +1,12 @@ 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; 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; use bsnext_core::servers_supervisor::actor::ServersSupervisor; @@ -17,7 +16,6 @@ use bsnext_dto::GetActiveServersResponse; use bsnext_input::startup::{StartupContext, TopLevelRunMode}; use bsnext_input::Input; 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; @@ -30,8 +28,8 @@ pub struct BsSystem { servers_addr: Addr, any_event_sender: Sender, pub(crate) input_monitors: Option, - pub(crate) any_monitors: HashMap, PathMonitorMeta)>, pub(crate) fs_task_tracker: Addr, + pub(crate) path_monitors: Addr, pub(crate) invoker_addr: Addr, pub(crate) cwd: PathBuf, pub(crate) start_context: StartupContext, @@ -84,13 +82,15 @@ impl BsSystem { ); let invoker_addr = invoker.start(); let fs_task_tracker = FsTaskTracker::new(invoker_addr.clone().recipient()).start(); + let monitor = PathMonitors::new(); + let monitor = monitor.start(); BsSystem { self_addr: None, capabilities_addr, servers_addr, any_event_sender, input_monitors: None, - any_monitors: Default::default(), + path_monitors: monitor, invoker_addr, fs_task_tracker, cwd, 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..755eecac 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)] @@ -108,11 +108,11 @@ 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 = Spec { + let spec = WatchSpec { before: queue, run: run_opts, debounce, diff --git a/crates/bsnext_system/src/watchables/any_watchable.rs b/crates/bsnext_system/src/watchables/any_watchable.rs index 1475cd6f..2cdb8559 100644 --- a/crates/bsnext_system/src/watchables/any_watchable.rs +++ b/crates/bsnext_system/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::route::WatchSpec; use bsnext_input::Input; 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 watch_spec: WatchSpec, } 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, + watch_spec: watcher.spec.clone().unwrap_or_default(), } }) .collect() diff --git a/crates/bsnext_system/src/watchables/mod.rs b/crates/bsnext_system/src/watchables/mod.rs index b111a590..5132919d 100644 --- a/crates/bsnext_system/src/watchables/mod.rs +++ b/crates/bsnext_system/src/watchables/mod.rs @@ -1,25 +1,28 @@ -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 actix::Recipient; use bsnext_input::{InferWatchers, Input, WatchGlobalConfig}; -use monitor_path_watchables::MonitorPathWatchables; +use bsnext_path_monitor::PathMonitorEvent; +use std::path::PathBuf; use tracing::debug; 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; -impl BsSystem { - #[tracing::instrument(skip_all, name = "BsSystem.accept_watchables")] - pub(crate) fn accept_watchables(&mut self, input: &Input, addr: Addr) { +#[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); @@ -62,13 +65,16 @@ impl BsSystem { 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); + + MonitorPathWatchables { + watchables, + cwd, + recipient, + } } } diff --git a/crates/bsnext_system/src/watchables/path_watchable.rs b/crates/bsnext_system/src/watchables/path_watchable.rs index d2751d95..6ccbdccd 100644 --- a/crates/bsnext_system/src/watchables/path_watchable.rs +++ b/crates/bsnext_system/src/watchables/path_watchable.rs @@ -1,11 +1,10 @@ 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; -use bsnext_input::route::Spec; +use bsnext_input::route::WatchSpec; use std::path::Path; #[derive(Debug, PartialEq, PartialOrd, Ord, Eq, Hash, Clone)] @@ -45,11 +44,11 @@ impl Display for PathWatchable { } impl PathWatchable { - pub fn spec_opts(&self) -> &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> { @@ -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_system/src/watchables/route_watchable.rs index 857e57ff..9ea30464 100644 --- a/crates/bsnext_system/src/watchables/route_watchable.rs +++ b/crates/bsnext_system/src/watchables/route_watchable.rs @@ -1,6 +1,4 @@ -use crate::tasks::task_spec::TaskSpec; -use crate::watchables::server_watchable::to_task_spec; -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 bsnext_input::Input; @@ -11,8 +9,7 @@ pub struct RouteWatchable { pub server_identity: ServerIdentity, pub route_path: String, pub dir: PathBuf, - pub spec: Spec, - pub task_spec: Option, + pub watch_spec: WatchSpec, } pub fn to_route_watchables(input: &Input) -> Vec { @@ -23,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, @@ -35,29 +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 run = to_task_spec(&spec); - 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, - task_spec: run, + watch_spec, }; tracing::trace!(?route_watchable); @@ -78,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_system/src/watchables/server_watchable.rs b/crates/bsnext_system/src/watchables/server_watchable.rs index 8ad4db9a..652691ac 100644 --- a/crates/bsnext_system/src/watchables/server_watchable.rs +++ b/crates/bsnext_system/src/watchables/server_watchable.rs @@ -1,5 +1,4 @@ -use crate::tasks::task_spec::TaskSpec; -use bsnext_input::route::Spec; +use bsnext_input::route::WatchSpec; use bsnext_input::server_config::ServerIdentity; use bsnext_input::Input; use std::path::PathBuf; @@ -8,8 +7,7 @@ use std::path::PathBuf; pub struct ServerWatchable { pub server_identity: ServerIdentity, pub dirs: Vec, - pub spec: Spec, - pub task_spec: Option, + pub watch_spec: WatchSpec, } 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, + watch_spec: watcher.spec.clone().unwrap_or_default(), } }) }) .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/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))?;