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},
};
use std::{future::Future, time::Duration};
use async_trait::async_trait;
use notify::{
event::{CreateKind, DataChange, ModifyKind, RenameMode},
Event, EventKind,
};
use tokio::{fs, select, spawn, sync::oneshot, time::sleep};
use tracing::{trace, warn};
use tokio::{fs, io};
use tracing::trace;
use super::{
utils::{create_dir, create_file, remove_event, rename, update_file},
utils::{create_dir, file_creation_or_update, remove_event, rename},
EventHandler,
};
#[derive(Debug, Default)]
pub(super) struct MacOsEventHandler {
maybe_rename_sender: Option<oneshot::Sender<Event>>,
rename_stack: Option<Event>,
}
#[async_trait]
@ -41,60 +39,27 @@ impl EventHandler for MacOsEventHandler {
trace!("Received MacOS event: {:#?}", event);
match event.kind {
EventKind::Create(create_kind) => match create_kind {
CreateKind::File => {
let (maybe_rename_tx, maybe_rename_rx) = oneshot::channel();
spawn(wait_to_create(
location,
event,
library_ctx.clone(),
create_file,
maybe_rename_rx,
));
self.maybe_rename_sender = Some(maybe_rename_tx);
}
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");
EventKind::Create(CreateKind::Folder) => {
create_dir(location, event, library_ctx.clone()).await?;
}
EventKind::Modify(ModifyKind::Data(DataChange::Content)) => {
// If a file had its content modified, then it was updated or created
file_creation_or_update(location, event, library_ctx).await?;
}
EventKind::Modify(ModifyKind::Name(RenameMode::Any)) => {
match self.rename_stack.take() {
None => {
self.rename_stack = Some(event);
}
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) => {
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 => {
trace!("Other MacOS event that we don't handle for now: {other_event_kind:#?}");
@ -104,27 +69,3 @@ impl EventHandler for MacOsEventHandler {
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 *
***************************************************************************************************
* Events dispatched on Linux: *
* Create File: *
* 1) EventKind::Create(CreateKind::File) *
* 2) EventKind::Modify(ModifyKind::Metadata(MetadataKind::Any)) *
* or EventKind::Modify(ModifyKind::Data(DataChange::Any)) *
* 3) EventKind::Access(AccessKind::Close(AccessMode::Write))) *
* Create Directory: *
* 1) EventKind::Create(CreateKind::Folder) *
* Update File: *
* 1) EventKind::Modify(ModifyKind::Data(DataChange::Any)) *
* 2) EventKind::Access(AccessKind::Close(AccessMode::Write))) *
* Update File (rename): *
* 1) EventKind::Modify(ModifyKind::Name(RenameMode::From)) *
* 2) EventKind::Modify(ModifyKind::Name(RenameMode::To)) *
* 3) EventKind::Modify(ModifyKind::Name(RenameMode::Both)) *
* Update Directory (rename): *
* 1) EventKind::Modify(ModifyKind::Name(RenameMode::From)) *
* 2) EventKind::Modify(ModifyKind::Name(RenameMode::To)) *
* 3) EventKind::Modify(ModifyKind::Name(RenameMode::Both)) *
* Delete File: *
* 1) EventKind::Remove(RemoveKind::File) *
* Delete Directory: *
* 1) EventKind::Remove(RemoveKind::Folder) *
* *
* Events dispatched on MacOS: *
* Create File: *
* 1) EventKind::Create(CreateKind::File) *
* Create Directory: *
* 1) EventKind::Create(CreateKind::Folder) *
* Update File: *
* 1) EventKind::Modify(ModifyKind::Data(DataChange::Any)) *
* Update File (rename): *
* 1) EventKind::Create(CreateKind::File) *
* 2) EventKind::Modify(ModifyKind::Name(RenameMode::Any)) *
* Update Directory (rename): *
* 1) EventKind::Create(CreateKind::Folder) *
* 2) EventKind::Modify(ModifyKind::Name(RenameMode::Any)) *
* Delete File: *
* 1) EventKind::Remove(RemoveKind::Any) *
* 2) EventKind::Modify(ModifyKind::Data(DataChange::Any)) - On parent directory *
* Delete Directory: *
* 1) EventKind::Remove(RemoveKind::Any) *
* 2) EventKind::Modify(ModifyKind::Data(DataChange::Any)) - On parent directory *
* *
* Events dispatched on Windows: *
* Create File: *
* 1) EventKind::Create(CreateKind::Any) *
* 2) EventKind::Modify(ModifyKind::Any) *
* Create Directory: *
* 1) EventKind::Create(CreateKind::Any) *
* Update File: *
* 1) EventKind::Modify(ModifyKind::Any) *
* Update File (rename): *
* 1) EventKind::Modify(ModifyKind::Name(RenameMode::From)) *
* 2) EventKind::Modify(ModifyKind::Name(RenameMode::To)) *
* Update Directory (rename): *
* 1) EventKind::Modify(ModifyKind::Name(RenameMode::From)) *
* 2) EventKind::Modify(ModifyKind::Name(RenameMode::To)) *
* Delete File: *
* 1) EventKind::Remove(RemoveKind::Any) *
* Delete Directory: *
* 1) EventKind::Remove(RemoveKind::Any) *
* *
* Events dispatched on Android: *
* TODO *
* *
* Events dispatched on iOS: *
* TODO *
* *
**************************************************************************************************/
* Some tests to validate our assumptions of events through different file systems *
***************************************************************************************************
* Events dispatched on Linux: *
* Create File: *
* 1) EventKind::Create(CreateKind::File) *
* 2) EventKind::Modify(ModifyKind::Metadata(MetadataKind::Any)) *
* or EventKind::Modify(ModifyKind::Data(DataChange::Any)) *
* 3) EventKind::Access(AccessKind::Close(AccessMode::Write))) *
* Create Directory: *
* 1) EventKind::Create(CreateKind::Folder) *
* Update File: *
* 1) EventKind::Modify(ModifyKind::Data(DataChange::Any)) *
* 2) EventKind::Access(AccessKind::Close(AccessMode::Write))) *
* Update File (rename): *
* 1) EventKind::Modify(ModifyKind::Name(RenameMode::From)) *
* 2) EventKind::Modify(ModifyKind::Name(RenameMode::To)) *
* 3) EventKind::Modify(ModifyKind::Name(RenameMode::Both)) *
* Update Directory (rename): *
* 1) EventKind::Modify(ModifyKind::Name(RenameMode::From)) *
* 2) EventKind::Modify(ModifyKind::Name(RenameMode::To)) *
* 3) EventKind::Modify(ModifyKind::Name(RenameMode::Both)) *
* Delete File: *
* 1) EventKind::Remove(RemoveKind::File) *
* Delete Directory: *
* 1) EventKind::Remove(RemoveKind::Folder) *
* *
* Events dispatched on MacOS: *
* Create File: *
* 1) EventKind::Create(CreateKind::File) *
* 2) EventKind::Modify(ModifyKind::Data(DataChange::Content)) *
* Create Directory: *
* 1) EventKind::Create(CreateKind::Folder) *
* Update File: *
* 1) EventKind::Modify(ModifyKind::Data(DataChange::Content)) *
* Update File (rename): *
* 1) EventKind::Modify(ModifyKind::Name(RenameMode::Any)) -- From *
* 2) EventKind::Modify(ModifyKind::Name(RenameMode::Any)) -- To *
* Update Directory (rename): *
* 1) EventKind::Modify(ModifyKind::Name(RenameMode::Any)) -- From *
* 2) EventKind::Modify(ModifyKind::Name(RenameMode::Any)) -- To *
* Delete File: *
* 1) EventKind::Remove(RemoveKind::File) *
* Delete Directory: *
* 1) EventKind::Remove(RemoveKind::Folder) *
* *
* Events dispatched on Windows: *
* Create File: *
* 1) EventKind::Create(CreateKind::Any) *
* 2) EventKind::Modify(ModifyKind::Any) *
* Create Directory: *
* 1) EventKind::Create(CreateKind::Any) *
* Update File: *
* 1) EventKind::Modify(ModifyKind::Any) *
* Update File (rename): *
* 1) EventKind::Modify(ModifyKind::Name(RenameMode::From)) *
* 2) EventKind::Modify(ModifyKind::Name(RenameMode::To)) *
* Update Directory (rename): *
* 1) EventKind::Modify(ModifyKind::Name(RenameMode::From)) *
* 2) EventKind::Modify(ModifyKind::Name(RenameMode::To)) *
* Delete File: *
* 1) EventKind::Remove(RemoveKind::Any) *
* Delete Directory: *
* 1) EventKind::Remove(RemoveKind::Any) *
* *
* Events dispatched on Android: *
* TODO *
* *
* Events dispatched on iOS: *
* TODO *
* *
**************************************************************************************************/
#[cfg(test)]
#[allow(unused)]
mod tests {
@ -376,8 +375,8 @@ mod tests {
Ok(maybe_event) => {
let event = maybe_event.expect("Failed to receive event");
debug!("Received event: {event:#?}");
// In case of file creation, we expect to see an close event on write mode
if event.paths[0] == path && event.kind == expected_event {
// Using `ends_with` here due to a weird edge case on CI tests at MacOS
if event.paths[0].ends_with(path) && event.kind == expected_event {
debug!("Received expected event: {expected_event:#?}");
break;
}