mirror of
https://github.com/storytold/lightcraft.git
synced 2026-11-01 05:57:09 -06:00
LRC-LIB-CATALOG-SWITCH: a library missing at launch is reported instead of being made anew with demo photos (#3239)
When the library last opened was missing at launch (its drive or network share not connected yet, or moved), LightCraft created an empty library at that path, seeded it with the demo photos and opened it. The launch plan now marks the library remembered in Settings, and when it holds no library the session opens nothing and the can't-open window offers Try Again, Choose Another Library, Continue Without Saving and Quit. The default location, --library and LIGHTCRAFT_LIBRARY are still created when missing, as before.
This commit is contained in:
@@ -253,7 +253,7 @@ pub(crate) fn services(cc: &eframe::CreationContext<'_>, log_file: Option<&std::
|
||||
pub(crate) fn open_session(in_memory: bool, launch: &Launch, seed_demo: bool) -> (Session, Option<LibraryProblem>) {
|
||||
let (mut s, problem) = match launch {
|
||||
Launch::Ask => (Session::new().with_fs().with_default_denoise_models().with_system_clock(), None),
|
||||
Launch::Open { dir, .. } => open_library_session(in_memory, dir.clone(), seed_demo),
|
||||
Launch::Open { dir, remembered, .. } => open_library_session(in_memory, dir.clone(), *remembered, seed_demo),
|
||||
};
|
||||
s.face_models_dir = lightcraft_engine::config::default_face_models_dir();
|
||||
s.slide_templates_dir = lightcraft_engine::config::config_dir().map(|d| d.join(lightcraft_engine::slide_template::DIR_NAME));
|
||||
@@ -267,7 +267,9 @@ pub(crate) fn open_session(in_memory: bool, launch: &Launch, seed_demo: bool) ->
|
||||
/// If the library can't be opened the session is empty and in memory — never seeded with the
|
||||
/// demo photos, never written anywhere — and the problem is returned: the window then says so
|
||||
/// and offers Try Again / Choose Another Library… / Continue Without Saving / Quit (issue #100).
|
||||
fn open_library_session(in_memory: bool, dir: Option<std::path::PathBuf>, seed_demo: bool) -> (Session, Option<LibraryProblem>) {
|
||||
/// So is a `remembered` library (the one last opened) that isn't there any more: opening would
|
||||
/// make a new, empty one in its place, e.g. on an unmounted drive's path (issue #3239).
|
||||
fn open_library_session(in_memory: bool, dir: Option<std::path::PathBuf>, remembered: bool, seed_demo: bool) -> (Session, Option<LibraryProblem>) {
|
||||
if in_memory {
|
||||
return (if seed_demo { Session::with_demo() } else { Session::new() }.with_fs().with_default_denoise_models().with_system_clock(), None);
|
||||
}
|
||||
@@ -276,9 +278,18 @@ fn open_library_session(in_memory: bool, dir: Option<std::path::PathBuf>, seed_d
|
||||
log::warn!("no library location (set --library or LIGHTCRAFT_LIBRARY)");
|
||||
return (unopened(), Some(LibraryProblem::new("", "There is no home folder to keep the library in. Choose a folder for it.")));
|
||||
};
|
||||
if remembered && !lightcraft_engine::library::is_library(&dir) {
|
||||
log::error!("library {} is missing", dir.display());
|
||||
let why = if dir.exists() {
|
||||
"There is no LightCraft library in this folder any more."
|
||||
} else {
|
||||
"The library isn't there: it was moved or renamed, or its drive or network share isn't connected."
|
||||
};
|
||||
return (unopened(), Some(LibraryProblem::new(dir.to_string_lossy(), why)));
|
||||
}
|
||||
let t0 = std::time::Instant::now();
|
||||
let mut s = Session::new().with_fs().with_default_denoise_models().with_system_clock();
|
||||
match s.open_library(&dir, seed_demo) {
|
||||
match s.open_library(&dir, seed_demo && !remembered) {
|
||||
Ok(r) => {
|
||||
let (replayed, torn) = (r.replayed, r.torn_bytes);
|
||||
log::info!(
|
||||
|
||||
@@ -29,7 +29,7 @@ fn launch_arguments_never_walk_a_drive_or_the_home_folder() {
|
||||
/// adds to Apple Photos on a worker (headless sessions wait instead).
|
||||
#[test]
|
||||
fn the_desktop_session_adds_to_apple_photos_in_the_background() {
|
||||
let (s, problem) = open_session(true, &Launch::Open { dir: None, alt_asks: false }, false);
|
||||
let (s, problem) = open_session(true, &Launch::Open { dir: None, alt_asks: false, remembered: false }, false);
|
||||
assert!(problem.is_none());
|
||||
assert!(s.apple_photos_background);
|
||||
assert!(!lightcraft_engine::Session::new().apple_photos_background);
|
||||
@@ -63,6 +63,35 @@ fn asking_at_launch_opens_nothing_and_still_saves_the_settings() {
|
||||
let _ = std::fs::remove_dir_all(&d);
|
||||
}
|
||||
|
||||
/// Issue #3239: the library remembered in Settings whose folder is missing at launch (a disk or
|
||||
/// network share not connected) is reported in the library problem window. Nothing is created
|
||||
/// there (not the folder, not its parents) and no demo photos are added.
|
||||
#[test]
|
||||
fn a_remembered_library_missing_at_launch_is_reported_not_made_anew() {
|
||||
let d = dir("missing-library");
|
||||
let gone = d.join("gone");
|
||||
let lib = gone.join("Lib");
|
||||
let launch = Launch::Open { dir: Some(lib.clone()), alt_asks: true, remembered: true };
|
||||
let (s, problem) = open_session(false, &launch, true);
|
||||
let problem = problem.expect("the missing library is reported");
|
||||
assert_eq!(problem.path, lib.to_string_lossy());
|
||||
assert!(problem.can_retry, "Try Again once the drive is back");
|
||||
assert!(!gone.exists(), "nothing is created where the library was");
|
||||
assert!(s.library.is_none() && s.catalog.is_empty(), "no library, no demo photos");
|
||||
// a remembered folder that is there but holds no library is reported too
|
||||
std::fs::create_dir_all(&lib).unwrap();
|
||||
let (s, problem) = open_session(false, &launch, true);
|
||||
assert!(problem.is_some() && s.library.is_none() && s.catalog.is_empty());
|
||||
assert!(!lightcraft_engine::library::is_library(&lib), "nothing written in it");
|
||||
// the default location (or --library) is still made when it's missing, with the demo photos
|
||||
let fresh = d.join("fresh").join("Lib");
|
||||
let (s, problem) = open_session(false, &Launch::Open { dir: Some(fresh.clone()), alt_asks: true, remembered: false }, true);
|
||||
assert!(problem.is_none() && s.library.is_some() && !s.catalog.is_empty(), "{problem:?}");
|
||||
assert!(lightcraft_engine::library::is_library(&fresh));
|
||||
drop(s);
|
||||
let _ = std::fs::remove_dir_all(&d);
|
||||
}
|
||||
|
||||
/// Issue #103: a damaged ui.json (which holds the library location) is kept aside and
|
||||
/// reported, not silently replaced by the defaults.
|
||||
#[test]
|
||||
|
||||
@@ -274,7 +274,9 @@ pub const ALT_SECONDS: f64 = 2.0;
|
||||
pub enum Launch {
|
||||
/// Open this library folder (`None`: there is nowhere to keep one; the problem window says
|
||||
/// so). `alt_asks`: holding Alt/Option as the window appears offers the recent libraries.
|
||||
Open { dir: Option<PathBuf>, alt_asks: bool },
|
||||
/// `remembered`: `dir` is the library last opened (Settings), not the default location, so a
|
||||
/// missing one is reported instead of created anew (issue #3239).
|
||||
Open { dir: Option<PathBuf>, alt_asks: bool, remembered: bool },
|
||||
/// Open nothing yet: the window lists the recent libraries ([`LibraryProblem::choose`]).
|
||||
Ask,
|
||||
}
|
||||
@@ -284,16 +286,18 @@ pub enum Launch {
|
||||
/// `settings` is `None` without a settings file; `memory` (`--memory`) never asks.
|
||||
pub fn launch_plan(memory: bool, flag: Option<PathBuf>, env: Option<PathBuf>, settings: Option<&AppSettings>, default: Option<PathBuf>) -> Launch {
|
||||
if memory {
|
||||
return Launch::Open { dir: None, alt_asks: false };
|
||||
return Launch::Open { dir: None, alt_asks: false, remembered: false };
|
||||
}
|
||||
if let Some(dir) = flag.or(env) {
|
||||
return Launch::Open { dir: Some(dir), alt_asks: false };
|
||||
return Launch::Open { dir: Some(dir), alt_asks: false, remembered: false };
|
||||
}
|
||||
if settings.is_some_and(|s| s.at_launch == LaunchLibrary::Ask) {
|
||||
return Launch::Ask;
|
||||
}
|
||||
let last = settings.map(|s| s.library_path.clone()).filter(|p| !p.is_empty()).map(PathBuf::from);
|
||||
Launch::Open { dir: last.or(default), alt_asks: true }
|
||||
// the default location is made when it's missing (first launch); a remembered library isn't
|
||||
let remembered = last.as_ref().is_some_and(|l| default.as_ref() != Some(l));
|
||||
Launch::Open { dir: last.or(default), alt_asks: true, remembered }
|
||||
}
|
||||
|
||||
/// One library in the launch window's list.
|
||||
|
||||
@@ -50,30 +50,35 @@ fn path(s: &str) -> Option<PathBuf> {
|
||||
Some(PathBuf::from(s))
|
||||
}
|
||||
|
||||
/// `--library`, then `LIGHTCRAFT_LIBRARY`, then the setting; `--memory` never asks.
|
||||
/// `--library`, then `LIGHTCRAFT_LIBRARY`, then the setting; `--memory` never asks. Only the
|
||||
/// library remembered in Settings is `remembered` (issue #3239: a missing one is reported, not
|
||||
/// made anew), and not when it is the default location.
|
||||
#[test]
|
||||
fn the_launch_plan_puts_the_flag_and_the_variable_before_the_setting() {
|
||||
let ask = AppSettings { at_launch: LaunchLibrary::Ask, library_path: "/last".into(), ..AppSettings::default() };
|
||||
let last = AppSettings { library_path: "/last".into(), recent_libraries: vec!["/last".into(), "/older".into()], ..AppSettings::default() };
|
||||
let at_default = AppSettings { library_path: "/default".into(), ..AppSettings::default() };
|
||||
let fresh = AppSettings::default();
|
||||
let open = |dir: &str, alt_asks| Launch::Open { dir: path(dir), alt_asks };
|
||||
let open = |dir: &str, alt_asks, remembered| Launch::Open { dir: path(dir), alt_asks, remembered };
|
||||
// --library, then LIGHTCRAFT_LIBRARY, whatever the setting says; neither asks on Alt
|
||||
for s in [None, Some(&ask), Some(&last)] {
|
||||
assert_eq!(launch_plan(false, path("/flag"), path("/env"), s, path("/default")), open("/flag", false));
|
||||
assert_eq!(launch_plan(false, None, path("/env"), s, path("/default")), open("/env", false));
|
||||
assert_eq!(launch_plan(false, path("/flag"), path("/env"), s, path("/default")), open("/flag", false, false));
|
||||
assert_eq!(launch_plan(false, None, path("/env"), s, path("/default")), open("/env", false, false));
|
||||
}
|
||||
// Ask me asks, with or without a recent list
|
||||
assert_eq!(launch_plan(false, None, None, Some(&ask), path("/default")), Launch::Ask);
|
||||
let ask_empty = AppSettings { at_launch: LaunchLibrary::Ask, ..AppSettings::default() };
|
||||
assert_eq!(launch_plan(false, None, None, Some(&ask_empty), None), Launch::Ask);
|
||||
// Open the last library: the last one, else the default place; Alt can still ask
|
||||
assert_eq!(launch_plan(false, None, None, Some(&last), path("/default")), open("/last", true));
|
||||
assert_eq!(launch_plan(false, None, None, Some(&fresh), path("/default")), open("/default", true));
|
||||
assert_eq!(launch_plan(false, None, None, None, path("/default")), open("/default", true));
|
||||
assert_eq!(launch_plan(false, None, None, None, None), Launch::Open { dir: None, alt_asks: true });
|
||||
assert_eq!(launch_plan(false, None, None, Some(&last), path("/default")), open("/last", true, true));
|
||||
assert_eq!(launch_plan(false, None, None, Some(&last), None), open("/last", true, true));
|
||||
assert_eq!(launch_plan(false, None, None, Some(&at_default), path("/default")), open("/default", true, false));
|
||||
assert_eq!(launch_plan(false, None, None, Some(&fresh), path("/default")), open("/default", true, false));
|
||||
assert_eq!(launch_plan(false, None, None, None, path("/default")), open("/default", true, false));
|
||||
assert_eq!(launch_plan(false, None, None, None, None), Launch::Open { dir: None, alt_asks: true, remembered: false });
|
||||
// --memory never asks and opens no folder
|
||||
assert_eq!(launch_plan(true, None, None, Some(&ask), path("/default")), Launch::Open { dir: None, alt_asks: false });
|
||||
assert_eq!(launch_plan(true, path("/flag"), None, Some(&last), None), Launch::Open { dir: None, alt_asks: false });
|
||||
assert_eq!(launch_plan(true, None, None, Some(&ask), path("/default")), Launch::Open { dir: None, alt_asks: false, remembered: false });
|
||||
assert_eq!(launch_plan(true, path("/flag"), None, Some(&last), None), Launch::Open { dir: None, alt_asks: false, remembered: false });
|
||||
}
|
||||
|
||||
/// Each listed library is checked when the window opens; the last one opened is selected.
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,5 +1,15 @@
|
||||
# What's new in LightCraft
|
||||
|
||||
## 2026-10-29
|
||||
|
||||
### A library on a disconnected drive is reported at launch, not replaced
|
||||
- When the library LightCraft last opened is missing at launch, because its drive or network share isn't connected
|
||||
yet or it was moved, the window now says it couldn't open the library and offers Try Again, Choose Another
|
||||
Library…, Continue Without Saving and Quit. Before, LightCraft quietly made a new, empty library at that path,
|
||||
filled it with the demo photos and opened it, so it looked as if the photos and edits were gone. Nothing is
|
||||
written there now. A library given with `--library` or `LIGHTCRAFT_LIBRARY`, and the default location on first
|
||||
launch, are still created as before.
|
||||
|
||||
## 2026-10-24
|
||||
|
||||
### Relative-date filters and smart albums follow the clock
|
||||
|
||||
Reference in New Issue
Block a user