Stream RAW saves with live progress v0.8.3

This commit is contained in:
dmo committed 2026-07-25 08:38:39 +02:00
1 parent 2dee67021e
commit f1704c9822
6 files changed
+85 -27

No files matched your search

+58 -19
View File
@@ -357,7 +357,7 @@ function updatePhotoStatus(photo) {
const values = photo.fileValues || photo.values;
if (photo.status === 'saved') {
const timing = photo.lastSaveTiming
? ` · ${photo.lastSaveTiming.total.toFixed(1)}s total · ${photo.lastSaveTiming.serverTransfer.toFixed(1)}s server/transfer · ${photo.lastSaveTiming.localWrite.toFixed(1)}s local write`
? ` · ${formatSaveTiming(photo.lastSaveTiming)}`
: '';
setSaveStatus('saved', 'Previously saved', `${formatMetadataSummary(values)}${timing}`);
} else if (photo.status === 'skipped') {
@@ -371,6 +371,13 @@ function updatePhotoStatus(photo) {
}
}
function formatSaveTiming(timing) {
if (timing.streamed) {
return `${timing.total.toFixed(1)}s total · ${timing.firstByte.toFixed(1)}s to first byte · ${timing.streamWrite.toFixed(1)}s stream/write`;
}
return `${timing.total.toFixed(1)}s total · ${timing.serverTransfer.toFixed(1)}s server/transfer · ${timing.localWrite.toFixed(1)}s local write`;
}
async function readPhotoMetadata(photo, file) {
if (photo.metadataLoaded) return;
const payload = new FormData();
@@ -625,20 +632,59 @@ async function savePhoto(moveNext) {
throw new Error(message);
}
const updatedBlob = await response.blob();
const serverAndTransferSeconds = (performance.now() - saveStarted) / 1000;
setSaveStatus('saving', 'Writing updated file locally…', `${photo.name} · ${serverAndTransferSeconds.toFixed(1)}s processing and transfer`);
const localWriteStarted = performance.now();
const responseReceivedAt = performance.now();
const firstByteSeconds = (responseReceivedAt - saveStarted) / 1000;
const writable = await photo.handle.createWritable({ keepExistingData: false });
let timing;
let responseReader = null;
try {
await writable.write(updatedBlob);
await writable.close();
if (response.body && typeof response.body.getReader === 'function') {
responseReader = response.body.getReader();
const expectedBytes = Number(response.headers.get('content-length')) || 0;
let receivedBytes = 0;
let lastProgressUpdate = 0;
while (true) {
const { done, value } = await responseReader.read();
if (done) break;
await writable.write(value);
receivedBytes += value.byteLength;
const now = performance.now();
if (now - lastProgressUpdate >= 200) {
const progress = expectedBytes
? `${Math.min(100, (receivedBytes / expectedBytes) * 100).toFixed(0)}% · ${formatBytes(receivedBytes)} / ${formatBytes(expectedBytes)}`
: `${formatBytes(receivedBytes)} received`;
setSaveStatus('saving', 'Streaming updated file…', `${photo.name} · ${progress}`);
lastProgressUpdate = now;
}
}
await writable.close();
const completedAt = performance.now();
timing = {
streamed: true,
total: (completedAt - saveStarted) / 1000,
firstByte: firstByteSeconds,
streamWrite: (completedAt - responseReceivedAt) / 1000,
};
} else {
const updatedBlob = await response.blob();
const serverAndTransferSeconds = (performance.now() - saveStarted) / 1000;
setSaveStatus('saving', 'Writing updated file locally…', `${photo.name} · buffered compatibility mode`);
const localWriteStarted = performance.now();
await writable.write(updatedBlob);
await writable.close();
const localWriteSeconds = (performance.now() - localWriteStarted) / 1000;
timing = {
streamed: false,
total: serverAndTransferSeconds + localWriteSeconds,
serverTransfer: serverAndTransferSeconds,
localWrite: localWriteSeconds,
};
}
} catch (error) {
if (responseReader) await responseReader.cancel().catch(() => {});
await writable.abort().catch(() => {});
throw error;
}
const localWriteSeconds = (performance.now() - localWriteStarted) / 1000;
const totalSaveSeconds = serverAndTransferSeconds + localWriteSeconds;
const updatedFile = await photo.handle.getFile();
photo.size = updatedFile.size;
@@ -650,21 +696,14 @@ async function savePhoto(moveNext) {
photo.fileValues = { ...savedValues };
photo.metadataLoaded = true;
photo.metadataPresent = true;
photo.lastSaveTiming = {
total: totalSaveSeconds,
serverTransfer: serverAndTransferSeconds,
localWrite: localWriteSeconds,
};
photo.lastSaveTiming = timing;
saveFolderState();
setSaveStatus(
'saved',
'Saved in place',
`${formatMetadataSummary(savedValues)} · ${serverAndTransferSeconds.toFixed(1)}s server/transfer · ${localWriteSeconds.toFixed(1)}s local write`,
);
showToast(
`Saved ${photo.name}: ${totalSaveSeconds.toFixed(1)}s total · `
+ `${serverAndTransferSeconds.toFixed(1)}s server/transfer · ${localWriteSeconds.toFixed(1)}s local write`,
`${formatMetadataSummary(savedValues)} · ${formatSaveTiming(timing)}`,
);
showToast(`Saved ${photo.name}: ${formatSaveTiming(timing)}`);
updateProgress();
const oldThumb = state.thumbUrls.get(photo.name);