refactor(chapter): Migrate more queries to SQLDelight

This commit is contained in:
Ahmad Ansori Palembani 2024-08-17 07:59:39 +07:00
parent fac21dbab7
commit 1e68e55cf7
Signed by: null2264
GPG key ID: BA64F8B60AF3EFB6
14 changed files with 160 additions and 95 deletions

View file

@ -10,12 +10,14 @@ import eu.kanade.tachiyomi.data.library.CustomMangaManager
import eu.kanade.tachiyomi.domain.manga.models.Manga import eu.kanade.tachiyomi.domain.manga.models.Manga
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import yokai.domain.chapter.interactor.GetChapter
class MangaBackupCreator( class MangaBackupCreator(
private val db: DatabaseHelper = Injekt.get(), private val db: DatabaseHelper = Injekt.get(),
private val customMangaManager: CustomMangaManager = Injekt.get(), private val customMangaManager: CustomMangaManager = Injekt.get(),
private val getChapter: GetChapter = Injekt.get(),
) { ) {
operator fun invoke(mangas: List<Manga>, options: BackupOptions): List<BackupManga> { suspend operator fun invoke(mangas: List<Manga>, options: BackupOptions): List<BackupManga> {
if (!options.libraryEntries) return emptyList() if (!options.libraryEntries) return emptyList()
return mangas.map { return mangas.map {
@ -30,14 +32,14 @@ class MangaBackupCreator(
* @param options options for the backup * @param options options for the backup
* @return [BackupManga] containing manga in a serializable form * @return [BackupManga] containing manga in a serializable form
*/ */
private fun backupManga(manga: Manga, options: BackupOptions): BackupManga { private suspend fun backupManga(manga: Manga, options: BackupOptions): BackupManga {
// Entry for this manga // Entry for this manga
val mangaObject = BackupManga.copyFrom(manga, if (options.customInfo) customMangaManager else null) val mangaObject = BackupManga.copyFrom(manga, if (options.customInfo) customMangaManager else null)
// Check if user wants chapter information in backup // Check if user wants chapter information in backup
if (options.chapters) { if (options.chapters) {
// Backup all the chapters // Backup all the chapters
val chapters = db.getChapters(manga).executeAsBlocking() val chapters = manga.id?.let { getChapter.awaitAll(it, false) }.orEmpty()
if (chapters.isNotEmpty()) { if (chapters.isNotEmpty()) {
mangaObject.chapters = chapters.map { BackupChapter.copyFrom(it) } mangaObject.chapters = chapters.map { BackupChapter.copyFrom(it) }
} }
@ -65,7 +67,7 @@ class MangaBackupCreator(
val historyForManga = db.getHistoryByMangaId(manga.id!!).executeAsBlocking() val historyForManga = db.getHistoryByMangaId(manga.id!!).executeAsBlocking()
if (historyForManga.isNotEmpty()) { if (historyForManga.isNotEmpty()) {
val history = historyForManga.mapNotNull { history -> val history = historyForManga.mapNotNull { history ->
val url = db.getChapter(history.chapter_id).executeAsBlocking()?.url val url = getChapter.awaitById(history.chapter_id)?.url
url?.let { BackupHistory(url, history.last_read, history.time_read) } url?.let { BackupHistory(url, history.last_read, history.time_read) }
} }
if (history.isNotEmpty()) { if (history.isNotEmpty()) {

View file

@ -21,6 +21,7 @@ import uy.kohesive.injekt.api.get
import yokai.domain.category.interactor.GetCategories import yokai.domain.category.interactor.GetCategories
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.UpdateChapter
import yokai.domain.library.custom.model.CustomMangaInfo import yokai.domain.library.custom.model.CustomMangaInfo
import yokai.domain.manga.interactor.GetManga import yokai.domain.manga.interactor.GetManga
import yokai.domain.manga.interactor.InsertManga import yokai.domain.manga.interactor.InsertManga
@ -32,6 +33,7 @@ class MangaBackupRestorer(
private val getCategories: GetCategories = Injekt.get(), private val getCategories: GetCategories = Injekt.get(),
private val getChapter: GetChapter = Injekt.get(), private val getChapter: GetChapter = Injekt.get(),
private val insertChapter: InsertChapter = Injekt.get(), private val insertChapter: InsertChapter = Injekt.get(),
private val updateChapter: UpdateChapter = Injekt.get(),
private val getManga: GetManga = Injekt.get(), private val getManga: GetManga = Injekt.get(),
private val insertManga: InsertManga = Injekt.get(), private val insertManga: InsertManga = Injekt.get(),
private val updateManga: UpdateManga = Injekt.get(), private val updateManga: UpdateManga = Injekt.get(),
@ -138,7 +140,7 @@ class MangaBackupRestorer(
} }
val newChapters = chapters.groupBy { it.id != null } val newChapters = chapters.groupBy { it.id != null }
newChapters[true]?.let { db.updateKnownChaptersBackup(it).executeAsBlocking() } newChapters[true]?.let { updateChapter.awaitAll(it.map(Chapter::toProgressUpdate)) }
newChapters[false]?.let { insertChapter.awaitBulk(it) } newChapters[false]?.let { insertChapter.awaitBulk(it) }
} }
@ -210,7 +212,7 @@ class MangaBackupRestorer(
historyToBeUpdated.add(dbHistory) historyToBeUpdated.add(dbHistory)
} else { } else {
// If not in database create // If not in database create
db.getChapter(url).executeAsBlocking()?.let { getChapter.awaitByUrl(url, false)?.let {
val historyToAdd = History.create(it).apply { val historyToAdd = History.create(it).apply {
last_read = lastRead last_read = lastRead
time_read = readDuration time_read = readDuration

View file

@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.data.database.models
import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SChapter
import java.io.Serializable import java.io.Serializable
import yokai.domain.chapter.models.ChapterUpdate
interface Chapter : SChapter, Serializable { interface Chapter : SChapter, Serializable {
@ -24,6 +25,15 @@ interface Chapter : SChapter, Serializable {
val isRecognizedNumber: Boolean val isRecognizedNumber: Boolean
get() = chapter_number >= 0f get() = chapter_number >= 0f
fun toProgressUpdate() =
ChapterUpdate(
id = this.id!!,
read = this.read,
bookmark = this.bookmark,
lastPageRead = this.last_page_read.toLong(),
pagesLeft = this.pages_left.toLong(),
)
companion object { companion object {
fun create(): Chapter = ChapterImpl().apply { fun create(): Chapter = ChapterImpl().apply {

View file

@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.data.database.queries
import com.pushtorefresh.storio.sqlite.queries.Query import com.pushtorefresh.storio.sqlite.queries.Query
import eu.kanade.tachiyomi.data.database.DbProvider import eu.kanade.tachiyomi.data.database.DbProvider
import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.resolvers.ChapterKnownBackupPutResolver
import eu.kanade.tachiyomi.data.database.resolvers.ChapterProgressPutResolver import eu.kanade.tachiyomi.data.database.resolvers.ChapterProgressPutResolver
import eu.kanade.tachiyomi.data.database.tables.ChapterTable import eu.kanade.tachiyomi.data.database.tables.ChapterTable
import eu.kanade.tachiyomi.domain.manga.models.Manga import eu.kanade.tachiyomi.domain.manga.models.Manga
@ -23,6 +22,7 @@ interface ChapterQueries : DbProvider {
) )
.prepare() .prepare()
// FIXME: Migrate to SQLDelight, on halt: in StorIO transaction
fun getChapter(id: Long) = db.get() fun getChapter(id: Long) = db.get()
.`object`(Chapter::class.java) .`object`(Chapter::class.java)
.withQuery( .withQuery(
@ -34,54 +34,12 @@ interface ChapterQueries : DbProvider {
) )
.prepare() .prepare()
fun getChapter(url: String) = db.get()
.`object`(Chapter::class.java)
.withQuery(
Query.builder()
.table(ChapterTable.TABLE)
.where("${ChapterTable.COL_URL} = ?")
.whereArgs(url)
.build(),
)
.prepare()
fun getChapters(url: String) = db.get()
.listOfObjects(Chapter::class.java)
.withQuery(
Query.builder()
.table(ChapterTable.TABLE)
.where("${ChapterTable.COL_URL} = ?")
.whereArgs(url)
.build(),
)
.prepare()
fun getChapter(url: String, mangaId: Long) = db.get()
.`object`(Chapter::class.java)
.withQuery(
Query.builder()
.table(ChapterTable.TABLE)
.where("${ChapterTable.COL_URL} = ? AND ${ChapterTable.COL_MANGA_ID} = ?")
.whereArgs(url, mangaId)
.build(),
)
.prepare()
// FIXME: Migrate to SQLDelight, on halt: in StorIO transaction // FIXME: Migrate to SQLDelight, on halt: in StorIO transaction
fun insertChapters(chapters: List<Chapter>) = db.put().objects(chapters).prepare() fun insertChapters(chapters: List<Chapter>) = db.put().objects(chapters).prepare()
fun updateKnownChaptersBackup(chapters: List<Chapter>) = db.put() // FIXME: Migrate to SQLDelight, on halt: in StorIO transaction
.objects(chapters)
.withPutResolver(ChapterKnownBackupPutResolver())
.prepare()
fun updateChapterProgress(chapter: Chapter) = db.put() fun updateChapterProgress(chapter: Chapter) = db.put()
.`object`(chapter) .`object`(chapter)
.withPutResolver(ChapterProgressPutResolver()) .withPutResolver(ChapterProgressPutResolver())
.prepare() .prepare()
fun updateChaptersProgress(chapters: List<Chapter>) = db.put()
.objects(chapters)
.withPutResolver(ChapterProgressPutResolver())
.prepare()
} }

View file

@ -2,7 +2,6 @@ package eu.kanade.tachiyomi.data.download
import android.content.Context import android.content.Context
import androidx.core.content.edit import androidx.core.content.edit
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.domain.manga.models.Manga import eu.kanade.tachiyomi.domain.manga.models.Manga
import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
@ -11,6 +10,8 @@ import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import yokai.domain.chapter.interactor.GetChapter
import yokai.domain.manga.interactor.GetManga
/** /**
* This class is used to persist active downloads across application restarts. * This class is used to persist active downloads across application restarts.
@ -28,7 +29,8 @@ class DownloadStore(
private val preferences = context.getSharedPreferences("active_downloads", Context.MODE_PRIVATE) private val preferences = context.getSharedPreferences("active_downloads", Context.MODE_PRIVATE)
private val json: Json by injectLazy() private val json: Json by injectLazy()
private val db: DatabaseHelper by injectLazy() private val getChapter: GetChapter by injectLazy()
private val getManga: GetManga by injectLazy()
/** /**
* Counter used to keep the queue order. * Counter used to keep the queue order.
@ -78,7 +80,7 @@ class DownloadStore(
/** /**
* Returns the list of downloads to restore. It should be called in a background thread. * Returns the list of downloads to restore. It should be called in a background thread.
*/ */
fun restore(): List<Download> { suspend fun restore(): List<Download> {
val objs = preferences.all val objs = preferences.all
.mapNotNull { it.value as? String } .mapNotNull { it.value as? String }
.mapNotNull { deserialize(it) } .mapNotNull { deserialize(it) }
@ -89,10 +91,10 @@ class DownloadStore(
val cachedManga = mutableMapOf<Long, Manga?>() val cachedManga = mutableMapOf<Long, Manga?>()
for ((mangaId, chapterId) in objs) { for ((mangaId, chapterId) in objs) {
val manga = cachedManga.getOrPut(mangaId) { val manga = cachedManga.getOrPut(mangaId) {
db.getManga(mangaId).executeAsBlocking() getManga.awaitById(mangaId)
} ?: continue } ?: continue
val source = sourceManager.get(manga.source) as? HttpSource ?: continue val source = sourceManager.get(manga.source) as? HttpSource ?: continue
val chapter = db.getChapter(chapterId).executeAsBlocking() ?: continue val chapter = getChapter.awaitById(chapterId) ?: continue
downloads.add(Download(source, manga, chapter)) downloads.add(Download(source, manga, chapter))
} }
} }

View file

@ -8,7 +8,6 @@ import android.content.Intent
import android.net.Uri import android.net.Uri
import androidx.core.content.IntentCompat import androidx.core.content.IntentCompat
import eu.kanade.tachiyomi.data.backup.restore.BackupRestoreJob import eu.kanade.tachiyomi.data.backup.restore.BackupRestoreJob
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.download.DownloadJob import eu.kanade.tachiyomi.data.download.DownloadJob
import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.download.DownloadManager
@ -28,11 +27,15 @@ import eu.kanade.tachiyomi.util.chapter.updateTrackChapterMarkedAsRead
import eu.kanade.tachiyomi.util.storage.DiskUtil import eu.kanade.tachiyomi.util.storage.DiskUtil
import eu.kanade.tachiyomi.util.storage.getUriCompat import eu.kanade.tachiyomi.util.storage.getUriCompat
import eu.kanade.tachiyomi.util.system.getParcelableCompat import eu.kanade.tachiyomi.util.system.getParcelableCompat
import eu.kanade.tachiyomi.util.system.launchIO
import eu.kanade.tachiyomi.util.system.notificationManager import eu.kanade.tachiyomi.util.system.notificationManager
import java.io.File
import uy.kohesive.injekt.Injekt 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 java.io.File import yokai.domain.chapter.interactor.GetChapter
import yokai.domain.chapter.interactor.UpdateChapter
import yokai.domain.manga.interactor.GetManga
import eu.kanade.tachiyomi.BuildConfig.APPLICATION_ID as ID import eu.kanade.tachiyomi.BuildConfig.APPLICATION_ID as ID
/** /**
@ -41,6 +44,10 @@ import eu.kanade.tachiyomi.BuildConfig.APPLICATION_ID as ID
* NOTE: Use local broadcasts if possible. * NOTE: Use local broadcasts if possible.
*/ */
class NotificationReceiver : BroadcastReceiver() { class NotificationReceiver : BroadcastReceiver() {
private val getChapter: GetChapter by injectLazy()
private val updateChapter: UpdateChapter by injectLazy()
private val getManga: GetManga by injectLazy()
/** /**
* Download manager. * Download manager.
*/ */
@ -204,23 +211,25 @@ class NotificationReceiver : BroadcastReceiver() {
* @param notificationId id of notification * @param notificationId id of notification
*/ */
private fun markAsRead(chapterUrls: Array<String>, mangaId: Long) { private fun markAsRead(chapterUrls: Array<String>, mangaId: Long) {
val db: DatabaseHelper = Injekt.get()
val preferences: PreferencesHelper = Injekt.get() val preferences: PreferencesHelper = Injekt.get()
val manga = db.getManga(mangaId).executeAsBlocking() ?: return
val chapters = chapterUrls.map { launchIO {
val chapter = db.getChapter(it, mangaId).executeAsBlocking() ?: return val manga = getManga.awaitById(mangaId) ?: return@launchIO
chapter.read = true val chapters = chapterUrls.map {
db.updateChapterProgress(chapter).executeAsBlocking() val chapter = getChapter.awaitByUrlAndMangaId(it, mangaId, false) ?: return@launchIO
if (preferences.removeAfterMarkedAsRead().get()) { chapter.read = true
val sourceManager: SourceManager = Injekt.get() updateChapter.await(chapter.toProgressUpdate())
val source = sourceManager.get(manga.source) ?: return if (preferences.removeAfterMarkedAsRead().get()) {
downloadManager.deleteChapters(listOf(chapter), manga, source) val sourceManager: SourceManager = Injekt.get()
val source = sourceManager.get(manga.source) ?: return@launchIO
downloadManager.deleteChapters(listOf(chapter), manga, source)
}
return@map chapter
} }
return@map chapter val newLastChapter = chapters.maxByOrNull { it.chapter_number.toInt() }
LibraryUpdateJob.updateMutableFlow.tryEmit(manga.id)
updateTrackChapterMarkedAsRead(Injekt.get(), preferences, newLastChapter, mangaId, 0)
} }
val newLastChapter = chapters.maxByOrNull { it.chapter_number.toInt() }
LibraryUpdateJob.updateMutableFlow.tryEmit(manga.id)
updateTrackChapterMarkedAsRead(db, preferences, newLastChapter, mangaId, 0)
} }
/** Method called when user wants to stop a restore /** Method called when user wants to stop a restore

View file

@ -64,6 +64,11 @@ import eu.kanade.tachiyomi.util.system.launchUI
import eu.kanade.tachiyomi.util.system.withIOContext import eu.kanade.tachiyomi.util.system.withIOContext
import eu.kanade.tachiyomi.util.system.withUIContext import eu.kanade.tachiyomi.util.system.withUIContext
import eu.kanade.tachiyomi.widget.TriStateCheckBox import eu.kanade.tachiyomi.widget.TriStateCheckBox
import java.io.File
import java.io.FileOutputStream
import java.io.OutputStream
import java.util.Date
import java.util.Locale
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll import kotlinx.coroutines.awaitAll
@ -78,16 +83,13 @@ import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
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.UpdateChapter
import yokai.domain.library.custom.model.CustomMangaInfo import yokai.domain.library.custom.model.CustomMangaInfo
import yokai.domain.manga.interactor.UpdateManga import yokai.domain.manga.interactor.UpdateManga
import yokai.domain.manga.models.MangaUpdate import yokai.domain.manga.models.MangaUpdate
import yokai.domain.storage.StorageManager import yokai.domain.storage.StorageManager
import yokai.i18n.MR import yokai.i18n.MR
import yokai.util.lang.getString import yokai.util.lang.getString
import java.io.File
import java.io.FileOutputStream
import java.io.OutputStream
import java.util.*
class MangaDetailsPresenter( class MangaDetailsPresenter(
val manga: Manga, val manga: Manga,
@ -101,6 +103,7 @@ class MangaDetailsPresenter(
) : BaseCoroutinePresenter<MangaDetailsController>(), DownloadQueue.DownloadListener { ) : BaseCoroutinePresenter<MangaDetailsController>(), DownloadQueue.DownloadListener {
private val getAvailableScanlators: GetAvailableScanlators by injectLazy() private val getAvailableScanlators: GetAvailableScanlators by injectLazy()
private val getChapter: GetChapter by injectLazy() private val getChapter: GetChapter by injectLazy()
private val updateChapter: UpdateChapter by injectLazy()
private val updateManga: UpdateManga by injectLazy() private val updateManga: UpdateManga by injectLazy()
private val customMangaManager: CustomMangaManager by injectLazy() private val customMangaManager: CustomMangaManager by injectLazy()
@ -508,10 +511,11 @@ class MangaDetailsPresenter(
*/ */
fun bookmarkChapters(selectedChapters: List<ChapterItem>, bookmarked: Boolean) { fun bookmarkChapters(selectedChapters: List<ChapterItem>, bookmarked: Boolean) {
presenterScope.launch(Dispatchers.IO) { presenterScope.launch(Dispatchers.IO) {
selectedChapters.forEach { val updates = selectedChapters.map {
it.bookmark = bookmarked it.bookmark = bookmarked
it.toProgressUpdate()
} }
db.updateChaptersProgress(selectedChapters).executeAsBlocking() updateChapter.awaitAll(updates)
getChapters() getChapters()
withContext(Dispatchers.Main) { view?.updateChapters(chapters) } withContext(Dispatchers.Main) { view?.updateChapters(chapters) }
} }
@ -529,15 +533,16 @@ class MangaDetailsPresenter(
lastRead: Int? = null, lastRead: Int? = null,
pagesLeft: Int? = null, pagesLeft: Int? = null,
) { ) {
presenterScope.launch(Dispatchers.IO) { presenterScope.launchIO {
selectedChapters.forEach { val updates = selectedChapters.map {
it.read = read it.read = read
if (!read) { if (!read) {
it.last_page_read = lastRead ?: 0 it.last_page_read = lastRead ?: 0
it.pages_left = pagesLeft ?: 0 it.pages_left = pagesLeft ?: 0
} }
it.toProgressUpdate()
} }
db.updateChaptersProgress(selectedChapters).executeAsBlocking() updateChapter.awaitAll(updates)
if (read && deleteNow && preferences.removeAfterMarkedAsRead().get()) { if (read && deleteNow && preferences.removeAfterMarkedAsRead().get()) {
deleteChapters(selectedChapters, false) deleteChapters(selectedChapters, false)
} }

View file

@ -78,6 +78,8 @@ 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.InsertChapter import yokai.domain.chapter.interactor.InsertChapter
import yokai.domain.chapter.interactor.UpdateChapter
import yokai.domain.chapter.models.ChapterUpdate
import yokai.domain.download.DownloadPreferences import yokai.domain.download.DownloadPreferences
import yokai.domain.manga.interactor.GetManga import yokai.domain.manga.interactor.GetManga
import yokai.domain.manga.interactor.InsertManga import yokai.domain.manga.interactor.InsertManga
@ -103,6 +105,7 @@ class ReaderViewModel(
) : ViewModel() { ) : ViewModel() {
private val getChapter: GetChapter by injectLazy() private val getChapter: GetChapter by injectLazy()
private val insertChapter: InsertChapter by injectLazy() private val insertChapter: InsertChapter by injectLazy()
private val updateChapter: UpdateChapter by injectLazy()
private val getManga: GetManga by injectLazy() private val getManga: GetManga by injectLazy()
private val insertManga: InsertManga by injectLazy() private val insertManga: InsertManga by injectLazy()
private val updateManga: UpdateManga by injectLazy() private val updateManga: UpdateManga by injectLazy()
@ -444,7 +447,16 @@ class ReaderViewModel(
fun toggleBookmark(chapter: Chapter) { fun toggleBookmark(chapter: Chapter) {
chapter.bookmark = !chapter.bookmark chapter.bookmark = !chapter.bookmark
db.updateChapterProgress(chapter).executeAsBlocking() viewModelScope.launchNonCancellableIO {
updateChapter.await(
ChapterUpdate(
id = chapter.id!!,
bookmark = chapter.bookmark,
lastPageRead = chapter.last_page_read.toLong(),
pagesLeft = chapter.pages_left.toLong(),
)
)
}
} }
/** /**
@ -617,6 +629,7 @@ class ReaderViewModel(
* Saves this [readerChapter]'s progress (last read page and whether it's read). * Saves this [readerChapter]'s progress (last read page and whether it's read).
* If incognito mode isn't on or has at least 1 tracker * If incognito mode isn't on or has at least 1 tracker
*/ */
// FIXME: Migrate to SQLDelight, on halt: in StorIO transaction
private fun saveChapterProgress(readerChapter: ReaderChapter) { private fun saveChapterProgress(readerChapter: ReaderChapter) {
readerChapter.requestedPage = readerChapter.chapter.last_page_read readerChapter.requestedPage = readerChapter.chapter.last_page_read
db.getChapter(readerChapter.chapter.id!!).executeAsBlocking()?.let { dbChapter -> db.getChapter(readerChapter.chapter.id!!).executeAsBlocking()?.let { dbChapter ->
@ -630,6 +643,7 @@ class ReaderViewModel(
/** /**
* Saves this [readerChapter] last read history. * Saves this [readerChapter] last read history.
*/ */
// FIXME: Migrate to SQLDelight, on halt: in StorIO transaction
private fun saveChapterHistory(readerChapter: ReaderChapter) { private fun saveChapterHistory(readerChapter: ReaderChapter) {
if (!preferences.incognitoMode().get()) { if (!preferences.incognitoMode().get()) {
val readAt = Date().time val readAt = Date().time

View file

@ -42,6 +42,7 @@ 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.RecentChapter
import yokai.domain.chapter.interactor.UpdateChapter
import yokai.domain.recents.RecentsPreferences import yokai.domain.recents.RecentsPreferences
import yokai.domain.ui.UiPreferences import yokai.domain.ui.UiPreferences
import yokai.i18n.MR import yokai.i18n.MR
@ -56,6 +57,7 @@ class RecentsPresenter(
) : 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 recentChapter: RecentChapter by injectLazy()
private val updateChapter: UpdateChapter by injectLazy()
private var recentsJob: Job? = null private var recentsJob: Job? = null
var recentItems = listOf<RecentMangaItem>() var recentItems = listOf<RecentMangaItem>()
@ -639,7 +641,7 @@ class RecentsPresenter(
pages_left = pagesLeft ?: 0 pages_left = pagesLeft ?: 0
} }
} }
db.updateChaptersProgress(listOf(chapter)).executeAsBlocking() updateChapter.await(chapter.toProgressUpdate())
getRecents() getRecents()
} }
} }

View file

@ -16,6 +16,7 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import yokai.domain.chapter.interactor.UpdateChapter
/** /**
* Helper method for syncing a remote track with the local chapters, and back * Helper method for syncing a remote track with the local chapters, and back
@ -25,20 +26,26 @@ import uy.kohesive.injekt.api.get
* @param remoteTrack the remote Track object. * @param remoteTrack the remote Track object.
* @param service the tracker service. * @param service the tracker service.
*/ */
fun syncChaptersWithTrackServiceTwoWay(db: DatabaseHelper, chapters: List<Chapter>, remoteTrack: Track, service: TrackService) { fun syncChaptersWithTrackServiceTwoWay(
val sortedChapters = chapters.sortedBy { it.chapter_number } db: DatabaseHelper,
sortedChapters chapters: List<Chapter>,
.filter { chapter -> chapter.chapter_number <= remoteTrack.last_chapter_read && !chapter.read } remoteTrack: Track,
.forEach { it.read = true } service: TrackService,
db.updateChaptersProgress(sortedChapters).executeAsBlocking() updateChapter: UpdateChapter = Injekt.get(),
) {
// only take into account continuous reading
val localLastRead = sortedChapters.takeWhile { it.read }.lastOrNull()?.chapter_number ?: 0F
// update remote
remoteTrack.last_chapter_read = localLastRead
launchIO { launchIO {
val sortedChapters = chapters.sortedBy { it.chapter_number }
sortedChapters
.filter { chapter -> chapter.chapter_number <= remoteTrack.last_chapter_read && !chapter.read }
.forEach { it.read = true }
updateChapter.awaitAll(sortedChapters.map(Chapter::toProgressUpdate))
// only take into account continuous reading
val localLastRead = sortedChapters.takeWhile { it.read }.lastOrNull()?.chapter_number ?: 0F
// update remote
remoteTrack.last_chapter_read = localLastRead
try { try {
service.update(remoteTrack) service.update(remoteTrack)
db.insertTrack(remoteTrack).executeAsBlocking() db.insertTrack(remoteTrack).executeAsBlocking()

View file

@ -16,9 +16,33 @@ class ChapterRepositoryImpl(private val handler: DatabaseHandler) : ChapterRepos
override fun getChaptersAsFlow(mangaId: Long, filterScanlators: Boolean): Flow<List<Chapter>> = override fun getChaptersAsFlow(mangaId: Long, filterScanlators: Boolean): Flow<List<Chapter>> =
handler.subscribeToList { chaptersQueries.getChaptersByMangaId(mangaId, filterScanlators.toInt().toLong(), Chapter::mapper) } handler.subscribeToList { chaptersQueries.getChaptersByMangaId(mangaId, filterScanlators.toInt().toLong(), Chapter::mapper) }
override suspend fun getChapterById(id: Long): Chapter? =
handler.awaitOneOrNull { chaptersQueries.getChaptersById(id, Chapter::mapper) }
override suspend fun getChaptersByUrl(url: String, filterScanlators: Boolean): List<Chapter> = override suspend fun getChaptersByUrl(url: String, filterScanlators: Boolean): List<Chapter> =
handler.awaitList { chaptersQueries.getChaptersByUrl(url, filterScanlators.toInt().toLong(), Chapter::mapper) } handler.awaitList { chaptersQueries.getChaptersByUrl(url, filterScanlators.toInt().toLong(), Chapter::mapper) }
override suspend fun getChapterByUrl(url: String, filterScanlators: Boolean): Chapter? =
handler.awaitOneOrNull { chaptersQueries.getChaptersByUrl(url, filterScanlators.toInt().toLong(), Chapter::mapper) }
override suspend fun getChaptersByUrlAndMangaId(
url: String,
mangaId: Long,
filterScanlators: Boolean
): List<Chapter> =
handler.awaitList {
chaptersQueries.getChaptersByUrlAndMangaId(url, mangaId, filterScanlators.toInt().toLong(), Chapter::mapper)
}
override suspend fun getChapterByUrlAndMangaId(
url: String,
mangaId: Long,
filterScanlators: Boolean
): Chapter? =
handler.awaitOneOrNull {
chaptersQueries.getChaptersByUrlAndMangaId(url, mangaId, filterScanlators.toInt().toLong(), Chapter::mapper)
}
override suspend fun getRecents(filterScanlators: Boolean, search: String, limit: Long, offset: Long): List<MangaChapter> = override suspend fun getRecents(filterScanlators: Boolean, search: String, limit: Long, offset: Long): List<MangaChapter> =
handler.awaitList { chaptersQueries.getRecents(search, filterScanlators.toInt().toLong(), limit, offset, MangaChapter::mapper) } handler.awaitList { chaptersQueries.getRecents(search, filterScanlators.toInt().toLong(), limit, offset, MangaChapter::mapper) }

View file

@ -9,7 +9,13 @@ interface ChapterRepository {
suspend fun getChapters(mangaId: Long, filterScanlators: Boolean): List<Chapter> suspend fun getChapters(mangaId: Long, filterScanlators: Boolean): List<Chapter>
fun getChaptersAsFlow(mangaId: Long, filterScanlators: Boolean): Flow<List<Chapter>> fun getChaptersAsFlow(mangaId: Long, filterScanlators: Boolean): Flow<List<Chapter>>
suspend fun getChapterById(id: Long): Chapter?
suspend fun getChaptersByUrl(url: String, filterScanlators: Boolean): List<Chapter> suspend fun getChaptersByUrl(url: String, filterScanlators: Boolean): List<Chapter>
suspend fun getChapterByUrl(url: String, filterScanlators: Boolean): Chapter?
suspend fun getChaptersByUrlAndMangaId(url: String, mangaId: Long, filterScanlators: Boolean): List<Chapter>
suspend fun getChapterByUrlAndMangaId(url: String, mangaId: Long, filterScanlators: Boolean): Chapter?
suspend fun getRecents(filterScanlators: Boolean, search: String = "", limit: Long = 25L, offset: Long = 0L): List<MangaChapter> suspend fun getRecents(filterScanlators: Boolean, search: String = "", limit: Long = 25L, offset: Long = 0L): List<MangaChapter>

View file

@ -11,8 +11,17 @@ class GetChapter(
suspend fun awaitAll(manga: Manga, filterScanlators: Boolean? = null) = suspend fun awaitAll(manga: Manga, filterScanlators: Boolean? = null) =
awaitAll(manga.id!!, filterScanlators ?: (manga.filtered_scanlators?.isNotEmpty() == true)) awaitAll(manga.id!!, filterScanlators ?: (manga.filtered_scanlators?.isNotEmpty() == true))
suspend fun awaitById(id: Long) = chapterRepository.getChapterById(id)
suspend fun awaitAllByUrl(chapterUrl: String, filterScanlators: Boolean) = suspend fun awaitAllByUrl(chapterUrl: String, filterScanlators: Boolean) =
chapterRepository.getChaptersByUrl(chapterUrl, filterScanlators) chapterRepository.getChaptersByUrl(chapterUrl, filterScanlators)
suspend fun awaitByUrl(chapterUrl: String, filterScanlators: Boolean) =
chapterRepository.getChapterByUrl(chapterUrl, filterScanlators)
suspend fun awaitAllByUrlAndMangaId(chapterUrl: String, mangaId: Long, filterScanlators: Boolean) =
chapterRepository.getChaptersByUrlAndMangaId(chapterUrl, mangaId, filterScanlators)
suspend fun awaitByUrlAndMangaId(chapterUrl: String, mangaId: Long, filterScanlators: Boolean) =
chapterRepository.getChapterByUrlAndMangaId(chapterUrl, mangaId, filterScanlators)
fun subscribeAll(mangaId: Long, filterScanlators: Boolean) = chapterRepository.getChaptersAsFlow(mangaId, filterScanlators) fun subscribeAll(mangaId: Long, filterScanlators: Boolean) = chapterRepository.getChaptersAsFlow(mangaId, filterScanlators)
} }

View file

@ -32,6 +32,10 @@ AND (
:apply_filter = 0 OR S.name IS NULL :apply_filter = 0 OR S.name IS NULL
); );
getChaptersById:
SELECT * FROM chapters
WHERE _id = :id;
getChaptersByUrl: getChaptersByUrl:
SELECT C.* SELECT C.*
FROM chapters AS C FROM chapters AS C
@ -43,6 +47,17 @@ AND (
:apply_filter = 0 OR S.name IS NULL :apply_filter = 0 OR S.name IS NULL
); );
getChaptersByUrlAndMangaId:
SELECT C.*
FROM chapters AS C
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 C.url = :url AND C.manga_id = :manga_id
AND (
:apply_filter = 0 OR S.name IS NULL
);
getRecents: getRecents:
SELECT SELECT
M.*, M.*,