Use SQLDelight in Backup/Restore (#7295)
* Use SQLDelight in Backup/Restore * Use CoroutineWorker
This commit is contained in:
parent
3c9f96d621
commit
fd5da2de3a
@ -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
|
||||
|
@ -3,4 +3,30 @@ CREATE TABLE categories(
|
||||
name TEXT NOT NULL,
|
||||
sort INTEGER NOT NULL,
|
||||
flags INTEGER NOT NULL
|
||||
);
|
||||
);
|
||||
|
||||
getCategories:
|
||||
SELECT
|
||||
_id AS id,
|
||||
name,
|
||||
sort AS `order`,
|
||||
flags
|
||||
FROM categories;
|
||||
|
||||
getCategoriesByMangaId:
|
||||
SELECT
|
||||
C._id AS id,
|
||||
C.name,
|
||||
C.sort AS `order`,
|
||||
C.flags
|
||||
FROM categories C
|
||||
JOIN mangas_categories MC
|
||||
ON C._id = MC.category_id
|
||||
WHERE MC.manga_id = :mangaId;
|
||||
|
||||
insert:
|
||||
INSERT INTO categories(name, sort, flags)
|
||||
VALUES (:name, :order, :flags);
|
||||
|
||||
selectLastInsertedRowId:
|
||||
SELECT last_insert_rowid();
|
@ -28,37 +28,18 @@ SELECT *
|
||||
FROM chapters
|
||||
WHERE manga_id = :mangaId;
|
||||
|
||||
getChapterByUrl:
|
||||
SELECT *
|
||||
FROM chapters
|
||||
WHERE url = :chapterUrl;
|
||||
|
||||
removeChaptersWithIds:
|
||||
DELETE FROM chapters
|
||||
WHERE _id IN :chapterIds;
|
||||
|
||||
insert:
|
||||
INSERT INTO chapters(
|
||||
manga_id,
|
||||
url,
|
||||
name,
|
||||
scanlator,
|
||||
read,
|
||||
bookmark,
|
||||
last_page_read,
|
||||
chapter_number,
|
||||
source_order,
|
||||
date_fetch,
|
||||
date_upload
|
||||
)
|
||||
VALUES (
|
||||
:mangaId,
|
||||
:url,
|
||||
:name,
|
||||
:scanlator,
|
||||
:read,
|
||||
:bookmark,
|
||||
:lastPageRead,
|
||||
:chapterNumber,
|
||||
:sourceOrder,
|
||||
:dateFetch,
|
||||
:dateUpload
|
||||
);
|
||||
INSERT INTO chapters(manga_id,url,name,scanlator,read,bookmark,last_page_read,chapter_number,source_order,date_fetch,date_upload)
|
||||
VALUES (:mangaId,:url,:name,:scanlator,:read,:bookmark,:lastPageRead,:chapterNumber,:sourceOrder,:dateFetch,:dateUpload);
|
||||
|
||||
update:
|
||||
UPDATE chapters
|
||||
|
@ -11,6 +11,28 @@ CREATE TABLE history(
|
||||
|
||||
CREATE INDEX history_history_chapter_id_index ON history(chapter_id);
|
||||
|
||||
getHistoryByMangaId:
|
||||
SELECT
|
||||
H._id,
|
||||
H.chapter_id,
|
||||
H.last_read,
|
||||
H.time_read
|
||||
FROM history H
|
||||
JOIN chapters C
|
||||
ON H.chapter_id = C._id
|
||||
WHERE C.manga_id = :mangaId AND C._id = H.chapter_id;
|
||||
|
||||
getHistoryByChapterUrl:
|
||||
SELECT
|
||||
H._id,
|
||||
H.chapter_id,
|
||||
H.last_read,
|
||||
H.time_read
|
||||
FROM history H
|
||||
JOIN chapters C
|
||||
ON H.chapter_id = C._id
|
||||
WHERE C.url = :chapterUrl AND C._id = H.chapter_id;
|
||||
|
||||
resetHistoryById:
|
||||
UPDATE history
|
||||
SET last_read = 0
|
||||
|
@ -15,4 +15,30 @@ CREATE TABLE manga_sync(
|
||||
UNIQUE (manga_id, sync_id) ON CONFLICT REPLACE,
|
||||
FOREIGN KEY(manga_id) REFERENCES mangas (_id)
|
||||
ON DELETE CASCADE
|
||||
);
|
||||
);
|
||||
|
||||
getTracksByMangaId:
|
||||
SELECT *
|
||||
FROM manga_sync
|
||||
WHERE manga_id = :mangaId;
|
||||
|
||||
insert:
|
||||
INSERT INTO manga_sync(manga_id,sync_id,remote_id,library_id,title,last_chapter_read,total_chapters,status,score,remote_url,start_date,finish_date)
|
||||
VALUES (:mangaId,:syncId,:remoteId,:libraryId,:title,:lastChapterRead,:totalChapters,:status,:score,:remoteUrl,:startDate,:finishDate);
|
||||
|
||||
update:
|
||||
UPDATE manga_sync
|
||||
SET
|
||||
manga_id = coalesce(:mangaId, manga_id),
|
||||
sync_id = coalesce(:syncId, sync_id),
|
||||
remote_id = coalesce(:mediaId, remote_id),
|
||||
library_id = coalesce(:libraryId, library_id),
|
||||
title = coalesce(:title, title),
|
||||
last_chapter_read = coalesce(:lastChapterRead, last_chapter_read),
|
||||
total_chapters = coalesce(:totalChapter, total_chapters),
|
||||
status = coalesce(:status, status),
|
||||
score = coalesce(:score, score),
|
||||
remote_url = coalesce(:trackingUrl, remote_url),
|
||||
start_date = coalesce(:startDate, start_date),
|
||||
finish_date = coalesce(:finishDate, finish_date)
|
||||
WHERE _id = :id;
|
||||
|
@ -25,11 +25,25 @@ CREATE TABLE mangas(
|
||||
CREATE INDEX library_favorite_index ON mangas(favorite) WHERE favorite = 1;
|
||||
CREATE INDEX mangas_url_index ON mangas(url);
|
||||
|
||||
insert:
|
||||
INSERT INTO mangas(source,url,artist,author,description,genre,title,status,thumbnail_url,favorite,last_update,next_update,initialized,viewer,chapter_flags,cover_last_modified,date_added)
|
||||
VALUES (:source,:url,:artist,:author,:description,:genre,:title,:status,:thumbnail_url,:favorite,:last_update,:next_update,:initialized,:viewer,:chapter_flags,:cover_last_modified,:date_added);
|
||||
|
||||
getMangaById:
|
||||
SELECT *
|
||||
FROM mangas
|
||||
WHERE _id = :id;
|
||||
|
||||
getMangaByUrlAndSource:
|
||||
SELECT *
|
||||
FROM mangas
|
||||
WHERE url = :url AND source = :source;
|
||||
|
||||
getFavorites:
|
||||
SELECT *
|
||||
FROM mangas
|
||||
WHERE favorite = 1;
|
||||
|
||||
getSourceIdWithFavoriteCount:
|
||||
SELECT
|
||||
source,
|
||||
@ -77,3 +91,6 @@ UPDATE mangas SET
|
||||
cover_last_modified = coalesce(:coverLastModified, cover_last_modified),
|
||||
date_added = coalesce(:dateAdded, date_added)
|
||||
WHERE _id = :mangaId;
|
||||
|
||||
selectLastInsertedRowId:
|
||||
SELECT last_insert_rowid();
|
||||
|
@ -6,4 +6,12 @@ CREATE TABLE mangas_categories(
|
||||
ON DELETE CASCADE,
|
||||
FOREIGN KEY(manga_id) REFERENCES mangas (_id)
|
||||
ON DELETE CASCADE
|
||||
);
|
||||
);
|
||||
|
||||
insert:
|
||||
INSERT INTO mangas_categories(manga_id, category_id)
|
||||
VALUES (:mangaId, :categoryId);
|
||||
|
||||
deleteMangaCategoryByMangaId:
|
||||
DELETE FROM mangas_categories
|
||||
WHERE manga_id = :mangaId;
|
Loading…
Reference in New Issue
Block a user