use crate::AssetLocationExt; use base64::Engine; use jellycommon::AssetLocation; use std::{future::Future, sync::LazyLock}; use tokio::sync::Mutex; pub fn cache_location(seed: &[&str]) -> (usize, AssetLocation) { use sha2::Digest; let mut d = sha2::Sha512::new(); for s in seed { d.update(s.as_bytes()); d.update(b"\0"); } let d = d.finalize(); let n = d[0] as usize | (d[1] as usize) << 8 | (d[2] as usize) << 16 | (d[3] as usize) << 24; let fname = base64::engine::general_purpose::URL_SAFE.encode(d); let fname = &fname[..22]; // about 128 bits (n, AssetLocation::Cache(fname.into())) } const CACHE_GENERATION_BUCKET_COUNT: usize = 1024; pub static CACHE_GENERATION_LOCKS: LazyLock<[Mutex<()>; CACHE_GENERATION_BUCKET_COUNT]> = LazyLock::new(|| [(); CACHE_GENERATION_BUCKET_COUNT].map(|_| Mutex::new(()))); pub async fn async_cache_file( seed: &[&str], generate: Fun, ) -> Result where Fun: FnOnce(tokio::fs::File) -> Fut, Fut: Future>, { let (bucket, location) = cache_location(seed); // we need a lock even if it exists since somebody might be still in the process of writing. let _guard = CACHE_GENERATION_LOCKS[bucket % CACHE_GENERATION_BUCKET_COUNT].lock(); let exists = tokio::fs::try_exists(location.path()).await?; if !exists { let f = tokio::fs::File::create(location.path()).await?; generate(f).await?; } drop(_guard); Ok(location) } pub fn cache_file(seed: &[&str], mut generate: Fun) -> Result where Fun: FnMut(std::fs::File) -> Result<(), anyhow::Error>, { let (bucket, location) = cache_location(seed); // we need a lock even if it exists since somebody might be still in the process of writing. let _guard = CACHE_GENERATION_LOCKS[bucket % CACHE_GENERATION_BUCKET_COUNT].blocking_lock(); let exists = location.path().exists(); if !exists { let f = std::fs::File::create(location.path())?; generate(f)?; } drop(_guard); Ok(location) }