use std::boxed::Box;
use std::ffi::OsStr;
use std::ffi::OsString;
use std::fs::File;
use std::fs::{self};
use std::hash::BuildHasher;
use std::io;
use std::io::prelude::*;
use std::path::Path;
use std::path::PathBuf;
use filetime::set_file_times;
use filetime::FileTime;
use ritelinked::DefaultHashBuilder;
use walkdir::WalkDir;
use crate::FileSize;
use crate::{Cache, LruCache};
fn get_all_files<P: AsRef<Path>>(path: P) -> Box<dyn Iterator<Item = (PathBuf, u64)>> {
let mut files: Vec<_> = WalkDir::new(path.as_ref())
.into_iter()
.filter_map(|e| {
e.ok().and_then(|f| {
if f.file_type().is_file() {
f.metadata().ok().and_then(|m| {
m.modified().ok().map(|mtime| (mtime, f.path().to_owned(), m.len()))
})
} else {
None
}
})
})
.collect();
files.sort_by_key(|k| k.0);
Box::new(files.into_iter().map(|(_mtime, path, size)| (path, size)))
}
pub type LruDiskCache = DiskCache<LruCache<OsString, u64, DefaultHashBuilder, FileSize>>;
pub struct DiskCache<C, S: BuildHasher + Clone = DefaultHashBuilder>
where
C: Cache<OsString, u64, S, FileSize>,
{
hash_builder: S,
cache: C,
root: PathBuf,
}
pub trait ReadSeek: Read + Seek + Send {}
impl<T: Read + Seek + Send> ReadSeek for T {}
enum AddFile<'a> {
AbsPath(PathBuf),
RelPath(&'a OsStr),
}
impl<C> DiskCache<C, DefaultHashBuilder>
where
C: Cache<OsString, u64, DefaultHashBuilder, FileSize>,
{
pub fn new<T>(path: T, size: u64) -> Result<Self>
where
PathBuf: From<T>,
{
DiskCache {
hash_builder: DefaultHashBuilder::new(),
cache: C::with_meter_and_hasher(size, FileSize, DefaultHashBuilder::new()),
root: PathBuf::from(path),
}
.init()
}
}
impl<C, S> DiskCache<C, S>
where
C: Cache<OsString, u64, S, FileSize>,
S: BuildHasher + Clone,
{
pub fn new_with_hasher<T>(path: T, size: u64, hash_builder: S) -> Result<Self>
where
PathBuf: From<T>,
{
DiskCache {
hash_builder: hash_builder.clone(),
cache: C::with_meter_and_hasher(size, FileSize, hash_builder),
root: PathBuf::from(path),
}
.init()
}
pub fn size(&self) -> u64 {
self.cache.size()
}
pub fn len(&self) -> usize {
self.cache.len()
}
pub fn is_empty(&self) -> bool {
self.cache.len() == 0
}
pub fn capacity(&self) -> u64 {
self.cache.capacity()
}
pub fn path(&self) -> &Path {
self.root.as_path()
}
fn rel_to_abs_path<K: AsRef<Path>>(&self, rel_path: K) -> PathBuf {
self.root.join(rel_path)
}
fn init(mut self) -> Result<Self> {
fs::create_dir_all(&self.root)?;
for (file, size) in get_all_files(&self.root) {
if !self.can_store(size) {
fs::remove_file(file).unwrap_or_else(|e| {
error!(
"Error removing file `{}` which is too large for the cache ({} bytes)",
e, size
)
});
} else {
self.add_file(AddFile::AbsPath(file), size)
.unwrap_or_else(|e| error!("Error adding file: {}", e));
}
}
Ok(self)
}
pub fn can_store(&self, size: u64) -> bool {
size <= self.cache.capacity() as u64
}
fn add_file(&mut self, addfile_path: AddFile<'_>, size: u64) -> Result<()> {
if !self.can_store(size) {
return Err(Error::FileTooLarge);
}
let rel_path = match addfile_path {
AddFile::AbsPath(ref p) => p.strip_prefix(&self.root).expect("Bad path?").as_os_str(),
AddFile::RelPath(p) => p,
};
while self.cache.size() as u64 + size > self.cache.capacity() as u64 {
let (rel_path, _) = self.cache.pop_by_policy().expect("Unexpectedly empty cache!");
let remove_path = self.rel_to_abs_path(rel_path);
fs::remove_file(&remove_path).unwrap_or_else(|e| {
panic!("Error removing file from cache: `{:?}`: {}", remove_path, e)
});
}
self.cache.put(rel_path.to_owned(), size);
Ok(())
}
fn insert_by<K: AsRef<OsStr>, F: FnOnce(&Path) -> io::Result<()>>(
&mut self,
key: K,
size: Option<u64>,
by: F,
) -> Result<()> {
if let Some(size) = size {
if !self.can_store(size) {
return Err(Error::FileTooLarge);
}
}
let rel_path = key.as_ref();
let path = self.rel_to_abs_path(rel_path);
fs::create_dir_all(path.parent().expect("Bad path?"))?;
by(&path)?;
let size = match size {
Some(size) => size,
None => fs::metadata(path)?.len(),
};
self.add_file(AddFile::RelPath(rel_path), size).map_err(|e| {
error!("Failed to insert file `{}`: {}", rel_path.to_string_lossy(), e);
fs::remove_file(&self.rel_to_abs_path(rel_path))
.expect("Failed to remove file we just created!");
e
})
}
pub fn insert_with<K: AsRef<OsStr>, F: FnOnce(File) -> io::Result<()>>(
&mut self,
key: K,
with: F,
) -> Result<()> {
self.insert_by(key, None, |path| with(File::create(&path)?))
}
pub fn insert_bytes<K: AsRef<OsStr>>(&mut self, key: K, bytes: &[u8]) -> Result<()> {
self.insert_by(key, Some(bytes.len() as u64), |path| {
let mut f = File::create(&path)?;
f.write_all(bytes)?;
Ok(())
})
}
pub fn insert_file<K: AsRef<OsStr>, P: AsRef<OsStr>>(&mut self, key: K, path: P) -> Result<()> {
let size = fs::metadata(path.as_ref())?.len();
self.insert_by(key, Some(size), |new_path| {
fs::rename(path.as_ref(), new_path).or_else(|_| {
warn!("fs::rename failed, falling back to copy!");
fs::copy(path.as_ref(), new_path)?;
fs::remove_file(path.as_ref()).unwrap_or_else(|e| {
error!("Failed to remove original file in insert_file: {}", e)
});
Ok(())
})
})
}
pub fn contains_key<K: AsRef<OsStr>>(&self, key: K) -> bool {
self.cache.contains(key.as_ref())
}
pub fn get_file<K: AsRef<OsStr>>(&mut self, key: K) -> Result<File> {
let rel_path = key.as_ref();
let path = self.rel_to_abs_path(rel_path);
self.cache.get(rel_path).ok_or(Error::FileNotInCache).and_then(|_| {
let t = FileTime::now();
set_file_times(&path, t, t)?;
File::open(path).map_err(Into::into)
})
}
pub fn get<K: AsRef<OsStr>>(&mut self, key: K) -> Result<Box<dyn ReadSeek>> {
self.get_file(key).map(|f| Box::new(f) as Box<dyn ReadSeek>)
}
pub fn remove<K: AsRef<OsStr>>(&mut self, key: K) -> Result<()> {
match self.cache.pop(key.as_ref()) {
Some(_) => {
let path = self.rel_to_abs_path(key.as_ref());
fs::remove_file(&path).map_err(|e| {
error!("Error removing file from cache: `{:?}`: {}", path, e);
Into::into(e)
})
}
None => Ok(()),
}
}
}
pub mod result {
use std::error::Error as StdError;
use std::fmt;
use std::io;
#[derive(Debug)]
pub enum Error {
FileTooLarge,
FileNotInCache,
Io(io::Error),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::FileTooLarge => write!(f, "File too large"),
Error::FileNotInCache => write!(f, "File not in cache"),
Error::Io(ref e) => write!(f, "{}", e),
}
}
}
impl StdError for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Error::FileTooLarge => None,
Error::FileNotInCache => None,
Error::Io(ref e) => Some(e),
}
}
}
impl From<io::Error> for Error {
fn from(e: io::Error) -> Error {
Error::Io(e)
}
}
pub type Result<T> = std::result::Result<T, Error>;
}
use result::*;