Use SQLDelight in Backup/Restore (#7295)

* Use SQLDelight in Backup/Restore

* Use CoroutineWorker

(cherry picked from commit fd5da2de3ab294988e8408077977f776cca5299a)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/data/backup/AbstractBackupManager.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupManager.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupRestore.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupCategory.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupManga.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/database/DatabaseHelper.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/database/queries/HistoryQueries.kt
#	app/src/main/java/eu/kanade/tachiyomi/source/model/SManga.kt
#	app/src/main/sqldelight/data/categories.sq
#	app/src/main/sqldelight/data/chapters.sq
This commit is contained in:
Andreas 2022-06-12 20:33:48 +02:00 committed by Jobobby04
parent bfd552cb3a
commit 70c2f97976
28 changed files with 834 additions and 322 deletions

View File

@ -30,4 +30,15 @@ val listOfStringsAndAdapter = object : ColumnAdapter<List<String>, String> {
} }
override fun encode(value: List<String>) = value.joinToString(separator = listOfStringsAndSeparator) override fun encode(value: List<String>) = value.joinToString(separator = listOfStringsAndSeparator)
} }
private const val listOfLongsSeparator = "/"
val listOfLongsAdapter = object : ColumnAdapter<List<Long>, String> {
override fun decode(databaseValue: String) =
if (databaseValue.isEmpty()) {
emptyList()
} else {
databaseValue.split(listOfLongsSeparator).mapNotNull { it.toLongOrNull() }
}
override fun encode(value: List<Long>) = value.joinToString(separator = listOfLongsSeparator)
}
// SY <-- // SY <--

View File

@ -0,0 +1,30 @@
package eu.kanade.data.exh
import exh.merged.sql.models.MergedMangaReference
val mergedMangaReferenceMapper = {
id: Long,
isInfoManga: Boolean,
getChapterUpdates: Boolean,
chapterSortMode: Long,
chapterPriority: Long,
downloadChapters: Boolean,
mergeId: Long,
mergeUrl: String,
mangaId: Long?,
mangaUrl: String,
mangaSourceId: Long, ->
MergedMangaReference(
id = id,
isInfoManga = isInfoManga,
getChapterUpdates = getChapterUpdates,
chapterSortMode = chapterSortMode.toInt(),
chapterPriority = chapterPriority.toInt(),
downloadChapters = downloadChapters,
mergeId = mergeId,
mergeUrl = mergeUrl,
mangaId = mangaId,
mangaUrl = mangaUrl,
mangaSourceId = mangaSourceId,
)
}

View File

@ -0,0 +1,14 @@
package eu.kanade.data.exh
import exh.metadata.sql.models.SearchMetadata
val searchMetadataMapper: (Long, String?, String, String?, Int) -> SearchMetadata =
{ mangaId, uploader, extra, indexedExtra, extraVersion ->
SearchMetadata(
mangaId = mangaId,
uploader = uploader,
extra = extra,
indexedExtra = indexedExtra,
extraVersion = extraVersion,
)
}

View File

@ -0,0 +1,14 @@
package eu.kanade.data.exh
import exh.metadata.sql.models.SearchTag
val searchTagMapper: (Long, Long, String?, String, Int) -> SearchTag =
{ id, mangaId, namespace, name, type ->
SearchTag(
id = id,
mangaId = mangaId,
namespace = namespace,
name = name,
type = type,
)
}

View File

@ -0,0 +1,13 @@
package eu.kanade.data.exh
import exh.metadata.sql.models.SearchTitle
val searchTitleMapper: (Long, Long, String, Int) -> SearchTitle =
{ id, mangaId, title, type ->
SearchTitle(
id = id,
mangaId = mangaId,
title = title,
type = type,
)
}

View File

@ -7,11 +7,13 @@ import androidx.sqlite.db.SupportSQLiteOpenHelper
import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory
import com.squareup.sqldelight.android.AndroidSqliteDriver import com.squareup.sqldelight.android.AndroidSqliteDriver
import com.squareup.sqldelight.db.SqlDriver import com.squareup.sqldelight.db.SqlDriver
import data.Categories
import data.History import data.History
import data.Mangas import data.Mangas
import eu.kanade.data.AndroidDatabaseHandler import eu.kanade.data.AndroidDatabaseHandler
import eu.kanade.data.DatabaseHandler import eu.kanade.data.DatabaseHandler
import eu.kanade.data.dateAdapter import eu.kanade.data.dateAdapter
import eu.kanade.data.listOfLongsAdapter
import eu.kanade.data.listOfStringsAdapter import eu.kanade.data.listOfStringsAdapter
import eu.kanade.data.listOfStringsAndAdapter import eu.kanade.data.listOfStringsAndAdapter
import eu.kanade.tachiyomi.data.cache.ChapterCache import eu.kanade.tachiyomi.data.cache.ChapterCache
@ -73,6 +75,11 @@ class AppModule(val app: Application) : InjektModule {
filtered_scanlatorsAdapter = listOfStringsAndAdapter, filtered_scanlatorsAdapter = listOfStringsAndAdapter,
// SY <-- // SY <--
), ),
// SY -->
categoriesAdapter = Categories.Adapter(
manga_orderAdapter = listOfLongsAdapter,
),
// SY <--
) )
} }

View File

