Defer library download counts if not needed

This commit is contained in:
arkon 2022-10-20 23:20:32 -04:00
parent 3318314c4a
commit 93827aba34
3 changed files with 65 additions and 47 deletions

View file

@ -6,8 +6,10 @@ import com.hippo.unifile.UniFile
import eu.kanade.domain.download.service.DownloadPreferences import eu.kanade.domain.download.service.DownloadPreferences
import eu.kanade.domain.manga.model.Manga import eu.kanade.domain.manga.model.Manga
import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import kotlinx.coroutines.MainScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
@ -32,7 +34,7 @@ class DownloadCache(
private val downloadPreferences: DownloadPreferences = Injekt.get(), private val downloadPreferences: DownloadPreferences = Injekt.get(),
) { ) {
private val scope = MainScope() private val scope = CoroutineScope(Dispatchers.IO)
/** /**
* The interval after which this cache should be invalidated. 1 hour shouldn't cause major * The interval after which this cache should be invalidated. 1 hour shouldn't cause major
@ -50,8 +52,10 @@ class DownloadCache(
init { init {
downloadPreferences.downloadsDirectory().changes() downloadPreferences.downloadsDirectory().changes()
.onEach { .onEach {
lastRenew = 0L // invalidate cache
rootDownloadsDir = RootDirectory(getDirectoryFromPreference()) rootDownloadsDir = RootDirectory(getDirectoryFromPreference())
// Invalidate cache
lastRenew = 0L
} }
.launchIn(scope) .launchIn(scope)
} }
@ -79,11 +83,11 @@ class DownloadCache(
renewCache() renewCache()
val sourceDir = rootDownloadsDir.files[sourceId] val sourceDir = rootDownloadsDir.sourceDirs[sourceId]
if (sourceDir != null) { if (sourceDir != null) {
val mangaDir = sourceDir.files[provider.getMangaDirName(mangaTitle)] val mangaDir = sourceDir.mangaDirs[provider.getMangaDirName(mangaTitle)]
if (mangaDir != null) { if (mangaDir != null) {
return provider.getValidChapterDirNames(chapterName, chapterScanlator).any { it in mangaDir.files } return provider.getValidChapterDirNames(chapterName, chapterScanlator).any { it in mangaDir.chapterDirs }
} }
} }
return false return false
@ -97,11 +101,11 @@ class DownloadCache(
fun getDownloadCount(manga: Manga): Int { fun getDownloadCount(manga: Manga): Int {
renewCache() renewCache()
val sourceDir = rootDownloadsDir.files[manga.source] val sourceDir = rootDownloadsDir.sourceDirs[manga.source]
if (sourceDir != null) { if (sourceDir != null) {
val mangaDir = sourceDir.files[provider.getMangaDirName(manga.title)] val mangaDir = sourceDir.mangaDirs[provider.getMangaDirName(manga.title)]
if (mangaDir != null) { if (mangaDir != null) {
return mangaDir.files.size return mangaDir.chapterDirs.size
} }
} }
return 0 return 0
@ -117,24 +121,24 @@ 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 = rootDownloadsDir.files[manga.source] var sourceDir = rootDownloadsDir.sourceDirs[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)
rootDownloadsDir.files += manga.source to sourceDir rootDownloadsDir.sourceDirs += manga.source to sourceDir
} }
// Retrieve the cached manga directory or cache a new one // Retrieve the cached manga directory or cache a new one
val mangaDirName = provider.getMangaDirName(manga.title) val mangaDirName = provider.getMangaDirName(manga.title)
var mangaDir = sourceDir.files[mangaDirName] var mangaDir = sourceDir.mangaDirs[mangaDirName]
if (mangaDir == null) { if (mangaDir == null) {
mangaDir = MangaDirectory(mangaUniFile) mangaDir = MangaDirectory(mangaUniFile)
sourceDir.files += mangaDirName to mangaDir sourceDir.mangaDirs += mangaDirName to mangaDir
} }
// Save the chapter directory // Save the chapter directory
mangaDir.files += chapterDirName mangaDir.chapterDirs += chapterDirName
} }
/** /**
@ -145,11 +149,11 @@ class DownloadCache(
*/ */
@Synchronized @Synchronized
fun removeChapter(chapter: Chapter, manga: Manga) { fun removeChapter(chapter: Chapter, manga: Manga) {
val sourceDir = rootDownloadsDir.files[manga.source] ?: return val sourceDir = rootDownloadsDir.sourceDirs[manga.source] ?: return
val mangaDir = sourceDir.files[provider.getMangaDirName(manga.title)] ?: return val mangaDir = sourceDir.mangaDirs[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.chapterDirs) {
mangaDir.files -= it mangaDir.chapterDirs -= it
} }
} }
} }
@ -162,12 +166,12 @@ class DownloadCache(
*/ */
@Synchronized @Synchronized
fun removeChapters(chapters: List<Chapter>, manga: Manga) { fun removeChapters(chapters: List<Chapter>, manga: Manga) {
val sourceDir = rootDownloadsDir.files[manga.source] ?: return val sourceDir = rootDownloadsDir.sourceDirs[manga.source] ?: return
val mangaDir = sourceDir.files[provider.getMangaDirName(manga.title)] ?: return val mangaDir = sourceDir.mangaDirs[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 {
if (it in mangaDir.files) { if (it in mangaDir.chapterDirs) {
mangaDir.files -= it mangaDir.chapterDirs -= it
} }
} }
} }
@ -180,10 +184,19 @@ class DownloadCache(
*/ */
@Synchronized @Synchronized
fun removeManga(manga: Manga) { fun removeManga(manga: Manga) {
val sourceDir = rootDownloadsDir.files[manga.source] ?: return val sourceDir = rootDownloadsDir.sourceDirs[manga.source] ?: return
val mangaDirName = provider.getMangaDirName(manga.title) val mangaDirName = provider.getMangaDirName(manga.title)
if (mangaDirName in sourceDir.files) { if (mangaDirName in sourceDir.mangaDirs) {
sourceDir.files -= mangaDirName sourceDir.mangaDirs -= mangaDirName
}
}
@Synchronized
fun removeSourceIfEmpty(source: Source) {
val sourceDir = provider.findSourceDir(source)
if (sourceDir?.listFiles()?.isEmpty() == true) {
sourceDir.delete()
rootDownloadsDir.sourceDirs -= source.id
} }
} }
@ -220,14 +233,14 @@ class DownloadCache(
}?.id }?.id
} }
rootDownloadsDir.files = sourceDirs rootDownloadsDir.sourceDirs = sourceDirs
sourceDirs.values.forEach { sourceDir -> sourceDirs.values.forEach { sourceDir ->
val mangaDirs = sourceDir.dir.listFiles() val mangaDirs = sourceDir.dir.listFiles()
.orEmpty() .orEmpty()
.associateNotNullKeys { it.name to MangaDirectory(it) } .associateNotNullKeys { it.name to MangaDirectory(it) }
sourceDir.files = mangaDirs sourceDir.mangaDirs = mangaDirs
mangaDirs.values.forEach { mangaDir -> mangaDirs.values.forEach { mangaDir ->
val chapterDirs = mangaDir.dir.listFiles() val chapterDirs = mangaDir.dir.listFiles()
@ -239,7 +252,7 @@ class DownloadCache(
} }
.toHashSet() .toHashSet()
mangaDir.files = chapterDirs mangaDir.chapterDirs = chapterDirs
} }
} }
@ -275,7 +288,7 @@ class DownloadCache(
*/ */
private class RootDirectory( private class RootDirectory(
val dir: UniFile, val dir: UniFile,
var files: Map<Long, SourceDirectory> = hashMapOf(), var sourceDirs: Map<Long, SourceDirectory> = hashMapOf(),
) )
/** /**
@ -283,7 +296,7 @@ private class RootDirectory(
*/ */
private class SourceDirectory( private class SourceDirectory(
val dir: UniFile, val dir: UniFile,
var files: Map<String, MangaDirectory> = hashMapOf(), var mangaDirs: Map<String, MangaDirectory> = hashMapOf(),
) )
/** /**
@ -291,5 +304,5 @@ private class SourceDirectory(
*/ */
private class MangaDirectory( private class MangaDirectory(
val dir: UniFile, val dir: UniFile,
var files: Set<String> = hashSetOf(), var chapterDirs: Set<String> = hashSetOf(),
) )

View file

@ -287,10 +287,7 @@ class DownloadManager(
} }
// Delete source directory if empty // Delete source directory if empty
val sourceDir = provider.findSourceDir(source) cache.removeSourceIfEmpty(source)
if (sourceDir?.listFiles()?.isEmpty() == true) {
sourceDir.delete()
}
} }
return filteredChapters return filteredChapters

