use super::builders::build_event;
use super::state::VirtualDeviceState;
use crate::ptp::{EventCode, ObjectHandle, StorageId};
use notify::{Config, EventKind, RecommendedWatcher, RecursiveMode, Watcher};
use std::path::PathBuf;
use std::sync::{Arc, Mutex};
#[derive(Debug)]
pub(super) struct PendingFsEvent {
pub rel_path: PathBuf,
pub storage_id: StorageId,
pub is_create: bool,
}
type StorageMap = Vec<(PathBuf, StorageId)>;
pub(super) fn start_fs_watcher(
state: &Arc<Mutex<VirtualDeviceState>>,
) -> Option<(RecommendedWatcher, Arc<Mutex<Vec<PendingFsEvent>>>)> {
let pending = Arc::new(Mutex::new(Vec::<PendingFsEvent>::new()));
let pending_clone = Arc::clone(&pending);
let storage_map: StorageMap = {
let s = state.lock().unwrap();
s.storages
.iter()
.map(|ss| {
let dir = ss
.config
.backing_dir
.canonicalize()
.unwrap_or_else(|_| ss.config.backing_dir.clone());
(dir, ss.storage_id)
})
.collect()
};
let mut watcher = RecommendedWatcher::new(
move |res: Result<notify::Event, notify::Error>| {
let event = match res {
Ok(e) => e,
Err(_) => return,
};
let is_create = matches!(
event.kind,
EventKind::Create(_)
| EventKind::Modify(notify::event::ModifyKind::Name(
notify::event::RenameMode::To
))
);
let is_remove = matches!(
event.kind,
EventKind::Remove(_)
| EventKind::Modify(notify::event::ModifyKind::Name(
notify::event::RenameMode::From
))
);
if !is_create && !is_remove {
return;
}
let mut pending = pending_clone.lock().unwrap();
for path in &event.paths {
let canonical = path.canonicalize().unwrap_or_else(|_| path.clone());
let (storage_id, backing_dir) =
match find_storage_for_path(&canonical, &storage_map) {
Some(v) => v,
None => continue,
};
let rel_path = match canonical.strip_prefix(backing_dir) {
Ok(r) => r.to_path_buf(),
Err(_) => continue,
};
pending.push(PendingFsEvent {
rel_path,
storage_id,
is_create,
});
}
},
Config::default(),
)
.ok()?;
let state_lock = state.lock().unwrap();
for storage in &state_lock.storages {
let _ = watcher.watch(&storage.config.backing_dir, RecursiveMode::Recursive);
}
drop(state_lock);
Some((watcher, pending))
}
pub(super) fn process_pending_fs_events(
state: &mut VirtualDeviceState,
pending: &Mutex<Vec<PendingFsEvent>>,
) {
let events: Vec<PendingFsEvent> = {
let mut queue = pending.lock().unwrap();
queue.drain(..).collect()
};
for fs_event in events {
if fs_event.is_create {
let already_known = state.objects.iter().any(|(_, obj)| {
obj.storage_id == fs_event.storage_id && obj.rel_path == fs_event.rel_path
});
if already_known {
continue;
}
let parent = if let Some(parent_rel) = fs_event.rel_path.parent() {
if parent_rel == std::path::Path::new("") {
ObjectHandle::ROOT
} else {
match state.objects.iter().find(|(_, obj)| {
obj.storage_id == fs_event.storage_id && obj.rel_path == parent_rel
}) {
Some((&h, _)) => ObjectHandle(h),
None => ObjectHandle::ROOT,
}
}
} else {
ObjectHandle::ROOT
};
let handle = state.alloc_handle();
state.objects.insert(
handle.0,
super::state::VirtualObject {
rel_path: fs_event.rel_path,
storage_id: fs_event.storage_id,
parent,
},
);
state
.event_queue
.push_back(build_event(EventCode::ObjectAdded, &[handle.0]));
state.event_queue.push_back(build_event(
EventCode::StorageInfoChanged,
&[fs_event.storage_id.0],
));
} else {
let handle = state
.objects
.iter()
.find(|(_, obj)| {
obj.storage_id == fs_event.storage_id && obj.rel_path == fs_event.rel_path
})
.map(|(&h, _)| h);
if let Some(h) = handle {
state.objects.remove(&h);
state
.event_queue
.push_back(build_event(EventCode::ObjectRemoved, &[h]));
state.event_queue.push_back(build_event(
EventCode::StorageInfoChanged,
&[fs_event.storage_id.0],
));
}
}
}
}
fn find_storage_for_path<'a>(
path: &std::path::Path,
storage_map: &'a StorageMap,
) -> Option<(StorageId, &'a PathBuf)> {
storage_map
.iter()
.find(|(dir, _)| path.starts_with(dir))
.map(|(dir, sid)| (*sid, dir))
}