fix(native-worker): keep batches and retries intact across seam races

- Verification challenge no longer abandons the batch: batch mates the
  worker cancel would mark skipped are requeued and fenced from later
  snapshot applications, and the adoption loop restarts the queue for
  requeued items when the run ends
- Pause vs cancel race: a cancelled result with no pause flag set waits
  up to 1.5s for the pause intent to land on the main looper before
  being classified as a permanent skip
- Stale Go cancel flag: new ResetDownloadCancel export (wired through
  Android and iOS bridges) drops a pre-registered cancel with no active
  download; retryItem/retryAllFailed call it so the first retry of a
  cancelled-before-start item no longer aborts instantly
- Reconcile-loop errors now cancel the native worker and clear the run
  id before marking items failed, instead of leaving the service
  downloading a batch the UI already wrote off
This commit is contained in:
zarzet
2026-07-10 09:48:52 +07:00
parent d819878ec7
commit 9b7f1631f0
8 changed files with 151 additions and 1 deletions
@@ -755,6 +755,27 @@ class DownloadService : Service() {
writeNativeAlbumReplayGainIfComplete()
} else {
val errorType = result.optString("error_type")
if (errorType == "cancelled" &&
!nativeWorkerPaused &&
!nativeWorkerCancelRequested &&
generation == nativeWorkerGeneration
) {
// A pause from Dart cancels the in-flight Go
// download directly but delivers the pause flag
// via a startService intent through the main
// looper; the download can unwind first. Give the
// flag a moment to settle before classifying this
// cancellation as a permanent skip.
var waitedMs = 0L
while (waitedMs < 1500 &&
!nativeWorkerPaused &&
!nativeWorkerCancelRequested &&
generation == nativeWorkerGeneration
) {
delay(100)
waitedMs += 100
}
}
if (errorType == "cancelled" && nativeWorkerPaused && !nativeWorkerCancelRequested) {
updateNativeWorkerItem(request.itemId) {
it.status = "queued"
@@ -2308,6 +2308,13 @@ class MainActivity: FlutterFragmentActivity() {
}
result.success(null)
}
"resetDownloadCancel" -> {
val itemId = call.argument<String>("item_id") ?: ""
withContext(Dispatchers.IO) {
Gobackend.resetDownloadCancel(itemId)
}
result.success(null)
}
"setDownloadDirectory" -> {
val path = call.argument<String>("path") ?: ""
withContext(Dispatchers.IO) {