MacOS file system event handling using FSEvents backend

This commit is contained in:
Ericson "Fogo" Soares 2023-01-13 15:55:16 -03:00
parent a18b2f8c8b
commit 4638f104be
2 changed files with 94 additions and 154 deletions

View file

@ -3,24 +3,22 @@ use crate::{
location::{indexer::indexer_job::indexer_job_location, manager::LocationManagerError}, location::{indexer::indexer_job::indexer_job_location, manager::LocationManagerError},
}; };
use std::{future::Future, time::Duration};
use async_trait::async_trait; use async_trait::async_trait;
use notify::{ use notify::{
event::{CreateKind, DataChange, ModifyKind, RenameMode}, event::{CreateKind, DataChange, ModifyKind, RenameMode},
Event, EventKind, Event, EventKind,
}; };
use tokio::{fs, select, spawn, sync::oneshot, time::sleep}; use tokio::{fs, io};
use tracing::{trace, warn}; use tracing::trace;
use super::{ use super::{
utils::{create_dir, create_file, remove_event, rename, update_file}, utils::{create_dir, file_creation_or_update, remove_event, rename},
EventHandler, EventHandler,
}; };
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub(super) struct MacOsEventHandler { pub(super) struct MacOsEventHandler {
maybe_rename_sender: Option<oneshot::Sender<Event>>, rename_stack: Option<Event>,
} }
#[async_trait] #[async_trait]
@ -41,60 +39,27 @@ impl EventHandler for MacOsEventHandler {
trace!("Received MacOS event: {:#?}", event); trace!("Received MacOS event: {:#?}", event);
match event.kind { match event.kind {
EventKind::Create(create_kind) => match create_kind { EventKind::Create(CreateKind::Folder) => {
CreateKind::File => { create_dir(location, event, library_ctx.clone()).await?;
let (maybe_rename_tx, maybe_rename_rx) = oneshot::channel(); }
spawn(wait_to_create( EventKind::Modify(ModifyKind::Data(DataChange::Content)) => {
location, // If a file had its content modified, then it was updated or created
event, file_creation_or_update(location, event, library_ctx).await?;
library_ctx.clone(), }
create_file, EventKind::Modify(ModifyKind::Name(RenameMode::Any)) => {
maybe_rename_rx, match self.rename_stack.take() {
)); None => {
self.maybe_rename_sender = Some(maybe_rename_tx); self.rename_stack = Some(event);
}
CreateKind::Folder => {
let (maybe_rename_tx, maybe_rename_rx) = oneshot::channel();
spawn(wait_to_create(
location,
event,
library_ctx.clone(),
create_dir,
maybe_rename_rx,
));
self.maybe_rename_sender = Some(maybe_rename_tx);
}
other => {
trace!("Ignoring other create event: {:#?}", other);
}
},
EventKind::Modify(ref modify_kind) => match modify_kind {
ModifyKind::Data(DataChange::Any) => {
if fs::metadata(&event.paths[0]).await?.is_file() {
update_file(location, event, library_ctx).await?;
} else {
trace!("Unexpected MacOS modify event on a directory");
} }
Some(from_event) => {
rename(&event.paths[0], &from_event.paths[0], location, library_ctx)
.await?;
}
}
}
// We ignore EventKind::Modify(ModifyKind::Data(DataChange::Any)) for directories
// as they're also used for removing files and directories, being emitted
// on the parent directory in this case
}
ModifyKind::Name(RenameMode::Any) => {
if let Some(rename_sender) = self.maybe_rename_sender.take() {
if !rename_sender.is_closed() && rename_sender.send(event).is_err() {
warn!("Failed to send rename event");
}
}
}
other => {
trace!("Ignoring other modify event: {:#?}", other);
}
},
EventKind::Remove(remove_kind) => { EventKind::Remove(remove_kind) => {
remove_event(location, event, remove_kind, library_ctx).await?; remove_event(location, event, remove_kind, library_ctx).await?;
// An EventKind::Modify(ModifyKind::Data(DataChange::Any)) - On parent directory
// is also emitted, but we can ignore it.
} }
other_event_kind => { other_event_kind => {
trace!("Other MacOS event that we don't handle for now: {other_event_kind:#?}"); trace!("Other MacOS event that we don't handle for now: {other_event_kind:#?}");
@ -104,27 +69,3 @@ impl EventHandler for MacOsEventHandler {
Ok(()) Ok(())
} }
} }
// FIX-ME: Had some troubles with borrowck, to receive a
// impl FnOnce(indexer_job_location::Data, Event, &LibraryContext) -> Fut
// as a parameter, had to move LibraryContext into the functions
async fn wait_to_create<Fut>(
location: indexer_job_location::Data,
event: Event,
library_ctx: LibraryContext,
create_fn: impl FnOnce(indexer_job_location::Data, Event, LibraryContext) -> Fut,
maybe_rename_rx: oneshot::Receiver<Event>,
) -> Result<(), LocationManagerError>
where
Fut: for<'r> Future<Output = Result<(), LocationManagerError>>,
{
select! {
() = sleep(Duration::from_secs(1)) => {
create_fn(location, event, library_ctx).await
},
Ok(rename_event) = maybe_rename_rx => {
trace!("Renaming file or directory instead of creating a new one");
rename(&event.paths[0], &rename_event.paths[0], location, &library_ctx).await
}
}
}

View file

@ -252,78 +252,77 @@ impl Drop for LocationWatcher {
} }
/*************************************************************************************************** /***************************************************************************************************
* Some tests to validate our assumptions of events through different file systems * * Some tests to validate our assumptions of events through different file systems *
*************************************************************************************************** ***************************************************************************************************
* Events dispatched on Linux: * * Events dispatched on Linux: *
* Create File: * * Create File: *
* 1) EventKind::Create(CreateKind::File) * * 1) EventKind::Create(CreateKind::File) *
* 2) EventKind::Modify(ModifyKind::Metadata(MetadataKind::Any)) * * 2) EventKind::Modify(ModifyKind::Metadata(MetadataKind::Any)) *
* or EventKind::Modify(ModifyKind::Data(DataChange::Any)) * * or EventKind::Modify(ModifyKind::Data(DataChange::Any)) *
* 3) EventKind::Access(AccessKind::Close(AccessMode::Write))) * * 3) EventKind::Access(AccessKind::Close(AccessMode::Write))) *
* Create Directory: * * Create Directory: *
* 1) EventKind::Create(CreateKind::Folder) * * 1) EventKind::Create(CreateKind::Folder) *
* Update File: * * Update File: *
* 1) EventKind::Modify(ModifyKind::Data(DataChange::Any)) * * 1) EventKind::Modify(ModifyKind::Data(DataChange::Any)) *
* 2) EventKind::Access(AccessKind::Close(AccessMode::Write))) * * 2) EventKind::Access(AccessKind::Close(AccessMode::Write))) *
* Update File (rename): * * Update File (rename): *
* 1) EventKind::Modify(ModifyKind::Name(RenameMode::From)) * * 1) EventKind::Modify(ModifyKind::Name(RenameMode::From)) *
* 2) EventKind::Modify(ModifyKind::Name(RenameMode::To)) * * 2) EventKind::Modify(ModifyKind::Name(RenameMode::To)) *
* 3) EventKind::Modify(ModifyKind::Name(RenameMode::Both)) * * 3) EventKind::Modify(ModifyKind::Name(RenameMode::Both)) *
* Update Directory (rename): * * Update Directory (rename): *
* 1) EventKind::Modify(ModifyKind::Name(RenameMode::From)) * * 1) EventKind::Modify(ModifyKind::Name(RenameMode::From)) *
* 2) EventKind::Modify(ModifyKind::Name(RenameMode::To)) * * 2) EventKind::Modify(ModifyKind::Name(RenameMode::To)) *
* 3) EventKind::Modify(ModifyKind::Name(RenameMode::Both)) * * 3) EventKind::Modify(ModifyKind::Name(RenameMode::Both)) *
* Delete File: * * Delete File: *
* 1) EventKind::Remove(RemoveKind::File) * * 1) EventKind::Remove(RemoveKind::File) *
* Delete Directory: * * Delete Directory: *
* 1) EventKind::Remove(RemoveKind::Folder) * * 1) EventKind::Remove(RemoveKind::Folder) *
* * * *
* Events dispatched on MacOS: * * Events dispatched on MacOS: *
* Create File: * * Create File: *
* 1) EventKind::Create(CreateKind::File) * * 1) EventKind::Create(CreateKind::File) *
* Create Directory: * * 2) EventKind::Modify(ModifyKind::Data(DataChange::Content)) *
* 1) EventKind::Create(CreateKind::Folder) * * Create Directory: *
* Update File: * * 1) EventKind::Create(CreateKind::Folder) *
* 1) EventKind::Modify(ModifyKind::Data(DataChange::Any)) * * Update File: *
* Update File (rename): * * 1) EventKind::Modify(ModifyKind::Data(DataChange::Content)) *
* 1) EventKind::Create(CreateKind::File) * * Update File (rename): *
* 2) EventKind::Modify(ModifyKind::Name(RenameMode::Any)) * * 1) EventKind::Modify(ModifyKind::Name(RenameMode::Any)) -- From *
* Update Directory (rename): * * 2) EventKind::Modify(ModifyKind::Name(RenameMode::Any)) -- To *
* 1) EventKind::Create(CreateKind::Folder) * * Update Directory (rename): *
* 2) EventKind::Modify(ModifyKind::Name(RenameMode::Any)) * * 1) EventKind::Modify(ModifyKind::Name(RenameMode::Any)) -- From *
* Delete File: * * 2) EventKind::Modify(ModifyKind::Name(RenameMode::Any)) -- To *
* 1) EventKind::Remove(RemoveKind::Any) * * Delete File: *
* 2) EventKind::Modify(ModifyKind::Data(DataChange::Any)) - On parent directory * * 1) EventKind::Remove(RemoveKind::File) *
* Delete Directory: * * Delete Directory: *
* 1) EventKind::Remove(RemoveKind::Any) * * 1) EventKind::Remove(RemoveKind::Folder) *
* 2) EventKind::Modify(ModifyKind::Data(DataChange::Any)) - On parent directory * * *
* * * Events dispatched on Windows: *
* Events dispatched on Windows: * * Create File: *
* Create File: * * 1) EventKind::Create(CreateKind::Any) *
* 1) EventKind::Create(CreateKind::Any) * * 2) EventKind::Modify(ModifyKind::Any) *
* 2) EventKind::Modify(ModifyKind::Any) * * Create Directory: *
* Create Directory: * * 1) EventKind::Create(CreateKind::Any) *
* 1) EventKind::Create(CreateKind::Any) * * Update File: *
* Update File: * * 1) EventKind::Modify(ModifyKind::Any) *
* 1) EventKind::Modify(ModifyKind::Any) * * Update File (rename): *
* Update File (rename): * * 1) EventKind::Modify(ModifyKind::Name(RenameMode::From)) *
* 1) EventKind::Modify(ModifyKind::Name(RenameMode::From)) * * 2) EventKind::Modify(ModifyKind::Name(RenameMode::To)) *
* 2) EventKind::Modify(ModifyKind::Name(RenameMode::To)) * * Update Directory (rename): *
* Update Directory (rename): * * 1) EventKind::Modify(ModifyKind::Name(RenameMode::From)) *
* 1) EventKind::Modify(ModifyKind::Name(RenameMode::From)) * * 2) EventKind::Modify(ModifyKind::Name(RenameMode::To)) *
* 2) EventKind::Modify(ModifyKind::Name(RenameMode::To)) * * Delete File: *
* Delete File: * * 1) EventKind::Remove(RemoveKind::Any) *
* 1) EventKind::Remove(RemoveKind::Any) * * Delete Directory: *
* Delete Directory: * * 1) EventKind::Remove(RemoveKind::Any) *
* 1) EventKind::Remove(RemoveKind::Any) * * *
* * * Events dispatched on Android: *
* Events dispatched on Android: * * TODO *
* TODO * * *
* * * Events dispatched on iOS: *
* Events dispatched on iOS: * * TODO *
* TODO * * *
* * **************************************************************************************************/
**************************************************************************************************/
#[cfg(test)] #[cfg(test)]
#[allow(unused)] #[allow(unused)]
mod tests { mod tests {
@ -376,8 +375,8 @@ mod tests {
Ok(maybe_event) => { Ok(maybe_event) => {
let event = maybe_event.expect("Failed to receive event"); let event = maybe_event.expect("Failed to receive event");
debug!("Received event: {event:#?}"); debug!("Received event: {event:#?}");
// In case of file creation, we expect to see an close event on write mode // Using `ends_with` here due to a weird edge case on CI tests at MacOS
if event.paths[0] == path && event.kind == expected_event { if event.paths[0].ends_with(path) && event.kind == expected_event {
debug!("Received expected event: {expected_event:#?}"); debug!("Received expected event: {expected_event:#?}");
break; break;
} }