You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
133 lines
3.5 KiB
133 lines
3.5 KiB
//! Videl path resolution
|
|
use super::*;
|
|
use std::{
|
|
path::{
|
|
Path,
|
|
PathBuf,
|
|
},
|
|
collections::HashMap,
|
|
fmt,
|
|
error,
|
|
borrow::Cow,
|
|
};
|
|
use std::os::unix::ffi::{OsStrExt, OsStringExt};
|
|
use tokio::{
|
|
fs::{
|
|
OpenOptions,
|
|
},
|
|
};
|
|
|
|
#[cfg(not(feature="fast-pathnames"))]
|
|
fn compute_hash_string(from: impl AsRef<[u8]>) -> String
|
|
{
|
|
use sha2::{Digest, Sha256};
|
|
let mut sha2 = Sha256::new();
|
|
sha2.update(from.as_ref());
|
|
let output = sha2.finalize();
|
|
output.into_iter().hex().collect()
|
|
}
|
|
|
|
lazy_static!{
|
|
static ref B64_TO: HashMap<char, char> = {
|
|
let mut table = HashMap::new();
|
|
table.insert('/', '-'); //cannot appear in file paths, to
|
|
table
|
|
};
|
|
static ref B64_FROM: HashMap<char, char> = {
|
|
B64_TO.iter().map(|(&x,&y)| (y,x)).collect()
|
|
};
|
|
}
|
|
|
|
fn replace_base64_to(string: impl AsRef<str>) -> String
|
|
{
|
|
string.as_ref().chars().replace_with(&B64_TO).collect()
|
|
}
|
|
|
|
fn replace_base64_from(string: impl AsRef<str>) -> String
|
|
{
|
|
string.as_ref().chars().replace_with(&B64_FROM).collect()
|
|
}
|
|
|
|
/// Resolve the database path for a certain file
|
|
pub fn mangle_path(config: &config::Config, path: impl AsRef<Path>) -> PathBuf
|
|
{
|
|
cfg_if!{
|
|
if #[cfg(feature="fast-pathnames")] {
|
|
config.base_dir.join(replace_base64_to(base64::encode(path.as_ref().as_os_str().as_bytes())))
|
|
} else {
|
|
config.base_dir.join(compute_hash_string(path.as_ref().as_os_str().as_bytes()))
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
#[non_exhaustive]
|
|
pub enum ResolutionError
|
|
{
|
|
Name,
|
|
Utf8,
|
|
#[cfg(feature="fast-pathnames")]
|
|
Base64Decode(base64::DecodeError),
|
|
IO(io::Error),
|
|
Open(io::Error),
|
|
Database(database::Error),
|
|
Unknown,
|
|
}
|
|
|
|
impl error::Error for ResolutionError
|
|
{
|
|
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
|
|
Some(match &self {
|
|
#[cfg(feature="fast-pathnames")] Self::Base64Decode(er) => er,
|
|
Self::IO(er)=>er,
|
|
Self::Open(er)=>er,
|
|
Self::Database(er)=>er,
|
|
_ => return None,
|
|
})
|
|
}
|
|
}
|
|
impl fmt::Display for ResolutionError
|
|
{
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
|
{
|
|
match self {
|
|
Self::Name => write!(f, "invalid pathname"),
|
|
Self::Utf8 => write!(f, "pathname contained invalid UTF-8"),
|
|
#[cfg(feature="fast-pathnames")] Self::Base64Decode(_) => write!(f, "failed to decode base64 pathname"),
|
|
Self::IO(_) => write!(f, "i/o error"),
|
|
Self::Open(_) => write!(f, "failed to open file"),
|
|
Self::Database(_) => write!(f, "failed to parse database"),
|
|
_ => write!(f, "unknown error")
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Find the original path from a database one
|
|
pub async fn demangle_path(path: impl AsRef<Path>) -> Result<PathBuf, ResolutionError>
|
|
{
|
|
cfg_if! {
|
|
if #[cfg(feature="fast-pathnames")] {
|
|
let part = path.as_ref().file_name().ok_or(ResolutionError::Name)?; //get the base64 encoded part
|
|
let part = replace_base64_from(part.to_str().ok_or(ResolutionError::Utf8)?); //replace characters back
|
|
let bytes = base64::decode(part).map_err(ResolutionError::Base64Decode)?;
|
|
|
|
Ok(std::ffi::OsString::from_vec(bytes).into())
|
|
} else {
|
|
let metafile = path.as_ref().join("metadata");
|
|
let file = OpenOptions::new()
|
|
.read(true)
|
|
.open(metafile).await.map_err(ResolutionError::Open)?;
|
|
let mut file = tokio::io::BufReader::new(file);
|
|
let db = database::load(&mut file).await.map_err(ResolutionError::Database)?;
|
|
Ok(db.path)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Get bytes from a path
|
|
pub fn path_bytes<'a, P: AsRef<Path> + ?Sized>(path: &'a P) -> Cow<'a, [u8]>
|
|
{
|
|
//for now, just use unix ext'ss things
|
|
Cow::Borrowed(path.as_ref().as_os_str().as_bytes())
|
|
}
|