mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-11-03 23:58:55 +01:00 
			
		
		
		
	Use SQLDelight in Backup/Restore (#7295)
* Use SQLDelight in Backup/Restore * Use CoroutineWorker
This commit is contained in:
		@@ -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!!,
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 
 | 
			
		||||
@@ -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,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) }
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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,
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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,
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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(),
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -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,
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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()
 | 
			
		||||
}
 | 
			
		||||
@@ -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()
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user