Merge pull request #5914 from adikshan11/fix/library-file-name-collision

Keep the other library when two names share a file name
This commit is contained in:
BFlat
2026-11-06 18:04:04 +08:00
committed by GitHub
2 changed files with 49 additions and 1 deletions
+22 -1
View File
@@ -211,7 +211,28 @@ impl<L: LibraryFile> Libraries<L> {
.user_dir()
.ok_or_else(|| bad(cmd, "no user library folder here (save with a path, or without one to get the data)"))?
.to_string();
let file = library_file_name(name, ext);
let mut file = library_file_name(name, ext);
// `COM¹` is saved as `COM¹-`, which can be another library's own name, and a library put
// under a numbered name holds a file another name can ask for: such a file is kept.
self.rescan();
let device = crate::file_access::is_device_name;
let id = format!("user/{file}").to_lowercase();
let fallback = |other: &str| library_file_name(other, ext).to_lowercase() != file.to_lowercase();
if self
.extra
.iter()
.any(|e| e.info.id.to_lowercase() == id && e.lib.name() != name && (device(name) || device(e.lib.name()) || fallback(e.lib.name())))
{
// Saved again, it goes back to the file it was given the first time.
let own = self.extra.iter().find(|e| e.info.category == "user" && e.lib.name() == name && extension(&e.info.id) == extension(&file));
file = match own.and_then(|e| e.info.id.strip_prefix("user/")) {
Some(own) => own.to_string(),
None => {
let mut taken: HashSet<String> = library_files(&dir, L::EXTS).iter().map(|p| file_name(p).to_lowercase()).collect();
free_name(&file, &mut taken)
}
};
}
let path = std::path::Path::new(&dir).join(&file).to_string_lossy().to_string();
// The app's own folder, under a name made safe: no root applies.
crate::file_access::unconfined(|| create_dir(&dir).and_then(|()| write_file(&path, bytes)))?;
+27
View File
@@ -765,6 +765,33 @@ fn a_pattern_library_adds_only_the_picked_pattern_and_it_undoes_and_saves() {
assert_eq!(back.swatch("Checkerboard").map(|w| &w.paint), doc(&s).swatch("Checkerboard").map(|w| &w.paint));
}
/// A device name is saved with a `-` after it, which can be another library's own name: neither
/// library replaces the other, and saving either again replaces only itself.
#[test]
fn a_library_named_like_a_device_keeps_the_library_with_its_file_name() {
for (first, second) in [("COM¹", "COM¹-"), ("COM¹-", "COM¹"), ("com¹", "COM¹-")] {
let dir = temp_dir("device-collision");
let mut s = session();
s.swatch_libraries.set_user_dir(Some(dir.to_string_lossy().to_string()));
let save =
|s: &mut Session, name: &str| run(s, "swatch.library.save", json!({"user": true, "name": name}))["library"].as_str().unwrap().to_string();
let a = save(&mut s, first);
let b = save(&mut s, second);
assert_ne!(a, b);
assert_eq!(save(&mut s, first), a, "saving {first} again replaces {first}");
assert_eq!(save(&mut s, second), b, "saving {second} again replaces {second}");
assert_eq!(s.swatch_libraries.get(&a).unwrap().1.name, first);
assert_eq!(s.swatch_libraries.get(&b).unwrap().1.name, second);
// The name of the numbered file the second one got is a third library's own name.
let third = b.strip_prefix("user/").unwrap().strip_suffix(".vcswatches").unwrap().to_string();
let c = save(&mut s, &third);
assert!(c != a && c != b, "{third}: {c}");
assert_eq!(save(&mut s, &third), c, "saving {third} again replaces {third}");
assert_eq!(s.swatch_libraries.get(&b).unwrap().1.name, second);
let _ = std::fs::remove_dir_all(&dir);
}
}
/// #3602: a library titled with a Windows device name is saved under a name that is not one.
#[test]
fn a_library_file_name_is_never_a_windows_device_name() {