refactor(recents): Fully migrate recents to use SQLDelight

This commit is contained in:
Ahmad Ansori Palembani 2024-08-27 08:16:04 +07:00
parent 79929b395e
commit 354ed7ce8a
Signed by: null2264
GPG key ID: BA64F8B60AF3EFB6
10 changed files with 354 additions and 86 deletions

View file

@ -39,16 +39,18 @@ interface History : Serializable {
this.chapter_id = chapter.id!! this.chapter_id = chapter.id!!
} }
fun create(): History = HistoryImpl()
fun mapper( fun mapper(
id: Long, id: Long,
chapterId: Long, chapterId: Long,
lastRead: Long, lastRead: Long?,
timeRead: Long timeRead: Long?,
) = HistoryImpl().apply { ): History = HistoryImpl().apply {
this.id = id this.id = id
this.chapter_id = chapterId this.chapter_id = chapterId
this.last_read = lastRead this.last_read = lastRead ?: 0L
this.time_read = timeRead this.time_read = timeRead ?: 0L
} }
} }
} }

View file

@ -13,6 +13,100 @@ data class MangaChapterHistory(val manga: Manga, val chapter: Chapter, val histo
companion object { companion object {
fun createBlank() = MangaChapterHistory(MangaImpl(), ChapterImpl(), HistoryImpl()) fun createBlank() = MangaChapterHistory(MangaImpl(), ChapterImpl(), HistoryImpl())
fun mapper(
// manga
mangaId: Long,
source: Long,
mangaUrl: String,
artist: String?,
author: String?,
description: String?,
genre: String?,
title: String,
status: Long,
thumbnailUrl: String?,
favorite: Boolean,
lastUpdate: Long?,
initialized: Boolean,
viewer: Long,
hideTitle: Boolean,
chapterFlags: Long,
dateAdded: Long?,
filteredScanlators: String?,
updateStrategy: Long,
coverLastModified: Long,
// chapter
chapterId: Long?,
_mangaId: Long?,
chapterUrl: String?,
name: String?,
scanlator: String?,
read: Boolean?,
bookmark: Boolean?,
lastPageRead: Long?,
pagesLeft: Long?,
chapterNumber: Double?,
sourceOrder: Long?,
dateFetch: Long?,
dateUpload: Long?,
// history
historyId: Long?,
historyChapterId: Long?,
historyLastRead: Long?,
historyTimeRead: Long?,
) = MangaChapterHistory(
Manga.mapper(
id = mangaId,
source = source,
url = mangaUrl,
artist = artist,
author = author,
description = description,
genre = genre,
title = title,
status = status,
thumbnailUrl = thumbnailUrl,
favorite = favorite,
lastUpdate = lastUpdate,
initialized = initialized,
viewerFlags = viewer,
hideTitle = hideTitle,
chapterFlags = chapterFlags,
dateAdded = dateAdded,
filteredScanlators = filteredScanlators,
updateStrategy = updateStrategy,
coverLastModified = coverLastModified,
),
chapterId?.let {
Chapter.mapper(
id = chapterId,
mangaId = _mangaId!!,
url = chapterUrl!!,
name = name!!,
scanlator = scanlator!!,
read = read!!,
bookmark = bookmark!!,
lastPageRead = lastPageRead!!,
pagesLeft = pagesLeft!!,
chapterNumber = chapterNumber!!,
sourceOrder = sourceOrder!!,
dateFetch = dateFetch!!,
dateUpload = dateUpload!!,
)
} ?: Chapter.create(),
historyId?.let {
History.mapper(
id = historyId,
chapterId = historyChapterId!!,
lastRead = historyLastRead,
timeRead = historyTimeRead,
)
} ?: History.create().apply {
historyLastRead?.let { last_read = it }
historyTimeRead?.let { time_read = it }
},
)
} }
} }

View file

