project_dirs/strategy/
windows.rs1use 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 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#[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
120pub trait Windows {
122 #[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 #[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 #[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 #[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 fn windows_system_with_env(&self, env: WindowsEnv) -> ProjectDirs;
160
161 fn windows_user_with_env(&self, env: WindowsEnv) -> ProjectDirs;
164
165 fn windows_user_local_with_env(&self, env: WindowsEnv) -> ProjectDirs;
167
168 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}