mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-10-08 00:51:07 +02:00
refactor(download): simplify finalization and remove the retired worker
This commit is contained in:
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
|
||||
|
||||
Reference in new issue
Block a user