fix(downloads): clean settled archives before deleting stale rows

This commit is contained in:
4gray committed 2026-09-08 03:19:32 +02:00
1 parent 0a780ecd10
commit 916f216655
3 files changed
+41 -2

No files matched your search

@@ -56,7 +56,13 @@ export async function removeDownloadRequest(downloadId: number) {
success: false,
error: 'Download is completing; try again shortly',
};
if (row?.filePath && removablePartialStatuses.has(row.status)) {
// A settled archive can still say downloading when its cancellation
// status write failed. Its journal must be drained before row deletion.
if (
row?.filePath &&
(row.contentType === 'catchup' ||
removablePartialStatuses.has(row.status))
) {
try {
if (row.contentType === 'catchup')
removeJournaledCatchupPartial(
@@ -78,6 +78,37 @@ describe('downloads events: partial-file cleanup', () => {
);
});
it.each([false, true])(
'retries settled archive cleanup despite a failed cancellation status write (locked=%s)',
async (locked) => {
const { deleteWhere } = mockDownloadRow({
...createDownloadRow('downloading'),
contentType: 'catchup',
});
const proof = {
version: 1,
phase: 'transfer',
partialCleanupPath: '/downloads/.iptvnator-cleanup-test/entry',
};
mockArchiveProofs.mockResolvedValue(new Map([[42, proof]]));
if (locked)
mockRemoveJournaledPartial.mockImplementation(() => {
throw new Error('EACCES');
});
await expect(
getHandler('DOWNLOADS_REMOVE')(null, 42)
).resolves.toMatchObject({ success: !locked });
expect(mockRemoveJournaledPartial).toHaveBeenCalledWith(
'/downloads/resume.mp4',
proof,
expect.any(Function),
true
);
if (locked) expect(deleteWhere).not.toHaveBeenCalled();
else expect(deleteWhere).toHaveBeenCalled();
}
);
it('keeps a committing archive row and journal intact on Remove', async () => {
const { deleteWhere } = mockDownloadRow(
createDownloadRow('downloading')
+3 -1
View File
@@ -89,7 +89,9 @@ overwritten by completion. Remove rejects this committing row before partial
cleanup or deletion, and Clear completed skips it, preserving the cascading
journal until completion finishes. Remove waits for an accepted active archive
cancellation to settle before reading/deleting its row; queued archives are
canceled first, and a concurrent new runtime attempt blocks removal. Remove, Clear completed and missing-file
canceled first, and a concurrent new runtime attempt blocks removal. A settled
archive still marked downloading after a failed status write also retries
journal cleanup before row deletion. Remove, Clear completed and missing-file
re-download use journal-backed private capture for archive partial cleanup;
unknown or replaced entries are preserved. Before capture, a synchronous SQLite
write records `partialCleanupPath` (or `finalCleanupPath` for failed promotion)