From 03d6d4846ffcd29e589fdecaddbc749217761bfe Mon Sep 17 00:00:00 2001 From: Mica White Date: Fri, 3 Apr 2026 19:45:17 -0400 Subject: Queue autosaves --- Cargo.lock | 80 ++++++++++++++++++++++++++++++++ Cargo.toml | 1 + src/bin/git-autosave-daemon.rs | 101 +++++++++++++++++++++++++++++++++++++---- 3 files changed, 174 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fee88d8..ecdfdfa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -443,6 +443,7 @@ dependencies = [ "log", "notify", "notify-debouncer-full", + "ping", "serde", "thiserror", "uuid", @@ -1016,6 +1017,17 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +[[package]] +name = "ping" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "044b1fa4f259f4df9ad5078e587b208f5d288a25407575fcddb9face30c7c692" +dependencies = [ + "rand", + "socket2", + "thiserror", +] + [[package]] name = "pkg-config" version = "0.3.32" @@ -1046,6 +1058,15 @@ dependencies = [ "zerovec", ] +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + [[package]] name = "prettyplease" version = "0.2.37" @@ -1086,6 +1107,35 @@ version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + [[package]] name = "redox_syscall" version = "0.5.18" @@ -1279,6 +1329,16 @@ version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +[[package]] +name = "socket2" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + [[package]] name = "stable_deref_trait" version = "1.2.1" @@ -1948,6 +2008,26 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zerocopy" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "zerofrom" version = "0.1.6" diff --git a/Cargo.toml b/Cargo.toml index 4bf7bdd..95a7808 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,3 +20,4 @@ colog = "1" hostname = "0.4" inquire = "0.9" auth-git2 = "0.5" +ping = "0.7" diff --git a/src/bin/git-autosave-daemon.rs b/src/bin/git-autosave-daemon.rs index 80edb34..d8c23fc 100644 --- a/src/bin/git-autosave-daemon.rs +++ b/src/bin/git-autosave-daemon.rs @@ -1,4 +1,8 @@ -use std::{collections::HashSet, time::Duration}; +use std::collections::HashSet; +use std::path::PathBuf; +use std::sync::mpsc::Receiver; +use std::sync::mpsc::Sender; +use std::time::Duration; use auth_git2::GitAuthenticator; use git_autosave::{Config, authenticate::Inquirer, commit_autosave, push_autosaves}; @@ -12,7 +16,67 @@ struct ConfigWatcher { repo_watcher: &'static mut Debouncer, } -struct Watcher(&'static Mutex); +struct RepoWatcher { + config: &'static Mutex, + push_queue: Sender, +} + +fn push_queue( + consumer: Receiver, + config: &'static Mutex, + mut key: ThreadKey, +) -> ! { + let mut queue = HashSet::new(); + + loop { + while ping::new("8.8.8.8".parse().unwrap()) + .socket_type(ping::SocketType::DGRAM) + .timeout(Duration::from_secs(60)) + .ttl(128) + .send() + .is_err() + { + let mut added_item = false; + while let Ok(workdir) = consumer.try_recv() { + added_item = true; + queue.insert(workdir); + } + if added_item { + log::warn!( + "There is no Internet connection, so pushes to the repository have been queued" + ); + } + std::thread::yield_now(); + } + + while let Ok(workdir) = consumer.try_recv() { + queue.insert(workdir); + } + + config.scoped_lock(&mut key, |config| { + for workdir in &queue { + log::info!("Pushing {}...", workdir.to_string_lossy()); + let Ok(repository) = Repository::open(workdir) else { + log::error!("Failed to open repository: {}", workdir.to_string_lossy()); + continue; + }; + let Ok(gitconfig) = repository.config() else { + log::error!("Failed to load gitconfig for repository: {:?}", workdir); + return; + }; + let auth = GitAuthenticator::new().set_prompter(Inquirer(&*config)); + let mut callbacks = RemoteCallbacks::new(); + callbacks.credentials(auth.credentials(&gitconfig)); + if let Err(e) = push_autosaves(&repository, callbacks) { + log::error!("Failed to push autosaves: {e}"); + } + + log::info!("Successfully pushed {}", workdir.to_string_lossy()); + } + }); + queue.clear(); + } +} impl DebounceEventHandler for ConfigWatcher { fn handle_event(&mut self, events: DebounceEventResult) { @@ -68,7 +132,7 @@ impl DebounceEventHandler for ConfigWatcher } } -impl DebounceEventHandler for Watcher { +impl DebounceEventHandler for RepoWatcher { fn handle_event(&mut self, events: DebounceEventResult) { let Some(mut key) = ThreadKey::get() else { log::error!("Failed to acquire thread key when autosaving repository. This is a bug!"); @@ -134,7 +198,7 @@ impl DebounceEventHandler for Watcher { workdirs_to_autosave.insert(workdir.to_path_buf()); repositories_to_autosave.push(repository); } - self.0.scoped_lock(&mut key, |config| { + self.config.scoped_lock(&mut key, |config| { for repository in repositories_to_autosave { let workdir = repository .workdir() @@ -152,8 +216,11 @@ impl DebounceEventHandler for Watcher { if let Err(e) = commit_autosave(&repository) { log::error!("Failed to commit autosave: {e}"); } - if let Err(e) = push_autosaves(&repository, callbacks) { - log::error!("Failed to push autosaves: {e}"); + if let Err(e) = self + .push_queue + .send(repository.workdir().unwrap().to_path_buf()) + { + log::error!("Failed to add repository to push queue: {e}"); } log::info!("Successfully autosaved {:?}", workdir); @@ -163,18 +230,36 @@ impl DebounceEventHandler for Watcher { } fn main() -> Result<(), anyhow::Error> { - let key = ThreadKey::get().expect("Could not get ThreadKey on startup. This is a bug!"); + let mut key = ThreadKey::get().expect("Could not get ThreadKey on startup. This is a bug!"); colog::init(); log::info!("Loading autosave config..."); let config: &'static Mutex = Box::leak(Box::new(Mutex::new(Config::load()?))); log::info!("Loaded autosave config"); + log::info!("Starting push queue..."); + let (sender, receiver) = std::sync::mpsc::channel(); + std::thread::spawn(move || { + let key = ThreadKey::get().unwrap(); + push_queue(receiver, config, key); + }); + config.scoped_lock(&mut key, |config| { + for repository in config.repositories() { + if let Err(e) = sender.send(repository.clone()) { + log::error!("Failed to queue {}: {e}", repository.to_string_lossy()); + } + } + }); + log::info!("Started push queue"); + log::info!("Starting repository watcher..."); let repo_watcher = Box::leak(Box::new(notify_debouncer_full::new_debouncer( Duration::from_secs(5), None, - Watcher(config), + RepoWatcher { + config, + push_queue: sender, + }, )?)); config.scoped_lock(key, |config| { log::info!("Adding repositories to watch..."); -- cgit v1.2.3