Add alias backwards compatibility for v3 databases
This commit is contained in:
parent
336d621969
commit
18d238a463
|
|
@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize};
|
|||
use crate::util::{DAY, HOUR, WEEK};
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct Dir<'a> {
|
||||
pub struct DirV4<'a> {
|
||||
#[serde(borrow)]
|
||||
pub path: Cow<'a, str>,
|
||||
pub rank: Rank,
|
||||
|
|
@ -15,12 +15,25 @@ pub struct Dir<'a> {
|
|||
pub aliases: Vec<Cow<'a, str>>,
|
||||
}
|
||||
|
||||
impl Dir<'_> {
|
||||
pub fn display(&self) -> DirDisplay<'_> {
|
||||
DirDisplay::new(self)
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct DirV3<'a> {
|
||||
#[serde(borrow)]
|
||||
pub path: Cow<'a, str>,
|
||||
pub rank: Rank,
|
||||
pub last_accessed: Epoch,
|
||||
}
|
||||
|
||||
pub trait Dir<'a> {
|
||||
fn path(&self) -> &str;
|
||||
fn score(&self, now: Epoch) -> Rank;
|
||||
}
|
||||
|
||||
impl Dir<'_> for DirV4<'_> {
|
||||
fn path(&self) -> &str {
|
||||
&self.path
|
||||
}
|
||||
|
||||
pub fn score(&self, now: Epoch) -> Rank {
|
||||
fn score(&self, now: Epoch) -> Rank {
|
||||
// The older the entry, the lesser its importance.
|
||||
let duration = now.saturating_sub(self.last_accessed);
|
||||
if duration < HOUR {
|
||||
|
|
@ -35,14 +48,40 @@ impl Dir<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct DirDisplay<'a> {
|
||||
dir: &'a Dir<'a>,
|
||||
impl DirV4<'_> {
|
||||
pub fn display(&self) -> DirDisplay<'_, Self> {
|
||||
DirDisplay::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Dir<'_> for DirV3<'_> {
|
||||
fn path(&self) -> &str {
|
||||
&self.path
|
||||
}
|
||||
|
||||
fn score(&self, now: Epoch) -> Rank {
|
||||
// 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 struct DirDisplay<'a, T: Dir<'a>> {
|
||||
dir: &'a T,
|
||||
now: Option<Epoch>,
|
||||
separator: char,
|
||||
}
|
||||
|
||||
impl<'a> DirDisplay<'a> {
|
||||
fn new(dir: &'a Dir) -> Self {
|
||||
impl<'a, T: Dir<'a>> DirDisplay<'a, T> {
|
||||
fn new(dir: &'a T) -> Self {
|
||||
Self { dir, separator: ' ', now: None }
|
||||
}
|
||||
|
||||
|
|
@ -57,13 +96,13 @@ impl<'a> DirDisplay<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
impl Display for DirDisplay<'_> {
|
||||
impl<'a, T: Dir<'a>> Display for DirDisplay<'a, T> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
if let Some(now) = self.now {
|
||||
let score = self.dir.score(now).clamp(0.0, 9999.0);
|
||||
write!(f, "{score:>6.1}{}", self.separator)?;
|
||||
}
|
||||
write!(f, "{}", self.dir.path)
|
||||
write!(f, "{}", self.dir.path())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,8 @@ use anyhow::{Context, Result, bail};
|
|||
use bincode::Options;
|
||||
use ouroboros::self_referencing;
|
||||
|
||||
pub use crate::db::dir::{Dir, Epoch, Rank};
|
||||
use crate::db::dir::{Dir, DirV3};
|
||||
pub use crate::db::dir::{DirV4, Epoch, Rank};
|
||||
pub use crate::db::stream::{Stream, StreamOptions};
|
||||
use crate::{config, util};
|
||||
|
||||
|
|
@ -18,12 +19,13 @@ pub struct Database {
|
|||
bytes: Vec<u8>,
|
||||
#[borrows(bytes)]
|
||||
#[covariant]
|
||||
pub dirs: Vec<Dir<'this>>,
|
||||
pub dirs: Vec<DirV4<'this>>,
|
||||
dirty: bool,
|
||||
}
|
||||
|
||||
impl Database {
|
||||
const VERSION: u32 = 3;
|
||||
const PREV_VERSION: u32 = 3;
|
||||
const VERSION: u32 = 4;
|
||||
|
||||
pub fn open() -> Result<Self> {
|
||||
let data_dir = config::data_dir()?;
|
||||
|
|
@ -82,7 +84,7 @@ impl Database {
|
|||
None => {
|
||||
let aliases =
|
||||
if let Some(alias) = alias { vec![alias.into().into()] } else { Vec::new() };
|
||||
dirs.push(Dir {
|
||||
dirs.push(DirV4 {
|
||||
path: path.into().into(),
|
||||
rank: by.max(0.0),
|
||||
last_accessed: now,
|
||||
|
|
@ -107,7 +109,7 @@ impl Database {
|
|||
self.with_dirs_mut(|dirs| {
|
||||
let aliases =
|
||||
if let Some(alias) = alias { vec![alias.into().into()] } else { Vec::new() };
|
||||
dirs.push(Dir { path: path.into().into(), rank, last_accessed: now, aliases })
|
||||
dirs.push(DirV4 { path: path.into().into(), rank, last_accessed: now, aliases })
|
||||
});
|
||||
self.with_dirty_mut(|dirty| *dirty = true);
|
||||
}
|
||||
|
|
@ -132,7 +134,7 @@ impl Database {
|
|||
None => {
|
||||
let aliases =
|
||||
if let Some(alias) = alias { vec![alias.into().into()] } else { Vec::new() };
|
||||
dirs.push(Dir {
|
||||
dirs.push(DirV4 {
|
||||
path: path.into().into(),
|
||||
rank: by.max(0.0),
|
||||
last_accessed: now,
|
||||
|
|
@ -215,7 +217,7 @@ impl Database {
|
|||
|
||||
pub fn sort_by_score(&mut self, now: Epoch) {
|
||||
self.with_dirs_mut(|dirs| {
|
||||
dirs.sort_unstable_by(|dir1: &Dir, dir2: &Dir| {
|
||||
dirs.sort_unstable_by(|dir1: &DirV4, dir2: &DirV4| {
|
||||
dir1.score(now).total_cmp(&dir2.score(now))
|
||||
})
|
||||
});
|
||||
|
|
@ -226,11 +228,11 @@ impl Database {
|
|||
*self.borrow_dirty()
|
||||
}
|
||||
|
||||
pub fn dirs(&self) -> &[Dir<'_>] {
|
||||
pub fn dirs(&self) -> &[DirV4<'_>] {
|
||||
self.borrow_dirs()
|
||||
}
|
||||
|
||||
fn serialize(dirs: &[Dir<'_>]) -> Result<Vec<u8>> {
|
||||
fn serialize(dirs: &[DirV4<'_>]) -> Result<Vec<u8>> {
|
||||
(|| -> bincode::Result<_> {
|
||||
// Preallocate buffer with combined size of sections.
|
||||
let buffer_size =
|
||||
|
|
@ -246,7 +248,7 @@ impl Database {
|
|||
.context("could not serialize database")
|
||||
}
|
||||
|
||||
fn deserialize(bytes: &[u8]) -> Result<Vec<Dir<'_>>> {
|
||||
fn deserialize(bytes: &[u8]) -> Result<Vec<DirV4<'_>>> {
|
||||
// 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
|
||||
|
|
@ -265,8 +267,27 @@ impl Database {
|
|||
Self::VERSION => {
|
||||
deserializer.deserialize(bytes_dirs).context("could not deserialize database")?
|
||||
}
|
||||
Self::PREV_VERSION => {
|
||||
let old_dirs = deserializer
|
||||
.deserialize::<Vec<DirV3>>(bytes_dirs)
|
||||
.context("could not deserialize v3 database")?;
|
||||
|
||||
old_dirs
|
||||
.into_iter()
|
||||
.map(|dir: DirV3| DirV4 {
|
||||
path: dir.path,
|
||||
rank: dir.rank,
|
||||
last_accessed: dir.last_accessed,
|
||||
aliases: Vec::new(),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
version => {
|
||||
bail!("unsupported version (got {version}, supports {})", Self::VERSION)
|
||||
bail!(
|
||||
"unsupported version (got {version}, supports {}, {})",
|
||||
Self::VERSION,
|
||||
Self::PREV_VERSION
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use std::{fs, path};
|
|||
|
||||
use glob::Pattern;
|
||||
|
||||
use crate::db::{Database, Dir, Epoch};
|
||||
use crate::db::{Database, DirV4, Epoch};
|
||||
use crate::util::{self, MONTH};
|
||||
|
||||
pub struct Stream<'a> {
|
||||
|
|
@ -22,7 +22,7 @@ impl<'a> Stream<'a> {
|
|||
Stream { db, idxs, options }
|
||||
}
|
||||
|
||||
pub fn next(&mut self) -> Option<&Dir<'_>> {
|
||||
pub fn next(&mut self) -> Option<&DirV4<'_>> {
|
||||
while let Some(idx) = self.idxs.next() {
|
||||
let dir = &self.db.dirs()[idx];
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ use std::path::PathBuf;
|
|||
use anyhow::Result;
|
||||
|
||||
use crate::config;
|
||||
use crate::db::{Database, Dir};
|
||||
use crate::db::{Database, DirV4};
|
||||
|
||||
pub(crate) trait Importer {
|
||||
/// Yields directory entries to be imported.
|
||||
|
|
@ -26,7 +26,7 @@ pub(crate) trait Importer {
|
|||
/// The outer `Result` reports failure to fetch the input (e.g. missing
|
||||
/// file, subprocess errored). The per-item `Result` reports a malformed
|
||||
/// row, which doesn't necessarily abort the whole import.
|
||||
fn dirs(&self) -> Result<impl Iterator<Item = Result<Dir<'static>, ImportError>>>;
|
||||
fn dirs(&self) -> Result<impl Iterator<Item = Result<DirV4<'static>, ImportError>>>;
|
||||
}
|
||||
|
||||
/// A single record that failed to import.
|
||||
|
|
|
|||
|
|
@ -5,14 +5,14 @@ use std::str;
|
|||
|
||||
use anyhow::{Context, Result, anyhow};
|
||||
|
||||
use crate::db::{Dir, Epoch};
|
||||
use crate::db::{DirV4, Epoch};
|
||||
use crate::import::{ImportError, Importer};
|
||||
|
||||
#[derive(clap::Args, Clone, Debug)]
|
||||
pub(crate) struct Atuin {}
|
||||
|
||||
impl Importer for Atuin {
|
||||
fn dirs(&self) -> Result<impl Iterator<Item = Result<Dir<'static>, ImportError>>> {
|
||||
fn dirs(&self) -> Result<impl Iterator<Item = Result<DirV4<'static>, ImportError>>> {
|
||||
// atuin renders `{time}` as `YYYY-MM-DD HH:MM:SS` in UTC.
|
||||
let mut child = Command::new("atuin")
|
||||
.args(["history", "list", "--format={time}\t{directory}", "--print0"])
|
||||
|
|
@ -46,7 +46,7 @@ impl Iter {
|
|||
ImportError { path: None, line_num: self.line_num, source }
|
||||
}
|
||||
|
||||
fn parse_line(&self, line: &[u8]) -> Result<Dir<'static>, ImportError> {
|
||||
fn parse_line(&self, line: &[u8]) -> Result<DirV4<'static>, ImportError> {
|
||||
let line =
|
||||
str::from_utf8(line).map_err(|e| self.err(anyhow!(e).context("invalid utf-8")))?;
|
||||
|
||||
|
|
@ -60,7 +60,7 @@ impl Iter {
|
|||
.assume_utc()
|
||||
.unix_timestamp();
|
||||
|
||||
let dir = Dir {
|
||||
let dir = DirV4 {
|
||||
path: Cow::Owned(path.to_string()),
|
||||
rank: 1.0,
|
||||
last_accessed: timestamp as Epoch,
|
||||
|
|
@ -71,7 +71,7 @@ impl Iter {
|
|||
}
|
||||
|
||||
impl Iterator for Iter {
|
||||
type Item = Result<Dir<'static>, ImportError>;
|
||||
type Item = Result<DirV4<'static>, ImportError>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
loop {
|
||||
|
|
|
|||
|
|
@ -6,14 +6,14 @@ use std::{env, str};
|
|||
|
||||
use anyhow::{Context, Result, anyhow};
|
||||
|
||||
use crate::db::Dir;
|
||||
use crate::db::DirV4;
|
||||
use crate::import::{ImportError, Importer};
|
||||
|
||||
#[derive(clap::Args, Clone, Debug)]
|
||||
pub(crate) struct Autojump {}
|
||||
|
||||
impl Importer for Autojump {
|
||||
fn dirs(&self) -> Result<impl Iterator<Item = Result<Dir<'static>, ImportError>>> {
|
||||
fn dirs(&self) -> Result<impl Iterator<Item = Result<DirV4<'static>, ImportError>>> {
|
||||
let path = data_path()?;
|
||||
let file = File::open(&path).with_context(|| format!("could not read {path:?}"))?;
|
||||
let reader = BufReader::new(file);
|
||||
|
|
@ -37,7 +37,7 @@ impl<R: BufRead> Iter<R> {
|
|||
ImportError { path: Some(self.path.clone()), line_num: self.line_num, source }
|
||||
}
|
||||
|
||||
fn parse_line(&self, line: &[u8]) -> Result<Dir<'static>, ImportError> {
|
||||
fn parse_line(&self, line: &[u8]) -> Result<DirV4<'static>, ImportError> {
|
||||
let line =
|
||||
str::from_utf8(line).map_err(|e| self.err(anyhow!(e).context("invalid utf-8")))?;
|
||||
|
||||
|
|
@ -52,12 +52,17 @@ impl<R: BufRead> Iter<R> {
|
|||
// take a while to normalize.
|
||||
let rank = sigmoid(rank);
|
||||
|
||||
Ok(Dir { path: Cow::Owned(path.to_string()), rank, last_accessed: 0, aliases: Vec::new() })
|
||||
Ok(DirV4 {
|
||||
path: Cow::Owned(path.to_string()),
|
||||
rank,
|
||||
last_accessed: 0,
|
||||
aliases: Vec::new(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: BufRead> Iterator for Iter<R> {
|
||||
type Item = Result<Dir<'static>, ImportError>;
|
||||
type Item = Result<DirV4<'static>, ImportError>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
loop {
|
||||
|
|
|
|||
|
|
@ -5,14 +5,14 @@ use std::path::PathBuf;
|
|||
|
||||
use anyhow::{Context, Result};
|
||||
|
||||
use crate::db::Dir;
|
||||
use crate::db::DirV4;
|
||||
use crate::import::{ImportError, Importer, z};
|
||||
|
||||
#[derive(clap::Args, Clone, Debug)]
|
||||
pub(crate) struct Fasd {}
|
||||
|
||||
impl Importer for Fasd {
|
||||
fn dirs(&self) -> Result<impl Iterator<Item = Result<Dir<'static>, ImportError>>> {
|
||||
fn dirs(&self) -> Result<impl Iterator<Item = Result<DirV4<'static>, ImportError>>> {
|
||||
let path = data_path()?;
|
||||
let file = File::open(&path).with_context(|| format!("could not read {path:?}"))?;
|
||||
let reader = BufReader::new(file);
|
||||
|
|
|
|||
|
|
@ -6,14 +6,14 @@ use std::{env, str};
|
|||
|
||||
use anyhow::{Context, Result, anyhow};
|
||||
|
||||
use crate::db::Dir;
|
||||
use crate::db::DirV4;
|
||||
use crate::import::{ImportError, Importer};
|
||||
|
||||
#[derive(clap::Args, Clone, Debug)]
|
||||
pub(crate) struct Z {}
|
||||
|
||||
impl Importer for Z {
|
||||
fn dirs(&self) -> Result<impl Iterator<Item = Result<Dir<'static>, ImportError>>> {
|
||||
fn dirs(&self) -> Result<impl Iterator<Item = Result<DirV4<'static>, ImportError>>> {
|
||||
let path = data_path()?;
|
||||
let file = File::open(&path).with_context(|| format!("could not read {path:?}"))?;
|
||||
let reader = BufReader::new(file);
|
||||
|
|
@ -37,7 +37,7 @@ impl<R: BufRead> Iter<R> {
|
|||
ImportError { path: Some(self.path.clone()), line_num: self.line_num, source }
|
||||
}
|
||||
|
||||
fn parse_line(&self, line: &[u8]) -> Result<Dir<'static>, ImportError> {
|
||||
fn parse_line(&self, line: &[u8]) -> Result<DirV4<'static>, ImportError> {
|
||||
let line =
|
||||
str::from_utf8(line).map_err(|e| self.err(anyhow!(e).context("invalid utf-8")))?;
|
||||
let err = || self.err(anyhow!("invalid entry: {line}"));
|
||||
|
|
@ -54,12 +54,12 @@ impl<R: BufRead> Iter<R> {
|
|||
|
||||
let path = split.next().ok_or_else(err)?;
|
||||
|
||||
Ok(Dir { path: Cow::Owned(path.to_string()), rank, last_accessed, aliases: Vec::new() })
|
||||
Ok(DirV4 { path: Cow::Owned(path.to_string()), rank, last_accessed, aliases: Vec::new() })
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: BufRead> Iterator for Iter<R> {
|
||||
type Item = Result<Dir<'static>, ImportError>;
|
||||
type Item = Result<DirV4<'static>, ImportError>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
loop {
|
||||
|
|
|
|||
|
|
@ -6,14 +6,14 @@ use std::path::PathBuf;
|
|||
|
||||
use anyhow::{Context, Result};
|
||||
|
||||
use crate::db::Dir;
|
||||
use crate::db::DirV4;
|
||||
use crate::import::{ImportError, Importer, z};
|
||||
|
||||
#[derive(clap::Args, Clone, Debug)]
|
||||
pub(crate) struct ZLua {}
|
||||
|
||||
impl Importer for ZLua {
|
||||
fn dirs(&self) -> Result<impl Iterator<Item = Result<Dir<'static>, ImportError>>> {
|
||||
fn dirs(&self) -> Result<impl Iterator<Item = Result<DirV4<'static>, ImportError>>> {
|
||||
let path = data_path()?;
|
||||
let err = match File::open(&path) {
|
||||
Ok(file) => return Ok(z::Iter::new(BufReader::new(file), path)),
|
||||
|
|
|
|||
|
|
@ -5,14 +5,14 @@ use std::path::PathBuf;
|
|||
|
||||
use anyhow::{Context, Result};
|
||||
|
||||
use crate::db::Dir;
|
||||
use crate::db::DirV4;
|
||||
use crate::import::{ImportError, Importer, z};
|
||||
|
||||
#[derive(clap::Args, Clone, Debug)]
|
||||
pub(crate) struct ZshZ {}
|
||||
|
||||
impl Importer for ZshZ {
|
||||
fn dirs(&self) -> Result<impl Iterator<Item = Result<Dir<'static>, ImportError>>> {
|
||||
fn dirs(&self) -> Result<impl Iterator<Item = Result<DirV4<'static>, ImportError>>> {
|
||||
let path = data_path()?;
|
||||
let file = File::open(&path).with_context(|| format!("could not read {path:?}"))?;
|
||||
let reader = BufReader::new(file);
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ use std::{env, mem};
|
|||
use anyhow::anyhow;
|
||||
use anyhow::{Context, Result, bail};
|
||||
|
||||
use crate::db::{Dir, Epoch};
|
||||
use crate::db::{DirV4, Epoch};
|
||||
use crate::error::SilentExit;
|
||||
|
||||
pub const SECOND: Epoch = 1;
|
||||
|
|
@ -121,7 +121,7 @@ impl Fzf {
|
|||
pub struct FzfChild(Child);
|
||||
|
||||
impl FzfChild {
|
||||
pub fn write(&mut self, dir: &Dir, now: Epoch) -> Result<Option<String>> {
|
||||
pub fn write(&mut self, dir: &DirV4, now: Epoch) -> Result<Option<String>> {
|
||||
let handle = self.0.stdin.as_mut().unwrap();
|
||||
match write!(handle, "{}\0", dir.display().with_score(now).with_separator('\t')) {
|
||||
Ok(()) => Ok(None),
|
||||
|
|
|
|||
Loading…
Reference in New Issue