use std::path::{Path, PathBuf};
use bytes::Bytes;
use tokio::io::{self, AsyncRead, AsyncReadExt, AsyncSeekExt, AsyncWriteExt, SeekFrom};
use super::CacheStore;
pub struct FileStore {
base: PathBuf,
}
impl FileStore {
pub fn new(base: impl AsRef<Path>) -> Self {
Self {
base: base.as_ref().to_owned(),
}
}
}
impl CacheStore<Path> for FileStore {
type Err = io::Error;
type Reader = impl Send + AsyncRead;
async fn fill<R>(&self, k: &Path, v: &mut R) -> Result<(), Self::Err>
where
R: AsyncRead + Unpin + Send,
{
let target = self.base.join(k);
let mut file = tokio::fs::OpenOptions::new()
.write(true)
.truncate(true)
.create(true)
.open(target)
.await?;
io::copy(v, &mut file).await?;
Ok(())
}
async fn get_reader<Q: ?Sized + Sync + AsRef<Path>>(
&self,
k: &Q,
offset: u64,
limit: u64,
) -> Result<Option<Self::Reader>, Self::Err> {
let target = self.base.join(k);
let file = tokio::fs::OpenOptions::new().read(true).open(target).await;
match file {
Ok(mut f) => {
f.seek(SeekFrom::Start(offset)).await?;
Ok(Some(f.take(limit)))
}
Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(None),
e => e.map(|_| None),
}
}
async fn evict<Q: ?Sized + Sync + AsRef<Path>>(&self, k: &Q) -> Result<(), Self::Err> {
let target = self.base.join(k);
tokio::fs::remove_file(target).await
}
}
#[cfg(test)]
mod test {
use tokio::{fs, io};
use super::FileStore;
use crate::CacheStore;
#[tokio::test]
async fn test_get() -> Result<(), io::Error> {
let base = ".";
let source = FileStore::new(base);
let mut entries = fs::read_dir(base).await?;
while let Some(e) = entries.next_entry().await? {
if e.file_type().await?.is_file() {
let len = e.metadata().await?.len();
let offset = len / 2;
let limit = (len - offset) / 2;
let mut f = source
.get_reader(e.file_name().as_os_str(), offset, limit)
.await?
.unwrap();
let mut data = Vec::with_capacity(limit as usize);
io::copy(&mut f, &mut data).await?;
let expect = fs::read(e.path()).await?;
assert_eq!(
data,
expect.as_slice()[offset as usize..(offset + limit) as usize]
);
}
}
Ok(())
}
}