mirror of
https://github.com/mihonapp/mihon.git
synced 2025-11-04 16:18:55 +01:00
Perform download cache renewal async
Don't block on cache renewals, but notify library on updates so that the badges show up when ready. We skip the cache when checking if a chapter is downloaded for the reader assuming that it's a relatively low cost to check for a single chapter. (Probably) fixes #8254 / fixes #7847
This commit is contained in:
@@ -6,12 +6,21 @@ import com.hippo.unifile.UniFile
|
||||
import eu.kanade.domain.download.service.DownloadPreferences
|
||||
import eu.kanade.domain.manga.model.Manga
|
||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||
import eu.kanade.tachiyomi.extension.ExtensionManager
|
||||
import eu.kanade.tachiyomi.source.Source
|
||||
import eu.kanade.tachiyomi.source.SourceManager
|
||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.withTimeout
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.util.concurrent.TimeUnit
|
||||
@@ -26,9 +35,15 @@ class DownloadCache(
|
||||
private val context: Context,
|
||||
private val provider: DownloadProvider = Injekt.get(),
|
||||
private val sourceManager: SourceManager = Injekt.get(),
|
||||
private val extensionManager: ExtensionManager = Injekt.get(),
|
||||
private val downloadPreferences: DownloadPreferences = Injekt.get(),
|
||||
) {
|
||||
|
||||
// This is just a mechanism of notifying consumers of updates to the cache, the value itself
|
||||
// is meaningless.
|
||||
private val _state: MutableStateFlow<Long> = MutableStateFlow(0L)
|
||||
val changes = _state.asStateFlow()
|
||||
|
||||
private val scope = CoroutineScope(Dispatchers.IO)
|
||||
|
||||
/**
|
||||
@@ -41,6 +56,7 @@ class DownloadCache(
|
||||
* The last time the cache was refreshed.
|
||||
*/
|
||||
private var lastRenew = 0L
|
||||
private var renewalJob: Job? = null
|
||||
|
||||
private var rootDownloadsDir = RootDirectory(getDirectoryFromPreference())
|
||||
|
||||
@@ -134,6 +150,8 @@ class DownloadCache(
|
||||
|
||||
// Save the chapter directory
|
||||
mangaDir.chapterDirs += chapterDirName
|
||||
|
||||
notifyChanges()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -151,6 +169,8 @@ class DownloadCache(
|
||||
mangaDir.chapterDirs -= it
|
||||
}
|
||||
}
|
||||
|
||||
notifyChanges()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -170,6 +190,8 @@ class DownloadCache(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
notifyChanges()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -184,6 +206,8 @@ class DownloadCache(
|
||||
if (mangaDirName in sourceDir.mangaDirs) {
|
||||
sourceDir.mangaDirs -= mangaDirName
|
||||
}
|
||||
|
||||
notifyChanges()
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
@@ -193,6 +217,8 @@ class DownloadCache(
|
||||
sourceDir.delete()
|
||||
rootDownloadsDir.sourceDirs -= source.id
|
||||
}
|
||||
|
||||
notifyChanges()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -206,76 +232,83 @@ class DownloadCache(
|
||||
/**
|
||||
* Renews the downloads cache.
|
||||
*/
|
||||
@Synchronized
|
||||
private fun renewCache() {
|
||||
if (lastRenew + renewInterval >= System.currentTimeMillis()) {
|
||||
// Avoid renewing cache if in the process nor too often
|
||||
if (lastRenew + renewInterval >= System.currentTimeMillis() || renewalJob?.isActive == true) {
|
||||
return
|
||||
}
|
||||
|
||||
val sources = sourceManager.getOnlineSources() + sourceManager.getStubSources()
|
||||
renewalJob = scope.launchIO {
|
||||
var sources = getSources()
|
||||
|
||||
// Ensure we try again later if no sources have been loaded
|
||||
if (sources.isEmpty()) {
|
||||
return
|
||||
}
|
||||
// Try to wait until extensions and sources have loaded
|
||||
withTimeout(30000L) {
|
||||
while (!extensionManager.isInitialized) {
|
||||
delay(2000L)
|
||||
}
|
||||
|
||||
val sourceDirs = rootDownloadsDir.dir.listFiles()
|
||||
.orEmpty()
|
||||
.associate { it.name to SourceDirectory(it) }
|
||||
.mapNotNullKeys { entry ->
|
||||
sources.find {
|
||||
provider.getSourceDirName(it).equals(entry.key, ignoreCase = true)
|
||||
}?.id
|
||||
while (sources.isEmpty()) {
|
||||
delay(2000L)
|
||||
sources = getSources()
|
||||
}
|
||||
}
|
||||
|
||||
rootDownloadsDir.sourceDirs = sourceDirs
|
||||
val sourceDirs = rootDownloadsDir.dir.listFiles().orEmpty()
|
||||
.associate { it.name to SourceDirectory(it) }
|
||||
.mapNotNullKeys { entry ->
|
||||
sources.find {
|
||||
provider.getSourceDirName(it).equals(entry.key, ignoreCase = true)
|
||||
}?.id
|
||||
}
|
||||
|
||||
sourceDirs.values.forEach { sourceDir ->
|
||||
val mangaDirs = sourceDir.dir.listFiles()
|
||||
.orEmpty()
|
||||
.associateNotNullKeys { it.name to MangaDirectory(it) }
|
||||
rootDownloadsDir.sourceDirs = sourceDirs
|
||||
|
||||
sourceDir.mangaDirs = mangaDirs
|
||||
sourceDirs.values
|
||||
.map { sourceDir ->
|
||||
async {
|
||||
val mangaDirs = sourceDir.dir.listFiles().orEmpty()
|
||||
.filterNot { it.name.isNullOrBlank() }
|
||||
.associate { it.name!! to MangaDirectory(it) }
|
||||
.toMutableMap()
|
||||
|
||||
mangaDirs.values.forEach { mangaDir ->
|
||||
val chapterDirs = mangaDir.dir.listFiles()
|
||||
.orEmpty()
|
||||
.mapNotNull { chapterDir ->
|
||||
chapterDir.name
|
||||
?.replace(".cbz", "")
|
||||
?.takeUnless { it.endsWith(Downloader.TMP_DIR_SUFFIX) }
|
||||
sourceDir.mangaDirs = mangaDirs
|
||||
|
||||
mangaDirs.values.forEach { mangaDir ->
|
||||
val chapterDirs = mangaDir.dir.listFiles().orEmpty()
|
||||
.mapNotNull { chapterDir ->
|
||||
chapterDir.name
|
||||
?.replace(".cbz", "")
|
||||
?.takeUnless { it.endsWith(Downloader.TMP_DIR_SUFFIX) }
|
||||
}
|
||||
.toMutableSet()
|
||||
|
||||
mangaDir.chapterDirs = chapterDirs
|
||||
}
|
||||
}
|
||||
.toHashSet()
|
||||
}
|
||||
.awaitAll()
|
||||
|
||||
mangaDir.chapterDirs = chapterDirs
|
||||
}
|
||||
lastRenew = System.currentTimeMillis()
|
||||
notifyChanges()
|
||||
}
|
||||
}
|
||||
|
||||
lastRenew = System.currentTimeMillis()
|
||||
private fun getSources(): List<Source> {
|
||||
return sourceManager.getOnlineSources() + sourceManager.getStubSources()
|
||||
}
|
||||
|
||||
private fun notifyChanges() {
|
||||
_state.value += 1
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new map containing only the key entries of [transform] that are not null.
|
||||
*/
|
||||
private inline fun <K, V, R> Map<out K, V>.mapNotNullKeys(transform: (Map.Entry<K?, V>) -> R?): Map<R, V> {
|
||||
private inline fun <K, V, R> Map<out K, V>.mapNotNullKeys(transform: (Map.Entry<K?, V>) -> R?): MutableMap<R, V> {
|
||||
val destination = LinkedHashMap<R, V>()
|
||||
forEach { element -> transform(element)?.let { destination[it] = element.value } }
|
||||
return destination
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a map from a list containing only the key entries of [transform] that are not null.
|
||||
*/
|
||||
private inline fun <T, K, V> Array<T>.associateNotNullKeys(transform: (T) -> Pair<K?, V>): Map<K, V> {
|
||||
val destination = LinkedHashMap<K, V>()
|
||||
for (element in this) {
|
||||
val (key, value) = transform(element)
|
||||
if (key != null) {
|
||||
destination[key] = value
|
||||
}
|
||||
}
|
||||
return destination
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -283,7 +316,7 @@ class DownloadCache(
|
||||
*/
|
||||
private class RootDirectory(
|
||||
val dir: UniFile,
|
||||
var sourceDirs: Map<Long, SourceDirectory> = hashMapOf(),
|
||||
var sourceDirs: MutableMap<Long, SourceDirectory> = mutableMapOf(),
|
||||
)
|
||||
|
||||
/**
|
||||
@@ -291,7 +324,7 @@ private class RootDirectory(
|
||||
*/
|
||||
private class SourceDirectory(
|
||||
val dir: UniFile,
|
||||
var mangaDirs: Map<String, MangaDirectory> = hashMapOf(),
|
||||
var mangaDirs: MutableMap<String, MangaDirectory> = mutableMapOf(),
|
||||
)
|
||||
|
||||
/**
|
||||
@@ -299,5 +332,5 @@ private class SourceDirectory(
|
||||
*/
|
||||
private class MangaDirectory(
|
||||
val dir: UniFile,
|
||||
var chapterDirs: Set<String> = hashSetOf(),
|
||||
var chapterDirs: MutableSet<String> = mutableSetOf(),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user