sound-visualisation/lib/resources/src/backend/filesystem.rs

218 lines
8.4 KiB
Rust

use crate::backend::{Backend, BackendSyncPoint, Modification};
use std::path::{Path, PathBuf};
use std::{fs, io};
use std::collections::VecDeque;
use crate::{Error, ResourcePath};
use std::sync::Mutex;
#[cfg(feature = "backend_filesystem_watch")]
mod watch_impl {
use std::collections::VecDeque;
use std::path::{Path, PathBuf};
use std::sync::mpsc::{channel, Receiver, TryRecvError};
use std::time::{Duration, Instant};
use notify::{RecommendedWatcher, Watcher as NotifyWatcher, RecursiveMode, DebouncedEvent};
use crate::backend::{BackendSyncPoint, Modification};
use crate::{ResourcePathBuf};
pub struct Watcher {
root_path: PathBuf,
_watcher: RecommendedWatcher,
receiver: Receiver<DebouncedEvent>,
outdated_at: Option<Instant>,
}
impl Watcher {
pub fn new(root_path: &Path) -> Option<Watcher> {
let (tx, rx) = channel();
let mut watcher: RecommendedWatcher = NotifyWatcher::new(tx, Duration::from_millis(50))
.map_err(|e| error!("failed to create watcher for {:?}, {:?}", root_path, e))
.ok()?;
watcher.watch(root_path, RecursiveMode::Recursive).ok()?;
Some(Watcher {
root_path: root_path.into(),
_watcher: watcher,
receiver: rx,
outdated_at: None,
})
}
pub fn notify_changes_synced(&mut self, point: BackendSyncPoint) {
if let Some(last_outdated) = self.outdated_at {
if point.instant == last_outdated {
self.outdated_at = None;
}
}
}
pub fn new_changes(&mut self, queue: &mut VecDeque<Modification>) -> Option<BackendSyncPoint> {
let mut something_outdated = false;
loop {
match self.receiver.try_recv() {
Ok(event) => {
match event {
DebouncedEvent::Create(path) => {
if let Some(resource_path) = ResourcePathBuf::from_filesystem_path(&self.root_path, &path) {
queue.push_back(Modification::Create(resource_path));
something_outdated = true;
} else {
warn!("unrecognised resource path {:?} for {} event", path, "Create")
}
},
DebouncedEvent::Write(path) => {
if let Some(resource_path) = ResourcePathBuf::from_filesystem_path(&self.root_path, &path) {
queue.push_back(Modification::Write(resource_path));
something_outdated = true;
} else {
warn!("unrecognised resource path {:?} for {} event", path, "Write")
}
},
DebouncedEvent::Chmod(path) => {
if let Some(resource_path) = ResourcePathBuf::from_filesystem_path(&self.root_path, &path) {
queue.push_back(Modification::Write(resource_path));
something_outdated = true;
} else {
warn!("unrecognised resource path {:?} for {} event", path, "Write")
}
},
DebouncedEvent::Remove(path) => {
if let Some(resource_path) = ResourcePathBuf::from_filesystem_path(&self.root_path, &path) {
queue.push_back(Modification::Remove(resource_path));
something_outdated = true;
} else {
warn!("unrecognised resource path {:?} for {} event", path, "Remove")
}
},
DebouncedEvent::Rename(from_path, to_path) => {
match (ResourcePathBuf::from_filesystem_path(&self.root_path, &from_path), ResourcePathBuf::from_filesystem_path(&self.root_path, &to_path)) {
(Some(from), Some(to)) => {
queue.push_back(Modification::Rename { from, to });
something_outdated = true;
},
(None, Some(_)) => warn!("unrecognised resource path {:?} for {} event", from_path, "Rename"),
(Some(_), None) => warn!("unrecognised resource path {:?} for {} event", to_path, "Rename"),
(None, None) => warn!("unrecognised resource paths {:?} and {:?} for Rename event", from_path, to_path),
}
},
_ => (),
}
},
Err(TryRecvError::Empty) => break,
Err(TryRecvError::Disconnected) => {
error!("filesystem watcher disconnected");
break;
},
}
}
if something_outdated {
let outdated_at = Instant::now();
self.outdated_at = Some(outdated_at);
Some(BackendSyncPoint { instant: outdated_at })
} else {
None
}
}
}
}
#[cfg(not(feature = "backend_filesystem_watch"))]
mod watch_impl {
use std::collections::VecDeque;
use crate::backend::{BackendSyncPoint, Modification};
pub struct Watcher {}
impl Watcher {
pub fn notify_changes_synced(&mut self, _point: BackendSyncPoint) {}
pub fn new_changes(&mut self, _queue: &mut VecDeque<Modification>) -> Option<BackendSyncPoint> {
None
}
}
}
pub struct FileSystem {
root_path: PathBuf,
can_write: bool,
watch: Option<Mutex<watch_impl::Watcher>>,
}
impl FileSystem {
pub fn from_rel_path<P: AsRef<Path>, RP: AsRef<ResourcePath>>(
root_path: P,
rel_path: RP,
) -> FileSystem {
FileSystem::from_path(resource_name_to_path(root_path.as_ref(), rel_path.as_ref()))
}
pub fn from_path<P: AsRef<Path>>(root_path: P) -> FileSystem {
FileSystem {
root_path: root_path.as_ref().into(),
can_write: false,
watch: None,
}
}
pub fn with_write(mut self) -> Self {
self.can_write = true;
self
}
#[cfg(feature = "backend_filesystem_watch")]
pub fn with_watch(mut self) -> Self {
self.watch = watch_impl::Watcher::new(&self.root_path).map(|v| Mutex::new(v));
self
}
}
impl Backend for FileSystem {
fn can_write(&self) -> bool {
self.can_write
}
fn exists(&self, path: &ResourcePath) -> bool {
resource_name_to_path(&self.root_path, path).exists()
}
fn notify_changes_synced(&mut self, point: BackendSyncPoint) {
if let Some(ref mut watch) = self.watch {
watch.lock().unwrap().notify_changes_synced(point);
}
}
fn new_changes(&mut self, queue: &mut VecDeque<Modification>) -> Option<BackendSyncPoint> {
if let Some(ref mut watch) = self.watch {
watch.lock().unwrap().new_changes(queue)
} else {
None
}
}
fn read_into(&mut self, path: &ResourcePath, mut output: &mut io::Write) -> Result<(), Error> {
let path = resource_name_to_path(&self.root_path, path);
let mut reader = io::BufReader::new(fs::File::open(path)?);
io::copy(&mut reader, &mut output)?;
Ok(())
}
fn write_from(&mut self, _path: &ResourcePath, _buffer: &mut io::Read) -> Result<(), Error> {
unimplemented!()
}
}
fn resource_name_to_path(root_dir: &Path, location: &ResourcePath) -> PathBuf {
let mut path: PathBuf = root_dir.into();
for part in location.items() {
path = path.join(part);
}
path
}