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.
videl/src/resolve.rs

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())
}