use std::fmt::{self, Debug};
use std::fs::{self, File};
use std::path::{Component, Path, PathBuf};
use std::time::{Duration, SystemTime};
use crate::callback::CallbackFn;
use crate::result::{Error, Result};
pub struct CacheLazyFile<'a> {
path: PathBuf,
name: String,
callback: Box<dyn CallbackFn>,
refresh_interval: Duration,
cache_root: &'a Path,
cache_refresh_interval: &'a Duration,
locked: bool,
}
impl<'a> CacheLazyFile<'a> {
pub(crate) fn new(
path: impl AsRef<Path>,
callback: impl CallbackFn + 'static,
refresh_interval: Duration,
cache_root: &'a Path,
cache_refresh_interval: &'a Duration,
) -> Result<Self> {
let path = path.as_ref();
let name = if let Some(component) = path.components().next_back()
&& let Component::Normal(name) = component
&& let Some(name) = name.to_str()
&& name.trim() != ""
{
name.to_string()
} else {
let path = path.to_path_buf();
let error = Error::InvalidPath { path };
return Err(error);
};
(!path.exists())
.then(|| {
let callback = Box::new(callback);
let path = path.to_path_buf();
let locked = false;
Self {
path,
name,
callback,
refresh_interval,
cache_root,
cache_refresh_interval,
locked,
}
})
.ok_or_else(|| {
let path = path.to_path_buf();
Error::FileAlreadyExists { path }
})
}
#[must_use]
pub fn with_refresh_interval(self, refresh_interval: Duration) -> Self {
let Self {
path,
name,
callback,
cache_root,
cache_refresh_interval,
locked,
..
} = self;
Self {
path,
name,
callback,
refresh_interval,
cache_root,
cache_refresh_interval,
locked,
}
}
#[must_use]
pub fn with_default_refresh_interval(self) -> Self {
let Self {
path,
name,
callback,
cache_root,
cache_refresh_interval,
locked,
..
} = self;
let refresh_interval = *cache_refresh_interval;
Self {
path,
name,
callback,
refresh_interval,
cache_root,
cache_refresh_interval,
locked,
}
}
#[must_use]
pub fn path(&self) -> &Path {
let Self { path, .. } = self;
path
}
#[must_use]
pub fn name(&self) -> &str {
let Self { name, .. } = self;
name
}
#[must_use]
pub fn refresh_interval(&self) -> Duration {
let Self { refresh_interval, .. } = self;
*refresh_interval
}
#[must_use]
pub fn is_locked(&self) -> bool {
let Self { locked, .. } = self;
*locked
}
#[must_use]
pub fn is_unlocked(&self) -> bool {
!self.is_locked()
}
pub fn is_valid(&self) -> Result<bool> {
let Self {
path, refresh_interval, ..
} = self;
let metadata = fs::metadata(path)?;
let modified = metadata.modified()?;
let elapsed = modified.elapsed()?;
Ok(elapsed < *refresh_interval)
}
pub fn is_invalid(&self) -> Result<bool> {
self.is_valid().map(|valid| !valid)
}
pub fn valid_until(&self) -> Result<SystemTime> {
let Self {
path, refresh_interval, ..
} = self;
let metadata = fs::metadata(path)?;
let modified = metadata.modified()?;
Ok(modified + *refresh_interval)
}
pub fn lock(&mut self) -> Result<()> {
self.is_unlocked()
.then(|| {
self.locked = true;
})
.ok_or_else(|| Error::FileAlreadyLocked)
}
pub fn unlock(&mut self) -> Result<()> {
self.is_locked()
.then(|| {
self.locked = false;
})
.ok_or_else(|| Error::FileAlreadyUnlocked)
}
pub fn create(&self) -> Result<File> {
let Self { path, callback, .. } = self;
File::options()
.create_new(true)
.read(false)
.write(true)
.open(path)
.map_err(Error::IO)
.and_then(|file| callback(file).map_err(Error::Callback))
.and_then(|()| File::options().read(true).write(false).open(path).map_err(Error::IO))
}
pub fn open(&self) -> Result<File> {
let Self { path, .. } = self;
if path.exists() {
self.refresh()?;
File::options().read(true).write(false).open(path).map_err(Error::IO)
} else {
self.create()
}
}
pub fn refresh(&self) -> Result<()> {
self.is_invalid()
.and_then(|invalid| if invalid { self.force_refresh() } else { Ok(()) })
}
pub fn force_refresh(&self) -> Result<()> {
let Self { path, callback, .. } = self;
File::options()
.read(false)
.write(true)
.truncate(true)
.open(path)
.map_err(Error::IO)
.and_then(|file| callback(file).map_err(Error::Callback))
}
pub fn remove(&self) -> Result<()> {
let Self { path, cache_root, .. } = self;
if path.exists() {
fs::remove_file(path)?;
let mut current_parent = path.parent();
while let Some(parent_dir) = current_parent
&& parent_dir != *cache_root
&& fs::read_dir(parent_dir)?.next().is_none()
{
fs::remove_dir(parent_dir)?;
current_parent = parent_dir.parent();
}
}
Ok(())
}
pub fn init(self) -> Result<CacheFile<'a>> {
let Self { path, .. } = &self;
if !path.exists() {
let _ = self.create()?;
}
let cache_file = CacheFile(self);
Ok(cache_file)
}
}
impl Debug for CacheLazyFile<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Self {
path,
refresh_interval,
locked,
..
} = self;
f.debug_struct("LazyFile")
.field("path", &path)
.field("callback", &"...")
.field("refresh_interval", &refresh_interval)
.field("locked", &locked)
.finish()
}
}
pub struct CacheFile<'a>(CacheLazyFile<'a>);
impl CacheFile<'_> {
#[must_use]
pub fn with_refresh_interval(self, refresh_interval: Duration) -> Self {
let Self(inner) = self;
let inner = inner.with_refresh_interval(refresh_interval);
Self(inner)
}
#[must_use]
pub fn with_default_refresh_interval(self) -> Self {
let Self(inner) = self;
let inner = inner.with_default_refresh_interval();
Self(inner)
}
#[must_use]
pub fn path(&self) -> &Path {
let Self(inner) = self;
inner.path()
}
#[must_use]
pub fn name(&self) -> &str {
let Self(inner) = self;
inner.name()
}
#[must_use]
pub fn refresh_interval(&self) -> Duration {
let Self(inner) = self;
inner.refresh_interval()
}
#[must_use]
pub fn is_locked(&self) -> bool {
let Self(inner) = self;
inner.is_locked()
}
#[must_use]
pub fn is_unlocked(&self) -> bool {
let Self(inner) = self;
inner.is_unlocked()
}
pub fn is_valid(&self) -> Result<bool> {
let Self(inner) = self;
inner.is_valid()
}
pub fn is_invalid(&self) -> Result<bool> {
let Self(inner) = self;
inner.is_invalid()
}
pub fn valid_until(&self) -> Result<SystemTime> {
let Self(inner) = self;
inner.valid_until()
}
pub fn lock(&mut self) -> Result<()> {
let Self(inner) = self;
inner.lock()
}
pub fn unlock(&mut self) -> Result<()> {
let Self(inner) = self;
inner.unlock()
}
pub fn open(&self) -> Result<File> {
let Self(inner) = self;
inner.open()
}
pub fn refresh(&self) -> Result<()> {
let Self(inner) = self;
inner.refresh()
}
pub fn force_refresh(&self) -> Result<()> {
let Self(inner) = self;
inner.force_refresh()
}
pub fn remove(&self) -> Result<()> {
let Self(inner) = self;
inner.remove()
}
}
impl Debug for CacheFile<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Self(inner) = self;
let CacheLazyFile {
path,
refresh_interval,
locked,
..
} = inner;
f.debug_struct("File")
.field("path", &path)
.field("callback", &"...")
.field("refresh_interval", &refresh_interval)
.field("locked", &locked)
.finish()
}
}