Files
ElyOffice/crates/ely-files/src/recent.rs
T

92 lines
2.9 KiB
Rust

//! Recently opened paths, kept in a JSON file.
use std::{
io,
path::{Path, PathBuf},
};
/// How many paths the list keeps.
const KEEP: usize = 20;
/// Recently opened paths, newest first.
pub fn recent(file: &Path) -> io::Result<Vec<PathBuf>> {
let listed: Vec<PathBuf> = match std::fs::read(file) {
Ok(bytes) => serde_json::from_slice(&bytes)
.map_err(|error| io::Error::other(format!("{}: {error}", file.display())))?,
Err(error) if error.kind() == io::ErrorKind::NotFound => return Ok(Vec::new()),
Err(error) => return Err(error),
};
// Older builds wrote `\\?\` paths.
let mut list = Vec::with_capacity(listed.len());
for path in listed {
let plain = dunce::simplified(&path).to_path_buf();
if !list.contains(&plain) {
list.push(plain);
}
}
Ok(list)
}
/// Takes `root` out of the list.
pub fn forget(file: &Path, root: &Path) -> io::Result<()> {
let mut list = recent(file)?;
list.retain(|known| known != root);
crate::save_atomic(
file,
&serde_json::to_vec(&list).map_err(io::Error::other)?,
0,
)
.map(drop)
}
/// Puts `root` first in the list and writes it.
pub fn remember(file: &Path, root: &Path) -> io::Result<Vec<PathBuf>> {
let mut list = recent(file)?;
list.retain(|known| known != root);
list.insert(0, root.to_path_buf());
list.truncate(KEEP);
if let Some(dir) = file.parent() {
std::fs::create_dir_all(dir)?;
}
crate::save_atomic(
file,
&serde_json::to_vec(&list).map_err(io::Error::other)?,
0,
)?;
Ok(list)
}
#[cfg(test)]
mod tests {
use std::path::{Path, PathBuf};
use super::{forget, recent, remember};
#[test]
fn the_list_puts_the_newest_first_and_keeps_twenty() {
let dir = tempfile::tempdir().expect("a temp dir");
let file = dir.path().join("data/recent.json");
assert!(recent(&file).expect("none yet").is_empty());
for n in 0..25 {
remember(&file, Path::new(&format!("/r/{n}"))).expect("remembers");
}
let list = remember(&file, Path::new("/r/10")).expect("remembers");
assert_eq!(list.len(), 20);
assert_eq!(list[..2], [PathBuf::from("/r/10"), PathBuf::from("/r/24")]);
forget(&file, Path::new("/r/10")).expect("forgets");
assert_eq!(recent(&file).expect("reads")[0], PathBuf::from("/r/24"));
#[cfg(windows)]
{
let old = [r"\\?\C:\r\old", r"C:\r\old", r"\\?\C:\r\new"];
std::fs::write(&file, serde_json::to_vec(&old).expect("json")).expect("writes");
let plain = [PathBuf::from(r"C:\r\old"), PathBuf::from(r"C:\r\new")];
assert_eq!(recent(&file).expect("reads"), plain);
}
std::fs::write(&file, "not json").expect("writes");
assert!(
remember(&file, Path::new("/r/1")).is_err(),
"a broken list is not overwritten"
);
}
}