refactor(download): simplify finalization and remove the retired worker

This commit is contained in:
zarzet committed 2026-09-30 21:44:31 +07:00
1 parent 3a63918e1f
commit b8e3a4c166
5 files changed
+32 -370

No files matched your search

@@ -10,8 +10,6 @@ import android.content.Intent
import android.content.pm.ServiceInfo
import android.net.ConnectivityManager
import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkRequest
import android.os.Build
import android.os.IBinder
import android.os.PowerManager
@@ -1432,295 +1430,6 @@ class DownloadService : Service() {
}
}
private suspend fun runNativeWorker(
requests: List<NativeDownloadRequest>,
settingsJson: String,
generation: Long
) {
val rateLimitAttempts = mutableMapOf<String, Int>()
val progressCoordinatorJob = startNativeWorkerProgressCoordinator(generation)
try {
var requestIndex = 0
while (requestIndex < requests.size) {
val request = requests[requestIndex]
while (isNativeWorkerPaused() &&
!nativeWorkerCancelRequested &&
generation == nativeWorkerGeneration
) {
writeNativeWorkerSnapshot(
isRunning = true,
isPaused = true,
currentItemId = request.itemId,
message = nativeWorkerPauseMessage(),
settingsJson = settingsJson,
includeItems = true
)
delay(500)
}
if (nativeWorkerCancelRequested || generation != nativeWorkerGeneration) {
break
}
var retryCurrentRequest = false
nativeWorkerCurrentItemId = request.itemId
currentTrackName = request.trackName
currentArtistName = request.artistName
currentStatus = "preparing"
lastProgress = 0L
lastTotal = 0L
updateNotification(0, 0)
updateNativeWorkerItem(request.itemId) {
it.status = "preparing"
it.progress = 0.0
it.bytesReceived = 0L
it.bytesTotal = 0L
it.error = ""
it.resultJson = null
}
writeNativeWorkerSnapshot(
isRunning = true,
isPaused = false,
currentItemId = request.itemId,
message = "Preparing",
settingsJson = settingsJson,
includeItems = true
)
var directoryScope: AutoCloseable? = null
try {
directoryScope = coreBackend.openDownloadDirectoryForRequest(request.requestJson)
coreBackend.initItemProgress(request.itemId)
val response = SafDownloadHandler.handle(this, request.requestJson, coreBackend)
if (generation != nativeWorkerGeneration) {
// Superseded while blocked in the download call; the
// new run owns the shared state now.
break
}
var result = JSONObject(response)
if (result.optBoolean("success", false)) {
currentStatus = "finalizing"
updateNativeWorkerItem(request.itemId) {
it.status = "finalizing"
it.progress = 0.95
it.error = ""
}
writeNativeWorkerSnapshot(
isRunning = true,
isPaused = false,
currentItemId = request.itemId,
message = "Finalizing",
settingsJson = settingsJson
)
result = NativeDownloadFinalizer.finalize(
this,
request.itemId,
request.requestJson,
request.itemJson,
result,
settingsJson
) {
nativeWorkerCancelRequested ||
isNativeWorkerPaused() ||
generation != nativeWorkerGeneration
}
}
if (result.optBoolean("success", false)) {
result.optJSONObject("replaygain")?.let { replayGain ->
synchronized(nativeReplayGainEntries) {
nativeReplayGainEntries.add(JSONObject(replayGain.toString()))
}
}
updateNativeWorkerItem(request.itemId) {
it.status = "completed"
it.progress = 1.0
it.error = ""
it.resultJson = result
}
writeNativeReplayGainJournal()
writeNativeAlbumReplayGainIfComplete()
} else {
val errorType = result.optString("error_type")
val errorMessage = result.optString("error")
if (errorType == "cancelled" &&
isNativeWorkerPaused() &&
!nativeWorkerCancelRequested
) {
updateNativeWorkerItem(request.itemId) {
it.status = "queued"
it.progress = 0.0
it.bytesReceived = 0L
it.bytesTotal = 0L
it.error = ""
it.resultJson = null
}
writeNativeWorkerSnapshot(
isRunning = true,
isPaused = true,
currentItemId = request.itemId,
message = "Paused",
settingsJson = settingsJson,
includeItems = true
)
retryCurrentRequest = true
} else if (NativeWorkerPolicy.shouldRetryRateLimit(
errorType = errorType,
errorMessage = errorMessage,
attempts = rateLimitAttempts[request.itemId] ?: 0,
)
) {
rateLimitAttempts[request.itemId] =
(rateLimitAttempts[request.itemId] ?: 0) + 1
val delaySeconds = NativeWorkerPolicy.rateLimitDelaySeconds(
retryAfterSeconds = result
.optInt("retry_after_seconds", 0)
.takeIf { it > 0 },
errorMessage = errorMessage,
)
currentStatus = "rate_limited"
updateNativeWorkerItem(request.itemId) {
it.status = "queued"
it.progress = 0.0
it.bytesReceived = 0L
it.bytesTotal = 0L
it.error = "Rate limited, retrying in ${delaySeconds}s"
it.resultJson = null
}
writeNativeWorkerSnapshot(
isRunning = true,
isPaused = isNativeWorkerPaused(),
currentItemId = request.itemId,
message = "Rate limited, retrying in ${delaySeconds}s",
settingsJson = settingsJson,
includeItems = true,
)
updateNotification(0L, 0L)
delay(delaySeconds * 1000L)
retryCurrentRequest = true
} else if (NativeWorkerPolicy.isVerificationRequired(
errorType = errorType,
errorMessage = errorMessage,
)
) {
nativeWorkerVerificationPaused = true
currentStatus = "verification_required"
updateNativeWorkerItem(request.itemId) {
it.status = "failed"
it.error = errorMessage
it.resultJson = result
}
writeNativeReplayGainJournal()
writeNativeWorkerSnapshot(
isRunning = true,
isPaused = true,
currentItemId = request.itemId,
message = "Verification required",
lastResult = result,
settingsJson = settingsJson,
includeItems = true,
)
// Publish immediately. If Flutter is alive it will
// replace this same notification ID while owning
// the interactive challenge; if Flutter is
// suspended, the native alert remains visible.
showNativeVerificationRequired(request, result)
updateNotification(0L, 0L)
retryCurrentRequest = true
} else {
updateNativeWorkerItem(request.itemId) {
it.status = if (errorType == "cancelled") {
"skipped"
} else {
"failed"
}
it.error = errorMessage
it.resultJson = result
}
writeNativeReplayGainJournal()
}
}
if (!retryCurrentRequest) {
writeNativeWorkerSnapshot(
isRunning = true,
isPaused = false,
currentItemId = request.itemId,
message = if (result.optBoolean("success", false)) "Completed" else "Failed",
lastResult = result,
settingsJson = settingsJson,
includeItems = true
)
}
} catch (e: CancellationException) {
if (nativeWorkerCancelRequested &&
generation == nativeWorkerGeneration
) {
updateNativeWorkerItem(request.itemId) {
it.status = "skipped"
it.error = "Cancelled"
}
}
throw e
} catch (e: Exception) {
updateNativeWorkerItem(request.itemId) {
it.status = "failed"
it.error = e.message ?: "Native download failed"
}
writeNativeReplayGainJournal()
writeNativeWorkerSnapshot(
isRunning = true,
isPaused = false,
currentItemId = request.itemId,
message = e.message ?: "Native download failed",
settingsJson = settingsJson,
includeItems = true
)
} finally {
directoryScope?.close()
finishNativePauseCancellation(request.itemId, generation)
updateNativeWorkerItemProgress(request.itemId)
try {
coreBackend.clearItemProgress(request.itemId)
} catch (_: Exception) {
}
}
if (!retryCurrentRequest) {
if (nativeWorkerCurrentItemId == request.itemId) {
nativeWorkerCurrentItemId = ""
}
requestIndex++
}
}
} finally {
stopNativeWorkerProgressCoordinator(progressCoordinatorJob)
if (generation == nativeWorkerGeneration) {
cancelScheduledNativeWorkerItemsSnapshot()
if (!nativeWorkerCancelRequested) {
flushNativeAlbumReplayGainJournalIfComplete()
}
val counts = nativeWorkerCounts()
val shouldNotifyCompletion =
NativeWorkerPolicy.shouldNotifyQueueComplete(
cancelRequested = nativeWorkerCancelRequested,
completed = counts.completed,
failed = counts.failed,
)
currentStatus = "finalizing"
releaseIdleDownloadMemory()
writeNativeWorkerSnapshot(
isRunning = false,
isPaused = false,
currentItemId = "",
message = if (nativeWorkerCancelRequested) "Cancelled" else "Finished",
settingsJson = settingsJson,
includeItems = true
)
stopForegroundService(cancelNativeWorker = false)
if (shouldNotifyCompletion) {
showNativeQueueComplete(counts)
}
}
}
}
private fun releaseIdleDownloadMemory() {
try {
// All workers and album tagging have finished. Release idle backend