From 354ed7ce8adce4c957a37d93c07a978b0fb61f88 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Tue, 27 Aug 2024 08:16:04 +0700 Subject: [PATCH] refactor(recents): Fully migrate recents to use SQLDelight --- .../tachiyomi/data/database/models/History.kt | 12 +- .../database/models/MangaChapterHistory.kt | 94 +++++++++++++ .../data/database/queries/HistoryQueries.kt | 47 ------- .../tachiyomi/ui/recents/RecentsPresenter.kt | 34 ++--- .../main/java/yokai/core/di/DomainModule.kt | 9 +- .../data/history/HistoryRepositoryImpl.kt | 33 +++++ .../chapter/interactor/RecentChapter.kt | 15 -- .../yokai/domain/history/HistoryRepository.kt | 9 ++ .../domain/recents/interactor/GetRecents.kt | 58 ++++++++ .../sqldelight/tachiyomi/data/history.sq | 129 +++++++++++++++++- 10 files changed, 354 insertions(+), 86 deletions(-) create mode 100644 app/src/main/java/yokai/data/history/HistoryRepositoryImpl.kt delete mode 100644 app/src/main/java/yokai/domain/chapter/interactor/RecentChapter.kt create mode 100644 app/src/main/java/yokai/domain/history/HistoryRepository.kt create mode 100644 app/src/main/java/yokai/domain/recents/interactor/GetRecents.kt diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/History.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/History.kt index 5966f54a33..ce2b07f966 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/History.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/History.kt @@ -39,16 +39,18 @@ interface History : Serializable { this.chapter_id = chapter.id!! } + fun create(): History = HistoryImpl() + fun mapper( id: Long, chapterId: Long, - lastRead: Long, - timeRead: Long - ) = HistoryImpl().apply { + lastRead: Long?, + timeRead: Long?, + ): History = HistoryImpl().apply { this.id = id this.chapter_id = chapterId - this.last_read = lastRead - this.time_read = timeRead + this.last_read = lastRead ?: 0L + this.time_read = timeRead ?: 0L } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaChapterHistory.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaChapterHistory.kt index c75332941e..2c5bf6665f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaChapterHistory.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaChapterHistory.kt @@ -13,6 +13,100 @@ data class MangaChapterHistory(val manga: Manga, val chapter: Chapter, val histo companion object { 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 } + }, + ) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/HistoryQueries.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/HistoryQueries.kt index 6f2e679d93..582d6670b7 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/HistoryQueries.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/HistoryQueries.kt @@ -52,22 +52,6 @@ interface HistoryQueries : DbProvider { .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 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 * @param startDate start date of the period @@ -85,37 +69,6 @@ interface HistoryQueries : DbProvider { .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 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() .listOfObjects(History::class.java) .withQuery( diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsPresenter.kt index dbbabd3069..ef8b06dd37 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsPresenter.kt @@ -17,7 +17,6 @@ import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.ui.base.presenter.BaseCoroutinePresenter import eu.kanade.tachiyomi.util.chapter.ChapterFilter 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.launchUI 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.injectLazy import yokai.domain.chapter.interactor.GetChapter -import yokai.domain.chapter.interactor.RecentChapter import yokai.domain.chapter.interactor.UpdateChapter import yokai.domain.recents.RecentsPreferences +import yokai.domain.recents.interactor.GetRecents import yokai.domain.ui.UiPreferences import yokai.i18n.MR @@ -56,7 +55,7 @@ class RecentsPresenter( private val chapterFilter: ChapterFilter = Injekt.get(), ) : BaseCoroutinePresenter(), DownloadQueue.DownloadListener { 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 var recentsJob: Job? = null @@ -172,26 +171,29 @@ class RecentsPresenter( var extraCount = 0 val cReading: List = when (viewType) { RecentsViewType.GroupedAll, RecentsViewType.UngroupedAll -> { - db.getAllRecentsTypes( - query, + getRecents.awaitAll( showRead, + true, isEndless, - if (isCustom) ENDLESS_LIMIT else pageOffset, !updatePageCount && !isOnFirstPage, - ).executeOnIO() + query, + (if (isCustom) ENDLESS_LIMIT else pageOffset).toLong(), + ) } RecentsViewType.History -> { val items = if (groupChaptersHistory == GroupType.BySeries) { - db.getRecentMangaLimit( - query, - if (isCustom) ENDLESS_LIMIT else pageOffset, + getRecents.awaitBySeries( + true, !updatePageCount && !isOnFirstPage, + query, + (if (isCustom) ENDLESS_LIMIT else pageOffset).toLong(), ) } else { - db.getHistoryUngrouped( - query, - if (isCustom) ENDLESS_LIMIT else pageOffset, + getRecents.awaitUngrouped( + true, !updatePageCount && !isOnFirstPage, + query, + (if (isCustom) ENDLESS_LIMIT else pageOffset).toLong(), ) } if (groupChaptersHistory.isByTime) { @@ -203,7 +205,7 @@ class RecentsPresenter( ) val dayOfWeek = Calendar.getInstance().get(Calendar.DAY_OF_WEEK) % 7 + 1 dateFormat.calendar.firstDayOfWeek = dayOfWeek - items.executeOnIO().groupBy { + items.groupBy { val date = it.history.last_read it.manga.id to if (date <= 0L) "-1" else dateFormat.format(Date(date)) } @@ -235,14 +237,14 @@ class RecentsPresenter( } } } else { - items.executeOnIO() + items } } RecentsViewType.Updates -> { dateFormat.applyPattern("yyyy-MM-dd") dateFormat.calendar.firstDayOfWeek = Calendar.getInstance().get(Calendar.DAY_OF_WEEK) - recentChapter.await( + getRecents.awaitUpdates( true, !updatePageCount && !isOnFirstPage, query, diff --git a/app/src/main/java/yokai/core/di/DomainModule.kt b/app/src/main/java/yokai/core/di/DomainModule.kt index 3feda772a9..cc9244af8a 100644 --- a/app/src/main/java/yokai/core/di/DomainModule.kt +++ b/app/src/main/java/yokai/core/di/DomainModule.kt @@ -8,6 +8,7 @@ import uy.kohesive.injekt.api.get import yokai.data.category.CategoryRepositoryImpl import yokai.data.chapter.ChapterRepositoryImpl import yokai.data.extension.repo.ExtensionRepoRepositoryImpl +import yokai.data.history.HistoryRepositoryImpl import yokai.data.library.custom.CustomMangaRepositoryImpl import yokai.data.manga.MangaRepositoryImpl 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.GetChapter 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.extension.interactor.TrustExtension 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.ReplaceExtensionRepo import yokai.domain.extension.repo.interactor.UpdateExtensionRepo +import yokai.domain.history.HistoryRepository import yokai.domain.library.custom.CustomMangaRepository import yokai.domain.library.custom.interactor.CreateCustomManga import yokai.domain.library.custom.interactor.DeleteCustomManga @@ -67,9 +69,12 @@ class DomainModule : InjektModule { addFactory { GetAvailableScanlators(get()) } addFactory { GetChapter(get()) } addFactory { InsertChapter(get()) } - addFactory { RecentChapter(get()) } addFactory { UpdateChapter(get()) } + addSingletonFactory { HistoryRepositoryImpl(get()) } + + addFactory { GetRecents(get(), get()) } + addSingletonFactory { CategoryRepositoryImpl(get()) } addFactory { GetCategories(get()) } } diff --git a/app/src/main/java/yokai/data/history/HistoryRepositoryImpl.kt b/app/src/main/java/yokai/data/history/HistoryRepositoryImpl.kt new file mode 100644 index 0000000000..dbc25b3a14 --- /dev/null +++ b/app/src/main/java/yokai/data/history/HistoryRepositoryImpl.kt @@ -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 = + 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 = + 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 = + handler.awaitList { historyQueries.getRecentsAll(includeRead.toInt().toLong(), search, filterScanlators.toInt().toLong(), limit, offset, MangaChapterHistory::mapper) } +} diff --git a/app/src/main/java/yokai/domain/chapter/interactor/RecentChapter.kt b/app/src/main/java/yokai/domain/chapter/interactor/RecentChapter.kt deleted file mode 100644 index 3e922fbb12..0000000000 --- a/app/src/main/java/yokai/domain/chapter/interactor/RecentChapter.kt +++ /dev/null @@ -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 { - val (limit, actualOffset) = limitAndOffset(true, isResuming, offset) - - return chapterRepository.getRecents(filterScanlators, search, limit, actualOffset) - } -} diff --git a/app/src/main/java/yokai/domain/history/HistoryRepository.kt b/app/src/main/java/yokai/domain/history/HistoryRepository.kt new file mode 100644 index 0000000000..b6bef3a656 --- /dev/null +++ b/app/src/main/java/yokai/domain/history/HistoryRepository.kt @@ -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 + suspend fun getRecentsBySeries(filterScanlators: Boolean, search: String = "", limit: Long = 25L, offset: Long = 0L): List + suspend fun getRecentsAll(includeRead: Boolean, filterScanlators: Boolean, search: String = "", limit: Long = 25L, offset: Long = 0L): List +} diff --git a/app/src/main/java/yokai/domain/recents/interactor/GetRecents.kt b/app/src/main/java/yokai/domain/recents/interactor/GetRecents.kt new file mode 100644 index 0000000000..9cf8c41fe6 --- /dev/null +++ b/app/src/main/java/yokai/domain/recents/interactor/GetRecents.kt @@ -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 { + 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 { + 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 { + 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 { + val (limit, actualOffset) = limitAndOffset(isEndless, isResuming, offset) + + return historyRepository.getRecentsAll(includeRead, filterScanlators, search, limit, actualOffset) + } +} diff --git a/data/src/commonMain/sqldelight/tachiyomi/data/history.sq b/data/src/commonMain/sqldelight/tachiyomi/data/history.sq index 8556216296..a1d037d908 100644 --- a/data/src/commonMain/sqldelight/tachiyomi/data/history.sq +++ b/data/src/commonMain/sqldelight/tachiyomi/data/history.sq @@ -32,7 +32,6 @@ LIMIT :limit OFFSET :offset; getRecentsBySeries: SELECT - M.url AS mangaUrl, M.*, C.*, H.* @@ -62,3 +61,131 @@ AND ( ) ORDER BY max_last_read.history_last_read DESC 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, '//') -- 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, '//') -- 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;