use std::{ path::{Path, PathBuf}, sync::{ Arc, atomic::{AtomicU64, Ordering}, }, time::{SystemTime, UNIX_EPOCH}, }; use lanspread_db::content_manifest::{ Blake3Digest, CatalogBundle, CatalogContentManifest, CatalogContentManifestBody, CatalogFileEntry, }; static NEXT_TEMP_ID: AtomicU64 = AtomicU64::new(0); pub(crate) struct TempDir(PathBuf); impl TempDir { pub(crate) fn new(prefix: &str) -> Self { let mut path = std::env::temp_dir(); let unique_id = NEXT_TEMP_ID.fetch_add(1, Ordering::Relaxed); path.push(format!( "{prefix}-{}-{}-{}", std::process::id(), unique_id, SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap_or_default() .as_nanos() )); std::fs::create_dir_all(&path).expect("temp dir should be created"); Self(path) } pub(crate) fn path(&self) -> &Path { &self.0 } pub(crate) fn game_root(&self) -> PathBuf { self.0.join("game") } } impl Drop for TempDir { fn drop(&mut self) { let _ = std::fs::remove_dir_all(&self.0); } } pub(crate) fn empty_catalog_bundle() -> Arc { catalog_bundle(std::iter::empty::<(String, String)>()) } pub(crate) fn catalog_bundle(entries: I) -> Arc where I: IntoIterator, G: Into, V: Into, { let manifests = entries .into_iter() .map(|(game_id, game_version)| { let game_version = game_version.into(); let version_digest = Blake3Digest::hash(game_version.as_bytes()); CatalogContentManifest::seal( CatalogContentManifestBody::new( game_id, &game_version, vec![ CatalogFileEntry::file( "version.ini", u64::try_from(game_version.len()) .expect("test version length should fit u64"), version_digest, vec![version_digest], ) .expect("test version.ini entry should be valid"), ], Vec::new(), ) .expect("test catalog manifest body should be valid"), ) .expect("test catalog manifest should seal") }) .collect::>(); Arc::new( CatalogBundle::from_manifests(manifests) .expect("test catalog bundle should be a complete immutable authority"), ) }