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 downloadTotal: Int = 0
}

View File

@ -3,15 +3,9 @@ package eu.kanade.tachiyomi.ui.library
import android.view.View
import com.bumptech.glide.load.engine.DiskCacheStrategy
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.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.getOrDefault
import eu.kanade.tachiyomi.source.LocalSource
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.
@ -27,37 +21,36 @@ class LibraryGridHolder(
private val adapter: FlexibleAdapter<*>
) : LibraryHolder(view, adapter) {
private val preferences: PreferencesHelper = Injekt.get()
/**
* Method called from [LibraryCategoryAdapter.onBindViewHolder]. It updates the data for this
* 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.
view.title.text = manga.title
view.title.text = item.manga.title
// Update the unread count and its visibility.
with(view.unread_text) {
visibility = if (manga.unread > 0) View.VISIBLE else View.GONE
text = manga.unread.toString()
visibility = if (item.manga.unread > 0) View.VISIBLE else View.GONE
text = item.manga.unread.toString()
}
// Update the download count and its visibility.
with(view.download_text) {
visibility = if (manga.downloadTotal > 0 && preferences.downloadBadge().getOrDefault()) View.VISIBLE else View.GONE
text = manga.downloadTotal.toString()
visibility = if (item.downloadCount > 0) View.VISIBLE else View.GONE
text = item.downloadCount.toString()
}
//set local visibility if its local manga
with(view.local_text){
visibility = if(manga.source == LocalSource.ID) View.VISIBLE else View.GONE
with(view.local_text) {
visibility = if(item.manga.source == LocalSource.ID) View.VISIBLE else View.GONE
}
// Update the cover.
GlideApp.with(view.context).clear(view.thumbnail)
GlideApp.with(view.context)
.load(manga)
.load(item.manga)
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.centerCrop()
.into(view.thumbnail)

View File

@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.ui.library
import android.view.View
import eu.davidea.flexibleadapter.FlexibleAdapter
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.
@ -21,8 +20,8 @@ abstract class LibraryHolder(
* Method called from [LibraryCategoryAdapter.onBindViewHolder]. It updates the data for this
* 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 {
var downloadCount = -1
override fun getLayoutRes(): Int {
return R.layout.catalogue_grid_item
}
@ -43,7 +45,7 @@ class LibraryItem(val manga: LibraryManga) : AbstractFlexibleItem<LibraryHolder>
position: Int,
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 com.bumptech.glide.load.engine.DiskCacheStrategy
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.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.getOrDefault
import eu.kanade.tachiyomi.source.LocalSource
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.
@ -26,31 +21,30 @@ class LibraryListHolder(
private val view: View,
private val adapter: FlexibleAdapter<*>
) : LibraryHolder(view, adapter) {
private val preferences: PreferencesHelper = Injekt.get()
/**
* Method called from [LibraryCategoryAdapter.onBindViewHolder]. It updates the data for this
* 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.
itemView.title.text = manga.title
itemView.title.text = item.manga.title
// Update the unread count and its visibility.
with(itemView.unread_text) {
visibility = if (manga.unread > 0) View.VISIBLE else View.GONE
text = manga.unread.toString()
visibility = if (item.manga.unread > 0) View.VISIBLE else View.GONE
text = item.manga.unread.toString()
}
// Update the download count and its visibility.
with(itemView.download_text) {
visibility = if (manga.downloadTotal > 0 && preferences.downloadBadge().getOrDefault()) View.VISIBLE else View.GONE
text = manga.downloadTotal.toString()
visibility = if (item.downloadCount > 0) View.VISIBLE else View.GONE
text = "${item.downloadCount}"
}
//show local text badge if local manga
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
@ -62,7 +56,7 @@ class LibraryListHolder(
// Update the cover.
GlideApp.with(itemView.context).clear(itemView.thumbnail)
GlideApp.with(itemView.context)
.load(manga)
.load(item.manga)
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.centerCrop()
.circleCrop()

View File

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