File watching using notify (#1310)

closes #1
This commit is contained in:
extrawurst 2022-09-02 09:59:13 +02:00 committed by GitHub
parent 5f7213730b
commit cf9ce9d4ff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 182 additions and 12 deletions

91
Cargo.lock generated
View File

@ -455,6 +455,18 @@ dependencies = [
"instant",
]
[[package]]
name = "filetime"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e94a7bbaa59354bc20dd75b67f23e2797b4490e9d6928203fb105c79e448c86c"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"windows-sys",
]
[[package]]
name = "filetreelist"
version = "0.5.0"
@ -502,6 +514,15 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "fsevent-sys"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2"
dependencies = [
"libc",
]
[[package]]
name = "futures"
version = "0.3.24"
@ -673,6 +694,8 @@ dependencies = [
"gh-emoji",
"itertools",
"log",
"notify",
"notify-debouncer-mini",
"once_cell",
"pprof",
"pretty_assertions",
@ -764,6 +787,26 @@ dependencies = [
"str_stack",
]
[[package]]
name = "inotify"
version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff"
dependencies = [
"bitflags",
"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 = "instant"
version = "0.1.12"
@ -816,6 +859,26 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "kqueue"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d6112e8f37b59803ac47a42d14f1f3a59bbf72fc6857ffc5be455e28a691f8e"
dependencies = [
"kqueue-sys",
"libc",
]
[[package]]
name = "kqueue-sys"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8367585489f01bc55dd27404dcf56b95e6da061a256a666ab23be9ba96a2e587"
dependencies = [
"bitflags",
"libc",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
@ -955,6 +1018,34 @@ version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb"
[[package]]
name = "notify"
version = "5.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed2c66da08abae1c024c01d635253e402341b4060a12e99b31c7594063bf490a"
dependencies = [
"bitflags",
"crossbeam-channel",
"filetime",
"fsevent-sys",
"inotify",
"kqueue",
"libc",
"mio",
"walkdir",
"winapi",
]
[[package]]
name = "notify-debouncer-mini"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c538ea1dd436b41e751922510cfbcaea2def87ed6ed94aa1edc15dc31b4c179"
dependencies = [
"crossbeam-channel",
"notify",
]
[[package]]
name = "num-format"
version = "0.4.0"

View File

@ -37,6 +37,8 @@ fuzzy-matcher = "0.3"
gh-emoji = { version = "1.0", optional = true }
itertools = "0.10"
log = "0.4"
notify = "5.0"
notify-debouncer-mini = "0.2"
once_cell = "1"
rayon-core = "1.9"
ron = "0.8"

View File

@ -4,7 +4,9 @@ allow = [
"MIT",
"Apache-2.0",
"BSD-2-Clause",
"BSD-3-Clause"
"BSD-3-Clause",
"CC0-1.0",
"ISC"
]
copyleft = "warn"
allow-osi-fsf-free = "neither"

View File

@ -110,7 +110,7 @@ impl App {
theme: Theme,
key_config: KeyConfig,
) -> Self {
log::trace!("open repo at: {:?}", repo);
log::trace!("open repo at: {:?}", &repo);
let queue = Queue::new();
let theme = Rc::new(theme);

View File

@ -36,11 +36,15 @@ mod strings;
mod tabs;
mod ui;
mod version;
mod watcher;
use crate::{app::App, args::process_cmdline};
use anyhow::{bail, Result};
use app::QuitState;
use asyncgit::{sync::RepoPath, AsyncGitNotification};
use asyncgit::{
sync::{utils::repo_work_dir, RepoPath},
AsyncGitNotification,
};
use backtrace::Backtrace;
use crossbeam_channel::{tick, unbounded, Receiver, Select};
use crossterm::{
@ -67,14 +71,14 @@ use tui::{
Terminal,
};
use ui::style::Theme;
use watcher::RepoWatcher;
static TICK_INTERVAL: Duration = Duration::from_secs(5);
static SPINNER_INTERVAL: Duration = Duration::from_millis(80);
///
#[derive(Clone)]
pub enum QueueEvent {
Tick,
Notify,
SpinnerUpdate,
AsyncEvent(AsyncNotification),
InputEvent(InputEvent),
@ -161,7 +165,8 @@ fn run_app(
let (tx_app, rx_app) = unbounded();
let rx_input = input.receiver();
let ticker = tick(TICK_INTERVAL);
let watcher = RepoWatcher::new(repo_work_dir(&repo)?.as_str())?;
let rx_watcher = watcher.receiver();
let spinner_ticker = tick(SPINNER_INTERVAL);
let mut app = App::new(
@ -179,13 +184,13 @@ fn run_app(
loop {
let event = if first_update {
first_update = false;
QueueEvent::Tick
QueueEvent::Notify
} else {
select_event(
&rx_input,
&rx_git,
&rx_app,
&ticker,
&rx_watcher,
&spinner_ticker,
)?
};
@ -208,7 +213,7 @@ fn run_app(
}
app.event(ev)?;
}
QueueEvent::Tick => app.update()?,
QueueEvent::Notify => app.update()?,
QueueEvent::AsyncEvent(ev) => {
if !matches!(
ev,
@ -282,7 +287,7 @@ fn select_event(
rx_input: &Receiver<InputEvent>,
rx_git: &Receiver<AsyncGitNotification>,
rx_app: &Receiver<AsyncAppNotification>,
rx_ticker: &Receiver<Instant>,
rx_notify: &Receiver<()>,
rx_spinner: &Receiver<Instant>,
) -> Result<QueueEvent> {
let mut sel = Select::new();
@ -290,7 +295,7 @@ fn select_event(
sel.recv(rx_input);
sel.recv(rx_git);
sel.recv(rx_app);
sel.recv(rx_ticker);
sel.recv(rx_notify);
sel.recv(rx_spinner);
let oper = sel.select();
@ -304,7 +309,7 @@ fn select_event(
2 => oper.recv(rx_app).map(|e| {
QueueEvent::AsyncEvent(AsyncNotification::App(e))
}),
3 => oper.recv(rx_ticker).map(|_| QueueEvent::Tick),
3 => oper.recv(rx_notify).map(|_| QueueEvent::Notify),
4 => oper.recv(rx_spinner).map(|_| QueueEvent::SpinnerUpdate),
_ => bail!("unknown select source"),
}?;

70
src/watcher.rs Normal file
View File

@ -0,0 +1,70 @@
use anyhow::Result;
use crossbeam_channel::{unbounded, Sender};
use notify::{Error, RecommendedWatcher, RecursiveMode};
use notify_debouncer_mini::{
new_debouncer, DebouncedEvent, Debouncer,
};
use std::{
path::Path, sync::mpsc::RecvError, thread, time::Duration,
};
pub struct RepoWatcher {
receiver: crossbeam_channel::Receiver<()>,
#[allow(dead_code)]
debouncer: Debouncer<RecommendedWatcher>,
}
impl RepoWatcher {
pub fn new(workdir: &str) -> Result<Self> {
let (tx, rx) = std::sync::mpsc::channel();
let mut debouncer =
new_debouncer(Duration::from_secs(2), None, tx)?;
debouncer
.watcher()
.watch(Path::new(workdir), RecursiveMode::Recursive)?;
let (out_tx, out_rx) = unbounded();
thread::spawn(move || {
if let Err(e) = Self::forwarder(&rx, &out_tx) {
//maybe we need to restart the forwarder now?
log::error!("notify receive error: {}", e);
}
});
Ok(Self {
debouncer,
receiver: out_rx,
})
}
///
pub fn receiver(&self) -> crossbeam_channel::Receiver<()> {
self.receiver.clone()
}
fn forwarder(
receiver: &std::sync::mpsc::Receiver<
Result<Vec<DebouncedEvent>, Vec<Error>>,
>,
sender: &Sender<()>,
) -> Result<(), RecvError> {
loop {
let ev = receiver.recv()?;
if let Ok(ev) = ev {
log::debug!("notify events: {}", ev.len());
for (idx, ev) in ev.iter().enumerate() {
log::debug!("notify [{}]: {:?}", idx, ev);
}
if !ev.is_empty() {
sender.send(()).expect("send error");
}
}
}
}
}