Steps to optimize recents loading

especially those with insanely large libraries
This commit is contained in:
Jays2Kings 2021-04-12 22:05:53 -04:00
parent 2ec4db3c10
commit 2f09ac8cb5
6 changed files with 229 additions and 260 deletions

View File

@ -56,5 +56,7 @@ open class DatabaseHelper(context: Context) :
inline fun inTransaction(block: () -> Unit) = db.inTransaction(block) inline fun inTransaction(block: () -> Unit) = db.inTransaction(block)
inline fun <T> inTransactionReturn(block: () -> T): T = db.inTransactionReturn(block)
fun lowLevel() = db.lowLevel() fun lowLevel() = db.lowLevel()
} }

View File

@ -15,7 +15,6 @@ import eu.kanade.tachiyomi.data.database.resolvers.MangaChapterGetResolver
import eu.kanade.tachiyomi.data.database.resolvers.MangaChapterHistoryGetResolver import eu.kanade.tachiyomi.data.database.resolvers.MangaChapterHistoryGetResolver
import eu.kanade.tachiyomi.data.database.tables.ChapterTable import eu.kanade.tachiyomi.data.database.tables.ChapterTable
import eu.kanade.tachiyomi.util.lang.sqLite import eu.kanade.tachiyomi.util.lang.sqLite
import java.util.Date
interface ChapterQueries : DbProvider { interface ChapterQueries : DbProvider {
@ -32,14 +31,12 @@ interface ChapterQueries : DbProvider {
) )
.prepare() .prepare()
fun getRecentChapters(date: Date) = getRecentChapters(Date(), date) fun getRecentChapters(search: String = "", offset: Int, isResuming: Boolean) = db.get()
fun getRecentChapters(startDate: Date, date: Date, search: String = "") = db.get()
.listOfObjects(MangaChapter::class.java) .listOfObjects(MangaChapter::class.java)
.withQuery( .withQuery(
RawQuery.builder() RawQuery.builder()
.query(getRecentsQuery(search.sqLite)) .query(getRecentsQuery(search.sqLite, offset, isResuming))
.args(date.time, startDate.time) // .args(date.time, startDate.time)
.observesTables(ChapterTable.TABLE) .observesTables(ChapterTable.TABLE)
.build() .build()
) )
@ -51,15 +48,12 @@ interface ChapterQueries : DbProvider {
* @param date recent date range * @param date recent date range
* @offset offset the db by * @offset offset the db by
*/ */
fun getUpdatedManga(date: Date, search: String = "", endless: Boolean) = fun getUpdatedManga(search: String = "", endless: Boolean, offset: Int, isResuming: Boolean) = db.get()
getUpdatedManga(Date(), date, search, endless)
fun getUpdatedManga(startDate: Date, date: Date, search: String = "", endless: Boolean) = db.get()
.listOfObjects(MangaChapterHistory::class.java) .listOfObjects(MangaChapterHistory::class.java)
.withQuery( .withQuery(
RawQuery.builder() RawQuery.builder()
.query(getRecentsQueryDistinct(search.sqLite, endless)) .query(getRecentsQueryDistinct(search.sqLite, endless, offset, isResuming))
.args(date.time, startDate.time) // .args(date.time, startDate.time)
.observesTables(ChapterTable.TABLE) .observesTables(ChapterTable.TABLE)
.build() .build()
) )

View File

@ -10,7 +10,6 @@ import eu.kanade.tachiyomi.data.database.resolvers.MangaChapterHistoryGetResolve
import eu.kanade.tachiyomi.data.database.tables.HistoryTable import eu.kanade.tachiyomi.data.database.tables.HistoryTable
import eu.kanade.tachiyomi.data.database.tables.MangaTable import eu.kanade.tachiyomi.data.database.tables.MangaTable
import eu.kanade.tachiyomi.util.lang.sqLite import eu.kanade.tachiyomi.util.lang.sqLite
import java.util.Date
interface HistoryQueries : DbProvider { interface HistoryQueries : DbProvider {
@ -18,36 +17,36 @@ interface HistoryQueries : DbProvider {
* Insert history into database * Insert history into database
* @param history object containing history information * @param history object containing history information
*/ */
fun insertHistory(history: History) = db.put().`object`(history).prepare() // fun insertHistory(history: History) = db.put().`object`(history).prepare()
// /**
// * Returns history of recent manga containing last read chapter in 25s
// * @param date recent date range
// * @offset offset the db by
// */
// fun getRecentManga(date: Date, offset: Int = 0, search: String = "") = db.get()
// .listOfObjects(MangaChapterHistory::class.java)
// .withQuery(
// RawQuery.builder()
// .query(getRecentMangasQuery(offset, search.sqLite))
// .args(date.time)
// .observesTables(HistoryTable.TABLE)
// .build()
// )
// .withGetResolver(MangaChapterHistoryGetResolver.INSTANCE)
// .prepare()
/** /**
* Returns history of recent manga containing last read chapter in 25s * Returns history of recent manga containing last read chapter in 25s
* @param date recent date range * @param date recent date range
* @offset offset the db by * @offset offset the db by
*/ */
fun getRecentManga(date: Date, offset: Int = 0, search: String = "") = db.get() fun getRecentlyAdded(search: String = "", endless: Boolean, offset: Int, isResuming: Boolean) = db.get()
.listOfObjects(MangaChapterHistory::class.java) .listOfObjects(MangaChapterHistory::class.java)
.withQuery( .withQuery(
RawQuery.builder() RawQuery.builder()
.query(getRecentMangasQuery(offset, search.sqLite)) .query(getRecentAdditionsQuery(search.sqLite, endless, offset, isResuming))
.args(date.time) // .args(date.time, startDate.time)
.observesTables(HistoryTable.TABLE)
.build()
)
.withGetResolver(MangaChapterHistoryGetResolver.INSTANCE)
.prepare()
/**
* Returns history of recent manga containing last read chapter in 25s
* @param date recent date range
* @offset offset the db by
*/
fun getRecentlyAdded(startDate: Date, date: Date, search: String = "", endless: Boolean) = db.get()
.listOfObjects(MangaChapterHistory::class.java)
.withQuery(
RawQuery.builder()
.query(getRecentAdditionsQuery(search.sqLite, endless))
.args(date.time, startDate.time)
.observesTables(MangaTable.TABLE) .observesTables(MangaTable.TABLE)
.build() .build()
) )
@ -59,20 +58,12 @@ interface HistoryQueries : DbProvider {
* @param date recent date range * @param date recent date range
* @offset offset the db by * @offset offset the db by
*/ */
fun getRecentMangaLimit(date: Date, limit: Int = 0, search: String = "") = fun getRecentMangaLimit(search: String = "", endless: Boolean, offset: Int, isResuming: Boolean) = db.get()
getRecentMangaLimit(Date(), date, limit, search)
/**
* Returns history of recent manga containing last read chapter in 25s
* @param date recent date range
* @offset offset the db by
*/
fun getRecentMangaLimit(startDate: Date, date: Date, limit: Int = 0, search: String = "") = db.get()
.listOfObjects(MangaChapterHistory::class.java) .listOfObjects(MangaChapterHistory::class.java)
.withQuery( .withQuery(
RawQuery.builder() RawQuery.builder()
.query(getRecentMangasLimitQuery(limit, search.sqLite)) .query(getRecentMangasLimitQuery(search.sqLite, endless, offset, isResuming))
.args(date.time, startDate.time) // .args(date.time, startDate.time)
.observesTables(HistoryTable.TABLE) .observesTables(HistoryTable.TABLE)
.build() .build()
) )
@ -84,29 +75,20 @@ interface HistoryQueries : DbProvider {
* @param date recent date range * @param date recent date range
* @offset offset the db by * @offset offset the db by
*/ */
fun getRecentsWithUnread(startDate: Date, date: Date, search: String = "", endless: Boolean) = db.get() fun getAllRecents(search: String = "", includeRead: Boolean, endless: Boolean, offset: Int, isResuming: Boolean) = db.get()
.listOfObjects(MangaChapterHistory::class.java) .listOfObjects(MangaChapterHistory::class.java)
.withQuery( .withQuery(
RawQuery.builder() RawQuery.builder()
.query(getRecentReadWithUnreadChapters(search.sqLite, endless)) .query(
.args(date.time, startDate.time) getRecentRead(
.observesTables(HistoryTable.TABLE) search.sqLite,
.build() includeRead,
endless,
offset,
isResuming
) )
.withGetResolver(MangaChapterHistoryGetResolver.INSTANCE) )
.prepare() // .args(date.time, startDate.time)
/**
* Returns history of recent manga containing last read chapter in 25s
* @param date recent date range
* @offset offset the db by
*/
fun getAllRecents(startDate: Date, date: Date, search: String = "", endless: Boolean) = db.get()
.listOfObjects(MangaChapterHistory::class.java)
.withQuery(
RawQuery.builder()
.query(getRecentRead(search.sqLite, endless))
.args(date.time, startDate.time)
.observesTables(HistoryTable.TABLE) .observesTables(HistoryTable.TABLE)
.build() .build()
) )

View File

@ -1,5 +1,6 @@
package eu.kanade.tachiyomi.data.database.queries package eu.kanade.tachiyomi.data.database.queries
import eu.kanade.tachiyomi.ui.recents.RecentsPresenter
import eu.kanade.tachiyomi.data.database.tables.CategoryTable as Category import eu.kanade.tachiyomi.data.database.tables.CategoryTable as Category
import eu.kanade.tachiyomi.data.database.tables.ChapterTable as Chapter import eu.kanade.tachiyomi.data.database.tables.ChapterTable as Chapter
import eu.kanade.tachiyomi.data.database.tables.HistoryTable as History import eu.kanade.tachiyomi.data.database.tables.HistoryTable as History
@ -41,36 +42,41 @@ val libraryQuery =
/** /**
* Query to get the recent chapters of manga from the library up to a date. * Query to get the recent chapters of manga from the library up to a date.
*/ */
fun getRecentsQuery(search: String) = fun getRecentsQuery(search: String, offset: Int, isResuming: Boolean) =
""" """
SELECT ${Manga.TABLE}.${Manga.COL_URL} as mangaUrl, * FROM ${Manga.TABLE} JOIN ${Chapter.TABLE} SELECT ${Manga.TABLE}.${Manga.COL_URL} as mangaUrl, * FROM ${Manga.TABLE} JOIN ${Chapter.TABLE}
ON ${Manga.TABLE}.${Manga.COL_ID} = ${Chapter.TABLE}.${Chapter.COL_MANGA_ID} ON ${Manga.TABLE}.${Manga.COL_ID} = ${Chapter.TABLE}.${Chapter.COL_MANGA_ID}
WHERE ${Manga.COL_FAVORITE} = 1 WHERE ${Manga.COL_FAVORITE} = 1
AND ${Chapter.COL_DATE_FETCH} > ?
AND ${Chapter.COL_DATE_FETCH} < ?
AND ${Chapter.COL_DATE_FETCH} > ${Manga.COL_DATE_ADDED} AND ${Chapter.COL_DATE_FETCH} > ${Manga.COL_DATE_ADDED}
AND lower(${Manga.COL_TITLE}) LIKE '%$search%' AND lower(${Manga.COL_TITLE}) LIKE '%$search%'
ORDER BY ${Chapter.COL_DATE_UPLOAD} DESC ORDER BY ${Chapter.COL_DATE_FETCH} DESC
${limitAndOffset(true, isResuming, offset)}
""" """
/** /**
* Query to get the recently added manga * Query to get the recently added manga
*/ */
fun getRecentAdditionsQuery(search: String, endless: Boolean) = fun getRecentAdditionsQuery(search: String, endless: Boolean, offset: Int, isResuming: Boolean) =
""" """
SELECT ${Manga.TABLE}.${Manga.COL_URL} as mangaUrl, * FROM ${Manga.TABLE} SELECT ${Manga.TABLE}.${Manga.COL_URL} as mangaUrl, * FROM ${Manga.TABLE}
WHERE ${Manga.COL_FAVORITE} = 1 WHERE ${Manga.COL_FAVORITE} = 1
AND ${Manga.COL_DATE_ADDED} > ?
AND ${Manga.COL_DATE_ADDED} < ?
AND lower(${Manga.COL_TITLE}) LIKE '%$search%' AND lower(${Manga.COL_TITLE}) LIKE '%$search%'
ORDER BY ${Manga.COL_DATE_ADDED} DESC ORDER BY ${Manga.COL_DATE_ADDED} DESC
${if (endless) "" else "LIMIT 8"} ${limitAndOffset(endless, isResuming, offset)}
""" """
fun limitAndOffset(endless: Boolean, isResuming: Boolean, offset: Int): String {
return when {
isResuming && endless -> "LIMIT $offset"
endless -> "LIMIT ${RecentsPresenter.ENDLESS_LIMIT}\nOFFSET $offset"
else -> "LIMIT 8"
}
}
/** /**
* Query to get the manga with recently uploaded chapters * Query to get the manga with recently uploaded chapters
*/ */
fun getRecentsQueryDistinct(search: String, endless: Boolean) = fun getRecentsQueryDistinct(search: String, endless: Boolean, offset: Int = 0, isResuming: Boolean) =
""" """
SELECT ${Manga.TABLE}.${Manga.COL_URL} as mangaUrl, ${Manga.TABLE}.*, ${Chapter.TABLE}.* SELECT ${Manga.TABLE}.${Manga.COL_URL} as mangaUrl, ${Manga.TABLE}.*, ${Chapter.TABLE}.*
FROM ${Manga.TABLE} FROM ${Manga.TABLE}
@ -80,17 +86,15 @@ fun getRecentsQueryDistinct(search: String, endless: Boolean) =
SELECT ${Chapter.TABLE}.${Chapter.COL_MANGA_ID},${Chapter.TABLE}.${Chapter.COL_ID} as ${History.COL_CHAPTER_ID},MAX(${Chapter.TABLE}.${Chapter.COL_DATE_UPLOAD}) SELECT ${Chapter.TABLE}.${Chapter.COL_MANGA_ID},${Chapter.TABLE}.${Chapter.COL_ID} as ${History.COL_CHAPTER_ID},MAX(${Chapter.TABLE}.${Chapter.COL_DATE_UPLOAD})
FROM ${Chapter.TABLE} JOIN ${Manga.TABLE} FROM ${Chapter.TABLE} JOIN ${Manga.TABLE}
ON ${Manga.TABLE}.${Manga.COL_ID} = ${Chapter.TABLE}.${Chapter.COL_MANGA_ID} ON ${Manga.TABLE}.${Manga.COL_ID} = ${Chapter.TABLE}.${Chapter.COL_MANGA_ID}
WHERE ${Chapter.COL_DATE_FETCH} > ? WHERE ${Chapter.COL_READ} = 0
AND ${Chapter.COL_DATE_FETCH} < ?
AND ${Chapter.COL_READ} = 0
GROUP BY ${Chapter.TABLE}.${Chapter.COL_MANGA_ID}) AS newest_chapter GROUP BY ${Chapter.TABLE}.${Chapter.COL_MANGA_ID}) AS newest_chapter
ON ${Chapter.TABLE}.${Chapter.COL_MANGA_ID} = newest_chapter.${Chapter.COL_MANGA_ID} ON ${Chapter.TABLE}.${Chapter.COL_MANGA_ID} = newest_chapter.${Chapter.COL_MANGA_ID}
WHERE ${Manga.COL_FAVORITE} = 1 WHERE ${Manga.COL_FAVORITE} = 1
AND newest_chapter.${History.COL_CHAPTER_ID} = ${Chapter.TABLE}.${Chapter.COL_ID} AND newest_chapter.${History.COL_CHAPTER_ID} = ${Chapter.TABLE}.${Chapter.COL_ID}
AND ${Chapter.COL_DATE_FETCH} > ${Manga.COL_DATE_ADDED} AND ${Chapter.COL_DATE_FETCH} > ${Manga.COL_DATE_ADDED}
AND lower(${Manga.COL_TITLE}) LIKE '%$search%' AND lower(${Manga.COL_TITLE}) LIKE '%$search%'
ORDER BY ${Chapter.COL_DATE_UPLOAD} DESC ORDER BY ${Chapter.COL_DATE_FETCH} DESC
${if (endless) "" else "LIMIT 8"} ${limitAndOffset(endless, isResuming, offset)}
""" """
/** /**
@ -98,9 +102,13 @@ fun getRecentsQueryDistinct(search: String, endless: Boolean) =
* The max_last_read table contains the most recent chapters grouped by manga * The max_last_read table contains the most recent chapters grouped by manga
* The select statement returns all information of chapters that have the same id as the chapter in max_last_read * The select statement returns all information of chapters that have the same id as the chapter in max_last_read
* and are read after the given time period * and are read after the given time period
* @return return limit is 25
*/ */
fun getRecentMangasQuery(offset: Int = 0, search: String = "") = fun getRecentMangasLimitQuery(
search: String = "",
endless: Boolean,
offset: Int = 0,
isResuming: Boolean
) =
""" """
SELECT ${Manga.TABLE}.${Manga.COL_URL} as mangaUrl, ${Manga.TABLE}.*, ${Chapter.TABLE}.*, ${History.TABLE}.* SELECT ${Manga.TABLE}.${Manga.COL_URL} as mangaUrl, ${Manga.TABLE}.*, ${Chapter.TABLE}.*, ${History.TABLE}.*
FROM ${Manga.TABLE} FROM ${Manga.TABLE}
@ -114,39 +122,10 @@ fun getRecentMangasQuery(offset: Int = 0, search: String = "") =
ON ${Chapter.TABLE}.${Chapter.COL_ID} = ${History.TABLE}.${History.COL_CHAPTER_ID} ON ${Chapter.TABLE}.${Chapter.COL_ID} = ${History.TABLE}.${History.COL_CHAPTER_ID}
GROUP BY ${Chapter.TABLE}.${Chapter.COL_MANGA_ID}) AS max_last_read GROUP BY ${Chapter.TABLE}.${Chapter.COL_MANGA_ID}) AS max_last_read
ON ${Chapter.TABLE}.${Chapter.COL_MANGA_ID} = max_last_read.${Chapter.COL_MANGA_ID} ON ${Chapter.TABLE}.${Chapter.COL_MANGA_ID} = max_last_read.${Chapter.COL_MANGA_ID}
WHERE ${History.TABLE}.${History.COL_LAST_READ} > ?
AND max_last_read.${History.COL_CHAPTER_ID} = ${History.TABLE}.${History.COL_CHAPTER_ID} AND max_last_read.${History.COL_CHAPTER_ID} = ${History.TABLE}.${History.COL_CHAPTER_ID}
AND lower(${Manga.TABLE}.${Manga.COL_TITLE}) LIKE '%$search%' AND lower(${Manga.TABLE}.${Manga.COL_TITLE}) LIKE '%$search%'
ORDER BY max_last_read.${History.COL_LAST_READ} DESC ORDER BY max_last_read.${History.COL_LAST_READ} DESC
LIMIT 25 OFFSET $offset ${limitAndOffset(endless, isResuming, offset)}
"""
/**
* Query to get the recently read chapters of manga from the library up to a date.
* The max_last_read table contains the most recent chapters grouped by manga
* The select statement returns all information of chapters that have the same id as the chapter in max_last_read
* and are read after the given time period
*/
fun getRecentMangasLimitQuery(limit: Int = 25, search: String = "") =
"""
SELECT ${Manga.TABLE}.${Manga.COL_URL} as mangaUrl, ${Manga.TABLE}.*, ${Chapter.TABLE}.*, ${History.TABLE}.*
FROM ${Manga.TABLE}
JOIN ${Chapter.TABLE}
ON ${Manga.TABLE}.${Manga.COL_ID} = ${Chapter.TABLE}.${Chapter.COL_MANGA_ID}
JOIN ${History.TABLE}
ON ${Chapter.TABLE}.${Chapter.COL_ID} = ${History.TABLE}.${History.COL_CHAPTER_ID}
JOIN (
SELECT ${Chapter.TABLE}.${Chapter.COL_MANGA_ID},${Chapter.TABLE}.${Chapter.COL_ID} as ${History.COL_CHAPTER_ID}, MAX(${History.TABLE}.${History.COL_LAST_READ}) as ${History.COL_LAST_READ}
FROM ${Chapter.TABLE} JOIN ${History.TABLE}
ON ${Chapter.TABLE}.${Chapter.COL_ID} = ${History.TABLE}.${History.COL_CHAPTER_ID}
GROUP BY ${Chapter.TABLE}.${Chapter.COL_MANGA_ID}) AS max_last_read
ON ${Chapter.TABLE}.${Chapter.COL_MANGA_ID} = max_last_read.${Chapter.COL_MANGA_ID}
WHERE ${History.TABLE}.${History.COL_LAST_READ} > ?
AND ${History.TABLE}.${History.COL_LAST_READ} < ?
AND max_last_read.${History.COL_CHAPTER_ID} = ${History.TABLE}.${History.COL_CHAPTER_ID}
AND lower(${Manga.TABLE}.${Manga.COL_TITLE}) LIKE '%$search%'
ORDER BY max_last_read.${History.COL_LAST_READ} DESC
LIMIT $limit
""" """
/** /**
@ -156,7 +135,13 @@ fun getRecentMangasLimitQuery(limit: Int = 25, search: String = "") =
* The select statement returns all information of chapters that have the same id as the chapter in max_last_read * The select statement returns all information of chapters that have the same id as the chapter in max_last_read
* and are read after the given time period * and are read after the given time period
*/ */
fun getRecentReadWithUnreadChapters(search: String = "", endless: Boolean) = fun getRecentRead(
search: String = "",
includeRead: Boolean,
endless: Boolean,
offset: Int = 0,
isResuming: Boolean
) =
""" """
SELECT ${Manga.TABLE}.${Manga.COL_URL} as mangaUrl, ${Manga.TABLE}.*, ${Chapter.TABLE}.*, ${History.TABLE}.* SELECT ${Manga.TABLE}.${Manga.COL_URL} as mangaUrl, ${Manga.TABLE}.*, ${Chapter.TABLE}.*, ${History.TABLE}.*
FROM ( FROM (
@ -165,11 +150,10 @@ fun getRecentReadWithUnreadChapters(search: String = "", endless: Boolean) =
LEFT JOIN ( LEFT JOIN (
SELECT ${Chapter.COL_MANGA_ID}, COUNT(*) AS unread SELECT ${Chapter.COL_MANGA_ID}, COUNT(*) AS unread
FROM ${Chapter.TABLE} FROM ${Chapter.TABLE}
WHERE ${Chapter.COL_READ} = 0
GROUP BY ${Chapter.COL_MANGA_ID} GROUP BY ${Chapter.COL_MANGA_ID}
) AS C ) AS C
ON ${Manga.COL_ID} = C.${Chapter.COL_MANGA_ID} ON ${Manga.COL_ID} = C.${Chapter.COL_MANGA_ID}
WHERE C.unread > 0 ${if (includeRead) "" else "WHERE C.unread > 0"}
GROUP BY ${Manga.COL_ID} GROUP BY ${Manga.COL_ID}
ORDER BY ${Manga.COL_TITLE} ORDER BY ${Manga.COL_TITLE}
) AS ${Manga.TABLE} ) AS ${Manga.TABLE}
@ -183,53 +167,10 @@ fun getRecentReadWithUnreadChapters(search: String = "", endless: Boolean) =
ON ${Chapter.TABLE}.${Chapter.COL_ID} = ${History.TABLE}.${History.COL_CHAPTER_ID} ON ${Chapter.TABLE}.${Chapter.COL_ID} = ${History.TABLE}.${History.COL_CHAPTER_ID}
GROUP BY ${Chapter.TABLE}.${Chapter.COL_MANGA_ID}) AS max_last_read GROUP BY ${Chapter.TABLE}.${Chapter.COL_MANGA_ID}) AS max_last_read
ON ${Chapter.TABLE}.${Chapter.COL_MANGA_ID} = max_last_read.${Chapter.COL_MANGA_ID} ON ${Chapter.TABLE}.${Chapter.COL_MANGA_ID} = max_last_read.${Chapter.COL_MANGA_ID}
WHERE ${History.TABLE}.${History.COL_LAST_READ} > ?
AND ${History.TABLE}.${History.COL_LAST_READ} < ?
AND max_last_read.${History.COL_CHAPTER_ID} = ${History.TABLE}.${History.COL_CHAPTER_ID} AND max_last_read.${History.COL_CHAPTER_ID} = ${History.TABLE}.${History.COL_CHAPTER_ID}
AND lower(${Manga.TABLE}.${Manga.COL_TITLE}) LIKE '%$search%' AND lower(${Manga.TABLE}.${Manga.COL_TITLE}) LIKE '%$search%'
ORDER BY max_last_read.${History.COL_LAST_READ} DESC ORDER BY max_last_read.${History.COL_LAST_READ} DESC
${if (endless) "" else "LIMIT 8"} ${limitAndOffset(endless, isResuming, offset)}
"""
/**
* Query to get the recently read manga that has more chapters to read
* The first from checks that there's an unread chapter
* The max_last_read table contains the most recent chapters grouped by manga
* The select statement returns all information of chapters that have the same id as the chapter in max_last_read
* and are read after the given time period
*/
fun getRecentRead(search: String = "", endless: Boolean) =
"""
SELECT ${Manga.TABLE}.${Manga.COL_URL} as mangaUrl, ${Manga.TABLE}.*, ${Chapter.TABLE}.*, ${History.TABLE}.*
FROM (
SELECT ${Manga.TABLE}.*
FROM ${Manga.TABLE}
LEFT JOIN (
SELECT ${Chapter.COL_MANGA_ID}, COUNT(*) AS unread
FROM ${Chapter.TABLE}
GROUP BY ${Chapter.COL_MANGA_ID}
) AS C
ON ${Manga.COL_ID} = C.${Chapter.COL_MANGA_ID}
WHERE C.unread > 0
GROUP BY ${Manga.COL_ID}
ORDER BY ${Manga.COL_TITLE}
) AS ${Manga.TABLE}
JOIN ${Chapter.TABLE}
ON ${Manga.TABLE}.${Manga.COL_ID} = ${Chapter.TABLE}.${Chapter.COL_MANGA_ID}
JOIN ${History.TABLE}
ON ${Chapter.TABLE}.${Chapter.COL_ID} = ${History.TABLE}.${History.COL_CHAPTER_ID}
JOIN (
SELECT ${Chapter.TABLE}.${Chapter.COL_MANGA_ID},${Chapter.TABLE}.${Chapter.COL_ID} as ${History.COL_CHAPTER_ID}, MAX(${History.TABLE}.${History.COL_LAST_READ}) as ${History.COL_LAST_READ}
FROM ${Chapter.TABLE} JOIN ${History.TABLE}
ON ${Chapter.TABLE}.${Chapter.COL_ID} = ${History.TABLE}.${History.COL_CHAPTER_ID}
GROUP BY ${Chapter.TABLE}.${Chapter.COL_MANGA_ID}) AS max_last_read
ON ${Chapter.TABLE}.${Chapter.COL_MANGA_ID} = max_last_read.${Chapter.COL_MANGA_ID}
WHERE ${History.TABLE}.${History.COL_LAST_READ} > ?
AND ${History.TABLE}.${History.COL_LAST_READ} < ?
AND max_last_read.${History.COL_CHAPTER_ID} = ${History.TABLE}.${History.COL_CHAPTER_ID}
AND lower(${Manga.TABLE}.${Manga.COL_TITLE}) LIKE '%$search%'
ORDER BY max_last_read.${History.COL_LAST_READ} DESC
${if (endless) "" else "LIMIT 8"}
""" """
fun getHistoryByMangaId() = fun getHistoryByMangaId() =

View File

@ -181,6 +181,8 @@ class RecentsController(bundle: Bundle? = null) :
} }
presenter.onCreate() presenter.onCreate()
binding.swipeRefresh.isRefreshing = true
if (presenter.recentItems.isNotEmpty()) { if (presenter.recentItems.isNotEmpty()) {
adapter.updateDataSet(presenter.recentItems) adapter.updateDataSet(presenter.recentItems)
} }
@ -345,7 +347,9 @@ class RecentsController(bundle: Bundle? = null) :
override fun onActivityResumed(activity: Activity) { override fun onActivityResumed(activity: Activity) {
super.onActivityResumed(activity) super.onActivityResumed(activity)
if (!isBindingInitialized) return if (!isBindingInitialized) return
if (!presenter.isLoading) {
refresh() refresh()
}
setBottomPadding() setBottomPadding()
binding.downloadBottomSheet.dlBottomSheet.update() binding.downloadBottomSheet.dlBottomSheet.update()
} }

