use std::path::PathBuf;
use std::sync::OnceLock;
pub use self::path::AppPath;
mod path;
pub mod system;
mod sealing {
use std::sync::OnceLock;
pub trait CellExt<T> {
fn get_or_try_set<E>(&self, f: impl Fn() -> Result<T, E>) -> Result<&T, E>;
}
impl<T> CellExt<T> for OnceLock<T> {
fn get_or_try_set<E>(&self, f: impl Fn() -> Result<T, E>) -> Result<&T, E> {
if let Some(value) = self.get() {
Ok(value)
} else {
let value = f()?;
Ok(self.get_or_init(|| value))
}
}
}
}
use sealing::CellExt;
pub struct App<'a> {
name: &'a str,
data: OnceLock<PathBuf>,
cache: OnceLock<PathBuf>,
docs: OnceLock<PathBuf>,
logs: OnceLock<PathBuf>,
config: OnceLock<PathBuf>,
}
impl<'a> App<'a> {
pub fn new(name: &'a str) -> App<'a> {
App {
name,
data: OnceLock::new(),
cache: OnceLock::new(),
docs: OnceLock::new(),
logs: OnceLock::new(),
config: OnceLock::new(),
}
}
pub fn get_data(&self) -> Option<AppPath<'_>> {
self.data
.get_or_try_set(|| system::get_app_data().ok_or(()).map(|v| v.join(self.name)))
.ok()
.map(|v| v.as_ref())
.map(AppPath::new)
}
pub fn get_cache(&self) -> Option<AppPath<'_>> {
self.cache
.get_or_try_set(|| {
system::get_app_cache()
.map(|v| v.join(self.name))
.or_else(|| self.get_data().map(|v| v.join("Cache")))
.ok_or(())
})
.ok()
.map(|v| v.as_ref())
.map(AppPath::new)
}
pub fn get_documents(&self) -> Option<AppPath<'_>> {
self.docs
.get_or_try_set(|| {
system::get_app_documents()
.or_else(|| self.get_data().map(|v| v.join("Documents")))
.ok_or(())
})
.ok()
.map(|v| v.as_ref())
.map(AppPath::new)
}
pub fn get_logs(&self) -> Option<AppPath<'_>> {
self.logs
.get_or_try_set(|| {
system::get_app_logs()
.map(|v| v.join(self.name))
.or_else(|| self.get_documents().map(|v| v.join("Logs")))
.ok_or(())
})
.ok()
.map(|v| v.as_ref())
.map(AppPath::new)
}
pub fn get_config(&self) -> Option<AppPath<'_>> {
self.config
.get_or_try_set(|| {
system::get_app_config()
.map(|v| v.join(self.name))
.or_else(|| self.get_data().map(|v| v.join("Config")))
.ok_or(())
})
.ok()
.map(|v| v.as_ref())
.map(AppPath::new)
}
}
impl<'a> Clone for App<'a> {
fn clone(&self) -> Self {
App {
name: self.name,
data: self.data.clone(),
cache: self.cache.clone(),
docs: self.docs.clone(),
logs: self.logs.clone(),
config: self.config.clone(),
}
}
}
#[cfg(test)]
mod tests {
use std::path::PathBuf;
use crate::dirs::App;
fn assert_sync_send<T: Sync + Send>(x: T) -> T {
x
}
#[test]
fn test_sync_send() {
let obj = App::new("test");
let _ = assert_sync_send(obj);
}
#[test]
fn api_breakage() {
let app = App::new("test");
let _: Option<PathBuf> = app
.get_logs()
.map(|v| v.create())
.unwrap()
.ok()
.map(|v| v.into());
}
}