parent
c5dea71892
commit
9e7586021c
@ -0,0 +1,24 @@
|
||||
//! Videl configuration
|
||||
use std::{
|
||||
path::{
|
||||
PathBuf,
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct Config
|
||||
{
|
||||
pub base_dir: PathBuf,
|
||||
}
|
||||
|
||||
impl Default for Config
|
||||
{
|
||||
#[inline]
|
||||
fn default() -> Self
|
||||
{
|
||||
Self {
|
||||
base_dir: PathBuf::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,82 @@
|
||||
//! De-duplicating functionality
|
||||
use std::{
|
||||
marker::PhantomData,
|
||||
hash::{
|
||||
Hash,
|
||||
Hasher,
|
||||
},
|
||||
iter::{
|
||||
self,
|
||||
|
||||
},
|
||||
};
|
||||
use smallmap::Map;
|
||||
|
||||
fn compute_hash_single<T: Hash>(value: &T) -> u64
|
||||
{
|
||||
let mut hasher = std::collections::hash_map::DefaultHasher::new();
|
||||
value.hash(&mut hasher);
|
||||
hasher.finish()
|
||||
}
|
||||
|
||||
/// De-duplicating iterator
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DedupIter<I, T>
|
||||
where T: Hash,
|
||||
{
|
||||
iter: I,
|
||||
hashes: Map<u64, ()>,
|
||||
_output: PhantomData<Map<T, ()>>,
|
||||
}
|
||||
|
||||
impl<I, T> Iterator for DedupIter<I, T>
|
||||
where I: Iterator<Item = T>,
|
||||
T: Hash
|
||||
{
|
||||
type Item = T;
|
||||
fn next(&mut self) -> Option<Self::Item>
|
||||
{
|
||||
while let Some(value) = self.iter.next()
|
||||
{
|
||||
if self.hashes.insert(compute_hash_single(&value), ()).is_none() {
|
||||
// Is unique hash
|
||||
return Some(value);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
let (min, max) = self.iter.size_hint();
|
||||
(std::cmp::min(1, min), max)
|
||||
}
|
||||
}
|
||||
impl<I, T> iter::FusedIterator for DedupIter<I, T>
|
||||
where I: iter::FusedIterator + Iterator<Item= T>,
|
||||
T: Hash{}
|
||||
|
||||
impl<I, T> DedupIter<I, T>
|
||||
where I: Iterator<Item = T>,
|
||||
T: Hash
|
||||
{
|
||||
pub fn into_inner(self) -> I
|
||||
{
|
||||
self.iter
|
||||
}
|
||||
}
|
||||
|
||||
pub trait DedupIterExt<T: Hash>: Sized
|
||||
{
|
||||
fn dedup(self) -> DedupIter<Self, T>;
|
||||
}
|
||||
|
||||
impl<I, T: Hash> DedupIterExt<T> for I
|
||||
where I: Iterator<Item = T>
|
||||
{
|
||||
fn dedup(self) -> DedupIter<Self, T> {
|
||||
DedupIter{
|
||||
iter: self,
|
||||
hashes: Map::with_capacity(8), // there are 8 bytes in u64, so preallocate pages to hold all possible key values. This is 16kb, I think.
|
||||
_output: PhantomData
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,177 @@
|
||||
//! Extensions
|
||||
use super::*;
|
||||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
hash::Hash,
|
||||
borrow::{
|
||||
Borrow,
|
||||
ToOwned,
|
||||
},
|
||||
num::NonZeroU8,
|
||||
};
|
||||
|
||||
pub use dedup::DedupIterExt;
|
||||
|
||||
/// Iterator that maps `T` -> `U`
|
||||
pub struct ReplacingIter<'a, I,T, U=T>
|
||||
{
|
||||
iter: I,
|
||||
table: &'a HashMap<T, U>,
|
||||
}
|
||||
|
||||
impl<'a, I,T,U> Iterator for ReplacingIter<'a, I,T,U>
|
||||
where I: Iterator<Item=T>,
|
||||
T: Hash+ Eq + ToOwned<Owned=U>,
|
||||
U: Borrow<T> + Clone,
|
||||
{
|
||||
type Item = U;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if let Some(item) = self.iter.next()
|
||||
{
|
||||
Some(self.table.get(&item)
|
||||
.map(Clone::clone)
|
||||
.unwrap_or(item.to_owned()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[inline] fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
self.iter.size_hint()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, I,T,U> ExactSizeIterator for ReplacingIter<'a, I,T,U>
|
||||
where I: Iterator<Item=T> + ExactSizeIterator,
|
||||
T: Hash+ Eq + ToOwned<Owned=U>,
|
||||
U: Borrow<T> + Clone{}
|
||||
|
||||
impl<'a, I,T,U> std::iter::FusedIterator for ReplacingIter<'a, I,T,U>
|
||||
where I: Iterator<Item=T> + std::iter::FusedIterator,
|
||||
T: Hash+ Eq + ToOwned<Owned=U>,
|
||||
U: Borrow<T> + Clone{}
|
||||
|
||||
impl<'a, I,T,U> std::iter::DoubleEndedIterator for ReplacingIter<'a, I,T,U>
|
||||
where I: Iterator<Item=T> + std::iter::DoubleEndedIterator,
|
||||
T: Hash+ Eq + ToOwned<Owned=U>,
|
||||
U: Borrow<T> + Clone
|
||||
{
|
||||
fn next_back(&mut self) -> Option<Self::Item> {
|
||||
if let Some(item) = self.iter.next_back()
|
||||
{
|
||||
Some(self.table.get(&item)
|
||||
.map(Clone::clone)
|
||||
.unwrap_or(item.to_owned()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a ,I,T,U> ReplacingIter<'a, I,T,U>
|
||||
{
|
||||
pub fn into_inner(self) -> I
|
||||
{
|
||||
self.iter
|
||||
}
|
||||
pub fn table(&self) -> &'a HashMap<T,U>
|
||||
{
|
||||
self.table
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ReplacingIterExt<T, U>: Sized
|
||||
{
|
||||
fn replace_with<'a>(self, table: &'a HashMap<T,U>) -> ReplacingIter<'a, Self, T, U>;
|
||||
}
|
||||
|
||||
impl<I,T,U> ReplacingIterExt<T,U> for I
|
||||
where I: Iterator<Item=T>,
|
||||
T: Hash+ Eq + ToOwned<Owned=U>,
|
||||
U: Borrow<T> + Clone,
|
||||
{
|
||||
fn replace_with<'a>(self, table: &'a HashMap<T,U>) -> ReplacingIter<'a, Self, T, U> {
|
||||
ReplacingIter {
|
||||
iter: self,
|
||||
table,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const fn create_hex_map() -> [(u8, u8); 256]
|
||||
{
|
||||
let mut out = [(0, 0); 256];
|
||||
const HEX: &[u8; 16] = b"0123456789abcdef";
|
||||
let mut i = 0usize;
|
||||
while i <= 255
|
||||
{
|
||||
out[i] = (
|
||||
HEX[i >> 4],
|
||||
HEX[i & 0xf]
|
||||
);
|
||||
i+=1;
|
||||
}
|
||||
out
|
||||
}
|
||||
const HEX_MAP: [(u8, u8); 256] = create_hex_map();
|
||||
|
||||
pub struct HexStrIterator<I>
|
||||
{
|
||||
iter: I,
|
||||
buf: Option<NonZeroU8>, //we don't need full `char` here, since we can only have 0-9a-f anyway
|
||||
}
|
||||
|
||||
impl<I> Iterator for HexStrIterator<I>
|
||||
where I: Iterator<Item = u8>
|
||||
{
|
||||
type Item = char;
|
||||
fn next(&mut self) -> Option<Self::Item>
|
||||
{
|
||||
if let Some(buf) = self.buf.take()
|
||||
{
|
||||
return Some(u8::from(buf) as char);
|
||||
}
|
||||
|
||||
if let Some(next) = self.iter.next() {
|
||||
let buf = HEX_MAP[next as usize];
|
||||
debug_assert_ne!(buf.1, 0);
|
||||
//SAFETY: We know `HEX_MAP` contains only non-zero bytes.
|
||||
unsafe {
|
||||
self.buf = Some(NonZeroU8::new_unchecked(buf.1));
|
||||
}
|
||||
Some(buf.0 as char)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
let (min, max) = self.iter.size_hint();
|
||||
|
||||
(min * 2, max.map(|x| x * 2))
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> ExactSizeIterator for HexStrIterator<I>
|
||||
where I: Iterator<Item = u8> + ExactSizeIterator{}
|
||||
|
||||
impl<I> std::iter::FusedIterator for HexStrIterator<I>
|
||||
where I: Iterator<Item = u8> + std::iter::FusedIterator{}
|
||||
|
||||
pub trait HexStrIterExt: Sized
|
||||
{
|
||||
fn hex(self) -> HexStrIterator<Self>;
|
||||
}
|
||||
|
||||
impl<I> HexStrIterExt for I
|
||||
where I: Iterator<Item = u8>
|
||||
{
|
||||
fn hex(self) -> HexStrIterator<Self>
|
||||
{
|
||||
HexStrIterator{
|
||||
iter: self,
|
||||
buf: None,
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,84 @@
|
||||
//! Videl path resolution
|
||||
use super::*;
|
||||
use std::{
|
||||
path::{
|
||||
Path,
|
||||
PathBuf,
|
||||
},
|
||||
collections::HashMap,
|
||||
fmt,
|
||||
error,
|
||||
};
|
||||
use std::os::unix::ffi::{OsStrExt, OsStringExt};
|
||||
|
||||
#[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)]
|
||||
pub struct ResolutionError;
|
||||
|
||||
impl error::Error for ResolutionError{}
|
||||
impl fmt::Display for ResolutionError
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
||||
{
|
||||
write!(f, "database path was in an invalid format")
|
||||
}
|
||||
}
|
||||
|
||||
/// 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)?; //get the base64 encoded part
|
||||
let part = replace_base64_from(part.to_str().ok_or(ResolutionError)?); //replace characters back
|
||||
let bytes = base64::decode(part).map_err(|_| ResolutionError)?;
|
||||
|
||||
Ok(std::ffi::OsString::from_vec(bytes).into())
|
||||
} else {
|
||||
//TODO: Look up in `path/metadata` file
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in new issue