View file

@ -338,27 +338,35 @@ class LibraryPresenter(
* @return an observable of the categories and its manga. * @return an observable of the categories and its manga.
*/ */
private fun getLibraryFlow(): Flow<Library> { private fun getLibraryFlow(): Flow<Library> {
val categoriesFlow = getCategories.subscribe() val libraryMangasFlow = combine(
val libraryMangasFlow = getLibraryManga.subscribe() getLibraryManga.subscribe(),
.map { list -> libraryPreferences.downloadBadge().changes(),
list.map { libraryManga -> ) { libraryMangaList, downloadBadgePref ->
libraryMangaList
.map { libraryManga ->
// Display mode based on user preference: take it from global library setting or category // Display mode based on user preference: take it from global library setting or category
LibraryItem(libraryManga).apply { LibraryItem(libraryManga).apply {
downloadCount = downloadManager.getDownloadCount(libraryManga.manga).toLong() downloadCount = if (downloadBadgePref) {
downloadManager.getDownloadCount(libraryManga.manga).toLong()
} else {
0
}
unreadCount = libraryManga.unreadCount unreadCount = libraryManga.unreadCount
isLocal = libraryManga.manga.isLocal() isLocal = libraryManga.manga.isLocal()
sourceLanguage = sourceManager.getOrStub(libraryManga.manga.source).lang sourceLanguage = sourceManager.getOrStub(libraryManga.manga.source).lang
} }
}.groupBy { it.libraryManga.category } }
} .groupBy { it.libraryManga.category }
return combine(categoriesFlow, libraryMangasFlow) { dbCategories, libraryManga -> }
val categories = if (libraryManga.isNotEmpty() && libraryManga.containsKey(0).not()) {
dbCategories.filterNot { it.isSystemCategory } return combine(getCategories.subscribe(), libraryMangasFlow) { categories, libraryManga ->
val displayCategories = if (libraryManga.isNotEmpty() && libraryManga.containsKey(0).not()) {
categories.filterNot { it.isSystemCategory }
} else { } else {
dbCategories categories
} }
state.categories = categories state.categories = displayCategories
Library(categories, libraryManga) Library(categories, libraryManga)
} }
} }