project_dirs/strategy/
windows.rs

1use std::collections::HashMap;
2use std::path::{Path, PathBuf};
3
4use crate::{Directory, Project, ProjectDirs};
5
6#[cfg(target_os = "windows")]
7use crate::FullProjectDirs;
8
9#[cfg(target_os = "windows")]
10pub(crate) mod win_only {
11    use super::WindowsEnv;
12    use std::os::windows::ffi::OsStringExt;
13    use std::path::PathBuf;
14    use std::slice;
15    use windows_sys::Win32::UI::Shell;
16
17    use std::ffi::OsString;
18    use std::ffi::c_void;
19
20    /// Method copied from dirs-sys-rs. Better to do it, than to write a mem-leaking code by hand!
21    pub fn known_folder(folder_id: windows_sys::core::GUID) -> Option<PathBuf> {
22        unsafe {
23            let mut path_ptr: windows_sys::core::PWSTR = std::ptr::null_mut();
24            let result =
25                Shell::SHGetKnownFolderPath(&folder_id, 0, std::ptr::null_mut(), &mut path_ptr);
26            if result == 0 {
27                let len = windows_sys::Win32::Globalization::lstrlenW(path_ptr) as usize;
28                let path = slice::from_raw_parts(path_ptr, len);
29                let ostr: OsString = OsStringExt::from_wide(path);
30                windows_sys::Win32::System::Com::CoTaskMemFree(path_ptr as *const c_void);
31                Some(PathBuf::from(ostr))
32            } else {
33                windows_sys::Win32::System::Com::CoTaskMemFree(path_ptr as *const c_void);
34                None
35            }
36        }
37    }
38
39    impl WindowsEnv {
40        pub fn new_system() -> Self {
41            Self {
42                program_files: known_folder(Shell::FOLDERID_ProgramFiles),
43                program_data: known_folder(Shell::FOLDERID_ProgramData),
44                roaming_app_data: known_folder(Shell::FOLDERID_RoamingAppData),
45                local_app_data: known_folder(Shell::FOLDERID_LocalAppData),
46            }
47        }
48    }
49}
50
51fn join_win_path(path: &Path, project: &Project) -> PathBuf {
52    path.join(&project.organization_name)
53        .join(&project.application_name)
54}
55
56fn changing_data(result: &mut HashMap<Directory, PathBuf>, path: &Path, project: &Project) {
57    result.insert(Directory::Data, join_win_path(path, project).join("data"));
58    result.insert(Directory::Cache, join_win_path(path, project).join("cache"));
59    result.insert(Directory::Runtime, join_win_path(path, project).join("tmp"));
60    result.insert(Directory::State, join_win_path(path, project).join("state"));
61    result.insert(Directory::Log, join_win_path(path, project).join("logs"));
62}
63
64fn static_data(result: &mut HashMap<Directory, PathBuf>, path: &Path, project: &Project) {
65    result.insert(Directory::Bin, join_win_path(path, project).join("bin"));
66    result.insert(
67        Directory::Config,
68        join_win_path(path, project).join("config"),
69    );
70    result.insert(
71        Directory::Include,
72        join_win_path(path, project).join("include"),
73    );
74    result.insert(Directory::Lib, join_win_path(path, project).join("lib"));
75}
76
77/// Environment variables for [`Windows`] trait.
78#[derive(Debug, Clone, Default)]
79pub struct WindowsEnv {
80    pub program_files: Option<PathBuf>,
81    pub program_data: Option<PathBuf>,
82    pub roaming_app_data: Option<PathBuf>,
83    pub local_app_data: Option<PathBuf>,
84}
85
86pub const PROGRAM_FILES: &str = "%ProgramFiles%";
87pub const PROGRAM_DATA: &str = "%ProgramData%";
88pub const ROAMING_APP_DATA: &str = "%RoamingAppData%";
89pub const LOCAL_APP_DATA: &str = "%LocalAppData%";
90
91impl WindowsEnv {
92    pub fn extend_with_env(
93        &mut self,
94        other: impl Iterator<Item = (impl AsRef<str>, Option<impl AsRef<str>>)>,
95        allow_clearing: bool,
96    ) {
97        for (k, v) in other {
98            let pathbuf_new_value: Option<PathBuf> = v.and_then(|str_value| {
99                if !str_value.as_ref().is_empty() {
100                    Some(PathBuf::from(str_value.as_ref()))
101                } else {
102                    None
103                }
104            });
105            let str_key: &str = k.as_ref();
106
107            if allow_clearing || pathbuf_new_value.is_some() {
108                match str_key {
109                    PROGRAM_FILES => self.program_files = pathbuf_new_value,
110                    PROGRAM_DATA => self.program_data = pathbuf_new_value,
111                    ROAMING_APP_DATA => self.roaming_app_data = pathbuf_new_value,
112                    LOCAL_APP_DATA => self.local_app_data = pathbuf_new_value,
113                    _ => (),
114                }
115            };
116        }
117    }
118}
119
120/// Retrive [`ProjectDirs`] and [`FullProjectDirs`] for Windows based systems.
121pub trait Windows {
122    /// Returns the project directories on the current system (global installation)
123    #[cfg(target_family = "windows")]
124    fn windows_system(&self) -> Option<FullProjectDirs> {
125        self.windows_system_with_env(WindowsEnv::new_system())
126            .try_into()
127            .ok()
128    }
129
130    /// Returns the project directories for the current user on the current system (user
131    /// installation)
132    #[cfg(target_family = "windows")]
133    fn windows_user(&self) -> Option<FullProjectDirs> {
134        self.windows_user_with_env(WindowsEnv::new_system())
135            .try_into()
136            .ok()
137    }
138
139    /// Returns the project directories for the current user on the current system. Assumes that
140    /// nothing is shared across the domain (nothing is roamed)
141    #[cfg(target_family = "windows")]
142    fn windows_user_local(&self) -> Option<FullProjectDirs> {
143        self.windows_user_local_with_env(WindowsEnv::new_system())
144            .try_into()
145            .ok()
146    }
147
148    /// Returns the project directories for the current user on the current system. Assumes that
149    /// everything is shared across the domain
150    #[cfg(target_family = "windows")]
151    fn windows_user_shared(&self) -> Option<FullProjectDirs> {
152        self.windows_user_shared_with_env(WindowsEnv::new_system())
153            .try_into()
154            .ok()
155    }
156
157    /// Returns the project directories for the given environment (using %ProgramFiles%, and
158    /// %ProgramData%) variables.
159    fn windows_system_with_env(&self, env: WindowsEnv) -> ProjectDirs;
160
161    /// Returns the project directories for the given environment (using %RoamingAppData%, and
162    /// %LocalAppData%) variables.
163    fn windows_user_with_env(&self, env: WindowsEnv) -> ProjectDirs;
164
165    /// Returns the project directories for the given environment (using %LocalAppData%) variables.
166    fn windows_user_local_with_env(&self, env: WindowsEnv) -> ProjectDirs;
167
168    /// Returns the project directories for the given environment (using %RoamingAppData%) variables.
169    fn windows_user_shared_with_env(&self, env: WindowsEnv) -> ProjectDirs;
170}
171
172impl Windows for Project {
173    fn windows_system_with_env(&self, env: WindowsEnv) -> ProjectDirs {
174        let mut result = HashMap::new();
175
176        if let Some(static_data_dir) = env.program_files {
177            static_data(&mut result, &static_data_dir, self);
178        }
179
180        if let Some(changing_data_dir) = env.program_data {
181            changing_data(&mut result, &changing_data_dir, self);
182        }
183
184        ProjectDirs::new(result)
185    }
186
187    fn windows_user_with_env(&self, env: WindowsEnv) -> ProjectDirs {
188        let mut result = HashMap::new();
189
190        if let Some(static_data_dir) = env.roaming_app_data {
191            static_data(&mut result, &static_data_dir, self);
192        }
193
194        if let Some(changing_data_dir) = env.local_app_data {
195            changing_data(&mut result, &changing_data_dir, self);
196        }
197
198        ProjectDirs::new(result)
199    }
200
201    fn windows_user_local_with_env(&self, env: WindowsEnv) -> ProjectDirs {
202        let mut result = HashMap::new();
203        if let Some(data_dir) = env.local_app_data {
204            changing_data(&mut result, &data_dir, self);
205            static_data(&mut result, &data_dir, self);
206            result.insert(Directory::ProjectRoot, data_dir);
207        }
208        ProjectDirs::new(result)
209    }
210
211    fn windows_user_shared_with_env(&self, env: WindowsEnv) -> ProjectDirs {
212        let mut result = HashMap::new();
213        if let Some(data_dir) = env.roaming_app_data {
214            changing_data(&mut result, &data_dir, self);
215            static_data(&mut result, &data_dir, self);
216            result.insert(Directory::ProjectRoot, data_dir);
217        }
218        ProjectDirs::new(result)
219    }
220}