@ -52,22 +52,6 @@ interface HistoryQueries : DbProvider {
.withGetResolver(MangaChapterHistoryGetResolver.INSTANCE) .withGetResolver(MangaChapterHistoryGetResolver.INSTANCE)
.prepare() .prepare()
/**
* Returns history of recent manga containing last read chapter in 25s
* @param date recent date range
* @offset offset the db by
*/
fun getRecentMangaLimit(search: String = "", offset: Int, isResuming: Boolean) = db.get()
.listOfObjects(MangaChapterHistory::class.java)
.withQuery(
RawQuery.builder()
.query(getRecentMangasLimitQuery(search.sqLite, offset, isResuming))
.observesTables(HistoryTable.TABLE)
.build(),
)
.withGetResolver(MangaChapterHistoryGetResolver.INSTANCE)
.prepare()
/** /**
* Returns history of manga read during period * Returns history of manga read during period
* @param startDate start date of the period * @param startDate start date of the period
@ -85,37 +69,6 @@ interface HistoryQueries : DbProvider {
.withGetResolver(MangaChapterHistoryGetResolver.INSTANCE) .withGetResolver(MangaChapterHistoryGetResolver.INSTANCE)
.prepare() .prepare()
/**
* Returns history of recent manga containing last read chapter in 25s
* @param date recent date range
* @offset offset the db by
*/
fun getAllRecentsTypes(
search: String = "",
includeRead: Boolean,
endless: Boolean,
offset: Int,
isResuming: Boolean,
) = db.get()
.listOfObjects(MangaChapterHistory::class.java)
.withQuery(
RawQuery.builder()
.query(
getAllRecentsType(
search.sqLite,
includeRead,
endless,
offset,
isResuming,
),
)
// .args(date.time, startDate.time)
.observesTables(HistoryTable.TABLE)
.build(),
)
.withGetResolver(MangaChapterHistoryGetResolver.INSTANCE)
.prepare()
fun getHistoryByMangaId(mangaId: Long) = db.get() fun getHistoryByMangaId(mangaId: Long) = db.get()
.listOfObjects(History::class.java) .listOfObjects(History::class.java)
.withQuery( .withQuery(

View file

@ -17,7 +17,6 @@ import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.ui.base.presenter.BaseCoroutinePresenter import eu.kanade.tachiyomi.ui.base.presenter.BaseCoroutinePresenter
import eu.kanade.tachiyomi.util.chapter.ChapterFilter import eu.kanade.tachiyomi.util.chapter.ChapterFilter
import eu.kanade.tachiyomi.util.chapter.ChapterSort import eu.kanade.tachiyomi.util.chapter.ChapterSort
import eu.kanade.tachiyomi.util.system.executeOnIO
import eu.kanade.tachiyomi.util.system.launchIO import eu.kanade.tachiyomi.util.system.launchIO
import eu.kanade.tachiyomi.util.system.launchUI import eu.kanade.tachiyomi.util.system.launchUI
import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.toast
@ -41,9 +40,9 @@ import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import yokai.domain.chapter.interactor.GetChapter import yokai.domain.chapter.interactor.GetChapter
import yokai.domain.chapter.interactor.RecentChapter
import yokai.domain.chapter.interactor.UpdateChapter import yokai.domain.chapter.interactor.UpdateChapter
import yokai.domain.recents.RecentsPreferences import yokai.domain.recents.RecentsPreferences
import yokai.domain.recents.interactor.GetRecents
import yokai.domain.ui.UiPreferences import yokai.domain.ui.UiPreferences
import yokai.i18n.MR import yokai.i18n.MR
@ -56,7 +55,7 @@ class RecentsPresenter(
private val chapterFilter: ChapterFilter = Injekt.get(), private val chapterFilter: ChapterFilter = Injekt.get(),
) : BaseCoroutinePresenter<RecentsController>(), DownloadQueue.DownloadListener { ) : BaseCoroutinePresenter<RecentsController>(), DownloadQueue.DownloadListener {
private val getChapter: GetChapter by injectLazy() private val getChapter: GetChapter by injectLazy()
private val recentChapter: RecentChapter by injectLazy() private val getRecents: GetRecents by injectLazy()
private val updateChapter: UpdateChapter by injectLazy() private val updateChapter: UpdateChapter by injectLazy()
private var recentsJob: Job? = null private var recentsJob: Job? = null
@ -172,26 +171,29 @@ class RecentsPresenter(
var extraCount = 0 var extraCount = 0
val cReading: List<MangaChapterHistory> = when (viewType) { val cReading: List<MangaChapterHistory> = when (viewType) {
RecentsViewType.GroupedAll, RecentsViewType.UngroupedAll -> { RecentsViewType.GroupedAll, RecentsViewType.UngroupedAll -> {
db.getAllRecentsTypes( getRecents.awaitAll(
query,
showRead, showRead,
true,
isEndless, isEndless,
if (isCustom) ENDLESS_LIMIT else pageOffset,
!updatePageCount && !isOnFirstPage, !updatePageCount && !isOnFirstPage,
).executeOnIO() query,
(if (isCustom) ENDLESS_LIMIT else pageOffset).toLong(),
)
} }
RecentsViewType.History -> { RecentsViewType.History -> {
val items = if (groupChaptersHistory == GroupType.BySeries) { val items = if (groupChaptersHistory == GroupType.BySeries) {
db.getRecentMangaLimit( getRecents.awaitBySeries(
query, true,
if (isCustom) ENDLESS_LIMIT else pageOffset,
!updatePageCount && !isOnFirstPage, !updatePageCount && !isOnFirstPage,
query,
(if (isCustom) ENDLESS_LIMIT else pageOffset).toLong(),
) )
} else { } else {
db.getHistoryUngrouped( getRecents.awaitUngrouped(
query, true,
if (isCustom) ENDLESS_LIMIT else pageOffset,
!updatePageCount && !isOnFirstPage, !updatePageCount && !isOnFirstPage,
query,
(if (isCustom) ENDLESS_LIMIT else pageOffset).toLong(),
) )
} }
if (groupChaptersHistory.isByTime) { if (groupChaptersHistory.isByTime) {
@ -203,7 +205,7 @@ class RecentsPresenter(
) )
val dayOfWeek = Calendar.getInstance().get(Calendar.DAY_OF_WEEK) % 7 + 1 val dayOfWeek = Calendar.getInstance().get(Calendar.DAY_OF_WEEK) % 7 + 1
dateFormat.calendar.firstDayOfWeek = dayOfWeek dateFormat.calendar.firstDayOfWeek = dayOfWeek
items.executeOnIO().groupBy { items.groupBy {
val date = it.history.last_read val date = it.history.last_read
it.manga.id to if (date <= 0L) "-1" else dateFormat.format(Date(date)) it.manga.id to if (date <= 0L) "-1" else dateFormat.format(Date(date))
} }
@ -235,14 +237,14 @@ class RecentsPresenter(
} }
} }
} else { } else {
items.executeOnIO() items
} }
} }
RecentsViewType.Updates -> { RecentsViewType.Updates -> {
dateFormat.applyPattern("yyyy-MM-dd") dateFormat.applyPattern("yyyy-MM-dd")
dateFormat.calendar.firstDayOfWeek = dateFormat.calendar.firstDayOfWeek =
Calendar.getInstance().get(Calendar.DAY_OF_WEEK) Calendar.getInstance().get(Calendar.DAY_OF_WEEK)
recentChapter.await( getRecents.awaitUpdates(
true, true,
!updatePageCount && !isOnFirstPage, !updatePageCount && !isOnFirstPage,
query, query,

View file

@ -8,6 +8,7 @@ import uy.kohesive.injekt.api.get
import yokai.data.category.CategoryRepositoryImpl import yokai.data.category.CategoryRepositoryImpl
import yokai.data.chapter.ChapterRepositoryImpl import yokai.data.chapter.ChapterRepositoryImpl
import yokai.data.extension.repo.ExtensionRepoRepositoryImpl import yokai.data.extension.repo.ExtensionRepoRepositoryImpl
import yokai.data.history.HistoryRepositoryImpl
import yokai.data.library.custom.CustomMangaRepositoryImpl import yokai.data.library.custom.CustomMangaRepositoryImpl
import yokai.data.manga.MangaRepositoryImpl import yokai.data.manga.MangaRepositoryImpl
import yokai.domain.category.CategoryRepository import yokai.domain.category.CategoryRepository
@ -17,7 +18,7 @@ import yokai.domain.chapter.interactor.DeleteChapter
import yokai.domain.chapter.interactor.GetAvailableScanlators import yokai.domain.chapter.interactor.GetAvailableScanlators
import yokai.domain.chapter.interactor.GetChapter import yokai.domain.chapter.interactor.GetChapter
import yokai.domain.chapter.interactor.InsertChapter import yokai.domain.chapter.interactor.InsertChapter
import yokai.domain.chapter.interactor.RecentChapter import yokai.domain.recents.interactor.GetRecents
import yokai.domain.chapter.interactor.UpdateChapter import yokai.domain.chapter.interactor.UpdateChapter
import yokai.domain.extension.interactor.TrustExtension import yokai.domain.extension.interactor.TrustExtension
import yokai.domain.extension.repo.ExtensionRepoRepository import yokai.domain.extension.repo.ExtensionRepoRepository
@ -27,6 +28,7 @@ import yokai.domain.extension.repo.interactor.GetExtensionRepo
import yokai.domain.extension.repo.interactor.GetExtensionRepoCount import yokai.domain.extension.repo.interactor.GetExtensionRepoCount
import yokai.domain.extension.repo.interactor.ReplaceExtensionRepo import yokai.domain.extension.repo.interactor.ReplaceExtensionRepo
import yokai.domain.extension.repo.interactor.UpdateExtensionRepo import yokai.domain.extension.repo.interactor.UpdateExtensionRepo
import yokai.domain.history.HistoryRepository
import yokai.domain.library.custom.CustomMangaRepository import yokai.domain.library.custom.CustomMangaRepository
import yokai.domain.library.custom.interactor.CreateCustomManga import yokai.domain.library.custom.interactor.CreateCustomManga
import yokai.domain.library.custom.interactor.DeleteCustomManga import yokai.domain.library.custom.interactor.DeleteCustomManga
@ -67,9 +69,12 @@ class DomainModule : InjektModule {
addFactory { GetAvailableScanlators(get()) } addFactory { GetAvailableScanlators(get()) }
addFactory { GetChapter(get()) } addFactory { GetChapter(get()) }
addFactory { InsertChapter(get()) } addFactory { InsertChapter(get()) }
addFactory { RecentChapter(get()) }
addFactory { UpdateChapter(get()) } addFactory { UpdateChapter(get()) }
addSingletonFactory<HistoryRepository> { HistoryRepositoryImpl(get()) }
addFactory { GetRecents(get(), get()) }
addSingletonFactory<CategoryRepository> { CategoryRepositoryImpl(get()) } addSingletonFactory<CategoryRepository> { CategoryRepositoryImpl(get()) }
addFactory { GetCategories(get()) } addFactory { GetCategories(get()) }
} }

View file

@ -0,0 +1,33 @@
package yokai.data.history
import eu.kanade.tachiyomi.data.database.models.MangaChapterHistory
import eu.kanade.tachiyomi.util.system.toInt
import yokai.data.DatabaseHandler
import yokai.domain.history.HistoryRepository
class HistoryRepositoryImpl(private val handler: DatabaseHandler) : HistoryRepository {
override suspend fun getRecentsUngrouped(
filterScanlators: Boolean,
search: String,
limit: Long,
offset: Long,
): List<MangaChapterHistory> =
handler.awaitList { historyQueries.getRecentsUngrouped(search, filterScanlators.toInt().toLong(), limit, offset, MangaChapterHistory::mapper) }
override suspend fun getRecentsBySeries(
filterScanlators: Boolean,
search: String,
limit: Long,
offset: Long,
): List<MangaChapterHistory> =
handler.awaitList { historyQueries.getRecentsBySeries(search, filterScanlators.toInt().toLong(), limit, offset, MangaChapterHistory::mapper) }
override suspend fun getRecentsAll(
includeRead: Boolean,
filterScanlators: Boolean,
search: String,
limit: Long,
offset: Long
): List<MangaChapterHistory> =
handler.awaitList { historyQueries.getRecentsAll(includeRead.toInt().toLong(), search, filterScanlators.toInt().toLong(), limit, offset, MangaChapterHistory::mapper) }
}

View file

@ -1,15 +0,0 @@
package yokai.domain.chapter.interactor
import eu.kanade.tachiyomi.data.database.models.MangaChapter
import yokai.domain.chapter.ChapterRepository
import yokai.util.limitAndOffset
class RecentChapter(
private val chapterRepository: ChapterRepository,
) {
suspend fun await(filterScanlators: Boolean, isResuming: Boolean, search: String = "", offset: Long = 0L): List<MangaChapter> {
val (limit, actualOffset) = limitAndOffset(true, isResuming, offset)
return chapterRepository.getRecents(filterScanlators, search, limit, actualOffset)
}
}

View file

@ -0,0 +1,9 @@
package yokai.domain.history
import eu.kanade.tachiyomi.data.database.models.MangaChapterHistory
interface HistoryRepository {
suspend fun getRecentsUngrouped(filterScanlators: Boolean, search: String = "", limit: Long = 25L, offset: Long = 0L): List<MangaChapterHistory>
suspend fun getRecentsBySeries(filterScanlators: Boolean, search: String = "", limit: Long = 25L, offset: Long = 0L): List<MangaChapterHistory>
suspend fun getRecentsAll(includeRead: Boolean, filterScanlators: Boolean, search: String = "", limit: Long = 25L, offset: Long = 0L): List<MangaChapterHistory>
}

View file

@ -0,0 +1,58 @@
package yokai.domain.recents.interactor
import eu.kanade.tachiyomi.data.database.models.MangaChapter
import eu.kanade.tachiyomi.data.database.models.MangaChapterHistory
import yokai.domain.chapter.ChapterRepository
import yokai.domain.history.HistoryRepository
import yokai.util.limitAndOffset
class GetRecents(
private val chapterRepository: ChapterRepository,
private val historyRepository: HistoryRepository,
) {
suspend fun awaitUpdates(
filterScanlators: Boolean,
isResuming: Boolean,
search: String = "",
offset: Long = 0L,
): List<MangaChapter> {
val (limit, actualOffset) = limitAndOffset(true, isResuming, offset)
return chapterRepository.getRecents(filterScanlators, search, limit, actualOffset)
}
suspend fun awaitUngrouped(
filterScanlators: Boolean,
isResuming: Boolean,
search: String = "",
offset: Long = 0L,
): List<MangaChapterHistory> {
val (limit, actualOffset) = limitAndOffset(true, isResuming, offset)
return historyRepository.getRecentsUngrouped(filterScanlators, search, limit, actualOffset)
}
suspend fun awaitBySeries(
filterScanlators: Boolean,
isResuming: Boolean,
search: String = "",
offset: Long = 0L,
): List<MangaChapterHistory> {
val (limit, actualOffset) = limitAndOffset(true, isResuming, offset)
return historyRepository.getRecentsBySeries(filterScanlators, search, limit, actualOffset)
}
suspend fun awaitAll(
includeRead: Boolean,
filterScanlators: Boolean,
isEndless: Boolean,
isResuming: Boolean,
search: String = "",
offset: Long = 0L,
): List<MangaChapterHistory> {
val (limit, actualOffset) = limitAndOffset(isEndless, isResuming, offset)
return historyRepository.getRecentsAll(includeRead, filterScanlators, search, limit, actualOffset)
}
}

View file

@ -32,7 +32,6 @@ LIMIT :limit OFFSET :offset;
getRecentsBySeries: getRecentsBySeries:
SELECT SELECT
M.url AS mangaUrl,
M.*, M.*,
C.*, C.*,
H.* H.*
@ -62,3 +61,131 @@ AND (
) )
ORDER BY max_last_read.history_last_read DESC ORDER BY max_last_read.history_last_read DESC
LIMIT :limit OFFSET :offset; LIMIT :limit OFFSET :offset;
getRecentsAll: -- AKA insanity
SELECT * FROM (
SELECT
M.*,
C.*,
H.*
FROM (
SELECT mangas.*
FROM mangas
LEFT JOIN (
SELECT manga_id, COUNT(*) AS unread
FROM chapters
WHERE read = 0
GROUP BY manga_id
) AS C
ON _id = C.manga_id
WHERE (
:include_read = 0 OR C.unread > 0
)
GROUP BY _id
ORDER BY title
) AS M
JOIN chapters AS C
ON M._id = C.manga_id
JOIN history AS H
ON C._id = H.history_chapter_id
JOIN (
SELECT
chapters.manga_id AS manga_id,
chapters._id AS history_chapter_id,
MAX(history.history_last_read) AS history_last_read
FROM chapters JOIN history
ON chapters._id = history.history_chapter_id
GROUP BY chapters.manga_id
) AS max_last_read
ON C.manga_id = max_last_read.manga_id
AND max_last_read.history_chapter_id = H.history_chapter_id
AND max_last_read.history_last_read > 0
LEFT JOIN scanlators_view AS S
ON C.manga_id = S.manga_id
AND ifnull(C.scanlator, 'N/A') = ifnull(S.name, '/<INVALID>/') -- I assume if it's N/A it shouldn't be filtered
WHERE lower(M.title) LIKE '%' || :search || '%'
AND (
:apply_filter = 0 OR S.name IS NULL
)
)
UNION --
SELECT * FROM (
SELECT
M.*,
C.*,
NULL AS history_id,
NULL AS history_chapter_id,
C.date_fetch AS history_last_read,
NULL AS history_time_read
FROM (
SELECT mangas.*
FROM mangas
LEFT JOIN (
SELECT manga_id, COUNT(*) AS unread
FROM chapters
WHERE read = 0
GROUP BY manga_id
) AS C2
ON _id = C2.manga_id
WHERE (
:include_read = 0 OR C2.unread > 0
)
GROUP BY _id
ORDER BY title
) AS M
JOIN chapters AS C
ON M._id = C.manga_id
JOIN history AS H
ON C._id = H.history_chapter_id
JOIN (
SELECT
chapters.manga_id,
chapters._id AS history_chapter_id,
max(chapters.date_upload)
FROM chapters JOIN mangas
ON mangas._id = chapters.manga_id
WHERE chapters.read = 0
GROUP BY chapters.manga_id
) AS newest_chapter
LEFT JOIN scanlators_view AS S
ON C.manga_id = S.manga_id
AND ifnull(C.scanlator, 'N/A') = ifnull(S.name, '/<INVALID>/') -- I assume if it's N/A it shouldn't be filtered
WHERE M.favorite = 1
AND newest_chapter.history_chapter_id = H.history_chapter_id
AND C.date_fetch > M.date_added
AND lower(M.title) LIKE '%' || :search || '%'
AND (
:apply_filter = 0 OR S.name IS NULL
)
)
UNION --
SELECT * FROM (
SELECT
M.*,
NULL AS _id,
NULL AS manga_id,
NULL AS url,
NULL AS name,
NULL AS read,
NULL AS scanlator,
NULL AS bookmark,
NULL AS date_fetch,
NULL AS date_upload,
NULL AS last_page_read,
NULL AS pages_left,
NULL AS chapter_number,
NULL AS source_order,
NULL AS history_id,
NULL AS history_chapter_id,
M.date_added AS history_last_read,
NULL AS history_time_read
FROM mangas AS M
WHERE M.favorite = 1
AND lower(M.title) LIKE '%' || :search || '%'
)
ORDER BY history_last_read DESC
LIMIT :limit OFFSET :offset;