use std::{collections::HashMap, future::Future, path::PathBuf, sync::Arc};
use bytes::Bytes;
use tokio::sync::Mutex;
use crate::{
model::FileInfo,
proto::FileSystem,
tokio::local::{TokioLocalStorage, TokioLocalStorageError},
};
#[derive(Debug, Clone)]
pub struct TokioCachedLocalStorage {
inner: TokioLocalStorage,
cache: Arc<Mutex<HashMap<String, FileInfo>>>,
}
impl TokioCachedLocalStorage {
pub fn new(base: PathBuf, ignore_parts: Vec<String>, cache: Vec<FileInfo>) -> Self {
let internal = TokioLocalStorage::new(base, ignore_parts);
let cache: HashMap<_, _> = cache.into_iter().map(|e| (e.local_unix_path.clone(), e)).collect();
let cache = Arc::new(Mutex::new(cache));
Self { inner: internal, cache }
}
pub fn try_cache_content(&self) -> Option<HashMap<String, FileInfo>> {
let lock = self.cache.try_lock().ok()?;
Some(lock.clone())
}
}
impl FileSystem for TokioCachedLocalStorage {
type Error = TokioLocalStorageError;
type StorePrepare = (tokio::fs::File, FileInfo);
fn all_files(&mut self) -> impl Future<Output = Result<Vec<FileInfo>, Self::Error>> + Send {
let cache = self.cache.clone();
async move {
let guard = cache.lock().await;
let cache = guard.clone().into_values().collect();
Ok(cache)
}
}
#[expect(clippy::manual_async_fn)]
fn prepare_store_file(
&self,
info: FileInfo,
) -> impl Future<Output = Result<Self::StorePrepare, Self::Error>> + Send {
async move { Ok((self.inner.prepare_store_file(info.clone()).await?, info)) }
}
fn store_file(
&self,
prepared: Self::StorePrepare,
data: Bytes,
) -> impl Future<Output = Result<(), Self::Error>> + Send {
let int_prepared = prepared.0;
let cache = self.cache.clone();
async move {
self.inner.store_file(int_prepared, data).await?;
let mut guard = cache.lock().await;
guard.insert(prepared.1.local_unix_path.clone(), prepared.1);
Ok(())
}
}
fn delete_file(&self, info: FileInfo) -> impl Future<Output = Result<(), Self::Error>> + Send {
let cache = self.cache.clone();
let path = info.local_unix_path.clone();
async move {
self.inner.delete_file(info).await?;
let mut guard = cache.lock().await;
guard.remove(&path);
Ok(())
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_try_cache_content() {
let base = PathBuf::from(".");
let ignore_parts = vec![];
let cache_data: [FileInfo; 1] = [FileInfo {
crc32: 123,
local_unix_path: "abc".to_string(),
}];
let tokio_cached_local_storage = TokioCachedLocalStorage::new(base, ignore_parts, cache_data.to_vec());
let content = tokio_cached_local_storage.try_cache_content();
assert_eq!(
content,
Some(
cache_data
.iter()
.map(|e| (e.local_unix_path.clone(), e.clone()))
.collect()
)
);
}
}