From 2c0ce5bb28e57407d0219cdcb928efd590ea23f7 Mon Sep 17 00:00:00 2001 From: birdbird <6892457-tzugen@users.noreply.gitlab.com> Date: Fri, 2 Oct 2026 11:21:28 +0000 Subject: [PATCH] Fix failedList removal in deleteAsync and narrow task exception handler - deleteAsync removed a failedList entry from the downloadQueue (a no-op: failed items were already polled out of the queue), leaving stale FAILED state for deleted tracks. Remove the entry from failedList instead. - The DownloadTask CoroutineExceptionHandler deleted the complete and pinned files on any uncaught exception. Those files may already hold a finalized download (exceptions can occur in post-rename steps such as the catalog write), so this destroyed finished data. Only the partial file is deleted now: retries do not append to a possibly untrustworthy stream, while finalized downloads and cancelled (resumable) downloads are left untouched. --- .../org/moire/ultrasonic/service/DownloadService.kt | 2 +- .../org/moire/ultrasonic/service/DownloadTask.kt | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/DownloadService.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/DownloadService.kt index 273b9b41..a24f941f 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/DownloadService.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/DownloadService.kt @@ -423,7 +423,7 @@ class DownloadService : private suspend fun deleteAsync(track: Track) { withContext(Dispatchers.IO) { downloadQueue.get(track.id)?.let { downloadQueue.remove(it) } - failedList[track.id]?.let { downloadQueue.remove(it) } + failedList.remove(track.id) cancelDownload(track) Storage.delete(track.getPartialFile()) diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/DownloadTask.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/DownloadTask.kt index 89b00619..275e36f8 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/DownloadTask.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/DownloadTask.kt @@ -206,8 +206,14 @@ class DownloadTask( exception -> Timber.w(exception, "Exception in DownloadTask ${downloadTrack.pinnedFile}") - Storage.delete(downloadTrack.completeFile) - Storage.delete(downloadTrack.pinnedFile) + // Only ever touch the partial file here: the complete/pinned files may hold a + // download that has already been finalized (the exception may have happened + // during a post-rename step such as the catalog write), so deleting them would + // destroy finished data. The partial file is removed so that a retry does not + // append to a possibly untrustworthy stream; pure cancellation keeps the partial + // file, because the exception handler is not invoked for CancellationException + // and cancelled downloads should remain resumable. + Storage.delete(downloadTrack.partialFile) } fun start() {