diff --git a/crates/lanspread-peer/src/download.rs b/crates/lanspread-peer/src/download.rs index 8c20a1c..830acb9 100644 --- a/crates/lanspread-peer/src/download.rs +++ b/crates/lanspread-peer/src/download.rs @@ -920,39 +920,12 @@ pub async fn download_game_files( #[cfg(test)] mod tests { use super::*; + use crate::test_support::TempDir; fn loopback_addr(port: u16) -> SocketAddr { SocketAddr::from(([127, 0, 0, 1], port)) } - struct TempDir(PathBuf); - - impl TempDir { - fn new() -> Self { - let mut path = std::env::temp_dir(); - path.push(format!( - "lanspread-download-{}-{}", - std::process::id(), - std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap_or_default() - .as_nanos() - )); - std::fs::create_dir_all(&path).expect("temp dir should be created"); - Self(path) - } - - fn path(&self) -> &Path { - &self.0 - } - } - - impl Drop for TempDir { - fn drop(&mut self) { - let _ = std::fs::remove_dir_all(&self.0); - } - } - #[test] fn build_peer_plans_handles_partial_final_chunk() { let peers = vec![loopback_addr(12000), loopback_addr(12001)]; @@ -1041,7 +1014,7 @@ mod tests { #[tokio::test] async fn prepare_game_storage_skips_version_ini_sentinel() { - let temp = TempDir::new(); + let temp = TempDir::new("lanspread-download"); let descs = vec![GameFileDescription { game_id: "game".to_string(), relative_path: "game/version.ini".to_string(), @@ -1080,7 +1053,7 @@ mod tests { #[tokio::test] async fn commit_version_ini_writes_sentinel_last_and_sweeps_discarded() { - let temp = TempDir::new(); + let temp = TempDir::new("lanspread-download"); let game_root = temp.path().join("game"); tokio::fs::create_dir_all(&game_root) .await @@ -1115,7 +1088,7 @@ mod tests { #[tokio::test] async fn begin_version_ini_transaction_parks_existing_sentinel() { - let temp = TempDir::new(); + let temp = TempDir::new("lanspread-download"); let game_root = temp.path().join("game"); tokio::fs::create_dir_all(&game_root) .await @@ -1142,7 +1115,7 @@ mod tests { #[tokio::test] async fn rollback_version_ini_transaction_sweeps_transients() { - let temp = TempDir::new(); + let temp = TempDir::new("lanspread-download"); let game_root = temp.path().join("game"); tokio::fs::create_dir_all(&game_root) .await diff --git a/crates/lanspread-peer/src/handlers.rs b/crates/lanspread-peer/src/handlers.rs index 8486141..e58b94e 100644 --- a/crates/lanspread-peer/src/handlers.rs +++ b/crates/lanspread-peer/src/handlers.rs @@ -652,46 +652,14 @@ mod tests { collections::HashSet, path::{Path, PathBuf}, sync::Arc, - time::{Duration, SystemTime, UNIX_EPOCH}, + time::Duration, }; use tokio::sync::mpsc; use tokio_util::{sync::CancellationToken, task::TaskTracker}; use super::*; - use crate::{UnpackFuture, Unpacker}; - - struct TempDir(PathBuf); - - impl TempDir { - fn new(prefix: &str) -> Self { - let mut path = std::env::temp_dir(); - path.push(format!( - "{prefix}-{}-{}", - std::process::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) - } - - fn path(&self) -> &Path { - &self.0 - } - - 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); - } - } + use crate::{UnpackFuture, Unpacker, test_support::TempDir}; struct FakeUnpacker; diff --git a/crates/lanspread-peer/src/install/intent.rs b/crates/lanspread-peer/src/install/intent.rs index 43f676a..8a3f40a 100644 --- a/crates/lanspread-peer/src/install/intent.rs +++ b/crates/lanspread-peer/src/install/intent.rs @@ -119,39 +119,12 @@ fn sync_parent_dir(_path: &Path) -> std::io::Result<()> { #[cfg(test)] mod tests { - use std::path::{Path, PathBuf}; - use super::*; - - struct TempDir(PathBuf); - - impl TempDir { - fn new() -> Self { - let mut path = std::env::temp_dir(); - path.push(format!( - "lanspread-intent-{}-{}", - std::process::id(), - now_unix_secs() - )); - path.push(format!("{:?}", std::thread::current().id()).replace(['(', ')'], "")); - std::fs::create_dir_all(&path).expect("temp dir should be created"); - Self(path) - } - - fn path(&self) -> &Path { - &self.0 - } - } - - impl Drop for TempDir { - fn drop(&mut self) { - let _ = std::fs::remove_dir_all(&self.0); - } - } + use crate::test_support::TempDir; #[tokio::test] async fn tmp_write_without_rename_leaves_previous_intent_intact() { - let temp = TempDir::new(); + let temp = TempDir::new("lanspread-intent"); let previous = InstallIntent::new( "game", InstallIntentState::Updating, @@ -180,7 +153,7 @@ mod tests { #[tokio::test] async fn schema_mismatch_is_treated_as_missing() { - let temp = TempDir::new(); + let temp = TempDir::new("lanspread-intent"); tokio::fs::write( intent_path(temp.path()), r#"{"schema_version":2,"id":"game","recorded_at":0,"state":"Updating"}"#, @@ -194,7 +167,7 @@ mod tests { #[tokio::test] async fn mismatched_id_is_treated_as_missing() { - let temp = TempDir::new(); + let temp = TempDir::new("lanspread-intent"); tokio::fs::write( intent_path(temp.path()), r#"{"schema_version":1,"id":"other","recorded_at":0,"state":"Updating"}"#, @@ -208,7 +181,7 @@ mod tests { #[tokio::test] async fn corrupt_intent_is_treated_as_missing() { - let temp = TempDir::new(); + let temp = TempDir::new("lanspread-intent"); tokio::fs::write(intent_path(temp.path()), b"not json") .await .expect("intent should be written"); @@ -219,7 +192,7 @@ mod tests { #[tokio::test] async fn old_manifest_hash_field_is_ignored_and_new_writes_omit_it() { - let temp = TempDir::new(); + let temp = TempDir::new("lanspread-intent"); tokio::fs::write( intent_path(temp.path()), r#"{"schema_version":1,"id":"game","recorded_at":0,"state":"Updating","eti_version":"20240101","manifest_hash":42}"#, diff --git a/crates/lanspread-peer/src/install/transaction.rs b/crates/lanspread-peer/src/install/transaction.rs index 6685160..fe10f56 100644 --- a/crates/lanspread-peer/src/install/transaction.rs +++ b/crates/lanspread-peer/src/install/transaction.rs @@ -464,15 +464,11 @@ mod tests { use std::{ collections::HashSet, path::{Path, PathBuf}, - sync::{ - Arc, - Mutex, - atomic::{AtomicU64, Ordering}, - }, + sync::{Arc, Mutex}, }; use super::*; - use crate::install::unpack::UnpackFuture; + use crate::{install::unpack::UnpackFuture, test_support::TempDir}; #[derive(Default)] struct FakeUnpacker { @@ -523,38 +519,6 @@ mod tests { } } - struct TempDir(PathBuf); - - static NEXT_TEMP_ID: AtomicU64 = AtomicU64::new(0); - - impl TempDir { - fn new() -> Self { - let mut path = std::env::temp_dir(); - let unique_id = NEXT_TEMP_ID.fetch_add(1, Ordering::Relaxed); - path.push(format!( - "lanspread-install-{}-{}-{}", - std::process::id(), - unique_id, - std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap_or_default() - .as_nanos() - )); - std::fs::create_dir_all(&path).expect("temp dir should be created"); - Self(path) - } - - 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); - } - } - fn write_file(path: &Path, bytes: &[u8]) { if let Some(parent) = path.parent() { std::fs::create_dir_all(parent).expect("parent dir should be created"); @@ -568,7 +532,7 @@ mod tests { #[tokio::test] async fn install_success_promotes_staging_and_clears_intent() { - let temp = TempDir::new(); + let temp = TempDir::new("lanspread-install"); let root = temp.game_root(); write_file(&root.join("game.eti"), b"archive"); write_file(&root.join("version.ini"), b"20250101"); @@ -585,7 +549,7 @@ mod tests { #[tokio::test] async fn install_unpacks_multiple_root_eti_archives_in_sorted_order() { - let temp = TempDir::new(); + let temp = TempDir::new("lanspread-install"); let root = temp.game_root(); write_file(&root.join("b.eti"), b"archive"); write_file(&root.join("a.eti"), b"archive"); @@ -608,7 +572,7 @@ mod tests { #[tokio::test] async fn update_failure_restores_previous_local() { - let temp = TempDir::new(); + let temp = TempDir::new("lanspread-install"); let root = temp.game_root(); write_file(&root.join("game.eti"), b"archive"); write_file(&root.join("version.ini"), b"20250101"); @@ -628,7 +592,7 @@ mod tests { #[tokio::test] async fn update_commit_rename_failure_restores_previous_local() { - let temp = TempDir::new(); + let temp = TempDir::new("lanspread-install"); let root = temp.game_root(); write_file(&root.join("game.eti"), b"archive"); write_file(&root.join("version.ini"), b"20250101"); @@ -656,7 +620,7 @@ mod tests { #[tokio::test] async fn update_success_promotes_new_local_and_removes_backup() { - let temp = TempDir::new(); + let temp = TempDir::new("lanspread-install"); let root = temp.game_root(); write_file(&root.join("game.eti"), b"archive"); write_file(&root.join("version.ini"), b"20250101"); @@ -676,7 +640,7 @@ mod tests { #[tokio::test] async fn uninstall_removes_only_local_install() { - let temp = TempDir::new(); + let temp = TempDir::new("lanspread-install"); let root = temp.game_root(); write_file(&root.join("game.eti"), b"archive"); write_file(&root.join("version.ini"), b"20250101"); @@ -696,7 +660,7 @@ mod tests { async fn uninstall_delete_failure_restores_backup() { use std::os::unix::fs::PermissionsExt; - let temp = TempDir::new(); + let temp = TempDir::new("lanspread-install"); let root = temp.game_root(); let locked_dir = root.join("local").join("locked"); write_file(&root.join("version.ini"), b"20250101"); @@ -881,7 +845,7 @@ mod tests { ]; for case in cases { - let temp = TempDir::new(); + let temp = TempDir::new("lanspread-install"); let root = temp.game_root(); seed_recovery_case(&root, &case); write_intent( @@ -909,7 +873,7 @@ mod tests { #[tokio::test] async fn none_recovery_leaves_markerless_reserved_dirs_untouched() { - let temp = TempDir::new(); + let temp = TempDir::new("lanspread-install"); let root = temp.game_root(); write_file(&root.join(".local.backup").join("user.txt"), b"user"); @@ -922,7 +886,7 @@ mod tests { #[tokio::test] async fn download_recovery_sweeps_reserved_version_files() { - let temp = TempDir::new(); + let temp = TempDir::new("lanspread-install"); let root = temp.game_root(); write_file(&root.join(VERSION_TMP_FILE), b"tmp"); write_file(&root.join(VERSION_DISCARDED_FILE), b"old"); @@ -937,13 +901,13 @@ mod tests { #[tokio::test] async fn startup_recovery_skips_active_game_roots() { - let temp = TempDir::new(); - let active_root = temp.0.join("active"); - let inactive_root = temp.0.join("inactive"); + let temp = TempDir::new("lanspread-install"); + let active_root = temp.path().join("active"); + let inactive_root = temp.path().join("inactive"); write_file(&active_root.join(VERSION_TMP_FILE), b"tmp"); write_file(&inactive_root.join(VERSION_TMP_FILE), b"tmp"); - recover_on_startup(&temp.0, &HashSet::from(["active".to_string()])) + recover_on_startup(temp.path(), &HashSet::from(["active".to_string()])) .await .expect("recovery should succeed"); diff --git a/crates/lanspread-peer/src/lib.rs b/crates/lanspread-peer/src/lib.rs index bb9be55..64e69ca 100644 --- a/crates/lanspread-peer/src/lib.rs +++ b/crates/lanspread-peer/src/lib.rs @@ -29,6 +29,8 @@ mod peer_db; mod remote_peer; mod services; mod startup; +#[cfg(test)] +mod test_support; // ============================================================================= // Public re-exports diff --git a/crates/lanspread-peer/src/local_games.rs b/crates/lanspread-peer/src/local_games.rs index 23dd9ed..e5e695c 100644 --- a/crates/lanspread-peer/src/local_games.rs +++ b/crates/lanspread-peer/src/local_games.rs @@ -620,41 +620,13 @@ pub async fn get_game_file_descriptions( mod tests { use std::{ collections::{HashMap, HashSet}, - path::{Path, PathBuf}, + path::Path, }; use lanspread_proto::Availability; use super::*; - use crate::context::OperationKind; - - struct TempDir(PathBuf); - - impl TempDir { - fn new() -> Self { - let mut path = std::env::temp_dir(); - path.push(format!( - "lanspread-local-games-{}-{}", - std::process::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) - } - - fn path(&self) -> &Path { - &self.0 - } - } - - impl Drop for TempDir { - fn drop(&mut self) { - let _ = std::fs::remove_dir_all(&self.0); - } - } + use crate::{context::OperationKind, test_support::TempDir}; fn write_file(path: &Path, bytes: &[u8]) { if let Some(parent) = path.parent() { @@ -665,7 +637,7 @@ mod tests { #[tokio::test] async fn scan_uses_version_ini_and_local_dir_as_independent_state() { - let temp = TempDir::new(); + let temp = TempDir::new("lanspread-local-games"); let catalog = HashSet::from([ "ready".to_string(), "local-only".to_string(), @@ -718,7 +690,7 @@ mod tests { #[tokio::test] async fn rescan_promotes_installed_only_game_to_ready_when_sentinel_appears() { - let temp = TempDir::new(); + let temp = TempDir::new("lanspread-local-games"); let catalog = HashSet::from(["game".to_string()]); std::fs::create_dir_all(temp.path().join("game").join("local")) .expect("local install dir should be created"); @@ -751,7 +723,7 @@ mod tests { #[tokio::test] async fn local_download_available_gates_on_catalog_operation_and_sentinel() { - let temp = TempDir::new(); + let temp = TempDir::new("lanspread-local-games"); let game_root = temp.path().join("game"); write_file(&game_root.join("version.ini"), b"20250101"); diff --git a/crates/lanspread-peer/src/path_validation.rs b/crates/lanspread-peer/src/path_validation.rs index 69123a6..4b4f56e 100644 --- a/crates/lanspread-peer/src/path_validation.rs +++ b/crates/lanspread-peer/src/path_validation.rs @@ -99,44 +99,11 @@ pub fn validate_game_file_path(game_dir: &Path, relative_path: &str) -> eyre::Re #[cfg(test)] mod tests { use super::*; - - fn create_temp_dir() -> std::io::Result { - let mut dir = std::env::temp_dir(); - let unique = format!( - "lanspread_test_{}_{}", - std::process::id(), - std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap_or_default() - .as_nanos() - ); - dir.push(unique); - std::fs::create_dir_all(&dir)?; - Ok(dir) - } - - struct TempDir(std::path::PathBuf); - - impl TempDir { - fn new() -> std::io::Result { - let path = create_temp_dir()?; - Ok(TempDir(path)) - } - - fn path(&self) -> &std::path::Path { - &self.0 - } - } - - impl Drop for TempDir { - fn drop(&mut self) { - let _ = std::fs::remove_dir_all(&self.0); - } - } + use crate::test_support::TempDir; #[test] fn test_valid_paths() { - let temp_dir = TempDir::new().expect("Failed to create temp dir for test"); + let temp_dir = TempDir::new("lanspread-path-validation"); let base = temp_dir.path(); // Valid relative paths @@ -156,7 +123,7 @@ mod tests { #[test] fn test_traversal_attempts() { - let temp_dir = TempDir::new().expect("Failed to create temp dir for test"); + let temp_dir = TempDir::new("lanspread-path-validation"); let base = temp_dir.path(); // These should all fail @@ -167,7 +134,7 @@ mod tests { #[test] fn test_double_dot_in_filename_allowed() { - let temp_dir = TempDir::new().expect("Failed to create temp dir for test"); + let temp_dir = TempDir::new("lanspread-path-validation"); let base = temp_dir.path(); assert!(validate_game_file_path(base, "data/file..txt").is_ok()); @@ -175,7 +142,7 @@ mod tests { #[test] fn test_missing_file_stays_within_base() { - let temp_dir = TempDir::new().expect("Failed to create temp dir for test"); + let temp_dir = TempDir::new("lanspread-path-validation"); let base = temp_dir.path(); #[allow(clippy::unwrap_used)] @@ -191,7 +158,7 @@ mod tests { #[test] fn test_absolute_paths() { - let temp_dir = TempDir::new().expect("Failed to create temp dir for test"); + let temp_dir = TempDir::new("lanspread-path-validation"); let base = temp_dir.path(); // Absolute paths should fail @@ -203,7 +170,7 @@ mod tests { #[test] fn test_windows_specific() { - let temp_dir = TempDir::new().expect("Failed to create temp dir for test"); + let temp_dir = TempDir::new("lanspread-path-validation"); let base = temp_dir.path(); // Windows-specific paths that should fail @@ -217,8 +184,8 @@ mod tests { fn test_symlink_escape_rejected() { use std::os::unix::fs::symlink; - let base_dir = TempDir::new().expect("Failed to create base temp dir"); - let outside_dir = TempDir::new().expect("Failed to create outside temp dir"); + let base_dir = TempDir::new("lanspread-path-validation-base"); + let outside_dir = TempDir::new("lanspread-path-validation-outside"); let base = base_dir.path(); let outside = outside_dir.path(); diff --git a/crates/lanspread-peer/src/services/local_monitor.rs b/crates/lanspread-peer/src/services/local_monitor.rs index 56cbe49..f1ef263 100644 --- a/crates/lanspread-peer/src/services/local_monitor.rs +++ b/crates/lanspread-peer/src/services/local_monitor.rs @@ -334,11 +334,8 @@ mod tests { use std::{ collections::HashSet, path::{Path, PathBuf}, - sync::{ - Arc, - atomic::{AtomicU64, Ordering}, - }, - time::{Duration, SystemTime, UNIX_EPOCH}, + sync::Arc, + time::Duration, }; use notify::EventKind; @@ -346,39 +343,13 @@ mod tests { use tokio_util::{sync::CancellationToken, task::TaskTracker}; use super::*; - use crate::{UnpackFuture, Unpacker, context::OperationKind, peer_db::PeerGameDB}; - - struct TempDir(PathBuf); - - static NEXT_TEMP_ID: AtomicU64 = AtomicU64::new(0); - - impl TempDir { - fn new() -> Self { - let mut path = std::env::temp_dir(); - let unique_id = NEXT_TEMP_ID.fetch_add(1, Ordering::Relaxed); - path.push(format!( - "lanspread-local-monitor-{}-{}-{}", - 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) - } - - fn path(&self) -> &Path { - &self.0 - } - } - - impl Drop for TempDir { - fn drop(&mut self) { - let _ = std::fs::remove_dir_all(&self.0); - } - } + use crate::{ + UnpackFuture, + Unpacker, + context::OperationKind, + peer_db::PeerGameDB, + test_support::TempDir, + }; struct NoopUnpacker; @@ -467,7 +438,7 @@ mod tests { #[tokio::test] async fn watch_event_for_active_game_is_dropped() { - let temp = TempDir::new(); + let temp = TempDir::new("lanspread-local-monitor"); let ctx = test_ctx( temp.path().to_path_buf(), HashSet::from(["game".to_string()]), @@ -501,7 +472,7 @@ mod tests { #[tokio::test] async fn burst_watch_events_collapse_to_two_rescans_for_same_game() { - let temp = TempDir::new(); + let temp = TempDir::new("lanspread-local-monitor"); let game_root = temp.path().join("game"); write_file(&game_root.join("version.ini"), b"20250101"); let ctx = test_ctx( @@ -538,7 +509,7 @@ mod tests { #[tokio::test] async fn fallback_scan_picks_up_sideloaded_catalog_game() { - let temp = TempDir::new(); + let temp = TempDir::new("lanspread-local-monitor"); write_file(&temp.path().join("game").join("version.ini"), b"20250101"); let ctx = test_ctx( temp.path().to_path_buf(), @@ -560,7 +531,7 @@ mod tests { #[tokio::test] async fn fallback_scan_ignores_non_catalog_game_without_library_delta() { - let temp = TempDir::new(); + let temp = TempDir::new("lanspread-local-monitor"); write_file( &temp.path().join("non-catalog").join("version.ini"), b"20250101", diff --git a/crates/lanspread-peer/src/services/stream.rs b/crates/lanspread-peer/src/services/stream.rs index bd1d98e..82b77f8 100644 --- a/crates/lanspread-peer/src/services/stream.rs +++ b/crates/lanspread-peer/src/services/stream.rs @@ -398,11 +398,7 @@ mod tests { use std::{ collections::HashSet, path::{Path, PathBuf}, - sync::{ - Arc, - atomic::{AtomicU64, Ordering}, - }, - time::{SystemTime, UNIX_EPOCH}, + sync::Arc, }; use tokio::sync::{RwLock, mpsc}; @@ -414,40 +410,9 @@ mod tests { Unpacker, context::{Ctx, OperationKind}, peer_db::PeerGameDB, + test_support::TempDir, }; - struct TempDir(PathBuf); - - static NEXT_TEMP_ID: AtomicU64 = AtomicU64::new(0); - - impl TempDir { - fn new() -> Self { - let mut path = std::env::temp_dir(); - let unique_id = NEXT_TEMP_ID.fetch_add(1, Ordering::Relaxed); - path.push(format!( - "lanspread-stream-{}-{}-{}", - 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) - } - - fn path(&self) -> &Path { - &self.0 - } - } - - impl Drop for TempDir { - fn drop(&mut self) { - let _ = std::fs::remove_dir_all(&self.0); - } - } - struct NoopUnpacker; impl Unpacker for NoopUnpacker { @@ -488,7 +453,7 @@ mod tests { #[tokio::test] async fn get_game_response_respects_serve_gates() { - let temp = TempDir::new(); + let temp = TempDir::new("lanspread-stream"); write_file(&temp.path().join("ready").join("version.ini"), b"20250101"); write_file( &temp.path().join("non-catalog").join("version.ini"), @@ -531,7 +496,7 @@ mod tests { #[tokio::test] async fn file_transfer_dispatch_respects_serve_gates() { - let temp = TempDir::new(); + let temp = TempDir::new("lanspread-stream"); write_file(&temp.path().join("ready").join("version.ini"), b"20250101"); write_file( &temp.path().join("non-catalog").join("version.ini"), diff --git a/crates/lanspread-peer/src/test_support.rs b/crates/lanspread-peer/src/test_support.rs new file mode 100644 index 0000000..9b2be80 --- /dev/null +++ b/crates/lanspread-peer/src/test_support.rs @@ -0,0 +1,41 @@ +use std::{ + path::{Path, PathBuf}, + sync::atomic::{AtomicU64, Ordering}, + time::{SystemTime, UNIX_EPOCH}, +}; + +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); + } +}