Download count shouldn't be stored as a database field

This commit is contained in:
inorichi 2017-10-21 23:43:46 +02:00
parent 60ac27e401
commit f88c86c799
6 changed files with 100 additions and 99 deletions

View File

@ -6,6 +6,4 @@ class LibraryManga : MangaImpl() {
var category: Int = 0 var category: Int = 0
var downloadTotal: Int = 0
} }

View File

@ -3,15 +3,9 @@ package eu.kanade.tachiyomi.ui.library
import android.view.View import android.view.View
import com.bumptech.glide.load.engine.DiskCacheStrategy import com.bumptech.glide.load.engine.DiskCacheStrategy
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.kanade.tachiyomi.data.database.models.LibraryManga
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.glide.GlideApp import eu.kanade.tachiyomi.data.glide.GlideApp
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.getOrDefault
import eu.kanade.tachiyomi.source.LocalSource import eu.kanade.tachiyomi.source.LocalSource
import kotlinx.android.synthetic.main.catalogue_grid_item.view.* import kotlinx.android.synthetic.main.catalogue_grid_item.view.*
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
/** /**
* Class used to hold the displayed data of a manga in the library, like the cover or the title. * Class used to hold the displayed data of a manga in the library, like the cover or the title.
@ -27,37 +21,36 @@ class LibraryGridHolder(
private val adapter: FlexibleAdapter<*> private val adapter: FlexibleAdapter<*>
) : LibraryHolder(view, adapter) { ) : LibraryHolder(view, adapter) {
private val preferences: PreferencesHelper = Injekt.get()
/** /**
* Method called from [LibraryCategoryAdapter.onBindViewHolder]. It updates the data for this * Method called from [LibraryCategoryAdapter.onBindViewHolder]. It updates the data for this
* holder with the given manga. * holder with the given manga.
* *
* @param manga the manga to bind. * @param item the manga item to bind.
*/ */
override fun onSetValues(manga: LibraryManga) { override fun onSetValues(item: LibraryItem) {
// Update the title of the manga. // Update the title of the manga.
view.title.text = manga.title view.title.text = item.manga.title
// Update the unread count and its visibility. // Update the unread count and its visibility.
with(view.unread_text) { with(view.unread_text) {
visibility = if (manga.unread > 0) View.VISIBLE else View.GONE visibility = if (item.manga.unread > 0) View.VISIBLE else View.GONE
text = manga.unread.toString() text = item.manga.unread.toString()
} }
// Update the download count and its visibility. // Update the download count and its visibility.
with(view.download_text) { with(view.download_text) {
visibility = if (manga.downloadTotal > 0 && preferences.downloadBadge().getOrDefault()) View.VISIBLE else View.GONE visibility = if (item.downloadCount > 0) View.VISIBLE else View.GONE
text = manga.downloadTotal.toString() text = item.downloadCount.toString()
} }
//set local visibility if its local manga //set local visibility if its local manga
with(view.local_text){ with(view.local_text) {
visibility = if(manga.source == LocalSource.ID) View.VISIBLE else View.GONE visibility = if(item.manga.source == LocalSource.ID) View.VISIBLE else View.GONE
} }
// Update the cover. // Update the cover.
GlideApp.with(view.context).clear(view.thumbnail) GlideApp.with(view.context).clear(view.thumbnail)
GlideApp.with(view.context) GlideApp.with(view.context)
.load(manga) .load(item.manga)
.diskCacheStrategy(DiskCacheStrategy.RESOURCE) .diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.centerCrop() .centerCrop()
.into(view.thumbnail) .into(view.thumbnail)

View File

@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.ui.library
import android.view.View import android.view.View
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.viewholders.FlexibleViewHolder import eu.davidea.viewholders.FlexibleViewHolder
import eu.kanade.tachiyomi.data.database.models.LibraryManga
/** /**
* Generic class used to hold the displayed data of a manga in the library. * Generic class used to hold the displayed data of a manga in the library.
@ -21,8 +20,8 @@ abstract class LibraryHolder(
* Method called from [LibraryCategoryAdapter.onBindViewHolder]. It updates the data for this * Method called from [LibraryCategoryAdapter.onBindViewHolder]. It updates the data for this
* holder with the given manga. * holder with the given manga.
* *
* @param manga the manga to bind. * @param item the manga item to bind.
*/ */
abstract fun onSetValues(manga: LibraryManga) abstract fun onSetValues(item: LibraryItem)
} }

View File

