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, spawn_windowless, 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 temporary_directory() -> std::path::PathBuf {
178 std::env::temp_dir()
179}
180
181pub(crate) fn state_directory() -> Option<std::path::PathBuf> {
183 resolve_state_directory(
184 environment::var("ARTIFACTIZE_STATE_HOME").map(Into::into),
185 STATE_VARIABLES
186 .iter()
187 .find_map(|name| environment::var(name).filter(|v| !v.is_empty()))
188 .map(Into::into),
189 home_directory(),
190 )
191}
192
193fn resolve_state_directory(
194 artifactize: Option<std::path::PathBuf>,
195 state: Option<std::path::PathBuf>,
196 home: Option<std::path::PathBuf>,
197) -> Option<std::path::PathBuf> {
198 artifactize
199 .filter(|p| !p.as_os_str().is_empty())
200 .or_else(|| {
201 state
202 .filter(|p| !p.as_os_str().is_empty())
203 .map(|p| p.join("artifactize"))
204 })
205 .or_else(|| {
206 home.filter(|p| !p.as_os_str().is_empty())
207 .map(|p| p.join(".local/state/artifactize"))
208 })
209}
210
211pub(crate) fn user_name() -> Option<String> {
213 USER_VARIABLES
214 .iter()
215 .find_map(|name| environment::var_text(name).filter(|value| !value.is_empty()))
216}
217
218pub(crate) fn fingerprint_line_ending(stdout: &[u8]) -> (&[u8], &'static str) {
220 if CRLF_LINE_ENDINGS {
221 (
222 stdout
223 .strip_suffix(b"\r\n")
224 .or_else(|| stdout.strip_suffix(b"\n"))
225 .unwrap_or(stdout),
226 "LF or CRLF",
227 )
228 } else {
229 (stdout.strip_suffix(b"\n").unwrap_or(stdout), "LF")
230 }
231}
232
233pub(crate) fn path_kind(path: &std::path::Path) -> std::io::Result<FileKind> {
235 match (path.parent(), path.file_name()) {
236 (Some(parent), Some(name)) => entry_kind(&open_directory(parent)?, name),
237 _ => open_directory(path).map(|_| FileKind::Directory),
238 }
239}
240
241pub(crate) fn program_text(command: &str) -> String {
243 path_text(std::path::Path::new(command))
244}
245
246pub(crate) fn serialize_program<S: serde::Serializer>(
248 command: &str,
249 serializer: S,
250) -> Result<S::Ok, S::Error> {
251 serializer.serialize_str(&program_text(command))
252}
253
254pub(crate) fn serialize_paths<S: serde::Serializer>(
256 paths: &[std::path::PathBuf],
257 serializer: S,
258) -> Result<S::Ok, S::Error> {
259 use serde::Serialize;
260 paths
261 .iter()
262 .map(|path| path_text(path))
263 .collect::<Vec<_>>()
264 .serialize(serializer)
265}
266
267#[cfg(test)]
268mod process_tests;
269
270#[cfg(test)]
271mod program_tests {
272 #[test]
273 fn a_declared_program_is_written_with_forward_slashes() {
274 assert_eq!(super::program_text("pnpm"), "pnpm");
275 assert_eq!(super::program_text("tools/run.sh"), "tools/run.sh");
276 #[cfg(windows)]
277 assert_eq!(super::program_text(r"tools\run.cmd"), "tools/run.cmd");
278 }
279}
280
281#[cfg(test)]
282mod state_directory_tests {
283 use super::*;
284 use std::path::PathBuf;
285
286 #[test]
287 fn state_home_precedence_and_empty_values() {
288 let artifactize = PathBuf::from("custom/state");
289 let state = PathBuf::from("state");
290 let home = PathBuf::from("reviewer");
291 assert_eq!(
292 resolve_state_directory(
293 Some(artifactize.clone()),
294 Some(state.clone()),
295 Some(home.clone())
296 ),
297 Some(artifactize)
298 );
299 assert_eq!(
300 resolve_state_directory(None, Some(state.clone()), Some(home.clone())),
301 Some(state.join("artifactize"))
302 );
303 assert_eq!(
304 resolve_state_directory(None, None, Some(home.clone())),
305 Some(home.join(".local/state/artifactize"))
306 );
307 let empty = Some(PathBuf::new());
308 assert_eq!(
309 resolve_state_directory(empty.clone(), empty.clone(), Some(home.clone())),
310 Some(home.join(".local/state/artifactize"))
311 );
312 assert_eq!(
313 resolve_state_directory(empty.clone(), empty.clone(), empty),
314 None
315 );
316 assert_eq!(resolve_state_directory(None, None, None), None);
317 }
318}
319
320mod console;
321pub use console::pause_after_help;
322
323mod interaction;
324pub(crate) use interaction::{is_within, label, paths_equal, private_anonymous_file};
325
326pub(crate) fn entry_exists(path: &std::path::Path) -> std::io::Result<bool> {
328 let Some(parent) = path.parent() else {
329 return Ok(false);
330 };
331 let directory = match open_directory(parent) {
332 Ok(directory) => directory,
333 Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(false),
334 Err(error) => return Err(error),
335 };
336 let Some(name) = path.file_name() else {
337 return Ok(false);
338 };
339 match entry_kind(&directory, name) {
340 Ok(_) => Ok(true),
341 Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(false),
342 Err(error) => Err(error),
343 }
344}
345
346pub(crate) fn marker_metadata(path: &std::path::Path) -> std::io::Result<std::fs::Metadata> {
351 path.symlink_metadata()
352}