From fd5da2de3ab294988e8408077977f776cca5299a Mon Sep 17 00:00:00 2001 From: Andreas Date: Sun, 12 Jun 2022 20:33:48 +0200 Subject: [PATCH] Use SQLDelight in Backup/Restore (#7295) * Use SQLDelight in Backup/Restore * Use CoroutineWorker --- .../data/backup/AbstractBackupManager.kt | 132 +++++++-- .../data/backup/AbstractBackupRestore.kt | 21 +- .../tachiyomi/data/backup/BackupCreatorJob.kt | 6 +- .../data/backup/full/FullBackupManager.kt | 254 +++++++++++------- .../data/backup/full/FullBackupRestore.kt | 40 ++- .../data/backup/full/models/BackupCategory.kt | 27 +- .../data/backup/full/models/BackupChapter.kt | 53 ++-- .../data/backup/full/models/BackupManga.kt | 17 +- .../data/backup/full/models/BackupTracking.kt | 50 ++-- .../tachiyomi/data/database/DatabaseHelper.kt | 3 +- .../data/database/models/MangaCategory.kt | 9 + .../data/database/queries/HistoryQueries.kt | 42 --- .../kanade/tachiyomi/source/model/SChapter.kt | 9 + .../kanade/tachiyomi/source/model/SManga.kt | 29 ++ app/src/main/sqldelight/data/categories.sq | 28 +- app/src/main/sqldelight/data/chapters.sq | 33 +-- app/src/main/sqldelight/data/history.sq | 22 ++ app/src/main/sqldelight/data/manga_sync.sq | 28 +- app/src/main/sqldelight/data/mangas.sq | 17 ++ .../main/sqldelight/data/mangas_categories.sq | 10 +- 20 files changed, 554 insertions(+), 276 deletions(-) delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/database/queries/HistoryQueries.kt diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/AbstractBackupManager.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/AbstractBackupManager.kt index 21b0ae55f2..60e792b41c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/AbstractBackupManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/AbstractBackupManager.kt @@ -2,7 +2,8 @@ package eu.kanade.tachiyomi.data.backup import android.content.Context import android.net.Uri -import eu.kanade.tachiyomi.data.database.DatabaseHelper +import eu.kanade.data.DatabaseHandler +import eu.kanade.data.toLong import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.toMangaInfo @@ -14,23 +15,26 @@ import eu.kanade.tachiyomi.source.model.toSChapter import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get +import data.Mangas as DbManga abstract class AbstractBackupManager(protected val context: Context) { - internal val db: DatabaseHelper = Injekt.get() + protected val handler: DatabaseHandler = Injekt.get() + internal val sourceManager: SourceManager = Injekt.get() internal val trackManager: TrackManager = Injekt.get() protected val preferences: PreferencesHelper = Injekt.get() - abstract fun createBackup(uri: Uri, flags: Int, isAutoBackup: Boolean): String + abstract suspend fun createBackup(uri: Uri, flags: Int, isAutoBackup: Boolean): String /** * Returns manga * * @return [Manga], null if not found */ - internal fun getMangaFromDatabase(manga: Manga): Manga? = - db.getManga(manga.url, manga.source).executeAsBlocking() + internal suspend fun getMangaFromDatabase(url: String, source: Long): DbManga? { + return handler.awaitOneOrNull { mangasQueries.getMangaByUrlAndSource(url, source) } + } /** * Fetches chapter information. @@ -56,36 +60,134 @@ abstract class AbstractBackupManager(protected val context: Context) { * * @return [Manga] from library */ - protected fun getFavoriteManga(): List = - db.getFavoriteMangas().executeAsBlocking() + protected suspend fun getFavoriteManga(): List { + return handler.awaitList { mangasQueries.getFavorites() } + } /** * Inserts manga and returns id * * @return id of [Manga], null if not found */ - internal fun insertManga(manga: Manga): Long? = - db.insertManga(manga).executeAsBlocking().insertedId() + internal suspend fun insertManga(manga: Manga): Long { + return handler.awaitOne(true) { + mangasQueries.insert( + source = manga.source, + url = manga.url, + artist = manga.artist, + author = manga.author, + description = manga.description, + genre = manga.getGenres(), + title = manga.title, + status = manga.status.toLong(), + thumbnail_url = manga.thumbnail_url, + favorite = manga.favorite, + last_update = manga.last_update, + next_update = 0L, + initialized = manga.initialized, + viewer = manga.viewer_flags.toLong(), + chapter_flags = manga.chapter_flags.toLong(), + cover_last_modified = manga.cover_last_modified, + date_added = manga.date_added, + ) + mangasQueries.selectLastInsertedRowId() + } + } + + internal suspend fun updateManga(manga: Manga): Long { + handler.await(true) { + mangasQueries.update( + source = manga.source, + url = manga.url, + artist = manga.artist, + author = manga.author, + description = manga.description, + genre = manga.genre, + title = manga.title, + status = manga.status.toLong(), + thumbnailUrl = manga.thumbnail_url, + favorite = manga.favorite.toLong(), + lastUpdate = manga.last_update, + initialized = manga.initialized.toLong(), + viewer = manga.viewer_flags.toLong(), + chapterFlags = manga.chapter_flags.toLong(), + coverLastModified = manga.cover_last_modified, + dateAdded = manga.date_added, + mangaId = manga.id!!, + ) + } + return manga.id!! + } /** * Inserts list of chapters */ - protected fun insertChapters(chapters: List) { - db.insertChapters(chapters).executeAsBlocking() + protected suspend fun insertChapters(chapters: List) { + handler.await(true) { + chapters.forEach { chapter -> + chaptersQueries.insert( + chapter.manga_id!!, + chapter.url, + chapter.name, + chapter.scanlator, + chapter.read, + chapter.bookmark, + chapter.last_page_read.toLong(), + chapter.chapter_number, + chapter.source_order.toLong(), + chapter.date_fetch, + chapter.date_upload, + ) + } + } } /** * Updates a list of chapters */ - protected fun updateChapters(chapters: List) { - db.updateChaptersBackup(chapters).executeAsBlocking() + protected suspend fun updateChapters(chapters: List) { + handler.await(true) { + chapters.forEach { chapter -> + chaptersQueries.update( + chapter.manga_id!!, + chapter.url, + chapter.name, + chapter.scanlator, + chapter.read.toLong(), + chapter.bookmark.toLong(), + chapter.last_page_read.toLong(), + chapter.chapter_number.toDouble(), + chapter.source_order.toLong(), + chapter.date_fetch, + chapter.date_upload, + chapter.id!!, + ) + } + } } /** * Updates a list of chapters with known database ids */ - protected fun updateKnownChapters(chapters: List) { - db.updateKnownChaptersBackup(chapters).executeAsBlocking() + protected suspend fun updateKnownChapters(chapters: List) { + handler.await(true) { + chapters.forEach { chapter -> + chaptersQueries.update( + mangaId = null, + url = null, + name = null, + scanlator = null, + read = chapter.read.toLong(), + bookmark = chapter.bookmark.toLong(), + lastPageRead = chapter.last_page_read.toLong(), + chapterNumber = null, + sourceOrder = null, + dateFetch = null, + dateUpload = null, + chapterId = chapter.id!!, + ) + } + } } /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/AbstractBackupRestore.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/AbstractBackupRestore.kt index f10856ede5..779426033d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/AbstractBackupRestore.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/AbstractBackupRestore.kt @@ -2,9 +2,9 @@ package eu.kanade.tachiyomi.data.backup import android.content.Context import android.net.Uri +import eu.kanade.data.DatabaseHandler import eu.kanade.data.chapter.NoChaptersException import eu.kanade.tachiyomi.R -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.database.models.Track @@ -20,7 +20,7 @@ import java.util.Locale abstract class AbstractBackupRestore(protected val context: Context, protected val notifier: BackupNotifier) { - protected val db: DatabaseHelper by injectLazy() + protected val handler: DatabaseHandler by injectLazy() protected val trackManager: TrackManager by injectLazy() var job: Job? = null @@ -91,7 +91,22 @@ abstract class AbstractBackupRestore(protected val co if (service != null && service.isLogged) { try { val updatedTrack = service.refresh(track) - db.insertTrack(updatedTrack).executeAsBlocking() + handler.await { + manga_syncQueries.insert( + updatedTrack.manga_id, + updatedTrack.sync_id.toLong(), + updatedTrack.media_id, + updatedTrack.library_id, + updatedTrack.title, + updatedTrack.last_chapter_read.toDouble(), + updatedTrack.total_chapters.toLong(), + updatedTrack.status.toLong(), + updatedTrack.score, + updatedTrack.tracking_url, + updatedTrack.started_reading_date, + updatedTrack.finished_reading_date, + ) + } } catch (e: Exception) { errors.add(Date() to "${manga.title} - ${e.message}") } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreatorJob.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreatorJob.kt index cff2ebde73..67db545e9b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreatorJob.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreatorJob.kt @@ -3,13 +3,13 @@ package eu.kanade.tachiyomi.data.backup import android.content.Context import android.net.Uri import androidx.core.net.toUri +import androidx.work.CoroutineWorker import androidx.work.ExistingPeriodicWorkPolicy import androidx.work.ExistingWorkPolicy import androidx.work.OneTimeWorkRequestBuilder import androidx.work.PeriodicWorkRequestBuilder import androidx.work.WorkInfo import androidx.work.WorkManager -import androidx.work.Worker import androidx.work.WorkerParameters import androidx.work.workDataOf import com.hippo.unifile.UniFile @@ -24,9 +24,9 @@ import uy.kohesive.injekt.api.get import java.util.concurrent.TimeUnit class BackupCreatorJob(private val context: Context, workerParams: WorkerParameters) : - Worker(context, workerParams) { + CoroutineWorker(context, workerParams) { - override fun doWork(): Result { + override suspend fun doWork(): Result { val preferences = Injekt.get() val notifier = BackupNotifier(context) val uri = inputData.getString(LOCATION_URI_KEY)?.let { Uri.parse(it) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupManager.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupManager.kt index 6e0ed654fb..8c5074e2ff 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupManager.kt @@ -3,6 +3,9 @@ package eu.kanade.tachiyomi.data.backup.full import android.content.Context import android.net.Uri import com.hippo.unifile.UniFile +import data.Manga_sync +import data.Mangas +import eu.kanade.domain.history.model.HistoryUpdate import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.backup.AbstractBackupManager import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_CATEGORY @@ -15,17 +18,16 @@ import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_TRACK import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_TRACK_MASK import eu.kanade.tachiyomi.data.backup.full.models.Backup import eu.kanade.tachiyomi.data.backup.full.models.BackupCategory -import eu.kanade.tachiyomi.data.backup.full.models.BackupChapter import eu.kanade.tachiyomi.data.backup.full.models.BackupFull import eu.kanade.tachiyomi.data.backup.full.models.BackupHistory import eu.kanade.tachiyomi.data.backup.full.models.BackupManga import eu.kanade.tachiyomi.data.backup.full.models.BackupSerializer import eu.kanade.tachiyomi.data.backup.full.models.BackupSource -import eu.kanade.tachiyomi.data.backup.full.models.BackupTracking +import eu.kanade.tachiyomi.data.backup.full.models.backupCategoryMapper +import eu.kanade.tachiyomi.data.backup.full.models.backupChapterMapper +import eu.kanade.tachiyomi.data.backup.full.models.backupTrackMapper import eu.kanade.tachiyomi.data.database.models.Chapter -import eu.kanade.tachiyomi.data.database.models.History import eu.kanade.tachiyomi.data.database.models.Manga -import eu.kanade.tachiyomi.data.database.models.MangaCategory import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.util.system.logcat import kotlinx.serialization.protobuf.ProtoBuf @@ -34,6 +36,7 @@ import okio.buffer import okio.gzip import okio.sink import java.io.FileOutputStream +import java.util.Date import kotlin.math.max class FullBackupManager(context: Context) : AbstractBackupManager(context) { @@ -46,20 +49,18 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) { * @param uri path of Uri * @param isAutoBackup backup called from scheduled backup job */ - override fun createBackup(uri: Uri, flags: Int, isAutoBackup: Boolean): String { + override suspend fun createBackup(uri: Uri, flags: Int, isAutoBackup: Boolean): String { // Create root object var backup: Backup? = null - db.inTransaction { - val databaseManga = getFavoriteManga() + val databaseManga = getFavoriteManga() - backup = Backup( - backupManga(databaseManga, flags), - backupCategories(flags), - emptyList(), - backupExtensionInfo(databaseManga), - ) - } + backup = Backup( + backupManga(databaseManga, flags), + backupCategories(flags), + emptyList(), + backupExtensionInfo(databaseManga), + ) var file: UniFile? = null try { @@ -112,13 +113,13 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) { } } - private fun backupManga(mangas: List, flags: Int): List { + private suspend fun backupManga(mangas: List, flags: Int): List { return mangas.map { backupMangaObject(it, flags) } } - private fun backupExtensionInfo(mangas: List): List { + private fun backupExtensionInfo(mangas: List): List { return mangas .asSequence() .map { it.source } @@ -133,12 +134,10 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) { * * @return list of [BackupCategory] to be backed up */ - private fun backupCategories(options: Int): List { + private suspend fun backupCategories(options: Int): List { // Check if user wants category information in backup return if (options and BACKUP_CATEGORY_MASK == BACKUP_CATEGORY) { - db.getCategories() - .executeAsBlocking() - .map { BackupCategory.copyFrom(it) } + handler.awaitList { categoriesQueries.getCategories(backupCategoryMapper) } } else { emptyList() } @@ -151,43 +150,43 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) { * @param options options for the backup * @return [BackupManga] containing manga in a serializable form */ - private fun backupMangaObject(manga: Manga, options: Int): BackupManga { + private suspend fun backupMangaObject(manga: Mangas, options: Int): BackupManga { // Entry for this manga val mangaObject = BackupManga.copyFrom(manga) // Check if user wants chapter information in backup if (options and BACKUP_CHAPTER_MASK == BACKUP_CHAPTER) { // Backup all the chapters - val chapters = db.getChapters(manga).executeAsBlocking() + val chapters = handler.awaitList { chaptersQueries.getChaptersByMangaId(manga._id, backupChapterMapper) } if (chapters.isNotEmpty()) { - mangaObject.chapters = chapters.map { BackupChapter.copyFrom(it) } + mangaObject.chapters = chapters } } // Check if user wants category information in backup if (options and BACKUP_CATEGORY_MASK == BACKUP_CATEGORY) { // Backup categories for this manga - val categoriesForManga = db.getCategoriesForManga(manga).executeAsBlocking() + val categoriesForManga = handler.awaitList { categoriesQueries.getCategoriesByMangaId(manga._id) } if (categoriesForManga.isNotEmpty()) { - mangaObject.categories = categoriesForManga.mapNotNull { it.order } + mangaObject.categories = categoriesForManga.map { it.order } } } // Check if user wants track information in backup if (options and BACKUP_TRACK_MASK == BACKUP_TRACK) { - val tracks = db.getTracks(manga.id).executeAsBlocking() + val tracks = handler.awaitList { manga_syncQueries.getTracksByMangaId(manga._id, backupTrackMapper) } if (tracks.isNotEmpty()) { - mangaObject.tracking = tracks.map { BackupTracking.copyFrom(it) } + mangaObject.tracking = tracks } } // Check if user wants history information in backup if (options and BACKUP_HISTORY_MASK == BACKUP_HISTORY) { - val historyForManga = db.getHistoryByMangaId(manga.id!!).executeAsBlocking() - if (historyForManga.isNotEmpty()) { - val history = historyForManga.mapNotNull { history -> - val url = db.getChapter(history.chapter_id).executeAsBlocking()?.url - url?.let { BackupHistory(url, history.last_read) } + val historyByMangaId = handler.awaitList(true) { historyQueries.getHistoryByMangaId(manga._id) } + if (historyByMangaId.isNotEmpty()) { + val history = historyByMangaId.map { history -> + val chapter = handler.awaitOne { chaptersQueries.getChapterById(history.chapter_id) } + BackupHistory(chapter.url, history.last_read?.time ?: 0L) } if (history.isNotEmpty()) { mangaObject.history = history @@ -198,10 +197,10 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) { return mangaObject } - fun restoreMangaNoFetch(manga: Manga, dbManga: Manga) { - manga.id = dbManga.id + suspend fun restoreMangaNoFetch(manga: Manga, dbManga: Mangas) { + manga.id = dbManga._id manga.copyFrom(dbManga) - insertManga(manga) + updateManga(manga) } /** @@ -210,7 +209,7 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) { * @param manga manga that needs updating * @return Updated manga info. */ - fun restoreManga(manga: Manga): Manga { + suspend fun restoreManga(manga: Manga): Manga { return manga.also { it.initialized = it.description != null it.id = insertManga(it) @@ -222,32 +221,36 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) { * * @param backupCategories list containing categories */ - internal fun restoreCategories(backupCategories: List) { + internal suspend fun restoreCategories(backupCategories: List) { // Get categories from file and from db - val dbCategories = db.getCategories().executeAsBlocking() + val dbCategories = handler.awaitList { categoriesQueries.getCategories() } // Iterate over them - backupCategories.map { it.getCategoryImpl() }.forEach { category -> - // Used to know if the category is already in the db - var found = false - for (dbCategory in dbCategories) { - // If the category is already in the db, assign the id to the file's category - // and do nothing - if (category.name == dbCategory.name) { - category.id = dbCategory.id - found = true - break + backupCategories + .map { it.getCategoryImpl() } + .forEach { category -> + // Used to know if the category is already in the db + var found = false + for (dbCategory in dbCategories) { + // If the category is already in the db, assign the id to the file's category + // and do nothing + if (category.name == dbCategory.name) { + category.id = dbCategory.id.toInt() + found = true + break + } + } + // If the category isn't in the db, remove the id and insert a new category + // Store the inserted id in the category + if (!found) { + // Let the db assign the id + category.id = null + category.id = handler.awaitOne { + categoriesQueries.insert(category.name, category.order.toLong(), category.flags.toLong()) + categoriesQueries.selectLastInsertedRowId() + }.toInt() } } - // If the category isn't in the db, remove the id and insert a new category - // Store the inserted id in the category - if (!found) { - // Let the db assign the id - category.id = null - val result = db.insertCategory(category).executeAsBlocking() - category.id = result.insertedId()?.toInt() - } - } } /** @@ -256,25 +259,30 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) { * @param manga the manga whose categories have to be restored. * @param categories the categories to restore. */ - internal fun restoreCategoriesForManga(manga: Manga, categories: List, backupCategories: List) { - val dbCategories = db.getCategories().executeAsBlocking() - val mangaCategoriesToUpdate = ArrayList(categories.size) + internal suspend fun restoreCategoriesForManga(manga: Manga, categories: List, backupCategories: List) { + val dbCategories = handler.awaitList { categoriesQueries.getCategories() } + val mangaCategoriesToUpdate = mutableListOf>() + categories.forEach { backupCategoryOrder -> backupCategories.firstOrNull { - it.order == backupCategoryOrder + it.order == backupCategoryOrder.toLong() }?.let { backupCategory -> dbCategories.firstOrNull { dbCategory -> dbCategory.name == backupCategory.name }?.let { dbCategory -> - mangaCategoriesToUpdate += MangaCategory.create(manga, dbCategory) + mangaCategoriesToUpdate.add(Pair(manga.id!!, dbCategory.id)) } } } // Update database if (mangaCategoriesToUpdate.isNotEmpty()) { - db.deleteOldMangasCategories(listOf(manga)).executeAsBlocking() - db.insertMangasCategories(mangaCategoriesToUpdate).executeAsBlocking() + handler.await(true) { + mangas_categoriesQueries.deleteMangaCategoryByMangaId(manga.id!!) + mangaCategoriesToUpdate.forEach { (mangaId, categoryId) -> + mangas_categoriesQueries.insert(mangaId, categoryId) + } + } } } @@ -283,28 +291,43 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) { * * @param history list containing history to be restored */ - internal fun restoreHistoryForManga(history: List) { + internal suspend fun restoreHistoryForManga(history: List) { // List containing history to be updated - val historyToBeUpdated = ArrayList(history.size) + val toUpdate = mutableListOf() for ((url, lastRead) in history) { - val dbHistory = db.getHistoryByChapterUrl(url).executeAsBlocking() + var dbHistory = handler.awaitOneOrNull { historyQueries.getHistoryByChapterUrl(url) } // Check if history already in database and update if (dbHistory != null) { - dbHistory.apply { - last_read = max(lastRead, dbHistory.last_read) - } - historyToBeUpdated.add(dbHistory) + dbHistory = dbHistory.copy(last_read = Date(max(lastRead, dbHistory.last_read?.time ?: 0L))) + toUpdate.add( + HistoryUpdate( + chapterId = dbHistory.chapter_id, + readAt = dbHistory.last_read!!, + sessionReadDuration = dbHistory.time_read, + ), + ) } else { // If not in database create - db.getChapter(url).executeAsBlocking()?.let { - val historyToAdd = History.create(it).apply { - last_read = lastRead + handler + .awaitOneOrNull { chaptersQueries.getChapterByUrl(url) } + ?.let { + HistoryUpdate( + chapterId = it._id, + readAt = Date(lastRead), + sessionReadDuration = 0, + ) } - historyToBeUpdated.add(historyToAdd) - } } } - db.upsertHistoryLastRead(historyToBeUpdated).executeAsBlocking() + handler.await(true) { + toUpdate.forEach { payload -> + historyQueries.upsert( + payload.chapterId, + payload.readAt, + payload.sessionReadDuration, + ) + } + } } /** @@ -313,56 +336,97 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) { * @param manga the manga whose sync have to be restored. * @param tracks the track list to restore. */ - internal fun restoreTrackForManga(manga: Manga, tracks: List) { + internal suspend fun restoreTrackForManga(manga: Manga, tracks: List) { // Fix foreign keys with the current manga id tracks.map { it.manga_id = manga.id!! } // Get tracks from database - val dbTracks = db.getTracks(manga.id).executeAsBlocking() - val trackToUpdate = mutableListOf() + + val dbTracks = handler.awaitList { manga_syncQueries.getTracksByMangaId(manga.id!!) } + val toUpdate = mutableListOf() + val toInsert = mutableListOf() tracks.forEach { track -> var isInDatabase = false for (dbTrack in dbTracks) { - if (track.sync_id == dbTrack.sync_id) { + if (track.sync_id == dbTrack.sync_id.toInt()) { // The sync is already in the db, only update its fields - if (track.media_id != dbTrack.media_id) { - dbTrack.media_id = track.media_id + var temp = dbTrack + if (track.media_id != dbTrack.remote_id) { + temp = temp.copy(remote_id = track.media_id) } if (track.library_id != dbTrack.library_id) { - dbTrack.library_id = track.library_id + temp = temp.copy(library_id = track.library_id) } - dbTrack.last_chapter_read = max(dbTrack.last_chapter_read, track.last_chapter_read) + temp = temp.copy(last_chapter_read = max(dbTrack.last_chapter_read, track.last_chapter_read.toDouble())) isInDatabase = true - trackToUpdate.add(dbTrack) + toUpdate.add(temp) break } } if (!isInDatabase) { // Insert new sync. Let the db assign the id track.id = null - trackToUpdate.add(track) + toInsert.add(track) } } // Update database - if (trackToUpdate.isNotEmpty()) { - db.insertTracks(trackToUpdate).executeAsBlocking() + if (toUpdate.isNotEmpty()) { + handler.await(true) { + toUpdate.forEach { track -> + manga_syncQueries.update( + track.manga_id, + track.sync_id, + track.remote_id, + track.library_id, + track.title, + track.last_chapter_read, + track.total_chapters, + track.status, + track.score.toDouble(), + track.remote_url, + track.start_date, + track.finish_date, + track._id, + ) + } + } + } + if (toInsert.isNotEmpty()) { + handler.await(true) { + toInsert.forEach { track -> + manga_syncQueries.insert( + track.manga_id, + track.sync_id.toLong(), + track.media_id, + track.library_id, + track.title, + track.last_chapter_read.toDouble(), + track.total_chapters.toLong(), + track.status.toLong(), + track.score, + track.tracking_url, + track.started_reading_date, + track.finished_reading_date, + ) + } + } } } - internal fun restoreChaptersForManga(manga: Manga, chapters: List) { - val dbChapters = db.getChapters(manga).executeAsBlocking() + internal suspend fun restoreChaptersForManga(manga: Manga, chapters: List) { + val dbChapters = handler.awaitList { chaptersQueries.getChaptersByMangaId(manga.id!!) } chapters.forEach { chapter -> val dbChapter = dbChapters.find { it.url == chapter.url } if (dbChapter != null) { - chapter.id = dbChapter.id + chapter.id = dbChapter._id chapter.copyFrom(dbChapter) if (dbChapter.read && !chapter.read) { chapter.read = dbChapter.read - chapter.last_page_read = dbChapter.last_page_read - } else if (chapter.last_page_read == 0 && dbChapter.last_page_read != 0) { - chapter.last_page_read = dbChapter.last_page_read + chapter.last_page_read = dbChapter.last_page_read.toInt() + } else if (chapter.last_page_read == 0 && dbChapter.last_page_read != 0L) { + chapter.last_page_read = dbChapter.last_page_read.toInt() } if (!chapter.bookmark && dbChapter.bookmark) { chapter.bookmark = dbChapter.bookmark diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupRestore.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupRestore.kt index acb633abbc..4d5944508f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupRestore.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupRestore.kt @@ -51,19 +51,17 @@ class FullBackupRestore(context: Context, notifier: BackupNotifier) : AbstractBa return true } - private fun restoreCategories(backupCategories: List) { - db.inTransaction { - backupManager.restoreCategories(backupCategories) - } + private suspend fun restoreCategories(backupCategories: List) { + backupManager.restoreCategories(backupCategories) restoreProgress += 1 showRestoreProgress(restoreProgress, restoreAmount, context.getString(R.string.categories)) } - private fun restoreManga(backupManga: BackupManga, backupCategories: List) { + private suspend fun restoreManga(backupManga: BackupManga, backupCategories: List) { val manga = backupManga.getMangaImpl() val chapters = backupManga.getChaptersImpl() - val categories = backupManga.categories + val categories = backupManga.categories.map { it.toInt() } val history = backupManga.brokenHistory.map { BackupHistory(it.url, it.lastRead) } + backupManga.history val tracks = backupManga.getTrackingImpl() @@ -87,7 +85,7 @@ class FullBackupRestore(context: Context, notifier: BackupNotifier) : AbstractBa * @param history history data from json * @param tracks tracking data from json */ - private fun restoreMangaData( + private suspend fun restoreMangaData( manga: Manga, chapters: List, categories: List, @@ -95,18 +93,16 @@ class FullBackupRestore(context: Context, notifier: BackupNotifier) : AbstractBa tracks: List, backupCategories: List, ) { - db.inTransaction { - val dbManga = backupManager.getMangaFromDatabase(manga) - if (dbManga == null) { - // Manga not in database - restoreMangaFetch(manga, chapters, categories, history, tracks, backupCategories) - } else { - // Manga in database - // Copy information from manga already in database - backupManager.restoreMangaNoFetch(manga, dbManga) - // Fetch rest of manga information - restoreMangaNoFetch(manga, chapters, categories, history, tracks, backupCategories) - } + val dbManga = backupManager.getMangaFromDatabase(manga.url, manga.source) + if (dbManga == null) { + // Manga not in database + restoreMangaFetch(manga, chapters, categories, history, tracks, backupCategories) + } else { + // Manga in database + // Copy information from manga already in database + backupManager.restoreMangaNoFetch(manga, dbManga) + // Fetch rest of manga information + restoreMangaNoFetch(manga, chapters, categories, history, tracks, backupCategories) } } @@ -117,7 +113,7 @@ class FullBackupRestore(context: Context, notifier: BackupNotifier) : AbstractBa * @param chapters chapters of manga that needs updating * @param categories categories that need updating */ - private fun restoreMangaFetch( + private suspend fun restoreMangaFetch( manga: Manga, chapters: List, categories: List, @@ -137,7 +133,7 @@ class FullBackupRestore(context: Context, notifier: BackupNotifier) : AbstractBa } } - private fun restoreMangaNoFetch( + private suspend fun restoreMangaNoFetch( backupManga: Manga, chapters: List, categories: List, @@ -150,7 +146,7 @@ class FullBackupRestore(context: Context, notifier: BackupNotifier) : AbstractBa restoreExtraForManga(backupManga, categories, history, tracks, backupCategories) } - private fun restoreExtraForManga(manga: Manga, categories: List, history: List, tracks: List, backupCategories: List) { + private suspend fun restoreExtraForManga(manga: Manga, categories: List, history: List, tracks: List, backupCategories: List) { // Restore categories backupManager.restoreCategoriesForManga(manga, categories, backupCategories) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupCategory.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupCategory.kt index 96cb2b7d1e..88bc7ec208 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupCategory.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupCategory.kt @@ -1,6 +1,5 @@ package eu.kanade.tachiyomi.data.backup.full.models -import eu.kanade.tachiyomi.data.database.models.Category import eu.kanade.tachiyomi.data.database.models.CategoryImpl import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoNumber @@ -8,26 +7,24 @@ import kotlinx.serialization.protobuf.ProtoNumber @Serializable class BackupCategory( @ProtoNumber(1) var name: String, - @ProtoNumber(2) var order: Int = 0, + @ProtoNumber(2) var order: Long = 0, // @ProtoNumber(3) val updateInterval: Int = 0, 1.x value not used in 0.x // Bump by 100 to specify this is a 0.x value - @ProtoNumber(100) var flags: Int = 0, + @ProtoNumber(100) var flags: Long = 0, ) { fun getCategoryImpl(): CategoryImpl { return CategoryImpl().apply { name = this@BackupCategory.name - flags = this@BackupCategory.flags - order = this@BackupCategory.order - } - } - - companion object { - fun copyFrom(category: Category): BackupCategory { - return BackupCategory( - name = category.name, - order = category.order, - flags = category.flags, - ) + flags = this@BackupCategory.flags.toInt() + order = this@BackupCategory.order.toInt() } } } + +val backupCategoryMapper = { _: Long, name: String, order: Long, flags: Long -> + BackupCategory( + name = name, + order = order, + flags = flags, + ) +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupChapter.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupChapter.kt index ca52145338..ffca3a2556 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupChapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupChapter.kt @@ -1,6 +1,5 @@ package eu.kanade.tachiyomi.data.backup.full.models -import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.ChapterImpl import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoNumber @@ -15,12 +14,12 @@ data class BackupChapter( @ProtoNumber(4) var read: Boolean = false, @ProtoNumber(5) var bookmark: Boolean = false, // lastPageRead is called progress in 1.x - @ProtoNumber(6) var lastPageRead: Int = 0, + @ProtoNumber(6) var lastPageRead: Long = 0, @ProtoNumber(7) var dateFetch: Long = 0, @ProtoNumber(8) var dateUpload: Long = 0, // chapterNumber is called number is 1.x @ProtoNumber(9) var chapterNumber: Float = 0F, - @ProtoNumber(10) var sourceOrder: Int = 0, + @ProtoNumber(10) var sourceOrder: Long = 0, ) { fun toChapterImpl(): ChapterImpl { return ChapterImpl().apply { @@ -30,27 +29,37 @@ data class BackupChapter( scanlator = this@BackupChapter.scanlator read = this@BackupChapter.read bookmark = this@BackupChapter.bookmark - last_page_read = this@BackupChapter.lastPageRead + last_page_read = this@BackupChapter.lastPageRead.toInt() date_fetch = this@BackupChapter.dateFetch date_upload = this@BackupChapter.dateUpload - source_order = this@BackupChapter.sourceOrder - } - } - - companion object { - fun copyFrom(chapter: Chapter): BackupChapter { - return BackupChapter( - url = chapter.url, - name = chapter.name, - chapterNumber = chapter.chapter_number, - scanlator = chapter.scanlator, - read = chapter.read, - bookmark = chapter.bookmark, - lastPageRead = chapter.last_page_read, - dateFetch = chapter.date_fetch, - dateUpload = chapter.date_upload, - sourceOrder = chapter.source_order, - ) + source_order = this@BackupChapter.sourceOrder.toInt() } } } + +val backupChapterMapper = { + _: Long, + _: Long, + url: String, + name: String, + scanlator: String?, + read: Boolean, + bookmark: Boolean, + lastPageRead: Long, + chapterNumber: Float, + source_order: Long, + dateFetch: Long, + dateUpload: Long, -> + BackupChapter( + url = url, + name = name, + chapterNumber = chapterNumber, + scanlator = scanlator, + read = read, + bookmark = bookmark, + lastPageRead = lastPageRead, + dateFetch = dateFetch, + dateUpload = dateUpload, + sourceOrder = source_order, + ) +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupManga.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupManga.kt index fcc24626a7..86496dc8a6 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupManga.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupManga.kt @@ -1,9 +1,10 @@ package eu.kanade.tachiyomi.data.backup.full.models +import data.Mangas import eu.kanade.tachiyomi.data.database.models.ChapterImpl -import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.MangaImpl import eu.kanade.tachiyomi.data.database.models.TrackImpl +import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoNumber @@ -28,7 +29,7 @@ data class BackupManga( @ProtoNumber(14) var viewer: Int = 0, // Replaced by viewer_flags // @ProtoNumber(15) val flags: Int = 0, 1.x value, not used in 0.x @ProtoNumber(16) var chapters: List = emptyList(), - @ProtoNumber(17) var categories: List = emptyList(), + @ProtoNumber(17) var categories: List = emptyList(), @ProtoNumber(18) var tracking: List = emptyList(), // Bump by 100 for values that are not saved/implemented in 1.x but are used in 0.x @ProtoNumber(100) var favorite: Boolean = true, @@ -68,22 +69,22 @@ data class BackupManga( } companion object { - fun copyFrom(manga: Manga): BackupManga { + fun copyFrom(manga: Mangas): BackupManga { return BackupManga( url = manga.url, title = manga.title, artist = manga.artist, author = manga.author, description = manga.description, - genre = manga.getGenres() ?: emptyList(), - status = manga.status, + genre = manga.genre ?: emptyList(), + status = manga.status.toInt(), thumbnailUrl = manga.thumbnail_url, favorite = manga.favorite, source = manga.source, dateAdded = manga.date_added, - viewer = manga.readingModeType, - viewer_flags = manga.viewer_flags, - chapterFlags = manga.chapter_flags, + viewer = (manga.viewer.toInt() and ReadingModeType.MASK), + viewer_flags = manga.viewer.toInt(), + chapterFlags = manga.chapter_flags.toInt(), ) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupTracking.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupTracking.kt index 5e45f86635..e87cd342d7 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupTracking.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupTracking.kt @@ -1,6 +1,5 @@ package eu.kanade.tachiyomi.data.backup.full.models -import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.database.models.TrackImpl import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoNumber @@ -48,23 +47,34 @@ data class BackupTracking( tracking_url = this@BackupTracking.trackingUrl } } - - companion object { - fun copyFrom(track: Track): BackupTracking { - return BackupTracking( - syncId = track.sync_id, - mediaId = track.media_id, - // forced not null so its compatible with 1.x backup system - libraryId = track.library_id!!, - title = track.title, - lastChapterRead = track.last_chapter_read, - totalChapters = track.total_chapters, - score = track.score, - status = track.status, - startedReadingDate = track.started_reading_date, - finishedReadingDate = track.finished_reading_date, - trackingUrl = track.tracking_url, - ) - } - } +} + +val backupTrackMapper = { + _id: Long, + manga_id: Long, + syncId: Long, + mediaId: Long, + libraryId: Long?, + title: String, + lastChapterRead: Double, + totalChapters: Long, + status: Long, + score: Float, + remoteUrl: String, + startDate: Long, + finishDate: Long, -> + BackupTracking( + syncId = syncId.toInt(), + mediaId = mediaId, + // forced not null so its compatible with 1.x backup system + libraryId = libraryId ?: 0, + title = title, + lastChapterRead = lastChapterRead.toFloat(), + totalChapters = totalChapters.toInt(), + score = score, + status = status.toInt(), + startedReadingDate = startDate, + finishedReadingDate = finishDate, + trackingUrl = remoteUrl, + ) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/DatabaseHelper.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/DatabaseHelper.kt index ff38f1bcd7..e87fa54f55 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/DatabaseHelper.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/DatabaseHelper.kt @@ -16,7 +16,6 @@ import eu.kanade.tachiyomi.data.database.models.MangaCategory import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.database.queries.CategoryQueries import eu.kanade.tachiyomi.data.database.queries.ChapterQueries -import eu.kanade.tachiyomi.data.database.queries.HistoryQueries import eu.kanade.tachiyomi.data.database.queries.MangaCategoryQueries import eu.kanade.tachiyomi.data.database.queries.MangaQueries import eu.kanade.tachiyomi.data.database.queries.TrackQueries @@ -27,7 +26,7 @@ import eu.kanade.tachiyomi.data.database.queries.TrackQueries class DatabaseHelper( openHelper: SupportSQLiteOpenHelper, ) : - MangaQueries, ChapterQueries, TrackQueries, CategoryQueries, MangaCategoryQueries, HistoryQueries { + MangaQueries, ChapterQueries, TrackQueries, CategoryQueries, MangaCategoryQueries { override val db = DefaultStorIOSQLite.builder() .sqliteOpenHelper(openHelper) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaCategory.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaCategory.kt index 2203370884..90dacc8788 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaCategory.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaCategory.kt @@ -1,5 +1,7 @@ package eu.kanade.tachiyomi.data.database.models +import data.GetCategories + class MangaCategory { var id: Long? = null @@ -16,5 +18,12 @@ class MangaCategory { mc.category_id = category.id!! return mc } + + fun create(manga: Manga, category: GetCategories): MangaCategory { + val mc = MangaCategory() + mc.manga_id = manga.id!! + mc.category_id = category.id.toInt() + return mc + } } } 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 deleted file mode 100644 index 9e54cc1d1d..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/HistoryQueries.kt +++ /dev/null @@ -1,42 +0,0 @@ -package eu.kanade.tachiyomi.data.database.queries - -import com.pushtorefresh.storio.sqlite.queries.RawQuery -import eu.kanade.tachiyomi.data.database.DbProvider -import eu.kanade.tachiyomi.data.database.models.History -import eu.kanade.tachiyomi.data.database.resolvers.HistoryUpsertResolver -import eu.kanade.tachiyomi.data.database.tables.HistoryTable - -interface HistoryQueries : DbProvider { - - fun getHistoryByMangaId(mangaId: Long) = db.get() - .listOfObjects(History::class.java) - .withQuery( - RawQuery.builder() - .query(getHistoryByMangaId()) - .args(mangaId) - .observesTables(HistoryTable.TABLE) - .build(), - ) - .prepare() - - fun getHistoryByChapterUrl(chapterUrl: String) = db.get() - .`object`(History::class.java) - .withQuery( - RawQuery.builder() - .query(getHistoryByChapterUrl()) - .args(chapterUrl) - .observesTables(HistoryTable.TABLE) - .build(), - ) - .prepare() - - /** - * Updates the history last read. - * Inserts history object if not yet in database - * @param historyList history object list - */ - fun upsertHistoryLastRead(historyList: List) = db.put() - .objects(historyList) - .withPutResolver(HistoryUpsertResolver()) - .prepare() -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/model/SChapter.kt b/app/src/main/java/eu/kanade/tachiyomi/source/model/SChapter.kt index f7e8a14add..62fc1f9d14 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/source/model/SChapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/model/SChapter.kt @@ -1,5 +1,6 @@ package eu.kanade.tachiyomi.source.model +import data.Chapters import tachiyomi.source.model.ChapterInfo import java.io.Serializable @@ -23,6 +24,14 @@ interface SChapter : Serializable { scanlator = other.scanlator } + fun copyFrom(other: Chapters) { + name = other.name + url = other.url + date_upload = other.date_upload + chapter_number = other.chapter_number + scanlator = other.scanlator + } + companion object { fun create(): SChapter { return SChapterImpl() diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/model/SManga.kt b/app/src/main/java/eu/kanade/tachiyomi/source/model/SManga.kt index 489938aba2..927faa2ea0 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/source/model/SManga.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/model/SManga.kt @@ -1,5 +1,6 @@ package eu.kanade.tachiyomi.source.model +import data.Mangas import tachiyomi.source.model.MangaInfo import java.io.Serializable @@ -51,6 +52,34 @@ interface SManga : Serializable { } } + fun copyFrom(other: Mangas) { + if (other.author != null) { + author = other.author + } + + if (other.artist != null) { + artist = other.artist + } + + if (other.description != null) { + description = other.description + } + + if (other.genre != null) { + genre = other.genre.joinToString(separator = ", ") + } + + if (other.thumbnail_url != null) { + thumbnail_url = other.thumbnail_url + } + + status = other.status.toInt() + + if (!initialized) { + initialized = other.initialized + } + } + companion object { const val UNKNOWN = 0 const val ONGOING = 1 diff --git a/app/src/main/sqldelight/data/categories.sq b/app/src/main/sqldelight/data/categories.sq index 628b6df7fa..eb7baf3519 100644 --- a/app/src/main/sqldelight/data/categories.sq +++ b/app/src/main/sqldelight/data/categories.sq @@ -3,4 +3,30 @@ CREATE TABLE categories( name TEXT NOT NULL, sort INTEGER NOT NULL, flags INTEGER NOT NULL -); \ No newline at end of file +); + +getCategories: +SELECT +_id AS id, +name, +sort AS `order`, +flags +FROM categories; + +getCategoriesByMangaId: +SELECT +C._id AS id, +C.name, +C.sort AS `order`, +C.flags +FROM categories C +JOIN mangas_categories MC +ON C._id = MC.category_id +WHERE MC.manga_id = :mangaId; + +insert: +INSERT INTO categories(name, sort, flags) +VALUES (:name, :order, :flags); + +selectLastInsertedRowId: +SELECT last_insert_rowid(); \ No newline at end of file diff --git a/app/src/main/sqldelight/data/chapters.sq b/app/src/main/sqldelight/data/chapters.sq index dd71cc8631..ec80bba3ac 100644 --- a/app/src/main/sqldelight/data/chapters.sq +++ b/app/src/main/sqldelight/data/chapters.sq @@ -28,37 +28,18 @@ SELECT * FROM chapters WHERE manga_id = :mangaId; +getChapterByUrl: +SELECT * +FROM chapters +WHERE url = :chapterUrl; + removeChaptersWithIds: DELETE FROM chapters WHERE _id IN :chapterIds; insert: -INSERT INTO chapters( - manga_id, - url, - name, - scanlator, - read, - bookmark, - last_page_read, - chapter_number, - source_order, - date_fetch, - date_upload -) -VALUES ( - :mangaId, - :url, - :name, - :scanlator, - :read, - :bookmark, - :lastPageRead, - :chapterNumber, - :sourceOrder, - :dateFetch, - :dateUpload -); +INSERT INTO chapters(manga_id,url,name,scanlator,read,bookmark,last_page_read,chapter_number,source_order,date_fetch,date_upload) +VALUES (:mangaId,:url,:name,:scanlator,:read,:bookmark,:lastPageRead,:chapterNumber,:sourceOrder,:dateFetch,:dateUpload); update: UPDATE chapters diff --git a/app/src/main/sqldelight/data/history.sq b/app/src/main/sqldelight/data/history.sq index 5b505bba6c..810c8d6735 100644 --- a/app/src/main/sqldelight/data/history.sq +++ b/app/src/main/sqldelight/data/history.sq @@ -11,6 +11,28 @@ CREATE TABLE history( CREATE INDEX history_history_chapter_id_index ON history(chapter_id); +getHistoryByMangaId: +SELECT +H._id, +H.chapter_id, +H.last_read, +H.time_read +FROM history H +JOIN chapters C +ON H.chapter_id = C._id +WHERE C.manga_id = :mangaId AND C._id = H.chapter_id; + +getHistoryByChapterUrl: +SELECT +H._id, +H.chapter_id, +H.last_read, +H.time_read +FROM history H +JOIN chapters C +ON H.chapter_id = C._id +WHERE C.url = :chapterUrl AND C._id = H.chapter_id; + resetHistoryById: UPDATE history SET last_read = 0 diff --git a/app/src/main/sqldelight/data/manga_sync.sq b/app/src/main/sqldelight/data/manga_sync.sq index dcd18442bf..dd34ef8577 100644 --- a/app/src/main/sqldelight/data/manga_sync.sq +++ b/app/src/main/sqldelight/data/manga_sync.sq @@ -15,4 +15,30 @@ CREATE TABLE manga_sync( UNIQUE (manga_id, sync_id) ON CONFLICT REPLACE, FOREIGN KEY(manga_id) REFERENCES mangas (_id) ON DELETE CASCADE -); \ No newline at end of file +); + +getTracksByMangaId: +SELECT * +FROM manga_sync +WHERE manga_id = :mangaId; + +insert: +INSERT INTO manga_sync(manga_id,sync_id,remote_id,library_id,title,last_chapter_read,total_chapters,status,score,remote_url,start_date,finish_date) +VALUES (:mangaId,:syncId,:remoteId,:libraryId,:title,:lastChapterRead,:totalChapters,:status,:score,:remoteUrl,:startDate,:finishDate); + +update: +UPDATE manga_sync +SET + manga_id = coalesce(:mangaId, manga_id), + sync_id = coalesce(:syncId, sync_id), + remote_id = coalesce(:mediaId, remote_id), + library_id = coalesce(:libraryId, library_id), + title = coalesce(:title, title), + last_chapter_read = coalesce(:lastChapterRead, last_chapter_read), + total_chapters = coalesce(:totalChapter, total_chapters), + status = coalesce(:status, status), + score = coalesce(:score, score), + remote_url = coalesce(:trackingUrl, remote_url), + start_date = coalesce(:startDate, start_date), + finish_date = coalesce(:finishDate, finish_date) +WHERE _id = :id; diff --git a/app/src/main/sqldelight/data/mangas.sq b/app/src/main/sqldelight/data/mangas.sq index c042853ace..a20f32a49a 100644 --- a/app/src/main/sqldelight/data/mangas.sq +++ b/app/src/main/sqldelight/data/mangas.sq @@ -25,11 +25,25 @@ CREATE TABLE mangas( CREATE INDEX library_favorite_index ON mangas(favorite) WHERE favorite = 1; CREATE INDEX mangas_url_index ON mangas(url); +insert: +INSERT INTO mangas(source,url,artist,author,description,genre,title,status,thumbnail_url,favorite,last_update,next_update,initialized,viewer,chapter_flags,cover_last_modified,date_added) +VALUES (:source,:url,:artist,:author,:description,:genre,:title,:status,:thumbnail_url,:favorite,:last_update,:next_update,:initialized,:viewer,:chapter_flags,:cover_last_modified,:date_added); + getMangaById: SELECT * FROM mangas WHERE _id = :id; +getMangaByUrlAndSource: +SELECT * +FROM mangas +WHERE url = :url AND source = :source; + +getFavorites: +SELECT * +FROM mangas +WHERE favorite = 1; + getSourceIdWithFavoriteCount: SELECT source, @@ -77,3 +91,6 @@ UPDATE mangas SET cover_last_modified = coalesce(:coverLastModified, cover_last_modified), date_added = coalesce(:dateAdded, date_added) WHERE _id = :mangaId; + +selectLastInsertedRowId: +SELECT last_insert_rowid(); diff --git a/app/src/main/sqldelight/data/mangas_categories.sq b/app/src/main/sqldelight/data/mangas_categories.sq index 6db91fe162..a97c9d3ca9 100644 --- a/app/src/main/sqldelight/data/mangas_categories.sq +++ b/app/src/main/sqldelight/data/mangas_categories.sq @@ -6,4 +6,12 @@ CREATE TABLE mangas_categories( ON DELETE CASCADE, FOREIGN KEY(manga_id) REFERENCES mangas (_id) ON DELETE CASCADE -); \ No newline at end of file +); + +insert: +INSERT INTO mangas_categories(manga_id, category_id) +VALUES (:mangaId, :categoryId); + +deleteMangaCategoryByMangaId: +DELETE FROM mangas_categories +WHERE manga_id = :mangaId; \ No newline at end of file