use std::ffi::{OsStr, OsString};
use std::fs;
use std::io::ErrorKind;
use std::num::NonZeroUsize;
use std::path::{Path, PathBuf};
use std::sync::LazyLock;
use std::thread;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use lru::LruCache;
use parking_lot::Mutex;
use crate::davpath::DavPath;
const CACHE_ENTRIES: usize = 4096;
const CACHE_MAX_AGE: u64 = 15 * 60;
const CACHE_SLEEP_MS: u64 = 30059;
static CACHE: LazyLock<Cache> = LazyLock::new(|| Cache::new(CACHE_ENTRIES));
pub(crate) fn resolve(base: impl Into<PathBuf>, path: &DavPath) -> PathBuf {
let base = base.into();
let path = path.as_rel_ospath();
let mut fullpath = base.clone();
fullpath.push(path);
if !fullpath.has_root() || fullpath.to_str().is_none() {
return fullpath;
}
let parent = match fullpath.parent() {
Some(p) => p,
None => return fullpath,
};
let cache = &*CACHE;
if let Some((path, _)) = cache.get(&fullpath) {
return path;
}
if fullpath.metadata().is_ok() {
return fullpath;
}
let segs = path.iter().collect::<Vec<_>>();
if segs.is_empty() {
return fullpath;
}
let (parent, parent_exists) = if segs.len() > 1 {
match cache.get(parent) {
Some((path, _)) => (path, true),
None => {
let exists = parent.exists();
if exists {
cache.insert(parent);
}
(parent.to_path_buf(), exists)
}
}
} else {
(parent.to_path_buf(), true)
};
if parent_exists {
let (newpath, stop) = lookup(parent, segs[segs.len() - 1], true);
if !stop {
cache.insert(&newpath);
}
return newpath;
}
let mut stop = false;
let mut newpath = base;
let lastseg = segs.len() - 1;
for (idx, seg) in segs.into_iter().enumerate() {
if !stop {
if idx == lastseg {
cache.insert(&newpath);
}
let (n, s) = lookup(newpath, seg, false);
newpath = n;
stop = s;
} else {
newpath.push(seg);
}
}
if !stop {
cache.insert(&newpath);
}
newpath
}
fn lookup(mut path: PathBuf, seg: &OsStr, no_init_check: bool) -> (PathBuf, bool) {
let mut path2 = path.clone();
path2.push(seg);
if !no_init_check {
match path2.metadata() {
Ok(_) => return (path2, false),
Err(ref e) if e.kind() != ErrorKind::NotFound => {
return (path2, true);
}
Err(_) => {}
}
}
let filename = match seg.to_str() {
Some(s) => s.to_lowercase(),
None => return (path2, true),
};
let dir = match path.read_dir() {
Ok(dir) => dir,
Err(_) => return (path2, true),
};
for entry in dir.into_iter() {
let entry = match entry {
Ok(e) => e,
Err(_) => continue,
};
let entry_name = entry.file_name();
let name = match entry_name.to_str() {
Some(n) => n,
None => continue,
};
if name.to_lowercase() == filename {
path.push(name);
return (path, false);
}
}
(path2, true)
}
pub struct Cache {
cache: Mutex<LruCache<PathBuf, Entry>>,
}
#[derive(Clone)]
struct Entry {
path: PathBuf,
time: u64,
}
fn pathbuf_to_lowercase(path: PathBuf) -> PathBuf {
let s = match OsString::from(path).into_string() {
Ok(s) => OsString::from(s.to_lowercase()),
Err(s) => s,
};
PathBuf::from(s)
}
impl Cache {
pub fn new(size: usize) -> Cache {
thread::spawn(move || {
loop {
thread::sleep(Duration::from_millis(CACHE_SLEEP_MS));
if let Ok(d) = SystemTime::now().duration_since(UNIX_EPOCH) {
let now = d.as_secs();
let mut cache = CACHE.cache.lock();
while let Some((_k, e)) = cache.peek_lru() {
trace!(target: "webdav_cache", "Cache: purge check: {_k:?}");
if e.time + CACHE_MAX_AGE > now {
break;
}
let _age = now - e.time;
if let Some((_k, _)) = cache.pop_lru() {
trace!(target: "webdav_cache", "Cache: purging {_k:?} (age {_age})");
} else {
break;
}
}
drop(cache);
}
}
});
Cache {
cache: Mutex::new(LruCache::new(NonZeroUsize::new(size).unwrap())),
}
}
pub fn insert(&self, path: &Path) {
let lc_path = pathbuf_to_lowercase(PathBuf::from(path));
if let Ok(d) = SystemTime::now().duration_since(UNIX_EPOCH) {
let e = Entry {
path: PathBuf::from(path),
time: d.as_secs(),
};
let mut cache = self.cache.lock();
cache.put(lc_path, e);
}
}
pub fn get(&self, path: &Path) -> Option<(PathBuf, fs::Metadata)> {
let lc_path = pathbuf_to_lowercase(PathBuf::from(path));
let e = {
let mut cache = self.cache.lock();
cache.get(&lc_path)?.clone()
};
match fs::metadata(&e.path) {
Err(_) => {
let mut cache = self.cache.lock();
cache.pop(&lc_path);
None
}
Ok(m) => Some((e.path, m)),
}
}
}