mirror of
https://github.com/spacedriveapp/spacedrive
synced 2024-07-18 22:29:11 +00:00
MacOS file system event handling using FSEvents backend
This commit is contained in:
parent
a18b2f8c8b
commit
4638f104be
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue