b5c8b69547
* Experiment with serde * Framed encoding serde POC * Outfox framing * Outfox rest compat (#3333) * Outfox forwarding compat * Tidy up interface * PacketSize compat * Address PR comments * Rebase on develop commit342883fcbeAuthor: durch <durch@users.noreply.github.com> Date: Thu Apr 27 09:17:18 2023 +0200 Put back PacketType 1 commit61a0ee5a19Author: Tommy Verrall <tommyvez@protonmail.com> Date: Wed Apr 26 16:37:29 2023 +0100 change output for cpu-cycle management logs commit3956109c7eAuthor: Tommy Verrall <tommy@nymtech.net> Date: Wed Apr 26 12:13:22 2023 +0100 change the workflow file to build with cpucycles commit8d725b13c5Author: durch <durch@users.noreply.github.com> Date: Mon Apr 24 13:14:58 2023 +0200 Outfox client compat commit4d166c389bAuthor: durch <durch@users.noreply.github.com> Date: Fri Apr 21 00:30:46 2023 +0200 Address PR comments commit145c3c1223Author: durch <durch@users.noreply.github.com> Date: Fri Apr 21 00:12:35 2023 +0200 Rename PacketMode commitcbd654d6fdAuthor: Drazen Urch <drazen@urch.eu> Date: Thu Apr 20 23:59:40 2023 +0200 Outfox rest compat (#3333) * Outfox forwarding compat * Tidy up interface * PacketSize compat commite7be91a94cAuthor: durch <durch@users.noreply.github.com> Date: Wed Apr 19 16:36:48 2023 +0200 Remove serde cruft commit582e7d566aAuthor: durch <durch@users.noreply.github.com> Date: Wed Apr 19 16:24:09 2023 +0200 Outfox framing commit6464da5f01Author: durch <durch@users.noreply.github.com> Date: Tue Apr 18 22:23:02 2023 +0200 Framing compat commitd5e77e499bAuthor: durch <durch@users.noreply.github.com> Date: Tue Apr 18 18:18:54 2023 +0200 Framed encoding serde POC commitf086f9c35aAuthor: durch <durch@users.noreply.github.com> Date: Tue Apr 18 16:54:21 2023 +0200 Experiment with serde * Client tweaks * Speed up from_plaintext * SurbAcks * More work on the reciever end, and outfox format * Cleanup and fmt * Wrap up rebase * Happy clippy * Fix lock files * Final cleanup
159 lines
5.1 KiB
Rust
159 lines
5.1 KiB
Rust
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
use futures::channel::mpsc;
|
|
use futures::StreamExt;
|
|
use notify::event::{DataChange, MetadataKind, ModifyKind};
|
|
use notify::{Config, Event, EventKind, RecommendedWatcher, RecursiveMode, Watcher};
|
|
use std::collections::HashMap;
|
|
use std::path::{Path, PathBuf};
|
|
use std::time::Duration;
|
|
use tokio::time::Instant;
|
|
|
|
pub type FileWatcherEventSender = mpsc::UnboundedSender<Event>;
|
|
pub type FileWatcherEventReceiver = mpsc::UnboundedReceiver<Event>;
|
|
|
|
/// Simple file watcher that sends a notification whenever there was any changed in the watched file.
|
|
pub struct AsyncFileWatcher {
|
|
path: PathBuf,
|
|
watcher: RecommendedWatcher,
|
|
is_watching: bool,
|
|
filters: Option<Vec<EventKind>>,
|
|
last_received: HashMap<EventKind, Instant>,
|
|
tick_duration: Duration,
|
|
|
|
inner_rx: mpsc::UnboundedReceiver<notify::Result<Event>>,
|
|
event_sender: FileWatcherEventSender,
|
|
}
|
|
|
|
impl AsyncFileWatcher {
|
|
pub fn new_file_changes_watcher<P: AsRef<Path>>(
|
|
path: P,
|
|
event_sender: FileWatcherEventSender,
|
|
) -> notify::Result<Self> {
|
|
Self::new(
|
|
path,
|
|
event_sender,
|
|
Some(vec![
|
|
EventKind::Modify(ModifyKind::Data(DataChange::Content)),
|
|
EventKind::Modify(ModifyKind::Data(DataChange::Any)),
|
|
EventKind::Modify(ModifyKind::Metadata(MetadataKind::Any)),
|
|
]),
|
|
None,
|
|
)
|
|
}
|
|
|
|
pub fn new<P: AsRef<Path>>(
|
|
path: P,
|
|
event_sender: FileWatcherEventSender,
|
|
filters: Option<Vec<EventKind>>,
|
|
tick_duration: Option<Duration>,
|
|
) -> notify::Result<Self> {
|
|
let watcher_config = Config::default();
|
|
let (inner_tx, inner_rx) = mpsc::unbounded();
|
|
let watcher = RecommendedWatcher::new(
|
|
move |res| {
|
|
if let Err(_err) = inner_tx.unbounded_send(res) {
|
|
// I guess it's theoretically possible during shutdown?
|
|
log::error!(
|
|
"failed to send watched file event - the received must have been dropped!"
|
|
);
|
|
}
|
|
},
|
|
watcher_config,
|
|
)?;
|
|
|
|
Ok(AsyncFileWatcher {
|
|
path: path.as_ref().to_path_buf(),
|
|
watcher,
|
|
is_watching: false,
|
|
filters,
|
|
last_received: HashMap::new(),
|
|
tick_duration: tick_duration.unwrap_or(Duration::from_secs(5)),
|
|
inner_rx,
|
|
event_sender,
|
|
})
|
|
}
|
|
|
|
pub fn with_filters(mut self, filters: Option<Vec<EventKind>>) -> Self {
|
|
self.filters = filters;
|
|
self
|
|
}
|
|
|
|
pub fn with_filter(mut self, filter: EventKind) -> Self {
|
|
match &mut self.filters {
|
|
None => {
|
|
self.filters = Some(vec![filter]);
|
|
}
|
|
Some(filters) => filters.push(filter),
|
|
}
|
|
self
|
|
}
|
|
|
|
fn should_propagate(&self, event: &Event, now: Instant) -> bool {
|
|
// when testing I was consistently getting two `Modify(Data(Any))` events in quick succession
|
|
// (probably to modify content and metadata).
|
|
// we really only want to propagate one of them
|
|
if let Some(previous) = self.last_received.get(&event.kind) {
|
|
if now.duration_since(*previous) < self.tick_duration {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
let Some(filters) = &self.filters else {
|
|
return true
|
|
};
|
|
|
|
for filter in filters {
|
|
if &event.kind == filter {
|
|
return true;
|
|
}
|
|
}
|
|
false
|
|
}
|
|
|
|
fn start_watching(&mut self) -> notify::Result<()> {
|
|
self.is_watching = true;
|
|
self.watcher.watch(&self.path, RecursiveMode::NonRecursive)
|
|
}
|
|
|
|
fn stop_watching(&mut self) -> notify::Result<()> {
|
|
self.is_watching = false;
|
|
self.watcher.unwatch(&self.path)
|
|
}
|
|
|
|
pub async fn watch(&mut self) -> notify::Result<()> {
|
|
self.start_watching()?;
|
|
|
|
while let Some(event) = self.inner_rx.next().await {
|
|
match event {
|
|
Ok(event) => {
|
|
let now = Instant::now();
|
|
if self.should_propagate(&event, now) {
|
|
self.last_received.insert(event.kind, now);
|
|
if let Err(_err) = self.event_sender.unbounded_send(event) {
|
|
log::error!("the file watcher receiver has been dropped!");
|
|
}
|
|
} else {
|
|
log::debug!("will not propagate information about {:?}", event);
|
|
}
|
|
}
|
|
Err(err) => {
|
|
// TODO: to be determined if this should stop the whole thing or not
|
|
// (need to know what kind of errors can be returned)
|
|
log::error!(
|
|
"encountered an error while watching {:?}: {err}",
|
|
self.path.as_path()
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
self.stop_watching()
|
|
}
|
|
|
|
pub fn is_watching(&self) -> bool {
|
|
self.is_watching
|
|
}
|
|
}
|