diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 6a536ee6c..a1c830a08 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -45,7 +45,7 @@
android:label="@string/label_categories"
android:parentActivityName=".ui.main.MainActivity" />
+
+
+
+
diff --git a/app/src/main/java/eu/kanade/tachiyomi/App.kt b/app/src/main/java/eu/kanade/tachiyomi/App.kt
index 6bc7e91ab..9fd73b878 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/App.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/App.kt
@@ -5,6 +5,7 @@ import android.content.Context
import android.content.res.Configuration
import android.support.multidex.MultiDex
import com.evernote.android.job.JobManager
+import eu.kanade.tachiyomi.data.backup.BackupCreatorJob
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
import eu.kanade.tachiyomi.data.updater.UpdateCheckerJob
import eu.kanade.tachiyomi.util.LocaleHelper
@@ -58,6 +59,7 @@ open class App : Application() {
when (tag) {
LibraryUpdateJob.TAG -> LibraryUpdateJob()
UpdateCheckerJob.TAG -> UpdateCheckerJob()
+ BackupCreatorJob.TAG -> BackupCreatorJob()
else -> null
}
}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreateService.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreateService.kt
new file mode 100644
index 000000000..d892ac58c
--- /dev/null
+++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreateService.kt
@@ -0,0 +1,166 @@
+package eu.kanade.tachiyomi.data.backup
+
+import android.app.IntentService
+import android.content.Context
+import android.content.Intent
+import android.net.Uri
+import com.github.salomonbrys.kotson.set
+import com.google.gson.JsonArray
+import com.google.gson.JsonObject
+import com.hippo.unifile.UniFile
+import eu.kanade.tachiyomi.data.backup.models.Backup
+import eu.kanade.tachiyomi.data.backup.models.Backup.CATEGORIES
+import eu.kanade.tachiyomi.data.backup.models.Backup.MANGAS
+import eu.kanade.tachiyomi.data.backup.models.Backup.VERSION
+import eu.kanade.tachiyomi.data.database.models.Manga
+import eu.kanade.tachiyomi.ui.setting.SettingsBackupFragment
+import eu.kanade.tachiyomi.util.sendLocalBroadcast
+import timber.log.Timber
+import eu.kanade.tachiyomi.BuildConfig.APPLICATION_ID as ID
+
+/**
+ * [IntentService] used to backup [Manga] information to [JsonArray]
+ */
+class BackupCreateService : IntentService(NAME) {
+
+ companion object {
+ // Name of class
+ private const val NAME = "BackupCreateService"
+
+ // Uri as string
+ private const val EXTRA_URI = "$ID.$NAME.EXTRA_URI"
+ // Backup called from job
+ private const val EXTRA_IS_JOB = "$ID.$NAME.EXTRA_IS_JOB"
+ // Options for backup
+ private const val EXTRA_FLAGS = "$ID.$NAME.EXTRA_FLAGS"
+
+ // Filter options
+ internal const val BACKUP_CATEGORY = 0x1
+ internal const val BACKUP_CATEGORY_MASK = 0x1
+ internal const val BACKUP_CHAPTER = 0x2
+ internal const val BACKUP_CHAPTER_MASK = 0x2
+ internal const val BACKUP_HISTORY = 0x4
+ internal const val BACKUP_HISTORY_MASK = 0x4
+ internal const val BACKUP_TRACK = 0x8
+ internal const val BACKUP_TRACK_MASK = 0x8
+ internal const val BACKUP_ALL = 0xF
+
+ /**
+ * Make a backup from library
+ *
+ * @param context context of application
+ * @param path path of Uri
+ * @param flags determines what to backup
+ * @param isJob backup called from job
+ */
+ fun makeBackup(context: Context, path: String, flags: Int, isJob: Boolean = false) {
+ val intent = Intent(context, BackupCreateService::class.java).apply {
+ putExtra(EXTRA_URI, path)
+ putExtra(EXTRA_IS_JOB, isJob)
+ putExtra(EXTRA_FLAGS, flags)
+ }
+ context.startService(intent)
+ }
+ }
+
+ private val backupManager by lazy { BackupManager(this) }
+
+ override fun onHandleIntent(intent: Intent?) {
+ if (intent == null) return
+
+ // Get values
+ val uri = intent.getStringExtra(EXTRA_URI)
+ val isJob = intent.getBooleanExtra(EXTRA_IS_JOB, false)
+ val flags = intent.getIntExtra(EXTRA_FLAGS, 0)
+ // Create backup
+ createBackupFromApp(Uri.parse(uri), flags, isJob)
+ }
+
+ /**
+ * Create backup Json file from database
+ *
+ * @param uri path of Uri
+ * @param isJob backup called from job
+ */
+ fun createBackupFromApp(uri: Uri, flags: Int, isJob: Boolean) {
+ // Create root object
+ val root = JsonObject()
+
+ // Create information object
+ val information = JsonObject()
+
+ // Create manga array
+ val mangaEntries = JsonArray()
+
+ // Create category array
+ val categoryEntries = JsonArray()
+
+ // Add value's to root
+ root[VERSION] = Backup.CURRENT_VERSION
+ root[MANGAS] = mangaEntries
+ root[CATEGORIES] = categoryEntries
+
+ backupManager.databaseHelper.inTransaction {
+ // Get manga from database
+ val mangas = backupManager.getFavoriteManga()
+
+ // Backup library manga and its dependencies
+ mangas.forEach { manga ->
+ mangaEntries.add(backupManager.backupMangaObject(manga, flags))
+ }
+
+ // Backup categories
+ if ((flags and BACKUP_CATEGORY_MASK) == BACKUP_CATEGORY) {
+ backupManager.backupCategories(categoryEntries)
+ }
+ }
+
+ try {
+ // When BackupCreatorJob
+ if (isJob) {
+ // Get dir of file
+ val dir = UniFile.fromUri(this, uri)
+
+ // Delete older backups
+ val numberOfBackups = backupManager.numberOfBackups()
+ val backupRegex = Regex("""tachiyomi_\d+-\d+-\d+_\d+-\d+.json""")
+ dir.listFiles { _, filename -> backupRegex.matches(filename) }
+ .orEmpty()
+ .sortedByDescending { it.name }
+ .drop(numberOfBackups - 1)
+ .forEach { it.delete() }
+
+ // Create new file to place backup
+ val newFile = dir.createFile(Backup.getDefaultFilename())
+ ?: throw Exception("Couldn't create backup file")
+
+ newFile.openOutputStream().bufferedWriter().use {
+ backupManager.parser.toJson(root, it)
+ }
+ } else {
+ val file = UniFile.fromUri(this, uri)
+ ?: throw Exception("Couldn't create backup file")
+ file.openOutputStream().bufferedWriter().use {
+ backupManager.parser.toJson(root, it)
+ }
+
+ // Show completed dialog
+ val intent = Intent(SettingsBackupFragment.INTENT_FILTER).apply {
+ putExtra(SettingsBackupFragment.ACTION, SettingsBackupFragment.ACTION_BACKUP_COMPLETED_DIALOG)
+ putExtra(SettingsBackupFragment.EXTRA_URI, file.uri.toString())
+ }
+ sendLocalBroadcast(intent)
+ }
+ } catch (e: Exception) {
+ Timber.e(e)
+ if (!isJob) {
+ // Show error dialog
+ val intent = Intent(SettingsBackupFragment.INTENT_FILTER).apply {
+ putExtra(SettingsBackupFragment.ACTION, SettingsBackupFragment.ACTION_ERROR_BACKUP_DIALOG)
+ putExtra(SettingsBackupFragment.EXTRA_ERROR_MESSAGE, e.message)
+ }
+ sendLocalBroadcast(intent)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreatorJob.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreatorJob.kt
new file mode 100644
index 000000000..6f4796be6
--- /dev/null
+++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreatorJob.kt
@@ -0,0 +1,41 @@
+package eu.kanade.tachiyomi.data.backup
+
+import com.evernote.android.job.Job
+import com.evernote.android.job.JobManager
+import com.evernote.android.job.JobRequest
+import eu.kanade.tachiyomi.data.preference.PreferencesHelper
+import eu.kanade.tachiyomi.data.preference.getOrDefault
+import uy.kohesive.injekt.Injekt
+import uy.kohesive.injekt.api.get
+
+class BackupCreatorJob : Job() {
+
+ override fun onRunJob(params: Params): Result {
+ val preferences = Injekt.get()
+ val path = preferences.backupsDirectory().getOrDefault()
+ val flags = BackupCreateService.BACKUP_ALL
+ BackupCreateService.makeBackup(context,path,flags,true)
+ return Result.SUCCESS
+ }
+
+ companion object {
+ const val TAG = "BackupCreator"
+
+ fun setupTask(prefInterval: Int? = null) {
+ val preferences = Injekt.get()
+ val interval = prefInterval ?: preferences.backupInterval().getOrDefault()
+ if (interval > 0) {
+ JobRequest.Builder(TAG)
+ .setPeriodic(interval * 60 * 60 * 1000L, 10 * 60 * 1000)
+ .setPersisted(true)
+ .setUpdateCurrent(true)
+ .build()
+ .schedule()
+ }
+ }
+
+ fun cancelTask() {
+ JobManager.instance().cancelAllForTag(TAG)
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupManager.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupManager.kt
index 90abe0ebd..2fc1998d5 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupManager.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupManager.kt
@@ -1,203 +1,213 @@
package eu.kanade.tachiyomi.data.backup
-import com.github.salomonbrys.kotson.fromJson
+import android.content.Context
+import com.github.salomonbrys.kotson.*
import com.google.gson.*
-import com.google.gson.stream.JsonReader
-import eu.kanade.tachiyomi.data.backup.serializer.BooleanSerializer
-import eu.kanade.tachiyomi.data.backup.serializer.IdExclusion
-import eu.kanade.tachiyomi.data.backup.serializer.IntegerSerializer
-import eu.kanade.tachiyomi.data.backup.serializer.LongSerializer
+import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_CATEGORY
+import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_CATEGORY_MASK
+import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_CHAPTER
+import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_CHAPTER_MASK
+import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_HISTORY
+import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_HISTORY_MASK
+import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_TRACK
+import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_TRACK_MASK
+import eu.kanade.tachiyomi.data.backup.models.Backup.CATEGORIES
+import eu.kanade.tachiyomi.data.backup.models.Backup.CHAPTERS
+import eu.kanade.tachiyomi.data.backup.models.Backup.CURRENT_VERSION
+import eu.kanade.tachiyomi.data.backup.models.Backup.HISTORY
+import eu.kanade.tachiyomi.data.backup.models.Backup.MANGA
+import eu.kanade.tachiyomi.data.backup.models.Backup.TRACK
+import eu.kanade.tachiyomi.data.backup.models.DHistory
+import eu.kanade.tachiyomi.data.backup.serializer.*
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.*
-import java.io.*
+import eu.kanade.tachiyomi.data.preference.PreferencesHelper
+import eu.kanade.tachiyomi.data.preference.getOrDefault
+import eu.kanade.tachiyomi.source.Source
+import eu.kanade.tachiyomi.source.SourceManager
+import eu.kanade.tachiyomi.util.syncChaptersWithSource
+import rx.Observable
+import uy.kohesive.injekt.injectLazy
import java.util.*
-/**
- * This class provides the necessary methods to create and restore backups for the data of the
- * application. The backup follows a JSON structure, with the following scheme:
- *
- * {
- * "mangas": [
- * {
- * "manga": {"id": 1, ...},
- * "chapters": [{"id": 1, ...}, {...}],
- * "sync": [{"id": 1, ...}, {...}],
- * "categories": ["cat1", "cat2", ...]
- * },
- * { ... }
- * ],
- * "categories": [
- * {"id": 1, ...},
- * {"id": 2, ...}
- * ]
- * }
- *
- * @param db the database helper.
- */
-class BackupManager(private val db: DatabaseHelper) {
-
- private val MANGA = "manga"
- private val MANGAS = "mangas"
- private val CHAPTERS = "chapters"
- private val TRACK = "sync"
- private val CATEGORIES = "categories"
-
- @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
- private val gson = GsonBuilder()
- .registerTypeAdapter(java.lang.Integer::class.java, IntegerSerializer())
- .registerTypeAdapter(java.lang.Boolean::class.java, BooleanSerializer())
- .registerTypeAdapter(java.lang.Long::class.java, LongSerializer())
- .setExclusionStrategies(IdExclusion())
- .create()
+class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
/**
- * Backups the data of the application to a file.
- *
- * @param file the file where the backup will be saved.
- * @throws IOException if there's any IO error.
+ * Database.
*/
- @Throws(IOException::class)
- fun backupToFile(file: File) {
- val root = backupToJson()
+ internal val databaseHelper: DatabaseHelper by injectLazy()
- FileWriter(file).use {
- gson.toJson(root, it)
+ /**
+ * Source manager.
+ */
+ internal val sourceManager: SourceManager by injectLazy()
+
+ /**
+ * Version of parser
+ */
+ var version: Int = version
+ private set
+
+ /**
+ * Json Parser
+ */
+ var parser: Gson = initParser()
+
+ /**
+ * Preferences
+ */
+ private val preferences: PreferencesHelper by injectLazy()
+
+ /**
+ * Set version of parser
+ *
+ * @param version version of parser
+ */
+ internal fun setVersion(version: Int) {
+ this.version = version
+ parser = initParser()
+ }
+
+ private fun initParser(): Gson {
+ return when (version) {
+ 1 -> GsonBuilder().create()
+ 2 -> GsonBuilder()
+ .registerTypeAdapter(MangaTypeAdapter.build())
+ .registerTypeHierarchyAdapter(ChapterTypeAdapter.build())
+ .registerTypeAdapter(CategoryTypeAdapter.build())
+ .registerTypeAdapter(HistoryTypeAdapter.build())
+ .registerTypeHierarchyAdapter(TrackTypeAdapter.build())
+ .create()
+ else -> throw Exception("Json version unknown")
}
}
/**
- * Creates a JSON object containing the backup of the app's data.
+ * Backup the categories of library
*
- * @return the backup as a JSON object.
+ * @param root root of categories json
*/
- fun backupToJson(): JsonObject {
- val root = JsonObject()
-
- // Backup library mangas and its dependencies
- val mangaEntries = JsonArray()
- root.add(MANGAS, mangaEntries)
- for (manga in db.getFavoriteMangas().executeAsBlocking()) {
- mangaEntries.add(backupManga(manga))
- }
-
- // Backup categories
- val categoryEntries = JsonArray()
- root.add(CATEGORIES, categoryEntries)
- for (category in db.getCategories().executeAsBlocking()) {
- categoryEntries.add(backupCategory(category))
- }
-
- return root
+ internal fun backupCategories(root: JsonArray) {
+ val categories = databaseHelper.getCategories().executeAsBlocking()
+ categories.forEach { root.add(parser.toJsonTree(it)) }
}
/**
- * Backups a manga and its related data (chapters, categories this manga is in, sync...).
+ * Convert a manga to Json
*
- * @param manga the manga to backup.
- * @return a JSON object containing all the data of the manga.
+ * @param manga manga that gets converted
+ * @return [JsonElement] containing manga information
*/
- private fun backupManga(manga: Manga): JsonObject {
+ internal fun backupMangaObject(manga: Manga, options: Int): JsonElement {
// Entry for this manga
val entry = JsonObject()
// Backup manga fields
- entry.add(MANGA, gson.toJsonTree(manga))
+ entry[MANGA] = parser.toJsonTree(manga)
- // Backup all the chapters
- val chapters = db.getChapters(manga).executeAsBlocking()
- if (!chapters.isEmpty()) {
- entry.add(CHAPTERS, gson.toJsonTree(chapters))
- }
-
- // Backup tracks
- val tracks = db.getTracks(manga).executeAsBlocking()
- if (!tracks.isEmpty()) {
- entry.add(TRACK, gson.toJsonTree(tracks))
- }
-
- // Backup categories for this manga
- val categoriesForManga = db.getCategoriesForManga(manga).executeAsBlocking()
- if (!categoriesForManga.isEmpty()) {
- val categoriesNames = ArrayList()
- for (category in categoriesForManga) {
- categoriesNames.add(category.name)
+ // Check if user wants chapter information in backup
+ if (options and BACKUP_CHAPTER_MASK == BACKUP_CHAPTER) {
+ // Backup all the chapters
+ val chapters = databaseHelper.getChapters(manga).executeAsBlocking()
+ if (!chapters.isEmpty()) {
+ val chaptersJson = parser.toJsonTree(chapters)
+ if (chaptersJson.asJsonArray.size() > 0) {
+ entry[CHAPTERS] = chaptersJson
+ }
+ }
+ }
+
+ // Check if user wants category information in backup
+ if (options and BACKUP_CATEGORY_MASK == BACKUP_CATEGORY) {
+ // Backup categories for this manga
+ val categoriesForManga = databaseHelper.getCategoriesForManga(manga).executeAsBlocking()
+ if (!categoriesForManga.isEmpty()) {
+ val categoriesNames = categoriesForManga.map { it.name }
+ entry[CATEGORIES] = parser.toJsonTree(categoriesNames)
+ }
+ }
+
+ // Check if user wants track information in backup
+ if (options and BACKUP_TRACK_MASK == BACKUP_TRACK) {
+ val tracks = databaseHelper.getTracks(manga).executeAsBlocking()
+ if (!tracks.isEmpty()) {
+ entry[TRACK] = parser.toJsonTree(tracks)
+ }
+ }
+
+ // Check if user wants history information in backup
+ if (options and BACKUP_HISTORY_MASK == BACKUP_HISTORY) {
+ val historyForManga = databaseHelper.getHistoryByMangaId(manga.id!!).executeAsBlocking()
+ if (!historyForManga.isEmpty()) {
+ val historyData = historyForManga.mapNotNull { history ->
+ val url = databaseHelper.getChapter(history.chapter_id).executeAsBlocking()?.url
+ url?.let { DHistory(url, history.last_read) }
+ }
+ val historyJson = parser.toJsonTree(historyData)
+ if (historyJson.asJsonArray.size() > 0) {
+ entry[HISTORY] = historyJson
+ }
}
- entry.add(CATEGORIES, gson.toJsonTree(categoriesNames))
}
return entry
}
- /**
- * Backups a category.
- *
- * @param category the category to backup.
- * @return a JSON object containing the data of the category.
- */
- private fun backupCategory(category: Category): JsonElement {
- return gson.toJsonTree(category)
+ fun restoreMangaNoFetch(manga: Manga, dbManga: Manga) {
+ manga.id = dbManga.id
+ manga.copyFrom(dbManga)
+ manga.favorite = true
+ insertManga(manga)
}
/**
- * Restores a backup from a file.
+ * [Observable] that fetches manga information
*
- * @param file the file containing the backup.
- * @throws IOException if there's any IO error.
+ * @param source source of manga
+ * @param manga manga that needs updating
+ * @return [Observable] that contains manga
*/
- @Throws(IOException::class)
- fun restoreFromFile(file: File) {
- JsonReader(FileReader(file)).use {
- val root = JsonParser().parse(it).asJsonObject
- restoreFromJson(root)
- }
+ fun restoreMangaFetchObservable(source: Source, manga: Manga): Observable {
+ return source.fetchMangaDetails(manga)
+ .map { networkManga ->
+ manga.copyFrom(networkManga)
+ manga.favorite = true
+ manga.initialized = true
+ manga.id = insertManga(manga)
+ manga
+ }
}
/**
- * Restores a backup from an input stream.
+ * [Observable] that fetches chapter information
*
- * @param stream the stream containing the backup.
- * @throws IOException if there's any IO error.
+ * @param source source of manga
+ * @param manga manga that needs updating
+ * @return [Observable] that contains manga
*/
- @Throws(IOException::class)
- fun restoreFromStream(stream: InputStream) {
- JsonReader(InputStreamReader(stream)).use {
- val root = JsonParser().parse(it).asJsonObject
- restoreFromJson(root)
- }
+ fun restoreChapterFetchObservable(source: Source, manga: Manga, chapters: List): Observable, List>> {
+ return source.fetchChapterList(manga)
+ .map { syncChaptersWithSource(databaseHelper, it, manga, source) }
+ .doOnNext {
+ if (it.first.isNotEmpty()) {
+ chapters.forEach { it.manga_id = manga.id }
+ insertChapters(chapters)
+ }
+ }
}
/**
- * Restores a backup from a JSON object. Everything executes in a single transaction so that
- * nothing is modified if there's an error.
+ * Restore the categories from Json
*
- * @param root the root of the JSON.
+ * @param jsonCategories array containing categories
*/
- fun restoreFromJson(root: JsonObject) {
- db.inTransaction {
- // Restore categories
- root.get(CATEGORIES)?.let {
- restoreCategories(it.asJsonArray)
- }
-
- // Restore mangas
- root.get(MANGAS)?.let {
- restoreMangas(it.asJsonArray)
- }
- }
- }
-
- /**
- * Restores the categories.
- *
- * @param jsonCategories the categories of the json.
- */
- private fun restoreCategories(jsonCategories: JsonArray) {
+ internal fun restoreCategories(jsonCategories: JsonArray) {
// Get categories from file and from db
- val dbCategories = db.getCategories().executeAsBlocking()
- val backupCategories = gson.fromJson>(jsonCategories)
+ val dbCategories = databaseHelper.getCategories().executeAsBlocking()
+ val backupCategories = parser.fromJson>(jsonCategories)
// Iterate over them
- for (category in backupCategories) {
+ backupCategories.forEach { category ->
// Used to know if the category is already in the db
var found = false
for (dbCategory in dbCategories) {
@@ -214,102 +224,20 @@ class BackupManager(private val db: DatabaseHelper) {
if (!found) {
// Let the db assign the id
category.id = null
- val result = db.insertCategory(category).executeAsBlocking()
+ val result = databaseHelper.insertCategory(category).executeAsBlocking()
category.id = result.insertedId()?.toInt()
}
}
}
- /**
- * Restores all the mangas and its related data.
- *
- * @param jsonMangas the mangas and its related data (chapters, sync, categories) from the json.
- */
- private fun restoreMangas(jsonMangas: JsonArray) {
- for (backupManga in jsonMangas) {
- // Map every entry to objects
- val element = backupManga.asJsonObject
- val manga = gson.fromJson(element.get(MANGA), MangaImpl::class.java)
- val chapters = gson.fromJson>(element.get(CHAPTERS) ?: JsonArray())
- val tracks = gson.fromJson>(element.get(TRACK) ?: JsonArray())
- val categories = gson.fromJson>(element.get(CATEGORIES) ?: JsonArray())
-
- // Restore everything related to this manga
- restoreManga(manga)
- restoreChaptersForManga(manga, chapters)
- restoreSyncForManga(manga, tracks)
- restoreCategoriesForManga(manga, categories)
- }
- }
-
- /**
- * Restores a manga.
- *
- * @param manga the manga to restore.
- */
- private fun restoreManga(manga: Manga) {
- // Try to find existing manga in db
- val dbManga = db.getManga(manga.url, manga.source).executeAsBlocking()
- if (dbManga == null) {
- // Let the db assign the id
- manga.id = null
- val result = db.insertManga(manga).executeAsBlocking()
- manga.id = result.insertedId()
- } else {
- // If it exists already, we copy only the values related to the source from the db
- // (they can be up to date). Local values (flags) are kept from the backup.
- manga.id = dbManga.id
- manga.copyFrom(dbManga)
- manga.favorite = true
- db.insertManga(manga).executeAsBlocking()
- }
- }
-
- /**
- * Restores the chapters of a manga.
- *
- * @param manga the manga whose chapters have to be restored.
- * @param chapters the chapters to restore.
- */
- private fun restoreChaptersForManga(manga: Manga, chapters: List) {
- // Fix foreign keys with the current manga id
- for (chapter in chapters) {
- chapter.manga_id = manga.id
- }
-
- val dbChapters = db.getChapters(manga).executeAsBlocking()
- val chaptersToUpdate = ArrayList()
- for (backupChapter in chapters) {
- // Try to find existing chapter in db
- val pos = dbChapters.indexOf(backupChapter)
- if (pos != -1) {
- // The chapter is already in the db, only update its fields
- val dbChapter = dbChapters[pos]
- // If one of them was read, the chapter will be marked as read
- dbChapter.read = backupChapter.read || dbChapter.read
- dbChapter.last_page_read = Math.max(backupChapter.last_page_read, dbChapter.last_page_read)
- chaptersToUpdate.add(dbChapter)
- } else {
- // Insert new chapter. Let the db assign the id
- backupChapter.id = null
- chaptersToUpdate.add(backupChapter)
- }
- }
-
- // Update database
- if (!chaptersToUpdate.isEmpty()) {
- db.insertChapters(chaptersToUpdate).executeAsBlocking()
- }
- }
-
/**
* Restores the categories a manga is in.
*
* @param manga the manga whose categories have to be restored.
* @param categories the categories to restore.
*/
- private fun restoreCategoriesForManga(manga: Manga, categories: List) {
- val dbCategories = db.getCategories().executeAsBlocking()
+ internal fun restoreCategoriesForManga(manga: Manga, categories: List) {
+ val dbCategories = databaseHelper.getCategories().executeAsBlocking()
val mangaCategoriesToUpdate = ArrayList()
for (backupCategoryStr in categories) {
for (dbCategory in dbCategories) {
@@ -324,45 +252,151 @@ class BackupManager(private val db: DatabaseHelper) {
if (!mangaCategoriesToUpdate.isEmpty()) {
val mangaAsList = ArrayList()
mangaAsList.add(manga)
- db.deleteOldMangasCategories(mangaAsList).executeAsBlocking()
- db.insertMangasCategories(mangaCategoriesToUpdate).executeAsBlocking()
+ databaseHelper.deleteOldMangasCategories(mangaAsList).executeAsBlocking()
+ databaseHelper.insertMangasCategories(mangaCategoriesToUpdate).executeAsBlocking()
}
}
+ /**
+ * Restore history from Json
+ *
+ * @param history list containing history to be restored
+ */
+ internal fun restoreHistoryForManga(history: List) {
+ // List containing history to be updated
+ val historyToBeUpdated = ArrayList()
+ 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 = Math.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.updateHistoryLastRead(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.
*/
- private fun restoreSyncForManga(manga: Manga, tracks: List