View File

@ -13,7 +13,6 @@ import eu.kanade.tachiyomi.data.library.LibraryServiceListener
import eu.kanade.tachiyomi.data.library.LibraryUpdateService import eu.kanade.tachiyomi.data.library.LibraryUpdateService
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.util.system.executeOnIO
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
@ -46,7 +45,7 @@ class RecentsPresenter(
var query = "" var query = ""
set(value) { set(value) {
field = value field = value
page = 0 resetOffsets()
} }
private val newAdditionsHeader = RecentMangaHeaderItem(RecentMangaHeaderItem.NEWLY_ADDED) private val newAdditionsHeader = RecentMangaHeaderItem(RecentMangaHeaderItem.NEWLY_ADDED)
private val newChaptersHeader = RecentMangaHeaderItem(RecentMangaHeaderItem.NEW_CHAPTERS) private val newChaptersHeader = RecentMangaHeaderItem(RecentMangaHeaderItem.NEW_CHAPTERS)
@ -55,23 +54,37 @@ class RecentsPresenter(
.CONTINUE_READING .CONTINUE_READING
) )
var finished = false var finished = false
var shouldMoveToTop = false var heldItems: HashMap<Int, List<RecentMangaItem>> = hashMapOf()
private var shouldMoveToTop = false
var viewType: Int = preferences.recentsViewType().get() var viewType: Int = preferences.recentsViewType().get()
private var page = 0
set(value) { set(value) {
field = value field = value
if (value == 0) { ENDLESS_LIMIT = if (value == VIEW_TYPE_UNGROUP_ALL) 25 else 50
}
private fun resetOffsets() {
finished = false finished = false
shouldMoveToTop = true shouldMoveToTop = true
updatesOffset = 0
historyOffset = 0
additionsOffset = 0
} }
}
private var updatesOffset = 0
private var historyOffset = 0
private var additionsOffset = 0
var isLoading = false
private set
private val isOnFirstPage: Boolean
get() = additionsOffset + historyOffset + updatesOffset == 0
init { init {
preferences.showReadInAllRecents() preferences.showReadInAllRecents()
.asFlow() .asFlow()
.drop(1) .drop(1)
.onEach { .onEach {
page = 0 resetOffsets()
getRecents() getRecents()
} }
.launchIn(scope) .launchIn(scope)
@ -97,97 +110,107 @@ class RecentsPresenter(
} }
} }
private suspend fun runRecents(oldQuery: String = "", updatePageCount: Boolean = false, retryCount: Int = 0, itemCount: Int = 0, limit: Boolean = false) { private suspend fun runRecents(
oldQuery: String = "",
updatePageCount: Boolean = false,
retryCount: Int = 0,
itemCount: Int = 0,
limit: Boolean = false,
customViewType: Int? = null
) {
if (retryCount > 15) { if (retryCount > 15) {
finished = true finished = true
setDownloadedChapters(recentItems) setDownloadedChapters(recentItems)
withContext(Dispatchers.Main) { controller?.showLists(recentItems, false) } if (customViewType == null) {
withContext(Dispatchers.Main) {
controller?.showLists(recentItems, false)
isLoading = false
}
}
return return
} }
val viewType = customViewType ?: viewType
// Timber.d("starting up items new page: $updatePageCount")
val showRead = preferences.showReadInAllRecents().get() && !limit val showRead = preferences.showReadInAllRecents().get() && !limit
if (updatePageCount) {
page++
}
val isUngrouped = viewType > VIEW_TYPE_GROUP_ALL && query.isEmpty() val isUngrouped = viewType > VIEW_TYPE_GROUP_ALL && query.isEmpty()
val cal = Calendar.getInstance().apply {
time = Date()
when {
query.isNotEmpty() -> add(Calendar.YEAR, -50)
isUngrouped -> add(Calendar.MONTH, -(page + 1))
else -> add(Calendar.MONTH, -1)
}
}
val startCal = Calendar.getInstance().apply {
time = Date()
when {
query.isNotEmpty() -> {}
isUngrouped && !updatePageCount -> {}
isUngrouped -> add(Calendar.MONTH, -page)
else -> {}
}
}
val calWeek = Calendar.getInstance().apply {
time = Date()
when {
query.isNotEmpty() -> add(Calendar.YEAR, -50)
isUngrouped -> add(Calendar.MONTH, -(page + 1))
else -> add(Calendar.WEEK_OF_YEAR, -1)
}
}
val calDay = Calendar.getInstance().apply {
time = Date()
when {
query.isNotEmpty() -> add(Calendar.YEAR, -50)
isUngrouped -> add(Calendar.MONTH, -(page + 1))
else -> add(Calendar.DAY_OF_YEAR, -1)
}
}
val isCustom = customViewType != null
val isEndless = isUngrouped && !limit
// Timber.d("set up cal items")
val (cReading, rUpdates, nAdditions) = db.inTransactionReturn {
val cReading = if (viewType != VIEW_TYPE_ONLY_UPDATES) { val cReading = if (viewType != VIEW_TYPE_ONLY_UPDATES) {
if (query.isEmpty() && viewType != VIEW_TYPE_ONLY_HISTORY) { if (query.isEmpty() && viewType != VIEW_TYPE_ONLY_HISTORY) {
if (showRead) { db.getAllRecents(
db.getAllRecents(startCal.time, cal.time, query, isUngrouped && !limit) query,
.executeOnIO() showRead,
isEndless,
if (isCustom) ENDLESS_LIMIT else historyOffset,
!updatePageCount && !isOnFirstPage
)
} else { } else {
db.getRecentsWithUnread(startCal.time, cal.time, query, isUngrouped && !limit) db.getRecentMangaLimit(
.executeOnIO() query,
} isEndless,
} else db.getRecentMangaLimit( if (isCustom) ENDLESS_LIMIT else historyOffset,
startCal.time, !updatePageCount && !isOnFirstPage
cal.time, )
if (viewType == VIEW_TYPE_ONLY_HISTORY) 200 else 8, }.executeAsBlocking()
query
).executeOnIO()
} else emptyList() } else emptyList()
// Timber.d("set up cReader items: ${cReading.size}")
val rUpdates = when { val rUpdates = when {
viewType == VIEW_TYPE_ONLY_UPDATES -> db.getRecentChapters(startCal.time, calWeek.time, query).executeOnIO().map { viewType == VIEW_TYPE_ONLY_UPDATES -> db.getRecentChapters(
query,
if (isCustom) ENDLESS_LIMIT else updatesOffset,
!updatePageCount && !isOnFirstPage
).executeAsBlocking().map {
MangaChapterHistory(it.manga, it.chapter, HistoryImpl()) MangaChapterHistory(it.manga, it.chapter, HistoryImpl())
} }
viewType != VIEW_TYPE_ONLY_HISTORY -> db.getUpdatedManga(startCal.time, calWeek.time, query, isUngrouped && !limit).executeOnIO() viewType != VIEW_TYPE_ONLY_HISTORY -> db.getUpdatedManga(
query,
isEndless,
if (isCustom) ENDLESS_LIMIT else updatesOffset,
!updatePageCount && !isOnFirstPage
).executeAsBlocking()
else -> emptyList() else -> emptyList()
} }
rUpdates.forEach { rUpdates.forEach {
it.history.last_read = it.chapter.date_fetch it.history.last_read = it.chapter.date_fetch
} }
// Timber.d("set up rUpdates items: ${rUpdates.size}")
val nAdditions = if (viewType < VIEW_TYPE_ONLY_HISTORY) { val nAdditions = if (viewType < VIEW_TYPE_ONLY_HISTORY) {
db.getRecentlyAdded(startCal.time, calDay.time, query, isUngrouped && !limit).executeOnIO() db.getRecentlyAdded(
query,
isEndless,
if (isCustom) ENDLESS_LIMIT else additionsOffset,
!updatePageCount && !isOnFirstPage
).executeAsBlocking()
} else emptyList() } else emptyList()
nAdditions.forEach { nAdditions.forEach {
it.history.last_read = it.manga.date_added it.history.last_read = it.manga.date_added
} }
// Timber.d("set up nAdditons items: ${nAdditions.size}")
Triple(cReading, rUpdates, nAdditions)
}
if (!isCustom &&
(historyOffset + updatesOffset + additionsOffset == 0 || updatePageCount)
) {
additionsOffset += nAdditions.size
historyOffset += cReading.size
updatesOffset += rUpdates.size
}
// Timber.d("loaded items: ")
if (query != oldQuery) return if (query != oldQuery) return
// Timber.d("query matches items: ")
val mangaList = (cReading + rUpdates + nAdditions).sortedByDescending { val mangaList = (cReading + rUpdates + nAdditions).sortedByDescending {
it.history.last_read it.history.last_read
}.distinctBy { }.distinctBy {
if (query.isEmpty() && viewType != VIEW_TYPE_ONLY_HISTORY) it.manga.id else it.chapter.id if (query.isEmpty() && viewType != VIEW_TYPE_ONLY_HISTORY && viewType != VIEW_TYPE_ONLY_UPDATES) it.manga.id else it.chapter.id
}.filter { mch -> }.filter { mch ->
if (updatePageCount && page > 0 && query.isEmpty()) { if (updatePageCount && !isOnFirstPage && query.isEmpty()) {
if (viewType != VIEW_TYPE_ONLY_HISTORY) { if (viewType != VIEW_TYPE_ONLY_HISTORY && viewType != VIEW_TYPE_ONLY_UPDATES) {
recentItems.none { mch.manga.id == it.mch.manga.id } recentItems.none { mch.manga.id == it.mch.manga.id }
} else { } else {
recentItems.none { mch.chapter.id == it.mch.chapter.id } recentItems.none { mch.chapter.id == it.mch.chapter.id }
@ -209,9 +232,11 @@ class RecentsPresenter(
else null else null
else Pair(it, chapter) else Pair(it, chapter)
} }
// Timber.d("setting new items")
val newItems = if (query.isEmpty() && !isUngrouped) { val newItems = if (query.isEmpty() && !isUngrouped) {
val nChaptersItems = val nChaptersItems =
pairs.asSequence().filter { it.first.history.id == null && it.first.chapter.id != null } pairs.asSequence()
.filter { it.first.history.id == null && it.first.chapter.id != null }
.sortedWith { f1, f2 -> .sortedWith { f1, f2 ->
if (abs(f1.second.date_fetch - f2.second.date_fetch) <= if (abs(f1.second.date_fetch - f2.second.date_fetch) <=
TimeUnit.HOURS.toMillis(12) TimeUnit.HOURS.toMillis(12)
@ -249,8 +274,8 @@ class RecentsPresenter(
}.flatten() }.flatten()
} else { } else {
if (viewType == VIEW_TYPE_ONLY_UPDATES) { if (viewType == VIEW_TYPE_ONLY_UPDATES) {
val map = TreeMap<Date, MutableList<Pair<MangaChapterHistory, Chapter>>> { val map =
d1, d2 -> TreeMap<Date, MutableList<Pair<MangaChapterHistory, Chapter>>> { d1, d2 ->
d2 d2
.compareTo(d1) .compareTo(d1)
} }
@ -264,24 +289,41 @@ class RecentsPresenter(
} }
} else pairs.map { RecentMangaItem(it.first, it.second, null) } } else pairs.map { RecentMangaItem(it.first, it.second, null) }
} }
recentItems = if (page == 0 || !updatePageCount) { // Timber.d("setting some items")
if (customViewType == null) {
recentItems = if (isOnFirstPage || !updatePageCount) {
newItems newItems
} else { } else {
recentItems + newItems recentItems + newItems
} }
} else {
heldItems[customViewType] = newItems
}
val newCount = itemCount + newItems.size val newCount = itemCount + newItems.size
val hasNewItems = newItems.isNotEmpty() val hasNewItems = newItems.isNotEmpty()
if (updatePageCount && newCount < 25 && viewType != VIEW_TYPE_GROUP_ALL && query.isEmpty() && !limit) { if (updatePageCount && newCount < 25 && viewType != VIEW_TYPE_GROUP_ALL && query.isEmpty() && !limit) {
page++ // Timber.d("needs to retry. has New items: $hasNewItems, offset: $subUpdatesOffset")
runRecents(oldQuery, true, retryCount + (if (hasNewItems) 0 else 1), newCount) runRecents(oldQuery, true, retryCount + (if (hasNewItems) 0 else 1), newCount)
return return
} }
if (!limit) { if (!limit) {
setDownloadedChapters(recentItems) setDownloadedChapters(recentItems)
if (customViewType == null) {
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
controller?.showLists(recentItems, hasNewItems, shouldMoveToTop) controller?.showLists(recentItems, hasNewItems, shouldMoveToTop)
isLoading = false
shouldMoveToTop = false shouldMoveToTop = false
} }
// scope.launchIO {
// (0..3).map {
// async {
// if (this@RecentsPresenter.viewType != it) {
// runRecents(oldQuery, customViewType = it)
// }
// }
// }.awaitAll()
// }
}
} }
} }
@ -312,7 +354,7 @@ class RecentsPresenter(
preferences.recentsViewType().set(pref) preferences.recentsViewType().set(pref)
} }
viewType = pref viewType = pref
page = 0 resetOffsets()
getRecents() getRecents()
} }
@ -452,8 +494,11 @@ class RecentsPresenter(
} }
fun requestNext() { fun requestNext() {
if (!isLoading) {
isLoading = true
getRecents(true) getRecents(true)
} }
}
companion object { companion object {
private var lastRecents: List<RecentMangaItem>? = null private var lastRecents: List<RecentMangaItem>? = null
@ -462,6 +507,7 @@ class RecentsPresenter(
const val VIEW_TYPE_UNGROUP_ALL = 1 const val VIEW_TYPE_UNGROUP_ALL = 1
const val VIEW_TYPE_ONLY_HISTORY = 2 const val VIEW_TYPE_ONLY_HISTORY = 2
const val VIEW_TYPE_ONLY_UPDATES = 3 const val VIEW_TYPE_ONLY_UPDATES = 3
var ENDLESS_LIMIT = 50
suspend fun getRecentManga(): List<Pair<Manga, Long>> { suspend fun getRecentManga(): List<Pair<Manga, Long>> {
val presenter = RecentsPresenter(null) val presenter = RecentsPresenter(null)