Resolve every base directory in one place, and on Windows
Five sites each read XDG_*_HOME and fell back to $HOME/.local/… on their own, which is fine on Linux and wrong everywhere else: Windows sets neither variable, so every one of them degraded to a path relative to the working directory — for a Start Menu launch, C:\Windows\System32. The models lookup walked XDG_DATA_DIRS the same way. dr_plat::dirs now holds the rule per platform: XDG on Unix, the known folders on Windows — %APPDATA% for config, which roams, and %LOCALAPPDATA% for data and state, which do not — and the executable's own directory as the system data dir, which is where the installer puts the models. The Android overrides stay where they were; only the fallback behind them moved. Both rule sets are unit-tested on either host, and the Windows one was confirmed by running the application under Wine: its log landed in AppData\Local\darkroom\state and nothing was written anywhere else.
This commit is contained in:
@@ -44,8 +44,7 @@
|
||||
//! reason [`crate::diagnostics`] redacts at the sink rather than trusting call
|
||||
//! sites: everything written here is readable by anyone holding the device.
|
||||
|
||||
use std::ffi::OsString;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
/// Declared once by the platform entry point; a guess otherwise.
|
||||
@@ -72,66 +71,13 @@ pub fn state_dir() -> PathBuf {
|
||||
if let Some(dir) = STATE_DIR.get() {
|
||||
return dir.clone();
|
||||
}
|
||||
xdg_state_dir(std::env::var_os("XDG_STATE_HOME"), std::env::var_os("HOME"))
|
||||
}
|
||||
|
||||
/// The XDG resolution, as a function of its inputs rather than of the process
|
||||
/// environment, so it can be tested without `set_var` racing every other test
|
||||
/// in the binary.
|
||||
///
|
||||
/// Relative values are ignored rather than resolved against the working
|
||||
/// directory: the base-directory specification says so explicitly, and the
|
||||
/// alternative is a `darkroom/` directory appearing wherever the app was
|
||||
/// launched from.
|
||||
fn xdg_state_dir(xdg_state_home: Option<OsString>, home: Option<OsString>) -> PathBuf {
|
||||
xdg_state_home
|
||||
.filter(|value| Path::new(value).is_absolute())
|
||||
.map(PathBuf::from)
|
||||
.or_else(|| {
|
||||
home.filter(|value| Path::new(value).is_absolute())
|
||||
.map(|value| PathBuf::from(value).join(".local/state"))
|
||||
})
|
||||
// A container or a systemd unit with neither variable set. Writing a
|
||||
// log into `/tmp` is a poor outcome; refusing to log at all, on the
|
||||
// one kind of machine nobody is sitting in front of, is a worse one.
|
||||
.unwrap_or_else(std::env::temp_dir)
|
||||
.join("darkroom")
|
||||
crate::dirs::base_dir(crate::dirs::Base::State)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn the_log_goes_under_the_state_directory_the_user_named() {
|
||||
// NFR-OPS-1 says "the XDG state directory", and honouring
|
||||
// $XDG_STATE_HOME is the whole of what that means to a user who has
|
||||
// moved theirs.
|
||||
let dir = xdg_state_dir(Some("/var/lib/dr".into()), Some("/home/someone".into()));
|
||||
assert_eq!(dir, PathBuf::from("/var/lib/dr/darkroom"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn without_the_variable_it_is_the_specifications_default() {
|
||||
let dir = xdg_state_dir(None, Some("/home/someone".into()));
|
||||
assert_eq!(dir, PathBuf::from("/home/someone/.local/state/darkroom"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_relative_value_is_ignored_rather_than_resolved() {
|
||||
// The specification requires this, and the failure it prevents is a
|
||||
// `darkroom/` directory appearing in whatever the working directory
|
||||
// happened to be — including, on a desktop launcher, `/`.
|
||||
let dir = xdg_state_dir(Some("state".into()), Some("/home/someone".into()));
|
||||
assert_eq!(dir, PathBuf::from("/home/someone/.local/state/darkroom"));
|
||||
|
||||
let dir = xdg_state_dir(Some("".into()), Some("".into()));
|
||||
assert!(
|
||||
dir.is_absolute(),
|
||||
"an empty HOME must not produce a relative state directory"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_declared_directory_is_declared_once() {
|
||||
// The property the entry points rely on: two callers cannot split the
|
||||
|
||||
Reference in New Issue
Block a user