mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-11-03 23:58:55 +01: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:
		@@ -14,7 +14,7 @@ import java.util.concurrent.TimeUnit
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 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
 | 
			
		||||
 * delete the folders at any time without the app noticing.
 | 
			
		||||
 *
 | 
			
		||||
@@ -41,27 +41,16 @@ class DownloadCache(
 | 
			
		||||
     */
 | 
			
		||||
    private var lastRenew = 0L
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The root directory for downloads.
 | 
			
		||||
     */
 | 
			
		||||
    private var rootDir = RootDirectory(getDirectoryFromPreference())
 | 
			
		||||
    private var rootDownloadsDir = RootDirectory(getDirectoryFromPreference())
 | 
			
		||||
 | 
			
		||||
    init {
 | 
			
		||||
        downloadPreferences.downloadsDirectory().changes()
 | 
			
		||||
            .onEach {
 | 
			
		||||
                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.
 | 
			
		||||
     *
 | 
			
		||||
@@ -83,9 +72,9 @@ class DownloadCache(
 | 
			
		||||
            return provider.findChapterDir(chapterName, chapterScanlator, mangaTitle, source) != null
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        checkRenew()
 | 
			
		||||
        renewCache()
 | 
			
		||||
 | 
			
		||||
        val sourceDir = rootDir.files[sourceId]
 | 
			
		||||
        val sourceDir = rootDownloadsDir.files[sourceId]
 | 
			
		||||
        if (sourceDir != null) {
 | 
			
		||||
            val mangaDir = sourceDir.files[provider.getMangaDirName(mangaTitle)]
 | 
			
		||||
            if (mangaDir != null) {
 | 
			
		||||
@@ -101,9 +90,9 @@ class DownloadCache(
 | 
			
		||||
     * @param manga the manga to check.
 | 
			
		||||
     */
 | 
			
		||||
    fun getDownloadCount(manga: Manga): Int {
 | 
			
		||||
        checkRenew()
 | 
			
		||||
        renewCache()
 | 
			
		||||
 | 
			
		||||
        val sourceDir = rootDir.files[manga.source]
 | 
			
		||||
        val sourceDir = rootDownloadsDir.files[manga.source]
 | 
			
		||||
        if (sourceDir != null) {
 | 
			
		||||
            val mangaDir = sourceDir.files[provider.getMangaDirName(manga.title)]
 | 
			
		||||
            if (mangaDir != null) {
 | 
			
		||||
@@ -113,54 +102,6 @@ class DownloadCache(
 | 
			
		||||
        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.
 | 
			
		||||
     *
 | 
			
		||||
@@ -171,12 +112,12 @@ class DownloadCache(
 | 
			
		||||
    @Synchronized
 | 
			
		||||
    fun addChapter(chapterDirName: String, mangaUniFile: UniFile, manga: Manga) {
 | 
			
		||||
        // 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) {
 | 
			
		||||
            val source = sourceManager.get(manga.source) ?: return
 | 
			
		||||
            val sourceUniFile = provider.findSourceDir(source) ?: return
 | 
			
		||||
            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
 | 
			
		||||
@@ -199,7 +140,7 @@ class DownloadCache(
 | 
			
		||||
     */
 | 
			
		||||
    @Synchronized
 | 
			
		||||
    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
 | 
			
		||||
        provider.getValidChapterDirNames(chapter.name, chapter.scanlator).forEach {
 | 
			
		||||
            if (it in mangaDir.files) {
 | 
			
		||||
@@ -216,7 +157,7 @@ class DownloadCache(
 | 
			
		||||
     */
 | 
			
		||||
    @Synchronized
 | 
			
		||||
    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
 | 
			
		||||
        chapters.forEach { chapter ->
 | 
			
		||||
            provider.getValidChapterDirNames(chapter.name, chapter.scanlator).forEach {
 | 
			
		||||
@@ -234,7 +175,7 @@ class DownloadCache(
 | 
			
		||||
     */
 | 
			
		||||
    @Synchronized
 | 
			
		||||
    fun removeManga(manga: Manga) {
 | 
			
		||||
        val sourceDir = rootDir.files[manga.source] ?: return
 | 
			
		||||
        val sourceDir = rootDownloadsDir.files[manga.source] ?: return
 | 
			
		||||
        val mangaDirName = provider.getMangaDirName(manga.title)
 | 
			
		||||
        if (mangaDirName in sourceDir.files) {
 | 
			
		||||
            sourceDir.files -= mangaDirName
 | 
			
		||||
@@ -242,28 +183,61 @@ class DownloadCache(
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Class to store the files under the root downloads directory.
 | 
			
		||||
     * Returns the downloads directory from the user's preferences.
 | 
			
		||||
     */
 | 
			
		||||
    private class RootDirectory(
 | 
			
		||||
        val dir: UniFile,
 | 
			
		||||
        var files: Map<Long, SourceDirectory> = hashMapOf(),
 | 
			
		||||
    )
 | 
			
		||||
    private fun getDirectoryFromPreference(): UniFile {
 | 
			
		||||
        val dir = downloadPreferences.downloadsDirectory().get()
 | 
			
		||||
        return UniFile.fromUri(context, dir.toUri())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Class to store the files under a source directory.
 | 
			
		||||
     * Renews the downloads cache.
 | 
			
		||||
     */
 | 
			
		||||
    private class SourceDirectory(
 | 
			
		||||
        val dir: UniFile,
 | 
			
		||||
        var files: Map<String, MangaDirectory> = hashMapOf(),
 | 
			
		||||
    )
 | 
			
		||||
    @Synchronized
 | 
			
		||||
    private fun renewCache() {
 | 
			
		||||
        if (lastRenew + renewInterval >= System.currentTimeMillis()) {
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Class to store the files under a manga directory.
 | 
			
		||||
     */
 | 
			
		||||
    private class MangaDirectory(
 | 
			
		||||
        val dir: UniFile,
 | 
			
		||||
        var files: Set<String> = hashSetOf(),
 | 
			
		||||
    )
 | 
			
		||||
        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.
 | 
			
		||||
@@ -288,3 +262,27 @@ class DownloadCache(
 | 
			
		||||
        return destination
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Class to store the files under the root downloads directory.
 | 
			
		||||
 */
 | 
			
		||||
private class RootDirectory(
 | 
			
		||||
    val dir: UniFile,
 | 
			
		||||
    var files: Map<Long, SourceDirectory> = hashMapOf(),
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Class to store the files under a source directory.
 | 
			
		||||
 */
 | 
			
		||||
private class SourceDirectory(
 | 
			
		||||
    val dir: UniFile,
 | 
			
		||||
    var files: Map<String, MangaDirectory> = hashMapOf(),
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Class to store the files under a manga directory.
 | 
			
		||||
 */
 | 
			
		||||
private class MangaDirectory(
 | 
			
		||||
    val dir: UniFile,
 | 
			
		||||
    var files: Set<String> = hashSetOf(),
 | 
			
		||||
)
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user