cotton/
app_dir.rs

1use std::{path::{PathBuf, Path}, fmt::{self, Display}, error::Error};
2use directories::{ProjectDirs, BaseDirs, UserDirs};
3use std::sync::Mutex;
4
5struct AppInfo {
6    pub name: String,
7    pub author: String,
8}
9
10static APP_INFO: Mutex<Option<AppInfo>> = Mutex::new(None);
11
12/// Initializes application name and author with CARGO_PKG_NAME and CARGO_PKG_AUTHORS.
13#[macro_export]
14macro_rules! init_app_info {
15    () => {
16        init_app_info_with(env!("CARGO_PKG_NAME"), env!("CARGO_PKG_AUTHORS"))
17    };
18}
19
20pub use init_app_info;
21
22/// Initializes application name and author with given values.
23pub fn init_app_info_with(name: impl Into<String>, author: impl Into<String>) {
24    let mut app_info = APP_INFO.lock().unwrap();
25    if app_info.is_none() {
26        app_info.replace(AppInfo { name: name.into(), author: author.into() });
27    }
28}
29
30/// Initializes application name and author guessing from environment.
31pub fn init_app_info_guess() {
32    let mut app_info = APP_INFO.lock().unwrap();
33    if app_info.is_none() {
34        let name = std::env::args().next().and_then(|a| Path::new(&a).file_name().and_then(|n| n.to_str().map(ToOwned::to_owned)));
35        app_info.replace(AppInfo { name: name.unwrap_or("cotton".to_owned()), author: "Anonymous".to_owned() });
36    }
37}
38
39#[derive(Debug)]
40pub enum AppDirError {
41    NoProjectDir,
42    NoBaseDir,
43    NoUserDir,
44}
45
46impl Display for AppDirError {
47    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48        match self {
49            AppDirError::NoProjectDir => write!(f, "getting project directories"),
50            AppDirError::NoBaseDir  => write!(f, "getting base directories"),
51            AppDirError::NoUserDir => write!(f, "getting user directories"),
52        }
53    }
54}
55
56impl Error for AppDirError {
57}
58
59pub fn project_dirs() -> Result<ProjectDirs, AppDirError> {
60    init_app_info_guess();
61    let app_info = APP_INFO.lock().unwrap();
62    let app_info = app_info.as_ref().unwrap();
63
64    ProjectDirs::from("", &app_info.author, &app_info.name).ok_or(AppDirError::NoProjectDir)
65}
66
67pub fn base_dirs() -> Result<BaseDirs, AppDirError> {
68    BaseDirs::new().ok_or(AppDirError::NoBaseDir)
69}
70
71pub fn user_dirs() -> Result<UserDirs, AppDirError> {
72    UserDirs::new().ok_or(AppDirError::NoUserDir)
73}
74
75/// Gets and creates if necessary application specific data directory.
76///
77/// If subdir is given then additional sub directory is crated.
78pub fn app_data<'i>(subdir: impl Into<Option<&'i str>>) -> Result<PathBuf, AppDirError> {
79    let data_dir = project_dirs()?;
80    let data_dir = data_dir.data_dir();
81    Ok(if let Some(subdir) = subdir.into() {
82        data_dir.join(subdir)
83    } else {
84        data_dir.to_owned()
85    })
86}
87
88/// Gets and creates if necessary application specific cache directory.
89///
90/// If subdir is given then additional sub directory is crated.
91pub fn app_cache<'i>(subdir: impl Into<Option<&'i str>>) -> Result<PathBuf, AppDirError> {
92    let cache_dir = project_dirs()?;
93    let cache_dir = cache_dir.cache_dir();
94    Ok(if let Some(subdir) = subdir.into() {
95        cache_dir.join(subdir)
96    } else {
97        cache_dir.to_owned()
98    })
99}