Skip to main content

upstream_rs/utils/
static_paths.rs

1use anyhow::{Context, Result};
2use std::path::PathBuf;
3
4/// Root directories for the application
5pub struct AppDirs {
6    pub user_dir: PathBuf,
7    pub config_dir: PathBuf,
8    pub data_dir: PathBuf,
9    pub packages_dir: PathBuf,
10    pub cache_dir: PathBuf,
11    pub metadata_dir: PathBuf,
12}
13
14impl Default for AppDirs {
15    fn default() -> Self {
16        Self::new().expect("failed to determine upstream app directories")
17    }
18}
19
20impl AppDirs {
21    pub fn new() -> Result<Self> {
22        let user_dir =
23            dirs::home_dir().context("Failed to determine the current user's home directory")?;
24        let config_dir = dirs::config_dir()
25            .context("Failed to determine the current user's config directory")?
26            .join("upstream");
27
28        let data_dir = user_dir.join(".upstream");
29        let packages_dir = data_dir.join("packages");
30        let cache_dir = data_dir.join("cache");
31        let metadata_dir = data_dir.join("metadata");
32
33        Ok(Self {
34            user_dir,
35            config_dir,
36            data_dir,
37            packages_dir,
38            cache_dir,
39            metadata_dir,
40        })
41    }
42}
43
44/// Paths to configuration and metadata files
45pub struct ConfigPaths {
46    pub config_file: PathBuf,
47    pub packages_file: PathBuf,
48    pub metadata_file: PathBuf,
49    pub paths_file: PathBuf,
50    pub paths_nu_file: PathBuf,
51}
52
53impl ConfigPaths {
54    pub fn new(dirs: &AppDirs) -> Self {
55        Self {
56            config_file: dirs.config_dir.join("config.toml"),
57            packages_file: dirs.metadata_dir.join("packages.json"),
58            metadata_file: dirs.metadata_dir.join("metadata.json"),
59            paths_file: dirs.metadata_dir.join("paths.sh"),
60            paths_nu_file: dirs.metadata_dir.join("paths.nu"),
61        }
62    }
63}
64
65/// Directories where packages are installed
66pub struct InstallPaths {
67    pub appimages_dir: PathBuf,
68    pub binaries_dir: PathBuf,
69    pub archives_dir: PathBuf,
70    pub rollback_dir: PathBuf,
71    pub tmp_dir: PathBuf,
72}
73
74impl InstallPaths {
75    pub fn new(dirs: &AppDirs) -> Self {
76        Self {
77            appimages_dir: dirs.packages_dir.join("appimages"),
78            binaries_dir: dirs.packages_dir.join("binaries"),
79            archives_dir: dirs.packages_dir.join("archives"),
80            rollback_dir: dirs.data_dir.join("rollback"),
81            tmp_dir: dirs.data_dir.join("tmp"),
82        }
83    }
84}
85
86/// Paths for system integration (symlinks, XDG dirs)
87pub struct IntegrationPaths {
88    pub symlinks_dir: PathBuf,
89    pub xdg_applications_dir: PathBuf,
90    pub icons_dir: PathBuf,
91    pub bash_completions_dir: PathBuf,
92    pub fish_completions_dir: PathBuf,
93    pub zsh_completions_dir: PathBuf,
94}
95
96impl IntegrationPaths {
97    pub fn new(dirs: &AppDirs) -> Self {
98        Self {
99            symlinks_dir: dirs.data_dir.join("symlinks"),
100            icons_dir: dirs.data_dir.join("icons"),
101            xdg_applications_dir: dirs.user_dir.join(".local/share/applications"),
102            bash_completions_dir: dirs
103                .user_dir
104                .join(".local/share/bash-completion/completions"),
105            fish_completions_dir: dirs.user_dir.join(".config/fish/completions"),
106            zsh_completions_dir: dirs.user_dir.join(".local/share/zsh/site-functions"),
107        }
108    }
109}
110
111/// Convenience wrapper that holds all path types
112pub struct UpstreamPaths {
113    pub dirs: AppDirs,
114    pub config: ConfigPaths,
115    pub install: InstallPaths,
116    pub integration: IntegrationPaths,
117}
118
119impl Default for UpstreamPaths {
120    fn default() -> Self {
121        Self::new().expect("failed to determine upstream paths")
122    }
123}
124
125impl UpstreamPaths {
126    pub fn new() -> Result<Self> {
127        let dirs = AppDirs::new()?;
128        Ok(Self {
129            config: ConfigPaths::new(&dirs),
130            install: InstallPaths::new(&dirs),
131            integration: IntegrationPaths::new(&dirs),
132            dirs,
133        })
134    }
135}
136
137#[cfg(test)]
138mod tests {
139    use super::UpstreamPaths;
140
141    #[test]
142    fn upstream_paths_are_composed_from_base_directories() {
143        let paths = UpstreamPaths::new().expect("paths");
144
145        assert_eq!(
146            paths.config.config_file,
147            paths.dirs.config_dir.join("config.toml")
148        );
149        assert_eq!(
150            paths.config.packages_file,
151            paths.dirs.metadata_dir.join("packages.json")
152        );
153        assert_eq!(
154            paths.config.metadata_file,
155            paths.dirs.metadata_dir.join("metadata.json")
156        );
157        assert_eq!(
158            paths.config.paths_nu_file,
159            paths.dirs.metadata_dir.join("paths.nu")
160        );
161        assert_eq!(
162            paths.install.binaries_dir,
163            paths.dirs.packages_dir.join("binaries")
164        );
165        assert_eq!(
166            paths.dirs.packages_dir,
167            paths.dirs.data_dir.join("packages")
168        );
169        assert_eq!(paths.dirs.cache_dir, paths.dirs.data_dir.join("cache"));
170        assert_eq!(
171            paths.integration.symlinks_dir,
172            paths.dirs.data_dir.join("symlinks")
173        );
174        assert_eq!(
175            paths.integration.fish_completions_dir,
176            paths.dirs.user_dir.join(".config/fish/completions")
177        );
178        assert_eq!(
179            paths.install.rollback_dir,
180            paths.dirs.data_dir.join("rollback")
181        );
182        assert_eq!(paths.install.tmp_dir, paths.dirs.data_dir.join("tmp"));
183    }
184}