mirror of
https://github.com/Hopiu/zoxide.git
synced 2026-05-18 04:01:09 +00:00
175 lines
4.6 KiB
Rust
175 lines
4.6 KiB
Rust
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};
|
|
|
|
#[derive(Debug, Deserialize, Serialize)]
|
|
pub struct DirList<'a>(#[serde(borrow)] pub Vec<Dir<'a>>);
|
|
|
|
impl DirList<'_> {
|
|
const VERSION: u32 = 3;
|
|
|
|
pub fn new() -> DirList<'static> {
|
|
DirList(Vec::new())
|
|
}
|
|
|
|
pub fn from_bytes(bytes: &[u8]) -> Result<DirList> {
|
|
// Assume a maximum size for the database. This prevents bincode from
|
|
// throwing strange errors when it encounters invalid data.
|
|
const MAX_SIZE: u64 = 32 << 20; // 32 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 database: 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 database")
|
|
}
|
|
|
|
pub fn to_bytes(&self) -> Result<Vec<u8>> {
|
|
(|| -> 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 database")
|
|
}
|
|
}
|
|
|
|
impl<'a> Deref for DirList<'a> {
|
|
type Target = Vec<Dir<'a>>;
|
|
|
|
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<Vec<Dir<'a>>> for DirList<'a> {
|
|
fn from(dirs: Vec<Dir<'a>>) -> Self {
|
|
DirList(dirs)
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, 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 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 u32
|
|
} else {
|
|
0
|
|
};
|
|
write!(f, "{:>4} {}", score, self.dir.path)
|
|
}
|
|
}
|
|
|
|
pub type Rank = f64;
|
|
pub type Epoch = u64;
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::{Dir, DirList};
|
|
|
|
use std::borrow::Cow;
|
|
|
|
#[test]
|
|
fn zero_copy() {
|
|
let dirs = DirList(vec![Dir {
|
|
path: "/".into(),
|
|
rank: 0.0,
|
|
last_accessed: 0,
|
|
}]);
|
|
|
|
let bytes = dirs.to_bytes().unwrap();
|
|
let dirs = DirList::from_bytes(&bytes).unwrap();
|
|
|
|
for dir in dirs.iter() {
|
|
assert!(matches!(dir.path, Cow::Borrowed(_)))
|
|
}
|
|
}
|
|
}
|