Libraries: a User Defined library file whose reading panics is skipped

The desktop app reads the User Defined swatch and graphic style
libraries while it creates its window: set_user_dir runs
Libraries::rescan, which read and parsed every file in the folder
outside the panic guard that commands have. A parser bug that panicked
on one file stopped the app before its window appeared, at every
launch while the file stayed in the folder.

rescan now reads and parses each file inside guard::catch_panic and
skips a file whose reading panics, as it skips one that can't be read.
The other libraries are listed.
This commit is contained in:
dotCooCoo
2026-10-21 15:20:15 +07:00
committed by bflatastic
parent 494343c274
commit 8712be97b0
2 changed files with 37 additions and 2 deletions
+5 -2
View File
@@ -152,13 +152,16 @@ impl<L: LibraryFile> Libraries<L> {
}
/// Re-read the library files of the user library folder (unreadable ones are skipped). The
/// folder is the app's own: automation roots don't apply to it ([`crate::file_access`]).
/// folder is the app's own: automation roots don't apply to it ([`crate::file_access`]). Each
/// file is read inside [`crate::guard::catch_panic`], and a file whose reading panics is
/// skipped too.
pub fn rescan(&mut self) {
self.extra.retain(|e| e.info.category != "user");
let Some(dir) = self.user_dir.clone() else { return };
for path in library_files(&dir, L::EXTS) {
let file = file_name(&path);
let Some(lib) = crate::file_access::unconfined(|| read_file(&path)).ok().and_then(|b| L::read(&b, stem(&file)).ok()) else { continue };
let read = || crate::file_access::unconfined(|| read_file(&path)).ok().and_then(|b| L::read(&b, stem(&file)).ok());
let Some(lib) = crate::guard::catch_panic(read).ok().flatten() else { continue };
let info = LibraryInfo { id: format!("user/{file}"), name: lib.name().to_string(), category: "user" };
self.extra.push(Extra { info, path: Some(path), lib: Arc::new(lib) });
}
+32
View File
@@ -189,6 +189,38 @@ fn libraries_saved_in_the_user_folder_are_user_defined() {
let _ = std::fs::remove_dir_all(dir);
}
/// A library kind whose reader panics on a file that holds `trap`, as a parser bug would.
#[derive(Default)]
struct Trap(String);
impl cmd::swatchlib::LibraryFile for Trap {
const EXTS: &'static [&'static str] = &["trap"];
fn read(bytes: &[u8], stem: &str) -> std::result::Result<Self, String> {
if bytes == b"trap" {
panic!("a parser bug");
}
Ok(Self(stem.to_string()))
}
fn name(&self) -> &str {
&self.0
}
}
/// When reading one file of the user library folder panics, `set_user_dir` (as the desktop app
/// calls it at launch) skips that file and lists the other one.
#[test]
fn a_user_library_file_whose_reading_panics_is_skipped() {
let dir = temp_dir("user-panic");
std::fs::create_dir_all(&dir).unwrap();
std::fs::write(dir.join("Calm.trap"), "calm").unwrap();
std::fs::write(dir.join("Loud.trap"), "trap").unwrap();
let mut libraries = cmd::swatchlib::Libraries::<Trap>::default();
libraries.set_user_dir(Some(dir.to_string_lossy().to_string()));
let names: Vec<&str> = libraries.infos().map(|l| l.name.as_str()).collect();
assert_eq!(names, ["Calm"]);
let _ = std::fs::remove_dir_all(dir);
}
#[test]
fn another_documents_swatches_load_as_a_library() {
let mut s = session();