Edit in PhotoCraft: restore the Windows/desktop-entry tests, test PATH lookup, sort AppImages by version

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Andrew Frahn
2026-10-20 09:59:33 +11:00
co-authored by Claude Sonnet 5.5
parent 380d3017b0
commit a656f99e47
+90 -8
View File
@@ -146,12 +146,7 @@ fn find_photocraft() -> Option<Launcher> {
// registers one on first run; its Exec a path or a name on the PATH), an AppImage where people
// keep them, else the Flatpak
let path_dirs: Vec<PathBuf> = env("PATH").into_iter().flat_map(|p| std::env::split_paths(&p).collect::<Vec<_>>()).collect();
let on_path = |name: &Path| -> Option<PathBuf> {
if name.is_absolute() || name.starts_with("/") {
return Some(name.to_path_buf()).filter(|p| p.is_file());
}
path_dirs.iter().map(|d| d.join(name)).find(|p| p.is_file())
};
let on_path = |name: &Path| resolve_program(name, &path_dirs);
let from_entry = || {
desktop_entry_dirs(env).into_iter().find_map(|d| {
let text = std::fs::read_to_string(d.join(format!("{PHOTOCRAFT_ID}.desktop"))).ok()?;
@@ -164,8 +159,8 @@ fn find_photocraft() -> Option<Launcher> {
dirs.iter().find_map(|d| {
let mut found: Vec<PathBuf> =
std::fs::read_dir(d).ok()?.flatten().map(|e| e.path()).filter(|p| is_photocraft_appimage(p) && p.is_file()).collect();
// the newest version by name
found.sort();
// the newest version by name (1.10 after 1.9)
found.sort_by_key(|p| version_key(&p.file_name().map(|n| n.to_string_lossy().to_lowercase()).unwrap_or_default()));
found.pop()
})
};
@@ -239,6 +234,31 @@ fn desktop_exec(entry: &str) -> Option<PathBuf> {
(!program.is_empty()).then(|| PathBuf::from(program))
}
/// A program from a desktop entry or the PATH: a path as it is (if it is a file), a bare name
/// looked for in `dirs`. A leading `/` counts as absolute on every platform (desktop entries are
/// a POSIX format; `Path::is_absolute` wants a drive letter on Windows, where the tests run too).
fn resolve_program(name: &Path, dirs: &[PathBuf]) -> Option<PathBuf> {
if name.is_absolute() || name.starts_with("/") {
return Some(name.to_path_buf()).filter(|p| p.is_file());
}
dirs.iter().map(|d| d.join(name)).find(|p| p.is_file())
}
/// A sort key that orders the digit runs in a name by value (`1.9` before `1.10`).
fn version_key(name: &str) -> Vec<(u64, String)> {
let mut out = Vec::new();
let mut rest = name;
while !rest.is_empty() {
let digits = rest.len() - rest.trim_start_matches(|c: char| c.is_ascii_digit()).len();
let (num, tail) = rest.split_at(digits);
let text_len = tail.find(|c: char| c.is_ascii_digit()).unwrap_or(tail.len());
let (text, next) = tail.split_at(text_len);
out.push((num.parse().unwrap_or(u64::MAX), text.to_string()));
rest = next;
}
out
}
/// An AppImage of PhotoCraft by its file name (`PhotoCraft-1.2-x86_64.AppImage`, any case).
fn is_photocraft_appimage(p: &Path) -> bool {
let name = p.file_name().map(|n| n.to_string_lossy().to_lowercase()).unwrap_or_default();
@@ -284,6 +304,68 @@ mod tests {
assert!(all_command(Launcher::Default, &paths).is_none(), "the system default opens them one by one");
}
#[test]
fn windows_lookup_prefers_the_recorded_install_folder() {
let env = |k: &str| match k {
"ProgramFiles" => Some(r"C:\Program Files".to_string()),
"LOCALAPPDATA" => Some(r"C:\Users\Simon\AppData\Local".to_string()),
_ => None,
};
let c = windows_candidates(Some(r"D:\Apps\PhotoCraft\".into()), Some(r#""C:\Program Files\PhotoCraft\photocraft.exe""#.into()), env);
let c: Vec<String> = c.iter().map(|p| p.to_string_lossy().replace('/', "\\")).collect();
assert!(c[0].starts_with(r"D:\Apps\PhotoCraft") && c[0].ends_with("photocraft.exe"), "{c:?}");
assert_eq!(c[1], r"C:\Program Files\PhotoCraft\photocraft.exe", "App Paths, quotes dropped");
assert!(c[2].starts_with(r"C:\Program Files") && c[2].ends_with("photocraft.exe"), "{c:?}");
assert!(c[3].contains("Programs") && c[3].ends_with("photocraft.exe"), "{c:?}");
assert_eq!(c.len(), 4);
}
#[test]
fn reg_query_output_is_read() {
let out = "\r\nHKEY_LOCAL_MACHINE\\Software\\PhotoCraft\r\n InstallDir REG_SZ C:\\Program Files\\PhotoCraft\\\r\n\r\n";
assert_eq!(reg_value(out).as_deref(), Some(r"C:\Program Files\PhotoCraft\"));
let default = "\r\nHKEY_LOCAL_MACHINE\\...\\photocraft.exe\r\n (Default) REG_SZ C:\\Program Files\\PhotoCraft\\photocraft.exe\r\n";
assert_eq!(reg_value(default).as_deref(), Some(r"C:\Program Files\PhotoCraft\photocraft.exe"));
assert_eq!(reg_value("ERROR: The system was unable to find the specified registry key or value."), None);
}
#[test]
fn desktop_entries_give_their_program() {
let appimage = "[Desktop Entry]\nName=PhotoCraft\nExec=\"/home/simon/Apps/photocraft-1.2-linux-x86_64.AppImage\" %F\n";
assert_eq!(desktop_exec(appimage), Some(PathBuf::from("/home/simon/Apps/photocraft-1.2-linux-x86_64.AppImage")));
assert_eq!(desktop_exec("Exec=/opt/photocraft/photocraft %F\n"), Some(PathBuf::from("/opt/photocraft/photocraft")));
assert_eq!(desktop_exec("Exec=photocraft %F\nTryExec=photocraft\n"), Some(PathBuf::from("photocraft")), "a bare name, for a PATH lookup");
assert_eq!(desktop_exec("[Desktop Entry]\n"), None);
let dirs = desktop_entry_dirs(|k| (k == "HOME").then(|| "/home/simon".to_string()));
assert_eq!(dirs[0], PathBuf::from("/home/simon/.local/share/applications"));
assert!(dirs.contains(&PathBuf::from("/usr/share/applications")));
}
#[test]
fn a_bare_exec_name_is_found_on_the_path() {
let a = std::env::temp_dir().join(format!("lc-editors-a-{}", std::process::id()));
let b = std::env::temp_dir().join(format!("lc-editors-b-{}", std::process::id()));
std::fs::create_dir_all(&a).unwrap();
std::fs::create_dir_all(&b).unwrap();
std::fs::write(b.join("photocraft"), b"").unwrap();
let dirs = vec![a.clone(), b.clone()];
assert_eq!(resolve_program(Path::new("photocraft"), &dirs), Some(b.join("photocraft")));
assert_eq!(resolve_program(Path::new("nothere"), &dirs), None);
let abs = b.join("photocraft");
assert_eq!(resolve_program(&abs, &[]), Some(abs.clone()), "a path needs no PATH");
assert_eq!(resolve_program(&b.join("missing"), &dirs), None);
let _ = std::fs::remove_dir_all(&a);
let _ = std::fs::remove_dir_all(&b);
}
#[test]
fn appimage_versions_sort_numerically() {
let mut names = ["photocraft-1.10-x86_64.appimage", "photocraft-1.9-x86_64.appimage", "photocraft-1.2-x86_64.appimage"];
names.sort_by_key(|n| version_key(n));
assert_eq!(names[2], "photocraft-1.10-x86_64.appimage");
assert_eq!(names[0], "photocraft-1.2-x86_64.appimage");
}
#[test]
fn a_failed_open_is_an_error() {
assert!(open_result(true, "").is_ok());