gcache 0.0.1

A cache group to accurate remote data access
Documentation
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(())
    }
}