diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/AbstractBackupManager.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/AbstractBackupManager.kt deleted file mode 100644 index fce4714cc8..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/AbstractBackupManager.kt +++ /dev/null @@ -1,99 +0,0 @@ -package eu.kanade.tachiyomi.data.backup - -import android.content.Context -import android.net.Uri -import eu.kanade.tachiyomi.data.database.DatabaseHelper -import eu.kanade.tachiyomi.data.database.models.Chapter -import eu.kanade.tachiyomi.data.database.models.Manga -import eu.kanade.tachiyomi.data.library.CustomMangaManager -import eu.kanade.tachiyomi.data.preference.PreferencesHelper -import eu.kanade.tachiyomi.data.track.TrackManager -import eu.kanade.tachiyomi.source.Source -import eu.kanade.tachiyomi.source.SourceManager -import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get - -abstract class AbstractBackupManager(protected val context: Context) { - - internal val db: DatabaseHelper = Injekt.get() - internal val sourceManager: SourceManager = Injekt.get() - internal val trackManager: TrackManager = Injekt.get() - protected val preferences: PreferencesHelper = Injekt.get() - protected val customMangaManager: CustomMangaManager = Injekt.get() - - abstract fun createBackup(uri: Uri, flags: Int, isAutoBackup: Boolean): String? - - /** - * Returns manga - * - * @return [Manga], null if not found - */ - internal fun getMangaFromDatabase(manga: Manga): Manga? = - db.getManga(manga.url, manga.source).executeAsBlocking() - - /** - * Fetches chapter information. - * - * @param source source of manga - * @param manga manga that needs updating - * @param chapters list of chapters in the backup - * @return Updated manga chapters. - */ - internal suspend fun restoreChapters(source: Source, manga: Manga, chapters: List): Pair, List> { - val fetchedChapters = source.getChapterList(manga) - val syncedChapters = syncChaptersWithSource(db, fetchedChapters, manga, source) - if (syncedChapters.first.isNotEmpty()) { - chapters.forEach { it.manga_id = manga.id } - updateChapters(chapters) - } - return syncedChapters - } - - /** - * Returns list containing manga from library - * - * @return [Manga] from library - */ - protected fun getFavoriteManga(): List = - db.getFavoriteMangas().executeAsBlocking() - - protected fun getReadManga(): List = - db.getReadNotInLibraryMangas().executeAsBlocking() - - /** - * Inserts manga and returns id - * - * @return id of [Manga], null if not found - */ - internal fun insertManga(manga: Manga): Long? = - db.insertManga(manga).executeAsBlocking().insertedId() - - /** - * Inserts list of chapters - */ - protected fun insertChapters(chapters: List) { - db.insertChapters(chapters).executeAsBlocking() - } - - /** - * Updates a list of chapters - */ - protected fun updateChapters(chapters: List) { - db.updateChaptersBackup(chapters).executeAsBlocking() - } - - /** - * Updates a list of chapters with known database ids - */ - protected fun updateKnownChapters(chapters: List) { - db.updateKnownChaptersBackup(chapters).executeAsBlocking() - } - - /** - * Return number of backups. - * - * @return number of backups selected by user - */ - protected fun numberOfBackups(): Int = preferences.numberOfBackups().get() -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/AbstractBackupRestore.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/AbstractBackupRestore.kt deleted file mode 100644 index 02b64b47cd..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/AbstractBackupRestore.kt +++ /dev/null @@ -1,137 +0,0 @@ -package eu.kanade.tachiyomi.data.backup - -import android.content.Context -import android.net.Uri -import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.data.database.DatabaseHelper -import eu.kanade.tachiyomi.data.database.models.Chapter -import eu.kanade.tachiyomi.data.database.models.Manga -import eu.kanade.tachiyomi.data.database.models.Track -import eu.kanade.tachiyomi.data.library.CustomMangaManager -import eu.kanade.tachiyomi.data.track.TrackManager -import eu.kanade.tachiyomi.source.Source -import eu.kanade.tachiyomi.util.chapter.NoChaptersException -import eu.kanade.tachiyomi.util.system.createFileInCacheDir -import uy.kohesive.injekt.injectLazy -import java.io.File -import java.text.SimpleDateFormat -import java.util.Date -import java.util.Locale - -abstract class AbstractBackupRestore(protected val context: Context, protected val notifier: BackupNotifier) { - - protected val db: DatabaseHelper by injectLazy() - protected val trackManager: TrackManager by injectLazy() - protected val customMangaManager: CustomMangaManager by injectLazy() - - protected lateinit var backupManager: T - - protected var restoreAmount = 0 - protected var restoreProgress = 0 - - /** - * Mapping of source ID to source name from backup data - */ - protected var sourceMapping: Map = emptyMap() - - protected val errors = mutableListOf>() - - abstract suspend fun performRestore(uri: Uri): Boolean - - suspend fun restoreBackup(uri: Uri): Boolean { - val startTime = System.currentTimeMillis() - restoreProgress = 0 - errors.clear() - - if (!performRestore(uri)) { - return false - } - - val endTime = System.currentTimeMillis() - val time = endTime - startTime - - val logFile = writeErrorLog() - - notifier.showRestoreComplete(time, errors.size, logFile.parent, logFile.name) - return true - } - - /** - * Fetches chapter information. - * - * @param source source of manga - * @param manga manga that needs updating - * @return Updated manga chapters. - */ - internal suspend fun updateChapters(source: Source, manga: Manga, chapters: List): Pair, List> { - return try { - backupManager.restoreChapters(source, manga, chapters) - } catch (e: Exception) { - // If there's any error, return empty update and continue. - val errorMessage = if (e is NoChaptersException) { - context.getString(R.string.no_chapters_error) - } else { - e.message - } - errors.add(Date() to "${manga.title} - $errorMessage") - Pair(emptyList(), emptyList()) - } - } - - /** - * Refreshes tracking information. - * - * @param manga manga that needs updating. - * @param tracks list containing tracks from restore file. - */ - internal suspend fun updateTracking(manga: Manga, tracks: List) { - tracks.forEach { track -> - val service = trackManager.getService(track.sync_id) - if (service != null && service.isLogged) { - try { - val updatedTrack = service.refresh(track) - db.insertTrack(updatedTrack).executeAsBlocking() - } catch (e: Exception) { - errors.add(Date() to "${manga.title} - ${e.message}") - } - } else { - val serviceName = service?.nameRes()?.let { context.getString(it) } - errors.add(Date() to "${manga.title} - ${context.getString(R.string.not_logged_into_, serviceName)}") - } - } - } - - /** - * Called to update dialog in [BackupConst] - * - * @param progress restore progress - * @param amount total restoreAmount of manga - * @param title title of restored manga - */ - internal fun showRestoreProgress( - progress: Int, - amount: Int, - title: String, - ) { - notifier.showRestoreProgress(title, progress, amount) - } - - internal fun writeErrorLog(): File { - try { - if (errors.isNotEmpty()) { - val file = context.createFileInCacheDir("tachiyomi_restore.txt") - val sdf = SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.getDefault()) - - file.bufferedWriter().use { out -> - errors.forEach { (date, message) -> - out.write("[${sdf.format(date)}] $message\n") - } - } - return file - } - } catch (e: Exception) { - // Empty - } - return 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 80e0ac935e..9d9780325d 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 @@ -36,14 +36,19 @@ import eu.kanade.tachiyomi.data.backup.models.IntPreferenceValue import eu.kanade.tachiyomi.data.backup.models.LongPreferenceValue import eu.kanade.tachiyomi.data.backup.models.StringPreferenceValue import eu.kanade.tachiyomi.data.backup.models.StringSetPreferenceValue +import eu.kanade.tachiyomi.data.database.DatabaseHelper 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.library.CustomMangaManager import eu.kanade.tachiyomi.data.preference.Preference import eu.kanade.tachiyomi.data.preference.PreferenceStore +import eu.kanade.tachiyomi.data.preference.PreferencesHelper +import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.source.ConfigurableSource +import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.preferenceKey import eu.kanade.tachiyomi.source.sourcePreferences @@ -57,10 +62,15 @@ import uy.kohesive.injekt.api.get import java.io.FileOutputStream import kotlin.math.max -class BackupManager(context: Context) : AbstractBackupManager(context) { +class BackupManager(val context: Context) { private val preferenceStore: PreferenceStore = Injekt.get() val parser = ProtoBuf + private val db: DatabaseHelper = Injekt.get() + private val sourceManager: SourceManager = Injekt.get() + private val trackManager: TrackManager = Injekt.get() + private val preferences: PreferencesHelper = Injekt.get() + private val customMangaManager: CustomMangaManager = Injekt.get() /** * Create backup Json file from database @@ -68,16 +78,17 @@ class BackupManager(context: Context) : AbstractBackupManager(context) { * @param uri path of Uri * @param isAutoBackup backup called from scheduled backup job */ - override fun createBackup(uri: Uri, flags: Int, isAutoBackup: Boolean): String { + fun createBackup(uri: Uri, flags: Int, isAutoBackup: Boolean): String { // Create root object var backup: Backup? = null db.inTransaction { - val databaseManga = getFavoriteManga() + if (flags and BACKUP_READ_MANGA_MASK == BACKUP_READ_MANGA) { - getReadManga() - } else { - emptyList() - } + val databaseManga = db.getFavoriteMangas().executeAsBlocking() + + if (flags and BACKUP_READ_MANGA_MASK == BACKUP_READ_MANGA) { + db.getReadNotInLibraryMangas().executeAsBlocking() + } else { + emptyList() + } backup = Backup( backupMangas(databaseManga, flags), @@ -98,7 +109,7 @@ class BackupManager(context: Context) : AbstractBackupManager(context) { dir = dir.createDirectory("automatic") // Delete older backups - val numberOfBackups = numberOfBackups() + val numberOfBackups = preferences.numberOfBackups().get() dir.listFiles { _, filename -> Backup.filenameRegex.matches(filename) } .orEmpty() .sortedByDescending { it.name } @@ -258,7 +269,7 @@ class BackupManager(context: Context) : AbstractBackupManager(context) { fun restoreExistingManga(manga: Manga, dbManga: Manga) { manga.id = dbManga.id manga.copyFrom(dbManga) - insertManga(manga) + db.insertManga(manga).executeAsBlocking() } /** @@ -270,7 +281,7 @@ class BackupManager(context: Context) : AbstractBackupManager(context) { fun restoreNewManga(manga: Manga): Manga { return manga.also { it.initialized = it.description != null - it.id = insertManga(it) + it.id = db.insertManga(it).executeAsBlocking().insertedId() } } @@ -432,7 +443,7 @@ class BackupManager(context: Context) : AbstractBackupManager(context) { } val newChapters = chapters.groupBy { it.id != null } - newChapters[true]?.let { updateKnownChapters(it) } - newChapters[false]?.let { insertChapters(it) } + newChapters[true]?.let { db.updateKnownChaptersBackup(it).executeAsBlocking() } + newChapters[false]?.let { db.insertChapters(it).executeAsBlocking() } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupRestorer.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupRestorer.kt index d1c6a4fd45..5605718b91 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupRestorer.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupRestorer.kt @@ -17,6 +17,7 @@ import eu.kanade.tachiyomi.data.backup.models.IntPreferenceValue import eu.kanade.tachiyomi.data.backup.models.LongPreferenceValue import eu.kanade.tachiyomi.data.backup.models.StringPreferenceValue import eu.kanade.tachiyomi.data.backup.models.StringSetPreferenceValue +import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Track @@ -25,6 +26,7 @@ import eu.kanade.tachiyomi.data.library.LibraryUpdateJob import eu.kanade.tachiyomi.data.preference.AndroidPreferenceStore import eu.kanade.tachiyomi.data.preference.PreferenceStore import eu.kanade.tachiyomi.source.sourcePreferences +import eu.kanade.tachiyomi.util.system.createFileInCacheDir import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.isActive import okio.buffer @@ -32,14 +34,49 @@ import okio.gzip import okio.source import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get +import uy.kohesive.injekt.injectLazy +import java.io.File +import java.text.SimpleDateFormat import java.util.Date +import java.util.Locale -class BackupRestorer(context: Context, notifier: BackupNotifier) : AbstractBackupRestore(context, notifier) { +class BackupRestorer(val context: Context, val notifier: BackupNotifier) { + private lateinit var backupManager: BackupManager + private val db: DatabaseHelper by injectLazy() + private val customMangaManager: CustomMangaManager by injectLazy() private val preferenceStore: PreferenceStore = Injekt.get() + private var restoreAmount = 0 + private var restoreProgress = 0 + + /** + * Mapping of source ID to source name from backup data + */ + private var sourceMapping: Map = emptyMap() + + private val errors = mutableListOf>() + + suspend fun restoreBackup(uri: Uri): Boolean { + val startTime = System.currentTimeMillis() + restoreProgress = 0 + errors.clear() + + if (!performRestore(uri)) { + return false + } + + val endTime = System.currentTimeMillis() + val time = endTime - startTime + + val logFile = writeErrorLog() + + notifier.showRestoreComplete(time, errors.size, logFile.parent, logFile.name) + return true + } + @SuppressLint("Recycle") - override suspend fun performRestore(uri: Uri): Boolean { + suspend fun performRestore(uri: Uri): Boolean { backupManager = BackupManager(context) val stream = context.contentResolver.openInputStream(uri) @@ -93,7 +130,7 @@ class BackupRestorer(context: Context, notifier: BackupNotifier) : AbstractBacku val customManga = backupManga.getCustomMangaInfo() try { - val dbManga = backupManager.getMangaFromDatabase(manga) + val dbManga = db.getManga(manga.url, manga.source).executeAsBlocking() if (dbManga == null) { // Manga not in database restoreExistingManga(manga, chapters, categories, history, tracks, backupCategories, customManga) @@ -216,4 +253,38 @@ class BackupRestorer(context: Context, notifier: BackupNotifier) : AbstractBacku } } } + + /** + * Called to update dialog in [BackupConst] + * + * @param progress restore progress + * @param amount total restoreAmount of manga + * @param title title of restored manga + */ + private fun showRestoreProgress( + progress: Int, + amount: Int, + title: String, + ) { + notifier.showRestoreProgress(title, progress, amount) + } + + internal fun writeErrorLog(): File { + try { + if (errors.isNotEmpty()) { + val file = context.createFileInCacheDir("tachiyomi_restore.txt") + val sdf = SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.getDefault()) + + file.bufferedWriter().use { out -> + errors.forEach { (date, message) -> + out.write("[${sdf.format(date)}] $message\n") + } + } + return file + } + } catch (e: Exception) { + // Empty + } + return File("") + } }