From a163a3ab2db79e3e288f9bd88d94ef3daee31b65 Mon Sep 17 00:00:00 2001 From: Tarek Date: Mon, 9 Sep 2024 11:45:14 +0300 Subject: [PATCH 01/15] feat: ResourceIndex watch API Signed-off-by: Tarek --- fs-index/Cargo.toml | 4 ++ fs-index/src/lib.rs | 5 +- fs-index/src/watch.rs | 107 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 114 insertions(+), 2 deletions(-) create mode 100644 fs-index/src/watch.rs diff --git a/fs-index/Cargo.toml b/fs-index/Cargo.toml index 0ded09dd..cbaf157a 100644 --- a/fs-index/Cargo.toml +++ b/fs-index/Cargo.toml @@ -10,10 +10,14 @@ bench = false [dependencies] log = { version = "0.4.17", features = ["release_max_level_off"] } +env_logger = "0.11" walkdir = "2.3.2" anyhow = "1.0.58" serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } +# For the watch API +notify = "6.1" +futures = "0.3" fs-storage = { path = "../fs-storage" } diff --git a/fs-index/src/lib.rs b/fs-index/src/lib.rs index 59030cef..5f15a4a2 100644 --- a/fs-index/src/lib.rs +++ b/fs-index/src/lib.rs @@ -1,10 +1,11 @@ mod index; mod serde; mod utils; - -pub use utils::load_or_build_index; +mod watch; pub use index::ResourceIndex; +pub use utils::load_or_build_index; +pub use watch::watch_index; #[cfg(test)] mod tests; diff --git a/fs-index/src/watch.rs b/fs-index/src/watch.rs new file mode 100644 index 00000000..4d4de096 --- /dev/null +++ b/fs-index/src/watch.rs @@ -0,0 +1,107 @@ +use std::{fs, path::Path}; + +use anyhow::Result; +use futures::{ + channel::mpsc::{channel, Receiver}, + SinkExt, StreamExt, +}; +use log::info; +use notify::{Config, Event, RecommendedWatcher, RecursiveMode, Watcher}; + +use data_resource::ResourceId; +use fs_storage::ARK_FOLDER; + +use crate::ResourceIndex; + +/// Watches a given directory for file system changes and automatically updates +/// the resource index. +/// +/// This function continuously monitors the specified directory and responds to +/// file system events such as file creation, modification, and deletion. When +/// an event is detected, the function updates the associated resource index and +/// stores the changes. +/// +/// The function runs asynchronously, whcih makes it suitable for non-blocking +/// contexts. It uses a recursive watcher to track all changes within the +/// directory tree. Events related to the internal `.ark` folder are ignored to +/// prevent unnecessary updates. +/// +/// # Arguments +/// +/// * `root_path` - The root directory to be watched. This path is canonicalized +/// to handle symbolic links and relative paths correctly. +pub async fn watch_index, Id: ResourceId>( + root_path: P, +) -> Result<()> { + log::debug!( + "Attempting to watch index at root path: {:?}", + root_path.as_ref() + ); + + let root_path = fs::canonicalize(root_path.as_ref())?; + let mut index: ResourceIndex = ResourceIndex::build(&root_path)?; + index.store()?; + + let (mut watcher, mut rx) = async_watcher()?; + info!("Watching directory: {:?}", root_path); + let config = Config::default(); + watcher.configure(config)?; + watcher.watch(root_path.as_ref(), RecursiveMode::Recursive)?; + info!("Started watcher with config: \n\t{:?}", config); + + let ark_folder = root_path.join(ARK_FOLDER); + while let Some(res) = rx.next().await { + match res { + Ok(event) => { + // If the event is a change in .ark folder, ignore it + if event + .paths + .iter() + .any(|p| p.starts_with(&ark_folder)) + { + continue; + } + // If the event is not a create, modify or remove, ignore it + if !(event.kind.is_create() + || event.kind.is_modify() + || event.kind.is_remove()) + { + continue; + } + + info!("Detected event: {:?}", event); + let file = event + .paths + .first() + .expect("Failed to get file path from event"); + log::debug!("Updating index for file: {:?}", file); + let relative_path = file.strip_prefix(&root_path)?; + index.update_one(&relative_path)?; + + index.store()?; + info!("Index updated and stored"); + } + Err(e) => log::error!("Error in watcher: {:?}", e), + } + } + + unreachable!("Watcher stream ended unexpectedly"); +} + +fn async_watcher( +) -> notify::Result<(RecommendedWatcher, Receiver>)> { + let (mut tx, rx) = channel(1); + + let watcher = RecommendedWatcher::new( + move |res| { + futures::executor::block_on(async { + if let Err(err) = tx.send(res).await { + log::error!("Error sending event: {:?}", err); + } + }) + }, + Config::default(), + )?; + + Ok((watcher, rx)) +} From 1d35318f13a0f0aec7b6d52d1152606795d352ab Mon Sep 17 00:00:00 2001 From: Tarek Date: Mon, 9 Sep 2024 11:53:00 +0300 Subject: [PATCH 02/15] docs(fs-index): add example for index watch Signed-off-by: Tarek --- fs-index/Cargo.toml | 2 ++ fs-index/examples/index_watch.rs | 35 ++++++++++++++++++++++++++++++++ fs-index/src/watch.rs | 2 +- 3 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 fs-index/examples/index_watch.rs diff --git a/fs-index/Cargo.toml b/fs-index/Cargo.toml index cbaf157a..ce74b817 100644 --- a/fs-index/Cargo.toml +++ b/fs-index/Cargo.toml @@ -32,6 +32,8 @@ criterion = { version = "0.5", features = ["html_reports"] } tempfile = "3.10" # Depending on `dev-hash` for testing dev-hash = { path = "../dev-hash" } +# Examples +tokio = { version = "1.40", features = ["full"] } [[bench]] name = "resource_index_benchmark" diff --git a/fs-index/examples/index_watch.rs b/fs-index/examples/index_watch.rs new file mode 100644 index 00000000..0e68a6fa --- /dev/null +++ b/fs-index/examples/index_watch.rs @@ -0,0 +1,35 @@ +use std::{path::Path, thread}; + +use anyhow::Result; +use log::LevelFilter; + +use dev_hash::Blake3; +use fs_index::watch_index; + +/// Example demonstrating how to use fs_index to watch a directory for changes +/// in a separate thread. This automatically updates the index when changes are +/// detected. +fn main() -> Result<()> { + env_logger::builder() + .filter_level(LevelFilter::Debug) + .init(); + + // Change this to the path of the directory you want to watch + let root = Path::new("test-assets"); + + let thread_handle = thread::spawn(move || { + tokio::runtime::Runtime::new() + .unwrap() + .block_on(async move { + if let Err(err) = watch_index::<_, Blake3>(root).await { + eprintln!("Error in watching index: {:?}", err); + } + }); + }); + + thread_handle + .join() + .expect("Failed to join thread"); + + Ok(()) +} diff --git a/fs-index/src/watch.rs b/fs-index/src/watch.rs index 4d4de096..5d8189ed 100644 --- a/fs-index/src/watch.rs +++ b/fs-index/src/watch.rs @@ -76,7 +76,7 @@ pub async fn watch_index, Id: ResourceId>( .expect("Failed to get file path from event"); log::debug!("Updating index for file: {:?}", file); let relative_path = file.strip_prefix(&root_path)?; - index.update_one(&relative_path)?; + index.update_one(relative_path)?; index.store()?; info!("Index updated and stored"); From 6aa9e497618a77061c148ebd4738edf9047356d7 Mon Sep 17 00:00:00 2001 From: Tarek Date: Mon, 9 Sep 2024 12:20:35 +0300 Subject: [PATCH 03/15] feat(ark-cli): add watch command to use fs-index watch api Signed-off-by: Tarek --- ark-cli/src/commands/mod.rs | 2 ++ ark-cli/src/commands/watch.rs | 27 +++++++++++++++++++++++++++ ark-cli/src/main.rs | 1 + 3 files changed, 30 insertions(+) create mode 100644 ark-cli/src/commands/watch.rs diff --git a/ark-cli/src/commands/mod.rs b/ark-cli/src/commands/mod.rs index eab0cc1a..631c3ec6 100644 --- a/ark-cli/src/commands/mod.rs +++ b/ark-cli/src/commands/mod.rs @@ -8,6 +8,7 @@ mod list; mod monitor; mod render; pub mod storage; +mod watch; pub use file::{file_append, file_insert, format_file, format_line}; @@ -18,6 +19,7 @@ pub enum Commands { Monitor(monitor::Monitor), Render(render::Render), List(list::List), + Watch(watch::Watch), #[command(about = "Manage links")] Link { #[clap(subcommand)] diff --git a/ark-cli/src/commands/watch.rs b/ark-cli/src/commands/watch.rs new file mode 100644 index 00000000..c77ac534 --- /dev/null +++ b/ark-cli/src/commands/watch.rs @@ -0,0 +1,27 @@ +use std::path::PathBuf; + +use fs_index::watch_index; + +use crate::{AppError, ResourceId}; + +#[derive(Clone, Debug, clap::Args)] +#[clap( + name = "watch", + about = "Watch the ark managed folder for changes and update the index accordingly" +)] +pub struct Watch { + #[clap( + help = "Path to the directory to watch for changes", + default_value = ".", + value_parser + )] + path: PathBuf, +} + +impl Watch { + pub async fn run(&self) -> Result<(), AppError> { + watch_index::<_, ResourceId>(&self.path) + .await + .map_err(|err| AppError::IndexError(err.to_string())) + } +} diff --git a/ark-cli/src/main.rs b/ark-cli/src/main.rs index e8029c18..7158df00 100644 --- a/ark-cli/src/main.rs +++ b/ark-cli/src/main.rs @@ -71,6 +71,7 @@ async fn run() -> Result<()> { Monitor(monitor) => monitor.run()?, Render(render) => render.run()?, List(list) => list.run()?, + Watch(watch) => watch.run().await?, Link { subcommand } => match subcommand { Create(create) => create.run().await?, Load(load) => load.run()?, From 058df7fe077db45093f2c3a3bd63ea2b39aeaceb Mon Sep 17 00:00:00 2001 From: Tarek Date: Mon, 9 Sep 2024 14:02:53 +0300 Subject: [PATCH 04/15] docs: update docs to mention watch api Signed-off-by: Tarek --- ark-cli/USAGE.md | 10 ++++++++++ fs-index/README.md | 2 ++ 2 files changed, 12 insertions(+) diff --git a/ark-cli/USAGE.md b/ark-cli/USAGE.md index 9a77906e..867b5e4e 100644 --- a/ark-cli/USAGE.md +++ b/ark-cli/USAGE.md @@ -136,6 +136,16 @@ $ /tmp/ark-cli list -t --filter=search 22-207093268 search,engine ``` +### Watch a Directory for Changes + +You can watch a directory for changes and automatically update the index by running the following command: + +```sh +ark-cli watch [PATH] +``` + +If you don't provide a path, the current directory (`.`) will be used by default. This command continuously monitors the specified directory for file changes (create, modify, or remove) and updates the index accordingly. It's useful for keeping your index in sync with the latest changes in the folder. + ## :zap: Low-level utilities :zap: There are commands which could be useful with time, when you grasp the basic concepts. Some of these commands also can be useful for debugging [ArkLib](https://github.com/ARK-Builders/ark-rust). diff --git a/fs-index/README.md b/fs-index/README.md index ca3fb565..9faeb651 100644 --- a/fs-index/README.md +++ b/fs-index/README.md @@ -15,6 +15,8 @@ The most important struct in this crate is `ResourceIndex` which comes with: - `get_resource_by_path`: Query a resource from the index by its path. - **Selective API** - `update_one`: Method to manually update a specific resource by selectively rescanning a single file. +- **Watch API** + - `watch`: Method to watch a directory for changes and update the index accordingly. ## Custom Serialization From 3292422c60fb8df055cedb26e997643f996559b3 Mon Sep 17 00:00:00 2001 From: Tarek Date: Mon, 9 Sep 2024 14:05:24 +0300 Subject: [PATCH 05/15] fix(fs-index): discard events we do not care for in watch api Signed-off-by: Tarek --- fs-index/src/watch.rs | 62 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 56 insertions(+), 6 deletions(-) diff --git a/fs-index/src/watch.rs b/fs-index/src/watch.rs index 5d8189ed..8fa832a7 100644 --- a/fs-index/src/watch.rs +++ b/fs-index/src/watch.rs @@ -61,12 +61,50 @@ pub async fn watch_index, Id: ResourceId>( { continue; } - // If the event is not a create, modify or remove, ignore it - if !(event.kind.is_create() - || event.kind.is_modify() - || event.kind.is_remove()) - { - continue; + // We only care for: + // - file modifications + // - file renames + // - file creations + // - file deletions + match event.kind { + notify::EventKind::Modify(_) => match event { + notify::Event { + kind: + notify::EventKind::Modify( + notify::event::ModifyKind::Data(_), + ), + .. + } => {} + notify::Event { + kind: + notify::EventKind::Modify( + notify::event::ModifyKind::Name(_), + ), + .. + } => {} + _ => continue, + }, + notify::EventKind::Create(_) => match event { + notify::Event { + kind: + notify::EventKind::Create( + notify::event::CreateKind::File, + ), + .. + } => {} + _ => continue, + }, + notify::EventKind::Remove(_) => match event { + notify::Event { + kind: + notify::EventKind::Remove( + notify::event::RemoveKind::File, + ), + .. + } => {} + _ => continue, + }, + _ => {} } info!("Detected event: {:?}", event); @@ -75,7 +113,19 @@ pub async fn watch_index, Id: ResourceId>( .first() .expect("Failed to get file path from event"); log::debug!("Updating index for file: {:?}", file); + + log::info!( + "\n Current resource index: {}", + index + .resources() + .iter() + .map(|x| x.path().to_str().unwrap().to_string()) + .collect::>() + .join("\n\t") + ); + let relative_path = file.strip_prefix(&root_path)?; + log::info!("Relative path: {:?}", relative_path); index.update_one(relative_path)?; index.store()?; From f494c7cdd191f7ae36148367012e416af9256f70 Mon Sep 17 00:00:00 2001 From: Tarek Date: Mon, 9 Sep 2024 14:10:45 +0300 Subject: [PATCH 06/15] fmt Signed-off-by: Tarek --- fs-index/src/watch.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs-index/src/watch.rs b/fs-index/src/watch.rs index 8fa832a7..f658a725 100644 --- a/fs-index/src/watch.rs +++ b/fs-index/src/watch.rs @@ -113,7 +113,7 @@ pub async fn watch_index, Id: ResourceId>( .first() .expect("Failed to get file path from event"); log::debug!("Updating index for file: {:?}", file); - + log::info!( "\n Current resource index: {}", index @@ -123,7 +123,7 @@ pub async fn watch_index, Id: ResourceId>( .collect::>() .join("\n\t") ); - + let relative_path = file.strip_prefix(&root_path)?; log::info!("Relative path: {:?}", relative_path); index.update_one(relative_path)?; From ef07e430c2f9e9c849740a32df0133aa61f22d76 Mon Sep 17 00:00:00 2001 From: Tarek Date: Mon, 9 Sep 2024 14:52:01 +0300 Subject: [PATCH 07/15] fix(fs-index): simpler match statement Signed-off-by: Tarek --- fs-index/src/watch.rs | 51 +++++++++++-------------------------------- 1 file changed, 13 insertions(+), 38 deletions(-) diff --git a/fs-index/src/watch.rs b/fs-index/src/watch.rs index f658a725..e5fb5581 100644 --- a/fs-index/src/watch.rs +++ b/fs-index/src/watch.rs @@ -67,44 +67,19 @@ pub async fn watch_index, Id: ResourceId>( // - file creations // - file deletions match event.kind { - notify::EventKind::Modify(_) => match event { - notify::Event { - kind: - notify::EventKind::Modify( - notify::event::ModifyKind::Data(_), - ), - .. - } => {} - notify::Event { - kind: - notify::EventKind::Modify( - notify::event::ModifyKind::Name(_), - ), - .. - } => {} - _ => continue, - }, - notify::EventKind::Create(_) => match event { - notify::Event { - kind: - notify::EventKind::Create( - notify::event::CreateKind::File, - ), - .. - } => {} - _ => continue, - }, - notify::EventKind::Remove(_) => match event { - notify::Event { - kind: - notify::EventKind::Remove( - notify::event::RemoveKind::File, - ), - .. - } => {} - _ => continue, - }, - _ => {} + notify::EventKind::Modify( + notify::event::ModifyKind::Data(_), + ) + | notify::EventKind::Modify( + notify::event::ModifyKind::Name(_), + ) + | notify::EventKind::Create( + notify::event::CreateKind::File, + ) + | notify::EventKind::Remove( + notify::event::RemoveKind::File, + ) => {} + _ => continue, } info!("Detected event: {:?}", event); From 91dbb9e464c089b9580fa21485e38ac3bd3e31da Mon Sep 17 00:00:00 2001 From: Tarek Date: Tue, 10 Sep 2024 13:23:12 +0300 Subject: [PATCH 08/15] feat(fs-index): add a check for needs_rescan() Signed-off-by: Tarek --- fs-index/src/watch.rs | 43 +++++++++++++++++++++++++------------------ 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/fs-index/src/watch.rs b/fs-index/src/watch.rs index e5fb5581..39d1d6bc 100644 --- a/fs-index/src/watch.rs +++ b/fs-index/src/watch.rs @@ -82,26 +82,33 @@ pub async fn watch_index, Id: ResourceId>( _ => continue, } - info!("Detected event: {:?}", event); - let file = event - .paths - .first() - .expect("Failed to get file path from event"); - log::debug!("Updating index for file: {:?}", file); + // If the event requires a rescan, update the entire index + // else, update the index for the specific file + if event.need_rescan() { + info!("Detected rescan event: {:?}", event); + index.update_all()?; + } else { + info!("Detected event: {:?}", event); + let file = event + .paths + .first() + .expect("Failed to get file path from event"); + log::debug!("Updating index for file: {:?}", file); - log::info!( - "\n Current resource index: {}", - index - .resources() - .iter() - .map(|x| x.path().to_str().unwrap().to_string()) - .collect::>() - .join("\n\t") - ); + log::info!( + "\n Current resource index: {}", + index + .resources() + .iter() + .map(|x| x.path().to_str().unwrap().to_string()) + .collect::>() + .join("\n\t") + ); - let relative_path = file.strip_prefix(&root_path)?; - log::info!("Relative path: {:?}", relative_path); - index.update_one(relative_path)?; + let relative_path = file.strip_prefix(&root_path)?; + log::info!("Relative path: {:?}", relative_path); + index.update_one(relative_path)?; + } index.store()?; info!("Index updated and stored"); From e193d7936c3c15bfc6dc3ee87281b7a93c86ef7f Mon Sep 17 00:00:00 2001 From: Tarek Date: Tue, 10 Sep 2024 14:01:44 +0300 Subject: [PATCH 09/15] docs: add a note in fs-index about ark-cli watch command Signed-off-by: Tarek --- fs-index/README.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/fs-index/README.md b/fs-index/README.md index 9faeb651..6d4cd680 100644 --- a/fs-index/README.md +++ b/fs-index/README.md @@ -18,6 +18,8 @@ The most important struct in this crate is `ResourceIndex` which comes with: - **Watch API** - `watch`: Method to watch a directory for changes and update the index accordingly. +> **Note:** To see the watch API in action, run the `index_watch` example or check `ark-cli watch` command. + ## Custom Serialization The `ResourceIndex` struct includes a custom serialization implementation to avoid writing a large repetitive index file with double maps. @@ -32,8 +34,14 @@ The `ResourceIndex` struct includes a custom serialization implementation to avo To get started, take a look at the examples in the `examples/` directory. -To run a specific example: +To run a specific example, run this command from the root of the project or the root of the crate: + +```shell +cargo run --example +``` + +For example, to run the `index_watch` example: ```shell -cargo run --example resource_index +cargo run --example index_watch ``` From ca8ccfb6a091cdc865551927cd873d8030abb958 Mon Sep 17 00:00:00 2001 From: Tarek Date: Mon, 16 Sep 2024 16:25:04 +0300 Subject: [PATCH 10/15] feat(fs-index): return stream of `WatchEvent` from `watch_index()` - Refactored `watch_index` to return an async stream of `WatchEvent` using `async_stream` and `tokio` - Integrated notify debouncer to collapse multiple rapid events into a single event - Updated the `watch_index` example to reflect the new implementation - Marked `ResourceId` as `Sync` and `Send` to allow usage in async contexts - Modified `ark-cli watch` to align with these changes Signed-off-by: Tarek --- ark-cli/Cargo.toml | 1 + ark-cli/src/commands/watch.rs | 22 +++- data-resource/src/lib.rs | 2 + fs-index/Cargo.toml | 6 +- fs-index/examples/index_watch.rs | 44 ++++--- fs-index/src/lib.rs | 4 +- fs-index/src/watch.rs | 202 ++++++++++++++++++------------- 7 files changed, 164 insertions(+), 117 deletions(-) diff --git a/ark-cli/Cargo.toml b/ark-cli/Cargo.toml index 08655206..a60f5a1a 100644 --- a/ark-cli/Cargo.toml +++ b/ark-cli/Cargo.toml @@ -18,6 +18,7 @@ serde_json = "1.0.82" chrono = "0.4.34" anyhow = "1.0.80" thiserror = "1.0.57" +futures = "0.3" # REGISTRAR log = { version = "0.4.17", features = ["release_max_level_off"] } diff --git a/ark-cli/src/commands/watch.rs b/ark-cli/src/commands/watch.rs index c77ac534..701f90fa 100644 --- a/ark-cli/src/commands/watch.rs +++ b/ark-cli/src/commands/watch.rs @@ -1,6 +1,8 @@ use std::path::PathBuf; -use fs_index::watch_index; +use futures::{pin_mut, StreamExt}; + +use fs_index::{watch_index, WatchEvent}; use crate::{AppError, ResourceId}; @@ -20,8 +22,20 @@ pub struct Watch { impl Watch { pub async fn run(&self) -> Result<(), AppError> { - watch_index::<_, ResourceId>(&self.path) - .await - .map_err(|err| AppError::IndexError(err.to_string())) + let stream = watch_index::<_, ResourceId>(&self.path); + pin_mut!(stream); + + while let Some(value) = stream.next().await { + match value { + WatchEvent::UpdatedOne(path) => { + println!("Updated file: {:?}", path); + } + WatchEvent::UpdatedAll(update) => { + println!("Updated all: {:?}", update); + } + } + } + + Ok(()) } } diff --git a/data-resource/src/lib.rs b/data-resource/src/lib.rs index ea7426c3..9368101c 100644 --- a/data-resource/src/lib.rs +++ b/data-resource/src/lib.rs @@ -25,6 +25,8 @@ pub trait ResourceId: + Hash + Serialize + DeserializeOwned + + Sync + + Send { /// Computes the resource identifier from the given file path fn from_path>(file_path: P) -> Result; diff --git a/fs-index/Cargo.toml b/fs-index/Cargo.toml index ce74b817..1e1cea99 100644 --- a/fs-index/Cargo.toml +++ b/fs-index/Cargo.toml @@ -10,14 +10,16 @@ bench = false [dependencies] log = { version = "0.4.17", features = ["release_max_level_off"] } -env_logger = "0.11" walkdir = "2.3.2" anyhow = "1.0.58" serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } # For the watch API notify = "6.1" +notify-debouncer-full = "0.3" futures = "0.3" +async-stream = "0.3" +tokio = { version = "1.40", features = ["full"] } fs-storage = { path = "../fs-storage" } @@ -32,8 +34,6 @@ criterion = { version = "0.5", features = ["html_reports"] } tempfile = "3.10" # Depending on `dev-hash` for testing dev-hash = { path = "../dev-hash" } -# Examples -tokio = { version = "1.40", features = ["full"] } [[bench]] name = "resource_index_benchmark" diff --git a/fs-index/examples/index_watch.rs b/fs-index/examples/index_watch.rs index 0e68a6fa..42531b97 100644 --- a/fs-index/examples/index_watch.rs +++ b/fs-index/examples/index_watch.rs @@ -1,35 +1,33 @@ -use std::{path::Path, thread}; +use std::path::Path; use anyhow::Result; -use log::LevelFilter; +use futures::{pin_mut, StreamExt}; use dev_hash::Blake3; -use fs_index::watch_index; - -/// Example demonstrating how to use fs_index to watch a directory for changes -/// in a separate thread. This automatically updates the index when changes are -/// detected. -fn main() -> Result<()> { - env_logger::builder() - .filter_level(LevelFilter::Debug) - .init(); +use fs_index::{watch_index, WatchEvent}; +/// A simple example of using `watch_index` to monitor a directory for file +/// changes. This asynchronously listens for updates and prints the paths of +/// changed files. +#[tokio::main] +async fn main() -> Result<()> { // Change this to the path of the directory you want to watch let root = Path::new("test-assets"); - let thread_handle = thread::spawn(move || { - tokio::runtime::Runtime::new() - .unwrap() - .block_on(async move { - if let Err(err) = watch_index::<_, Blake3>(root).await { - eprintln!("Error in watching index: {:?}", err); - } - }); - }); + let stream = watch_index::<_, Blake3>(root); + + pin_mut!(stream); // needed for iteration - thread_handle - .join() - .expect("Failed to join thread"); + while let Some(value) = stream.next().await { + match value { + WatchEvent::UpdatedOne(path) => { + println!("Updated file: {:?}", path); + } + WatchEvent::UpdatedAll(update) => { + println!("Updated all: {:?}", update); + } + } + } Ok(()) } diff --git a/fs-index/src/lib.rs b/fs-index/src/lib.rs index 5f15a4a2..231c6f0c 100644 --- a/fs-index/src/lib.rs +++ b/fs-index/src/lib.rs @@ -3,9 +3,9 @@ mod serde; mod utils; mod watch; -pub use index::ResourceIndex; +pub use index::{IndexUpdate, ResourceIndex}; pub use utils::load_or_build_index; -pub use watch::watch_index; +pub use watch::{watch_index, WatchEvent}; #[cfg(test)] mod tests; diff --git a/fs-index/src/watch.rs b/fs-index/src/watch.rs index 39d1d6bc..60cd1ffa 100644 --- a/fs-index/src/watch.rs +++ b/fs-index/src/watch.rs @@ -1,59 +1,84 @@ -use std::{fs, path::Path}; - -use anyhow::Result; -use futures::{ - channel::mpsc::{channel, Receiver}, - SinkExt, StreamExt, +use std::{ + fs, + path::{Path, PathBuf}, + thread, + time::Duration, }; -use log::info; -use notify::{Config, Event, RecommendedWatcher, RecursiveMode, Watcher}; + +use async_stream::stream; +use futures::Stream; +use notify::{RecursiveMode, Watcher}; +use notify_debouncer_full::new_debouncer; +use tokio::sync::mpsc; use data_resource::ResourceId; use fs_storage::ARK_FOLDER; -use crate::ResourceIndex; +use crate::{IndexUpdate, ResourceIndex}; -/// Watches a given directory for file system changes and automatically updates -/// the resource index. -/// -/// This function continuously monitors the specified directory and responds to -/// file system events such as file creation, modification, and deletion. When -/// an event is detected, the function updates the associated resource index and -/// stores the changes. -/// -/// The function runs asynchronously, whcih makes it suitable for non-blocking -/// contexts. It uses a recursive watcher to track all changes within the -/// directory tree. Events related to the internal `.ark` folder are ignored to -/// prevent unnecessary updates. -/// -/// # Arguments +/// Represents the different kinds of events that can occur when watching the +/// resource index. +#[derive(Debug)] +pub enum WatchEvent { + /// Represents an update to a single resource. + UpdatedOne(PathBuf), + /// Represents an update to all resources. + UpdatedAll(IndexUpdate), +} + +/// Watches for file system changes and emits events related to the +/// [ResourceIndex]. /// -/// * `root_path` - The root directory to be watched. This path is canonicalized -/// to handle symbolic links and relative paths correctly. -pub async fn watch_index, Id: ResourceId>( +/// This function sets up a file watcher that monitors a specified root path for +/// changes to files. It sends events such as file creations, modifications, +/// renames, and deletions through an asynchronous stream. The function uses a +/// debouncer to ensure that multiple rapid events are collapsed into a single +/// event. +pub fn watch_index, Id: ResourceId + 'static>( root_path: P, -) -> Result<()> { +) -> impl Stream> { log::debug!( "Attempting to watch index at root path: {:?}", root_path.as_ref() ); - let root_path = fs::canonicalize(root_path.as_ref())?; - let mut index: ResourceIndex = ResourceIndex::build(&root_path)?; - index.store()?; - - let (mut watcher, mut rx) = async_watcher()?; - info!("Watching directory: {:?}", root_path); - let config = Config::default(); - watcher.configure(config)?; - watcher.watch(root_path.as_ref(), RecursiveMode::Recursive)?; - info!("Started watcher with config: \n\t{:?}", config); + let root_path = fs::canonicalize(root_path.as_ref()).unwrap(); + let mut index: ResourceIndex = + ResourceIndex::build(&root_path).unwrap(); + index.store().unwrap(); + let (tx, mut rx) = mpsc::channel(100); let ark_folder = root_path.join(ARK_FOLDER); - while let Some(res) = rx.next().await { - match res { - Ok(event) => { - // If the event is a change in .ark folder, ignore it + + // We need to spawn a new thread to run the blocking file system watcher + thread::spawn(move || { + // Setup the synchronous channel (notify debouncer expects this) + let (sync_tx, sync_rx) = std::sync::mpsc::channel(); + + let mut debouncer = + new_debouncer(Duration::from_secs(2), None, sync_tx).unwrap(); + let watcher = debouncer.watcher(); + watcher + .watch(&root_path, RecursiveMode::Recursive) + .unwrap(); + log::info!("Started debouncer file system watcher for {:?}", root_path); + + while let Ok(events) = sync_rx.recv() { + let events = match events { + Ok(evts) => evts, + Err(errs) => { + for err in errs { + log::error!("Error receiving event: {:?}", err); + } + continue; + } + }; + + // Send events to the async channel + for event in events { + log::trace!("Received event: {:?}", event); + + // If the event is a change in the .ark folder, ignore it if event .paths .iter() @@ -61,18 +86,25 @@ pub async fn watch_index, Id: ResourceId>( { continue; } + + let event_kind = event.event.kind; // We only care for: // - file modifications // - file renames // - file creations // - file deletions - match event.kind { + match event_kind { notify::EventKind::Modify( notify::event::ModifyKind::Data(_), ) | notify::EventKind::Modify( notify::event::ModifyKind::Name(_), ) + // On macOS, we noticed that force deleting a file + // triggers a metadata change event for some reason + | notify::EventKind::Modify( + notify::event::ModifyKind::Metadata(_), + ) | notify::EventKind::Create( notify::event::CreateKind::File, ) @@ -82,58 +114,58 @@ pub async fn watch_index, Id: ResourceId>( _ => continue, } - // If the event requires a rescan, update the entire index - // else, update the index for the specific file - if event.need_rescan() { - info!("Detected rescan event: {:?}", event); - index.update_all()?; + let watch_event: WatchEvent = if event.need_rescan() { + log::info!("Detected rescan event: {:?}", event); + match index.update_all() { + Ok(update_result) => { + WatchEvent::UpdatedAll(update_result) + } + Err(e) => { + log::error!("Failed to update all: {:?}", e); + continue; + } + } } else { - info!("Detected event: {:?}", event); + // Update the index for the specific file let file = event .paths .first() .expect("Failed to get file path from event"); - log::debug!("Updating index for file: {:?}", file); - - log::info!( - "\n Current resource index: {}", - index - .resources() - .iter() - .map(|x| x.path().to_str().unwrap().to_string()) - .collect::>() - .join("\n\t") - ); - - let relative_path = file.strip_prefix(&root_path)?; - log::info!("Relative path: {:?}", relative_path); - index.update_one(relative_path)?; - } - index.store()?; - info!("Index updated and stored"); - } - Err(e) => log::error!("Error in watcher: {:?}", e), - } - } + let relative_path = match file.strip_prefix(&root_path) { + Ok(path) => path, + Err(e) => { + log::error!("Failed to get relative path: {:?}", e); + continue; + } + }; - unreachable!("Watcher stream ended unexpectedly"); -} + if let Err(e) = index.update_one(relative_path) { + log::error!("Failed to update one: {:?}", e); + continue; + } -fn async_watcher( -) -> notify::Result<(RecommendedWatcher, Receiver>)> { - let (mut tx, rx) = channel(1); + WatchEvent::UpdatedOne(relative_path.to_path_buf()) + }; - let watcher = RecommendedWatcher::new( - move |res| { - futures::executor::block_on(async { - if let Err(err) = tx.send(res).await { - log::error!("Error sending event: {:?}", err); + if let Err(e) = index.store() { + log::error!("Failed to store index: {:?}", e); } - }) - }, - Config::default(), - )?; - Ok((watcher, rx)) + // Use blocking send to the async channel because we are in a + // separate thread + if tx.blocking_send(watch_event).is_err() { + log::error!("Failed to send event to async channel"); + break; + } + } + } + }); + + // Create an async stream that reads from the receiver + stream! { + while let Some(event) = rx.recv().await { + yield event; + } + } } From 1fe073104c2857ea3459abb090004a2b5400b5d1 Mon Sep 17 00:00:00 2001 From: Tarek Date: Mon, 16 Sep 2024 16:28:52 +0300 Subject: [PATCH 11/15] feat(fs-index): modify `ResourceIndex::update_one()` to return `IndexUpdate` - Updated `update_one()` to return `IndexUpdate` with detailed changes - Modified `WatchEvent::UpdatedOne` to include all file changes - Added getters for `Timestamped` for easier access to data - Updated `index_watch` example and `ark-cli watch` to adapt to these changes Signed-off-by: Tarek --- ark-cli/src/commands/watch.rs | 41 ++++++++++++++++++++++++++++---- fs-index/examples/index_watch.rs | 4 ++-- fs-index/src/index.rs | 28 ++++++++++++++++++++-- fs-index/src/watch.rs | 24 +++++++++---------- 4 files changed, 76 insertions(+), 21 deletions(-) diff --git a/ark-cli/src/commands/watch.rs b/ark-cli/src/commands/watch.rs index 701f90fa..6ef1d1e8 100644 --- a/ark-cli/src/commands/watch.rs +++ b/ark-cli/src/commands/watch.rs @@ -4,7 +4,7 @@ use futures::{pin_mut, StreamExt}; use fs_index::{watch_index, WatchEvent}; -use crate::{AppError, ResourceId}; +use crate::{AppError, DateTime, ResourceId, Utc}; #[derive(Clone, Debug, clap::Args)] #[clap( @@ -27,11 +27,44 @@ impl Watch { while let Some(value) = stream.next().await { match value { - WatchEvent::UpdatedOne(path) => { - println!("Updated file: {:?}", path); + WatchEvent::UpdatedOne(update) => { + println!("Index updated with a single file change"); + + let added = update.added(); + let removed = update.removed(); + for file in added { + let time_stamped_path = file.1.iter().next().unwrap(); + let file_path = time_stamped_path.item(); + let last_modified = time_stamped_path.last_modified(); + let last_modified: DateTime = last_modified.into(); + println!( + "\tAdded file: {:?} (last modified: {})", + file_path, + last_modified.format("%d/%m/%Y %T") + ); + } + for file in removed { + println!("\tRemoved file with hash: {:?}", file); + } } WatchEvent::UpdatedAll(update) => { - println!("Updated all: {:?}", update); + println!("Index fully updated"); + + let added = update.added(); + let removed = update.removed(); + + for file in added { + let time_stamped_path = file.1.iter().next().unwrap(); + let file_path = time_stamped_path.item(); + let last_modified = time_stamped_path.last_modified(); + println!( + "\tAdded file: {:?} (last modified: {:?})", + file_path, last_modified + ); + } + for file in removed { + println!("\tRemoved file with hash: {:?}", file); + } } } } diff --git a/fs-index/examples/index_watch.rs b/fs-index/examples/index_watch.rs index 42531b97..f105a3eb 100644 --- a/fs-index/examples/index_watch.rs +++ b/fs-index/examples/index_watch.rs @@ -20,8 +20,8 @@ async fn main() -> Result<()> { while let Some(value) = stream.next().await { match value { - WatchEvent::UpdatedOne(path) => { - println!("Updated file: {:?}", path); + WatchEvent::UpdatedOne(update) => { + println!("Updated file: {:?}", update); } WatchEvent::UpdatedAll(update) => { println!("Updated all: {:?}", update); diff --git a/fs-index/src/index.rs b/fs-index/src/index.rs index 6746ac29..defb1a5b 100644 --- a/fs-index/src/index.rs +++ b/fs-index/src/index.rs @@ -65,6 +65,17 @@ pub struct Timestamped { pub(crate) last_modified: SystemTime, } +impl Timestamped { + pub fn item(&self) -> &Item { + &self.item + } + + /// Return the last modified time + pub fn last_modified(&self) -> SystemTime { + self.last_modified + } +} + type IndexedPaths = HashSet>; /// Represents the index of resources in a directory. @@ -491,10 +502,15 @@ impl ResourceIndex { pub fn update_one>( &mut self, relative_path: P, - ) -> Result<()> { + ) -> Result> { let path = relative_path.as_ref(); let entry_path = self.root.join(path); + let mut result = IndexUpdate { + added: HashMap::new(), + removed: HashSet::new(), + }; + // Check if the entry exists in the file system if !entry_path.exists() { // If the entry does not exist in the file system, it's a removal @@ -515,6 +531,7 @@ impl ResourceIndex { self.id_to_paths.remove(&id.item); } + result.removed.insert(id.item); log::trace!("Resource removed: {:?}", path); } else { // If the entry exists in the file system, it's an addition or @@ -547,9 +564,16 @@ impl ResourceIndex { .or_default() .insert(path.to_path_buf()); + let timpestamped_path = Timestamped { + item: path.to_path_buf(), + last_modified, + }; + result + .added + .insert(id, HashSet::from([timpestamped_path])); log::trace!("Resource added/updated: {:?}", path); } - Ok(()) + Ok(result) } } diff --git a/fs-index/src/watch.rs b/fs-index/src/watch.rs index 60cd1ffa..6ed970e3 100644 --- a/fs-index/src/watch.rs +++ b/fs-index/src/watch.rs @@ -1,9 +1,4 @@ -use std::{ - fs, - path::{Path, PathBuf}, - thread, - time::Duration, -}; +use std::{fs, path::Path, thread, time::Duration}; use async_stream::stream; use futures::Stream; @@ -21,7 +16,7 @@ use crate::{IndexUpdate, ResourceIndex}; #[derive(Debug)] pub enum WatchEvent { /// Represents an update to a single resource. - UpdatedOne(PathBuf), + UpdatedOne(IndexUpdate), /// Represents an update to all resources. UpdatedAll(IndexUpdate), } @@ -103,7 +98,7 @@ pub fn watch_index, Id: ResourceId + 'static>( // On macOS, we noticed that force deleting a file // triggers a metadata change event for some reason | notify::EventKind::Modify( - notify::event::ModifyKind::Metadata(_), + notify::event::ModifyKind::Metadata(notify::event::MetadataKind::Any), ) | notify::EventKind::Create( notify::event::CreateKind::File, @@ -140,12 +135,15 @@ pub fn watch_index, Id: ResourceId + 'static>( } }; - if let Err(e) = index.update_one(relative_path) { - log::error!("Failed to update one: {:?}", e); - continue; + match index.update_one(relative_path) { + Ok(update_result) => { + WatchEvent::UpdatedOne(update_result) + } + Err(e) => { + log::error!("Failed to update one: {:?}", e); + continue; + } } - - WatchEvent::UpdatedOne(relative_path.to_path_buf()) }; if let Err(e) = index.store() { From 94bcd2595be339227e3dfef525ca41311bf4e02a Mon Sep 17 00:00:00 2001 From: Tarek Date: Mon, 16 Sep 2024 17:21:13 +0300 Subject: [PATCH 12/15] feat(fs-index): push watch API dependencies behind a feature flag Signed-off-by: Tarek --- ark-cli/Cargo.toml | 2 +- fs-index/Cargo.toml | 14 +++++++++----- fs-index/README.md | 2 +- fs-index/src/lib.rs | 2 ++ 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/ark-cli/Cargo.toml b/ark-cli/Cargo.toml index a60f5a1a..ea661d3d 100644 --- a/ark-cli/Cargo.toml +++ b/ark-cli/Cargo.toml @@ -26,7 +26,7 @@ lazy_static = "1.4.0" canonical-path = "2.0.2" -fs-index = { path = "../fs-index" } +fs-index = { path = "../fs-index", features = ["watch"] } fs-atomic-versions = { path = "../fs-atomic-versions" } fs-metadata = { path = "../fs-metadata" } fs-properties = { path = "../fs-properties" } diff --git a/fs-index/Cargo.toml b/fs-index/Cargo.toml index 1e1cea99..4057f8aa 100644 --- a/fs-index/Cargo.toml +++ b/fs-index/Cargo.toml @@ -15,11 +15,11 @@ anyhow = "1.0.58" serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } # For the watch API -notify = "6.1" -notify-debouncer-full = "0.3" -futures = "0.3" -async-stream = "0.3" -tokio = { version = "1.40", features = ["full"] } +notify = { version = "6.1", optional = true } +notify-debouncer-full = { version = "0.3", optional = true } +futures = { version = "0.3", optional = true } +async-stream = { version = "0.3", optional = true } +tokio = { version = "1.40", features = ["full"], optional = true } fs-storage = { path = "../fs-storage" } @@ -27,6 +27,10 @@ fs-storage = { path = "../fs-storage" } data-error = { path = "../data-error" } data-resource = { path = "../data-resource" } + +[features] +watch = ["notify", "notify-debouncer-full", "futures", "async-stream", "tokio"] + [dev-dependencies] uuid = { version = "1.6.1", features = ["v4"] } # benchmarking diff --git a/fs-index/README.md b/fs-index/README.md index 6d4cd680..427db943 100644 --- a/fs-index/README.md +++ b/fs-index/README.md @@ -15,7 +15,7 @@ The most important struct in this crate is `ResourceIndex` which comes with: - `get_resource_by_path`: Query a resource from the index by its path. - **Selective API** - `update_one`: Method to manually update a specific resource by selectively rescanning a single file. -- **Watch API** +- **Watch API** (Enable with `watch` feature) - `watch`: Method to watch a directory for changes and update the index accordingly. > **Note:** To see the watch API in action, run the `index_watch` example or check `ark-cli watch` command. diff --git a/fs-index/src/lib.rs b/fs-index/src/lib.rs index 231c6f0c..ab86fe00 100644 --- a/fs-index/src/lib.rs +++ b/fs-index/src/lib.rs @@ -1,10 +1,12 @@ mod index; mod serde; mod utils; +#[cfg(feature = "watch")] mod watch; pub use index::{IndexUpdate, ResourceIndex}; pub use utils::load_or_build_index; +#[cfg(feature = "watch")] pub use watch::{watch_index, WatchEvent}; #[cfg(test)] From 6122365f070a0ce68b16268f9f479e576c9a2a71 Mon Sep 17 00:00:00 2001 From: Tarek Date: Tue, 29 Oct 2024 17:08:05 +0300 Subject: [PATCH 13/15] fix: only return the id if it is completely removed in update_one Signed-off-by: Tarek --- fs-index/src/index.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/fs-index/src/index.rs b/fs-index/src/index.rs index defb1a5b..e50eab82 100644 --- a/fs-index/src/index.rs +++ b/fs-index/src/index.rs @@ -529,9 +529,8 @@ impl ResourceIndex { // If the ID has no paths, remove it from the ID to paths map if self.id_to_paths[&id.item].is_empty() { self.id_to_paths.remove(&id.item); + result.removed.insert(id.item); } - - result.removed.insert(id.item); log::trace!("Resource removed: {:?}", path); } else { // If the entry exists in the file system, it's an addition or From 71a81e994831f00e4cc4a32b1feadd79ed70b318 Mon Sep 17 00:00:00 2001 From: Tarek Date: Tue, 29 Oct 2024 17:56:46 +0300 Subject: [PATCH 14/15] feat: add a shell script to test ark-cli watch Signed-off-by: Tarek --- .github/workflows/build.yml | 9 ++++ integration/ark-cli-watch.sh | 83 ++++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+) create mode 100755 integration/ark-cli-watch.sh diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a6a7d947..9148fa98 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -39,6 +39,9 @@ jobs: - name: Run tests run: cargo test --verbose + - name: Run `ark-cli watch` test + run: ./integration/ark-cli-watch.sh + - name: Build Release run: cargo build --verbose --release @@ -71,6 +74,9 @@ jobs: - name: Run tests run: cargo test --workspace --verbose + - name: Run `ark-cli watch` test + run: ./integration/ark-cli-watch.sh + - name: Install JDK uses: actions/setup-java@v4.2.1 with: @@ -100,6 +106,9 @@ jobs: - name: Run tests run: cargo test --workspace --verbose + - name: Run `ark-cli watch` test + run: ./integration/ark-cli-watch.sh + - name: Install JDK uses: actions/setup-java@v4.2.1 with: diff --git a/integration/ark-cli-watch.sh b/integration/ark-cli-watch.sh new file mode 100755 index 00000000..b5a172f2 --- /dev/null +++ b/integration/ark-cli-watch.sh @@ -0,0 +1,83 @@ +#!/bin/bash + +# Set up paths +WATCH_DIR="test-assets" +OUTPUT_FILE="ark-watch-output.txt" +INDEX_FILE="$WATCH_DIR/.ark/index" + +# Function to check the index file content +check_index() { + # Expecting a certain number of resources based on the operations done + expected_count=$1 + shift + expected_resources=("$@") + + # Get the actual count of resources in the index + resources_count=$(jq '.resources | keys | length' "$INDEX_FILE") + + if [ "$resources_count" -ne "$expected_count" ]; then + echo "Index sanity check failed: expected $expected_count resources, found $resources_count" + exit 1 + fi + + # Check the paths of the resources in the index + for resource in "${expected_resources[@]}"; do + if ! jq -e ".resources | has(\"$resource\")" "$INDEX_FILE" > /dev/null; then + echo "Index sanity check failed: resource \"$resource\" not found in index." + exit 1 + fi + done + + echo "Current resources in index:" + jq '.resources' "$INDEX_FILE" +} + +# Start `ark-cli watch` in the background and capture output +echo "Starting ark-cli watch on $WATCH_DIR..." +ark-cli watch "$WATCH_DIR" > "$OUTPUT_FILE" & +WATCH_PID=$! +sleep 1 # Wait a bit to ensure the watch command is up + +# Initial sanity check for index file +check_index 2 "test.pdf" "lena.jpg" # Initially should contain lena.jpg and test.pdf + +echo "Modifying files in $WATCH_DIR..." + +# Step 1: Copy `lena.jpg` to `lena_copy.jpg` +cp "$WATCH_DIR/lena.jpg" "$WATCH_DIR/lena_copy.jpg" +sleep 3 + +check_index 3 "lena.jpg" "lena_copy.jpg" "test.pdf" + +# Step 2: Remove `test.pdf` +rm "$WATCH_DIR/test.pdf" +sleep 3 + +check_index 2 "lena.jpg" "lena_copy.jpg" + +# Step 3: Create a new empty file `note.txt` +touch "$WATCH_DIR/note.txt" +sleep 3 + +# Final index check after all operations +echo "Verifying final index state..." +check_index 3 "lena.jpg" "lena_copy.jpg" "note.txt" # Expect three resources now + +# Allow `ark-cli watch` time to process and then kill it +sleep 1 +kill $WATCH_PID + +# Wait briefly for output to complete +wait $WATCH_PID 2>/dev/null + +# Read and verify the ark-watch-output.txt contents +echo "Checking ark-cli watch output..." +expected_change_count=3 # Three file changes done +actual_change_count=$(grep -c "Index updated with a single file change" "$OUTPUT_FILE") + +if [ "$actual_change_count" -ne "$expected_change_count" ]; then + echo "Output verification failed: expected $expected_change_count updates, found $actual_change_count" + exit 1 +fi + +echo "All checks passed successfully!" From c0fee8750d33943c1b672fd5174bdadf1672d86f Mon Sep 17 00:00:00 2001 From: Tarek Date: Tue, 29 Oct 2024 18:14:33 +0300 Subject: [PATCH 15/15] fix: use ark-cli from path directly Signed-off-by: Tarek --- .github/workflows/build.yml | 6 +++--- integration/ark-cli-watch.sh | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9148fa98..7701fb74 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -39,12 +39,12 @@ jobs: - name: Run tests run: cargo test --verbose - - name: Run `ark-cli watch` test - run: ./integration/ark-cli-watch.sh - - name: Build Release run: cargo build --verbose --release + - name: Run `ark-cli watch` test + run: ./integration/ark-cli-watch.sh + - name: Install JDK uses: actions/setup-java@v4.2.1 with: diff --git a/integration/ark-cli-watch.sh b/integration/ark-cli-watch.sh index b5a172f2..58d935f8 100755 --- a/integration/ark-cli-watch.sh +++ b/integration/ark-cli-watch.sh @@ -4,6 +4,7 @@ WATCH_DIR="test-assets" OUTPUT_FILE="ark-watch-output.txt" INDEX_FILE="$WATCH_DIR/.ark/index" +ARK_CLI="./target/release/ark-cli" # Function to check the index file content check_index() { @@ -34,7 +35,7 @@ check_index() { # Start `ark-cli watch` in the background and capture output echo "Starting ark-cli watch on $WATCH_DIR..." -ark-cli watch "$WATCH_DIR" > "$OUTPUT_FILE" & +$ARK_CLI watch "$WATCH_DIR" > "$OUTPUT_FILE" & WATCH_PID=$! sleep 1 # Wait a bit to ensure the watch command is up