From 1efcef15e940264d451b663abb57969f4485e0e3 Mon Sep 17 00:00:00 2001 From: jdx <216188+jdx@users.noreply.github.com> Date: Fri, 6 Dec 2024 18:12:50 +0000 Subject: [PATCH] wip --- .idea/workspace.xml | 26 +++- Cargo.lock | 267 +++++++++++++++++++++++++++++++++++++++++- Cargo.toml | 18 +-- src/async_watcher.rs | 39 ++++++ src/cli/daemon/run.rs | 3 +- src/env.rs | 4 +- src/logger.rs | 2 +- src/main.rs | 12 +- src/pid_file.rs | 27 +++-- src/supervisor.rs | 57 +++++++-- 10 files changed, 419 insertions(+), 36 deletions(-) create mode 100644 src/async_watcher.rs diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 0f83f1d..185e4e6 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -8,8 +8,15 @@ + + + + + + + + + + @@ -28,6 +42,10 @@ "associatedIndex": 1 }]]> + + + + + + diff --git a/Cargo.lock b/Cargo.lock index a5bb118..025f08f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -75,6 +75,31 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "async-trait" +version = "0.1.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "async-watcher" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74775c968d086c9d1fcf647270d79a469762dad4df4fc180f4511bea9dbba0cb" +dependencies = [ + "async-trait", + "notify", + "serde", + "thiserror 1.0.69", + "tokio", + "tracing", +] + [[package]] name = "autocfg" version = "1.4.0" @@ -209,6 +234,15 @@ dependencies = [ "libc", ] +[[package]] +name = "crossbeam-channel" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-deque" version = "0.8.5" @@ -364,6 +398,15 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "fsevent-sys" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" +dependencies = [ + "libc", +] + [[package]] name = "fslock" version = "0.2.1" @@ -460,6 +503,26 @@ dependencies = [ "serde", ] +[[package]] +name = "inotify" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff" +dependencies = [ + "bitflags 1.3.2", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" +dependencies = [ + "libc", +] + [[package]] name = "interprocess" version = "2.2.2" @@ -481,6 +544,32 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +[[package]] +name = "itoa" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" + +[[package]] +name = "kqueue" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7447f1ca1b7b563588a205fe93dea8df60fd981423a768bc1c0ded35ed147d0c" +dependencies = [ + "kqueue-sys", + "libc", +] + +[[package]] +name = "kqueue-sys" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" +dependencies = [ + "bitflags 1.3.2", + "libc", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -567,6 +656,18 @@ dependencies = [ "adler2", ] +[[package]] +name = "mio" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.48.0", +] + [[package]] name = "mio" version = "1.0.3" @@ -601,6 +702,24 @@ dependencies = [ "libc", ] +[[package]] +name = "notify" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "729f63e1ca555a43fe3efa4f3efdf4801c479da85b432242a7b726f353c88486" +dependencies = [ + "bitflags 1.3.2", + "crossbeam-channel", + "filetime", + "fsevent-sys", + "inotify", + "kqueue", + "libc", + "mio 0.8.11", + "walkdir", + "windows-sys 0.45.0", +] + [[package]] name = "ntapi" version = "0.4.1" @@ -684,6 +803,7 @@ checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" name = "pitchfork-cli" version = "0.1.0" dependencies = [ + "async-watcher", "clap", "console", "dirs", @@ -694,6 +814,7 @@ dependencies = [ "once_cell", "psutil", "serde", + "serde_json", "sysinfo", "tokio", "toml", @@ -824,6 +945,21 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -850,6 +986,19 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_json" +version = "1.0.133" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" +dependencies = [ + "indexmap", + "itoa", + "memchr", + "ryu", + "serde", +] + [[package]] name = "serde_spanned" version = "0.6.8" @@ -985,7 +1134,7 @@ dependencies = [ "backtrace", "bytes", "libc", - "mio", + "mio 1.0.3", "parking_lot", "pin-project-lite", "signal-hook-registry", @@ -1040,6 +1189,37 @@ dependencies = [ "winnow", ] +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +dependencies = [ + "once_cell", +] + [[package]] name = "typenum" version = "1.17.0" @@ -1076,6 +1256,16 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -1104,6 +1294,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -1163,6 +1362,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + [[package]] name = "windows-sys" version = "0.48.0" @@ -1190,6 +1398,21 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + [[package]] name = "windows-targets" version = "0.48.5" @@ -1221,6 +1444,12 @@ dependencies = [ "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -1233,6 +1462,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -1245,6 +1480,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -1263,6 +1504,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -1275,6 +1522,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -1287,6 +1540,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -1299,6 +1558,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" diff --git a/Cargo.toml b/Cargo.toml index 950c11b..e8bb727 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,17 +10,19 @@ name = "pitchfork" path = "src/main.rs" [dependencies] +async-watcher = "0.3.0" clap = { version = "4.5.23", features = ["derive"] } +console = "0.15.8" dirs = "5.0.1" -once_cell = "1.20.2" -xx = {version = "2", features = ["fslock", "hash"]} -log = "0.4.22" -psutil = "3.3.0" eyre = "0.6.12" +indexmap = { version = "2.7.0", features = ["serde"] } interprocess = { version = "2.2.2", features = ["tokio"] } -tokio = { version = "1.42.0", features = ["full"] } -sysinfo = "0.33.0" +log = "0.4.22" +once_cell = "1.20.2" +psutil = "3.3.0" serde = { version = "1.0.215", features = ["derive"] } +serde_json = { version = "1.0.133", features = ["indexmap"] } +sysinfo = "0.33.0" +tokio = { version = "1.42.0", features = ["full"] } toml = { version = "0.8.19", features = ["indexmap", "preserve_order"] } -console = "0.15.8" -indexmap = { version = "2.7.0", features = ["serde"] } +xx = { version = "2", features = ["fslock", "hash"] } diff --git a/src/async_watcher.rs b/src/async_watcher.rs new file mode 100644 index 0000000..8ef5874 --- /dev/null +++ b/src/async_watcher.rs @@ -0,0 +1,39 @@ +use crate::Result; +use async_watcher::notify::RecursiveMode; +use async_watcher::{ + notify::{self, RecommendedWatcher}, + AsyncDebouncer, DebouncedEvent, +}; +use std::{path::Path, time::Duration}; +use tokio::sync::mpsc::Receiver; + +pub async fn async_debounce_watch>( + paths: Vec<(P, &str)>, +) -> Result<( + Receiver, Vec>>, + AsyncDebouncer, +)> { + let (tx, rx) = tokio::sync::mpsc::channel(100); + + let mut debouncer = + AsyncDebouncer::new(Duration::from_secs(1), Some(Duration::from_secs(1)), tx).await?; + + // add the paths to the watcher + paths.iter().for_each(|(p, rm)| { + debouncer + .watcher() + .watch( + p.as_ref(), + if *rm == "nonrecursive" { + RecursiveMode::NonRecursive + } else if *rm == "recursive" { + RecursiveMode::Recursive + } else { + unreachable!("invalid RecursiveMode") + }, + ) + .unwrap(); + }); + + Ok((rx, debouncer)) +} diff --git a/src/cli/daemon/run.rs b/src/cli/daemon/run.rs index edacc10..69b0bd7 100644 --- a/src/cli/daemon/run.rs +++ b/src/cli/daemon/run.rs @@ -2,7 +2,6 @@ use crate::pid_file::PidFile; use crate::supervisor::Supervisor; use crate::Result; use crate::{env, procs}; -use log::{warn}; #[derive(Debug, clap::Args)] pub struct Run { @@ -14,7 +13,7 @@ impl Run { pub async fn run(&self) -> Result<()> { let pid_file = PidFile::read(&*env::PITCHFORK_PID_FILE)?; if let Some(existing_pid) = pid_file.get("pitchfork") { - if self.kill_or_stop(*existing_pid)? == false { + if !(self.kill_or_stop(*existing_pid)?) { return Ok(()); } } diff --git a/src/env.rs b/src/env.rs index dbdcc11..b770aa0 100644 --- a/src/env.rs +++ b/src/env.rs @@ -2,7 +2,9 @@ use once_cell::sync::Lazy; pub use std::env::*; use std::path::PathBuf; -pub static HOME_DIR: Lazy = Lazy::new(|| dirs::home_dir().unwrap_or(PathBuf::new())); +pub static BIN_PATH: Lazy = Lazy::new(|| current_exe().unwrap()); + +pub static HOME_DIR: Lazy = Lazy::new(|| dirs::home_dir().unwrap_or_default()); pub static PITCHFORK_STATE_DIR: Lazy = Lazy::new(|| { var_path("PITCHFORK_STATE_DIR").unwrap_or( dirs::state_dir() diff --git a/src/logger.rs b/src/logger.rs index be7ab54..7758f4d 100644 --- a/src/logger.rs +++ b/src/logger.rs @@ -6,7 +6,7 @@ use std::sync::Mutex; use std::thread; use crate::{env, ui}; -use log::{warn, Level, LevelFilter, Metadata, Record}; +use log::{Level, LevelFilter, Metadata, Record}; use once_cell::sync::Lazy; #[derive(Debug)] diff --git a/src/main.rs b/src/main.rs index 87e1b33..3082caf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,12 +1,16 @@ +#[macro_use] +extern crate log; + mod cli; +mod daemon; mod env; -mod pid_file; -mod procs; mod logger; -mod ui; +mod pid_file; mod pitchfork_toml; -mod daemon; +mod procs; mod supervisor; +mod ui; +mod async_watcher; pub use eyre::Result; diff --git a/src/pid_file.rs b/src/pid_file.rs index 21acbb0..4e9eac9 100644 --- a/src/pid_file.rs +++ b/src/pid_file.rs @@ -1,31 +1,40 @@ use std::collections::BTreeMap; use std::fmt; use std::fmt::Debug; -use std::path::Path; +use std::path::{Path, PathBuf}; use crate::Result; -#[derive(Default, Debug, serde::Serialize, serde::Deserialize)] +#[derive(Debug, serde::Serialize, serde::Deserialize)] pub struct PidFile { pids: BTreeMap, + #[serde(skip)] + pub(crate) path: PathBuf, } impl PidFile { + pub fn new(path: PathBuf) -> Self { + Self { + pids: Default::default(), + path, + } + } + pub fn read>(path: P) -> Result { let path = path.as_ref(); if !path.exists() { - return Ok(Self::default()); + return Ok(Self::new(path.to_path_buf())); } let _lock = xx::fslock::get(path, false)?; let raw = xx::file::read_to_string(path)?; - let pids = toml::from_str(&raw)?; - Ok(pids) + let mut pid_file: Self = toml::from_str(&raw)?; + pid_file.path = path.to_path_buf(); + Ok(pid_file) } - pub fn write>(&self, path: P) -> Result<()> { - let path = path.as_ref(); - let _lock = xx::fslock::get(path, false)?; + pub fn write(&self) -> Result<()> { + let _lock = xx::fslock::get(&self.path, false)?; let raw = toml::to_string(self)?; - xx::file::write(path, raw)?; + xx::file::write(&self.path, raw)?; Ok(()) } diff --git a/src/supervisor.rs b/src/supervisor.rs index 1719e24..97b0710 100644 --- a/src/supervisor.rs +++ b/src/supervisor.rs @@ -1,13 +1,15 @@ +use std::path::PathBuf; use std::time::Duration; -use log::info; -use tokio::time; use crate::pid_file::PidFile; -use crate::{env, Result}; +use crate::{async_watcher, env, Result}; +use tokio::{select, time}; -pub struct Supervisor{ +pub struct Supervisor { pid_file: PidFile, } +const INTERVAL: Duration = Duration::from_secs(10); + impl Supervisor { pub fn new(pid_file: PidFile) -> Self { Self { pid_file } @@ -15,15 +17,52 @@ impl Supervisor { pub async fn start(mut self) -> Result<()> { let pid = std::process::id(); + debug!("Starting supervisor with pid {pid}"); self.pid_file.set("pitchfork".to_string(), pid); - self.pid_file.write(&*env::PITCHFORK_PID_FILE)?; - println!("Start"); + self.pid_file.write()?; + + let mut interval = time::interval(INTERVAL); - let mut interval = time::interval(Duration::from_millis(1000)); + let (mut file_events, _debouncer) = async_watcher::async_debounce_watch(vec![ + (&*env::BIN_PATH, "nonrecursive"), + (&self.pid_file.path, "nonrecursive"), + ]).await?; + self.refresh(vec![]).await?; + let mut last_run = time::Instant::now(); loop { - interval.tick().await; - info!("Daemon running"); + select! { + _ = interval.tick() => { + if last_run.elapsed() < INTERVAL { + continue; + } + if let Err(err) = self.refresh(vec![]).await { + error!("interval error: {:?}", err); + } + }, + f = file_events.recv() => { + match f { + Some(Ok(event)) => { + let paths = event.into_iter().flat_map(|e| e.event.paths).collect(); + if let Err(err) = self.refresh(paths).await { + error!("watch error: {:?}", err); + } + } + Some(Err(e)) => { + error!("watch error: {:?}", e); + } + None => { + error!("watch channel closed"); + } + } + } + } + last_run = time::Instant::now(); } } + + async fn refresh(&mut self, paths: Vec) -> Result<()> { + dbg!(paths); + Ok(()) + } }