@ -16,6 +16,8 @@ import kotlinx.android.synthetic.main.catalogue_grid_item.view.*
class LibraryItem(val manga: LibraryManga) : AbstractFlexibleItem<LibraryHolder>(), IFilterable { class LibraryItem(val manga: LibraryManga) : AbstractFlexibleItem<LibraryHolder>(), IFilterable {
var downloadCount = -1
override fun getLayoutRes(): Int { override fun getLayoutRes(): Int {
return R.layout.catalogue_grid_item return R.layout.catalogue_grid_item
} }
@ -43,7 +45,7 @@ class LibraryItem(val manga: LibraryManga) : AbstractFlexibleItem<LibraryHolder>
position: Int, position: Int,
payloads: List<Any?>?) { payloads: List<Any?>?) {
holder.onSetValues(manga) holder.onSetValues(this)
} }
/** /**

View File

@ -3,14 +3,9 @@ package eu.kanade.tachiyomi.ui.library
import android.view.View import android.view.View
import com.bumptech.glide.load.engine.DiskCacheStrategy import com.bumptech.glide.load.engine.DiskCacheStrategy
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.kanade.tachiyomi.data.database.models.LibraryManga
import eu.kanade.tachiyomi.data.glide.GlideApp import eu.kanade.tachiyomi.data.glide.GlideApp
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.getOrDefault
import eu.kanade.tachiyomi.source.LocalSource import eu.kanade.tachiyomi.source.LocalSource
import kotlinx.android.synthetic.main.catalogue_list_item.view.* import kotlinx.android.synthetic.main.catalogue_list_item.view.*
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
/** /**
* Class used to hold the displayed data of a manga in the library, like the cover or the title. * Class used to hold the displayed data of a manga in the library, like the cover or the title.
@ -26,31 +21,30 @@ class LibraryListHolder(
private val view: View, private val view: View,
private val adapter: FlexibleAdapter<*> private val adapter: FlexibleAdapter<*>
) : LibraryHolder(view, adapter) { ) : LibraryHolder(view, adapter) {
private val preferences: PreferencesHelper = Injekt.get()
/** /**
* Method called from [LibraryCategoryAdapter.onBindViewHolder]. It updates the data for this * Method called from [LibraryCategoryAdapter.onBindViewHolder]. It updates the data for this
* holder with the given manga. * holder with the given manga.
* *
* @param manga the manga to bind. * @param item the manga item to bind.
*/ */
override fun onSetValues(manga: LibraryManga) { override fun onSetValues(item: LibraryItem) {
// Update the title of the manga. // Update the title of the manga.
itemView.title.text = manga.title itemView.title.text = item.manga.title
// Update the unread count and its visibility. // Update the unread count and its visibility.
with(itemView.unread_text) { with(itemView.unread_text) {
visibility = if (manga.unread > 0) View.VISIBLE else View.GONE visibility = if (item.manga.unread > 0) View.VISIBLE else View.GONE
text = manga.unread.toString() text = item.manga.unread.toString()
} }
// Update the download count and its visibility. // Update the download count and its visibility.
with(itemView.download_text) { with(itemView.download_text) {
visibility = if (manga.downloadTotal > 0 && preferences.downloadBadge().getOrDefault()) View.VISIBLE else View.GONE visibility = if (item.downloadCount > 0) View.VISIBLE else View.GONE
text = manga.downloadTotal.toString() text = "${item.downloadCount}"
} }
//show local text badge if local manga //show local text badge if local manga
with(itemView.local_text) { with(itemView.local_text) {
visibility = if (manga.source == LocalSource.ID) View.VISIBLE else View.GONE visibility = if (item.manga.source == LocalSource.ID) View.VISIBLE else View.GONE
} }
// Create thumbnail onclick to simulate long click // Create thumbnail onclick to simulate long click
@ -62,7 +56,7 @@ class LibraryListHolder(
// Update the cover. // Update the cover.
GlideApp.with(itemView.context).clear(itemView.thumbnail) GlideApp.with(itemView.context).clear(itemView.thumbnail)
GlideApp.with(itemView.context) GlideApp.with(itemView.context)
.load(manga) .load(item.manga)
.diskCacheStrategy(DiskCacheStrategy.RESOURCE) .diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.centerCrop() .centerCrop()
.circleCrop() .circleCrop()

View File

@ -1,13 +1,11 @@
package eu.kanade.tachiyomi.ui.library package eu.kanade.tachiyomi.ui.library
import android.os.Bundle import android.os.Bundle
import android.util.Pair
import com.hippo.unifile.UniFile import com.hippo.unifile.UniFile
import com.jakewharton.rxrelay.BehaviorRelay import com.jakewharton.rxrelay.BehaviorRelay
import eu.kanade.tachiyomi.data.cache.CoverCache import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Category import eu.kanade.tachiyomi.data.database.models.Category
import eu.kanade.tachiyomi.data.database.models.LibraryManga
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.MangaCategory import eu.kanade.tachiyomi.data.database.models.MangaCategory
import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.download.DownloadManager
@ -30,6 +28,16 @@ import java.io.IOException
import java.io.InputStream import java.io.InputStream
import java.util.* import java.util.*
/**
* Class containing library information.
*/
private data class Library(val categories: List<Category>, val mangaMap: LibraryMap)
/**
* Typealias for the library manga, using the category as keys, and list of manga as values.
*/
private typealias LibraryMap = Map<Int, List<LibraryItem>>
/** /**
* Presenter of [LibraryController]. * Presenter of [LibraryController].
*/ */
@ -80,16 +88,15 @@ class LibraryPresenter(
fun subscribeLibrary() { fun subscribeLibrary() {
if (librarySubscription.isNullOrUnsubscribed()) { if (librarySubscription.isNullOrUnsubscribed()) {
librarySubscription = getLibraryObservable() librarySubscription = getLibraryObservable()
.combineLatest(filterTriggerRelay.observeOn(Schedulers.io()),
{ lib, _ -> Pair(lib.first, applyFilters(lib.second)) })
.combineLatest(downloadTriggerRelay.observeOn(Schedulers.io()), .combineLatest(downloadTriggerRelay.observeOn(Schedulers.io()),
{ lib, _ -> Pair(lib.first, addDownloadTotal(lib.second)) }) { lib, _ -> lib.apply { setDownloadCount(mangaMap) } })
.combineLatest(filterTriggerRelay.observeOn(Schedulers.io()),
{ lib, _ -> lib.copy(mangaMap = applyFilters(lib.mangaMap)) })
.combineLatest(sortTriggerRelay.observeOn(Schedulers.io()), .combineLatest(sortTriggerRelay.observeOn(Schedulers.io()),
{ lib, _ -> Pair(lib.first, applySort(lib.second)) }) { lib, _ -> lib.copy(mangaMap = applySort(lib.mangaMap)) })
.map { Pair(it.first, it.second.mapValues { it.value.map(::LibraryItem) }) }
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribeLatestCache({ view, pair -> .subscribeLatestCache({ view, (categories, mangaMap) ->
view.onNextLibraryUpdate(pair.first, pair.second) view.onNextLibraryUpdate(categories, mangaMap)
}) })
} }
} }
@ -99,7 +106,7 @@ class LibraryPresenter(
* *
* @param map the map to filter. * @param map the map to filter.
*/ */
private fun applyFilters(map: Map<Int, List<LibraryManga>>): Map<Int, List<LibraryManga>> { private fun applyFilters(map: LibraryMap): LibraryMap {
// Cached list of downloaded manga directories given a source id. // Cached list of downloaded manga directories given a source id.
val mangaDirsForSource = mutableMapOf<Long, Map<String?, UniFile>>() val mangaDirsForSource = mutableMapOf<Long, Map<String?, UniFile>>()
@ -112,31 +119,36 @@ class LibraryPresenter(
val filterCompleted = preferences.filterCompleted().getOrDefault() val filterCompleted = preferences.filterCompleted().getOrDefault()
val filterFn: (LibraryManga) -> Boolean = f@ { manga -> val filterFn: (LibraryItem) -> Boolean = f@ { item ->
// Filter out manga without source. // Filter out manga without source.
val source = sourceManager.get(manga.source) ?: return@f false val source = sourceManager.get(item.manga.source) ?: return@f false
// Filter when there isn't unread chapters. // Filter when there isn't unread chapters.
if (filterUnread && manga.unread == 0) { if (filterUnread && item.manga.unread == 0) {
return@f false return@f false
} }
if (filterCompleted && manga.status != SManga.COMPLETED) { if (filterCompleted && item.manga.status != SManga.COMPLETED) {
return@f false return@f false
} }
// Filter when the download directory doesn't exist or is null. // Filter when the download directory doesn't exist or is null.
if (filterDownloaded) { if (filterDownloaded) {
// Don't bother with directory checking if download count has been set.
if (item.downloadCount != -1) {
return@f item.downloadCount > 0
}
// Get the directories for the source of the manga. // Get the directories for the source of the manga.
val dirsForSource = mangaDirsForSource.getOrPut(source.id) { val dirsForSource = mangaDirsForSource.getOrPut(source.id) {
val sourceDir = downloadManager.findSourceDir(source) val sourceDir = downloadManager.findSourceDir(source)
sourceDir?.listFiles()?.associateBy { it.name }.orEmpty() sourceDir?.listFiles()?.associateBy { it.name }.orEmpty()
} }
val mangaDirName = downloadManager.getMangaDirName(manga) val mangaDirName = downloadManager.getMangaDirName(item.manga)
val mangaDir = dirsForSource[mangaDirName] ?: return@f false val mangaDir = dirsForSource[mangaDirName] ?: return@f false
val hasDirs = chapterDirectories.getOrPut(manga.id!!) { val hasDirs = chapterDirectories.getOrPut(item.manga.id!!) {
mangaDir.listFiles()?.isNotEmpty() ?: false mangaDir.listFiles()?.isNotEmpty() ?: false
} }
if (!hasDirs) { if (!hasDirs) {
@ -150,45 +162,48 @@ class LibraryPresenter(
} }
/** /**
* Adds Downloaded chapter count to manga * Sets downloaded chapter count to each manga.
* *
* @param map the map to filter. * @param map the map of manga.
*/ */
private fun addDownloadTotal(map: Map<Int, List<LibraryManga>>): Map<Int, List<LibraryManga>> { private fun setDownloadCount(map: LibraryMap) {
// Cached list of downloaded manga directories given a source id. if (!preferences.downloadBadge().getOrDefault()) {
if (preferences.downloadBadge().getOrDefault()) { // Unset download count if the preference is not enabled.
val mangaDirsForSource = mutableMapOf<Long, Map<String?, UniFile>>() for ((_, itemList) in map) {
for (item in itemList) {
// Cached list of downloaded chapter directories for a manga. item.downloadCount = -1
val chapterDirectories = mutableMapOf<Long, Int>()
for ((key, mangaList) in map) {
for (manga in mangaList) {
manga.downloadTotal = getDownloadedCountFromDirectory(manga, mangaDirsForSource, chapterDirectories)
} }
} }
return
} }
return map;
}
//Get count of downloaded chapters for a manga // Cached list of downloaded manga directories given a source id.
fun getDownloadedCountFromDirectory(manga: Manga, mangaDirsForSource: MutableMap<Long, Map<String?, UniFile>>, chapterDirectories: MutableMap<Long, Int>): Int { val mangaDirsForSource = mutableMapOf<Long, Map<String?, UniFile>>()
val source = sourceManager.get(manga.source) ?: return 0;
// Get the directories for the source of the manga.
val dirsForSource = mangaDirsForSource.getOrPut(source.id) {
val sourceDir = downloadManager.findSourceDir(source)
sourceDir?.listFiles()?.associateBy { it.name }.orEmpty()
}
val mangaDirName = downloadManager.getMangaDirName(manga)
val mangaDir = dirsForSource[mangaDirName] ?: return 0
chapterDirectories.getOrPut(manga.id!!) { // Cached list of downloaded chapter directories for a manga.
if (mangaDir.listFiles()?.isNotEmpty() ?: false) { val chapterDirectories = mutableMapOf<Long, Int>()
return mangaDir.listFiles()!!.size
val downloadCountFn: (LibraryItem) -> Int = f@ { item ->
val source = sourceManager.get(item.manga.source) ?: return@f 0
// Get the directories for the source of the manga.
val dirsForSource = mangaDirsForSource.getOrPut(source.id) {
val sourceDir = downloadManager.findSourceDir(source)
sourceDir?.listFiles()?.associateBy { it.name }.orEmpty()
}
val mangaDirName = downloadManager.getMangaDirName(item.manga)
val mangaDir = dirsForSource[mangaDirName] ?: return@f 0
chapterDirectories.getOrPut(item.manga.id!!) {
mangaDir.listFiles()?.size ?: 0
}
}
for ((_, itemList) in map) {
for (item in itemList) {
item.downloadCount = downloadCountFn(item)
} }
return 0;
} }
return 0;
} }
/** /**
@ -196,7 +211,7 @@ class LibraryPresenter(
* *
* @param map the map to sort. * @param map the map to sort.
*/ */
private fun applySort(map: Map<Int, List<LibraryManga>>): Map<Int, List<LibraryManga>> { private fun applySort(map: LibraryMap): LibraryMap {
val sortingMode = preferences.librarySortingMode().getOrDefault() val sortingMode = preferences.librarySortingMode().getOrDefault()
val lastReadManga by lazy { val lastReadManga by lazy {
@ -208,25 +223,25 @@ class LibraryPresenter(
db.getTotalChapterManga().executeAsBlocking().associate { it.id!! to counter++ } db.getTotalChapterManga().executeAsBlocking().associate { it.id!! to counter++ }
} }
val sortFn: (LibraryManga, LibraryManga) -> Int = { manga1, manga2 -> val sortFn: (LibraryItem, LibraryItem) -> Int = { i1, i2 ->
when (sortingMode) { when (sortingMode) {
LibrarySort.ALPHA -> manga1.title.compareTo(manga2.title) LibrarySort.ALPHA -> i1.manga.title.compareTo(i2.manga.title)
LibrarySort.LAST_READ -> { LibrarySort.LAST_READ -> {
// Get index of manga, set equal to list if size unknown. // Get index of manga, set equal to list if size unknown.
val manga1LastRead = lastReadManga[manga1.id!!] ?: lastReadManga.size val manga1LastRead = lastReadManga[i1.manga.id!!] ?: lastReadManga.size
val manga2LastRead = lastReadManga[manga2.id!!] ?: lastReadManga.size val manga2LastRead = lastReadManga[i2.manga.id!!] ?: lastReadManga.size
manga1LastRead.compareTo(manga2LastRead) manga1LastRead.compareTo(manga2LastRead)
} }
LibrarySort.LAST_UPDATED -> manga2.last_update.compareTo(manga1.last_update) LibrarySort.LAST_UPDATED -> i2.manga.last_update.compareTo(i1.manga.last_update)
LibrarySort.UNREAD -> manga1.unread.compareTo(manga2.unread) LibrarySort.UNREAD -> i1.manga.unread.compareTo(i2.manga.unread)
LibrarySort.TOTAL -> { LibrarySort.TOTAL -> {
val manga1TotalChapter = totalChapterManga[manga1.id!!] ?: 0 val manga1TotalChapter = totalChapterManga[i1.manga.id!!] ?: 0
val mange2TotalChapter = totalChapterManga[manga2.id!!] ?: 0 val mange2TotalChapter = totalChapterManga[i2.manga.id!!] ?: 0
manga1TotalChapter.compareTo(mange2TotalChapter) manga1TotalChapter.compareTo(mange2TotalChapter)
} }
LibrarySort.SOURCE -> { LibrarySort.SOURCE -> {
val source1Name = sourceManager.get(manga1.source)?.name ?: "" val source1Name = sourceManager.get(i1.manga.source)?.name ?: ""
val source2Name = sourceManager.get(manga2.source)?.name ?: "" val source2Name = sourceManager.get(i2.manga.source)?.name ?: ""
source1Name.compareTo(source2Name) source1Name.compareTo(source2Name)
} }
else -> throw Exception("Unknown sorting mode") else -> throw Exception("Unknown sorting mode")
@ -246,7 +261,7 @@ class LibraryPresenter(
* *
* @return an observable of the categories and its manga. * @return an observable of the categories and its manga.
*/ */
private fun getLibraryObservable(): Observable<Pair<List<Category>, Map<Int, List<LibraryManga>>>> { private fun getLibraryObservable(): Observable<Library> {
return Observable.combineLatest(getCategoriesObservable(), getLibraryMangasObservable(), return Observable.combineLatest(getCategoriesObservable(), getLibraryMangasObservable(),
{ dbCategories, libraryManga -> { dbCategories, libraryManga ->
val categories = if (libraryManga.containsKey(0)) val categories = if (libraryManga.containsKey(0))
@ -255,7 +270,7 @@ class LibraryPresenter(
dbCategories dbCategories
this.categories = categories this.categories = categories
Pair(categories, libraryManga) Library(categories, libraryManga)
}) })
} }
@ -274,9 +289,9 @@ class LibraryPresenter(
* @return an observable containing a map with the category id as key and a list of manga as the * @return an observable containing a map with the category id as key and a list of manga as the
* value. * value.
*/ */
private fun getLibraryMangasObservable(): Observable<Map<Int, List<LibraryManga>>> { private fun getLibraryMangasObservable(): Observable<LibraryMap> {
return db.getLibraryMangas().asRxObservable() return db.getLibraryMangas().asRxObservable()
.map { list -> list.groupBy { it.category } } .map { list -> list.map(::LibraryItem).groupBy { it.manga.category } }
} }
/** /**