mirror of
https://github.com/storytold/photocraft.git
synced 2026-10-24 09:57:08 -06:00
fix(linux): keep crash-recovery autosaves in $XDG_STATE_HOME, out of the config folder (#6612) (#6941)
* fix(linux): keep crash-recovery autosaves in $XDG_STATE_HOME, out of the config folder (#6612) * test(linux): cover the XDG state recovery folder (#6612)
This commit is contained in:
@@ -10,8 +10,8 @@
|
||||
//! 3. The platform convention: macOS `~/Library/Application Support/Photocraft`, Windows
|
||||
//! `%APPDATA%\Photocraft`, Linux `$XDG_CONFIG_HOME/photocraft` or `~/.config/photocraft`.
|
||||
//!
|
||||
//! One exception: on Windows the platform convention keeps crash-recovery autosaves out of the
|
||||
//! roaming profile ([`recovery_dir`]).
|
||||
//! One exception: the platform convention keeps crash-recovery autosaves out of what is synced
|
||||
//! with the settings: the Windows roaming profile and the Linux config folder ([`recovery_dir`]).
|
||||
|
||||
use std::ffi::OsString;
|
||||
use std::path::{Path, PathBuf};
|
||||
@@ -77,18 +77,35 @@ pub fn recovery_dir() -> Option<PathBuf> {
|
||||
})
|
||||
}
|
||||
|
||||
/// The recovery folder for `data` (#5648). Autosaves hold whole documents and are rewritten every
|
||||
/// few minutes, so on Windows the platform convention puts them in the machine-local
|
||||
/// `%LOCALAPPDATA%\Photocraft\Recovery`: the roaming `%APPDATA%` may be synced at sign-in and
|
||||
/// sign-out or capped by policy. They stay out of `%TEMP%`, which cleanup tools may empty before
|
||||
/// a crashed document is recovered. The override and portable folders keep everything in one
|
||||
/// place, and other platforms already keep these folders local.
|
||||
/// The recovery folder for `data`. Autosaves hold whole documents and are rewritten every few
|
||||
/// minutes, so with the platform convention they stay out of what is synced with the settings:
|
||||
/// - Windows (#5648): the machine-local `%LOCALAPPDATA%\Photocraft\Recovery`; the roaming
|
||||
/// `%APPDATA%` may be synced at sign-in and sign-out or capped by policy.
|
||||
/// - Linux and other XDG systems (#6612): `$XDG_STATE_HOME/photocraft/Recovery`, by default
|
||||
/// `~/.local/state/photocraft/Recovery`, where the XDG Base Directory spec puts state that
|
||||
/// should outlive a restart but isn't configuration; dotfile managers and config sync often copy
|
||||
/// `~/.config` whole. As the spec asks, a relative `XDG_STATE_HOME` is ignored.
|
||||
/// - macOS: `~/Library/Application Support/Photocraft/Recovery` as before. Apple's guidelines put
|
||||
/// app-created data that can't be regenerated there; `~/Library/Caches` may be purged by the
|
||||
/// system.
|
||||
///
|
||||
/// They are never in a temp or cache folder, which cleanup tools may empty before a crashed
|
||||
/// document is recovered. The override and portable folders keep everything in one place.
|
||||
pub fn resolve_recovery(data: &DataDir, env: impl Fn(&str) -> Option<OsString>) -> Option<PathBuf> {
|
||||
if cfg!(windows)
|
||||
&& data.mode == Mode::Platform
|
||||
&& let Some(local) = env("LOCALAPPDATA").filter(|l| !l.is_empty())
|
||||
{
|
||||
return Some(PathBuf::from(local).join("Photocraft").join(RECOVERY));
|
||||
if data.mode == Mode::Platform {
|
||||
if cfg!(windows) {
|
||||
if let Some(local) = env("LOCALAPPDATA").filter(|l| !l.is_empty()) {
|
||||
return Some(PathBuf::from(local).join("Photocraft").join(RECOVERY));
|
||||
}
|
||||
} else if !cfg!(target_os = "macos") {
|
||||
let state = env("XDG_STATE_HOME")
|
||||
.map(PathBuf::from)
|
||||
.filter(|s| s.is_absolute())
|
||||
.or_else(|| env("HOME").filter(|h| !h.is_empty()).map(|h| PathBuf::from(h).join(".local").join("state")));
|
||||
if let Some(state) = state {
|
||||
return Some(state.join("photocraft").join(RECOVERY));
|
||||
}
|
||||
}
|
||||
}
|
||||
data.dir.as_ref().map(|d| d.join(RECOVERY))
|
||||
}
|
||||
@@ -623,6 +640,34 @@ mod tests {
|
||||
let _ = std::fs::remove_dir_all(&root);
|
||||
}
|
||||
|
||||
/// #6612: on Linux (and other XDG systems) autosaves go to `$XDG_STATE_HOME`, by default
|
||||
/// `~/.local/state`, not the config folder; a relative `XDG_STATE_HOME` is ignored. Windows
|
||||
/// and macOS don't read it, and an override or portable folder keeps them inside itself.
|
||||
#[test]
|
||||
fn autosaves_go_to_the_xdg_state_folder() {
|
||||
let root = temp("recovery-xdg");
|
||||
let settings = root.join("home").join(".config").join("photocraft");
|
||||
let home = root.join("home");
|
||||
let state = root.join("state");
|
||||
let platform = DataDir { dir: Some(settings.clone()), mode: Mode::Platform, warning: None };
|
||||
let xdg = !cfg!(windows) && !cfg!(target_os = "macos");
|
||||
let expect = |xdg_path: PathBuf| Some(if xdg { xdg_path } else { settings.join("Recovery") });
|
||||
assert_eq!(resolve_recovery(&platform, env_of(&[("XDG_STATE_HOME", &state), ("HOME", &home)])), expect(state.join("photocraft").join("Recovery")));
|
||||
let default = home.join(".local").join("state").join("photocraft").join("Recovery");
|
||||
assert_eq!(resolve_recovery(&platform, env_of(&[("HOME", &home)])), expect(default.clone()));
|
||||
let relative = PathBuf::from("relative/state");
|
||||
assert_eq!(resolve_recovery(&platform, env_of(&[("XDG_STATE_HOME", &relative), ("HOME", &home)])), expect(default.clone()));
|
||||
let empty = PathBuf::new();
|
||||
assert_eq!(resolve_recovery(&platform, env_of(&[("XDG_STATE_HOME", &empty), ("HOME", &home)])), expect(default));
|
||||
// No home at all: beside the settings, as before.
|
||||
assert_eq!(resolve_recovery(&platform, |_| None), Some(settings.join("Recovery")));
|
||||
for mode in [Mode::Override, Mode::Portable] {
|
||||
let d = DataDir { dir: Some(settings.clone()), mode, warning: None };
|
||||
assert_eq!(resolve_recovery(&d, env_of(&[("XDG_STATE_HOME", &state), ("HOME", &home)])), Some(settings.join("Recovery")), "{mode:?}");
|
||||
}
|
||||
let _ = std::fs::remove_dir_all(&root);
|
||||
}
|
||||
|
||||
/// An autosave bundle and its sidecar, as `format::Autosaver` writes them.
|
||||
fn autosave(dir: &Path, key: &str) {
|
||||
std::fs::create_dir_all(dir.join(format!("{key}.pcraft"))).unwrap();
|
||||
|
||||
@@ -293,7 +293,7 @@ fn block_on<T>(future: impl Future<Output = T>) -> T {
|
||||
|
||||
/// Per-user settings directory: `PHOTOCRAFT_CONFIG_DIR`, else `<exe dir>/PhotoCraftData` in
|
||||
/// portable mode, else the platform convention. Everything the app persists lives under it
|
||||
/// except, on Windows, crash-recovery autosaves; see [`crate::app_dirs`].
|
||||
/// except, on Windows and Linux, crash-recovery autosaves; see [`crate::app_dirs`].
|
||||
pub fn config_dir() -> Option<PathBuf> {
|
||||
crate::app_dirs::config_dir()
|
||||
}
|
||||
@@ -308,8 +308,9 @@ pub fn presets_dir() -> Option<PathBuf> {
|
||||
config_dir().map(|d| d.join("Presets"))
|
||||
}
|
||||
|
||||
/// Crash-recovery autosaves: under the settings directory, except on Windows, where they stay
|
||||
/// out of the roaming profile (see [`crate::app_dirs::recovery_dir`]).
|
||||
/// Crash-recovery autosaves: under the settings directory, except where that is synced with the
|
||||
/// settings: the Windows roaming profile and the Linux config folder (see
|
||||
/// [`crate::app_dirs::resolve_recovery`]).
|
||||
fn recovery_dir() -> Option<PathBuf> {
|
||||
crate::app_dirs::recovery_dir()
|
||||
}
|
||||
|
||||
@@ -248,7 +248,9 @@ The desktop app stores them in `preferences.json` in the platform config directo
|
||||
`~/Library/Application Support/Photocraft`, Windows `%APPDATA%\Photocraft`, Linux
|
||||
`$XDG_CONFIG_HOME/photocraft`; override with `PHOTOCRAFT_CONFIG_DIR`); autosaves go to its
|
||||
`Recovery` folder (on Windows to `%LOCALAPPDATA%\Photocraft\Recovery`, out of the roaming
|
||||
profile; autosaves left by earlier versions move there at startup), and the window size and
|
||||
profile, and on Linux to `$XDG_STATE_HOME/photocraft/Recovery`, by default
|
||||
`~/.local/state/photocraft/Recovery`, out of the config folder; autosaves left by earlier
|
||||
versions move there at startup), and the window size and
|
||||
position, the dock width and floating-panel geometry
|
||||
to `ui.ron` beside it (a file holding values that would crash startup is discarded). In portable mode (a `portable.txt` or `PhotoCraft.portable` file beside the
|
||||
executable, as in the Windows portable zip) that directory is `PhotoCraftData` next to the
|
||||
|
||||
Reference in New Issue
Block a user