Write the composite under a .part name until it is whole
The merge writes its outbox record before the DNG, which takes minutes, and a drain that ran in the meantime took whatever lay at the record's name: a sync pass that fired mid-merge uploaded the first part of the composite and cleared the record. The file is now written as x.dng.part and renamed into place once the last strip is in; the drain skips a record whose payload does not exist yet.
This commit is contained in:
+22
-1
@@ -617,8 +617,17 @@ fn run_inner(
|
|||||||
(path, true)
|
(path, true)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
// Written under a name of its own and renamed into place once the last
|
||||||
|
// strip is in. The record above names `out_path`, and a drain that runs
|
||||||
|
// while the merge is still writing — a sync pass fires one whenever the
|
||||||
|
// sweep finishes — takes whatever is at that name: it uploaded the first
|
||||||
|
// few hundred megabytes of a composite, cleared the record, and the file
|
||||||
|
// on the server stayed truncated for good. `pending` skips a record whose
|
||||||
|
// payload does not exist yet, so the rename is what releases it.
|
||||||
|
let part_path = part_name(&out_path);
|
||||||
let cleanup = |path: &Path| {
|
let cleanup = |path: &Path| {
|
||||||
let _ = std::fs::remove_file(path);
|
let _ = std::fs::remove_file(path);
|
||||||
|
let _ = std::fs::remove_file(part_name(path));
|
||||||
if staged {
|
if staged {
|
||||||
let _ = std::fs::remove_file(crate::export::destination_record(path));
|
let _ = std::fs::remove_file(crate::export::destination_record(path));
|
||||||
}
|
}
|
||||||
@@ -798,7 +807,7 @@ fn run_inner(
|
|||||||
let inscribed_for_writer = inscribed.clone();
|
let inscribed_for_writer = inscribed.clone();
|
||||||
let fill_for_writer = fill_cam.is_some();
|
let fill_for_writer = fill_cam.is_some();
|
||||||
let file =
|
let file =
|
||||||
std::fs::File::create(&out_path).map_err(|e| format!("{}: {e}", out_path.display()))?;
|
std::fs::File::create(&part_path).map_err(|e| format!("{}: {e}", part_path.display()))?;
|
||||||
let writer = executors::spawn(Executor::Io, "dng-write", move || -> Result<(), String> {
|
let writer = executors::spawn(Executor::Io, "dng-write", move || -> Result<(), String> {
|
||||||
let mut file = std::io::BufWriter::new(file);
|
let mut file = std::io::BufWriter::new(file);
|
||||||
dr_export::write_linear_dng(
|
dr_export::write_linear_dng(
|
||||||
@@ -910,6 +919,10 @@ fn run_inner(
|
|||||||
cleanup(&out_path);
|
cleanup(&out_path);
|
||||||
return Err(e);
|
return Err(e);
|
||||||
}
|
}
|
||||||
|
if let Err(e) = std::fs::rename(&part_path, &out_path) {
|
||||||
|
cleanup(&out_path);
|
||||||
|
return Err(format!("{}: {e}", out_path.display()));
|
||||||
|
}
|
||||||
log::info!(
|
log::info!(
|
||||||
"merge: {}×{} written to {} in {:?}",
|
"merge: {}×{} written to {} in {:?}",
|
||||||
out_w,
|
out_w,
|
||||||
@@ -1471,6 +1484,14 @@ fn stem(p: &Path) -> String {
|
|||||||
.unwrap_or_else(|| p.display().to_string())
|
.unwrap_or_else(|| p.display().to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Where the composite is written until it is whole: `x.dng.part` for
|
||||||
|
/// `x.dng`.
|
||||||
|
fn part_name(path: &Path) -> PathBuf {
|
||||||
|
let mut name = path.as_os_str().to_owned();
|
||||||
|
name.push(".part");
|
||||||
|
PathBuf::from(name)
|
||||||
|
}
|
||||||
|
|
||||||
/// `name` in `dir`, numbered if that name is taken: a merge never
|
/// `name` in `dir`, numbered if that name is taken: a merge never
|
||||||
/// overwrites (FR-MRG-3).
|
/// overwrites (FR-MRG-3).
|
||||||
fn unused_name(dir: &Path, name: &str) -> PathBuf {
|
fn unused_name(dir: &Path, name: &str) -> PathBuf {
|
||||||
|
|||||||
Reference in New Issue
Block a user