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::Mutex;
type StorageMap = Vec<(PathBuf, StorageId)>;
pub(super) fn start_fs_watcher(
state: &std::sync::Arc<Mutex<VirtualDeviceState>>,
) -> Option<RecommendedWatcher> {
let state_clone = std::sync::Arc::clone(state);
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,
};
handle_notify_event(&state_clone, &storage_map, event);
},
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)
}
fn handle_notify_event(
state: &std::sync::Arc<Mutex<VirtualDeviceState>>,
storage_map: &StorageMap,
event: notify::Event,
) {
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;
}
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,
};
if rel_path.as_os_str().is_empty() {
continue;
}
let mut state = state.lock().unwrap();
if is_create {
let already_known = state
.objects
.iter()
.any(|(_, obj)| obj.storage_id == storage_id && obj.rel_path == rel_path);
if already_known {
continue;
}
let parent =
if let Some(parent_rel) = rel_path.parent() {
if parent_rel == std::path::Path::new("") {
ObjectHandle::ROOT
} else {
match state.objects.iter().find(|(_, obj)| {
obj.storage_id == 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,
storage_id,
parent,
},
);
state
.event_queue
.push_back(build_event(EventCode::ObjectAdded, &[handle.0]));
state
.event_queue
.push_back(build_event(EventCode::StorageInfoChanged, &[storage_id.0]));
} else {
let handle = state
.objects
.iter()
.find(|(_, obj)| obj.storage_id == storage_id && obj.rel_path == 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, &[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))
}