Skip to content

Commit cf9ce9d

Browse files
authored
File watching using notify (#1310)
closes #1
1 parent 5f72137 commit cf9ce9d

File tree

6 files changed

+182
-12
lines changed

6 files changed

+182
-12
lines changed

Cargo.lock

Lines changed: 91 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ fuzzy-matcher = "0.3"
3737
gh-emoji = { version = "1.0", optional = true }
3838
itertools = "0.10"
3939
log = "0.4"
40+
notify = "5.0"
41+
notify-debouncer-mini = "0.2"
4042
once_cell = "1"
4143
rayon-core = "1.9"
4244
ron = "0.8"

deny.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ allow = [
44
"MIT",
55
"Apache-2.0",
66
"BSD-2-Clause",
7-
"BSD-3-Clause"
7+
"BSD-3-Clause",
8+
"CC0-1.0",
9+
"ISC"
810
]
911
copyleft = "warn"
1012
allow-osi-fsf-free = "neither"

src/app.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ impl App {
110110
theme: Theme,
111111
key_config: KeyConfig,
112112
) -> Self {
113-
log::trace!("open repo at: {:?}", repo);
113+
log::trace!("open repo at: {:?}", &repo);
114114

115115
let queue = Queue::new();
116116
let theme = Rc::new(theme);

src/main.rs

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,15 @@ mod strings;
3636
mod tabs;
3737
mod ui;
3838
mod version;
39+
mod watcher;
3940

4041
use crate::{app::App, args::process_cmdline};
4142
use anyhow::{bail, Result};
4243
use app::QuitState;
43-
use asyncgit::{sync::RepoPath, AsyncGitNotification};
44+
use asyncgit::{
45+
sync::{utils::repo_work_dir, RepoPath},
46+
AsyncGitNotification,
47+
};
4448
use backtrace::Backtrace;
4549
use crossbeam_channel::{tick, unbounded, Receiver, Select};
4650
use crossterm::{
@@ -67,14 +71,14 @@ use tui::{
6771
Terminal,
6872
};
6973
use ui::style::Theme;
74+
use watcher::RepoWatcher;
7075

71-
static TICK_INTERVAL: Duration = Duration::from_secs(5);
7276
static SPINNER_INTERVAL: Duration = Duration::from_millis(80);
7377

7478
///
7579
#[derive(Clone)]
7680
pub enum QueueEvent {
77-
Tick,
81+
Notify,
7882
SpinnerUpdate,
7983
AsyncEvent(AsyncNotification),
8084
InputEvent(InputEvent),
@@ -161,7 +165,8 @@ fn run_app(
161165
let (tx_app, rx_app) = unbounded();
162166

163167
let rx_input = input.receiver();
164-
let ticker = tick(TICK_INTERVAL);
168+
let watcher = RepoWatcher::new(repo_work_dir(&repo)?.as_str())?;
169+
let rx_watcher = watcher.receiver();
165170
let spinner_ticker = tick(SPINNER_INTERVAL);
166171

167172
let mut app = App::new(
@@ -179,13 +184,13 @@ fn run_app(
179184
loop {
180185
let event = if first_update {
181186
first_update = false;
182-
QueueEvent::Tick
187+
QueueEvent::Notify
183188
} else {
184189
select_event(
185190
&rx_input,
186191
&rx_git,
187192
&rx_app,
188-
&ticker,
193+
&rx_watcher,
189194
&spinner_ticker,
190195
)?
191196
};
@@ -208,7 +213,7 @@ fn run_app(
208213
}
209214
app.event(ev)?;
210215
}
211-
QueueEvent::Tick => app.update()?,
216+
QueueEvent::Notify => app.update()?,
212217
QueueEvent::AsyncEvent(ev) => {
213218
if !matches!(
214219
ev,
@@ -282,15 +287,15 @@ fn select_event(
282287
rx_input: &Receiver<InputEvent>,
283288
rx_git: &Receiver<AsyncGitNotification>,
284289
rx_app: &Receiver<AsyncAppNotification>,
285-
rx_ticker: &Receiver<Instant>,
290+
rx_notify: &Receiver<()>,
286291
rx_spinner: &Receiver<Instant>,
287292
) -> Result<QueueEvent> {
288293
let mut sel = Select::new();
289294

290295
sel.recv(rx_input);
291296
sel.recv(rx_git);
292297
sel.recv(rx_app);
293-
sel.recv(rx_ticker);
298+
sel.recv(rx_notify);
294299
sel.recv(rx_spinner);
295300

296301
let oper = sel.select();
@@ -304,7 +309,7 @@ fn select_event(
304309
2 => oper.recv(rx_app).map(|e| {
305310
QueueEvent::AsyncEvent(AsyncNotification::App(e))
306311
}),
307-
3 => oper.recv(rx_ticker).map(|_| QueueEvent::Tick),
312+
3 => oper.recv(rx_notify).map(|_| QueueEvent::Notify),
308313
4 => oper.recv(rx_spinner).map(|_| QueueEvent::SpinnerUpdate),
309314
_ => bail!("unknown select source"),
310315
}?;

src/watcher.rs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
use anyhow::Result;
2+
use crossbeam_channel::{unbounded, Sender};
3+
use notify::{Error, RecommendedWatcher, RecursiveMode};
4+
use notify_debouncer_mini::{
5+
new_debouncer, DebouncedEvent, Debouncer,
6+
};
7+
use std::{
8+
path::Path, sync::mpsc::RecvError, thread, time::Duration,
9+
};
10+
11+
pub struct RepoWatcher {
12+
receiver: crossbeam_channel::Receiver<()>,
13+
#[allow(dead_code)]
14+
debouncer: Debouncer<RecommendedWatcher>,
15+
}
16+
17+
impl RepoWatcher {
18+
pub fn new(workdir: &str) -> Result<Self> {
19+
let (tx, rx) = std::sync::mpsc::channel();
20+
21+
let mut debouncer =
22+
new_debouncer(Duration::from_secs(2), None, tx)?;
23+
24+
debouncer
25+
.watcher()
26+
.watch(Path::new(workdir), RecursiveMode::Recursive)?;
27+
28+
let (out_tx, out_rx) = unbounded();
29+
30+
thread::spawn(move || {
31+
if let Err(e) = Self::forwarder(&rx, &out_tx) {
32+
//maybe we need to restart the forwarder now?
33+
log::error!("notify receive error: {}", e);
34+
}
35+
});
36+
37+
Ok(Self {
38+
debouncer,
39+
receiver: out_rx,
40+
})
41+
}
42+
43+
///
44+
pub fn receiver(&self) -> crossbeam_channel::Receiver<()> {
45+
self.receiver.clone()
46+
}
47+
48+
fn forwarder(
49+
receiver: &std::sync::mpsc::Receiver<
50+
Result<Vec<DebouncedEvent>, Vec<Error>>,
51+
>,
52+
sender: &Sender<()>,
53+
) -> Result<(), RecvError> {
54+
loop {
55+
let ev = receiver.recv()?;
56+
57+
if let Ok(ev) = ev {
58+
log::debug!("notify events: {}", ev.len());
59+
60+
for (idx, ev) in ev.iter().enumerate() {
61+
log::debug!("notify [{}]: {:?}", idx, ev);
62+
}
63+
64+
if !ev.is_empty() {
65+
sender.send(()).expect("send error");
66+
}
67+
}
68+
}
69+
}
70+
}

0 commit comments

Comments
 (0)