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#[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
22pub 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
30pub 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
75pub 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
88pub 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}