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:
parent
bfd552cb3a
commit
70c2f97976
@ -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 <--
|
||||||
|
@ -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,
|
||||||
|
)
|
||||||
|
}
|
14
app/src/main/java/eu/kanade/data/exh/SearchMetadataMapper.kt
Normal file
14
app/src/main/java/eu/kanade/data/exh/SearchMetadataMapper.kt
Normal 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,
|
||||||
|
)
|
||||||
|
}
|
14
app/src/main/java/eu/kanade/data/exh/SearchTag.kt
Normal file
14
app/src/main/java/eu/kanade/data/exh/SearchTag.kt
Normal 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,
|
||||||
|
)
|
||||||
|
}
|
13
app/src/main/java/eu/kanade/data/exh/SearchTitle.kt
Normal file
13
app/src/main/java/eu/kanade/data/exh/SearchTitle.kt
Normal 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,
|
||||||
|
)
|
||||||
|
}
|
@ -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 <--
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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!!,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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}")
|
||||||
}
|
}
|
||||||
|
@ -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) }
|
||||||
|
@ -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 <--
|
||||||
|
@ -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!!
|
||||||
|
@ -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,
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
@ -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 ?: "[]",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
@ -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,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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()
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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();
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -15,4 +15,30 @@ CREATE TABLE manga_sync(
|
|||||||
UNIQUE (manga_id, sync_id) ON CONFLICT REPLACE,
|
UNIQUE (manga_id, sync_id) ON CONFLICT REPLACE,
|
||||||
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;
|
||||||
|
@ -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();
|
||||||
|
@ -6,4 +6,12 @@ CREATE TABLE mangas_categories(
|
|||||||
ON DELETE CASCADE,
|
ON DELETE CASCADE,
|
||||||
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;
|
@ -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 (?, ?, ?, ?, ?);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user