use crate::config::get_zo_maxage; use crate::dir::Dir; use crate::error::AppError; use crate::types::{Rank, Timestamp}; use failure::ResultExt; use fs2::FileExt; use serde::{Deserialize, Serialize}; use std::fs::File; use std::io::{self, BufReader, BufWriter}; use std::io::{Read, Write}; use std::path::Path; #[derive(Debug, Default, Deserialize, Serialize)] pub struct DB { pub dirs: Vec, #[serde(skip)] pub modified: bool, } impl DB { pub fn open>(path: P) -> Result { match File::open(path) { Ok(file) => { file.lock_shared() .with_context(|_| AppError::FileLockError)?; let rd = BufReader::new(file); let db = DB::read_from(rd).with_context(|_| AppError::DBReadError)?; Ok(db) } Err(err) => match err.kind() { io::ErrorKind::NotFound => Ok(DB::default()), _ => Err(err).with_context(|_| AppError::FileOpenError)?, }, } } pub fn save>(&mut self, path: P) -> Result<(), failure::Error> { if self.modified { let file = File::create(path).with_context(|_| AppError::FileOpenError)?; file.lock_exclusive() .with_context(|_| AppError::FileLockError)?; let wr = BufWriter::new(file); self.write_into(wr) .with_context(|_| AppError::DBWriteError)?; } Ok(()) } pub fn read_from(rd: R) -> Result { bincode::deserialize_from(rd) } pub fn write_into(&self, wr: W) -> Result<(), bincode::Error> { bincode::serialize_into(wr, &self) } pub fn add>(&mut self, path: P, now: Timestamp) -> Result<(), failure::Error> { let path_abs = path .as_ref() .canonicalize() .with_context(|_| AppError::PathAccessError)?; let path_str = path_abs.to_str().ok_or_else(|| AppError::UnicodeError)?; match self.dirs.iter_mut().find(|dir| dir.path == path_str) { None => self.dirs.push(Dir { path: path_str.to_owned(), last_accessed: now, rank: 1.0, }), Some(dir) => { dir.last_accessed = now; dir.rank += 1.0; } }; let max_age = get_zo_maxage()?; let sum_age = self.dirs.iter().map(|dir| dir.rank).sum::(); if sum_age > max_age { let factor = max_age / sum_age; for dir in &mut self.dirs { dir.rank *= factor; } } self.dirs.retain(|dir| dir.rank >= 1.0); self.modified = true; Ok(()) } pub fn query(&mut self, keywords: &[String], now: Timestamp) -> Option { // TODO: expand "~" in queries loop { let (idx, dir) = self .dirs .iter() .enumerate() .filter(|(_, dir)| dir.is_match(keywords)) .max_by_key(|(_, dir)| dir.get_frecency(now) as i64)?; if dir.is_dir() { return Some(dir.to_owned()); } else { self.dirs.remove(idx); self.modified = true; } } } pub fn remove>(&mut self, path: P) -> Result<(), failure::Error> { let path_abs = path .as_ref() .canonicalize() .with_context(|_| AppError::PathAccessError)?; let path_str = path_abs.to_str().ok_or_else(|| AppError::UnicodeError)?; if let Some(idx) = self.dirs.iter().position(|dir| dir.path == path_str) { self.dirs.remove(idx); self.modified = true; } Ok(()) } pub fn remove_invalid(&mut self) { let dirs_len = self.dirs.len(); self.dirs.retain(|dir| dir.is_dir()); if self.dirs.len() != dirs_len { self.modified = true; } } }