Prechádzať zdrojové kódy

Use SQLDelight in Backup/Restore (#7295)

* Use SQLDelight in Backup/Restore

* Use CoroutineWorker
Andreas 2 rokov pred
rodič
commit
fd5da2de3a
20 zmenil súbory, kde vykonal 551 pridanie a 273 odobranie
  1. 117 15
      app/src/main/java/eu/kanade/tachiyomi/data/backup/AbstractBackupManager.kt
  2. 18 3
      app/src/main/java/eu/kanade/tachiyomi/data/backup/AbstractBackupRestore.kt
  3. 3 3
      app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreatorJob.kt
  4. 159 95
      app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupManager.kt
  5. 18 22
      app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupRestore.kt
  6. 11 14
      app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupCategory.kt
  7. 30 21
      app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupChapter.kt
  8. 9 8
      app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupManga.kt
  9. 29 19
      app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupTracking.kt
  10. 1 2
      app/src/main/java/eu/kanade/tachiyomi/data/database/DatabaseHelper.kt
  11. 9 0
      app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaCategory.kt
  12. 0 42
      app/src/main/java/eu/kanade/tachiyomi/data/database/queries/HistoryQueries.kt
  13. 9 0
      app/src/main/java/eu/kanade/tachiyomi/source/model/SChapter.kt
  14. 29 0
      app/src/main/java/eu/kanade/tachiyomi/source/model/SManga.kt
  15. 27 1
      app/src/main/sqldelight/data/categories.sq
  16. 7 26
      app/src/main/sqldelight/data/chapters.sq
  17. 22 0
      app/src/main/sqldelight/data/history.sq
  18. 27 1
      app/src/main/sqldelight/data/manga_sync.sq
  19. 17 0
      app/src/main/sqldelight/data/mangas.sq
  20. 9 1
      app/src/main/sqldelight/data/mangas_categories.sq

+ 117 - 15
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<Manga> =
-        db.getFavoriteMangas().executeAsBlocking()
+    protected suspend fun getFavoriteManga(): List<DbManga> {
+        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<Chapter>) {
-        db.insertChapters(chapters).executeAsBlocking()
+    protected suspend fun insertChapters(chapters: List<Chapter>) {
+        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<Chapter>) {
-        db.updateChaptersBackup(chapters).executeAsBlocking()
+    protected suspend fun updateChapters(chapters: List<Chapter>) {
+        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<Chapter>) {
-        db.updateKnownChaptersBackup(chapters).executeAsBlocking()
+    protected suspend fun updateKnownChapters(chapters: List<Chapter>) {
+        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!!,
+                )
+            }
+        }
     }
 
     /**

+ 18 - 3
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<T : AbstractBackupManager>(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<T : AbstractBackupManager>(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}")
                 }

+ 3 - 3
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<PreferencesHelper>()
         val notifier = BackupNotifier(context)
         val uri = inputData.getString(LOCATION_URI_KEY)?.let { Uri.parse(it) }

+ 159 - 95
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<Manga>, flags: Int): List<BackupManga> {
+    private suspend fun backupManga(mangas: List<Mangas>, flags: Int): List<BackupManga> {
         return mangas.map {
             backupMangaObject(it, flags)
         }
     }
 
-    private fun backupExtensionInfo(mangas: List<Manga>): List<BackupSource> {
+    private fun backupExtensionInfo(mangas: List<Mangas>): List<BackupSource> {
         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<BackupCategory> {
+    private suspend fun backupCategories(options: Int): List<BackupCategory> {
         // 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<BackupCategory>) {
+    internal suspend fun restoreCategories(backupCategories: List<BackupCategory>) {
         // 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<Int>, backupCategories: List<BackupCategory>) {
-        val dbCategories = db.getCategories().executeAsBlocking()
-        val mangaCategoriesToUpdate = ArrayList<MangaCategory>(categories.size)
+    internal suspend fun restoreCategoriesForManga(manga: Manga, categories: List<Int>, backupCategories: List<BackupCategory>) {
+        val dbCategories = handler.awaitList { categoriesQueries.getCategories() }
+        val mangaCategoriesToUpdate = mutableListOf<Pair<Long, Long>>()
+
         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<BackupHistory>) {
+    internal suspend fun restoreHistoryForManga(history: List<BackupHistory>) {
         // List containing history to be updated
-        val historyToBeUpdated = ArrayList<History>(history.size)
+        val toUpdate = mutableListOf<HistoryUpdate>()
         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<Track>) {
+    internal suspend fun restoreTrackForManga(manga: Manga, tracks: List<Track>) {
         // 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<Track>()
+
+        val dbTracks = handler.awaitList { manga_syncQueries.getTracksByMangaId(manga.id!!) }
+        val toUpdate = mutableListOf<Manga_sync>()
+        val toInsert = mutableListOf<Track>()
 
         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<Chapter>) {
-        val dbChapters = db.getChapters(manga).executeAsBlocking()
+    internal suspend fun restoreChaptersForManga(manga: Manga, chapters: List<Chapter>) {
+        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

+ 18 - 22
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<BackupCategory>) {
-        db.inTransaction {
-            backupManager.restoreCategories(backupCategories)
-        }
+    private suspend fun restoreCategories(backupCategories: List<BackupCategory>) {
+        backupManager.restoreCategories(backupCategories)
 
         restoreProgress += 1
         showRestoreProgress(restoreProgress, restoreAmount, context.getString(R.string.categories))
     }
 
-    private fun restoreManga(backupManga: BackupManga, backupCategories: List<BackupCategory>) {
+    private suspend fun restoreManga(backupManga: BackupManga, backupCategories: List<BackupCategory>) {
         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<Chapter>,
         categories: List<Int>,
@@ -95,18 +93,16 @@ class FullBackupRestore(context: Context, notifier: BackupNotifier) : AbstractBa
         tracks: List<Track>,
         backupCategories: List<BackupCategory>,
     ) {
-        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<Chapter>,
         categories: List<Int>,
@@ -137,7 +133,7 @@ class FullBackupRestore(context: Context, notifier: BackupNotifier) : AbstractBa
         }
     }
 
-    private fun restoreMangaNoFetch(
+    private suspend fun restoreMangaNoFetch(
         backupManga: Manga,
         chapters: List<Chapter>,
         categories: List<Int>,
@@ -150,7 +146,7 @@ class FullBackupRestore(context: Context, notifier: BackupNotifier) : AbstractBa
         restoreExtraForManga(backupManga, categories, history, tracks, backupCategories)
     }
 
-    private fun restoreExtraForManga(manga: Manga, categories: List<Int>, history: List<BackupHistory>, tracks: List<Track>, backupCategories: List<BackupCategory>) {
+    private suspend fun restoreExtraForManga(manga: Manga, categories: List<Int>, history: List<BackupHistory>, tracks: List<Track>, backupCategories: List<BackupCategory>) {
         // Restore categories
         backupManager.restoreCategoriesForManga(manga, categories, backupCategories)
 

+ 11 - 14
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 = [email protected]
-            flags = [email protected]
-            order = [email protected]
+            flags = [email protected].toInt()
+            order = [email protected].toInt()
         }
     }
+}
 
-    companion object {
-        fun copyFrom(category: Category): BackupCategory {
-            return BackupCategory(
-                name = category.name,
-                order = category.order,
-                flags = category.flags,
-            )
-        }
-    }
+val backupCategoryMapper = { _: Long, name: String, order: Long, flags: Long ->
+    BackupCategory(
+        name = name,
+        order = order,
+        flags = flags,
+    )
 }

+ 30 - 21
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 = [email protected]
             read = [email protected]
             bookmark = [email protected]
-            last_page_read = [email protected]
+            last_page_read = [email protected].toInt()
             date_fetch = [email protected]
             date_upload = [email protected]
-            source_order = [email protected]
+            source_order = [email protected].toInt()
         }
     }
+}
 
-    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,
-            )
-        }
-    }
+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,
+    )
 }

+ 9 - 8
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<BackupChapter> = emptyList(),
-    @ProtoNumber(17) var categories: List<Int> = emptyList(),
+    @ProtoNumber(17) var categories: List<Long> = emptyList(),
     @ProtoNumber(18) var tracking: List<BackupTracking> = 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(),
             )
         }
     }

+ 29 - 19
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 = [email protected]
         }
     }
+}
 
-    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,
+    )
 }

+ 1 - 2
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)

+ 9 - 0
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
+        }
     }
 }

+ 0 - 42
app/src/main/java/eu/kanade/tachiyomi/data/database/queries/HistoryQueries.kt

@@ -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<History>) = db.put()
-        .objects(historyList)
-        .withPutResolver(HistoryUpsertResolver())
-        .prepare()
-}

+ 9 - 0
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()

+ 29 - 0
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

+ 27 - 1
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
-);
+);
+
+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();

+ 7 - 26
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

+ 22 - 0
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

+ 27 - 1
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
-);
+);
+
+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;

+ 17 - 0
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();

+ 9 - 1
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
-);
+);
+
+insert:
+INSERT INTO mangas_categories(manga_id, category_id)
+VALUES (:mangaId, :categoryId);
+
+deleteMangaCategoryByMangaId:
+DELETE FROM mangas_categories
+WHERE manga_id = :mangaId;