Compare commits

...
Author SHA1 Message Date
bflatasticandClaude Opus 5.5 3e40d9b1f1 Preset store: a missing store folder no longer warns about actions.json (Windows)
Opening the preset store read actions.json unconditionally and decided "the file is missing"
by matching the error text ("not found", "no such file", "os error 2"). On Windows, a file in a
folder that doesn't exist yet reports "The system cannot find the path specified (os error 3)",
so a fresh install (store folder created on the first save) got a spurious
"actions: actions.json skipped" warning at startup, and two engine tests failed on Windows.

actions.json is now read only when the store lists it, like index.json already was; the
error-text heuristic is gone.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-08 06:08:26 +07:00
2 changed files with 41 additions and 9 deletions

No files matched your search

+6 -9
View File
@@ -425,7 +425,10 @@ pub fn open(backend: Box<dyn PresetBackend>) -> Opened {
warnings.push(format!("brush presets: {e}"));
Vec::new()
});
let index: Option<IndexFile> = if files.iter().any(|(n, _)| n == INDEX_FILE) {
// Optional files are read only when listed: what a read of a missing file (or of a file in a
// store folder that doesn't exist yet) reports differs per platform and backend.
let listed = |name: &str| files.iter().any(|(n, _)| n == name);
let index: Option<IndexFile> = if listed(INDEX_FILE) {
match backend.read(INDEX_FILE, MAX_INDEX_BYTES).and_then(|b| serde_json::from_slice(&b).map_err(|e| e.to_string())) {
Ok(i) => Some(i),
Err(e) => {
@@ -489,7 +492,7 @@ pub fn open(backend: Box<dyn PresetBackend>) -> Opened {
}
}
let actions = load_actions(backend.as_ref(), &mut warnings);
let actions = if listed(ACTIONS_FILE) { load_actions(backend.as_ref(), &mut warnings) } else { Vec::new() };
let mut store = PresetStore { backend, synced_rev: None, groups: HashMap::new(), tips: HashMap::new(), index: None, actions_rev: 0, warnings: Vec::new() };
let mut presets = Vec::new();
for (file, size, g) in parsed {
@@ -532,11 +535,10 @@ pub fn open(backend: Box<dyn PresetBackend>) -> Opened {
Opened { store, presets, hidden_builtins, order, warnings, actions }
}
/// `actions.json`: a missing file is an empty list. Anything else unreadable is a warning.
/// `actions.json`, read when the store lists it. Anything unreadable is a warning.
fn load_actions(backend: &dyn PresetBackend, warnings: &mut Vec<String>) -> Vec<crate::actions_cmds::Action> {
let bytes = match backend.read(ACTIONS_FILE, MAX_ACTIONS_BYTES) {
Ok(b) => b,
Err(e) if missing_file(&e) => return Vec::new(),
Err(e) => {
warnings.push(format!("actions: {ACTIONS_FILE} skipped: {e}"));
return Vec::new();
@@ -555,11 +557,6 @@ fn load_actions(backend: &dyn PresetBackend, warnings: &mut Vec<String>) -> Vec<
}
}
fn missing_file(err: &str) -> bool {
let lower = err.to_ascii_lowercase();
lower.contains("not found") || lower.contains("no such file") || lower.contains("os error 2")
}
/// Read and decode tips, in parallel on native targets.
fn decode_tips(backend: &dyn PresetBackend, hashes: &[String], sizes: &HashMap<String, u64>) -> Vec<Result<GrayTile, String>> {
let read = |h: &String| -> Result<Vec<u8>, String> {
+35
View File
@@ -320,6 +320,41 @@ fn write_failures_are_warnings() {
let _ = std::fs::remove_file(&dir.0);
}
/// A store whose folder doesn't exist yet: nothing is listed and every read fails the way Windows
/// reports a file in a missing folder (os error 3, not "not found").
struct NoFolder;
impl PresetBackend for NoFolder {
fn list(&self) -> Result<Vec<(String, u64)>, String> {
Ok(Vec::new())
}
fn read(&self, name: &str, _max: u64) -> Result<Vec<u8>, String> {
Err(format!("{name}: The system cannot find the path specified. (os error 3)"))
}
fn write(&self, _name: &str, _bytes: &[u8]) -> Result<(), String> {
Ok(())
}
fn remove(&self, _name: &str) -> Result<(), String> {
Ok(())
}
}
#[test]
fn a_store_folder_that_does_not_exist_yet_loads_without_warnings() {
// A fresh install: the folder appears on the first save, so opening must not warn.
let o = open(Box::new(NoFolder));
assert!(o.warnings.is_empty() && o.actions.is_empty(), "{:?}", o.warnings);
let dir = TempDir::new("fresh");
let o = open_dir(dir.0.join("not").join("yet"));
assert!(o.warnings.is_empty(), "{:?}", o.warnings);
// An unreadable actions file that is there is still reported.
let mem = MemBackend::default();
mem.files.lock().unwrap().insert(ACTIONS_FILE.into(), b"{".to_vec());
let o = open(Box::new(mem));
assert!(o.warnings.iter().any(|w| w.contains(ACTIONS_FILE)), "{:?}", o.warnings);
}
#[test]
fn backends_refuse_paths_outside_the_store() {
let mem = MemBackend::default();