-
Notifications
You must be signed in to change notification settings - Fork 108
fix: V1 import no longer fails on graphs with 1000+ same-named attachments #960
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
1b10d76
03f9d47
1dfe7f1
3be517a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -13,7 +13,7 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| //! can simply retry. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use std::collections::HashMap; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use std::io::Write; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use std::io::{Read, Write}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use std::path::Path; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use std::sync::atomic::{AtomicBool, Ordering}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use std::sync::{Arc, Mutex}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -33,8 +33,6 @@ const CONNECT_TIMEOUT: Duration = Duration::from_secs(15); | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// a large video on a slow connection still finishes. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const READ_TIMEOUT: Duration = Duration::from_secs(60); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const CONCURRENT_DOWNLOADS: usize = 6; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Collision probes before giving up, mirroring `persist_unique`'s cap. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const MAX_NAME_PROBES: u32 = 1000; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// One remote-asset URL occurrence inside a markdown file. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pub(super) struct RemoteSpan { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -519,21 +517,21 @@ pub(super) fn plan_asset_name( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| staged: &Path, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| taken: &std::collections::HashSet<String>, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) -> AppResult<PlannedAssetName> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let (stem, extension) = match desired.rfind('.') { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Some(index) if index > 0 => desired.split_at(index), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _ => (desired, ""), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for attempt in 1..=MAX_NAME_PROBES { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let candidate = if attempt == 1 { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| desired.to_string() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| format!("{stem}-{attempt}{extension}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let mut candidates = super::assets::NameCandidates::new(desired, staged.to_path_buf()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let mut probe_count = 0; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| while let Some(candidate) = candidates.next()? { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| probe_count += 1; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if taken.contains(&candidate) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| continue; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let target = assets_dir.join(&candidate); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if !target.exists() && !super::io::file_occupied(&target) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if probe_count > super::assets::SEQUENTIAL_NAME_PROBES { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if let Some(name) = legacy_numbered_reuse_name(assets_dir, desired, staged, taken)? | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return Ok(PlannedAssetName { name, reuse: true }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+529
to
+534
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Run the legacy reuse scan after checking the first digest candidate. The legacy scan runs only when a digest candidate is free. If all digest candidates exist with different bytes, Check the current digest candidate for exact-byte reuse first. Then run Proposed fix let mut candidates = super::assets::NameCandidates::new(desired, staged.to_path_buf());
let mut probe_count = 0;
+ let mut legacy_reuse_checked = false;
while let Some(candidate) = candidates.next()? {
probe_count += 1;
if taken.contains(&candidate) {
continue;
}
let target = assets_dir.join(&candidate);
+ if target.is_file() && same_file_bytes(&target, staged)? {
+ return Ok(PlannedAssetName {
+ name: candidate,
+ reuse: true,
+ });
+ }
+ if !legacy_reuse_checked
+ && probe_count > super::assets::SEQUENTIAL_NAME_PROBES
+ {
+ legacy_reuse_checked = true;
+ if let Some(name) = legacy_numbered_reuse_name(assets_dir, desired, staged, taken)? {
+ return Ok(PlannedAssetName { name, reuse: true });
+ }
+ }
if !target.exists() && !super::io::file_occupied(&target) {
- if probe_count > super::assets::SEQUENTIAL_NAME_PROBES {
- if let Some(name) = legacy_numbered_reuse_name(assets_dir, desired, staged, taken)?
- {
- return Ok(PlannedAssetName { name, reuse: true });
- }
- }
return Ok(PlannedAssetName {
name: candidate,
reuse: false,
});
}
- if target.is_file() && same_file_bytes(&target, staged)? {
- return Ok(PlannedAssetName {
- name: candidate,
- reuse: true,
- });
- }
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return Ok(PlannedAssetName { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name: candidate, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| reuse: false, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -547,10 +545,31 @@ pub(super) fn plan_asset_name( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Err(AppError::io(format!( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "no free asset name after {MAX_NAME_PROBES} probes for {desired}" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "no free asset name after {} probes for {desired}", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| super::assets::MAX_NAME_PROBES | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ))) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fn legacy_numbered_reuse_name( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assets_dir: &Path, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| desired: &str, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| staged: &Path, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| taken: &std::collections::HashSet<String>, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) -> AppResult<Option<String>> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let (stem, ext) = super::assets::split_name(desired); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for suffix in (super::assets::SEQUENTIAL_NAME_PROBES + 1)..=super::assets::MAX_NAME_PROBES { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let candidate = format!("{stem}-{suffix}{ext}"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if taken.contains(&candidate) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| continue; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let target = assets_dir.join(&candidate); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if target.is_file() && same_file_bytes(&target, staged)? { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return Ok(Some(candidate)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Ok(None) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Whether two files hold identical bytes (length check first, so comparing | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// large attachments is cheap in the common differing case). | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pub(super) fn same_file_bytes(existing: &Path, staged: &Path) -> AppResult<bool> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -559,7 +578,23 @@ pub(super) fn same_file_bytes(existing: &Path, staged: &Path) -> AppResult<bool> | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if existing_meta.len() != staged_meta.len() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return Ok(false); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Ok(std::fs::read(existing)? == std::fs::read(staged)?) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let mut existing_file = std::fs::File::open(existing)?; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let mut staged_file = std::fs::File::open(staged)?; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let mut existing_buffer = [0_u8; 64 * 1024]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let mut staged_buffer = [0_u8; 64 * 1024]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| loop { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let existing_read = existing_file.read(&mut existing_buffer)?; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let staged_read = staged_file.read(&mut staged_buffer)?; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if existing_read != staged_read { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return Ok(false); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if existing_read == 0 { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return Ok(true); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if existing_buffer[..existing_read] != staged_buffer[..staged_read] { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return Ok(false); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+585
to
+595
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/usr/bin/env bash
set -euo pipefail
curl -fsSL https://doc.rust-lang.org/std/io/trait.Read.html |
rg -n -C 2 'smaller than the length of the buffer|shorter'Repository: team-reflect/reflect-open Length of output: 163 🏁 Script executed: #!/usr/bin/env bash
set -euo pipefail
printf '%s\n' '--- target function context ---'
sed -n '530,620p' apps/desktop/src-tauri/src/fs/import_assets.rs
printf '%s\n' '--- related symbols and call sites ---'
rg -n -C 3 'same_file_bytes|existing_meta|existing_buffer|staged_buffer|asset reuse|reuse' apps/desktop/src-tauri/src/fs/import_assets.rs
printf '%s\n' '--- Read::read documentation available locally ---'
rustc --print sysroot 2>/dev/null || true
rg -n -C 3 'smaller than the length of the buffer|short read|shorter than' "$(rustc --print sysroot 2>/dev/null)/share/doc/rust/html/std/io/trait.Read.html" 2>/dev/null || trueRepository: team-reflect/reflect-open Length of output: 9042 🏁 Script executed: #!/usr/bin/env bash
set -euo pipefail
printf '%s\n' '--- Rust Read contract ---'
curl -fsSL https://doc.rust-lang.org/std/io/trait.Read.html |
rg -n -C 4 'It is not an error|fewer bytes|less than|short read|number of bytes' |
head -n 80
printf '%s\n' '--- read_exact contract ---'
curl -fsSL https://doc.rust-lang.org/std/io/trait.Read.html |
rg -n -C 4 'read_exact|UnexpectedEof' |
head -n 80
printf '%s\n' '--- relevant tests ---'
sed -n '740,885p' apps/desktop/src-tauri/src/fs/import_assets.rsRepository: team-reflect/reflect-open Length of output: 33925 Do not treat different short-read lengths as different file contents.
Use 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Persist a staged download at its planned name. The plan already verified | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -757,4 +792,88 @@ mod tests { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert_eq!(planned.name, "photo-3.webp"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert!(!planned.reuse); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// A years-long V1 graph can hold thousands of pastes all named | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// `image.png`; once the sequential suffixes are dense the plan must jump | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// to digest names instead of exhausting the probe cap. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #[test] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fn plan_switches_to_digest_names_when_sequential_probes_exhaust() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let dir = tempdir().unwrap(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let assets = dir.path().join("assets"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fs::create_dir_all(&assets).unwrap(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let staged = dir.path().join("staged"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fs::write(&staged, b"screenshot bytes").unwrap(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let mut taken = HashSet::from(["image.png".to_string()]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for suffix in 2..=crate::fs::assets::SEQUENTIAL_NAME_PROBES { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| taken.insert(format!("image-{suffix}.png")); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let planned = plan_asset_name(&assets, "image.png", &staged, &taken).unwrap(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let digest = planned | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .name | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .strip_prefix("image-") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .and_then(|rest| rest.strip_suffix(".png")) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .unwrap(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert_eq!(digest.len(), 8); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert!(digest.chars().all(|ch| ch.is_ascii_hexdigit())); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert!(!planned.reuse); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Re-importing the same bytes re-derives the same digest name and | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // reuses the file already on disk. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fs::write(assets.join(&planned.name), b"screenshot bytes").unwrap(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let again = plan_asset_name(&assets, "image.png", &staged, &taken).unwrap(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert_eq!(again.name, planned.name); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert!(again.reuse); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // A different file lands beside it under its own digest. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let other = dir.path().join("other"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fs::write(&other, b"a different screenshot").unwrap(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let planned_other = plan_asset_name(&assets, "image.png", &other, &taken).unwrap(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert_ne!(planned_other.name, planned.name); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert!(!planned_other.reuse); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #[test] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fn plan_reuses_legacy_numbered_names_after_digest_switch() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let dir = tempdir().unwrap(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let assets = dir.path().join("assets"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fs::create_dir_all(&assets).unwrap(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let staged = dir.path().join("staged"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fs::write(&staged, b"same old screenshot").unwrap(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fs::write(assets.join("image-9.png"), b"same old screenshot").unwrap(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let mut taken = HashSet::from(["image.png".to_string()]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for suffix in 2..=crate::fs::assets::SEQUENTIAL_NAME_PROBES { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| taken.insert(format!("image-{suffix}.png")); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let planned = plan_asset_name(&assets, "image.png", &staged, &taken).unwrap(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert_eq!(planned.name, "image-9.png"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert!(planned.reuse); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #[test] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fn same_file_bytes_compares_large_files_incrementally() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let dir = tempdir().unwrap(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let first = dir.path().join("first"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let second = dir.path().join("second"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let different = dir.path().join("different"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let mut first_file = fs::File::create(&first).unwrap(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let mut second_file = fs::File::create(&second).unwrap(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let mut different_file = fs::File::create(&different).unwrap(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let chunk = [11_u8; 1024]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let different_chunk = [12_u8; 1024]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for _ in 0..100 { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| first_file.write_all(&chunk).unwrap(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| second_file.write_all(&chunk).unwrap(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| different_file.write_all(&different_chunk).unwrap(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| drop(first_file); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| drop(second_file); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| drop(different_file); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert!(same_file_bytes(&first, &second).unwrap()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert!(!same_file_bytes(&first, &different).unwrap()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.