mirror of
https://github.com/mihonapp/mihon.git
synced 2024-11-21 20:47:03 -05:00
Always renew download cache if no sources have been loaded yet
Fixes #7705. Somewhat janky solution to handle when loading the sources on app start is slower than the initial download cache priming.
This commit is contained in:
parent
d6cbff2837
commit
558aad1a71
1 changed files with 86 additions and 88 deletions
|
@ -14,7 +14,7 @@ import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cache where we dump the downloads directory from the filesystem. This class is needed because
|
* Cache where we dump the downloads directory from the filesystem. This class is needed because
|
||||||
* directory checking is expensive and it slowdowns the app. The cache is invalidated by the time
|
* directory checking is expensive and it slows down the app. The cache is invalidated by the time
|
||||||
* defined in [renewInterval] as we don't have any control over the filesystem and the user can
|
* defined in [renewInterval] as we don't have any control over the filesystem and the user can
|
||||||
* delete the folders at any time without the app noticing.
|
* delete the folders at any time without the app noticing.
|
||||||
*
|
*
|
||||||
|
@ -41,27 +41,16 @@ class DownloadCache(
|
||||||
*/
|
*/
|
||||||
private var lastRenew = 0L
|
private var lastRenew = 0L
|
||||||
|
|
||||||
/**
|
private var rootDownloadsDir = RootDirectory(getDirectoryFromPreference())
|
||||||
* The root directory for downloads.
|
|
||||||
*/
|
|
||||||
private var rootDir = RootDirectory(getDirectoryFromPreference())
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
downloadPreferences.downloadsDirectory().changes()
|
downloadPreferences.downloadsDirectory().changes()
|
||||||
.onEach {
|
.onEach {
|
||||||
lastRenew = 0L // invalidate cache
|
lastRenew = 0L // invalidate cache
|
||||||
rootDir = RootDirectory(getDirectoryFromPreference())
|
rootDownloadsDir = RootDirectory(getDirectoryFromPreference())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the downloads directory from the user's preferences.
|
|
||||||
*/
|
|
||||||
private fun getDirectoryFromPreference(): UniFile {
|
|
||||||
val dir = downloadPreferences.downloadsDirectory().get()
|
|
||||||
return UniFile.fromUri(context, dir.toUri())
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the chapter is downloaded.
|
* Returns true if the chapter is downloaded.
|
||||||
*
|
*
|
||||||
|
@ -83,9 +72,9 @@ class DownloadCache(
|
||||||
return provider.findChapterDir(chapterName, chapterScanlator, mangaTitle, source) != null
|
return provider.findChapterDir(chapterName, chapterScanlator, mangaTitle, source) != null
|
||||||
}
|
}
|
||||||
|
|
||||||
checkRenew()
|
renewCache()
|
||||||
|
|
||||||
val sourceDir = rootDir.files[sourceId]
|
val sourceDir = rootDownloadsDir.files[sourceId]
|
||||||
if (sourceDir != null) {
|
if (sourceDir != null) {
|
||||||
val mangaDir = sourceDir.files[provider.getMangaDirName(mangaTitle)]
|
val mangaDir = sourceDir.files[provider.getMangaDirName(mangaTitle)]
|
||||||
if (mangaDir != null) {
|
if (mangaDir != null) {
|
||||||
|
@ -101,9 +90,9 @@ class DownloadCache(
|
||||||
* @param manga the manga to check.
|
* @param manga the manga to check.
|
||||||
*/
|
*/
|
||||||
fun getDownloadCount(manga: Manga): Int {
|
fun getDownloadCount(manga: Manga): Int {
|
||||||
checkRenew()
|
renewCache()
|
||||||
|
|
||||||
val sourceDir = rootDir.files[manga.source]
|
val sourceDir = rootDownloadsDir.files[manga.source]
|
||||||
if (sourceDir != null) {
|
if (sourceDir != null) {
|
||||||
val mangaDir = sourceDir.files[provider.getMangaDirName(manga.title)]
|
val mangaDir = sourceDir.files[provider.getMangaDirName(manga.title)]
|
||||||
if (mangaDir != null) {
|
if (mangaDir != null) {
|
||||||
|
@ -113,54 +102,6 @@ class DownloadCache(
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the cache needs a renewal and performs it if needed.
|
|
||||||
*/
|
|
||||||
@Synchronized
|
|
||||||
private fun checkRenew() {
|
|
||||||
if (lastRenew + renewInterval < System.currentTimeMillis()) {
|
|
||||||
renew()
|
|
||||||
lastRenew = System.currentTimeMillis()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Renews the downloads cache.
|
|
||||||
*/
|
|
||||||
private fun renew() {
|
|
||||||
val sources = sourceManager.getOnlineSources() + sourceManager.getStubSources()
|
|
||||||
|
|
||||||
val sourceDirs = rootDir.dir.listFiles()
|
|
||||||
.orEmpty()
|
|
||||||
.associate { it.name to SourceDirectory(it) }
|
|
||||||
.mapNotNullKeys { entry ->
|
|
||||||
sources.find { provider.getSourceDirName(it).equals(entry.key, ignoreCase = true) }?.id
|
|
||||||
}
|
|
||||||
|
|
||||||
rootDir.files = sourceDirs
|
|
||||||
|
|
||||||
sourceDirs.values.forEach { sourceDir ->
|
|
||||||
val mangaDirs = sourceDir.dir.listFiles()
|
|
||||||
.orEmpty()
|
|
||||||
.associateNotNullKeys { it.name to MangaDirectory(it) }
|
|
||||||
|
|
||||||
sourceDir.files = mangaDirs
|
|
||||||
|
|
||||||
mangaDirs.values.forEach { mangaDir ->
|
|
||||||
val chapterDirs = mangaDir.dir.listFiles()
|
|
||||||
.orEmpty()
|
|
||||||
.mapNotNull { chapterDir ->
|
|
||||||
chapterDir.name
|
|
||||||
?.replace(".cbz", "")
|
|
||||||
?.takeUnless { it.endsWith(Downloader.TMP_DIR_SUFFIX) }
|
|
||||||
}
|
|
||||||
.toHashSet()
|
|
||||||
|
|
||||||
mangaDir.files = chapterDirs
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a chapter that has just been download to this cache.
|
* Adds a chapter that has just been download to this cache.
|
||||||
*
|
*
|
||||||
|
@ -171,12 +112,12 @@ class DownloadCache(
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun addChapter(chapterDirName: String, mangaUniFile: UniFile, manga: Manga) {
|
fun addChapter(chapterDirName: String, mangaUniFile: UniFile, manga: Manga) {
|
||||||
// Retrieve the cached source directory or cache a new one
|
// Retrieve the cached source directory or cache a new one
|
||||||
var sourceDir = rootDir.files[manga.source]
|
var sourceDir = rootDownloadsDir.files[manga.source]
|
||||||
if (sourceDir == null) {
|
if (sourceDir == null) {
|
||||||
val source = sourceManager.get(manga.source) ?: return
|
val source = sourceManager.get(manga.source) ?: return
|
||||||
val sourceUniFile = provider.findSourceDir(source) ?: return
|
val sourceUniFile = provider.findSourceDir(source) ?: return
|
||||||
sourceDir = SourceDirectory(sourceUniFile)
|
sourceDir = SourceDirectory(sourceUniFile)
|
||||||
rootDir.files += manga.source to sourceDir
|
rootDownloadsDir.files += manga.source to sourceDir
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retrieve the cached manga directory or cache a new one
|
// Retrieve the cached manga directory or cache a new one
|
||||||
|
@ -199,7 +140,7 @@ class DownloadCache(
|
||||||
*/
|
*/
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun removeChapter(chapter: Chapter, manga: Manga) {
|
fun removeChapter(chapter: Chapter, manga: Manga) {
|
||||||
val sourceDir = rootDir.files[manga.source] ?: return
|
val sourceDir = rootDownloadsDir.files[manga.source] ?: return
|
||||||
val mangaDir = sourceDir.files[provider.getMangaDirName(manga.title)] ?: return
|
val mangaDir = sourceDir.files[provider.getMangaDirName(manga.title)] ?: return
|
||||||
provider.getValidChapterDirNames(chapter.name, chapter.scanlator).forEach {
|
provider.getValidChapterDirNames(chapter.name, chapter.scanlator).forEach {
|
||||||
if (it in mangaDir.files) {
|
if (it in mangaDir.files) {
|
||||||
|
@ -216,7 +157,7 @@ class DownloadCache(
|
||||||
*/
|
*/
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun removeChapters(chapters: List<Chapter>, manga: Manga) {
|
fun removeChapters(chapters: List<Chapter>, manga: Manga) {
|
||||||
val sourceDir = rootDir.files[manga.source] ?: return
|
val sourceDir = rootDownloadsDir.files[manga.source] ?: return
|
||||||
val mangaDir = sourceDir.files[provider.getMangaDirName(manga.title)] ?: return
|
val mangaDir = sourceDir.files[provider.getMangaDirName(manga.title)] ?: return
|
||||||
chapters.forEach { chapter ->
|
chapters.forEach { chapter ->
|
||||||
provider.getValidChapterDirNames(chapter.name, chapter.scanlator).forEach {
|
provider.getValidChapterDirNames(chapter.name, chapter.scanlator).forEach {
|
||||||
|
@ -234,13 +175,94 @@ class DownloadCache(
|
||||||
*/
|
*/
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun removeManga(manga: Manga) {
|
fun removeManga(manga: Manga) {
|
||||||
val sourceDir = rootDir.files[manga.source] ?: return
|
val sourceDir = rootDownloadsDir.files[manga.source] ?: return
|
||||||
val mangaDirName = provider.getMangaDirName(manga.title)
|
val mangaDirName = provider.getMangaDirName(manga.title)
|
||||||
if (mangaDirName in sourceDir.files) {
|
if (mangaDirName in sourceDir.files) {
|
||||||
sourceDir.files -= mangaDirName
|
sourceDir.files -= mangaDirName
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the downloads directory from the user's preferences.
|
||||||
|
*/
|
||||||
|
private fun getDirectoryFromPreference(): UniFile {
|
||||||
|
val dir = downloadPreferences.downloadsDirectory().get()
|
||||||
|
return UniFile.fromUri(context, dir.toUri())
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renews the downloads cache.
|
||||||
|
*/
|
||||||
|
@Synchronized
|
||||||
|
private fun renewCache() {
|
||||||
|
if (lastRenew + renewInterval >= System.currentTimeMillis()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val sources = sourceManager.getOnlineSources() + sourceManager.getStubSources()
|
||||||
|
|
||||||
|
// Ensure we try again later if no sources have been loaded
|
||||||
|
if (sources.isEmpty()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
rootDownloadsDir.files = sourceDirs
|
||||||
|
|
||||||
|
sourceDirs.values.forEach { sourceDir ->
|
||||||
|
val mangaDirs = sourceDir.dir.listFiles()
|
||||||
|
.orEmpty()
|
||||||
|
.associateNotNullKeys { it.name to MangaDirectory(it) }
|
||||||
|
|
||||||
|
sourceDir.files = mangaDirs
|
||||||
|
|
||||||
|
mangaDirs.values.forEach { mangaDir ->
|
||||||
|
val chapterDirs = mangaDir.dir.listFiles()
|
||||||
|
.orEmpty()
|
||||||
|
.mapNotNull { chapterDir ->
|
||||||
|
chapterDir.name
|
||||||
|
?.replace(".cbz", "")
|
||||||
|
?.takeUnless { it.endsWith(Downloader.TMP_DIR_SUFFIX) }
|
||||||
|
}
|
||||||
|
.toHashSet()
|
||||||
|
|
||||||
|
mangaDir.files = chapterDirs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lastRenew = System.currentTimeMillis()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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> {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class to store the files under the root downloads directory.
|
* Class to store the files under the root downloads directory.
|
||||||
*/
|
*/
|
||||||
|
@ -264,27 +286,3 @@ class DownloadCache(
|
||||||
val dir: UniFile,
|
val dir: UniFile,
|
||||||
var files: Set<String> = hashSetOf(),
|
var files: Set<String> = hashSetOf(),
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
|
||||||
* 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> {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in a new issue