@ -3,7 +3,7 @@ package eu.kanade.tachiyomi.data.backup
import android.content.Context import android.content.Context
import android.net.Uri import android.net.Uri
import eu.kanade.data.DatabaseHandler import eu.kanade.data.DatabaseHandler
import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.data.toLong
import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.toMangaInfo import eu.kanade.tachiyomi.data.database.models.toMangaInfo
@ -18,11 +18,12 @@ import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
import exh.eh.EHentaiThrottleManager import exh.eh.EHentaiThrottleManager
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import data.Mangas as DbManga
abstract class AbstractBackupManager(protected val context: Context) { abstract class AbstractBackupManager(protected val context: Context) {
internal val db: DatabaseHelper = Injekt.get() protected val handler: DatabaseHandler = Injekt.get()
internal val database: DatabaseHandler = Injekt.get()
internal val sourceManager: SourceManager = Injekt.get() internal val sourceManager: SourceManager = Injekt.get()
internal val trackManager: TrackManager = Injekt.get() internal val trackManager: TrackManager = Injekt.get()
protected val preferences: PreferencesHelper = Injekt.get() protected val preferences: PreferencesHelper = Injekt.get()
@ -31,15 +32,16 @@ abstract class AbstractBackupManager(protected val context: Context) {
protected val customMangaManager: CustomMangaManager = Injekt.get() protected val customMangaManager: CustomMangaManager = Injekt.get()
// SY <-- // SY <--
abstract fun createBackup(uri: Uri, flags: Int, isAutoBackup: Boolean): String abstract suspend fun createBackup(uri: Uri, flags: Int, isAutoBackup: Boolean): String
/** /**
* Returns manga * Returns manga
* *
* @return [Manga], null if not found * @return [Manga], null if not found
*/ */
internal fun getMangaFromDatabase(manga: Manga): Manga? = internal suspend fun getMangaFromDatabase(url: String, source: Long): DbManga? {
db.getManga(manga.url, manga.source).executeAsBlocking() return handler.awaitOneOrNull { mangasQueries.getMangaByUrlAndSource(url, source) }
}
/** /**
* Fetches chapter information. * Fetches chapter information.
@ -72,20 +74,23 @@ abstract class AbstractBackupManager(protected val context: Context) {
* *
* @return [Manga] from library * @return [Manga] from library
*/ */
protected fun getFavoriteManga(): List<Manga> = protected suspend fun getFavoriteManga(): List<DbManga> {
db.getFavoriteMangas().executeAsBlocking() return handler.awaitList { mangasQueries.getFavorites() }
}
// SY --> // SY -->
protected fun getReadManga(): List<Manga> = protected suspend fun getReadManga(): List<DbManga> {
db.getReadNotInLibraryMangas().executeAsBlocking() return handler.awaitList { mangasQueries.getReadMangaNotInLibrary() }
}
/** /**
* Returns list containing merged manga that are possibly not in the library * Returns list containing merged manga that are possibly not in the library
* *
* @return merged [Manga] that are possibly not in the library * @return merged [Manga] that are possibly not in the library
*/ */
protected fun getMergedManga(): List<Manga> = protected suspend fun getMergedManga(): List<DbManga> {
db.getMergedMangas().executeAsBlocking() return handler.awaitList { mergedQueries.selectAllMergedMangas() }
}
// SY <-- // SY <--
/** /**
@ -93,28 +98,125 @@ abstract class AbstractBackupManager(protected val context: Context) {
* *
* @return id of [Manga], null if not found * @return id of [Manga], null if not found
*/ */
internal fun insertManga(manga: Manga): Long? = internal suspend fun insertManga(manga: Manga): Long {
db.insertManga(manga).executeAsBlocking().insertedId() 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 * Inserts list of chapters
*/ */
protected fun insertChapters(chapters: List<Chapter>) { protected suspend fun insertChapters(chapters: List<Chapter>) {
db.insertChapters(chapters).executeAsBlocking() 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 * Updates a list of chapters
*/ */
protected fun updateChapters(chapters: List<Chapter>) { protected suspend fun updateChapters(chapters: List<Chapter>) {
db.updateChaptersBackup(chapters).executeAsBlocking() 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 * Updates a list of chapters with known database ids
*/ */
protected fun updateKnownChapters(chapters: List<Chapter>) { protected suspend fun updateKnownChapters(chapters: List<Chapter>) {
db.updateKnownChaptersBackup(chapters).executeAsBlocking() 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!!,
)
}
}
} }
/** /**

View File

@ -2,9 +2,9 @@ package eu.kanade.tachiyomi.data.backup
import android.content.Context import android.content.Context
import android.net.Uri import android.net.Uri
import eu.kanade.data.DatabaseHandler
import eu.kanade.data.chapter.NoChaptersException import eu.kanade.data.chapter.NoChaptersException
import eu.kanade.tachiyomi.R 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.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.database.models.Track
@ -22,7 +22,7 @@ import java.util.Locale
abstract class AbstractBackupRestore<T : AbstractBackupManager>(protected val context: Context, protected val notifier: BackupNotifier) { 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() protected val trackManager: TrackManager by injectLazy()
// SY --> // SY -->
@ -101,7 +101,22 @@ abstract class AbstractBackupRestore<T : AbstractBackupManager>(protected val co
if (service != null && service.isLogged) { if (service != null && service.isLogged) {
try { try {
val updatedTrack = service.refresh(track) 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) { } catch (e: Exception) {
errors.add(Date() to "${manga.title} - ${e.message}") errors.add(Date() to "${manga.title} - ${e.message}")
} }

View File

@ -3,13 +3,13 @@ package eu.kanade.tachiyomi.data.backup
import android.content.Context import android.content.Context
import android.net.Uri import android.net.Uri
import androidx.core.net.toUri import androidx.core.net.toUri
import androidx.work.CoroutineWorker
import androidx.work.ExistingPeriodicWorkPolicy import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.ExistingWorkPolicy import androidx.work.ExistingWorkPolicy
import androidx.work.OneTimeWorkRequestBuilder import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.PeriodicWorkRequestBuilder import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkInfo import androidx.work.WorkInfo
import androidx.work.WorkManager import androidx.work.WorkManager
import androidx.work.Worker
import androidx.work.WorkerParameters import androidx.work.WorkerParameters
import androidx.work.workDataOf import androidx.work.workDataOf
import com.hippo.unifile.UniFile import com.hippo.unifile.UniFile
@ -24,9 +24,9 @@ import uy.kohesive.injekt.api.get
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
class BackupCreatorJob(private val context: Context, workerParams: WorkerParameters) : 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 preferences = Injekt.get<PreferencesHelper>()
val notifier = BackupNotifier(context) val notifier = BackupNotifier(context)
val uri = inputData.getString(LOCATION_URI_KEY)?.let { Uri.parse(it) } val uri = inputData.getString(LOCATION_URI_KEY)?.let { Uri.parse(it) }

View File

@ -3,8 +3,11 @@ package eu.kanade.tachiyomi.data.backup.full
import android.content.Context import android.content.Context
import android.net.Uri import android.net.Uri
import com.hippo.unifile.UniFile import com.hippo.unifile.UniFile
import eu.kanade.data.exh.savedSearchMapper import data.Manga_sync
import eu.kanade.tachiyomi.Database import data.Mangas
import eu.kanade.data.exh.mergedMangaReferenceMapper
import eu.kanade.data.manga.mangaMapper
import eu.kanade.domain.history.model.HistoryUpdate
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.backup.AbstractBackupManager import eu.kanade.tachiyomi.data.backup.AbstractBackupManager
import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_CATEGORY import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_CATEGORY
@ -21,7 +24,6 @@ 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.BackupConst.BACKUP_TRACK_MASK
import eu.kanade.tachiyomi.data.backup.full.models.Backup 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.BackupCategory
import eu.kanade.tachiyomi.data.backup.full.models.BackupChapter
import eu.kanade.tachiyomi.data.backup.full.models.BackupFlatMetadata import eu.kanade.tachiyomi.data.backup.full.models.BackupFlatMetadata
import eu.kanade.tachiyomi.data.backup.full.models.BackupFull 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.BackupHistory
@ -30,29 +32,29 @@ import eu.kanade.tachiyomi.data.backup.full.models.BackupMergedMangaReference
import eu.kanade.tachiyomi.data.backup.full.models.BackupSavedSearch import eu.kanade.tachiyomi.data.backup.full.models.BackupSavedSearch
import eu.kanade.tachiyomi.data.backup.full.models.BackupSerializer 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.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.backupMergedMangaReferenceMapper
import eu.kanade.tachiyomi.data.backup.full.models.backupSavedSearchMapper
import eu.kanade.tachiyomi.data.backup.full.models.backupTrackMapper
import eu.kanade.tachiyomi.data.database.models.Chapter 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.Manga
import eu.kanade.tachiyomi.data.database.models.MangaCategory
import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.source.online.MetadataSource import eu.kanade.tachiyomi.source.online.MetadataSource
import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.system.logcat import eu.kanade.tachiyomi.util.system.logcat
import exh.metadata.metadata.base.awaitFlatMetadataForManga
import exh.metadata.metadata.base.awaitInsertFlatMetadata
import exh.metadata.metadata.base.getFlatMetadataForManga import exh.metadata.metadata.base.getFlatMetadataForManga
import exh.metadata.metadata.base.insertFlatMetadataAsync
import exh.source.MERGED_SOURCE_ID import exh.source.MERGED_SOURCE_ID
import exh.source.getMainSource import exh.source.getMainSource
import exh.util.executeOnIO
import exh.util.nullIfBlank import exh.util.nullIfBlank
import kotlinx.serialization.protobuf.ProtoBuf import kotlinx.serialization.protobuf.ProtoBuf
import logcat.LogPriority import logcat.LogPriority
import okio.buffer import okio.buffer
import okio.gzip import okio.gzip
import okio.sink import okio.sink
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.io.FileOutputStream import java.io.FileOutputStream
import java.util.Date
import kotlin.math.max import kotlin.math.max
class FullBackupManager(context: Context) : AbstractBackupManager(context) { class FullBackupManager(context: Context) : AbstractBackupManager(context) {
@ -65,25 +67,25 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
* @param uri path of Uri * @param uri path of Uri
* @param isAutoBackup backup called from scheduled backup job * @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 // Create root object
var backup: Backup? = null var backup: Backup? = null
db.inTransaction { val databaseManga = getFavoriteManga() /* SY --> */ + if (flags and BACKUP_READ_MANGA_MASK == BACKUP_READ_MANGA) {
val databaseManga = getFavoriteManga() /* SY --> */ + if (flags and BACKUP_READ_MANGA_MASK == BACKUP_READ_MANGA) { getReadManga()
getReadManga() } else {
} else { emptyList()
emptyList() } + getMergedManga() // SY <--
} + getMergedManga() // SY <--
backup = Backup( backup = Backup(
backupManga(databaseManga, flags), backupManga(databaseManga, flags),
backupCategories(flags), backupCategories(flags),
emptyList(), emptyList(),
backupExtensionInfo(databaseManga), backupExtensionInfo(databaseManga),
backupSavedSearches(), // SY -->
) backupSavedSearches(),
} // SY <--
)
var file: UniFile? = null var file: UniFile? = null
try { try {
@ -136,13 +138,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 { return mangas.map {
backupMangaObject(it, flags) backupMangaObject(it, flags)
} }
} }
private fun backupExtensionInfo(mangas: List<Manga>): List<BackupSource> { private fun backupExtensionInfo(mangas: List<Mangas>): List<BackupSource> {
return mangas return mangas
.asSequence() .asSequence()
.map { it.source } .map { it.source }
@ -157,12 +159,10 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
* *
* @return list of [BackupCategory] to be backed up * @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 // Check if user wants category information in backup
return if (options and BACKUP_CATEGORY_MASK == BACKUP_CATEGORY) { return if (options and BACKUP_CATEGORY_MASK == BACKUP_CATEGORY) {
db.getCategories() handler.awaitList { categoriesQueries.getCategories(backupCategoryMapper) }
.executeAsBlocking()
.map { BackupCategory.copyFrom(it) }
} else { } else {
emptyList() emptyList()
} }
@ -174,16 +174,8 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
* *
* @return list of [BackupSavedSearch] to be backed up * @return list of [BackupSavedSearch] to be backed up
*/ */
private fun backupSavedSearches(): List<BackupSavedSearch> { private suspend fun backupSavedSearches(): List<BackupSavedSearch> {
// TODO: Database handler please return handler.awaitList { saved_searchQueries.selectAll(backupSavedSearchMapper) }
return Injekt.get<Database>().saved_searchQueries.selectAll(savedSearchMapper).executeAsList().map {
BackupSavedSearch(
it.name,
it.query.orEmpty(),
it.filtersJson ?: "[]",
it.source,
)
}
} }
// SY <-- // SY <--
@ -194,23 +186,19 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
* @param options options for the backup * @param options options for the backup
* @return [BackupManga] containing manga in a serializable form * @return [BackupManga] containing manga in a serializable form
*/ */
private fun backupMangaObject(manga: Manga, options: Int): BackupManga { private suspend fun backupMangaObject(manga: Mangas, options: Int): BackupManga {
// Entry for this manga // Entry for this manga
val mangaObject = BackupManga.copyFrom(manga /* SY --> */, if (options and BACKUP_CUSTOM_INFO_MASK == BACKUP_CUSTOM_INFO) customMangaManager else null /* SY <-- */) val mangaObject = BackupManga.copyFrom(manga /* SY --> */, if (options and BACKUP_CUSTOM_INFO_MASK == BACKUP_CUSTOM_INFO) customMangaManager else null /* SY <-- */)
// SY --> // SY -->
if (manga.source == MERGED_SOURCE_ID) { if (manga.source == MERGED_SOURCE_ID) {
manga.id?.let { mangaId -> mangaObject.mergedMangaReferences = handler.awaitList { mergedQueries.selectByMergeId(manga._id, backupMergedMangaReferenceMapper) }
mangaObject.mergedMangaReferences = db.getMergedMangaReferences(mangaId)
.executeAsBlocking()
.map { BackupMergedMangaReference.copyFrom(it) }
}
} }
val source = sourceManager.get(manga.source)?.getMainSource<MetadataSource<*, *>>() val source = sourceManager.get(manga.source)?.getMainSource<MetadataSource<*, *>>()
if (source != null) { if (source != null) {
manga.id?.let { mangaId -> manga._id.let { mangaId ->
db.getFlatMetadataForManga(mangaId).executeAsBlocking()?.let { flatMetadata -> handler.getFlatMetadataForManga(mangaId)?.let { flatMetadata ->
mangaObject.flatMetadata = BackupFlatMetadata.copyFrom(flatMetadata) mangaObject.flatMetadata = BackupFlatMetadata.copyFrom(flatMetadata)
} }
} }
@ -220,36 +208,36 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
// Check if user wants chapter information in backup // Check if user wants chapter information in backup
if (options and BACKUP_CHAPTER_MASK == BACKUP_CHAPTER) { if (options and BACKUP_CHAPTER_MASK == BACKUP_CHAPTER) {
// Backup all the chapters // Backup all the chapters
val chapters = db.getChapters(manga).executeAsBlocking() val chapters = handler.awaitList { chaptersQueries.getChaptersByMangaId(manga._id, backupChapterMapper) }
if (chapters.isNotEmpty()) { if (chapters.isNotEmpty()) {
mangaObject.chapters = chapters.map { BackupChapter.copyFrom(it) } mangaObject.chapters = chapters
} }
} }
// Check if user wants category information in backup // Check if user wants category information in backup
if (options and BACKUP_CATEGORY_MASK == BACKUP_CATEGORY) { if (options and BACKUP_CATEGORY_MASK == BACKUP_CATEGORY) {
// Backup categories for this manga // Backup categories for this manga
val categoriesForManga = db.getCategoriesForManga(manga).executeAsBlocking() val categoriesForManga = handler.awaitList { categoriesQueries.getCategoriesByMangaId(manga._id) }
if (categoriesForManga.isNotEmpty()) { if (categoriesForManga.isNotEmpty()) {
mangaObject.categories = categoriesForManga.mapNotNull { it.order } mangaObject.categories = categoriesForManga.map { it.order }
} }
} }
// Check if user wants track information in backup // Check if user wants track information in backup
if (options and BACKUP_TRACK_MASK == BACKUP_TRACK) { 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()) { if (tracks.isNotEmpty()) {
mangaObject.tracking = tracks.map { BackupTracking.copyFrom(it) } mangaObject.tracking = tracks
} }
} }
// Check if user wants history information in backup // Check if user wants history information in backup
if (options and BACKUP_HISTORY_MASK == BACKUP_HISTORY) { if (options and BACKUP_HISTORY_MASK == BACKUP_HISTORY) {
val historyForManga = db.getHistoryByMangaId(manga.id!!).executeAsBlocking() val historyByMangaId = handler.awaitList(true) { historyQueries.getHistoryByMangaId(manga._id) }
if (historyForManga.isNotEmpty()) { if (historyByMangaId.isNotEmpty()) {
val history = historyForManga.mapNotNull { history -> val history = historyByMangaId.map { history ->
val url = db.getChapter(history.chapter_id).executeAsBlocking()?.url val chapter = handler.awaitOne { chaptersQueries.getChapterById(history.chapter_id) }
url?.let { BackupHistory(url, history.last_read) } BackupHistory(chapter.url, history.last_read?.time ?: 0L)
} }
if (history.isNotEmpty()) { if (history.isNotEmpty()) {
mangaObject.history = history mangaObject.history = history
@ -260,10 +248,10 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
return mangaObject return mangaObject
} }
fun restoreMangaNoFetch(manga: Manga, dbManga: Manga) { suspend fun restoreMangaNoFetch(manga: Manga, dbManga: Mangas) {
manga.id = dbManga.id manga.id = dbManga._id
manga.copyFrom(dbManga) manga.copyFrom(dbManga)
insertManga(manga) updateManga(manga)
} }
/** /**
@ -272,7 +260,7 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
* @param manga manga that needs updating * @param manga manga that needs updating
* @return Updated manga info. * @return Updated manga info.
*/ */
fun restoreManga(manga: Manga): Manga { suspend fun restoreManga(manga: Manga): Manga {
return manga.also { return manga.also {
it.initialized = it.description != null it.initialized = it.description != null
it.id = insertManga(it) it.id = insertManga(it)
@ -284,32 +272,36 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
* *
* @param backupCategories list containing categories * @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 // Get categories from file and from db
val dbCategories = db.getCategories().executeAsBlocking() val dbCategories = handler.awaitList { categoriesQueries.getCategories() }
// Iterate over them // Iterate over them
backupCategories.map { it.getCategoryImpl() }.forEach { category -> backupCategories
// Used to know if the category is already in the db .map { it.getCategoryImpl() }
var found = false .forEach { category ->
for (dbCategory in dbCategories) { // Used to know if the category is already in the db
// If the category is already in the db, assign the id to the file's category var found = false
// and do nothing for (dbCategory in dbCategories) {
if (category.name == dbCategory.name) { // If the category is already in the db, assign the id to the file's category
category.id = dbCategory.id // and do nothing
found = true if (category.name == dbCategory.name) {
break 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(), category.mangaOrder)
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()
}
}
} }
/** /**
@ -318,25 +310,30 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
* @param manga the manga whose categories have to be restored. * @param manga the manga whose categories have to be restored.
* @param categories the categories to restore. * @param categories the categories to restore.
*/ */
internal fun restoreCategoriesForManga(manga: Manga, categories: List<Int>, backupCategories: List<BackupCategory>) { internal suspend fun restoreCategoriesForManga(manga: Manga, categories: List<Int>, backupCategories: List<BackupCategory>) {
val dbCategories = db.getCategories().executeAsBlocking() val dbCategories = handler.awaitList { categoriesQueries.getCategories() }
val mangaCategoriesToUpdate = ArrayList<MangaCategory>(categories.size) val mangaCategoriesToUpdate = mutableListOf<Pair<Long, Long>>()
categories.forEach { backupCategoryOrder -> categories.forEach { backupCategoryOrder ->
backupCategories.firstOrNull { backupCategories.firstOrNull {
it.order == backupCategoryOrder it.order == backupCategoryOrder.toLong()
}?.let { backupCategory -> }?.let { backupCategory ->
dbCategories.firstOrNull { dbCategory -> dbCategories.firstOrNull { dbCategory ->
dbCategory.name == backupCategory.name dbCategory.name == backupCategory.name
}?.let { dbCategory -> }?.let { dbCategory ->
mangaCategoriesToUpdate += MangaCategory.create(manga, dbCategory) mangaCategoriesToUpdate.add(Pair(manga.id!!, dbCategory.id))
} }
} }
} }
// Update database // Update database
if (mangaCategoriesToUpdate.isNotEmpty()) { if (mangaCategoriesToUpdate.isNotEmpty()) {
db.deleteOldMangasCategories(listOf(manga)).executeAsBlocking() handler.await(true) {
db.insertMangasCategories(mangaCategoriesToUpdate).executeAsBlocking() mangas_categoriesQueries.deleteMangaCategoryByMangaId(manga.id!!)
mangaCategoriesToUpdate.forEach { (mangaId, categoryId) ->
mangas_categoriesQueries.insert(mangaId, categoryId)
}
}
} }
} }
@ -345,28 +342,43 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
* *
* @param history list containing history to be restored * @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 // List containing history to be updated
val historyToBeUpdated = ArrayList<History>(history.size) val toUpdate = mutableListOf<HistoryUpdate>()
for ((url, lastRead) in history) { 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 // Check if history already in database and update
if (dbHistory != null) { if (dbHistory != null) {
dbHistory.apply { dbHistory = dbHistory.copy(last_read = Date(max(lastRead, dbHistory.last_read?.time ?: 0L)))
last_read = max(lastRead, dbHistory.last_read) toUpdate.add(
} HistoryUpdate(
historyToBeUpdated.add(dbHistory) chapterId = dbHistory.chapter_id,
readAt = dbHistory.last_read!!,
sessionReadDuration = dbHistory.time_read,
),
)
} else { } else {
// If not in database create // If not in database create
db.getChapter(url).executeAsBlocking()?.let { handler
val historyToAdd = History.create(it).apply { .awaitOneOrNull { chaptersQueries.getChapterByUrl(url) }
last_read = lastRead ?.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,
)
}
}
} }
/** /**
@ -375,56 +387,97 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
* @param manga the manga whose sync have to be restored. * @param manga the manga whose sync have to be restored.
* @param tracks the track list to restore. * @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 // Fix foreign keys with the current manga id
tracks.map { it.manga_id = manga.id!! } tracks.map { it.manga_id = manga.id!! }
// Get tracks from database // 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 -> tracks.forEach { track ->
var isInDatabase = false var isInDatabase = false
for (dbTrack in dbTracks) { 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 // The sync is already in the db, only update its fields
if (track.media_id != dbTrack.media_id) { var temp = dbTrack
dbTrack.media_id = track.media_id if (track.media_id != dbTrack.remote_id) {
temp = temp.copy(remote_id = track.media_id)
} }
if (track.library_id != dbTrack.library_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 isInDatabase = true
trackToUpdate.add(dbTrack) toUpdate.add(temp)
break break
} }
} }
if (!isInDatabase) { if (!isInDatabase) {
// Insert new sync. Let the db assign the id // Insert new sync. Let the db assign the id
track.id = null track.id = null
trackToUpdate.add(track) toInsert.add(track)
} }
} }
// Update database // Update database
if (trackToUpdate.isNotEmpty()) { if (toUpdate.isNotEmpty()) {
db.insertTracks(trackToUpdate).executeAsBlocking() 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>) { internal suspend fun restoreChaptersForManga(manga: Manga, chapters: List<Chapter>) {
val dbChapters = db.getChapters(manga).executeAsBlocking() val dbChapters = handler.awaitList { chaptersQueries.getChaptersByMangaId(manga.id!!) }
chapters.forEach { chapter -> chapters.forEach { chapter ->
val dbChapter = dbChapters.find { it.url == chapter.url } val dbChapter = dbChapters.find { it.url == chapter.url }
if (dbChapter != null) { if (dbChapter != null) {
chapter.id = dbChapter.id chapter.id = dbChapter._id
chapter.copyFrom(dbChapter) chapter.copyFrom(dbChapter)
if (dbChapter.read && !chapter.read) { if (dbChapter.read && !chapter.read) {
chapter.read = dbChapter.read chapter.read = dbChapter.read
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 != 0) { } else if (chapter.last_page_read == 0 && dbChapter.last_page_read != 0L) {
chapter.last_page_read = dbChapter.last_page_read chapter.last_page_read = dbChapter.last_page_read.toInt()
} }
if (!chapter.bookmark && dbChapter.bookmark) { if (!chapter.bookmark && dbChapter.bookmark) {
chapter.bookmark = dbChapter.bookmark chapter.bookmark = dbChapter.bookmark
@ -441,13 +494,13 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
// SY --> // SY -->
internal suspend fun restoreSavedSearches(backupSavedSearches: List<BackupSavedSearch>) { internal suspend fun restoreSavedSearches(backupSavedSearches: List<BackupSavedSearch>) {
val currentSavedSearches = database.awaitList { val currentSavedSearches = handler.awaitList {
saved_searchQueries.selectAll(savedSearchMapper) saved_searchQueries.selectNamesAndSources()
} }
database.await(true) { handler.await {
backupSavedSearches.filter { backupSavedSearch -> backupSavedSearches.filter { backupSavedSearch ->
currentSavedSearches.none { it.name == backupSavedSearch.name && it.source == backupSavedSearch.source } currentSavedSearches.none { it.source == backupSavedSearch.source && it.name == backupSavedSearch.name }
}.forEach { }.forEach {
saved_searchQueries.insertSavedSearch( saved_searchQueries.insertSavedSearch(
_id = null, _id = null,
@ -467,44 +520,43 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
* @param manga the merge manga for the references * @param manga the merge manga for the references
* @param backupMergedMangaReferences the list of backup manga references for the merged manga * @param backupMergedMangaReferences the list of backup manga references for the merged manga
*/ */
internal fun restoreMergedMangaReferencesForManga(manga: Manga, backupMergedMangaReferences: List<BackupMergedMangaReference>) { internal suspend fun restoreMergedMangaReferencesForManga(mergeMangaId: Long, backupMergedMangaReferences: List<BackupMergedMangaReference>) {
// Get merged manga references from file and from db // Get merged manga references from file and from db
val dbMergedMangaReferences = db.getMergedMangaReferences().executeAsBlocking() val dbMergedMangaReferences = handler.awaitList { mergedQueries.selectAll(mergedMangaReferenceMapper) }
// Iterate over them // Iterate over them
backupMergedMangaReferences.forEach { backupMergedMangaReference -> backupMergedMangaReferences.forEach { backupMergedMangaReference ->
// Used to know if the merged manga reference is already in the db
var found = false
for (dbMergedMangaReference in dbMergedMangaReferences) {
// If the backupMergedMangaReference is already in the db, assign the id to the file's backupMergedMangaReference
// and do nothing
if (backupMergedMangaReference.mergeUrl == dbMergedMangaReference.mergeUrl && backupMergedMangaReference.mangaUrl == dbMergedMangaReference.mangaUrl) {
found = true
break
}
}
// If the backupMergedMangaReference isn't in the db, remove the id and insert a new backupMergedMangaReference // If the backupMergedMangaReference isn't in the db, remove the id and insert a new backupMergedMangaReference
// Store the inserted id in the backupMergedMangaReference // Store the inserted id in the backupMergedMangaReference
if (!found) { if (dbMergedMangaReferences.none { backupMergedMangaReference.mergeUrl == it.mergeUrl && backupMergedMangaReference.mangaUrl == it.mangaUrl }) {
// Let the db assign the id // Let the db assign the id
val mergedManga = db.getManga(backupMergedMangaReference.mangaUrl, backupMergedMangaReference.mangaSourceId).executeAsBlocking() ?: return@forEach val mergedManga = handler.awaitOneOrNull { mangasQueries.getMangaByUrlAndSource(backupMergedMangaReference.mangaUrl, backupMergedMangaReference.mangaSourceId, mangaMapper) } ?: return@forEach
val mergedMangaReference = backupMergedMangaReference.getMergedMangaReference() backupMergedMangaReference.getMergedMangaReference().run {
mergedMangaReference.mergeId = manga.id mergeId = mergeMangaId
mergedMangaReference.mangaId = mergedManga.id mangaId = mergedManga.id
db.insertMergedManga(mergedMangaReference).executeAsBlocking() handler.await {
mergedQueries.insertMerged(
null,
isInfoManga,
getChapterUpdates,
chapterSortMode.toLong(),
chapterPriority.toLong(),
downloadChapters,
mergeId!!,
mergeUrl,
mangaId,
mangaUrl,
mangaSourceId,
)
}
}
} }
} }
} }
internal fun restoreFlatMetadata(manga: Manga, backupFlatMetadata: BackupFlatMetadata) { internal suspend fun restoreFlatMetadata(mangaId: Long, backupFlatMetadata: BackupFlatMetadata) {
val mangaId = manga.id ?: return if (handler.awaitFlatMetadataForManga(mangaId) == null) {
launchIO { handler.awaitInsertFlatMetadata(backupFlatMetadata.getFlatMetadata(mangaId))
db.getFlatMetadataForManga(mangaId).executeOnIO().let {
if (it == null) {
val flatMetadata = backupFlatMetadata.getFlatMetadata(mangaId)
db.insertFlatMetadataAsync(flatMetadata).await()
}
}
} }
} }
// SY <-- // SY <--

View File

@ -66,10 +66,8 @@ class FullBackupRestore(context: Context, notifier: BackupNotifier) : AbstractBa
return true return true
} }
private fun restoreCategories(backupCategories: List<BackupCategory>) { private suspend fun restoreCategories(backupCategories: List<BackupCategory>) {
db.inTransaction { backupManager.restoreCategories(backupCategories)
backupManager.restoreCategories(backupCategories)
}
restoreProgress += 1 restoreProgress += 1
showRestoreProgress(restoreProgress, restoreAmount, context.getString(R.string.categories)) showRestoreProgress(restoreProgress, restoreAmount, context.getString(R.string.categories))
@ -84,10 +82,10 @@ class FullBackupRestore(context: Context, notifier: BackupNotifier) : AbstractBa
} }
// SY <-- // SY <--
private fun restoreManga(backupManga: BackupManga, backupCategories: List<BackupCategory>) { private suspend fun restoreManga(backupManga: BackupManga, backupCategories: List<BackupCategory>) {
val manga = backupManga.getMangaImpl() val manga = backupManga.getMangaImpl()
val chapters = backupManga.getChaptersImpl() 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 history = backupManga.brokenHistory.map { BackupHistory(it.url, it.lastRead) } + backupManga.history
val tracks = backupManga.getTrackingImpl() val tracks = backupManga.getTrackingImpl()
// SY --> // SY -->
@ -120,7 +118,7 @@ class FullBackupRestore(context: Context, notifier: BackupNotifier) : AbstractBa
* @param history history data from json * @param history history data from json
* @param tracks tracking data from json * @param tracks tracking data from json
*/ */
private fun restoreMangaData( private suspend fun restoreMangaData(
manga: Manga, manga: Manga,
chapters: List<Chapter>, chapters: List<Chapter>,
categories: List<Int>, categories: List<Int>,
@ -133,18 +131,16 @@ class FullBackupRestore(context: Context, notifier: BackupNotifier) : AbstractBa
customManga: CustomMangaManager.MangaJson?, customManga: CustomMangaManager.MangaJson?,
// SY --> // SY -->
) { ) {
db.inTransaction { val dbManga = backupManager.getMangaFromDatabase(manga.url, manga.source)
val dbManga = backupManager.getMangaFromDatabase(manga) if (dbManga == null) {
if (dbManga == null) { // Manga not in database
// Manga not in database restoreMangaFetch(manga, chapters, categories, history, tracks, backupCategories/* SY --> */, mergedMangaReferences, flatMetadata, customManga/* SY <-- */)
restoreMangaFetch(manga, chapters, categories, history, tracks, backupCategories/* SY --> */, mergedMangaReferences, flatMetadata, customManga/* SY <-- */) } else {
} else { // Manga in database
// Manga in database // Copy information from manga already in database
// Copy information from manga already in database backupManager.restoreMangaNoFetch(manga, dbManga)
backupManager.restoreMangaNoFetch(manga, dbManga) // Fetch rest of manga information
// Fetch rest of manga information restoreMangaNoFetch(manga, chapters, categories, history, tracks, backupCategories/* SY --> */, mergedMangaReferences, flatMetadata, customManga/* SY <-- */)
restoreMangaNoFetch(manga, chapters, categories, history, tracks, backupCategories/* SY --> */, mergedMangaReferences, flatMetadata, customManga/* SY <-- */)
}
} }
} }
@ -155,7 +151,7 @@ class FullBackupRestore(context: Context, notifier: BackupNotifier) : AbstractBa
* @param chapters chapters of manga that needs updating * @param chapters chapters of manga that needs updating
* @param categories categories that need updating * @param categories categories that need updating
*/ */
private fun restoreMangaFetch( private suspend fun restoreMangaFetch(
manga: Manga, manga: Manga,
chapters: List<Chapter>, chapters: List<Chapter>,
categories: List<Int>, categories: List<Int>,
@ -179,7 +175,7 @@ class FullBackupRestore(context: Context, notifier: BackupNotifier) : AbstractBa
} }
} }
private fun restoreMangaNoFetch( private suspend fun restoreMangaNoFetch(
backupManga: Manga, backupManga: Manga,
chapters: List<Chapter>, chapters: List<Chapter>,
categories: List<Int>, categories: List<Int>,
@ -197,7 +193,7 @@ class FullBackupRestore(context: Context, notifier: BackupNotifier) : AbstractBa
restoreExtraForManga(backupManga, categories, history, tracks, backupCategories/* SY --> */, mergedMangaReferences, flatMetadata, customManga/* SY <-- */) restoreExtraForManga(backupManga, categories, history, tracks, backupCategories/* SY --> */, mergedMangaReferences, flatMetadata, customManga/* SY <-- */)
} }
private fun restoreExtraForManga( private suspend fun restoreExtraForManga(
manga: Manga, manga: Manga,
categories: List<Int>, categories: List<Int>,
history: List<BackupHistory>, history: List<BackupHistory>,
@ -220,10 +216,10 @@ class FullBackupRestore(context: Context, notifier: BackupNotifier) : AbstractBa
// SY --> // SY -->
// Restore merged manga references if its a merged manga // Restore merged manga references if its a merged manga
backupManager.restoreMergedMangaReferencesForManga(manga, mergedMangaReferences) backupManager.restoreMergedMangaReferencesForManga(manga.id!!, mergedMangaReferences)
// Restore flat metadata for metadata sources // Restore flat metadata for metadata sources
flatMetadata?.let { backupManager.restoreFlatMetadata(manga, it) } flatMetadata?.let { backupManager.restoreFlatMetadata(manga.id!!, it) }
// Restore Custom Info // Restore Custom Info
customManga?.id = manga.id!! customManga?.id = manga.id!!

View File

@ -1,6 +1,5 @@
package eu.kanade.tachiyomi.data.backup.full.models 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 eu.kanade.tachiyomi.data.database.models.CategoryImpl
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber import kotlinx.serialization.protobuf.ProtoNumber
@ -8,30 +7,28 @@ import kotlinx.serialization.protobuf.ProtoNumber
@Serializable @Serializable
class BackupCategory( class BackupCategory(
@ProtoNumber(1) var name: String, @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 // @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 // Bump by 100 to specify this is a 0.x value
@ProtoNumber(100) var flags: Int = 0, @ProtoNumber(100) var flags: Long = 0,
// SY specific values // SY specific values
@ProtoNumber(600) var mangaOrder: List<Long> = emptyList(), @ProtoNumber(600) var mangaOrder: List<Long> = emptyList(),
) { ) {
fun getCategoryImpl(): CategoryImpl { fun getCategoryImpl(): CategoryImpl {
return CategoryImpl().apply { return CategoryImpl().apply {
name = this@BackupCategory.name name = this@BackupCategory.name
flags = this@BackupCategory.flags flags = this@BackupCategory.flags.toInt()
order = this@BackupCategory.order order = this@BackupCategory.order.toInt()
mangaOrder = this@BackupCategory.mangaOrder mangaOrder = this@BackupCategory.mangaOrder
} }
} }
}
companion object {
fun copyFrom(category: Category): BackupCategory { val backupCategoryMapper = { _: Long, name: String, order: Long, flags: Long, mangaOrder: List<Long> ->
return BackupCategory( BackupCategory(
name = category.name, name = name,
order = category.order, order = order,
flags = category.flags, flags = flags,
mangaOrder = category.mangaOrder, mangaOrder = mangaOrder,
) )
}
}
} }

View File

@ -1,6 +1,5 @@
package eu.kanade.tachiyomi.data.backup.full.models 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 eu.kanade.tachiyomi.data.database.models.ChapterImpl
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber import kotlinx.serialization.protobuf.ProtoNumber
@ -15,12 +14,12 @@ data class BackupChapter(
@ProtoNumber(4) var read: Boolean = false, @ProtoNumber(4) var read: Boolean = false,
@ProtoNumber(5) var bookmark: Boolean = false, @ProtoNumber(5) var bookmark: Boolean = false,
// lastPageRead is called progress in 1.x // 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(7) var dateFetch: Long = 0,
@ProtoNumber(8) var dateUpload: Long = 0, @ProtoNumber(8) var dateUpload: Long = 0,
// chapterNumber is called number is 1.x // chapterNumber is called number is 1.x
@ProtoNumber(9) var chapterNumber: Float = 0F, @ProtoNumber(9) var chapterNumber: Float = 0F,
@ProtoNumber(10) var sourceOrder: Int = 0, @ProtoNumber(10) var sourceOrder: Long = 0,
) { ) {
fun toChapterImpl(): ChapterImpl { fun toChapterImpl(): ChapterImpl {
return ChapterImpl().apply { return ChapterImpl().apply {
@ -30,27 +29,37 @@ data class BackupChapter(
scanlator = this@BackupChapter.scanlator scanlator = this@BackupChapter.scanlator
read = this@BackupChapter.read read = this@BackupChapter.read
bookmark = this@BackupChapter.bookmark bookmark = this@BackupChapter.bookmark
last_page_read = this@BackupChapter.lastPageRead last_page_read = this@BackupChapter.lastPageRead.toInt()
date_fetch = this@BackupChapter.dateFetch date_fetch = this@BackupChapter.dateFetch
date_upload = this@BackupChapter.dateUpload date_upload = this@BackupChapter.dateUpload
source_order = this@BackupChapter.sourceOrder source_order = this@BackupChapter.sourceOrder.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,
)
}

View File

@ -1,10 +1,12 @@
package eu.kanade.tachiyomi.data.backup.full.models package eu.kanade.tachiyomi.data.backup.full.models
import data.Mangas
import eu.kanade.data.listOfStringsAndAdapter
import eu.kanade.tachiyomi.data.database.models.ChapterImpl 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.MangaImpl
import eu.kanade.tachiyomi.data.database.models.TrackImpl import eu.kanade.tachiyomi.data.database.models.TrackImpl
import eu.kanade.tachiyomi.data.library.CustomMangaManager import eu.kanade.tachiyomi.data.library.CustomMangaManager
import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber import kotlinx.serialization.protobuf.ProtoNumber
@ -29,7 +31,7 @@ data class BackupManga(
@ProtoNumber(14) var viewer: Int = 0, // Replaced by viewer_flags @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(15) val flags: Int = 0, 1.x value, not used in 0.x
@ProtoNumber(16) var chapters: List<BackupChapter> = emptyList(), @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(), @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 // 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, @ProtoNumber(100) var favorite: Boolean = true,
@ -109,28 +111,28 @@ data class BackupManga(
} }
companion object { companion object {
fun copyFrom(manga: Manga /* SY --> */, customMangaManager: CustomMangaManager?/* SY <-- */): BackupManga { fun copyFrom(manga: Mangas /* SY --> */, customMangaManager: CustomMangaManager?/* SY <-- */): BackupManga {
return BackupManga( return BackupManga(
url = manga.url, url = manga.url,
// SY --> // SY -->
title = manga.originalTitle, title = manga.title,
artist = manga.originalArtist, artist = manga.artist,
author = manga.originalAuthor, author = manga.author,
description = manga.originalDescription, description = manga.description,
genre = manga.getOriginalGenres() ?: emptyList(), genre = manga.genre ?: emptyList(),
status = manga.originalStatus, status = manga.status.toInt(),
// SY <-- // SY <--
thumbnailUrl = manga.thumbnail_url, thumbnailUrl = manga.thumbnail_url,
favorite = manga.favorite, favorite = manga.favorite,
source = manga.source, source = manga.source,
dateAdded = manga.date_added, dateAdded = manga.date_added,
viewer = manga.readingModeType, viewer = (manga.viewer.toInt() and ReadingModeType.MASK),
viewer_flags = manga.viewer_flags, viewer_flags = manga.viewer.toInt(),
chapterFlags = manga.chapter_flags, chapterFlags = manga.chapter_flags.toInt(),
filtered_scanlators = manga.filtered_scanlators, filtered_scanlators = listOfStringsAndAdapter.encode(manga.filtered_scanlators.orEmpty()),
// SY --> // SY -->
).also { backupManga -> ).also { backupManga ->
customMangaManager?.getManga(manga)?.let { customMangaManager?.getManga(manga._id)?.let {
backupManga.customTitle = it.title backupManga.customTitle = it.title
backupManga.customArtist = it.artist backupManga.customArtist = it.artist
backupManga.customAuthor = it.author backupManga.customAuthor = it.author

View File

@ -33,19 +33,28 @@ data class BackupMergedMangaReference(
id = null, id = null,
) )
} }
}
companion object {
fun copyFrom(mergedMangaReference: MergedMangaReference): BackupMergedMangaReference { val backupMergedMangaReferenceMapper = {
return BackupMergedMangaReference( _: Long,
isInfoManga = mergedMangaReference.isInfoManga, isInfoManga: Boolean,
getChapterUpdates = mergedMangaReference.getChapterUpdates, getChapterUpdates: Boolean,
chapterSortMode = mergedMangaReference.chapterSortMode, chapterSortMode: Long,
chapterPriority = mergedMangaReference.chapterPriority, chapterPriority: Long,
downloadChapters = mergedMangaReference.downloadChapters, downloadChapters: Boolean,
mergeUrl = mergedMangaReference.mergeUrl, _: Long,
mangaUrl = mergedMangaReference.mangaUrl, mergeUrl: String,
mangaSourceId = mergedMangaReference.mangaSourceId, _: Long?,
) mangaUrl: String,
} mangaSourceId: Long, ->
} BackupMergedMangaReference(
isInfoManga = isInfoManga,
getChapterUpdates = getChapterUpdates,
chapterSortMode = chapterSortMode.toInt(),
chapterPriority = chapterPriority.toInt(),
downloadChapters = downloadChapters,
mergeUrl = mergeUrl,
mangaUrl = mangaUrl,
mangaSourceId = mangaSourceId,
)
} }

View File

@ -13,3 +13,17 @@ data class BackupSavedSearch(
@ProtoNumber(3) val filterList: String = "", @ProtoNumber(3) val filterList: String = "",
@ProtoNumber(4) val source: Long = 0, @ProtoNumber(4) val source: Long = 0,
) )
val backupSavedSearchMapper = {
_: Long,
source: Long,
name: String,
query: String?,
filtersJson: String?, ->
BackupSavedSearch(
source = source,
name = name,
query = query.orEmpty(),
filterList = filtersJson ?: "[]",
)
}

View File

@ -1,6 +1,5 @@
package eu.kanade.tachiyomi.data.backup.full.models 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 eu.kanade.tachiyomi.data.database.models.TrackImpl
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber import kotlinx.serialization.protobuf.ProtoNumber
@ -48,23 +47,34 @@ data class BackupTracking(
tracking_url = this@BackupTracking.trackingUrl tracking_url = this@BackupTracking.trackingUrl
} }
} }
}
companion object {
fun copyFrom(track: Track): BackupTracking { val backupTrackMapper = {
return BackupTracking( _id: Long,
syncId = track.sync_id, manga_id: Long,
mediaId = track.media_id, syncId: Long,
// forced not null so its compatible with 1.x backup system mediaId: Long,
libraryId = track.library_id!!, libraryId: Long?,
title = track.title, title: String,
lastChapterRead = track.last_chapter_read, lastChapterRead: Double,
totalChapters = track.total_chapters, totalChapters: Long,
score = track.score, status: Long,
status = track.status, score: Float,
startedReadingDate = track.started_reading_date, remoteUrl: String,
finishedReadingDate = track.finished_reading_date, startDate: Long,
trackingUrl = track.tracking_url, 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,
)
} }

View File

@ -1,5 +1,7 @@
package eu.kanade.tachiyomi.data.database.models package eu.kanade.tachiyomi.data.database.models
import data.GetCategories
class MangaCategory { class MangaCategory {
var id: Long? = null var id: Long? = null
@ -16,5 +18,12 @@ class MangaCategory {
mc.category_id = category.id!! mc.category_id = category.id!!
return mc 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
}
} }
} }

View File

@ -1,5 +1,6 @@
package eu.kanade.tachiyomi.source.model package eu.kanade.tachiyomi.source.model
import data.Chapters
import tachiyomi.source.model.ChapterInfo import tachiyomi.source.model.ChapterInfo
import java.io.Serializable import java.io.Serializable
@ -23,6 +24,14 @@ interface SChapter : Serializable {
scanlator = other.scanlator 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 { companion object {
fun create(): SChapter { fun create(): SChapter {
return SChapterImpl() return SChapterImpl()

View File

@ -1,5 +1,6 @@
package eu.kanade.tachiyomi.source.model package eu.kanade.tachiyomi.source.model
import data.Mangas
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.MangaImpl import eu.kanade.tachiyomi.data.database.models.MangaImpl
import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.download.DownloadManager
@ -82,6 +83,45 @@ interface SManga : Serializable {
} }
} }
fun copyFrom(other: Mangas) {
// EXH -->
if (other.title.isNotBlank() && originalTitle != other.title) {
val oldTitle = originalTitle
title = other.title
val source = (this as? Manga)?.source
if (source != null) {
Injekt.get<DownloadManager>().renameMangaDir(oldTitle, other.title, source)
}
}
// EXH <--
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 { companion object {
const val UNKNOWN = 0 const val UNKNOWN = 0
const val ONGOING = 1 const val ONGOING = 1

View File

@ -1,6 +1,11 @@
package exh.metadata.metadata.base package exh.metadata.metadata.base
import com.pushtorefresh.storio.operations.PreparedOperation import com.pushtorefresh.storio.operations.PreparedOperation
import eu.kanade.data.AndroidDatabaseHandler
import eu.kanade.data.DatabaseHandler
import eu.kanade.data.exh.searchMetadataMapper
import eu.kanade.data.exh.searchTagMapper
import eu.kanade.data.exh.searchTitleMapper
import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.DatabaseHelper
import exh.metadata.sql.models.SearchMetadata import exh.metadata.sql.models.SearchMetadata
import exh.metadata.sql.models.SearchTag import exh.metadata.sql.models.SearchTag
@ -31,6 +36,29 @@ data class FlatMetadata(
} }
} }
fun DatabaseHandler.getFlatMetadataForManga(mangaId: Long): FlatMetadata? {
this as AndroidDatabaseHandler
val meta = db.search_metadataQueries.selectByMangaId(mangaId, searchMetadataMapper).executeAsOneOrNull()
return if (meta != null) {
val tags = db.search_tagsQueries.selectByMangaId(mangaId, searchTagMapper).executeAsList()
val titles = db.search_titlesQueries.selectByMangaId(mangaId, searchTitleMapper).executeAsList()
FlatMetadata(meta, tags, titles)
} else null
}
suspend fun DatabaseHandler.awaitFlatMetadataForManga(mangaId: Long): FlatMetadata? {
return await {
val meta = search_metadataQueries.selectByMangaId(mangaId, searchMetadataMapper).executeAsOneOrNull()
if (meta != null) {
val tags = search_tagsQueries.selectByMangaId(mangaId, searchTagMapper).executeAsList()
val titles = search_titlesQueries.selectByMangaId(mangaId, searchTitleMapper).executeAsList()
FlatMetadata(meta, tags, titles)
} else null
}
}
fun DatabaseHelper.getFlatMetadataForManga(mangaId: Long): PreparedOperation<FlatMetadata?> { fun DatabaseHelper.getFlatMetadataForManga(mangaId: Long): PreparedOperation<FlatMetadata?> {
// We have to use fromCallable because StorIO messes up the thread scheduling if we use their rx functions // We have to use fromCallable because StorIO messes up the thread scheduling if we use their rx functions
val single = Single.fromCallable { val single = Single.fromCallable {
@ -92,6 +120,43 @@ private fun <T> preparedOperationFromSingle(single: Single<T>): PreparedOperatio
} }
} }
fun DatabaseHandler.insertFlatMetadata(flatMetadata: FlatMetadata) {
require(flatMetadata.metadata.mangaId != -1L)
this as AndroidDatabaseHandler // todo remove when legacy backup is dead
db.transaction {
flatMetadata.metadata.let {
db.search_metadataQueries.upsert(it.mangaId, it.uploader, it.extra, it.indexedExtra, it.extraVersion)
}
db.search_tagsQueries.deleteByManga(flatMetadata.metadata.mangaId)
flatMetadata.tags.forEach {
db.search_tagsQueries.insert(it.mangaId, it.namespace, it.name, it.type)
}
db.search_titlesQueries.deleteByManga(flatMetadata.metadata.mangaId)
flatMetadata.titles.forEach {
db.search_titlesQueries.insert(it.mangaId, it.title, it.type)
}
}
}
suspend fun DatabaseHandler.awaitInsertFlatMetadata(flatMetadata: FlatMetadata) {
require(flatMetadata.metadata.mangaId != -1L)
await(true) {
flatMetadata.metadata.run {
search_metadataQueries.upsert(mangaId, uploader, extra, indexedExtra, extraVersion)
}
search_tagsQueries.deleteByManga(flatMetadata.metadata.mangaId)
flatMetadata.tags.forEach {
search_tagsQueries.insert(it.mangaId, it.namespace, it.name, it.type)
}
search_titlesQueries.deleteByManga(flatMetadata.metadata.mangaId)
flatMetadata.titles.forEach {
search_titlesQueries.insert(it.mangaId, it.title, it.type)
}
}
}
fun DatabaseHelper.insertFlatMetadata(flatMetadata: FlatMetadata) { fun DatabaseHelper.insertFlatMetadata(flatMetadata: FlatMetadata) {
require(flatMetadata.metadata.mangaId != -1L) require(flatMetadata.metadata.mangaId != -1L)

View File

@ -1,7 +1,36 @@
import kotlin.collections.List;
CREATE TABLE categories( CREATE TABLE categories(
_id INTEGER NOT NULL PRIMARY KEY, _id INTEGER NOT NULL PRIMARY KEY,
name TEXT NOT NULL, name TEXT NOT NULL,
sort INTEGER NOT NULL, sort INTEGER NOT NULL,
flags INTEGER NOT NULL, flags INTEGER NOT NULL,
manga_order TEXT NOT NULL manga_order TEXT AS List<Long> NOT NULL
); );
getCategories:
SELECT
_id AS id,
name,
sort AS `order`,
flags,
manga_order AS `mangaOrder`
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, manga_order)
VALUES (:name, :order, :flags, :mangaOrder);
selectLastInsertedRowId:
SELECT last_insert_rowid();

View File

@ -28,6 +28,11 @@ SELECT *
FROM chapters FROM chapters
WHERE manga_id = :mangaId; WHERE manga_id = :mangaId;
getChapterByUrl:
SELECT *
FROM chapters
WHERE url = :chapterUrl;
getMergedChaptersByMangaId: getMergedChaptersByMangaId:
SELECT chapters.* SELECT chapters.*
FROM ( FROM (
@ -41,32 +46,8 @@ DELETE FROM chapters
WHERE _id IN :chapterIds; WHERE _id IN :chapterIds;
insert: insert:
INSERT INTO chapters( INSERT INTO chapters(manga_id,url,name,scanlator,read,bookmark,last_page_read,chapter_number,source_order,date_fetch,date_upload)
manga_id, VALUES (:mangaId,:url,:name,:scanlator,:read,:bookmark,:lastPageRead,:chapterNumber,:sourceOrder,:dateFetch,:dateUpload);
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:
UPDATE chapters UPDATE chapters

View File

@ -11,6 +11,28 @@ CREATE TABLE history(
CREATE INDEX history_history_chapter_id_index ON history(chapter_id); 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: resetHistoryById:
UPDATE history UPDATE history
SET last_read = 0 SET last_read = 0

View File

@ -16,3 +16,29 @@ CREATE TABLE manga_sync(
FOREIGN KEY(manga_id) REFERENCES mangas (_id) FOREIGN KEY(manga_id) REFERENCES mangas (_id)
ON DELETE CASCADE 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;

View File

@ -26,11 +26,32 @@ CREATE TABLE mangas(
CREATE INDEX library_favorite_index ON mangas(favorite) WHERE favorite = 1; CREATE INDEX library_favorite_index ON mangas(favorite) WHERE favorite = 1;
CREATE INDEX mangas_url_index ON mangas(url); 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: getMangaById:
SELECT * SELECT *
FROM mangas FROM mangas
WHERE _id = :id; WHERE _id = :id;
getMangaByUrlAndSource:
SELECT *
FROM mangas
WHERE url = :url AND source = :source;
getFavorites:
SELECT *
FROM mangas
WHERE favorite = 1;
getReadMangaNotInLibrary:
SELECT mangas.*
FROM mangas
WHERE favorite = 0 AND _id IN(
SELECT chapters.manga_id FROM chapters WHERE read = 1 OR last_page_read != 0
);
getSourceIdWithFavoriteCount: getSourceIdWithFavoriteCount:
SELECT SELECT
source, source,
@ -88,3 +109,6 @@ UPDATE mangas SET
cover_last_modified = coalesce(:coverLastModified, cover_last_modified), cover_last_modified = coalesce(:coverLastModified, cover_last_modified),
date_added = coalesce(:dateAdded, date_added) date_added = coalesce(:dateAdded, date_added)
WHERE _id = :mangaId; WHERE _id = :mangaId;
selectLastInsertedRowId:
SELECT last_insert_rowid();

View File

@ -7,3 +7,11 @@ CREATE TABLE mangas_categories(
FOREIGN KEY(manga_id) REFERENCES mangas (_id) FOREIGN KEY(manga_id) REFERENCES mangas (_id)
ON DELETE CASCADE ON DELETE CASCADE
); );
insert:
INSERT INTO mangas_categories(manga_id, category_id)
VALUES (:mangaId, :categoryId);
deleteMangaCategoryByMangaId:
DELETE FROM mangas_categories
WHERE manga_id = :mangaId;

View File

@ -21,6 +21,10 @@ SELECT * FROM saved_search WHERE _id = ?;
selectByIds: selectByIds:
SELECT * FROM saved_search WHERE _id IN ?; SELECT * FROM saved_search WHERE _id IN ?;
selectNamesAndSources:
SELECT source, name
FROM saved_search;
insertSavedSearch: insertSavedSearch:
INSERT INTO saved_search (_id, source, name, query, filters_json) INSERT INTO saved_search (_id, source, name, query, filters_json)
VALUES (?, ?, ?, ?, ?); VALUES (?, ?, ?, ?, ?);