mirror of
https://github.com/storytold/lightcraft.git
synced 2026-10-14 19:27:01 -04:00
Merge PR #1007
This commit is contained in:
commit
9fe9beffbc
4 files changed
+31
-7
No files matched your search
@@ -1,6 +1,7 @@
|
||||
//! Real Session partial moves, sidecar serialization/merging, AutoWrite and history.
|
||||
use super::*;
|
||||
use crate::rename::{MoveFs, RealFs};
|
||||
use crate::tests_xmp::{same_path_for_volume, same_path_or_file};
|
||||
use lightcraft_catalog::{Op, Photo, Source};
|
||||
use serde_json::json;
|
||||
|
||||
@@ -16,7 +17,7 @@ impl MoveFs for Faults {
|
||||
RealFs.same_file(a, b)
|
||||
}
|
||||
fn rename_no_replace(&self, a: &Path, b: &Path) -> std::io::Result<()> {
|
||||
if self.0.iter().any(|p| p == a) {
|
||||
if self.0.iter().any(|p| same_path_or_file(p, a)) {
|
||||
Err(std::io::Error::new(std::io::ErrorKind::PermissionDenied, "injected partial move"))
|
||||
} else {
|
||||
RealFs.rename_no_replace(a, b)
|
||||
@@ -261,8 +262,8 @@ fn queued_stem_retry_preserves_shared_packet_after_batch_rollback() {
|
||||
assert!(!second_packet.exists());
|
||||
let pending = queues(&s);
|
||||
assert_eq!(pending.len(), 1);
|
||||
assert_eq!(pending[0].from, shared);
|
||||
assert_eq!(pending[0].to, second_packet);
|
||||
assert!(same_path_for_volume(&pending[0].from, &shared), "{:?}", pending[0].from);
|
||||
assert!(same_path_for_volume(&pending[0].to, &second_packet), "{:?}", pending[0].to);
|
||||
let catalog_before = s.catalog.to_snapshot();
|
||||
let history_before = (s.undo.len(), s.redo.len(), s.commits());
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
//! Native files and genuine Session entry points: packet ownership survives partial renames.
|
||||
use super::*;
|
||||
use crate::rename::{MoveFs, RealFs};
|
||||
use crate::tests_xmp::same_path_or_file;
|
||||
use lightcraft_catalog::{Op, Photo, Source};
|
||||
use serde_json::json;
|
||||
|
||||
@@ -16,7 +17,7 @@ impl MoveFs for Faults {
|
||||
RealFs.same_file(a, b)
|
||||
}
|
||||
fn rename_no_replace(&self, a: &Path, b: &Path) -> std::io::Result<()> {
|
||||
if self.0.iter().any(|p| p == a) {
|
||||
if self.0.iter().any(|p| same_path_or_file(p, a)) {
|
||||
Err(std::io::Error::new(std::io::ErrorKind::PermissionDenied, "injected provenance partial move"))
|
||||
} else {
|
||||
RealFs.rename_no_replace(a, b)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
//! Real Session/serializer regressions for saves that overlap retained physical repairs.
|
||||
use super::*;
|
||||
use crate::rename::{MoveFs, RealFs};
|
||||
use crate::tests_xmp::{case_insensitive, same_path_or_file};
|
||||
use lightcraft_catalog::{Op, Photo, Source};
|
||||
use serde_json::json;
|
||||
|
||||
@@ -19,14 +20,14 @@ impl MoveFs for PartialRename {
|
||||
RealFs.same_file(a, b)
|
||||
}
|
||||
fn rename_no_replace(&self, a: &Path, b: &Path) -> std::io::Result<()> {
|
||||
if a == self.sidecar || a == self.photo {
|
||||
if same_path_or_file(a, &self.sidecar) || same_path_or_file(a, &self.photo) {
|
||||
Err(std::io::Error::new(std::io::ErrorKind::PermissionDenied, "transient rename fault"))
|
||||
} else {
|
||||
RealFs.rename_no_replace(a, b)
|
||||
}
|
||||
}
|
||||
fn copy_no_replace(&self, a: &Path, b: &Path) -> std::io::Result<()> {
|
||||
if a == self.sidecar {
|
||||
if same_path_or_file(a, &self.sidecar) {
|
||||
Err(std::io::Error::new(std::io::ErrorKind::PermissionDenied, "transient copy fault"))
|
||||
} else {
|
||||
RealFs.copy_no_replace(a, b)
|
||||
@@ -150,7 +151,8 @@ fn autowrite_repairs_partial_rename_before_merging_foreign_xmp_across_library_sw
|
||||
assert_eq!(parsed.rating, Some(4));
|
||||
assert_eq!(parsed.title.as_deref(), Some("Final title"));
|
||||
assert!(parsed.keywords.as_ref().unwrap().contains(&"café".to_string()));
|
||||
if upper {
|
||||
// (on a case-insensitive volume the two spellings are one file)
|
||||
if upper && !case_insensitive(&root) {
|
||||
assert!(!preferred.exists(), "merge uses the repaired uppercase packet");
|
||||
}
|
||||
for (step, title) in [("edit.undo", "Fresh title"), ("edit.redo", "Final title")] {
|
||||
|
||||
@@ -7,6 +7,26 @@ use lightcraft_catalog::{ColorLabel, Flag, PhotoId};
|
||||
use serde_json::json;
|
||||
|
||||
use crate::Session;
|
||||
use crate::rename::{MoveFs, RealFs};
|
||||
|
||||
/// Whether `dir` is on a case-insensitive volume (the default on macOS and Windows): its path in upper case still resolves.
|
||||
pub(crate) fn case_insensitive(dir: &Path) -> bool {
|
||||
Path::new(&dir.to_string_lossy().to_uppercase()).exists()
|
||||
}
|
||||
|
||||
/// The same path, or the same file under another spelling (`a.XMP` and `a.xmp` on a case-insensitive volume). Fault
|
||||
/// injectors match this way, so an injected failure fires whichever spelling the code under test uses for a file; on a
|
||||
/// case-sensitive volume only identical paths match.
|
||||
pub(crate) fn same_path_or_file(a: &Path, b: &Path) -> bool {
|
||||
a == b || RealFs.same_file(a, b)
|
||||
}
|
||||
|
||||
/// The same path, or on a case-insensitive volume a path that differs only in ASCII case: the code under test may spell
|
||||
/// a sidecar `.XMP` for a file stored as `.xmp` there, while on a case-sensitive volume it offers only the spelling that
|
||||
/// exists.
|
||||
pub(crate) fn same_path_for_volume(a: &Path, b: &Path) -> bool {
|
||||
a == b || (a.parent().is_some_and(case_insensitive) && a.to_string_lossy().eq_ignore_ascii_case(&b.to_string_lossy()))
|
||||
}
|
||||
|
||||
pub(crate) fn temp_dir(tag: &str) -> PathBuf {
|
||||
let d = std::env::temp_dir().join(format!("lc-xmp-{tag}-{}", std::process::id()));
|
||||
|
||||
Reference in new issue
Block a user