Round 3: fix three defects introduced by the round-2 fixes
Auditing my own fixes found all three. None were in the original code. The VTS crack sort was byte-wise case-SENSITIVE while the filters that select those files (vts_group_of / is_title_vob) are case-insensitive. On a case-sensitive volume a set holding vts_01_1.vob beside VTS_01_2.VOB sorted part 2 first, because V (0x56) precedes v (0x76) — reintroducing exactly the budget-exhaustion the ordering exists to prevent. Now sorted on the same uppercase normalisation the filters apply. Refusing a name that round-trips to empty aborted the WHOLE plan. A sidecar folder named with a single emoji made a backup un-rippable that 1.6.0 handled fine, and reported it as a collision with a file that does not exist. It is now skipped with a warning: the entry is unaddressable either way, but one irrelevant file should not cost the user their rip. The mtime check now applies only to files whose CONTENT the plan read — the IFOs, whose bytes 0xC0/0xC4 place every VOB. Everything else is planned from size alone, which is already checked, so comparing mtime there bought nothing and risked a real false positive: disc backups commonly live on exFAT/FAT32, which stores local time, so a long rip spanning a DST transition would see a whole-hour shift on an untouched multi-gigabyte VOB and abort hours in.
This commit is contained in:
+18
-1
@@ -127,11 +127,28 @@ impl DirImage {
|
||||
let mut files = Vec::with_capacity(nodes.len());
|
||||
let mut ranges = Vec::new();
|
||||
for (idx, node) in nodes.iter().enumerate() {
|
||||
// Carry the plan-time mtime ONLY for files whose CONTENT the plan
|
||||
// read — the DVD IFOs, whose bytes 0xC0/0xC4 decide where every VOB
|
||||
// is placed (`layout::place_video_ts` -> `read_head`).
|
||||
//
|
||||
// For every other file the plan depends on the SIZE alone, and size
|
||||
// is already checked. Comparing mtime on those buys nothing and
|
||||
// costs real false positives: disc backups commonly live on
|
||||
// exFAT/FAT32, which stores local time, so a long rip spanning a
|
||||
// DST transition sees a whole-hour shift on a file nobody touched
|
||||
// and would abort hours in, blaming a change that did not happen.
|
||||
// The multi-gigabyte VOBs are exactly the files a long rip re-opens
|
||||
// after the handle cache evicts them.
|
||||
let content_sensitive = node
|
||||
.disc_path
|
||||
.rsplit('.')
|
||||
.next()
|
||||
.is_some_and(|e| e.eq_ignore_ascii_case("IFO"));
|
||||
files.push(FileRef {
|
||||
host: node.host.clone(),
|
||||
disc_path: node.disc_path.clone(),
|
||||
size: node.size,
|
||||
mtime: node.mtime,
|
||||
mtime: content_sensitive.then_some(node.mtime).flatten(),
|
||||
});
|
||||
let mut offset = 0u64;
|
||||
for e in &node.extents {
|
||||
|
||||
Reference in New Issue
Block a user