mirror of
https://github.com/mihonapp/mihon.git
synced 2024-11-07 03:07:25 +01:00
Remove logic for restoring legacy JSON backups
- Protobuf backups have been around for 1.5 years now - The ability to restore online-dependant data from JSON backups gets harder as time goes on and sources drift - If users really need a way to restore them, they can use an older version of the app, or a separate tool for translating between the formats could be created
This commit is contained in:
parent
cd0294b1b6
commit
d1be221d7a
@ -12,14 +12,15 @@ import eu.kanade.tachiyomi.source.Source
|
|||||||
import eu.kanade.tachiyomi.source.SourceManager
|
import eu.kanade.tachiyomi.source.SourceManager
|
||||||
import eu.kanade.tachiyomi.source.model.toSChapter
|
import eu.kanade.tachiyomi.source.model.toSChapter
|
||||||
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
|
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
abstract class AbstractBackupManager(protected val context: Context) {
|
abstract class AbstractBackupManager(protected val context: Context) {
|
||||||
|
|
||||||
internal val databaseHelper: DatabaseHelper by injectLazy()
|
internal val db: DatabaseHelper = Injekt.get()
|
||||||
internal val sourceManager: SourceManager by injectLazy()
|
internal val sourceManager: SourceManager = Injekt.get()
|
||||||
internal val trackManager: TrackManager by injectLazy()
|
internal val trackManager: TrackManager = Injekt.get()
|
||||||
protected val preferences: PreferencesHelper by injectLazy()
|
protected val preferences: PreferencesHelper = Injekt.get()
|
||||||
|
|
||||||
abstract fun createBackup(uri: Uri, flags: Int, isAutoBackup: Boolean): String
|
abstract fun createBackup(uri: Uri, flags: Int, isAutoBackup: Boolean): String
|
||||||
|
|
||||||
@ -29,7 +30,7 @@ abstract class AbstractBackupManager(protected val context: Context) {
|
|||||||
* @return [Manga], null if not found
|
* @return [Manga], null if not found
|
||||||
*/
|
*/
|
||||||
internal fun getMangaFromDatabase(manga: Manga): Manga? =
|
internal fun getMangaFromDatabase(manga: Manga): Manga? =
|
||||||
databaseHelper.getManga(manga.url, manga.source).executeAsBlocking()
|
db.getManga(manga.url, manga.source).executeAsBlocking()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches chapter information.
|
* Fetches chapter information.
|
||||||
@ -42,7 +43,7 @@ abstract class AbstractBackupManager(protected val context: Context) {
|
|||||||
internal suspend fun restoreChapters(source: Source, manga: Manga, chapters: List<Chapter>): Pair<List<Chapter>, List<Chapter>> {
|
internal suspend fun restoreChapters(source: Source, manga: Manga, chapters: List<Chapter>): Pair<List<Chapter>, List<Chapter>> {
|
||||||
val fetchedChapters = source.getChapterList(manga.toMangaInfo())
|
val fetchedChapters = source.getChapterList(manga.toMangaInfo())
|
||||||
.map { it.toSChapter() }
|
.map { it.toSChapter() }
|
||||||
val syncedChapters = syncChaptersWithSource(databaseHelper, fetchedChapters, manga, source)
|
val syncedChapters = syncChaptersWithSource(db, fetchedChapters, manga, source)
|
||||||
if (syncedChapters.first.isNotEmpty()) {
|
if (syncedChapters.first.isNotEmpty()) {
|
||||||
chapters.forEach { it.manga_id = manga.id }
|
chapters.forEach { it.manga_id = manga.id }
|
||||||
updateChapters(chapters)
|
updateChapters(chapters)
|
||||||
@ -56,7 +57,7 @@ abstract class AbstractBackupManager(protected val context: Context) {
|
|||||||
* @return [Manga] from library
|
* @return [Manga] from library
|
||||||
*/
|
*/
|
||||||
protected fun getFavoriteManga(): List<Manga> =
|
protected fun getFavoriteManga(): List<Manga> =
|
||||||
databaseHelper.getFavoriteMangas().executeAsBlocking()
|
db.getFavoriteMangas().executeAsBlocking()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inserts manga and returns id
|
* Inserts manga and returns id
|
||||||
@ -64,27 +65,27 @@ 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 fun insertManga(manga: Manga): Long? =
|
||||||
databaseHelper.insertManga(manga).executeAsBlocking().insertedId()
|
db.insertManga(manga).executeAsBlocking().insertedId()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inserts list of chapters
|
* Inserts list of chapters
|
||||||
*/
|
*/
|
||||||
protected fun insertChapters(chapters: List<Chapter>) {
|
protected fun insertChapters(chapters: List<Chapter>) {
|
||||||
databaseHelper.insertChapters(chapters).executeAsBlocking()
|
db.insertChapters(chapters).executeAsBlocking()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates a list of chapters
|
* Updates a list of chapters
|
||||||
*/
|
*/
|
||||||
protected fun updateChapters(chapters: List<Chapter>) {
|
protected fun updateChapters(chapters: List<Chapter>) {
|
||||||
databaseHelper.updateChaptersBackup(chapters).executeAsBlocking()
|
db.updateChaptersBackup(chapters).executeAsBlocking()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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 fun updateKnownChapters(chapters: List<Chapter>) {
|
||||||
databaseHelper.updateKnownChaptersBackup(chapters).executeAsBlocking()
|
db.updateKnownChaptersBackup(chapters).executeAsBlocking()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -2,17 +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.tachiyomi.data.track.TrackManager
|
|
||||||
import eu.kanade.tachiyomi.source.SourceManager
|
|
||||||
import uy.kohesive.injekt.injectLazy
|
|
||||||
|
|
||||||
abstract class AbstractBackupRestoreValidator {
|
abstract class AbstractBackupRestoreValidator {
|
||||||
protected val sourceManager: SourceManager by injectLazy()
|
|
||||||
protected val trackManager: TrackManager by injectLazy()
|
|
||||||
|
|
||||||
abstract fun validate(context: Context, uri: Uri): Results
|
abstract fun validate(context: Context, uri: Uri): Results
|
||||||
|
|
||||||
data class Results(val missingSources: List<String>, val missingTrackers: List<String>)
|
data class Results(val missingSources: List<String>, val missingTrackers: List<String>)
|
||||||
}
|
}
|
||||||
|
|
||||||
class ValidatorParseException(e: Exception) : RuntimeException(e)
|
|
||||||
|
@ -6,11 +6,6 @@ object BackupConst {
|
|||||||
|
|
||||||
private const val NAME = "BackupRestoreServices"
|
private const val NAME = "BackupRestoreServices"
|
||||||
const val EXTRA_URI = "$ID.$NAME.EXTRA_URI"
|
const val EXTRA_URI = "$ID.$NAME.EXTRA_URI"
|
||||||
const val EXTRA_FLAGS = "$ID.$NAME.EXTRA_FLAGS"
|
|
||||||
const val EXTRA_MODE = "$ID.$NAME.EXTRA_MODE"
|
|
||||||
|
|
||||||
const val BACKUP_TYPE_LEGACY = 0
|
|
||||||
const val BACKUP_TYPE_FULL = 1
|
|
||||||
|
|
||||||
// Filter options
|
// Filter options
|
||||||
internal const val BACKUP_CATEGORY = 0x1
|
internal const val BACKUP_CATEGORY = 0x1
|
||||||
|
@ -9,7 +9,6 @@ import android.os.PowerManager
|
|||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.backup.full.FullBackupRestore
|
import eu.kanade.tachiyomi.data.backup.full.FullBackupRestore
|
||||||
import eu.kanade.tachiyomi.data.backup.legacy.LegacyBackupRestore
|
|
||||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||||
import eu.kanade.tachiyomi.util.system.acquireWakeLock
|
import eu.kanade.tachiyomi.util.system.acquireWakeLock
|
||||||
import eu.kanade.tachiyomi.util.system.isServiceRunning
|
import eu.kanade.tachiyomi.util.system.isServiceRunning
|
||||||
@ -44,11 +43,10 @@ class BackupRestoreService : Service() {
|
|||||||
* @param context context of application
|
* @param context context of application
|
||||||
* @param uri path of Uri
|
* @param uri path of Uri
|
||||||
*/
|
*/
|
||||||
fun start(context: Context, uri: Uri, mode: Int) {
|
fun start(context: Context, uri: Uri) {
|
||||||
if (!isRunning(context)) {
|
if (!isRunning(context)) {
|
||||||
val intent = Intent(context, BackupRestoreService::class.java).apply {
|
val intent = Intent(context, BackupRestoreService::class.java).apply {
|
||||||
putExtra(BackupConst.EXTRA_URI, uri)
|
putExtra(BackupConst.EXTRA_URI, uri)
|
||||||
putExtra(BackupConst.EXTRA_MODE, mode)
|
|
||||||
}
|
}
|
||||||
ContextCompat.startForegroundService(context, intent)
|
ContextCompat.startForegroundService(context, intent)
|
||||||
}
|
}
|
||||||
@ -118,15 +116,11 @@ class BackupRestoreService : Service() {
|
|||||||
*/
|
*/
|
||||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
val uri = intent?.getParcelableExtra<Uri>(BackupConst.EXTRA_URI) ?: return START_NOT_STICKY
|
val uri = intent?.getParcelableExtra<Uri>(BackupConst.EXTRA_URI) ?: return START_NOT_STICKY
|
||||||
val mode = intent.getIntExtra(BackupConst.EXTRA_MODE, BackupConst.BACKUP_TYPE_FULL)
|
|
||||||
|
|
||||||
// Cancel any previous job if needed.
|
// Cancel any previous job if needed.
|
||||||
backupRestore?.job?.cancel()
|
backupRestore?.job?.cancel()
|
||||||
|
|
||||||
backupRestore = when (mode) {
|
backupRestore = FullBackupRestore(this, notifier)
|
||||||
BackupConst.BACKUP_TYPE_FULL -> FullBackupRestore(this, notifier)
|
|
||||||
else -> LegacyBackupRestore(this, notifier)
|
|
||||||
}
|
|
||||||
|
|
||||||
val handler = CoroutineExceptionHandler { _, exception ->
|
val handler = CoroutineExceptionHandler { _, exception ->
|
||||||
logcat(LogPriority.ERROR, exception)
|
logcat(LogPriority.ERROR, exception)
|
||||||
|
@ -50,7 +50,7 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
|
|||||||
// Create root object
|
// Create root object
|
||||||
var backup: Backup? = null
|
var backup: Backup? = null
|
||||||
|
|
||||||
databaseHelper.inTransaction {
|
db.inTransaction {
|
||||||
val databaseManga = getFavoriteManga()
|
val databaseManga = getFavoriteManga()
|
||||||
|
|
||||||
backup = Backup(
|
backup = Backup(
|
||||||
@ -136,7 +136,7 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
|
|||||||
private fun backupCategories(options: Int): List<BackupCategory> {
|
private 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) {
|
||||||
databaseHelper.getCategories()
|
db.getCategories()
|
||||||
.executeAsBlocking()
|
.executeAsBlocking()
|
||||||
.map { BackupCategory.copyFrom(it) }
|
.map { BackupCategory.copyFrom(it) }
|
||||||
} else {
|
} else {
|
||||||
@ -158,7 +158,7 @@ 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 = databaseHelper.getChapters(manga).executeAsBlocking()
|
val chapters = db.getChapters(manga).executeAsBlocking()
|
||||||
if (chapters.isNotEmpty()) {
|
if (chapters.isNotEmpty()) {
|
||||||
mangaObject.chapters = chapters.map { BackupChapter.copyFrom(it) }
|
mangaObject.chapters = chapters.map { BackupChapter.copyFrom(it) }
|
||||||
}
|
}
|
||||||
@ -167,7 +167,7 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
|
|||||||
// 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 = databaseHelper.getCategoriesForManga(manga).executeAsBlocking()
|
val categoriesForManga = db.getCategoriesForManga(manga).executeAsBlocking()
|
||||||
if (categoriesForManga.isNotEmpty()) {
|
if (categoriesForManga.isNotEmpty()) {
|
||||||
mangaObject.categories = categoriesForManga.mapNotNull { it.order }
|
mangaObject.categories = categoriesForManga.mapNotNull { it.order }
|
||||||
}
|
}
|
||||||
@ -175,7 +175,7 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
|
|||||||
|
|
||||||
// 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 = databaseHelper.getTracks(manga).executeAsBlocking()
|
val tracks = db.getTracks(manga).executeAsBlocking()
|
||||||
if (tracks.isNotEmpty()) {
|
if (tracks.isNotEmpty()) {
|
||||||
mangaObject.tracking = tracks.map { BackupTracking.copyFrom(it) }
|
mangaObject.tracking = tracks.map { BackupTracking.copyFrom(it) }
|
||||||
}
|
}
|
||||||
@ -183,10 +183,10 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
|
|||||||
|
|
||||||
// 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 = databaseHelper.getHistoryByMangaId(manga.id!!).executeAsBlocking()
|
val historyForManga = db.getHistoryByMangaId(manga.id!!).executeAsBlocking()
|
||||||
if (historyForManga.isNotEmpty()) {
|
if (historyForManga.isNotEmpty()) {
|
||||||
val history = historyForManga.mapNotNull { history ->
|
val history = historyForManga.mapNotNull { history ->
|
||||||
val url = databaseHelper.getChapter(history.chapter_id).executeAsBlocking()?.url
|
val url = db.getChapter(history.chapter_id).executeAsBlocking()?.url
|
||||||
url?.let { BackupHistory(url, history.last_read) }
|
url?.let { BackupHistory(url, history.last_read) }
|
||||||
}
|
}
|
||||||
if (history.isNotEmpty()) {
|
if (history.isNotEmpty()) {
|
||||||
@ -224,7 +224,7 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
|
|||||||
*/
|
*/
|
||||||
internal fun restoreCategories(backupCategories: List<BackupCategory>) {
|
internal fun restoreCategories(backupCategories: List<BackupCategory>) {
|
||||||
// Get categories from file and from db
|
// Get categories from file and from db
|
||||||
val dbCategories = databaseHelper.getCategories().executeAsBlocking()
|
val dbCategories = db.getCategories().executeAsBlocking()
|
||||||
|
|
||||||
// Iterate over them
|
// Iterate over them
|
||||||
backupCategories.map { it.getCategoryImpl() }.forEach { category ->
|
backupCategories.map { it.getCategoryImpl() }.forEach { category ->
|
||||||
@ -244,7 +244,7 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
|
|||||||
if (!found) {
|
if (!found) {
|
||||||
// Let the db assign the id
|
// Let the db assign the id
|
||||||
category.id = null
|
category.id = null
|
||||||
val result = databaseHelper.insertCategory(category).executeAsBlocking()
|
val result = db.insertCategory(category).executeAsBlocking()
|
||||||
category.id = result.insertedId()?.toInt()
|
category.id = result.insertedId()?.toInt()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -257,7 +257,7 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
|
|||||||
* @param categories the categories to restore.
|
* @param categories the categories to restore.
|
||||||
*/
|
*/
|
||||||
internal fun restoreCategoriesForManga(manga: Manga, categories: List<Int>, backupCategories: List<BackupCategory>) {
|
internal fun restoreCategoriesForManga(manga: Manga, categories: List<Int>, backupCategories: List<BackupCategory>) {
|
||||||
val dbCategories = databaseHelper.getCategories().executeAsBlocking()
|
val dbCategories = db.getCategories().executeAsBlocking()
|
||||||
val mangaCategoriesToUpdate = ArrayList<MangaCategory>(categories.size)
|
val mangaCategoriesToUpdate = ArrayList<MangaCategory>(categories.size)
|
||||||
categories.forEach { backupCategoryOrder ->
|
categories.forEach { backupCategoryOrder ->
|
||||||
backupCategories.firstOrNull {
|
backupCategories.firstOrNull {
|
||||||
@ -273,8 +273,8 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
|
|||||||
|
|
||||||
// Update database
|
// Update database
|
||||||
if (mangaCategoriesToUpdate.isNotEmpty()) {
|
if (mangaCategoriesToUpdate.isNotEmpty()) {
|
||||||
databaseHelper.deleteOldMangasCategories(listOf(manga)).executeAsBlocking()
|
db.deleteOldMangasCategories(listOf(manga)).executeAsBlocking()
|
||||||
databaseHelper.insertMangasCategories(mangaCategoriesToUpdate).executeAsBlocking()
|
db.insertMangasCategories(mangaCategoriesToUpdate).executeAsBlocking()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -287,7 +287,7 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
|
|||||||
// List containing history to be updated
|
// List containing history to be updated
|
||||||
val historyToBeUpdated = ArrayList<History>(history.size)
|
val historyToBeUpdated = ArrayList<History>(history.size)
|
||||||
for ((url, lastRead) in history) {
|
for ((url, lastRead) in history) {
|
||||||
val dbHistory = databaseHelper.getHistoryByChapterUrl(url).executeAsBlocking()
|
val dbHistory = db.getHistoryByChapterUrl(url).executeAsBlocking()
|
||||||
// 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.apply {
|
||||||
@ -296,7 +296,7 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
|
|||||||
historyToBeUpdated.add(dbHistory)
|
historyToBeUpdated.add(dbHistory)
|
||||||
} else {
|
} else {
|
||||||
// If not in database create
|
// If not in database create
|
||||||
databaseHelper.getChapter(url).executeAsBlocking()?.let {
|
db.getChapter(url).executeAsBlocking()?.let {
|
||||||
val historyToAdd = History.create(it).apply {
|
val historyToAdd = History.create(it).apply {
|
||||||
last_read = lastRead
|
last_read = lastRead
|
||||||
}
|
}
|
||||||
@ -304,7 +304,7 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
databaseHelper.upsertHistoryLastRead(historyToBeUpdated).executeAsBlocking()
|
db.upsertHistoryLastRead(historyToBeUpdated).executeAsBlocking()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -318,7 +318,7 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
|
|||||||
tracks.map { it.manga_id = manga.id!! }
|
tracks.map { it.manga_id = manga.id!! }
|
||||||
|
|
||||||
// Get tracks from database
|
// Get tracks from database
|
||||||
val dbTracks = databaseHelper.getTracks(manga).executeAsBlocking()
|
val dbTracks = db.getTracks(manga).executeAsBlocking()
|
||||||
val trackToUpdate = mutableListOf<Track>()
|
val trackToUpdate = mutableListOf<Track>()
|
||||||
|
|
||||||
tracks.forEach { track ->
|
tracks.forEach { track ->
|
||||||
@ -346,12 +346,12 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
|
|||||||
}
|
}
|
||||||
// Update database
|
// Update database
|
||||||
if (trackToUpdate.isNotEmpty()) {
|
if (trackToUpdate.isNotEmpty()) {
|
||||||
databaseHelper.insertTracks(trackToUpdate).executeAsBlocking()
|
db.insertTracks(trackToUpdate).executeAsBlocking()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun restoreChaptersForManga(manga: Manga, chapters: List<Chapter>) {
|
internal fun restoreChaptersForManga(manga: Manga, chapters: List<Chapter>) {
|
||||||
val dbChapters = databaseHelper.getChapters(manga).executeAsBlocking()
|
val dbChapters = db.getChapters(manga).executeAsBlocking()
|
||||||
|
|
||||||
chapters.forEach { chapter ->
|
chapters.forEach { chapter ->
|
||||||
val dbChapter = dbChapters.find { it.url == chapter.url }
|
val dbChapter = dbChapters.find { it.url == chapter.url }
|
||||||
|
@ -34,8 +34,8 @@ class FullBackupRestore(context: Context, notifier: BackupNotifier) : AbstractBa
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Store source mapping for error messages
|
// Store source mapping for error messages
|
||||||
var backupMaps = backup.backupBrokenSources.map { BackupSource(it.name, it.sourceId) } + backup.backupSources
|
val backupMaps = backup.backupBrokenSources.map { BackupSource(it.name, it.sourceId) } + backup.backupSources
|
||||||
sourceMapping = backupMaps.map { it.sourceId to it.name }.toMap()
|
sourceMapping = backupMaps.associate { it.sourceId to it.name }
|
||||||
|
|
||||||
// Restore individual manga
|
// Restore individual manga
|
||||||
backup.backupManga.forEach {
|
backup.backupManga.forEach {
|
||||||
|
@ -4,14 +4,20 @@ import android.content.Context
|
|||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.backup.AbstractBackupRestoreValidator
|
import eu.kanade.tachiyomi.data.backup.AbstractBackupRestoreValidator
|
||||||
import eu.kanade.tachiyomi.data.backup.ValidatorParseException
|
|
||||||
import eu.kanade.tachiyomi.data.backup.full.models.BackupSerializer
|
import eu.kanade.tachiyomi.data.backup.full.models.BackupSerializer
|
||||||
|
import eu.kanade.tachiyomi.data.track.TrackManager
|
||||||
|
import eu.kanade.tachiyomi.source.SourceManager
|
||||||
import okio.buffer
|
import okio.buffer
|
||||||
import okio.gzip
|
import okio.gzip
|
||||||
import okio.source
|
import okio.source
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
class FullBackupRestoreValidator : AbstractBackupRestoreValidator() {
|
class FullBackupRestoreValidator : AbstractBackupRestoreValidator() {
|
||||||
|
|
||||||
|
private val sourceManager: SourceManager = Injekt.get()
|
||||||
|
private val trackManager: TrackManager = Injekt.get()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks for critical backup file data.
|
* Checks for critical backup file data.
|
||||||
*
|
*
|
||||||
@ -27,11 +33,11 @@ class FullBackupRestoreValidator : AbstractBackupRestoreValidator() {
|
|||||||
.use { it.readByteArray() }
|
.use { it.readByteArray() }
|
||||||
backupManager.parser.decodeFromByteArray(BackupSerializer, backupString)
|
backupManager.parser.decodeFromByteArray(BackupSerializer, backupString)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
throw ValidatorParseException(e)
|
throw IllegalStateException(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (backup.backupManga.isEmpty()) {
|
if (backup.backupManga.isEmpty()) {
|
||||||
throw Exception(context.getString(R.string.invalid_backup_file_missing_manga))
|
throw IllegalStateException(context.getString(R.string.invalid_backup_file_missing_manga))
|
||||||
}
|
}
|
||||||
|
|
||||||
val sources = backup.backupSources.associate { it.sourceId to it.name }
|
val sources = backup.backupSources.associate { it.sourceId to it.name }
|
||||||
|
@ -1,252 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.backup.legacy
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.net.Uri
|
|
||||||
import eu.kanade.tachiyomi.data.backup.AbstractBackupManager
|
|
||||||
import eu.kanade.tachiyomi.data.backup.legacy.models.Backup.Companion.CURRENT_VERSION
|
|
||||||
import eu.kanade.tachiyomi.data.backup.legacy.models.DHistory
|
|
||||||
import eu.kanade.tachiyomi.data.backup.legacy.serializer.CategoryImplTypeSerializer
|
|
||||||
import eu.kanade.tachiyomi.data.backup.legacy.serializer.CategoryTypeSerializer
|
|
||||||
import eu.kanade.tachiyomi.data.backup.legacy.serializer.ChapterImplTypeSerializer
|
|
||||||
import eu.kanade.tachiyomi.data.backup.legacy.serializer.ChapterTypeSerializer
|
|
||||||
import eu.kanade.tachiyomi.data.backup.legacy.serializer.HistoryTypeSerializer
|
|
||||||
import eu.kanade.tachiyomi.data.backup.legacy.serializer.MangaImplTypeSerializer
|
|
||||||
import eu.kanade.tachiyomi.data.backup.legacy.serializer.MangaTypeSerializer
|
|
||||||
import eu.kanade.tachiyomi.data.backup.legacy.serializer.TrackImplTypeSerializer
|
|
||||||
import eu.kanade.tachiyomi.data.backup.legacy.serializer.TrackTypeSerializer
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Category
|
|
||||||
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.data.database.models.toMangaInfo
|
|
||||||
import eu.kanade.tachiyomi.source.Source
|
|
||||||
import eu.kanade.tachiyomi.source.model.toSManga
|
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import kotlinx.serialization.modules.SerializersModule
|
|
||||||
import kotlinx.serialization.modules.contextual
|
|
||||||
import kotlin.math.max
|
|
||||||
|
|
||||||
class LegacyBackupManager(context: Context, version: Int = CURRENT_VERSION) : AbstractBackupManager(context) {
|
|
||||||
|
|
||||||
val parser: Json = when (version) {
|
|
||||||
2 -> Json {
|
|
||||||
// Forks may have added items to backup
|
|
||||||
ignoreUnknownKeys = true
|
|
||||||
|
|
||||||
// Register custom serializers
|
|
||||||
serializersModule = SerializersModule {
|
|
||||||
contextual(MangaTypeSerializer)
|
|
||||||
contextual(MangaImplTypeSerializer)
|
|
||||||
contextual(ChapterTypeSerializer)
|
|
||||||
contextual(ChapterImplTypeSerializer)
|
|
||||||
contextual(CategoryTypeSerializer)
|
|
||||||
contextual(CategoryImplTypeSerializer)
|
|
||||||
contextual(TrackTypeSerializer)
|
|
||||||
contextual(TrackImplTypeSerializer)
|
|
||||||
contextual(HistoryTypeSerializer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> throw Exception("Unknown backup version")
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create backup Json file from database
|
|
||||||
*
|
|
||||||
* @param uri path of Uri
|
|
||||||
* @param isAutoBackup backup called from scheduled backup job
|
|
||||||
*/
|
|
||||||
override fun createBackup(uri: Uri, flags: Int, isAutoBackup: Boolean) =
|
|
||||||
throw IllegalStateException("Legacy backup creation is not supported")
|
|
||||||
|
|
||||||
fun restoreMangaNoFetch(manga: Manga, dbManga: Manga) {
|
|
||||||
manga.id = dbManga.id
|
|
||||||
manga.copyFrom(dbManga)
|
|
||||||
manga.favorite = true
|
|
||||||
insertManga(manga)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches manga information
|
|
||||||
*
|
|
||||||
* @param source source of manga
|
|
||||||
* @param manga manga that needs updating
|
|
||||||
* @return Updated manga.
|
|
||||||
*/
|
|
||||||
suspend fun fetchManga(source: Source, manga: Manga): Manga {
|
|
||||||
val networkManga = source.getMangaDetails(manga.toMangaInfo())
|
|
||||||
return manga.also {
|
|
||||||
it.copyFrom(networkManga.toSManga())
|
|
||||||
it.favorite = true
|
|
||||||
it.initialized = true
|
|
||||||
it.id = insertManga(manga)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Restore the categories from Json
|
|
||||||
*
|
|
||||||
* @param backupCategories array containing categories
|
|
||||||
*/
|
|
||||||
internal fun restoreCategories(backupCategories: List<Category>) {
|
|
||||||
// Get categories from file and from db
|
|
||||||
val dbCategories = databaseHelper.getCategories().executeAsBlocking()
|
|
||||||
|
|
||||||
// Iterate over them
|
|
||||||
backupCategories.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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 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 = databaseHelper.insertCategory(category).executeAsBlocking()
|
|
||||||
category.id = result.insertedId()?.toInt()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Restores the categories a manga is in.
|
|
||||||
*
|
|
||||||
* @param manga the manga whose categories have to be restored.
|
|
||||||
* @param categories the categories to restore.
|
|
||||||
*/
|
|
||||||
internal fun restoreCategoriesForManga(manga: Manga, categories: List<String>) {
|
|
||||||
val dbCategories = databaseHelper.getCategories().executeAsBlocking()
|
|
||||||
val mangaCategoriesToUpdate = ArrayList<MangaCategory>(categories.size)
|
|
||||||
for (backupCategoryStr in categories) {
|
|
||||||
for (dbCategory in dbCategories) {
|
|
||||||
if (backupCategoryStr == dbCategory.name) {
|
|
||||||
mangaCategoriesToUpdate.add(MangaCategory.create(manga, dbCategory))
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update database
|
|
||||||
if (mangaCategoriesToUpdate.isNotEmpty()) {
|
|
||||||
databaseHelper.deleteOldMangasCategories(listOf(manga)).executeAsBlocking()
|
|
||||||
databaseHelper.insertMangasCategories(mangaCategoriesToUpdate).executeAsBlocking()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Restore history from Json
|
|
||||||
*
|
|
||||||
* @param history list containing history to be restored
|
|
||||||
*/
|
|
||||||
internal fun restoreHistoryForManga(history: List<DHistory>) {
|
|
||||||
// List containing history to be updated
|
|
||||||
val historyToBeUpdated = ArrayList<History>(history.size)
|
|
||||||
for ((url, lastRead) in history) {
|
|
||||||
val dbHistory = databaseHelper.getHistoryByChapterUrl(url).executeAsBlocking()
|
|
||||||
// Check if history already in database and update
|
|
||||||
if (dbHistory != null) {
|
|
||||||
dbHistory.apply {
|
|
||||||
last_read = max(lastRead, dbHistory.last_read)
|
|
||||||
}
|
|
||||||
historyToBeUpdated.add(dbHistory)
|
|
||||||
} else {
|
|
||||||
// If not in database create
|
|
||||||
databaseHelper.getChapter(url).executeAsBlocking()?.let {
|
|
||||||
val historyToAdd = History.create(it).apply {
|
|
||||||
last_read = lastRead
|
|
||||||
}
|
|
||||||
historyToBeUpdated.add(historyToAdd)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
databaseHelper.upsertHistoryLastRead(historyToBeUpdated).executeAsBlocking()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Restores the sync of a manga.
|
|
||||||
*
|
|
||||||
* @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>) {
|
|
||||||
// Get tracks from database
|
|
||||||
val dbTracks = databaseHelper.getTracks(manga).executeAsBlocking()
|
|
||||||
val trackToUpdate = ArrayList<Track>(tracks.size)
|
|
||||||
|
|
||||||
tracks.forEach { track ->
|
|
||||||
// Fix foreign keys with the current manga id
|
|
||||||
track.manga_id = manga.id!!
|
|
||||||
|
|
||||||
val service = trackManager.getService(track.sync_id)
|
|
||||||
if (service != null && service.isLogged) {
|
|
||||||
var isInDatabase = false
|
|
||||||
for (dbTrack in dbTracks) {
|
|
||||||
if (track.sync_id == dbTrack.sync_id) {
|
|
||||||
// The sync is already in the db, only update its fields
|
|
||||||
if (track.media_id != dbTrack.media_id) {
|
|
||||||
dbTrack.media_id = track.media_id
|
|
||||||
}
|
|
||||||
if (track.library_id != dbTrack.library_id) {
|
|
||||||
dbTrack.library_id = track.library_id
|
|
||||||
}
|
|
||||||
dbTrack.last_chapter_read = max(dbTrack.last_chapter_read, track.last_chapter_read)
|
|
||||||
isInDatabase = true
|
|
||||||
trackToUpdate.add(dbTrack)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!isInDatabase) {
|
|
||||||
// Insert new sync. Let the db assign the id
|
|
||||||
track.id = null
|
|
||||||
trackToUpdate.add(track)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Update database
|
|
||||||
if (trackToUpdate.isNotEmpty()) {
|
|
||||||
databaseHelper.insertTracks(trackToUpdate).executeAsBlocking()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Restore the chapters for manga if chapters already in database
|
|
||||||
*
|
|
||||||
* @param manga manga of chapters
|
|
||||||
* @param chapters list containing chapters that get restored
|
|
||||||
* @return boolean answering if chapter fetch is not needed
|
|
||||||
*/
|
|
||||||
internal fun restoreChaptersForManga(manga: Manga, chapters: List<Chapter>): Boolean {
|
|
||||||
val dbChapters = databaseHelper.getChapters(manga).executeAsBlocking()
|
|
||||||
|
|
||||||
// Return if fetch is needed
|
|
||||||
if (dbChapters.isEmpty() || dbChapters.size < chapters.size) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
for (chapter in chapters) {
|
|
||||||
val pos = dbChapters.indexOf(chapter)
|
|
||||||
if (pos != -1) {
|
|
||||||
val dbChapter = dbChapters[pos]
|
|
||||||
chapter.id = dbChapter.id
|
|
||||||
chapter.copyFrom(dbChapter)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
chapter.manga_id = manga.id
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter the chapters that couldn't be found.
|
|
||||||
updateChapters(chapters.filter { it.id != null })
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,184 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.backup.legacy
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.net.Uri
|
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.data.backup.AbstractBackupRestore
|
|
||||||
import eu.kanade.tachiyomi.data.backup.BackupNotifier
|
|
||||||
import eu.kanade.tachiyomi.data.backup.legacy.models.Backup
|
|
||||||
import eu.kanade.tachiyomi.data.backup.legacy.models.DHistory
|
|
||||||
import eu.kanade.tachiyomi.data.backup.legacy.models.MangaObject
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Category
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Track
|
|
||||||
import eu.kanade.tachiyomi.source.Source
|
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import kotlinx.serialization.json.JsonObject
|
|
||||||
import kotlinx.serialization.json.decodeFromJsonElement
|
|
||||||
import kotlinx.serialization.json.decodeFromStream
|
|
||||||
import kotlinx.serialization.json.intOrNull
|
|
||||||
import kotlinx.serialization.json.jsonPrimitive
|
|
||||||
import java.util.Date
|
|
||||||
|
|
||||||
class LegacyBackupRestore(context: Context, notifier: BackupNotifier) : AbstractBackupRestore<LegacyBackupManager>(context, notifier) {
|
|
||||||
|
|
||||||
override suspend fun performRestore(uri: Uri): Boolean {
|
|
||||||
// Read the json and create a Json Object,
|
|
||||||
// cannot use the backupManager json deserializer one because its not initialized yet
|
|
||||||
val backupObject = Json.decodeFromStream<JsonObject>(
|
|
||||||
context.contentResolver.openInputStream(uri)!!,
|
|
||||||
)
|
|
||||||
|
|
||||||
// Get parser version
|
|
||||||
val version = backupObject["version"]?.jsonPrimitive?.intOrNull ?: 1
|
|
||||||
|
|
||||||
// Initialize manager
|
|
||||||
backupManager = LegacyBackupManager(context, version)
|
|
||||||
|
|
||||||
// Decode the json object to a Backup object
|
|
||||||
val backup = backupManager.parser.decodeFromJsonElement<Backup>(backupObject)
|
|
||||||
|
|
||||||
restoreAmount = backup.mangas.size + 1 // +1 for categories
|
|
||||||
|
|
||||||
// Restore categories
|
|
||||||
backup.categories?.let { restoreCategories(it) }
|
|
||||||
|
|
||||||
// Store source mapping for error messages
|
|
||||||
sourceMapping = LegacyBackupRestoreValidator.getSourceMapping(backup.extensions ?: emptyList())
|
|
||||||
|
|
||||||
// Restore individual manga
|
|
||||||
backup.mangas.forEach {
|
|
||||||
if (job?.isActive != true) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
restoreManga(it)
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun restoreCategories(categoriesJson: List<Category>) {
|
|
||||||
db.inTransaction {
|
|
||||||
backupManager.restoreCategories(categoriesJson)
|
|
||||||
}
|
|
||||||
|
|
||||||
restoreProgress += 1
|
|
||||||
showRestoreProgress(restoreProgress, restoreAmount, context.getString(R.string.categories))
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun restoreManga(mangaJson: MangaObject) {
|
|
||||||
val manga = mangaJson.manga
|
|
||||||
val chapters = mangaJson.chapters ?: emptyList()
|
|
||||||
val categories = mangaJson.categories ?: emptyList()
|
|
||||||
val history = mangaJson.history ?: emptyList()
|
|
||||||
val tracks = mangaJson.track ?: emptyList()
|
|
||||||
|
|
||||||
val source = backupManager.sourceManager.get(manga.source)
|
|
||||||
val sourceName = sourceMapping[manga.source] ?: manga.source.toString()
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (source != null) {
|
|
||||||
restoreMangaData(manga, source, chapters, categories, history, tracks)
|
|
||||||
} else {
|
|
||||||
errors.add(Date() to "${manga.title} [$sourceName]: ${context.getString(R.string.source_not_found_name, sourceName)}")
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
errors.add(Date() to "${manga.title} [$sourceName]: ${e.message}")
|
|
||||||
}
|
|
||||||
|
|
||||||
restoreProgress += 1
|
|
||||||
showRestoreProgress(restoreProgress, restoreAmount, manga.title)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a manga restore observable
|
|
||||||
*
|
|
||||||
* @param manga manga data from json
|
|
||||||
* @param source source to get manga data from
|
|
||||||
* @param chapters chapters data from json
|
|
||||||
* @param categories categories data from json
|
|
||||||
* @param history history data from json
|
|
||||||
* @param tracks tracking data from json
|
|
||||||
*/
|
|
||||||
private suspend fun restoreMangaData(
|
|
||||||
manga: Manga,
|
|
||||||
source: Source,
|
|
||||||
chapters: List<Chapter>,
|
|
||||||
categories: List<String>,
|
|
||||||
history: List<DHistory>,
|
|
||||||
tracks: List<Track>,
|
|
||||||
) {
|
|
||||||
val dbManga = backupManager.getMangaFromDatabase(manga)
|
|
||||||
|
|
||||||
db.inTransaction {
|
|
||||||
if (dbManga == null) {
|
|
||||||
// Manga not in database
|
|
||||||
restoreMangaFetch(source, manga, chapters, categories, history, tracks)
|
|
||||||
} else { // Manga in database
|
|
||||||
// Copy information from manga already in database
|
|
||||||
backupManager.restoreMangaNoFetch(manga, dbManga)
|
|
||||||
// Fetch rest of manga information
|
|
||||||
restoreMangaNoFetch(source, manga, chapters, categories, history, tracks)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches manga information.
|
|
||||||
*
|
|
||||||
* @param manga manga that needs updating
|
|
||||||
* @param chapters chapters of manga that needs updating
|
|
||||||
* @param categories categories that need updating
|
|
||||||
*/
|
|
||||||
private suspend fun restoreMangaFetch(
|
|
||||||
source: Source,
|
|
||||||
manga: Manga,
|
|
||||||
chapters: List<Chapter>,
|
|
||||||
categories: List<String>,
|
|
||||||
history: List<DHistory>,
|
|
||||||
tracks: List<Track>,
|
|
||||||
) {
|
|
||||||
try {
|
|
||||||
val fetchedManga = backupManager.fetchManga(source, manga)
|
|
||||||
fetchedManga.id ?: return
|
|
||||||
|
|
||||||
updateChapters(source, fetchedManga, chapters)
|
|
||||||
|
|
||||||
restoreExtraForManga(fetchedManga, categories, history, tracks)
|
|
||||||
|
|
||||||
updateTracking(fetchedManga, tracks)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
errors.add(Date() to "${manga.title} - ${e.message}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun restoreMangaNoFetch(
|
|
||||||
source: Source,
|
|
||||||
backupManga: Manga,
|
|
||||||
chapters: List<Chapter>,
|
|
||||||
categories: List<String>,
|
|
||||||
history: List<DHistory>,
|
|
||||||
tracks: List<Track>,
|
|
||||||
) {
|
|
||||||
if (!backupManager.restoreChaptersForManga(backupManga, chapters)) {
|
|
||||||
updateChapters(source, backupManga, chapters)
|
|
||||||
}
|
|
||||||
|
|
||||||
restoreExtraForManga(backupManga, categories, history, tracks)
|
|
||||||
|
|
||||||
updateTracking(backupManga, tracks)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun restoreExtraForManga(manga: Manga, categories: List<String>, history: List<DHistory>, tracks: List<Track>) {
|
|
||||||
// Restore categories
|
|
||||||
backupManager.restoreCategoriesForManga(manga, categories)
|
|
||||||
|
|
||||||
// Restore history
|
|
||||||
backupManager.restoreHistoryForManga(history)
|
|
||||||
|
|
||||||
// Restore tracking
|
|
||||||
backupManager.restoreTrackForManga(manga, tracks)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,66 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.backup.legacy
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.net.Uri
|
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.data.backup.AbstractBackupRestoreValidator
|
|
||||||
import eu.kanade.tachiyomi.data.backup.ValidatorParseException
|
|
||||||
import eu.kanade.tachiyomi.data.backup.legacy.models.Backup
|
|
||||||
import kotlinx.serialization.json.decodeFromStream
|
|
||||||
|
|
||||||
class LegacyBackupRestoreValidator : AbstractBackupRestoreValidator() {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks for critical backup file data.
|
|
||||||
*
|
|
||||||
* @throws Exception if version or manga cannot be found.
|
|
||||||
* @return List of missing sources or missing trackers.
|
|
||||||
*/
|
|
||||||
override fun validate(context: Context, uri: Uri): Results {
|
|
||||||
val backupManager = LegacyBackupManager(context)
|
|
||||||
|
|
||||||
val backup = try {
|
|
||||||
backupManager.parser.decodeFromStream<Backup>(
|
|
||||||
context.contentResolver.openInputStream(uri)!!,
|
|
||||||
)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
throw ValidatorParseException(e)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (backup.version == null) {
|
|
||||||
throw Exception(context.getString(R.string.invalid_backup_file_missing_data))
|
|
||||||
}
|
|
||||||
|
|
||||||
if (backup.mangas.isEmpty()) {
|
|
||||||
throw Exception(context.getString(R.string.invalid_backup_file_missing_manga))
|
|
||||||
}
|
|
||||||
|
|
||||||
val sources = getSourceMapping(backup.extensions ?: emptyList())
|
|
||||||
val missingSources = sources
|
|
||||||
.filter { sourceManager.get(it.key) == null }
|
|
||||||
.values
|
|
||||||
.sorted()
|
|
||||||
|
|
||||||
val trackers = backup.mangas
|
|
||||||
.filterNot { it.track.isNullOrEmpty() }
|
|
||||||
.flatMap { it.track ?: emptyList() }
|
|
||||||
.map { it.sync_id }
|
|
||||||
.distinct()
|
|
||||||
val missingTrackers = trackers
|
|
||||||
.mapNotNull { trackManager.getService(it) }
|
|
||||||
.filter { !it.isLogged }
|
|
||||||
.map { context.getString(it.nameRes()) }
|
|
||||||
.sorted()
|
|
||||||
|
|
||||||
return Results(missingSources, missingTrackers)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun getSourceMapping(extensionsMapping: List<String>): Map<Long, String> {
|
|
||||||
return extensionsMapping.associate {
|
|
||||||
val items = it.split(":")
|
|
||||||
items[0].toLong() to items[1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,37 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.backup.legacy.models
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Category
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Track
|
|
||||||
import kotlinx.serialization.Contextual
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
import java.text.SimpleDateFormat
|
|
||||||
import java.util.Date
|
|
||||||
import java.util.Locale
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class Backup(
|
|
||||||
val version: Int? = null,
|
|
||||||
var mangas: MutableList<MangaObject> = mutableListOf(),
|
|
||||||
var categories: List<@Contextual Category>? = null,
|
|
||||||
var extensions: List<String>? = null,
|
|
||||||
) {
|
|
||||||
companion object {
|
|
||||||
const val CURRENT_VERSION = 2
|
|
||||||
|
|
||||||
fun getDefaultFilename(): String {
|
|
||||||
val date = SimpleDateFormat("yyyy-MM-dd_HH-mm", Locale.getDefault()).format(Date())
|
|
||||||
return "tachiyomi_$date.json"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class MangaObject(
|
|
||||||
var manga: @Contextual Manga,
|
|
||||||
var chapters: List<@Contextual Chapter>? = null,
|
|
||||||
var categories: List<String>? = null,
|
|
||||||
var track: List<@Contextual Track>? = null,
|
|
||||||
var history: List<@Contextual DHistory>? = null,
|
|
||||||
)
|
|
@ -1,3 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.backup.legacy.models
|
|
||||||
|
|
||||||
data class DHistory(val url: String, val lastRead: Long)
|
|
@ -1,49 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.backup.legacy.serializer
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Category
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.CategoryImpl
|
|
||||||
import kotlinx.serialization.KSerializer
|
|
||||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
|
||||||
import kotlinx.serialization.descriptors.buildClassSerialDescriptor
|
|
||||||
import kotlinx.serialization.encoding.Decoder
|
|
||||||
import kotlinx.serialization.encoding.Encoder
|
|
||||||
import kotlinx.serialization.json.JsonDecoder
|
|
||||||
import kotlinx.serialization.json.JsonEncoder
|
|
||||||
import kotlinx.serialization.json.add
|
|
||||||
import kotlinx.serialization.json.buildJsonArray
|
|
||||||
import kotlinx.serialization.json.int
|
|
||||||
import kotlinx.serialization.json.jsonArray
|
|
||||||
import kotlinx.serialization.json.jsonPrimitive
|
|
||||||
|
|
||||||
/**
|
|
||||||
* JSON Serializer used to write / read [CategoryImpl] to / from json
|
|
||||||
*/
|
|
||||||
open class CategoryBaseSerializer<T : Category> : KSerializer<T> {
|
|
||||||
override val descriptor: SerialDescriptor = buildClassSerialDescriptor("Category")
|
|
||||||
|
|
||||||
override fun serialize(encoder: Encoder, value: T) {
|
|
||||||
encoder as JsonEncoder
|
|
||||||
encoder.encodeJsonElement(
|
|
||||||
buildJsonArray {
|
|
||||||
add(value.name)
|
|
||||||
add(value.order)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
override fun deserialize(decoder: Decoder): T {
|
|
||||||
// make a category impl and cast as T so that the serializer accepts it
|
|
||||||
return CategoryImpl().apply {
|
|
||||||
decoder as JsonDecoder
|
|
||||||
val array = decoder.decodeJsonElement().jsonArray
|
|
||||||
name = array[0].jsonPrimitive.content
|
|
||||||
order = array[1].jsonPrimitive.int
|
|
||||||
} as T
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Allow for serialization of a category and category impl
|
|
||||||
object CategoryTypeSerializer : CategoryBaseSerializer<Category>()
|
|
||||||
|
|
||||||
object CategoryImplTypeSerializer : CategoryBaseSerializer<CategoryImpl>()
|
|
@ -1,66 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.backup.legacy.serializer
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.ChapterImpl
|
|
||||||
import kotlinx.serialization.KSerializer
|
|
||||||
import kotlinx.serialization.descriptors.buildClassSerialDescriptor
|
|
||||||
import kotlinx.serialization.encoding.Decoder
|
|
||||||
import kotlinx.serialization.encoding.Encoder
|
|
||||||
import kotlinx.serialization.json.JsonDecoder
|
|
||||||
import kotlinx.serialization.json.JsonEncoder
|
|
||||||
import kotlinx.serialization.json.buildJsonObject
|
|
||||||
import kotlinx.serialization.json.intOrNull
|
|
||||||
import kotlinx.serialization.json.jsonObject
|
|
||||||
import kotlinx.serialization.json.jsonPrimitive
|
|
||||||
import kotlinx.serialization.json.put
|
|
||||||
|
|
||||||
/**
|
|
||||||
* JSON Serializer used to write / read [ChapterImpl] to / from json
|
|
||||||
*/
|
|
||||||
open class ChapterBaseSerializer<T : Chapter> : KSerializer<T> {
|
|
||||||
|
|
||||||
override val descriptor = buildClassSerialDescriptor("Chapter")
|
|
||||||
|
|
||||||
override fun serialize(encoder: Encoder, value: T) {
|
|
||||||
encoder as JsonEncoder
|
|
||||||
encoder.encodeJsonElement(
|
|
||||||
buildJsonObject {
|
|
||||||
put(URL, value.url)
|
|
||||||
if (value.read) {
|
|
||||||
put(READ, 1)
|
|
||||||
}
|
|
||||||
if (value.bookmark) {
|
|
||||||
put(BOOKMARK, 1)
|
|
||||||
}
|
|
||||||
if (value.last_page_read != 0) {
|
|
||||||
put(LAST_READ, value.last_page_read)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
override fun deserialize(decoder: Decoder): T {
|
|
||||||
// make a chapter impl and cast as T so that the serializer accepts it
|
|
||||||
return ChapterImpl().apply {
|
|
||||||
decoder as JsonDecoder
|
|
||||||
val jsonObject = decoder.decodeJsonElement().jsonObject
|
|
||||||
url = jsonObject[URL]!!.jsonPrimitive.content
|
|
||||||
read = jsonObject[READ]?.jsonPrimitive?.intOrNull == 1
|
|
||||||
bookmark = jsonObject[BOOKMARK]?.jsonPrimitive?.intOrNull == 1
|
|
||||||
last_page_read = jsonObject[LAST_READ]?.jsonPrimitive?.intOrNull ?: last_page_read
|
|
||||||
} as T
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val URL = "u"
|
|
||||||
private const val READ = "r"
|
|
||||||
private const val BOOKMARK = "b"
|
|
||||||
private const val LAST_READ = "l"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Allow for serialization of a chapter and chapter impl
|
|
||||||
object ChapterTypeSerializer : ChapterBaseSerializer<Chapter>()
|
|
||||||
|
|
||||||
object ChapterImplTypeSerializer : ChapterBaseSerializer<ChapterImpl>()
|
|
@ -1,41 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.backup.legacy.serializer
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.data.backup.legacy.models.DHistory
|
|
||||||
import kotlinx.serialization.KSerializer
|
|
||||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
|
||||||
import kotlinx.serialization.descriptors.buildClassSerialDescriptor
|
|
||||||
import kotlinx.serialization.encoding.Decoder
|
|
||||||
import kotlinx.serialization.encoding.Encoder
|
|
||||||
import kotlinx.serialization.json.JsonDecoder
|
|
||||||
import kotlinx.serialization.json.JsonEncoder
|
|
||||||
import kotlinx.serialization.json.add
|
|
||||||
import kotlinx.serialization.json.buildJsonArray
|
|
||||||
import kotlinx.serialization.json.jsonArray
|
|
||||||
import kotlinx.serialization.json.jsonPrimitive
|
|
||||||
import kotlinx.serialization.json.long
|
|
||||||
|
|
||||||
/**
|
|
||||||
* JSON Serializer used to write / read [DHistory] to / from json
|
|
||||||
*/
|
|
||||||
object HistoryTypeSerializer : KSerializer<DHistory> {
|
|
||||||
override val descriptor: SerialDescriptor = buildClassSerialDescriptor("History")
|
|
||||||
|
|
||||||
override fun serialize(encoder: Encoder, value: DHistory) {
|
|
||||||
encoder as JsonEncoder
|
|
||||||
encoder.encodeJsonElement(
|
|
||||||
buildJsonArray {
|
|
||||||
add(value.url)
|
|
||||||
add(value.lastRead)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun deserialize(decoder: Decoder): DHistory {
|
|
||||||
decoder as JsonDecoder
|
|
||||||
val array = decoder.decodeJsonElement().jsonArray
|
|
||||||
return DHistory(
|
|
||||||
url = array[0].jsonPrimitive.content,
|
|
||||||
lastRead = array[1].jsonPrimitive.long,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,56 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.backup.legacy.serializer
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.MangaImpl
|
|
||||||
import kotlinx.serialization.KSerializer
|
|
||||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
|
||||||
import kotlinx.serialization.descriptors.buildClassSerialDescriptor
|
|
||||||
import kotlinx.serialization.encoding.Decoder
|
|
||||||
import kotlinx.serialization.encoding.Encoder
|
|
||||||
import kotlinx.serialization.json.JsonDecoder
|
|
||||||
import kotlinx.serialization.json.JsonEncoder
|
|
||||||
import kotlinx.serialization.json.add
|
|
||||||
import kotlinx.serialization.json.buildJsonArray
|
|
||||||
import kotlinx.serialization.json.int
|
|
||||||
import kotlinx.serialization.json.jsonArray
|
|
||||||
import kotlinx.serialization.json.jsonPrimitive
|
|
||||||
import kotlinx.serialization.json.long
|
|
||||||
|
|
||||||
/**
|
|
||||||
* JSON Serializer used to write / read [MangaImpl] to / from json
|
|
||||||
*/
|
|
||||||
open class MangaBaseSerializer<T : Manga> : KSerializer<T> {
|
|
||||||
override val descriptor: SerialDescriptor = buildClassSerialDescriptor("Manga")
|
|
||||||
|
|
||||||
override fun serialize(encoder: Encoder, value: T) {
|
|
||||||
encoder as JsonEncoder
|
|
||||||
encoder.encodeJsonElement(
|
|
||||||
buildJsonArray {
|
|
||||||
add(value.url)
|
|
||||||
add(value.title)
|
|
||||||
add(value.source)
|
|
||||||
add(value.viewer_flags)
|
|
||||||
add(value.chapter_flags)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
override fun deserialize(decoder: Decoder): T {
|
|
||||||
// make a manga impl and cast as T so that the serializer accepts it
|
|
||||||
return MangaImpl().apply {
|
|
||||||
decoder as JsonDecoder
|
|
||||||
val array = decoder.decodeJsonElement().jsonArray
|
|
||||||
url = array[0].jsonPrimitive.content
|
|
||||||
title = array[1].jsonPrimitive.content
|
|
||||||
source = array[2].jsonPrimitive.long
|
|
||||||
viewer_flags = array[3].jsonPrimitive.int
|
|
||||||
chapter_flags = array[4].jsonPrimitive.int
|
|
||||||
} as T
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Allow for serialization of a manga and manga impl
|
|
||||||
object MangaTypeSerializer : MangaBaseSerializer<Manga>()
|
|
||||||
|
|
||||||
object MangaImplTypeSerializer : MangaBaseSerializer<MangaImpl>()
|
|
@ -1,68 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.backup.legacy.serializer
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Track
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.TrackImpl
|
|
||||||
import kotlinx.serialization.KSerializer
|
|
||||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
|
||||||
import kotlinx.serialization.descriptors.buildClassSerialDescriptor
|
|
||||||
import kotlinx.serialization.encoding.Decoder
|
|
||||||
import kotlinx.serialization.encoding.Encoder
|
|
||||||
import kotlinx.serialization.json.JsonDecoder
|
|
||||||
import kotlinx.serialization.json.JsonEncoder
|
|
||||||
import kotlinx.serialization.json.buildJsonObject
|
|
||||||
import kotlinx.serialization.json.float
|
|
||||||
import kotlinx.serialization.json.int
|
|
||||||
import kotlinx.serialization.json.jsonObject
|
|
||||||
import kotlinx.serialization.json.jsonPrimitive
|
|
||||||
import kotlinx.serialization.json.long
|
|
||||||
import kotlinx.serialization.json.put
|
|
||||||
|
|
||||||
/**
|
|
||||||
* JSON Serializer used to write / read [TrackImpl] to / from json
|
|
||||||
*/
|
|
||||||
open class TrackBaseSerializer<T : Track> : KSerializer<T> {
|
|
||||||
override val descriptor: SerialDescriptor = buildClassSerialDescriptor("Track")
|
|
||||||
|
|
||||||
override fun serialize(encoder: Encoder, value: T) {
|
|
||||||
encoder as JsonEncoder
|
|
||||||
encoder.encodeJsonElement(
|
|
||||||
buildJsonObject {
|
|
||||||
put(TITLE, value.title)
|
|
||||||
put(SYNC, value.sync_id)
|
|
||||||
put(MEDIA, value.media_id)
|
|
||||||
put(LIBRARY, value.library_id)
|
|
||||||
put(LAST_READ, value.last_chapter_read)
|
|
||||||
put(TRACKING_URL, value.tracking_url)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
override fun deserialize(decoder: Decoder): T {
|
|
||||||
// make a track impl and cast as T so that the serializer accepts it
|
|
||||||
return TrackImpl().apply {
|
|
||||||
decoder as JsonDecoder
|
|
||||||
val jsonObject = decoder.decodeJsonElement().jsonObject
|
|
||||||
title = jsonObject[TITLE]!!.jsonPrimitive.content
|
|
||||||
sync_id = jsonObject[SYNC]!!.jsonPrimitive.int
|
|
||||||
media_id = jsonObject[MEDIA]!!.jsonPrimitive.long
|
|
||||||
library_id = jsonObject[LIBRARY]!!.jsonPrimitive.long
|
|
||||||
last_chapter_read = jsonObject[LAST_READ]!!.jsonPrimitive.float
|
|
||||||
tracking_url = jsonObject[TRACKING_URL]!!.jsonPrimitive.content
|
|
||||||
} as T
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val SYNC = "s"
|
|
||||||
private const val MEDIA = "r"
|
|
||||||
private const val LIBRARY = "ml"
|
|
||||||
private const val TITLE = "t"
|
|
||||||
private const val LAST_READ = "l"
|
|
||||||
private const val TRACKING_URL = "u"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Allow for serialization of a track and track impl
|
|
||||||
object TrackTypeSerializer : TrackBaseSerializer<Track>()
|
|
||||||
|
|
||||||
object TrackImplTypeSerializer : TrackBaseSerializer<TrackImpl>()
|
|
@ -29,12 +29,4 @@ object TrackTable {
|
|||||||
const val COL_START_DATE = "start_date"
|
const val COL_START_DATE = "start_date"
|
||||||
|
|
||||||
const val COL_FINISH_DATE = "finish_date"
|
const val COL_FINISH_DATE = "finish_date"
|
||||||
|
|
||||||
val insertFromTempTable: String
|
|
||||||
get() =
|
|
||||||
"""
|
|
||||||
|INSERT INTO $TABLE($COL_ID,$COL_MANGA_ID,$COL_SYNC_ID,$COL_MEDIA_ID,$COL_LIBRARY_ID,$COL_TITLE,$COL_LAST_CHAPTER_READ,$COL_TOTAL_CHAPTERS,$COL_STATUS,$COL_SCORE,$COL_TRACKING_URL,$COL_START_DATE,$COL_FINISH_DATE)
|
|
||||||
|SELECT $COL_ID,$COL_MANGA_ID,$COL_SYNC_ID,$COL_MEDIA_ID,$COL_LIBRARY_ID,$COL_TITLE,$COL_LAST_CHAPTER_READ,$COL_TOTAL_CHAPTERS,$COL_STATUS,$COL_SCORE,$COL_TRACKING_URL,$COL_START_DATE,$COL_FINISH_DATE
|
|
||||||
|FROM ${TABLE}_tmp
|
|
||||||
""".trimMargin()
|
|
||||||
}
|
}
|
||||||
|
@ -22,10 +22,8 @@ import eu.kanade.tachiyomi.R
|
|||||||
import eu.kanade.tachiyomi.data.backup.BackupConst
|
import eu.kanade.tachiyomi.data.backup.BackupConst
|
||||||
import eu.kanade.tachiyomi.data.backup.BackupCreatorJob
|
import eu.kanade.tachiyomi.data.backup.BackupCreatorJob
|
||||||
import eu.kanade.tachiyomi.data.backup.BackupRestoreService
|
import eu.kanade.tachiyomi.data.backup.BackupRestoreService
|
||||||
import eu.kanade.tachiyomi.data.backup.ValidatorParseException
|
|
||||||
import eu.kanade.tachiyomi.data.backup.full.FullBackupRestoreValidator
|
import eu.kanade.tachiyomi.data.backup.full.FullBackupRestoreValidator
|
||||||
import eu.kanade.tachiyomi.data.backup.full.models.BackupFull
|
import eu.kanade.tachiyomi.data.backup.full.models.BackupFull
|
||||||
import eu.kanade.tachiyomi.data.backup.legacy.LegacyBackupRestoreValidator
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.DialogController
|
import eu.kanade.tachiyomi.ui.base.controller.DialogController
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.requestPermissionsSafe
|
import eu.kanade.tachiyomi.ui.base.controller.requestPermissionsSafe
|
||||||
import eu.kanade.tachiyomi.util.preference.bindTo
|
import eu.kanade.tachiyomi.util.preference.bindTo
|
||||||
@ -272,19 +270,9 @@ class SettingsBackupController : SettingsController() {
|
|||||||
val uri: Uri = args.getParcelable(KEY_URI)!!
|
val uri: Uri = args.getParcelable(KEY_URI)!!
|
||||||
|
|
||||||
return try {
|
return try {
|
||||||
var type = BackupConst.BACKUP_TYPE_FULL
|
val results = FullBackupRestoreValidator().validate(activity, uri)
|
||||||
val results = try {
|
|
||||||
FullBackupRestoreValidator().validate(activity, uri)
|
|
||||||
} catch (_: ValidatorParseException) {
|
|
||||||
type = BackupConst.BACKUP_TYPE_LEGACY
|
|
||||||
LegacyBackupRestoreValidator().validate(activity, uri)
|
|
||||||
}
|
|
||||||
|
|
||||||
var message = if (type == BackupConst.BACKUP_TYPE_FULL) {
|
var message = activity.getString(R.string.backup_restore_content_full)
|
||||||
activity.getString(R.string.backup_restore_content_full)
|
|
||||||
} else {
|
|
||||||
activity.getString(R.string.backup_restore_content)
|
|
||||||
}
|
|
||||||
if (results.missingSources.isNotEmpty()) {
|
if (results.missingSources.isNotEmpty()) {
|
||||||
message += "\n\n${activity.getString(R.string.backup_restore_missing_sources)}\n${results.missingSources.joinToString("\n") { "- $it" }}"
|
message += "\n\n${activity.getString(R.string.backup_restore_missing_sources)}\n${results.missingSources.joinToString("\n") { "- $it" }}"
|
||||||
}
|
}
|
||||||
@ -296,7 +284,7 @@ class SettingsBackupController : SettingsController() {
|
|||||||
.setTitle(R.string.pref_restore_backup)
|
.setTitle(R.string.pref_restore_backup)
|
||||||
.setMessage(message)
|
.setMessage(message)
|
||||||
.setPositiveButton(R.string.action_restore) { _, _ ->
|
.setPositiveButton(R.string.action_restore) { _, _ ->
|
||||||
BackupRestoreService.start(activity, uri, type)
|
BackupRestoreService.start(activity, uri)
|
||||||
}
|
}
|
||||||
.create()
|
.create()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
@ -436,16 +436,13 @@
|
|||||||
<string name="pref_backup_service_category">Automatic backups</string>
|
<string name="pref_backup_service_category">Automatic backups</string>
|
||||||
<string name="pref_backup_interval">Backup frequency</string>
|
<string name="pref_backup_interval">Backup frequency</string>
|
||||||
<string name="pref_backup_slots">Maximum backups</string>
|
<string name="pref_backup_slots">Maximum backups</string>
|
||||||
<string name="source_not_found_name">Source not found: %1$s</string>
|
|
||||||
<string name="tracker_not_logged_in">Not logged in: %1$s</string>
|
<string name="tracker_not_logged_in">Not logged in: %1$s</string>
|
||||||
<string name="backup_restore_invalid_uri">Error: empty URI</string>
|
<string name="backup_restore_invalid_uri">Error: empty URI</string>
|
||||||
<string name="backup_created">Backup created</string>
|
<string name="backup_created">Backup created</string>
|
||||||
<string name="invalid_backup_file">Invalid backup file</string>
|
<string name="invalid_backup_file">Invalid backup file</string>
|
||||||
<string name="invalid_backup_file_missing_data">File is missing data.</string>
|
|
||||||
<string name="invalid_backup_file_missing_manga">Backup does not contain any manga.</string>
|
<string name="invalid_backup_file_missing_manga">Backup does not contain any manga.</string>
|
||||||
<string name="backup_restore_missing_sources">Missing sources:</string>
|
<string name="backup_restore_missing_sources">Missing sources:</string>
|
||||||
<string name="backup_restore_missing_trackers">Trackers not logged into:</string>
|
<string name="backup_restore_missing_trackers">Trackers not logged into:</string>
|
||||||
<string name="backup_restore_content">Restore uses sources to fetch data, carrier costs may apply.\n\nMake sure you have installed all necessary extensions and are logged in to sources and tracking services before restoring.</string>
|
|
||||||
<string name="backup_restore_content_full">Data from the backup file will be restored.\n\nYou will need to install any missing extensions and log in to tracking services afterwards to use them.</string>
|
<string name="backup_restore_content_full">Data from the backup file will be restored.\n\nYou will need to install any missing extensions and log in to tracking services afterwards to use them.</string>
|
||||||
<string name="restore_completed">Restore completed</string>
|
<string name="restore_completed">Restore completed</string>
|
||||||
<string name="restore_duration">%02d min, %02d sec</string>
|
<string name="restore_duration">%02d min, %02d sec</string>
|
||||||
|
Loading…
Reference in New Issue
Block a user