Merge #2701@9c7bdbd44 (exists docs, root-safe permission test) into round3a-integration

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
francoisgn
2026-10-24 06:56:55 +03:00
co-authored by Claude Opus 5.5
4 changed files with 17 additions and 8 deletions
+4 -3
View File
@@ -201,9 +201,10 @@ pub enum Unavailable {
} }
impl Unavailable { impl Unavailable {
/// The folder isn't a library that is there to open (gone, a file, not a library any more, or /// The folder isn't a library that is there to open (gone, a file, or not a library any more):
/// unlistable): what greys out File ▸ Open Recent Library and makes `app.recentLibraries` say /// what greys out File ▸ Open Recent Library and makes `app.recentLibraries` say `exists: false`.
/// `exists: false`. An unreadable catalog or a check that failed doesn't say the library is gone. /// A folder that can't be listed, an unreadable catalog or a check that failed doesn't say the
/// library is gone (the launch window still shows why, and offers Open only for one it checked).
pub fn is_missing(&self) -> bool { pub fn is_missing(&self) -> bool {
// a folder that refuses to list is there (permissions, macOS privacy): opening it says why // a folder that refuses to list is there (permissions, macOS privacy): opening it says why
matches!(self, Unavailable::NotFound | Unavailable::NotAFolder | Unavailable::NoLibrary) matches!(self, Unavailable::NotFound | Unavailable::NotAFolder | Unavailable::NoLibrary)
+3 -3
View File
@@ -1456,9 +1456,9 @@ pub(crate) fn free_name(location: &std::path::Path, base: &str) -> std::path::Pa
/// `app.recentLibraries {remove?: path, clear?: bool}`: the recently opened libraries, newest /// `app.recentLibraries {remove?: path, clear?: bool}`: the recently opened libraries, newest
/// first, as `[{path, name, exists, checking, open, lastOpened}]`. `exists` is what the last /// first, as `[{path, name, exists, checking, open, lastOpened}]`. `exists` is what the last
/// background check found (`false`: moved, on a disconnected drive, a folder that can't be listed /// background check found (`false`: moved, on a disconnected drive, not a folder or no longer a
/// or no longer a library; `null`: not checked yet, or the check couldn't tell — an unreadable catalog, a check /// library; `null`: not checked yet, or the check couldn't tell — a folder that can't be listed, an
/// that failed; `checking` says whether a check is running): the command never /// unreadable catalog, a lookup or check that failed; `checking` says whether a check is running): the command never
/// looks at a folder itself, as one on an offline network share can take seconds to answer. /// looks at a folder itself, as one on an offline network share can take seconds to answer.
/// `lastOpened`: ISO 8601 time, `null` when not known. `remove` forgets that /// `lastOpened`: ISO 8601 time, `null` when not known. `remove` forgets that
/// one entry (an error if the list doesn't hold it) and `clear` forgets them all; neither touches /// one entry (an error if the list doesn't hold it) and `clear` forgets them all; neither touches
@@ -879,6 +879,13 @@ fn a_failed_lookup_doesnt_mark_a_recent_library_missing() {
let inside = make_library(&locked.join("Inside")); let inside = make_library(&locked.join("Inside"));
let gone = dir.join("Gone").to_string_lossy().into_owned(); let gone = dir.join("Gone").to_string_lossy().into_owned();
std::fs::set_permissions(&locked, std::fs::Permissions::from_mode(0o000)).unwrap(); std::fs::set_permissions(&locked, std::fs::Permissions::from_mode(0o000)).unwrap();
if std::fs::read_dir(&locked).is_ok() {
// running as root (some CI containers): the mode bits don't stop it, so no lookup fails
std::fs::set_permissions(&locked, std::fs::Permissions::from_mode(0o755)).unwrap();
let _ = std::fs::remove_dir_all(&dir);
lightcraft_testkit::skip!(System, "a folder with no permissions can still be read (running as root)");
return;
}
let mut app = LightcraftApp::new(lightcraft_engine::Session::new().with_fs(), Services::default()); let mut app = LightcraftApp::new(lightcraft_engine::Session::new().with_fs(), Services::default());
app.ui.settings.recent_libraries = vec![inside.clone(), gone.clone()]; app.ui.settings.recent_libraries = vec![inside.clone(), gone.clone()];
let mut h = Headless::new(app, [900.0, 600.0], 1.0); let mut h = Headless::new(app, [900.0, 600.0], 1.0);
+3 -2
View File
@@ -190,8 +190,9 @@ Enter opens it and Escape quits (over an open library: closes the window and kee
opening a library ends it. While it shows, the settings are saved as usual (a removal is kept). opening a library ends it. While it shows, the settings are saved as usual (a removal is kept).
`app.recentLibraries` returns `[{path, name, exists, checking, open, lastOpened}]`. `exists` is what the last `app.recentLibraries` returns `[{path, name, exists, checking, open, lastOpened}]`. `exists` is what the last
background check found (`false`: moved, on a disconnected drive, a folder that can't be listed or no longer a background check found (`false`: moved, on a disconnected drive, not a folder or no longer a library; `null`: not
library; `null`: not checked yet, or the check couldn't tell, e.g. an unreadable catalog) and `checking` whether one checked yet, or the check couldn't tell, e.g. a folder that can't be listed, an unreadable catalog or a lookup that
failed) and `checking` whether one
is running: the command never looks at a folder itself. A failed open never marks a library missing (another program is running: the command never looks at a folder itself. A failed open never marks a library missing (another program
may have it). Recent libraries are checked in the may have it). Recent libraries are checked in the
background when the app starts (the first 8) and when the launch window opens; one not found is looked for again every background when the app starts (the first 8) and when the launch window opens; one not found is looked for again every