use super::Query; use anyhow::{bail, Context, Result}; use bincode::Options as _; use serde::{Deserialize, Serialize}; use std::borrow::Cow; use std::fmt::{self, Display, Formatter}; use std::ops::{Deref, DerefMut}; use std::path::Path; #[derive(Debug, Deserialize, Serialize)] pub struct DirList<'a>(#[serde(borrow)] Vec>); impl DirList<'_> { const VERSION: u32 = 3; pub fn new() -> DirList<'static> { DirList(Vec::new()) } pub fn from_bytes(bytes: &[u8]) -> Result { // Assume a maximum size for the store. This prevents bincode from throwing strange // errors when it encounters invalid data. const MAX_SIZE: u64 = 8 << 20; // 8 MiB let deserializer = &mut bincode::options() .with_fixint_encoding() .with_limit(MAX_SIZE); // Split bytes into sections. let version_size = deserializer.serialized_size(&Self::VERSION).unwrap() as _; if bytes.len() < version_size { bail!("could not deserialize store: corrupted data"); } let (bytes_version, bytes_dirs) = bytes.split_at(version_size); // Deserialize sections. (|| { let version = deserializer.deserialize(bytes_version)?; match version { Self::VERSION => Ok(deserializer.deserialize(bytes_dirs)?), version => bail!( "unsupported version (got {}, supports {})", version, Self::VERSION, ), } })() .context("could not deserialize store") } pub fn to_bytes(&self) -> Result> { (|| -> bincode::Result<_> { // Preallocate buffer with combined size of sections. let version_size = bincode::serialized_size(&Self::VERSION)?; let dirs_size = bincode::serialized_size(&self)?; let buffer_size = version_size + dirs_size; let mut buffer = Vec::with_capacity(buffer_size as _); // Serialize sections into buffer. bincode::serialize_into(&mut buffer, &Self::VERSION)?; bincode::serialize_into(&mut buffer, &self)?; Ok(buffer) })() .context("could not serialize store") } } impl<'a> Deref for DirList<'a> { type Target = Vec>; fn deref(&self) -> &Self::Target { &self.0 } } impl<'a> DerefMut for DirList<'a> { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } } impl<'a> From>> for DirList<'a> { fn from(dirs: Vec>) -> Self { DirList(dirs) } } #[derive(Debug, Deserialize, Serialize)] pub struct Dir<'a> { #[serde(borrow)] pub path: Cow<'a, str>, pub rank: Rank, pub last_accessed: Epoch, } impl Dir<'_> { pub fn is_match(&self, query: &Query) -> bool { query.matches(&self.path) && Path::new(self.path.as_ref()).is_dir() } pub fn score(&self, now: Epoch) -> Rank { const HOUR: Epoch = 60 * 60; const DAY: Epoch = 24 * HOUR; const WEEK: Epoch = 7 * DAY; // The older the entry, the lesser its importance. let duration = now.saturating_sub(self.last_accessed); if duration < HOUR { self.rank * 4.0 } else if duration < DAY { self.rank * 2.0 } else if duration < WEEK { self.rank * 0.5 } else { self.rank * 0.25 } } pub fn display(&self) -> DirDisplay { DirDisplay { dir: self } } pub fn display_score(&self, now: Epoch) -> DirDisplayScore { DirDisplayScore { dir: self, now } } } pub struct DirDisplay<'a> { dir: &'a Dir<'a>, } impl Display for DirDisplay<'_> { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { write!(f, "{}", self.dir.path) } } pub struct DirDisplayScore<'a> { dir: &'a Dir<'a>, now: Epoch, } impl Display for DirDisplayScore<'_> { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { let score = self.dir.score(self.now); let score = if score > 9999.0 { 9999 } else if score > 0.0 { score as _ } else { 0 }; write!(f, "{:>4} {}", score, self.dir.path) } } pub type Rank = f64; pub type Epoch = u64;