1use std::{
2 path::{Path, PathBuf},
3 sync::OnceLock,
4};
5
6use directories::ProjectDirs;
7
8const APP_NAME: &str = "syndicationd";
9const CONFIG_FILE_NAME: &str = "config.toml";
10const SQLITE_DB_FILE_NAME: &str = "synd.db";
11const LOG_FILE_NAME: &str = "synd.log";
12
13#[derive(Debug)]
15pub struct SyndicationdDirs {
16 project_dirs: ProjectDirs,
17}
18
19impl SyndicationdDirs {
20 pub fn current() -> &'static Self {
21 static DIRS: OnceLock<SyndicationdDirs> = OnceLock::new();
22
23 DIRS.get_or_init(|| Self::new().expect("failed to resolve syndicationd directories"))
24 }
25
26 pub fn new() -> Option<Self> {
27 ProjectDirs::from("", "", APP_NAME).map(|project_dirs| Self { project_dirs })
28 }
29
30 pub fn cache_dir(&self) -> &Path {
31 self.project_dirs.cache_dir()
32 }
33
34 pub fn config_file(&self) -> PathBuf {
35 self.project_dirs.config_dir().join(CONFIG_FILE_NAME)
36 }
37
38 pub fn sqlite_db(&self) -> PathBuf {
39 self.project_dirs.data_dir().join(SQLITE_DB_FILE_NAME)
40 }
41
42 pub fn log_file(&self) -> PathBuf {
43 self.project_dirs.data_dir().join(LOG_FILE_NAME)
44 }
45
46 pub fn runtime_dir(&self) -> Option<&Path> {
47 self.project_dirs.runtime_dir()
48 }
49
50 pub fn runtime_dir_or_temp(&self) -> PathBuf {
51 self.runtime_dir()
52 .map_or_else(|| std::env::temp_dir().join(APP_NAME), Path::to_path_buf)
53 }
54}
55
56#[cfg(test)]
57mod tests {
58 use crate::dirs::SyndicationdDirs;
59
60 #[test]
61 fn resolves_current_platform_directories() {
62 let dirs = SyndicationdDirs::new().unwrap();
63
64 assert!(dirs.cache_dir().is_absolute());
65 assert!(dirs.config_file().ends_with("config.toml"));
66 assert!(dirs.sqlite_db().ends_with("synd.db"));
67 assert!(dirs.log_file().ends_with("synd.log"));
68 assert!(dirs.runtime_dir_or_temp().is_absolute());
69 }
70}