diff --git a/Cargo.lock b/Cargo.lock index 01fd5e57b..874df280e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -590,6 +590,7 @@ version = "0.1.0" dependencies = [ "chrono", "crossbeam", + "dirs", "futures", "libc", "rayon", diff --git a/helper/Cargo.toml b/helper/Cargo.toml index 0c7741b9e..306143a30 100644 --- a/helper/Cargo.toml +++ b/helper/Cargo.toml @@ -10,11 +10,12 @@ repository = "https://github.com/Cuprate/cuprate/tree/main/consensus" [features] # All features on by default. -default = ["std", "atomic", "asynch", "num", "time", "thread", "constants"] +default = ["std", "atomic", "asynch", "fs", "num", "time", "thread", "constants"] std = [] atomic = ["dep:crossbeam"] asynch = ["dep:futures", "dep:rayon"] constants = [] +fs = ["dep:dirs"] num = [] time = ["dep:chrono", "std"] thread = ["std", "dep:target_os_lib"] @@ -22,6 +23,7 @@ thread = ["std", "dep:target_os_lib"] [dependencies] crossbeam = { workspace = true, optional = true } chrono = { workspace = true, optional = true, features = ["std", "clock"] } +dirs = { workspace = true, optional = true } futures = { workspace = true, optional = true, features = ["std"] } rayon = { workspace = true, optional = true } @@ -34,4 +36,4 @@ target_os_lib = { package = "windows", version = ">=0.51", features = ["Win32_Sy target_os_lib = { package = "libc", version = "0.2.151", optional = true } [dev-dependencies] -tokio = { workspace = true } +tokio = { workspace = true, features = ["full"] } diff --git a/helper/src/constants.rs b/helper/src/constants.rs index ca8c3dcf3..b77fad107 100644 --- a/helper/src/constants.rs +++ b/helper/src/constants.rs @@ -2,7 +2,7 @@ //! //! `#[no_std]` compatible. -//---------------------------------------------------------------------------------------------------- Use +//---------------------------------------------------------------------------------------------------- Commit /// The current commit hash of the root Cuprate repository. /// /// # Case & length diff --git a/helper/src/fs.rs b/helper/src/fs.rs new file mode 100644 index 000000000..8a82024fd --- /dev/null +++ b/helper/src/fs.rs @@ -0,0 +1,189 @@ +//! Cuprate directories and filenames. +//! +//! # TODO +//! Document how environment variables can change these. +//! +//! # Reference +//! +//! + +//---------------------------------------------------------------------------------------------------- Use +use std::{ + path::{Path, PathBuf}, + sync::OnceLock, +}; + +//---------------------------------------------------------------------------------------------------- Const +/// Cuprate's main directory. +/// +/// This is the head PATH node used for any top-level Cuprate directories. +/// +/// | OS | PATH | +/// |---------|-----------------------------------------------------| +/// | Windows | `C:\Users\Alice\AppData\Roaming\Cuprate\` | +/// | macOS | `/Users/Alice/Library/Application Support/Cuprate/` | +/// | Linux | `/home/alice/.config/cuprate/` | +/// +/// This is shared between all Cuprate programs. +/// +/// # Value +/// This is `Cuprate` on `Windows|macOS` and `cuprate` on everything else. +/// +/// # Monero Equivalent +/// `.bitmonero` +pub const CUPRATE_DIR: &str = { + if cfg!(target_os = "windows") || cfg!(target_os = "macos") { + // The standard for main directories is capitalized. + "Cuprate" + } else { + // Standard on Linux + BSDs is lowercase. + "cuprate" + } +}; + +//---------------------------------------------------------------------------------------------------- Directories +/// Create a (private) `OnceLock` and accessor function for common PATHs used by Cuprate. +/// +/// This currently creates these directories: +/// - [`cuprate_cache_dir()`] +/// - [`cuprate_config_dir()`] +/// - [`cuprate_data_dir()`] +/// +/// FIXME: Use `LazyLock` when stabilized. +/// . +/// . +macro_rules! impl_dir_oncelock_and_fn { + ($( + $(#[$attr:meta])* // Documentation and any `derive`'s. + $fn:ident, // Name of the corresponding access function. + $dirs_fn:ident, // Name of the `dirs` function to use, the PATH prefix. + $once_lock:ident // Name of the `OnceLock`. + ),* $(,)?) => {$( + /// Local `OnceLock` containing the Path. + static $once_lock: OnceLock = OnceLock::new(); + + // Create the `OnceLock` if needed, append + // the Cuprate directory string and return. + $(#[$attr])* + pub fn $fn() -> &'static Path { + $once_lock.get_or_init(|| { + // There's nothing we can do but panic if + // we cannot acquire critical system directories. + // + // Although, this realistically won't panic on + // normal systems for all OS's supported by `dirs`. + let mut path = dirs::$dirs_fn().unwrap(); + + // FIXME: + // Consider a user who does `HOME=/ ./cuprated` + // + // Should we say "that's stupid" and panic here? + // Or should it be respected? + // We really don't want a `rm -rf /` type of situation... + assert!( + path.parent().is_some(), + "SAFETY: returned OS PATH was either root or empty, aborting" + ); + + // Returned OS PATH should be absolute, not relative. + assert!(path.is_absolute(), "SAFETY: returned OS PATH was not absolute"); + + path.push(CUPRATE_DIR); + path + }) + } + )*}; +} + +impl_dir_oncelock_and_fn! { + /// Cuprate's cache directory. + /// + /// This is the PATH used for any Cuprate cache files. + /// + /// | OS | PATH | + /// |---------|-----------------------------------------| + /// | Windows | `C:\Users\Alice\AppData\Local\Cuprate\` | + /// | macOS | `/Users/Alice/Library/Caches/Cuprate/` | + /// | Linux | `/home/alice/.cache/cuprate/` | + cuprate_cache_dir, + cache_dir, + CUPRATE_CACHE_DIR, + + /// Cuprate's config directory. + /// + /// This is the PATH used for any Cuprate configuration files. + /// + /// | OS | PATH | + /// |---------|-----------------------------------------------------| + /// | Windows | `C:\Users\Alice\AppData\Roaming\Cuprate\` | + /// | macOS | `/Users/Alice/Library/Application Support/Cuprate/` | + /// | Linux | `/home/alice/.config/cuprate/` | + cuprate_config_dir, + config_dir, + CUPRATE_CONFIG_DIR, + + /// Cuprate's data directory. + /// + /// This is the PATH used for any Cuprate data files. + /// + /// | OS | PATH | + /// |---------|-----------------------------------------------------| + /// | Windows | `C:\Users\Alice\AppData\Roaming\Cuprate\` | + /// | macOS | `/Users/Alice/Library/Application Support/Cuprate/` | + /// | Linux | `/home/alice/.local/share/cuprate/` | + cuprate_data_dir, + data_dir, + CUPRATE_DATA_DIR, +} + +//---------------------------------------------------------------------------------------------------- Tests +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn dir_sanity_check() { + assert!(cuprate_cache_dir().is_absolute()); + assert!(cuprate_config_dir().is_absolute()); + assert!(cuprate_data_dir().is_absolute()); + + if cfg!(target_os = "windows") { + let dir = cuprate_cache_dir(); + println!("cuprate_cache_dir: {dir:?}"); + assert!(dir.ends_with(r"AppData\Local\Cuprate")); + + let dir = cuprate_config_dir(); + println!("cuprate_config_dir: {dir:?}"); + assert!(dir.ends_with(r"AppData\Roaming\Cuprate")); + + let dir = cuprate_data_dir(); + println!("cuprate_data_dir: {dir:?}"); + assert!(dir.ends_with(r"AppData\Roaming\Cuprate")); + } else if cfg!(target_os = "macos") { + let dir = cuprate_cache_dir(); + println!("cuprate_cache_dir: {dir:?}"); + assert!(dir.ends_with("Library/Caches/Cuprate")); + + let dir = cuprate_config_dir(); + println!("cuprate_config_dir: {dir:?}"); + assert!(dir.ends_with("Library/Application Support/Cuprate")); + + let dir = cuprate_data_dir(); + println!("cuprate_data_dir: {dir:?}"); + assert!(dir.ends_with("Library/Application Support/Cuprate")); + } else { + // Assumes Linux. + let dir = cuprate_cache_dir(); + println!("cuprate_cache_dir: {dir:?}"); + assert!(dir.ends_with(".cache/cuprate")); + + let dir = cuprate_config_dir(); + println!("cuprate_config_dir: {dir:?}"); + assert!(dir.ends_with(".config/cuprate")); + + let dir = cuprate_data_dir(); + println!("cuprate_data_dir: {dir:?}"); + assert!(dir.ends_with(".local/share/cuprate")); + } + } +} diff --git a/helper/src/lib.rs b/helper/src/lib.rs index 1bd87c42a..fc947a650 100644 --- a/helper/src/lib.rs +++ b/helper/src/lib.rs @@ -43,6 +43,9 @@ pub mod atomic; #[cfg(feature = "constants")] pub mod constants; +#[cfg(feature = "fs")] +pub mod fs; + pub mod network; #[cfg(feature = "num")]