refactor: Migrated most manga queries and some chapter queries to SQLDelight

This commit is contained in:
Ahmad Ansori Palembani 2024-06-19 11:12:37 +07:00
parent 5ed2934b73
commit f6080cd5eb
Signed by: null2264
GPG key ID: BA64F8B60AF3EFB6
26 changed files with 450 additions and 211 deletions

View file

@ -28,6 +28,9 @@
- Update dependency com.google.gms:google-services to v4.4.2
- Add crashlytics integration for Kermit
- Replace ProgressBar with ProgressIndicator from Material3 to improve UI consistency
- Merge lastFetch and lastRead query into library_view VIEW
- More StorIO to SQLDelight migrations
- Merge lastFetch and lastRead query into library_view VIEW
- Migrated a few more chapter related queries
- Migrated most of manga related queries
- Update Japenese translation
- Update dependency com.github.tachiyomiorg:unifile to a9de196cc7

View file

@ -4,16 +4,17 @@ import eu.kanade.tachiyomi.data.backup.models.BackupCategory
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import yokai.domain.category.interactor.GetCategories
class CategoriesBackupRestorer(
private val db: DatabaseHelper = Injekt.get(),
private val getCategories: GetCategories = Injekt.get(),
) {
@Suppress("RedundantSuspendModifier")
suspend fun restoreCategories(backupCategories: List<BackupCategory>, onComplete: () -> Unit) {
// Get categories from file and from db
// Do it outside of transaction because StorIO might hang because we're using SQLDelight
val dbCategories = getCategories.await()
db.inTransaction {
// Get categories from file and from db
val dbCategories = db.getCategories().executeAsBlocking()
// Iterate over them
backupCategories.map { it.getCategoryImpl() }.forEach { category ->
// Used to know if the category is already in the db

View file

@ -17,14 +17,24 @@ import eu.kanade.tachiyomi.util.manga.MangaUtil
import eu.kanade.tachiyomi.util.system.launchNow
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import yokai.domain.category.interactor.GetCategories
import yokai.domain.chapter.interactor.GetChapters
import yokai.domain.library.custom.model.CustomMangaInfo
import yokai.domain.manga.interactor.GetManga
import yokai.domain.manga.interactor.InsertManga
import yokai.domain.manga.interactor.UpdateManga
import kotlin.math.max
class MangaBackupRestorer(
private val db: DatabaseHelper = Injekt.get(),
private val customMangaManager: CustomMangaManager = Injekt.get(),
private val getCategories: GetCategories = Injekt.get(),
private val getChapters: GetChapters = Injekt.get(),
private val getManga: GetManga = Injekt.get(),
private val insertManga: InsertManga = Injekt.get(),
private val updateManga: UpdateManga = Injekt.get(),
) {
fun restoreManga(
suspend fun restoreManga(
backupManga: BackupManga,
backupCategories: List<BackupCategory>,
onComplete: (Manga) -> Unit,
@ -40,19 +50,19 @@ class MangaBackupRestorer(
val filteredScanlators = backupManga.excludedScanlators
try {
val dbManga = db.getManga(manga.url, manga.source).executeAsBlocking()
val dbManga = getManga.awaitByUrlAndSource(manga.url, manga.source)
if (dbManga == null) {
// Manga not in database
restoreExistingManga(manga, chapters, categories, history, tracks, backupCategories, filteredScanlators, customManga)
restoreNewManga(manga, chapters, categories, history, tracks, backupCategories, filteredScanlators, customManga)
} else {
// Manga in database
// Copy information from manga already in database
manga.id = dbManga.id
manga.filtered_scanlators = dbManga.filtered_scanlators
manga.copyFrom(dbManga)
db.insertManga(manga).executeAsBlocking()
updateManga.await(manga.toMangaUpdate())
// Fetch rest of manga information
restoreNewManga(manga, chapters, categories, history, tracks, backupCategories, filteredScanlators, customManga)
restoreExistingManga(manga, chapters, categories, history, tracks, backupCategories, filteredScanlators, customManga)
}
} catch (e: Exception) {
onError(manga, e)
@ -69,7 +79,7 @@ class MangaBackupRestorer(
* @param chapters chapters of manga that needs updating
* @param categories categories that need updating
*/
private fun restoreExistingManga(
private suspend fun restoreNewManga(
manga: Manga,
chapters: List<Chapter>,
categories: List<Int>,
@ -81,7 +91,7 @@ class MangaBackupRestorer(
) {
val fetchedManga = manga.also {
it.initialized = it.description != null
it.id = db.insertManga(it).executeAsBlocking().insertedId()
it.id = insertManga.await(it)
}
fetchedManga.id ?: return
@ -89,7 +99,7 @@ class MangaBackupRestorer(
restoreExtras(fetchedManga, categories, history, tracks, backupCategories, filteredScanlators, customManga)
}
private fun restoreNewManga(
private suspend fun restoreExistingManga(
backupManga: Manga,
chapters: List<Chapter>,
categories: List<Int>,
@ -103,8 +113,8 @@ class MangaBackupRestorer(
restoreExtras(backupManga, categories, history, tracks, backupCategories, filteredScanlators, customManga)
}
private fun restoreChapters(manga: Manga, chapters: List<Chapter>) {
val dbChapters = db.getChapters(manga).executeAsBlocking()
private suspend fun restoreChapters(manga: Manga, chapters: List<Chapter>) {
val dbChapters = getChapters.await(manga)
chapters.forEach { chapter ->
val dbChapter = dbChapters.find { it.url == chapter.url }
@ -130,7 +140,7 @@ class MangaBackupRestorer(
newChapters[false]?.let { db.insertChapters(it).executeAsBlocking() }
}
private fun restoreExtras(
private suspend fun restoreExtras(
manga: Manga,
categories: List<Int>,
history: List<BackupHistory>,
@ -157,8 +167,8 @@ class MangaBackupRestorer(
* @param manga the manga whose categories have to be restored.
* @param categories the categories to restore.
*/
private fun restoreCategories(manga: Manga, categories: List<Int>, backupCategories: List<BackupCategory>) {
val dbCategories = db.getCategories().executeAsBlocking()
private suspend fun restoreCategories(manga: Manga, categories: List<Int>, backupCategories: List<BackupCategory>) {
val dbCategories = getCategories.await()
val mangaCategoriesToUpdate = ArrayList<MangaCategory>(categories.size)
categories.forEach { backupCategoryOrder ->
backupCategories.firstOrNull {
@ -184,7 +194,7 @@ class MangaBackupRestorer(
*
* @param history list containing history to be restored
*/
internal fun restoreHistoryForManga(history: List<BackupHistory>) {
internal suspend fun restoreHistoryForManga(history: List<BackupHistory>) {
// List containing history to be updated
val historyToBeUpdated = ArrayList<History>(history.size)
for ((url, lastRead, readDuration) in history) {
@ -216,7 +226,7 @@ class MangaBackupRestorer(
* @param manga the manga whose sync have to be restored.
* @param tracks the track list to restore.
*/
private fun restoreTrackForManga(manga: Manga, tracks: List<Track>) {
private suspend fun restoreTrackForManga(manga: Manga, tracks: List<Track>) {
// Fix foreign keys with the current manga id
tracks.map { it.manga_id = manga.id!! }
@ -253,8 +263,8 @@ class MangaBackupRestorer(
}
}
private fun restoreFilteredScanlatorsForManga(manga: Manga, filteredScanlators: List<String>) {
private suspend fun restoreFilteredScanlatorsForManga(manga: Manga, filteredScanlators: List<String>) {
val actualList = ChapterUtil.getScanlators(manga.filtered_scanlators) + filteredScanlators
MangaUtil.setScanlatorFilter(db, manga, actualList.toSet())
MangaUtil.setScanlatorFilter(updateManga, manga, actualList.toSet())
}
}

View file

@ -12,6 +12,7 @@ import eu.kanade.tachiyomi.util.manga.MangaCoverMetadata
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import yokai.data.updateStrategyAdapter
import yokai.domain.manga.models.MangaUpdate
import java.util.*
// TODO: Transform into data class
@ -327,6 +328,30 @@ interface Manga : SManga {
MangaCoverMetadata.addCoverColor(this, value.first, value.second)
}
fun toMangaUpdate(): MangaUpdate {
return MangaUpdate(
id = id!!,
source = source,
url = url,
artist = artist,
author = author,
description = description,
genres = genre?.split(", ").orEmpty(),
title = title,
status = status,
thumbnailUrl = thumbnail_url,
favorite = favorite,
lastUpdate = last_update,
initialized = initialized,
viewerFlags = viewer_flags,
hideTitle = hide_title,
chapterFlags = chapter_flags,
dateAdded = date_added,
filteredScanlators = filtered_scanlators,
updateStrategy = update_strategy,
)
}
companion object {
// Generic filter that does not filter anything

View file

@ -6,10 +6,8 @@ import eu.kanade.tachiyomi.data.database.DbProvider
import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.MangaChapter
import eu.kanade.tachiyomi.data.database.resolvers.ChapterBackupPutResolver
import eu.kanade.tachiyomi.data.database.resolvers.ChapterKnownBackupPutResolver
import eu.kanade.tachiyomi.data.database.resolvers.ChapterProgressPutResolver
import eu.kanade.tachiyomi.data.database.resolvers.ChapterSourceOrderPutResolver
import eu.kanade.tachiyomi.data.database.resolvers.MangaChapterGetResolver
import eu.kanade.tachiyomi.data.database.tables.ChapterTable
import eu.kanade.tachiyomi.util.lang.sqLite
@ -88,15 +86,6 @@ interface ChapterQueries : DbProvider {
fun insertChapters(chapters: List<Chapter>) = db.put().objects(chapters).prepare()
fun deleteChapter(chapter: Chapter) = db.delete().`object`(chapter).prepare()
fun deleteChapters(chapters: List<Chapter>) = db.delete().objects(chapters).prepare()
fun updateChaptersBackup(chapters: List<Chapter>) = db.put()
.objects(chapters)
.withPutResolver(ChapterBackupPutResolver())
.prepare()
fun updateKnownChaptersBackup(chapters: List<Chapter>) = db.put()
.objects(chapters)
.withPutResolver(ChapterKnownBackupPutResolver())
@ -111,9 +100,4 @@ interface ChapterQueries : DbProvider {
.objects(chapters)
.withPutResolver(ChapterProgressPutResolver())
.prepare()
fun fixChaptersSourceOrder(chapters: List<Chapter>) = db.put()
.objects(chapters)
.withPutResolver(ChapterSourceOrderPutResolver())
.prepare()
}

View file

@ -9,10 +9,6 @@ import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.SourceIdMangaCount
import eu.kanade.tachiyomi.data.database.resolvers.MangaDateAddedPutResolver
import eu.kanade.tachiyomi.data.database.resolvers.MangaFavoritePutResolver
import eu.kanade.tachiyomi.data.database.resolvers.MangaFilteredScanlatorsPutResolver
import eu.kanade.tachiyomi.data.database.resolvers.MangaFlagsPutResolver
import eu.kanade.tachiyomi.data.database.resolvers.MangaInfoPutResolver
import eu.kanade.tachiyomi.data.database.resolvers.MangaLastUpdatedPutResolver
import eu.kanade.tachiyomi.data.database.resolvers.MangaTitlePutResolver
import eu.kanade.tachiyomi.data.database.resolvers.SourceIdMangaCountGetResolver
import eu.kanade.tachiyomi.data.database.tables.ChapterTable
@ -91,55 +87,24 @@ interface MangaQueries : DbProvider {
fun insertManga(manga: Manga) = db.put().`object`(manga).prepare()
fun updateChapterFlags(manga: Manga) = db.put()
.`object`(manga)
.withPutResolver(MangaFlagsPutResolver(MangaTable.COL_CHAPTER_FLAGS, Manga::chapter_flags))
.prepare()
fun updateChapterFlags(manga: List<Manga>) = db.put()
.objects(manga)
.withPutResolver(MangaFlagsPutResolver(MangaTable.COL_CHAPTER_FLAGS, Manga::chapter_flags, true))
.prepare()
fun updateViewerFlags(manga: Manga) = db.put()
.`object`(manga)
.withPutResolver(MangaFlagsPutResolver(MangaTable.COL_VIEWER, Manga::viewer_flags))
.prepare()
fun updateViewerFlags(manga: List<Manga>) = db.put()
.objects(manga)
.withPutResolver(MangaFlagsPutResolver(MangaTable.COL_VIEWER, Manga::viewer_flags, true))
.prepare()
fun updateLastUpdated(manga: Manga) = db.put()
.`object`(manga)
.withPutResolver(MangaLastUpdatedPutResolver())
.prepare()
// FIXME: Migrate to SQLDelight, on halt: used by StorIO's inTransaction
fun updateMangaFavorite(manga: Manga) = db.put()
.`object`(manga)
.withPutResolver(MangaFavoritePutResolver())
.prepare()
// FIXME: Migrate to SQLDelight, on halt: used by StorIO's inTransaction
fun updateMangaAdded(manga: Manga) = db.put()
.`object`(manga)
.withPutResolver(MangaDateAddedPutResolver())
.prepare()
// FIXME: Migrate to SQLDelight, on halt: used by StorIO's inTransaction
fun updateMangaTitle(manga: Manga) = db.put()
.`object`(manga)
.withPutResolver(MangaTitlePutResolver())
.prepare()
fun updateMangaInfo(manga: Manga) = db.put()
.`object`(manga)
.withPutResolver(MangaInfoPutResolver())
.prepare()
fun deleteManga(manga: Manga) = db.delete().`object`(manga).prepare()
fun deleteMangas(mangas: List<Manga>) = db.delete().objects(mangas).prepare()
fun deleteMangasNotInLibraryBySourceIds(sourceIds: List<Long>) = db.delete()
.byQuery(
DeleteQuery.builder()
@ -166,14 +131,6 @@ interface MangaQueries : DbProvider {
)
.prepare()
fun deleteMangas() = db.delete()
.byQuery(
DeleteQuery.builder()
.table(MangaTable.TABLE)
.build(),
)
.prepare()
fun getReadNotInLibraryMangas() = db.get()
.listOfObjects(Manga::class.java)
.withQuery(
@ -182,12 +139,4 @@ interface MangaQueries : DbProvider {
.build(),
)
.prepare()
fun getTotalChapterManga() = db.get().listOfObjects(Manga::class.java)
.withQuery(RawQuery.builder().query(getTotalChapterMangaQuery()).observesTables(MangaTable.TABLE).build()).prepare()
fun updateMangaFilteredScanlators(manga: Manga) = db.put()
.`object`(manga)
.withPutResolver(MangaFilteredScanlatorsPutResolver())
.prepare()
}

View file

@ -402,7 +402,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
val fetchedChapters = source.getChapterList(manga.copy())
if (fetchedChapters.isNotEmpty()) {
val newChapters = syncChaptersWithSource(db, fetchedChapters, manga, source)
val newChapters = syncChaptersWithSource(fetchedChapters, manga, source)
if (newChapters.first.isNotEmpty()) {
if (shouldDownload) {
downloadChapters(

View file

@ -1136,7 +1136,7 @@ class LibraryPresenter(
val mangaToDelete = mangas.distinctBy { it.id }
.mapNotNull { if (it.id != null) MangaUpdate(it.id!!, favorite = false) else null }
withIOContext { updateManga.updateAll(mangaToDelete) }
withIOContext { updateManga.awaitAll(mangaToDelete) }
getLibrary()
}
}
@ -1173,7 +1173,7 @@ class LibraryPresenter(
val mangaToAdd = mangas.distinctBy { it.id }
.mapNotNull { if (it.id != null) MangaUpdate(it.id!!, favorite = true) else null }
withIOContext { updateManga.updateAll(mangaToAdd) }
withIOContext { updateManga.awaitAll(mangaToAdd) }
(view as? FilteredLibraryController)?.updateStatsPage()
getLibrary()
}
@ -1504,8 +1504,8 @@ class LibraryPresenter(
fun updateDB() {
val db: DatabaseHelper = Injekt.get()
val getLibraryManga: GetLibraryManga by injectLazy()
val libraryManga = runBlocking { getLibraryManga.await() }
db.inTransaction {
val libraryManga = runBlocking { getLibraryManga.await() }
libraryManga.forEach { manga ->
if (manga.date_added == 0L) {
val chapters = db.getChapters(manga).executeAsBlocking()
@ -1529,8 +1529,8 @@ class LibraryPresenter(
val db: DatabaseHelper = Injekt.get()
val cc: CoverCache = Injekt.get()
val getLibraryManga: GetLibraryManga by injectLazy()
val libraryManga = runBlocking { getLibraryManga.await() }
db.inTransaction {
val libraryManga = runBlocking { getLibraryManga.await() }
libraryManga.forEach { manga ->
if (manga.thumbnail_url?.startsWith("custom", ignoreCase = true) == true) {
val file = cc.getCoverFile(manga)

View file

@ -53,10 +53,10 @@ import eu.kanade.tachiyomi.util.manga.MangaUtil
import eu.kanade.tachiyomi.util.shouldDownloadNewChapters
import eu.kanade.tachiyomi.util.storage.DiskUtil
import eu.kanade.tachiyomi.util.system.ImageUtil
import eu.kanade.tachiyomi.util.system.executeOnIO
import eu.kanade.tachiyomi.util.system.launchIO
import eu.kanade.tachiyomi.util.system.launchNow
import eu.kanade.tachiyomi.util.system.launchUI
import eu.kanade.tachiyomi.util.system.withIOContext
import eu.kanade.tachiyomi.util.system.withUIContext
import eu.kanade.tachiyomi.widget.TriStateCheckBox
import kotlinx.coroutines.Dispatchers
@ -74,6 +74,8 @@ import uy.kohesive.injekt.injectLazy
import yokai.domain.chapter.interactor.GetAvailableScanlators
import yokai.domain.chapter.interactor.GetChapters
import yokai.domain.library.custom.model.CustomMangaInfo
import yokai.domain.manga.interactor.UpdateManga
import yokai.domain.manga.models.MangaUpdate
import yokai.domain.storage.StorageManager
import java.io.File
import java.io.FileOutputStream
@ -92,6 +94,7 @@ class MangaDetailsPresenter(
) : BaseCoroutinePresenter<MangaDetailsController>(), DownloadQueue.DownloadListener {
private val getAvailableScanlators: GetAvailableScanlators by injectLazy()
private val getChapters: GetChapters by injectLazy()
private val updateManga: UpdateManga by injectLazy()
private val customMangaManager: CustomMangaManager by injectLazy()
private val mangaShortcutManager: MangaShortcutManager by injectLazy()
@ -404,7 +407,7 @@ class MangaDetailsPresenter(
}
val finChapters = chapters.await()
if (finChapters.isNotEmpty()) {
val newChapters = syncChaptersWithSource(db, finChapters, manga, source)
val newChapters = withIOContext { syncChaptersWithSource(finChapters, manga, source) }
if (newChapters.first.isNotEmpty()) {
if (manga.shouldDownloadNewChapters(db, preferences)) {
downloadChapters(
@ -469,7 +472,7 @@ class MangaDetailsPresenter(
}
isLoading = false
try {
syncChaptersWithSource(db, chapters, manga, source)
syncChaptersWithSource(chapters, manga, source)
getChapters()
withContext(Dispatchers.Main) {
@ -555,14 +558,14 @@ class MangaDetailsPresenter(
if (mangaSortMatchesDefault()) {
manga.setSortToGlobal()
}
asyncUpdateMangaAndChapters()
presenterScope.launchIO { asyncUpdateMangaAndChapters() }
}
fun setGlobalChapterSort(sort: Int, descend: Boolean) {
preferences.sortChapterOrder().set(sort)
preferences.chaptersDescAsDefault().set(descend)
manga.setSortToGlobal()
asyncUpdateMangaAndChapters()
presenterScope.launchIO { asyncUpdateMangaAndChapters() }
}
fun mangaSortMatchesDefault(): Boolean {
@ -583,7 +586,7 @@ class MangaDetailsPresenter(
fun resetSortingToDefault() {
manga.setSortToGlobal()
asyncUpdateMangaAndChapters()
presenterScope.launchIO { asyncUpdateMangaAndChapters() }
}
/**
@ -613,7 +616,7 @@ class MangaDetailsPresenter(
if (mangaFilterMatchesDefault()) {
manga.setFilterToGlobal()
}
asyncUpdateMangaAndChapters()
presenterScope.launchIO { asyncUpdateMangaAndChapters() }
}
/**
@ -623,7 +626,7 @@ class MangaDetailsPresenter(
fun hideTitle(hide: Boolean) {
manga.displayMode = if (hide) Manga.CHAPTER_DISPLAY_NUMBER else Manga.CHAPTER_DISPLAY_NAME
manga.setFilterToLocal()
db.updateChapterFlags(manga).executeAsBlocking()
presenterScope.launchIO { updateManga.await(MangaUpdate(manga.id!!, chapterFlags = manga.chapter_flags)) }
if (mangaFilterMatchesDefault()) {
manga.setFilterToGlobal()
}
@ -632,7 +635,7 @@ class MangaDetailsPresenter(
fun resetFilterToDefault() {
manga.setFilterToGlobal()
asyncUpdateMangaAndChapters()
presenterScope.launchIO { asyncUpdateMangaAndChapters() }
}
fun setGlobalChapterFilters(
@ -663,15 +666,13 @@ class MangaDetailsPresenter(
)
preferences.hideChapterTitlesByDefault().set(manga.hideChapterTitles)
manga.setFilterToGlobal()
asyncUpdateMangaAndChapters()
presenterScope.launchIO { asyncUpdateMangaAndChapters() }
}
private fun asyncUpdateMangaAndChapters(justChapters: Boolean = false) {
presenterScope.launch {
if (!justChapters) db.updateChapterFlags(manga).executeOnIO()
getChapters()
withContext(Dispatchers.Main) { view?.updateChapters(chapters) }
}
private suspend fun asyncUpdateMangaAndChapters(justChapters: Boolean = false) {
if (!justChapters) updateManga.await(MangaUpdate(manga.id!!, chapterFlags = manga.chapter_flags))
getChapters()
withUIContext { view?.updateChapters(chapters) }
}
private fun isScanlatorFiltered() = manga.filtered_scanlators?.isNotEmpty() == true
@ -690,13 +691,15 @@ class MangaDetailsPresenter(
}
fun setScanlatorFilter(filteredScanlators: Set<String>) {
val manga = manga
MangaUtil.setScanlatorFilter(
db,
manga,
if (filteredScanlators.size == allChapterScanlators.size) emptySet() else filteredScanlators
)
asyncUpdateMangaAndChapters()
presenterScope.launchIO {
val manga = manga
MangaUtil.setScanlatorFilter(
updateManga,
manga,
if (filteredScanlators.size == allChapterScanlators.size) emptySet() else filteredScanlators
)
asyncUpdateMangaAndChapters()
}
}
fun toggleFavorite(): Boolean {
@ -802,11 +805,23 @@ class MangaDetailsPresenter(
}
}
manga.viewer_flags = -1
db.updateViewerFlags(manga).executeAsBlocking()
presenterScope.launchIO { updateManga.await(MangaUpdate(manga.id!!, viewerFlags = manga.viewer_flags)) }
}
manga.status = status ?: SManga.UNKNOWN
LocalSource(downloadManager.context).updateMangaInfo(manga, lang)
db.updateMangaInfo(manga).executeAsBlocking()
presenterScope.launchIO {
updateManga.await(
MangaUpdate(
manga.id!!,
title = manga.originalTitle,
author = manga.originalAuthor,
artist = manga.originalArtist,
description = manga.originalDescription,
genres = manga.originalGenre?.split(", ").orEmpty(),
status = manga.originalStatus,
)
)
}
} else {
var genre = if (!tags.isNullOrEmpty() && tags.joinToString(", ") != manga.originalGenre) {
tags.map { tag -> tag.replaceFirstChar { it.titlecase(Locale.getDefault()) } }
@ -817,7 +832,7 @@ class MangaDetailsPresenter(
if (seriesType != null) {
genre = setSeriesType(seriesType, genre?.joinToString())
manga.viewer_flags = -1
db.updateViewerFlags(manga).executeAsBlocking()
presenterScope.launchIO { updateManga.await(MangaUpdate(manga.id!!, viewerFlags = manga.viewer_flags)) }
}
val manga = CustomMangaInfo(
mangaId = manga.id!!,

View file

@ -44,6 +44,7 @@ import eu.kanade.tachiyomi.util.system.getResourceColor
import eu.kanade.tachiyomi.util.system.launchUI
import eu.kanade.tachiyomi.util.system.materialAlertDialog
import eu.kanade.tachiyomi.util.system.toast
import eu.kanade.tachiyomi.util.system.withIOContext
import eu.kanade.tachiyomi.util.view.RecyclerWindowInsetsListener
import eu.kanade.tachiyomi.util.view.activityBinding
import eu.kanade.tachiyomi.util.view.isControllerVisible
@ -61,7 +62,7 @@ import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.sync.withPermit
import kotlinx.coroutines.withContext
import uy.kohesive.injekt.injectLazy
import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.atomic.*
import kotlin.coroutines.CoroutineContext
class MigrationListController(bundle: Bundle? = null) :
@ -190,7 +191,6 @@ class MigrationListController(bundle: Bundle? = null) :
val chapters = source.getChapterList(localManga)
try {
syncChaptersWithSource(
db,
chapters,
localManga,
source,
@ -232,7 +232,7 @@ class MigrationListController(bundle: Bundle? = null) :
emptyList()
}
withContext(Dispatchers.IO) {
syncChaptersWithSource(db, chapters, localManga, source)
syncChaptersWithSource(chapters, localManga, source)
}
localManga
} else {
@ -368,7 +368,7 @@ class MigrationListController(bundle: Bundle? = null) :
val localManga = smartSearchEngine.networkToLocalManga(manga, source.id)
try {
val chapters = source.getChapterList(localManga)
syncChaptersWithSource(db, chapters, localManga, source)
withIOContext { syncChaptersWithSource(chapters, localManga, source) }
} catch (e: Exception) {
return@async null
}

View file

@ -75,6 +75,8 @@ import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import yokai.domain.chapter.interactor.GetChapters
import yokai.domain.download.DownloadPreferences
import yokai.domain.manga.interactor.UpdateManga
import yokai.domain.manga.models.MangaUpdate
import yokai.domain.storage.StorageManager
import java.util.*
import java.util.concurrent.*
@ -94,6 +96,7 @@ class ReaderViewModel(
private val downloadPreferences: DownloadPreferences = Injekt.get(),
) : ViewModel() {
private val getChapters: GetChapters by injectLazy()
private val updateManga: UpdateManga by injectLazy()
private val mutableState = MutableStateFlow(State())
val state = mutableState.asStateFlow()
@ -333,7 +336,6 @@ class ReaderViewModel(
val chapterId: Long
if (chapters.isNotEmpty()) {
val newChapters = syncChaptersWithSource(
db,
chapters,
manga,
delegatedSource.delegate!!,
@ -678,7 +680,7 @@ class ReaderViewModel(
manga.viewer_flags = 0
}
manga.readingModeType = if (cantSwitchToLTR) 0 else readerType
db.updateViewerFlags(manga).asRxObservable().subscribe()
viewModelScope.launchIO { updateManga.await(MangaUpdate(manga.id!!, viewerFlags = manga.viewer_flags)) }
}
return if (manga.readingModeType == 0) default else manga.readingModeType
}
@ -689,9 +691,9 @@ class ReaderViewModel(
fun setMangaReadingMode(readingModeType: Int) {
val manga = manga ?: return
runBlocking(Dispatchers.IO) {
viewModelScope.launchIO {
manga.readingModeType = readingModeType
db.updateViewerFlags(manga).executeAsBlocking()
updateManga.await(MangaUpdate(manga.id!!, viewerFlags = manga.viewer_flags))
val currChapters = state.value.viewerChapters
if (currChapters != null) {
// Save current page
@ -726,12 +728,11 @@ class ReaderViewModel(
fun setMangaOrientationType(rotationType: Int) {
val manga = manga ?: return
this.manga?.orientationType = rotationType
db.updateViewerFlags(manga).executeAsBlocking()
Logger.i { "Manga orientation is ${manga.orientationType}" }
viewModelScope.launchIO {
db.updateViewerFlags(manga).executeAsBlocking()
updateManga.await(MangaUpdate(manga.id!!, viewerFlags = manga.viewer_flags))
val currChapters = state.value.viewerChapters
if (currChapters != null) {
mutableState.update {

View file

@ -1,6 +1,5 @@
package eu.kanade.tachiyomi.util.chapter
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.download.DownloadManager
@ -8,7 +7,17 @@ import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.util.chapter.ChapterFilter.Companion.filterChaptersByScanlators
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import yokai.data.DatabaseHandler
import yokai.domain.chapter.interactor.DeleteChapters
import yokai.domain.chapter.interactor.GetChapters
import yokai.domain.chapter.interactor.InsertChapters
import yokai.domain.chapter.interactor.UpdateChapters
import yokai.domain.chapter.models.ChapterUpdate
import yokai.domain.manga.interactor.UpdateManga
import yokai.domain.manga.models.MangaUpdate
import java.util.*
/**
@ -20,11 +29,16 @@ import java.util.*
* @param source the source of the chapters.
* @return a pair of new insertions and deletions.
*/
fun syncChaptersWithSource(
db: DatabaseHelper,
suspend fun syncChaptersWithSource(
rawSourceChapters: List<SChapter>,
manga: Manga,
source: Source,
deleteChapters: DeleteChapters = Injekt.get(),
getChapters: GetChapters = Injekt.get(),
insertChapters: InsertChapters = Injekt.get(),
updateChapters: UpdateChapters = Injekt.get(),
updateManga: UpdateManga = Injekt.get(),
handler: DatabaseHandler = Injekt.get(),
): Pair<List<Chapter>, List<Chapter>> {
if (rawSourceChapters.isEmpty()) {
throw Exception("No chapters found")
@ -32,7 +46,7 @@ fun syncChaptersWithSource(
val downloadManager: DownloadManager by injectLazy()
// Chapters from db.
val dbChapters = db.getChapters(manga).executeAsBlocking()
val dbChapters = getChapters.await(manga)
val sourceChapters = rawSourceChapters
.distinctBy { it.url }
@ -49,7 +63,7 @@ fun syncChaptersWithSource(
val toAdd = mutableListOf<Chapter>()
// Chapters whose metadata have changed.
val toChange = mutableListOf<Chapter>()
val toChange = mutableListOf<ChapterUpdate>()
for (sourceChapter in sourceChapters) {
val dbChapter = dbChapters.find { it.url == sourceChapter.url }
@ -71,12 +85,15 @@ fun syncChaptersWithSource(
) {
downloadManager.renameChapter(source, manga, dbChapter, sourceChapter)
}
dbChapter.scanlator = sourceChapter.scanlator
dbChapter.name = sourceChapter.name
dbChapter.date_upload = sourceChapter.date_upload
dbChapter.chapter_number = sourceChapter.chapter_number
dbChapter.source_order = sourceChapter.source_order
toChange.add(dbChapter)
val update = ChapterUpdate(
dbChapter.id!!,
scanlator = sourceChapter.scanlator,
name = sourceChapter.name,
dateUpload = sourceChapter.date_upload,
chapterNumber = sourceChapter.chapter_number.toDouble(),
sourceOrder = sourceChapter.source_order.toLong(),
)
toChange.add(update)
}
}
}
@ -101,73 +118,84 @@ fun syncChaptersWithSource(
val newestDate = dbChapters.maxOfOrNull { it.date_upload } ?: 0L
if (newestDate != 0L && newestDate != manga.last_update) {
manga.last_update = newestDate
db.updateLastUpdated(manga).executeAsBlocking()
val update = MangaUpdate(manga.id!!, lastUpdate = manga.last_update)
updateManga.await(update)
}
return Pair(emptyList(), emptyList())
}
val readded = mutableListOf<Chapter>()
db.inTransaction {
val deletedChapterNumbers = TreeSet<Float>()
val deletedReadChapterNumbers = TreeSet<Float>()
if (toDelete.isNotEmpty()) {
for (c in toDelete) {
if (c.read) {
deletedReadChapterNumbers.add(c.chapter_number)
}
deletedChapterNumbers.add(c.chapter_number)
val deletedChapterNumbers = TreeSet<Float>()
val deletedReadChapterNumbers = TreeSet<Float>()
if (toDelete.isNotEmpty()) {
for (c in toDelete) {
if (c.read) {
deletedReadChapterNumbers.add(c.chapter_number)
}
db.deleteChapters(toDelete).executeAsBlocking()
deletedChapterNumbers.add(c.chapter_number)
}
if (toAdd.isNotEmpty()) {
// Set the date fetch for new items in reverse order to allow another sorting method.
// Sources MUST return the chapters from most to less recent, which is common.
var now = Date().time
for (i in toAdd.indices.reversed()) {
val chapter = toAdd[i]
chapter.date_fetch = now++
if (chapter.isRecognizedNumber && chapter.chapter_number in deletedChapterNumbers) {
// Try to mark already read chapters as read when the source deletes them
if (chapter.chapter_number in deletedReadChapterNumbers) {
chapter.read = true
}
// Try to to use the fetch date it originally had to not pollute 'Updates' tab
toDelete.filter { it.chapter_number == chapter.chapter_number }
.minByOrNull { it.date_fetch }?.let {
chapter.date_fetch = it.date_fetch
}
readded.add(chapter)
}
}
val chapters = db.insertChapters(toAdd).executeAsBlocking()
toAdd.forEach { chapter ->
chapter.id = chapters.results().getValue(chapter).insertedId()
}
}
if (toChange.isNotEmpty()) {
db.insertChapters(toChange).executeAsBlocking()
}
// Fix order in source.
db.fixChaptersSourceOrder(sourceChapters).executeAsBlocking()
// Set this manga as updated since chapters were changed
val newestChapterDate = db.getChapters(manga).executeAsBlocking()
.maxOfOrNull { it.date_upload } ?: 0L
if (newestChapterDate == 0L) {
if (toAdd.isNotEmpty()) {
manga.last_update = Date().time
}
} else {
manga.last_update = newestChapterDate
}
db.updateLastUpdated(manga).executeAsBlocking()
deleteChapters.awaitAll(toDelete)
}
if (toAdd.isNotEmpty()) {
// Set the date fetch for new items in reverse order to allow another sorting method.
// Sources MUST return the chapters from most to less recent, which is common.
var now = Date().time
for (i in toAdd.indices.reversed()) {
val chapter = toAdd[i]
chapter.date_fetch = now++
if (chapter.isRecognizedNumber && chapter.chapter_number in deletedChapterNumbers) {
// Try to mark already read chapters as read when the source deletes them
if (chapter.chapter_number in deletedReadChapterNumbers) {
chapter.read = true
}
// Try to to use the fetch date it originally had to not pollute 'Updates' tab
toDelete.filter { it.chapter_number == chapter.chapter_number }
.minByOrNull { it.date_fetch }?.let {
chapter.date_fetch = it.date_fetch
}
readded.add(chapter)
}
}
toAdd.forEach { chapter ->
chapter.id = insertChapters.await(chapter)
}
}
if (toChange.isNotEmpty()) {
updateChapters.awaitAll(toChange)
}
// Fix order in source.
handler.await(inTransaction = true) {
sourceChapters.forEach { chapter ->
if (chapter.manga_id == null) return@forEach
chaptersQueries.fixSourceOrder(
url = chapter.url,
mangaId = chapter.manga_id!!,
sourceOrder = chapter.source_order.toLong(),
)
}
}
var mangaUpdate: MangaUpdate? = null
// Set this manga as updated since chapters were changed
val newestChapterDate = getChapters.await(manga)
.maxOfOrNull { it.date_upload } ?: 0L
if (newestChapterDate == 0L) {
if (toAdd.isNotEmpty()) {
manga.last_update = Date().time
mangaUpdate = MangaUpdate(manga.id!!, lastUpdate = manga.last_update)
}
} else {
manga.last_update = newestChapterDate
mangaUpdate = MangaUpdate(manga.id!!, lastUpdate = manga.last_update)
}
mangaUpdate?.let { updateManga.await(it) }
val reAddedSet = readded.toSet()
return Pair(
toAdd.subtract(reAddedSet).toList().filterChaptersByScanlators(manga),

View file

@ -1,13 +1,17 @@
package eu.kanade.tachiyomi.util.manga
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.util.chapter.ChapterUtil
import yokai.domain.manga.interactor.UpdateManga
import yokai.domain.manga.models.MangaUpdate
object MangaUtil {
fun setScanlatorFilter(db: DatabaseHelper, manga: Manga, filteredScanlators: Set<String>) {
suspend fun setScanlatorFilter(updateManga: UpdateManga, manga: Manga, filteredScanlators: Set<String>) {
if (manga.id == null) return
manga.filtered_scanlators =
if (filteredScanlators.isEmpty()) null else ChapterUtil.getScanlatorString(filteredScanlators)
db.updateMangaFilteredScanlators(manga).executeAsBlocking()
updateManga.await(MangaUpdate(manga.id!!, filteredScanlators = manga.filtered_scanlators))
}
}

View file

@ -13,8 +13,11 @@ import yokai.data.manga.MangaRepositoryImpl
import yokai.domain.category.CategoryRepository
import yokai.domain.category.interactor.GetCategories
import yokai.domain.chapter.ChapterRepository
import yokai.domain.chapter.interactor.DeleteChapters
import yokai.domain.chapter.interactor.GetAvailableScanlators
import yokai.domain.chapter.interactor.GetChapters
import yokai.domain.chapter.interactor.InsertChapters
import yokai.domain.chapter.interactor.UpdateChapters
import yokai.domain.extension.interactor.TrustExtension
import yokai.domain.extension.repo.ExtensionRepoRepository
import yokai.domain.extension.repo.interactor.CreateExtensionRepo
@ -30,6 +33,7 @@ import yokai.domain.library.custom.interactor.GetCustomManga
import yokai.domain.library.custom.interactor.RelinkCustomManga
import yokai.domain.manga.MangaRepository
import yokai.domain.manga.interactor.GetLibraryManga
import yokai.domain.manga.interactor.GetManga
import yokai.domain.manga.interactor.InsertManga
import yokai.domain.manga.interactor.UpdateManga
@ -52,13 +56,17 @@ class DomainModule : InjektModule {
addFactory { RelinkCustomManga(get()) }
addSingletonFactory<MangaRepository> { MangaRepositoryImpl(get()) }
addFactory { GetManga(get()) }
addFactory { GetLibraryManga(get()) }
addFactory { InsertManga(get()) }
addFactory { UpdateManga(get()) }
addSingletonFactory<ChapterRepository> { ChapterRepositoryImpl(get()) }
addFactory { DeleteChapters(get()) }
addFactory { GetAvailableScanlators(get()) }
addFactory { GetChapters(get()) }
addFactory { InsertChapters(get()) }
addFactory { UpdateChapters(get()) }
addSingletonFactory<CategoryRepository> { CategoryRepositoryImpl(get()) }
addFactory { GetCategories(get()) }

View file

@ -1,10 +1,12 @@
package yokai.data.chapter
import co.touchlab.kermit.Logger
import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.util.system.toInt
import kotlinx.coroutines.flow.Flow
import yokai.data.DatabaseHandler
import yokai.domain.chapter.ChapterRepository
import yokai.domain.chapter.models.ChapterUpdate
class ChapterRepositoryImpl(private val handler: DatabaseHandler) : ChapterRepository {
override suspend fun getChapters(mangaId: Long, filterScanlators: Boolean): List<Chapter> =
@ -18,4 +20,93 @@ class ChapterRepositoryImpl(private val handler: DatabaseHandler) : ChapterRepos
override fun getScanlatorsByChapterAsFlow(mangaId: Long): Flow<List<String>> =
handler.subscribeToList { chaptersQueries.getScanlatorsByMangaId(mangaId) { it.orEmpty() } }
override suspend fun delete(chapter: Chapter) =
try {
partialDelete(chapter)
true
} catch (e: Exception) {
Logger.e(e) { "Failed to delete chapter with id '${chapter.id}'" }
false
}
override suspend fun deleteAll(chapters: List<Chapter>) =
try {
partialDelete(*chapters.toTypedArray())
true
} catch (e: Exception) {
Logger.e(e) { "Failed to bulk delete chapters" }
false
}
private suspend fun partialDelete(vararg chapters: Chapter) {
handler.await(inTransaction = true) {
chapters.forEach { chapter ->
if (chapter.id == null) return@forEach
chaptersQueries.delete(chapter.id!!)
}
}
}
override suspend fun update(update: ChapterUpdate): Boolean =
try {
partialUpdate(update)
true
} catch (e: Exception) {
Logger.e(e) { "Failed to update chapter with id '${update.id}'" }
false
}
override suspend fun updateAll(updates: List<ChapterUpdate>): Boolean =
try {
partialUpdate(*updates.toTypedArray())
true
} catch (e: Exception) {
Logger.e(e) { "Failed to bulk update chapters" }
false
}
private suspend fun partialUpdate(vararg updates: ChapterUpdate) {
handler.await(inTransaction = true) {
updates.forEach { update ->
chaptersQueries.update(
chapterId = update.id,
mangaId = update.mangaId,
url = update.url,
name = update.name,
scanlator = update.scanlator,
read = update.read,
bookmark = update.bookmark,
lastPageRead = update.lastPageRead,
pagesLeft = update.pagesLeft,
chapterNumber = update.chapterNumber,
sourceOrder = update.sourceOrder,
dateFetch = update.dateFetch,
dateUpload = update.dateUpload
)
}
}
}
override suspend fun insert(chapter: Chapter): Long? {
if (chapter.manga_id == null) return null
return handler.awaitOneOrNullExecutable(inTransaction = true) {
chaptersQueries.insert(
mangaId = chapter.manga_id!!,
url = chapter.url,
name = chapter.name,
scanlator = chapter.scanlator,
read = chapter.read,
bookmark = chapter.bookmark,
lastPageRead = chapter.last_page_read.toLong(),
pagesLeft = chapter.pages_left.toLong(),
chapterNumber = chapter.chapter_number.toDouble(),
sourceOrder = chapter.source_order.toLong(),
dateFetch = chapter.date_fetch,
dateUpload = chapter.date_upload,
)
chaptersQueries.selectLastInsertedRowId()
}
}
}

View file

@ -11,10 +11,16 @@ import yokai.domain.manga.MangaRepository
import yokai.domain.manga.models.MangaUpdate
class MangaRepositoryImpl(private val handler: DatabaseHandler) : MangaRepository {
override suspend fun getManga(): List<Manga> =
override suspend fun getMangaList(): List<Manga> =
handler.awaitList { mangasQueries.findAll(Manga::mapper) }
override fun getMangaAsFlow(): Flow<List<Manga>> =
override suspend fun getMangaByUrlAndSource(url: String, source: Long): Manga? =
handler.awaitOneOrNull { mangasQueries.findByUrlAndSource(url, source, Manga::mapper) }
override suspend fun getMangaById(id: Long): Manga? =
handler.awaitOneOrNull { mangasQueries.findById(id, Manga::mapper) }
override fun getMangaListAsFlow(): Flow<List<Manga>> =
handler.subscribeToList { mangasQueries.findAll(Manga::mapper) }
override suspend fun getLibraryManga(): List<LibraryManga> =

View file

@ -2,6 +2,7 @@ package yokai.domain.chapter
import eu.kanade.tachiyomi.data.database.models.Chapter
import kotlinx.coroutines.flow.Flow
import yokai.domain.chapter.models.ChapterUpdate
interface ChapterRepository {
suspend fun getChapters(mangaId: Long, filterScanlators: Boolean): List<Chapter>
@ -9,4 +10,12 @@ interface ChapterRepository {
suspend fun getScanlatorsByChapter(mangaId: Long): List<String>
fun getScanlatorsByChapterAsFlow(mangaId: Long): Flow<List<String>>
suspend fun delete(chapter: Chapter): Boolean
suspend fun deleteAll(chapters: List<Chapter>): Boolean
suspend fun update(update: ChapterUpdate): Boolean
suspend fun updateAll(updates: List<ChapterUpdate>): Boolean
suspend fun insert(chapter: Chapter): Long?
}

View file

@ -0,0 +1,11 @@
package yokai.domain.chapter.interactor
import eu.kanade.tachiyomi.data.database.models.Chapter
import yokai.domain.chapter.ChapterRepository
class DeleteChapters(
private val chapterRepository: ChapterRepository,
) {
suspend fun await(chapter: Chapter) = chapterRepository.delete(chapter)
suspend fun awaitAll(chapters: List<Chapter>) = chapterRepository.deleteAll(chapters)
}

View file

@ -0,0 +1,10 @@
package yokai.domain.chapter.interactor
import eu.kanade.tachiyomi.data.database.models.Chapter
import yokai.domain.chapter.ChapterRepository
class InsertChapters(
private val chapterRepository: ChapterRepository,
) {
suspend fun await(chapter: Chapter) = chapterRepository.insert(chapter)
}

View file

@ -0,0 +1,11 @@
package yokai.domain.chapter.interactor
import yokai.domain.chapter.ChapterRepository
import yokai.domain.chapter.models.ChapterUpdate
class UpdateChapters(
private val chapterRepository: ChapterRepository,
) {
suspend fun await(chapter: ChapterUpdate) = chapterRepository.update(chapter)
suspend fun awaitAll(chapters: List<ChapterUpdate>) = chapterRepository.updateAll(chapters)
}

View file

@ -6,8 +6,10 @@ import kotlinx.coroutines.flow.Flow
import yokai.domain.manga.models.MangaUpdate
interface MangaRepository {
suspend fun getManga(): List<Manga>
fun getMangaAsFlow(): Flow<List<Manga>>
suspend fun getMangaList(): List<Manga>
suspend fun getMangaByUrlAndSource(url: String, source: Long): Manga?
suspend fun getMangaById(id: Long): Manga?
fun getMangaListAsFlow(): Flow<List<Manga>>
suspend fun getLibraryManga(): List<LibraryManga>
fun getLibraryMangaAsFlow(): Flow<List<LibraryManga>>
suspend fun update(update: MangaUpdate): Boolean

View file

@ -0,0 +1,13 @@
package yokai.domain.manga.interactor
import yokai.domain.manga.MangaRepository
class GetManga (
private val mangaRepository: MangaRepository,
) {
suspend fun awaitAll() = mangaRepository.getMangaList()
fun subscribeAll() = mangaRepository.getMangaListAsFlow()
suspend fun awaitByUrlAndSource(url: String, source: Long) = mangaRepository.getMangaByUrlAndSource(url, source)
suspend fun awaitById(id: Long) = mangaRepository.getMangaById(id)
}

View file

@ -6,6 +6,6 @@ import yokai.domain.manga.models.MangaUpdate
class UpdateManga (
private val mangaRepository: MangaRepository,
) {
suspend fun update(update: MangaUpdate) = mangaRepository.update(update)
suspend fun updateAll(updates: List<MangaUpdate>) = mangaRepository.updateAll(updates)
suspend fun await(update: MangaUpdate) = mangaRepository.update(update)
suspend fun awaitAll(updates: List<MangaUpdate>) = mangaRepository.updateAll(updates)
}

View file

@ -36,3 +36,34 @@ getScanlatorsByMangaId:
SELECT scanlator
FROM chapters
WHERE manga_id = :mangaId;
delete:
DELETE FROM chapters
WHERE _id = :chapterId;
update:
UPDATE chapters SET
manga_id = coalesce(:mangaId, manga_id),
url = coalesce(:url, url),
name = coalesce(:name, name),
scanlator = coalesce(:scanlator, scanlator),
read = coalesce(:read, read),
bookmark = coalesce(:bookmark, bookmark),
last_page_read = coalesce(:lastPageRead, last_page_read),
pages_left = coalesce(:pagesLeft, pages_left),
chapter_number = coalesce(:chapterNumber, chapter_number),
source_order = coalesce(:sourceOrder, source_order),
date_fetch = coalesce(:dateFetch, date_fetch),
date_upload = coalesce(:dateUpload, date_upload)
WHERE _id = :chapterId;
fixSourceOrder:
UPDATE chapters SET source_order = :sourceOrder
WHERE url = :url AND manga_id = :mangaId;
insert:
INSERT INTO chapters (manga_id, url, name, scanlator, read, bookmark, last_page_read, pages_left, chapter_number, source_order, date_fetch, date_upload)
VALUES (:mangaId, :url, :name, :scanlator, :read, :bookmark, :lastPageRead, :pagesLeft, :chapterNumber, :sourceOrder, :dateFetch, :dateUpload);
selectLastInsertedRowId:
SELECT last_insert_rowid();

View file

@ -30,6 +30,16 @@ findAll:
SELECT *
FROM mangas;
findByUrlAndSource:
SELECT *
FROM mangas
WHERE url = :url AND source = :source;
findById:
SELECT *
FROM mangas
WHERE _id = :mangaId;
insert:
INSERT INTO mangas (source, url, artist, author, description, genre, title, status, thumbnail_url, favorite, last_update, initialized, viewer, hide_title, chapter_flags, date_added, filtered_scanlators, update_strategy)
VALUES (:source, :url, :artist, :author, :description, :genre, :title, :status, :thumbnailUrl, :favorite, :lastUpdate, :initialized, :viewer, :hideTitle, :chapterFlags, :dateAdded, :filteredScanlators, :updateStrategy);

View file

@ -0,0 +1,17 @@
package yokai.domain.chapter.models
data class ChapterUpdate(
val id: Long,
val mangaId: Long? = null,
val url: String? = null,
val name: String? = null,
val scanlator: String? = null,
val read: Boolean? = null,
val bookmark: Boolean? = null,
val lastPageRead: Long? = null,
val pagesLeft: Long? = null,
val chapterNumber: Double? = null,
val sourceOrder: Long? = null,
val dateFetch: Long? = null,
val dateUpload: Long? = null,
)