etcetera/base_strategy/windows.rs
1use std::path::{Path, PathBuf};
2
3use crate::HomeDirError;
4
5/// This strategy follows Windows’ conventions. It seems that all Windows GUI apps, and some command-line ones follow this pattern. The specification is available [here](https://docs.microsoft.com/en-us/windows/win32/shell/knownfolderid).
6///
7/// This initial example removes all the relevant environment variables to show the strategy’s use of the:
8/// - (on Windows) SHGetKnownFolderPath API.
9/// - (on non-Windows) Windows default directories.
10///
11/// ```
12/// use etcetera::base_strategy::BaseStrategy;
13/// use etcetera::base_strategy::Windows;
14/// use std::path::Path;
15///
16/// // Remove the environment variables that the strategy reads from.
17/// unsafe {
18/// std::env::remove_var("USERPROFILE");
19/// std::env::remove_var("APPDATA");
20/// std::env::remove_var("LOCALAPPDATA");
21/// }
22///
23/// let base_strategy = Windows::new().unwrap();
24///
25/// let home_dir = etcetera::home_dir().unwrap();
26///
27/// assert_eq!(
28/// base_strategy.home_dir(),
29/// &home_dir
30/// );
31/// assert_eq!(
32/// base_strategy.config_dir().strip_prefix(&home_dir),
33/// Ok(Path::new("AppData/Roaming/"))
34/// );
35/// assert_eq!(
36/// base_strategy.data_dir().strip_prefix(&home_dir),
37/// Ok(Path::new("AppData/Roaming/"))
38/// );
39/// assert_eq!(
40/// base_strategy.cache_dir().strip_prefix(&home_dir),
41/// Ok(Path::new("AppData/Local/"))
42/// );
43/// assert_eq!(
44/// base_strategy.state_dir(),
45/// None
46/// );
47/// assert_eq!(
48/// base_strategy.runtime_dir(),
49/// None
50/// );
51/// ```
52///
53/// This next example gives the environment variables values:
54///
55/// ```
56/// use etcetera::base_strategy::BaseStrategy;
57/// use etcetera::base_strategy::Windows;
58/// use std::path::Path;
59///
60/// let home_path = if cfg!(windows) {
61/// "C:\\foo\\".to_string()
62/// } else {
63/// etcetera::home_dir().unwrap().to_string_lossy().to_string()
64/// };
65/// let data_path = if cfg!(windows) {
66/// "C:\\bar\\"
67/// } else {
68/// "/bar/"
69/// };
70/// let cache_path = if cfg!(windows) {
71/// "C:\\baz\\"
72/// } else {
73/// "/baz/"
74/// };
75///
76/// unsafe {
77/// std::env::set_var("USERPROFILE", &home_path);
78/// std::env::set_var("APPDATA", data_path);
79/// std::env::set_var("LOCALAPPDATA", cache_path);
80/// }
81///
82/// let base_strategy = Windows::new().unwrap();
83///
84/// assert_eq!(
85/// base_strategy.home_dir(),
86/// Path::new(&home_path)
87/// );
88/// assert_eq!(
89/// base_strategy.config_dir(),
90/// Path::new(data_path)
91/// );
92/// assert_eq!(
93/// base_strategy.data_dir(),
94/// Path::new(data_path)
95/// );
96/// assert_eq!(
97/// base_strategy.cache_dir(),
98/// Path::new(cache_path)
99/// );
100/// assert_eq!(
101/// base_strategy.state_dir(),
102/// None
103/// );
104/// assert_eq!(
105/// base_strategy.runtime_dir(),
106/// None
107/// );
108/// ```
109
110#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
111pub struct Windows {
112 home_dir: PathBuf,
113}
114
115impl Windows {
116 /// Create a new Windows BaseStrategy
117 pub fn new() -> Result<Self, HomeDirError> {
118 Ok(Self {
119 home_dir: crate::home_dir()?,
120 })
121 }
122
123 fn dir_inner(env: &'static str) -> Option<PathBuf> {
124 std::env::var_os(env)
125 .filter(|s| !s.is_empty())
126 .map(PathBuf::from)
127 .or_else(|| Self::dir_crt(env))
128 }
129
130 // Ref: https://github.com/rust-lang/cargo/blob/home-0.5.11/crates/home/src/windows.rs
131 // We should keep this code in sync with the above.
132 #[cfg(all(windows, not(target_vendor = "uwp")))]
133 fn dir_crt(env: &'static str) -> Option<PathBuf> {
134 use std::ffi::OsString;
135 use std::os::windows::ffi::OsStringExt;
136 use std::ptr;
137 use std::slice;
138
139 use windows_sys::Win32::Foundation::S_OK;
140 use windows_sys::Win32::System::Com::CoTaskMemFree;
141 use windows_sys::Win32::UI::Shell::{
142 FOLDERID_LocalAppData, FOLDERID_RoamingAppData, KF_FLAG_DONT_VERIFY,
143 SHGetKnownFolderPath,
144 };
145
146 unsafe extern "C" {
147 fn wcslen(buf: *const u16) -> usize;
148 }
149
150 let folder_id = match env {
151 "APPDATA" => FOLDERID_RoamingAppData,
152 "LOCALAPPDATA" => FOLDERID_LocalAppData,
153 _ => return None,
154 };
155
156 unsafe {
157 let mut path = ptr::null_mut();
158 match SHGetKnownFolderPath(
159 &folder_id,
160 KF_FLAG_DONT_VERIFY as u32,
161 std::ptr::null_mut(),
162 &mut path,
163 ) {
164 S_OK => {
165 let path_slice = slice::from_raw_parts(path, wcslen(path));
166 let s = OsString::from_wide(path_slice);
167 CoTaskMemFree(path.cast());
168 Some(PathBuf::from(s))
169 }
170 _ => {
171 // Free any allocated memory even on failure. A null ptr is a no-op for `CoTaskMemFree`.
172 CoTaskMemFree(path.cast());
173 None
174 }
175 }
176 }
177 }
178
179 #[cfg(not(all(windows, not(target_vendor = "uwp"))))]
180 fn dir_crt(_env: &'static str) -> Option<PathBuf> {
181 None
182 }
183}
184
185impl super::BaseStrategy for Windows {
186 fn home_dir(&self) -> &Path {
187 &self.home_dir
188 }
189
190 fn config_dir(&self) -> PathBuf {
191 self.data_dir()
192 }
193
194 fn data_dir(&self) -> PathBuf {
195 Self::dir_inner("APPDATA").unwrap_or_else(|| self.home_dir.join("AppData").join("Roaming"))
196 }
197
198 fn cache_dir(&self) -> PathBuf {
199 Self::dir_inner("LOCALAPPDATA")
200 .unwrap_or_else(|| self.home_dir.join("AppData").join("Local"))
201 }
202
203 fn state_dir(&self) -> Option<PathBuf> {
204 None
205 }
206
207 fn runtime_dir(&self) -> Option<PathBuf> {
208 None
209 }
210}