mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2026-08-29 14:47:21 +02:00
Cleaning up retry logic and unused return types
This commit is contained in:
@@ -98,7 +98,15 @@ class DownloadItemManager(
|
|||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun addDownloadItem(downloadItem: DownloadItem) {
|
fun addDownloadItem(downloadItem: DownloadItem) {
|
||||||
if (downloadItemQueue.any { it.id == downloadItem.id }) return
|
val existingItem = downloadItemQueue.find { it.id == downloadItem.id }
|
||||||
|
if (existingItem != null) {
|
||||||
|
if (existingItem.terminalFailureAt != null) {
|
||||||
|
retryDownloadItem(existingItem)
|
||||||
|
checkUpdateDownloadQueue()
|
||||||
|
notifyQueueChanged()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
persist(downloadItem, force = true)
|
persist(downloadItem, force = true)
|
||||||
downloadItemQueue.add(downloadItem)
|
downloadItemQueue.add(downloadItem)
|
||||||
clientEventEmitter.onDownloadItem(downloadItem)
|
clientEventEmitter.onDownloadItem(downloadItem)
|
||||||
@@ -108,22 +116,24 @@ class DownloadItemManager(
|
|||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun retryAll() {
|
fun retryAll() {
|
||||||
downloadItemQueue.forEach { item ->
|
downloadItemQueue.forEach(::retryDownloadItem)
|
||||||
item.terminalFailureAt = null
|
|
||||||
IncompleteDownloadCleanup.cancel(context, item.id)
|
|
||||||
item.downloadItemParts.filter { it.failed }.forEach { part ->
|
|
||||||
part.failed = false
|
|
||||||
part.completed = false
|
|
||||||
part.isMoving = false
|
|
||||||
part.downloadId = null
|
|
||||||
part.retryCount = 0
|
|
||||||
}
|
|
||||||
persist(item, force = true)
|
|
||||||
}
|
|
||||||
checkUpdateDownloadQueue()
|
checkUpdateDownloadQueue()
|
||||||
notifyQueueChanged()
|
notifyQueueChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun retryDownloadItem(item: DownloadItem) {
|
||||||
|
item.terminalFailureAt = null
|
||||||
|
IncompleteDownloadCleanup.cancel(context, item.id)
|
||||||
|
item.downloadItemParts.filter { it.failed }.forEach { part ->
|
||||||
|
part.failed = false
|
||||||
|
part.completed = false
|
||||||
|
part.isMoving = false
|
||||||
|
part.downloadId = null
|
||||||
|
part.retryCount = 0
|
||||||
|
}
|
||||||
|
persist(item, force = true)
|
||||||
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun cancelAll() {
|
fun cancelAll() {
|
||||||
activeCalls.values.forEach(Call::cancel)
|
activeCalls.values.forEach(Call::cancel)
|
||||||
@@ -139,7 +149,12 @@ class DownloadItemManager(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun hasWork(): Boolean = downloadItemQueue.isNotEmpty()
|
fun hasWork(): Boolean =
|
||||||
|
downloadItemQueue.any { item ->
|
||||||
|
item.downloadItemParts.any { part ->
|
||||||
|
(!part.completed && !part.failed) || part.isMoving
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
private fun checkUpdateDownloadQueue() {
|
private fun checkUpdateDownloadQueue() {
|
||||||
@@ -156,7 +171,7 @@ class DownloadItemManager(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
startWatchingDownloads()
|
if (hasWork()) startWatchingDownloads() else notifyQueueChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun startDownload(item: DownloadItem, part: DownloadItemPart) {
|
private fun startDownload(item: DownloadItem, part: DownloadItemPart) {
|
||||||
@@ -202,7 +217,7 @@ class DownloadItemManager(
|
|||||||
activeParts.forEach(::handlePartUpdate)
|
activeParts.forEach(::handlePartUpdate)
|
||||||
synchronized(this@DownloadItemManager) {
|
synchronized(this@DownloadItemManager) {
|
||||||
checkUpdateDownloadQueue()
|
checkUpdateDownloadQueue()
|
||||||
if (downloadItemQueue.isEmpty()) {
|
if (!hasWork()) {
|
||||||
watcherRunning = false
|
watcherRunning = false
|
||||||
notifyQueueChanged()
|
notifyQueueChanged()
|
||||||
return@launch
|
return@launch
|
||||||
@@ -235,10 +250,11 @@ class DownloadItemManager(
|
|||||||
if (part.isInternalStorage) finalizeInternalFile(item, part) else moveDownloadedFile(item, part)
|
if (part.isInternalStorage) finalizeInternalFile(item, part) else moveDownloadedFile(item, part)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
private fun failOrRetry(item: DownloadItem, part: DownloadItemPart, reason: String) {
|
private fun failOrRetry(item: DownloadItem, part: DownloadItemPart, reason: String) {
|
||||||
removeActivePart(part)
|
removeActivePart(part)
|
||||||
part.retryCount += 1
|
part.retryCount += 1
|
||||||
releaseReservation(part)
|
reservations.remove(part.destinationPath)
|
||||||
if (part.retryCount > MAX_RETRIES) {
|
if (part.retryCount > MAX_RETRIES) {
|
||||||
Log.e(tag, "$reason after $MAX_RETRIES retries: ${part.filename}")
|
Log.e(tag, "$reason after $MAX_RETRIES retries: ${part.filename}")
|
||||||
part.failed = true
|
part.failed = true
|
||||||
@@ -255,10 +271,6 @@ class DownloadItemManager(
|
|||||||
part.downloadId = null
|
part.downloadId = null
|
||||||
part.isMoving = false
|
part.isMoving = false
|
||||||
persist(item, force = true)
|
persist(item, force = true)
|
||||||
scope.launch {
|
|
||||||
delay(RETRY_BASE_DELAY_MS * (1L shl (part.retryCount - 1)))
|
|
||||||
synchronized(this@DownloadItemManager) { checkUpdateDownloadQueue() }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun finalizeInternalFile(item: DownloadItem, part: DownloadItemPart) {
|
private fun finalizeInternalFile(item: DownloadItem, part: DownloadItemPart) {
|
||||||
@@ -323,6 +335,7 @@ class DownloadItemManager(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
private fun failFinalization(item: DownloadItem, part: DownloadItemPart, message: String) {
|
private fun failFinalization(item: DownloadItem, part: DownloadItemPart, message: String) {
|
||||||
Log.e(tag, message)
|
Log.e(tag, message)
|
||||||
part.isMoving = false
|
part.isMoving = false
|
||||||
@@ -330,12 +343,13 @@ class DownloadItemManager(
|
|||||||
failOrRetry(item, part, message)
|
failOrRetry(item, part, message)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
private fun completePart(item: DownloadItem, part: DownloadItemPart) {
|
private fun completePart(item: DownloadItem, part: DownloadItemPart) {
|
||||||
part.moved = true
|
part.moved = true
|
||||||
part.completed = true
|
part.completed = true
|
||||||
part.failed = false
|
part.failed = false
|
||||||
part.isMoving = false
|
part.isMoving = false
|
||||||
releaseReservation(part)
|
reservations.remove(part.destinationPath)
|
||||||
removeActivePart(part)
|
removeActivePart(part)
|
||||||
persist(item, force = true)
|
persist(item, force = true)
|
||||||
checkDownloadItemFinished(item)
|
checkDownloadItemFinished(item)
|
||||||
@@ -393,8 +407,6 @@ class DownloadItemManager(
|
|||||||
private fun storageKey(file: File): String =
|
private fun storageKey(file: File): String =
|
||||||
if (file.absolutePath.startsWith(context.filesDir.absolutePath)) "internal" else "external"
|
if (file.absolutePath.startsWith(context.filesDir.absolutePath)) "internal" else "external"
|
||||||
|
|
||||||
private fun releaseReservation(part: DownloadItemPart) { reservations.remove(part.destinationPath) }
|
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
private fun removeActivePart(part: DownloadItemPart) {
|
private fun removeActivePart(part: DownloadItemPart) {
|
||||||
activeCalls.remove(part.id)
|
activeCalls.remove(part.id)
|
||||||
@@ -408,7 +420,7 @@ class DownloadItemManager(
|
|||||||
DeviceManager.dbManager.saveDownloadItem(item)
|
DeviceManager.dbManager.saveDownloadItem(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun notifyQueueChanged() { clientEventEmitter.onQueueChanged(downloadItemQueue.isNotEmpty()) }
|
private fun notifyQueueChanged() { clientEventEmitter.onQueueChanged(hasWork()) }
|
||||||
|
|
||||||
fun destroy() {
|
fun destroy() {
|
||||||
activeCalls.values.forEach(Call::cancel)
|
activeCalls.values.forEach(Call::cancel)
|
||||||
@@ -445,7 +457,6 @@ class DownloadItemManager(
|
|||||||
const val MAX_SIMULTANEOUS_DOWNLOADS = 3
|
const val MAX_SIMULTANEOUS_DOWNLOADS = 3
|
||||||
const val WATCH_INTERVAL_MS = 1_000L
|
const val WATCH_INTERVAL_MS = 1_000L
|
||||||
const val STALL_TIMEOUT_MS = 60_000L
|
const val STALL_TIMEOUT_MS = 60_000L
|
||||||
const val RETRY_BASE_DELAY_MS = 5_000L
|
|
||||||
const val MAX_RETRIES = 5
|
const val MAX_RETRIES = 5
|
||||||
const val PERSIST_INTERVAL_MS = 2_000L
|
const val PERSIST_INTERVAL_MS = 2_000L
|
||||||
const val MIN_FREE_SPACE_BYTES = 100L * 1024L * 1024L
|
const val MIN_FREE_SPACE_BYTES = 100L * 1024L * 1024L
|
||||||
|
|||||||
+3
-5
@@ -35,15 +35,13 @@ object IncompleteDownloadCleanup {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Removes failures retained longer than 24 hours when scheduled work did not run. */
|
/** Removes failures retained longer than 24 hours when scheduled work did not run. */
|
||||||
fun cleanupExpired(context: Context): Set<String> {
|
fun cleanupExpired(context: Context) {
|
||||||
val now = System.currentTimeMillis()
|
val now = System.currentTimeMillis()
|
||||||
return DeviceManager.dbManager.getDownloadItems()
|
DeviceManager.dbManager.getDownloadItems()
|
||||||
.filter { item -> isEligible(item, now) }
|
.filter { item -> isEligible(item, now) }
|
||||||
.map { item ->
|
.forEach { item ->
|
||||||
deleteItem(context, item)
|
deleteItem(context, item)
|
||||||
item.id
|
|
||||||
}
|
}
|
||||||
.toSet()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isEligible(item: DownloadItem, now: Long): Boolean {
|
private fun isEligible(item: DownloadItem, now: Long): Boolean {
|
||||||
|
|||||||
Reference in New Issue
Block a user