/* This file is part of jellything (https://codeberg.org/metamuffin/jellything) which is licensed under the GNU Affero General Public License (version 3); see /COPYING. Copyright (C) 2023 metamuffin */ #![feature(lazy_cell)] use base64::Engine; use jellycommon::{config::GlobalConfig, AssetLocation}; use std::{fs::File, future::Future, path::PathBuf, sync::LazyLock}; use tokio::sync::Mutex; pub static CONF: LazyLock = LazyLock::new(|| { serde_json::from_reader( File::open( std::env::args() .nth(1) .expect("First argument must specify the config.json to use."), ) .unwrap(), ) .unwrap() }); 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].lock(); let exists = location.path().exists(); if !exists { let f = std::fs::File::create(location.path())?; generate(f)?; } drop(_guard); Ok(location) } pub trait AssetLocationExt { fn path(&self) -> PathBuf; } impl AssetLocationExt for AssetLocation { fn path(&self) -> PathBuf { match self { AssetLocation::Assets(p) => CONF.asset_path.join(p), AssetLocation::Cache(p) => CONF.cache_path.join(p), AssetLocation::Library(p) => CONF.library_path.join(p), } } }