summaryrefslogtreecommitdiffstats
path: root/src/fscache.rs
diff options
context:
space:
mode:
authorrabite <rabite@posteo.de>2019-03-29 02:53:21 +0100
committerrabite <rabite@posteo.de>2019-03-29 02:53:21 +0100
commitb52e63fd8d74ed7587db6d37aca585f6185c77a3 (patch)
tree2e4a24610b1ab090dcb133ca542f39595e831436 /src/fscache.rs
parent8d8d9631b5fce48e2e65410aa4d0df64948f5ad8 (diff)
global file cache/event dispatch
Diffstat (limited to 'src/fscache.rs')
-rw-r--r--src/fscache.rs294
1 files changed, 294 insertions, 0 deletions
diff --git a/src/fscache.rs b/src/fscache.rs
new file mode 100644
index 0000000..c650dff
--- /dev/null
+++ b/src/fscache.rs
@@ -0,0 +1,294 @@
+use notify::{INotifyWatcher, Watcher, DebouncedEvent, RecursiveMode};
+
+use std::sync::{Arc, RwLock};
+use std::sync::mpsc::{channel, Sender, Receiver};
+use std::collections::{HashMap, HashSet};
+use std::time::Duration;
+use std::path::PathBuf;
+
+use crate::preview::{Async, Stale};
+use crate::files::{Files, File, SortBy};
+use crate::dirty::*;
+use crate::widget::Events;
+use crate::fail::{HResult, HError, ErrorLog};
+
+
+#[derive(Debug, Clone)]
+pub struct DirSettings {
+ sort: SortBy,
+ dirs_first: bool,
+ reverse: bool,
+ show_hidden: bool,
+ filter: Option<String>,
+}
+
+impl DirSettings {
+ fn new() -> DirSettings {
+ DirSettings {
+ sort: SortBy::Name,
+ dirs_first: true,
+ reverse: false,
+ show_hidden: true,
+ filter: None
+ }
+ }
+}
+
+#[derive(Debug, Clone)]
+pub struct TabSettings {
+ selection: Option<File>,
+ multi_selections: Vec<File>,
+ dir_settings: DirSettings,
+}
+
+impl TabSettings {
+ fn new() -> TabSettings {
+ TabSettings {
+ selection: None,
+ multi_selections: vec![],
+ dir_settings: DirSettings::new()
+ }
+ }
+}
+
+
+impl std::fmt::Debug for FsCache {
+ fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
+ write!(formatter,
+ "{:?}\n{:?}\n{:?}",
+ self.tab_settings,
+ self.watched_dirs,
+ self.files)
+ }
+}
+
+unsafe impl Sync for FsCache {}
+
+
+#[derive(Clone)]
+pub struct FsCache {
+ files: Arc<RwLock<HashMap<File, Files>>>,
+ pub tab_settings: Arc<RwLock<HashMap<File, TabSettings>>>,
+ watched_dirs: Arc<RwLock<HashSet<File>>>,
+ watcher: Arc<RwLock<INotifyWatcher>>,
+ pub fs_changes: Arc<RwLock<Vec<(File, Option<File>, Option<File>)>>>,
+ sender: Sender<Events>,
+}
+
+impl FsCache {
+ pub fn new(sender: Sender<Events>) -> FsCache {
+ let (tx_fs_event, rx_fs_event) = channel();
+ let watcher = INotifyWatcher::new(tx_fs_event,
+ Duration::from_secs(2)).unwrap();
+
+
+ let fs_cache = FsCache {
+ files: Arc::new(RwLock::new(HashMap::new())),
+ tab_settings: Arc::new(RwLock::new(HashMap::new())),
+ watched_dirs: Arc::new(RwLock::new(HashSet::new())),
+ watcher: Arc::new(RwLock::new(watcher)),
+ fs_changes: Arc::new(RwLock::new(vec![])),
+ sender: sender.clone(),
+ };
+
+ watch_fs(rx_fs_event,
+ fs_cache.files.clone(),
+ fs_cache.fs_changes.clone(),
+ sender.clone());
+
+ fs_cache
+ }
+
+ pub fn new_client(&self, settings: HashMap<File, TabSettings>) -> HResult<FsCache> {
+ let mut cache = self.clone();
+ cache.tab_settings = Arc::new(RwLock::new(settings));
+ Ok(cache)
+ }
+}
+
+pub type CachedFiles = (Option<File>, Async<Files>);
+
+impl FsCache {
+ pub fn get_files(&self, dir: &File, stale: Stale) -> HResult<CachedFiles> {
+ if self.files.read()?.contains_key(dir) {
+ self.get_cached_files(dir)
+ } else {
+ self.add_watch(&dir).log();
+ let dir = dir.clone();
+ let cache = self.files.clone();
+ let files = Async::new(Box::new(move |_| {
+ let files = Files::new_from_path_cancellable(&dir.path, stale)?;
+ Ok(files)
+ }));
+ Ok((None, files))
+ }
+ }
+
+ pub fn get_files_sync(&self, dir: &File) -> HResult<Files> {
+ let mut files = self.get_files(&dir, Stale::new())?.1;
+ files.wait()
+ }
+
+ pub fn get_selection(&self, dir: &File) -> HResult<File> {
+ Ok(self.tab_settings.read()?.get(&dir).as_ref()?.selection.as_ref()?.clone())
+ }
+
+ pub fn save_settings(&self, files: &Files, selection: Option<File>) -> HResult<()> {
+ let dir = files.directory.clone();
+ let tab_settings = FsCache::extract_tab_settings(&files, selection);
+ self.tab_settings.write()?.insert(dir, tab_settings);
+ Ok(())
+ }
+
+ pub fn put_files(&self, files: Files, selection: Option<File>) -> HResult<()> {
+ let dir = files.directory.clone();
+
+ let tab_settings = FsCache::extract_tab_settings(&files, selection);
+
+ self.tab_settings.write()?.insert(dir.clone(), tab_settings);
+
+ let mut file_cache = self.files.write()?;
+
+ if !file_cache.contains_key(&files.directory) {
+ file_cache.insert(dir, files);
+ }
+
+ Ok(())
+ }
+
+ pub fn is_cached(&self, dir: &File) -> HResult<bool> {
+ Ok(self.files.read()?.contains_key(dir))
+ }
+
+ fn add_watch(&self, dir: &File) -> HResult<()> {
+ if !self.watched_dirs.read()?.contains(&dir) {
+ self.watcher.write()?.watch(&dir.path, RecursiveMode::NonRecursive)?
+ }
+ Ok(())
+ }
+
+ fn remove_watch(&self, dir: &File) -> HResult<()> {
+ if self.watched_dirs.read()?.contains(&dir) {
+ self.watched_dirs.write()?.remove(dir);
+ self.watcher.write()?.unwatch(&dir.path)?
+ }
+ Ok(())
+ }
+
+ fn get_cached_files(&self, dir: &File) -> HResult<CachedFiles> {
+ let tab_settings = match self.tab_settings.read()?.get(&dir) {
+ Some(tab_settings) => tab_settings.clone(),
+ None => TabSettings::new()
+ };
+ let selection = tab_settings.selection.clone();
+ let file_cache = self.files.clone();
+ let dir = dir.clone();
+
+ let files = Async::new(Box::new(move |_| {
+ let mut files = file_cache.read()?.get(&dir)?.clone();
+ let tab_settings = &tab_settings;
+
+ files.sort = tab_settings.dir_settings.sort;
+ files.dirs_first = tab_settings.dir_settings.dirs_first;
+ files.reverse = tab_settings.dir_settings.reverse;
+ files.show_hidden = tab_settings.dir_settings.show_hidden;
+ files.filter = tab_settings.dir_settings.filter.clone();
+
+ if tab_settings.multi_selections.len() > 0 {
+ for file in &mut files.files {
+ for selected_files in &tab_settings.multi_selections {
+ if file.path == selected_files.path {
+ file.selected = true;
+ }
+ }
+ }
+ }
+
+ files.sort();
+ Ok(files)
+ }));
+
+ Ok((selection, files))
+ }
+
+ fn extract_tab_settings(files: &Files, selection: Option<File>) -> TabSettings {
+ TabSettings {
+ selection: selection,
+ multi_selections: files.get_selected().into_iter().cloned().collect(),
+ dir_settings: DirSettings {
+ sort: files.sort,
+ dirs_first: files.dirs_first,
+ reverse: files.reverse,
+ show_hidden: files.show_hidden,
+ filter: files.filter.clone(),
+ }
+ }
+ }
+}
+
+
+fn watch_fs(rx_fs_events: Receiver<DebouncedEvent>,
+ fs_cache: Arc<RwLock<HashMap<File, Files>>>,
+ fs_changes: Arc<RwLock<Vec<(File, Option<File>, Option<File>)>>>,
+ sender: Sender<Events>) {
+ std::thread::spawn(move || -> HResult<()> {
+ for event in rx_fs_events.iter() {
+ apply_event(&fs_cache, &fs_changes, event).log();
+
+ Ok(sender.send(Events::WidgetReady)?).log();
+ }
+ Ok(())
+ });
+}
+
+fn apply_event(fs_cache: &Arc<RwLock<HashMap<File, Files>>>,
+ fs_changes: &Arc<RwLock<Vec<(File, Option<File>, Option<File>)>>>,
+ event: DebouncedEvent)
+ -> HResult<()> {
+ let path = &event.get_source_path()?;
+
+ for dir in fs_cache.write()?.values_mut() {
+ if dir.path_in_here(&path).unwrap_or(false) {
+ let old_file = dir.find_file_with_path(&path).cloned();
+ let dirty_meta = old_file
+ .as_ref()
+ .map(|f| f.dirty_meta.clone())
+ .unwrap_or(None);
+ let mut new_file = match event {
+ DebouncedEvent::Remove(_) => None,
+ _ => Some(File::new_from_path(&path, dirty_meta)?)
+ };
+
+ new_file.as_mut().map(|file| file.meta_sync());
+ dir.replace_file(old_file.as_ref(), new_file.clone()).log();
+
+ fs_changes.write()?.push((dir.directory.clone(),
+ old_file,
+ new_file));
+ }
+ }
+ Ok(())
+}
+
+trait PathFromEvent {
+ fn get_source_path(&self) -> HResult<&PathBuf>;
+}
+
+impl PathFromEvent for DebouncedEvent {
+ fn get_source_path(&self) -> HResult<&PathBuf> {
+ match self {
+ DebouncedEvent::Create(path) |
+ DebouncedEvent::Write(path) |
+ DebouncedEvent::Chmod(path) |
+ DebouncedEvent::Remove(path) |
+ DebouncedEvent::NoticeWrite(path) |
+ DebouncedEvent::NoticeRemove(path) => Ok(path),
+ DebouncedEvent::Rename(old_path, _) => Ok(old_path),
+ DebouncedEvent::Error(err, path)
+ => Err(HError::INotifyError(format!("{}, {:?}", err, path))),
+ DebouncedEvent::Rescan
+ => Err(HError::INotifyError("Need to rescan".to_string()))
+
+ }
+ }
+}