use std::path::{Path, PathBuf};
use fs_err as fs;
use crate::common::universal_io::{UioResult, UniversalIoError};
const LOCAL_FILE_SUFFIX: &str = ".partial";
#[derive(Debug)]
pub struct DiskCacheConfig {
remote_dir: PathBuf,
local_dir: PathBuf,
}
impl DiskCacheConfig {
pub fn new(remote_dir: PathBuf, local_dir: PathBuf) -> UioResult<Self> {
let local_dir = fs::canonicalize(&local_dir)
.map_err(|err| UniversalIoError::extract_not_found(err, &local_dir))?;
Ok(Self {
remote_dir: canonicalize_remote(&remote_dir),
local_dir,
})
}
pub fn local_dir(&self) -> &Path {
&self.local_dir
}
pub fn local_path_for(&self, remote_path: &Path) -> UioResult<PathBuf> {
let resolved = canonicalize_remote(remote_path);
let rel =
resolved
.strip_prefix(&self.remote_dir)
.map_err(|_| UniversalIoError::NotFound {
path: remote_path.to_path_buf(),
})?;
let mut local = self.local_dir.join(rel);
local.as_mut_os_string().push(LOCAL_FILE_SUFFIX);
Ok(local)
}
}
fn canonicalize_remote(path: &Path) -> PathBuf {
fs::canonicalize(path).unwrap_or_else(|_| path.to_path_buf())
}
#[cfg(test)]
mod tests {
use std::assert_matches;
use fs_err as fs;
use super::DiskCacheConfig;
#[test]
fn strips_remote_dir_and_appends_suffix() {
let tmp = tempfile::Builder::new()
.prefix("simplediskcache-tests")
.tempdir()
.unwrap();
let remote_dir = tmp.path().join("remote");
let local_dir = tmp.path().join("local");
fs::create_dir_all(remote_dir.join("collections/c/segment")).unwrap();
fs::create_dir_all(&local_dir).unwrap();
let input = remote_dir.join("collections/c/segment/data.bin");
fs::write(&input, b"").unwrap();
let cfg = DiskCacheConfig::new(remote_dir, local_dir).unwrap();
let local = cfg.local_path_for(&input).unwrap();
assert_eq!(
local,
cfg.local_dir()
.join("collections/c/segment/data.bin.partial"),
);
}
#[test]
fn rejects_path_outside_remote_dir() {
let tmp = tempfile::Builder::new()
.prefix("simplediskcache-tests")
.tempdir()
.unwrap();
let remote_dir = tmp.path().join("remote");
let other_dir = tmp.path().join("other");
let local_dir = tmp.path().join("local");
fs::create_dir_all(&remote_dir).unwrap();
fs::create_dir_all(&other_dir).unwrap();
fs::create_dir_all(&local_dir).unwrap();
let outside = other_dir.join("data.bin");
fs::write(&outside, b"").unwrap();
let cfg = DiskCacheConfig::new(remote_dir, local_dir).unwrap();
let err = cfg.local_path_for(&outside).unwrap_err();
assert_matches!(err, crate::common::universal_io::UniversalIoError::NotFound { .. });
}
#[test]
fn maps_logical_remote_key_without_local_canonicalize() {
let tmp = tempfile::Builder::new()
.prefix("simplediskcache-tests")
.tempdir()
.unwrap();
let local_dir = tmp.path().join("local");
fs::create_dir_all(&local_dir).unwrap();
let cfg =
DiskCacheConfig::new(std::path::PathBuf::from("bucket/segments"), local_dir).unwrap();
let local = cfg
.local_path_for(std::path::Path::new("bucket/segments/seg/abc/page_0.dat"))
.unwrap();
assert_eq!(local, cfg.local_dir().join("seg/abc/page_0.dat.partial"));
}
}