1pub(crate) mod desktop_app;
20pub mod desktop_commands;
21pub mod environment;
22mod hidden_input;
23pub(crate) mod install;
24pub(crate) mod net;
25pub(crate) mod path_serde;
26pub(crate) mod paths;
27pub(crate) mod program;
28#[cfg(unix)]
29mod unix;
30#[cfg(windows)]
31mod windows;
32
33#[cfg(unix)]
34use unix as os;
35#[cfg(windows)]
36use windows as os;
37
38pub(crate) use os::{
39 CRLF_LINE_ENDINGS, Child, ENV_NAMES_IGNORE_CASE, HOME_VARIABLE, PrivateTempDir,
40 STATE_VARIABLES, TEMP_VARIABLES, USER_VARIABLES, create_private_dir, create_private_dir_all,
41 exit_signal, file_identity, hide_console_window, home_directory, host_name, ipc, is_owner_only,
42 is_private_dir, is_private_file, kill_saved_group, kill_saved_session, path_from_bytes,
43 private_options, private_tempdir_in, process_start_time, release_free_memory,
44 remove_abandoned_directory, resolve_system_aliases, restrict_file, running_process_start_time,
45 spawn_detached, spawn_gated, spawn_now, spawn_run, spawn_watched, sync_dir,
46 transient_file_access,
47};
48pub use os::{HiddenInput, VISIBLE_INPUT_FALLBACK, stop_requested};
49pub(crate) use os::{PRIVATE_DIRECTORY, PRIVATE_FILE};
50
51#[derive(Debug, Clone)]
56pub struct Footprint {
57 _release: std::sync::Arc<Release>,
58}
59
60impl Footprint {
61 pub(crate) fn new() -> Self {
62 Self {
63 _release: std::sync::Arc::new(Release),
64 }
65 }
66}
67
68#[derive(Debug)]
69struct Release;
70
71impl Drop for Release {
72 fn drop(&mut self) {
73 #[cfg(test)]
74 RELEASES.with(|count| count.set(count.get() + 1));
75 release_free_memory();
76 }
77}
78
79#[cfg(test)]
80thread_local! {
81 static RELEASES: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
82}
83
84#[cfg(test)]
86pub(crate) fn releases() -> usize {
87 RELEASES.with(std::cell::Cell::get)
88}
89
90#[derive(Debug, Clone, Copy, PartialEq, Eq)]
93pub(crate) struct FileIdentity {
94 volume: u64,
95 index: u64,
96}
97
98#[derive(Debug, Clone, Copy, PartialEq, Eq)]
100pub(crate) enum ProcessEnd {
101 Exited(i32),
103 Signaled(Option<i32>),
105}
106
107pub(crate) fn process_end(status: &std::process::ExitStatus) -> ProcessEnd {
109 match status.code() {
110 Some(code) => ProcessEnd::Exited(code),
111 None => ProcessEnd::Signaled(exit_signal(status)),
112 }
113}
114
115pub(crate) fn exit_description(status: &std::process::ExitStatus) -> String {
118 status.to_string()
119}
120
121pub(crate) const FILE_LOCK_RETRY_INTERVAL: std::time::Duration =
124 std::time::Duration::from_millis(25);
125
126pub(crate) use artifactize_tools::files::{
127 EntryName, FileKind, canonicalize, entry_kind, link_target, open_directory, open_entry,
128 open_no_follow, read_dir,
129};
130pub use artifactize_tools::files::{open_nonblocking, path_text};
131
132pub fn file_uri_path(uri: &str) -> Option<std::path::PathBuf> {
135 url::Url::parse(uri)
136 .ok()?
137 .to_file_path()
138 .ok()
139 .filter(|path| path.is_absolute())
140}
141
142pub fn absolute_folder(text: &str) -> Option<std::path::PathBuf> {
145 Some(std::path::PathBuf::from(text)).filter(|path| path.is_absolute())
146}
147
148pub fn open_regular(path: &std::path::Path) -> std::io::Result<std::fs::File> {
151 let file = open_no_follow(std::fs::OpenOptions::new().read(true), path)?;
152 if file.metadata()?.is_file() {
153 Ok(file)
154 } else {
155 Err(std::io::Error::new(
156 std::io::ErrorKind::InvalidInput,
157 "not a regular file",
158 ))
159 }
160}
161
162pub(crate) fn path_from_line(bytes: &[u8]) -> std::path::PathBuf {
165 let path = if CRLF_LINE_ENDINGS {
166 bytes
167 .strip_suffix(b"\r\n")
168 .or_else(|| bytes.strip_suffix(b"\n"))
169 .unwrap_or(bytes)
170 } else {
171 bytes.strip_suffix(b"\n").unwrap_or(bytes)
172 };
173 path_from_bytes(path)
174}
175
176pub(crate) fn state_directory() -> Option<std::path::PathBuf> {
178 resolve_state_directory(
179 environment::var("ARTIFACTIZE_STATE_HOME").map(Into::into),
180 STATE_VARIABLES
181 .iter()
182 .find_map(|name| environment::var(name).filter(|v| !v.is_empty()))
183 .map(Into::into),
184 home_directory(),
185 )
186}
187
188fn resolve_state_directory(
189 artifactize: Option<std::path::PathBuf>,
190 state: Option<std::path::PathBuf>,
191 home: Option<std::path::PathBuf>,
192) -> Option<std::path::PathBuf> {
193 artifactize
194 .filter(|p| !p.as_os_str().is_empty())
195 .or_else(|| {
196 state
197 .filter(|p| !p.as_os_str().is_empty())
198 .map(|p| p.join("artifactize"))
199 })
200 .or_else(|| {
201 home.filter(|p| !p.as_os_str().is_empty())
202 .map(|p| p.join(".local/state/artifactize"))
203 })
204}
205
206pub(crate) fn user_name() -> Option<String> {
208 USER_VARIABLES
209 .iter()
210 .find_map(|name| environment::var_text(name).filter(|value| !value.is_empty()))
211}
212
213pub(crate) fn fingerprint_line_ending(stdout: &[u8]) -> (&[u8], &'static str) {
215 if CRLF_LINE_ENDINGS {
216 (
217 stdout
218 .strip_suffix(b"\r\n")
219 .or_else(|| stdout.strip_suffix(b"\n"))
220 .unwrap_or(stdout),
221 "LF or CRLF",
222 )
223 } else {
224 (stdout.strip_suffix(b"\n").unwrap_or(stdout), "LF")
225 }
226}
227
228pub(crate) fn path_kind(path: &std::path::Path) -> std::io::Result<FileKind> {
230 match (path.parent(), path.file_name()) {
231 (Some(parent), Some(name)) => entry_kind(&open_directory(parent)?, name),
232 _ => open_directory(path).map(|_| FileKind::Directory),
233 }
234}
235
236pub(crate) fn program_text(command: &str) -> String {
238 path_text(std::path::Path::new(command))
239}
240
241pub(crate) fn serialize_program<S: serde::Serializer>(
243 command: &str,
244 serializer: S,
245) -> Result<S::Ok, S::Error> {
246 serializer.serialize_str(&program_text(command))
247}
248
249pub(crate) fn serialize_paths<S: serde::Serializer>(
251 paths: &[std::path::PathBuf],
252 serializer: S,
253) -> Result<S::Ok, S::Error> {
254 use serde::Serialize;
255 paths
256 .iter()
257 .map(|path| path_text(path))
258 .collect::<Vec<_>>()
259 .serialize(serializer)
260}
261
262#[cfg(test)]
263mod process_tests;
264
265#[cfg(test)]
266mod program_tests {
267 #[test]
268 fn a_declared_program_is_written_with_forward_slashes() {
269 assert_eq!(super::program_text("pnpm"), "pnpm");
270 assert_eq!(super::program_text("tools/run.sh"), "tools/run.sh");
271 #[cfg(windows)]
272 assert_eq!(super::program_text(r"tools\run.cmd"), "tools/run.cmd");
273 }
274}
275
276#[cfg(test)]
277mod state_directory_tests {
278 use super::*;
279 use std::path::PathBuf;
280
281 #[test]
282 fn state_home_precedence_and_empty_values() {
283 let artifactize = PathBuf::from("custom/state");
284 let state = PathBuf::from("state");
285 let home = PathBuf::from("reviewer");
286 assert_eq!(
287 resolve_state_directory(
288 Some(artifactize.clone()),
289 Some(state.clone()),
290 Some(home.clone())
291 ),
292 Some(artifactize)
293 );
294 assert_eq!(
295 resolve_state_directory(None, Some(state.clone()), Some(home.clone())),
296 Some(state.join("artifactize"))
297 );
298 assert_eq!(
299 resolve_state_directory(None, None, Some(home.clone())),
300 Some(home.join(".local/state/artifactize"))
301 );
302 let empty = Some(PathBuf::new());
303 assert_eq!(
304 resolve_state_directory(empty.clone(), empty.clone(), Some(home.clone())),
305 Some(home.join(".local/state/artifactize"))
306 );
307 assert_eq!(
308 resolve_state_directory(empty.clone(), empty.clone(), empty),
309 None
310 );
311 assert_eq!(resolve_state_directory(None, None, None), None);
312 }
313}
314
315mod console;
316pub use console::pause_after_help;
317
318mod interaction;
319pub(crate) use interaction::{is_within, label, paths_equal, private_anonymous_file};
320
321pub(crate) fn entry_exists(path: &std::path::Path) -> std::io::Result<bool> {
323 let Some(parent) = path.parent() else {
324 return Ok(false);
325 };
326 let directory = match open_directory(parent) {
327 Ok(directory) => directory,
328 Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(false),
329 Err(error) => return Err(error),
330 };
331 let Some(name) = path.file_name() else {
332 return Ok(false);
333 };
334 match entry_kind(&directory, name) {
335 Ok(_) => Ok(true),
336 Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(false),
337 Err(error) => Err(error),
338 }
339}
340
341pub(crate) fn marker_metadata(path: &std::path::Path) -> std::io::Result<std::fs::Metadata> {
346 path.symlink_metadata()
347}