From 9f0052eceb8bdbe9ca1ea18a2aac18a4718387bf Mon Sep 17 00:00:00 2001 From: arkon Date: Sat, 6 Aug 2022 15:27:25 -0400 Subject: [PATCH] More backup/restore code cleanup --- .../data/backup/AbstractBackupManager.kt | 205 ------------------ .../data/backup/AbstractBackupRestore.kt | 153 ------------- .../tachiyomi/data/backup/BackupManager.kt | 185 ++++++++++++++-- .../data/backup/BackupRestoreService.kt | 2 +- .../tachiyomi/data/backup/BackupRestorer.kt | 76 ++++++- .../source/browse/BrowseSourcePresenter.kt | 4 +- .../kanade/tachiyomi/ui/more/MorePresenter.kt | 6 +- .../kanade/tachiyomi/util/MangaExtensions.kt | 8 +- .../util/chapter/ChapterSettingsHelper.kt | 40 ++-- .../util/system/ContextExtensions.kt | 6 +- 10 files changed, 270 insertions(+), 415 deletions(-) delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/backup/AbstractBackupManager.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/backup/AbstractBackupRestore.kt 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 84c91951e7..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/AbstractBackupManager.kt +++ /dev/null @@ -1,205 +0,0 @@ -package eu.kanade.tachiyomi.data.backup - -import android.content.Context -import android.net.Uri -import eu.kanade.data.DatabaseHandler -import eu.kanade.data.toLong -import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource -import eu.kanade.domain.chapter.model.toDbChapter -import eu.kanade.domain.manga.interactor.GetFavorites -import eu.kanade.tachiyomi.data.database.models.Chapter -import eu.kanade.tachiyomi.data.database.models.Manga -import eu.kanade.tachiyomi.data.database.models.toDomainManga -import eu.kanade.tachiyomi.data.database.models.toMangaInfo -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.source.model.toSChapter -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get -import data.Mangas as DbManga -import eu.kanade.domain.manga.model.Manga as DomainManga - -abstract class AbstractBackupManager(protected val context: Context) { - - protected val handler: DatabaseHandler = Injekt.get() - - internal val sourceManager: SourceManager = Injekt.get() - internal val trackManager: TrackManager = Injekt.get() - protected val preferences: PreferencesHelper = Injekt.get() - private val getFavorites: GetFavorites = Injekt.get() - private val syncChaptersWithSource: SyncChaptersWithSource = Injekt.get() - - abstract suspend fun createBackup(uri: Uri, flags: Int, isAutoBackup: Boolean): String - - /** - * Returns manga - * - * @return [Manga], null if not found - */ - internal suspend fun getMangaFromDatabase(url: String, source: Long): DbManga? { - return handler.awaitOneOrNull { mangasQueries.getMangaByUrlAndSource(url, source) } - } - - /** - * 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.toMangaInfo()) - .map { it.toSChapter() } - val syncedChapters = syncChaptersWithSource.await(fetchedChapters, manga.toDomainManga()!!, source) - if (syncedChapters.first.isNotEmpty()) { - chapters.forEach { it.manga_id = manga.id } - updateChapters(chapters) - } - return syncedChapters.first.map { it.toDbChapter() } to syncedChapters.second.map { it.toDbChapter() } - } - - /** - * Returns list containing manga from library - * - * @return [Manga] from library - */ - protected suspend fun getFavoriteManga(): List { - return getFavorites.await() - } - - /** - * Inserts manga and returns id - * - * @return id of [Manga], null if not found - */ - internal suspend fun insertManga(manga: Manga): Long { - return handler.awaitOne(true) { - mangasQueries.insert( - source = manga.source, - url = manga.url, - artist = manga.artist, - author = manga.author, - description = manga.description, - genre = manga.getGenres(), - title = manga.title, - status = manga.status.toLong(), - thumbnailUrl = manga.thumbnail_url, - favorite = manga.favorite, - lastUpdate = manga.last_update, - nextUpdate = 0L, - initialized = manga.initialized, - viewerFlags = manga.viewer_flags.toLong(), - chapterFlags = manga.chapter_flags.toLong(), - coverLastModified = manga.cover_last_modified, - dateAdded = manga.date_added, - ) - mangasQueries.selectLastInsertedRowId() - } - } - - internal suspend fun updateManga(manga: Manga): Long { - handler.await(true) { - mangasQueries.update( - source = manga.source, - url = manga.url, - artist = manga.artist, - author = manga.author, - description = manga.description, - genre = manga.genre, - title = manga.title, - status = manga.status.toLong(), - thumbnailUrl = manga.thumbnail_url, - favorite = manga.favorite.toLong(), - lastUpdate = manga.last_update, - initialized = manga.initialized.toLong(), - viewer = manga.viewer_flags.toLong(), - chapterFlags = manga.chapter_flags.toLong(), - coverLastModified = manga.cover_last_modified, - dateAdded = manga.date_added, - mangaId = manga.id!!, - ) - } - return manga.id!! - } - - /** - * Inserts list of chapters - */ - protected suspend fun insertChapters(chapters: List) { - handler.await(true) { - chapters.forEach { chapter -> - chaptersQueries.insert( - chapter.manga_id!!, - chapter.url, - chapter.name, - chapter.scanlator, - chapter.read, - chapter.bookmark, - chapter.last_page_read.toLong(), - chapter.chapter_number, - chapter.source_order.toLong(), - chapter.date_fetch, - chapter.date_upload, - ) - } - } - } - - /** - * Updates a list of chapters - */ - protected suspend fun updateChapters(chapters: List) { - handler.await(true) { - chapters.forEach { chapter -> - chaptersQueries.update( - chapter.manga_id!!, - chapter.url, - chapter.name, - chapter.scanlator, - chapter.read.toLong(), - chapter.bookmark.toLong(), - chapter.last_page_read.toLong(), - chapter.chapter_number.toDouble(), - chapter.source_order.toLong(), - chapter.date_fetch, - chapter.date_upload, - chapter.id!!, - ) - } - } - } - - /** - * Updates a list of chapters with known database ids - */ - protected suspend fun updateKnownChapters(chapters: List) { - handler.await(true) { - chapters.forEach { chapter -> - chaptersQueries.update( - mangaId = null, - url = null, - name = null, - scanlator = null, - read = chapter.read.toLong(), - bookmark = chapter.bookmark.toLong(), - lastPageRead = chapter.last_page_read.toLong(), - chapterNumber = null, - sourceOrder = null, - dateFetch = null, - dateUpload = null, - chapterId = chapter.id!!, - ) - } - } - } - - /** - * 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 bc60ffda56..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/AbstractBackupRestore.kt +++ /dev/null @@ -1,153 +0,0 @@ -package eu.kanade.tachiyomi.data.backup - -import android.content.Context -import android.net.Uri -import eu.kanade.data.DatabaseHandler -import eu.kanade.data.chapter.NoChaptersException -import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.data.database.models.Chapter -import eu.kanade.tachiyomi.data.database.models.Manga -import eu.kanade.tachiyomi.data.database.models.Track -import eu.kanade.tachiyomi.data.track.TrackManager -import eu.kanade.tachiyomi.source.Source -import eu.kanade.tachiyomi.util.system.createFileInCacheDir -import kotlinx.coroutines.Job -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 handler: DatabaseHandler by injectLazy() - protected val trackManager: TrackManager by injectLazy() - - var job: Job? = null - - 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.toLong()) - if (service != null && service.isLogged) { - try { - val updatedTrack = service.refresh(track) - handler.await { - manga_syncQueries.insert( - updatedTrack.manga_id, - updatedTrack.sync_id.toLong(), - updatedTrack.media_id, - updatedTrack.library_id, - updatedTrack.title, - updatedTrack.last_chapter_read.toDouble(), - updatedTrack.total_chapters.toLong(), - updatedTrack.status.toLong(), - updatedTrack.score, - updatedTrack.tracking_url, - updatedTrack.started_reading_date, - updatedTrack.finished_reading_date, - ) - } - } catch (e: Exception) { - errors.add(Date() to "${manga.title} - ${e.message}") - } - } else { - val serviceName = service?.nameRes()?.let { context.getString(it) } - errors.add(Date() to "${manga.title} - ${context.getString(R.string.tracker_not_logged_in, 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 71b7426039..76d7568f29 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 @@ -5,9 +5,12 @@ import android.net.Uri import com.hippo.unifile.UniFile import data.Manga_sync import data.Mangas -import eu.kanade.data.category.categoryMapper +import eu.kanade.data.DatabaseHandler +import eu.kanade.data.toLong +import eu.kanade.domain.category.interactor.GetCategories import eu.kanade.domain.category.model.Category import eu.kanade.domain.history.model.HistoryUpdate +import eu.kanade.domain.manga.interactor.GetFavorites import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_CATEGORY import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_CATEGORY_MASK @@ -29,35 +32,43 @@ import eu.kanade.tachiyomi.data.backup.models.backupTrackMapper 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.preference.PreferencesHelper +import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.util.system.logcat import kotlinx.serialization.protobuf.ProtoBuf import logcat.LogPriority import okio.buffer import okio.gzip import okio.sink +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get import java.io.FileOutputStream import java.util.Date import kotlin.math.max import eu.kanade.domain.manga.model.Manga as DomainManga -class BackupManager(context: Context) : AbstractBackupManager(context) { +class BackupManager( + private val context: Context, +) { - val parser = ProtoBuf + private val handler: DatabaseHandler = Injekt.get() + private val sourceManager: SourceManager = Injekt.get() + private val preferences: PreferencesHelper = Injekt.get() + private val getCategories: GetCategories = Injekt.get() + private val getFavorites: GetFavorites = Injekt.get() + + internal val parser = ProtoBuf /** - * Create backup Json file from database + * Create backup file from database * * @param uri path of Uri * @param isAutoBackup backup called from scheduled backup job */ @Suppress("BlockingMethodInNonBlockingContext") - override suspend fun createBackup(uri: Uri, flags: Int, isAutoBackup: Boolean): String { - // Create root object - var backup: Backup? = null - - val databaseManga = getFavoriteManga() - - backup = Backup( + suspend fun createBackup(uri: Uri, flags: Int, isAutoBackup: Boolean): String { + val databaseManga = getFavorites.await() + val backup = Backup( backupMangas(databaseManga, flags), backupCategories(flags), emptyList(), @@ -73,7 +84,7 @@ class BackupManager(context: Context) : AbstractBackupManager(context) { dir = dir.createDirectory("automatic") // Delete older backups - val numberOfBackups = numberOfBackups() + val numberOfBackups = preferences.numberOfBackups().get() val backupRegex = Regex("""tachiyomi_\d+-\d+-\d+_\d+-\d+.proto.gz""") dir.listFiles { _, filename -> backupRegex.matches(filename) } .orEmpty() @@ -93,7 +104,7 @@ class BackupManager(context: Context) : AbstractBackupManager(context) { throw IllegalStateException("Failed to get handle on file") } - val byteArray = parser.encodeToByteArray(BackupSerializer, backup!!) + val byteArray = parser.encodeToByteArray(BackupSerializer, backup) if (byteArray.isEmpty()) { throw IllegalStateException(context.getString(R.string.empty_backup_error)) } @@ -133,7 +144,7 @@ class BackupManager(context: Context) : AbstractBackupManager(context) { private suspend fun backupCategories(options: Int): List { // Check if user wants category information in backup return if (options and BACKUP_CATEGORY_MASK == BACKUP_CATEGORY) { - handler.awaitList { categoriesQueries.getCategories(categoryMapper) } + getCategories.await() .filterNot(Category::isSystemCategory) .map(backupCategoryMapper) } else { @@ -170,7 +181,7 @@ class BackupManager(context: Context) : AbstractBackupManager(context) { // Check if user wants category information in backup if (options and BACKUP_CATEGORY_MASK == BACKUP_CATEGORY) { // Backup categories for this manga - val categoriesForManga = handler.awaitList { categoriesQueries.getCategoriesByMangaId(manga.id) } + val categoriesForManga = getCategories.await(manga.id) if (categoriesForManga.isNotEmpty()) { mangaObject.categories = categoriesForManga.map { it.order } } @@ -201,7 +212,7 @@ class BackupManager(context: Context) : AbstractBackupManager(context) { return mangaObject } - suspend fun restoreExistingManga(manga: Manga, dbManga: Mangas) { + internal suspend fun restoreExistingManga(manga: Manga, dbManga: Mangas) { manga.id = dbManga._id manga.copyFrom(dbManga) updateManga(manga) @@ -213,7 +224,7 @@ class BackupManager(context: Context) : AbstractBackupManager(context) { * @param manga manga that needs updating * @return Updated manga info. */ - suspend fun restoreNewManga(manga: Manga): Manga { + internal suspend fun restoreNewManga(manga: Manga): Manga { return manga.also { it.initialized = it.description != null it.id = insertManga(it) @@ -227,7 +238,7 @@ class BackupManager(context: Context) : AbstractBackupManager(context) { */ internal suspend fun restoreCategories(backupCategories: List) { // Get categories from file and from db - val dbCategories = handler.awaitList { categoriesQueries.getCategories(categoryMapper) } + val dbCategories = getCategories.await() val categories = backupCategories.map { var category = it.getCategory() @@ -267,7 +278,7 @@ class BackupManager(context: Context) : AbstractBackupManager(context) { * @param categories the categories to restore. */ internal suspend fun restoreCategories(manga: Manga, categories: List, backupCategories: List) { - val dbCategories = handler.awaitList { categoriesQueries.getCategories() } + val dbCategories = getCategories.await() val mangaCategoriesToUpdate = mutableListOf>() categories.forEach { backupCategoryOrder -> @@ -353,7 +364,6 @@ class BackupManager(context: Context) : AbstractBackupManager(context) { tracks.map { it.manga_id = manga.id!! } // Get tracks from database - val dbTracks = handler.awaitList { manga_syncQueries.getTracksByMangaId(manga.id!!) } val toUpdate = mutableListOf() val toInsert = mutableListOf() @@ -452,4 +462,139 @@ class BackupManager(context: Context) : AbstractBackupManager(context) { newChapters[true]?.let { updateKnownChapters(it) } newChapters[false]?.let { insertChapters(it) } } + + /** + * Returns manga + * + * @return [Manga], null if not found + */ + internal suspend fun getMangaFromDatabase(url: String, source: Long): Mangas? { + return handler.awaitOneOrNull { mangasQueries.getMangaByUrlAndSource(url, source) } + } + + /** + * Inserts manga and returns id + * + * @return id of [Manga], null if not found + */ + private suspend fun insertManga(manga: Manga): Long { + return handler.awaitOne(true) { + mangasQueries.insert( + source = manga.source, + url = manga.url, + artist = manga.artist, + author = manga.author, + description = manga.description, + genre = manga.getGenres(), + title = manga.title, + status = manga.status.toLong(), + thumbnailUrl = manga.thumbnail_url, + favorite = manga.favorite, + lastUpdate = manga.last_update, + nextUpdate = 0L, + initialized = manga.initialized, + viewerFlags = manga.viewer_flags.toLong(), + chapterFlags = manga.chapter_flags.toLong(), + coverLastModified = manga.cover_last_modified, + dateAdded = manga.date_added, + ) + mangasQueries.selectLastInsertedRowId() + } + } + + private suspend fun updateManga(manga: Manga): Long { + handler.await(true) { + mangasQueries.update( + source = manga.source, + url = manga.url, + artist = manga.artist, + author = manga.author, + description = manga.description, + genre = manga.genre, + title = manga.title, + status = manga.status.toLong(), + thumbnailUrl = manga.thumbnail_url, + favorite = manga.favorite.toLong(), + lastUpdate = manga.last_update, + initialized = manga.initialized.toLong(), + viewer = manga.viewer_flags.toLong(), + chapterFlags = manga.chapter_flags.toLong(), + coverLastModified = manga.cover_last_modified, + dateAdded = manga.date_added, + mangaId = manga.id!!, + ) + } + return manga.id!! + } + + /** + * Inserts list of chapters + */ + private suspend fun insertChapters(chapters: List) { + handler.await(true) { + chapters.forEach { chapter -> + chaptersQueries.insert( + chapter.manga_id!!, + chapter.url, + chapter.name, + chapter.scanlator, + chapter.read, + chapter.bookmark, + chapter.last_page_read.toLong(), + chapter.chapter_number, + chapter.source_order.toLong(), + chapter.date_fetch, + chapter.date_upload, + ) + } + } + } + + /** + * Updates a list of chapters + */ + private suspend fun updateChapters(chapters: List) { + handler.await(true) { + chapters.forEach { chapter -> + chaptersQueries.update( + chapter.manga_id!!, + chapter.url, + chapter.name, + chapter.scanlator, + chapter.read.toLong(), + chapter.bookmark.toLong(), + chapter.last_page_read.toLong(), + chapter.chapter_number.toDouble(), + chapter.source_order.toLong(), + chapter.date_fetch, + chapter.date_upload, + chapter.id!!, + ) + } + } + } + + /** + * Updates a list of chapters with known database ids + */ + private suspend fun updateKnownChapters(chapters: List) { + handler.await(true) { + chapters.forEach { chapter -> + chaptersQueries.update( + mangaId = null, + url = null, + name = null, + scanlator = null, + read = chapter.read.toLong(), + bookmark = chapter.bookmark.toLong(), + lastPageRead = chapter.last_page_read.toLong(), + chapterNumber = null, + sourceOrder = null, + dateFetch = null, + dateUpload = null, + chapterId = chapter.id!!, + ) + } + } + } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupRestoreService.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupRestoreService.kt index d652c4f585..53b2e7f564 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupRestoreService.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupRestoreService.kt @@ -69,7 +69,7 @@ class BackupRestoreService : Service() { private lateinit var wakeLock: PowerManager.WakeLock private lateinit var ioScope: CoroutineScope - private var restorer: AbstractBackupRestore<*>? = null + private var restorer: BackupRestorer? = null private lateinit var notifier: BackupNotifier override fun onCreate() { 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 42e181984e..1ec8572425 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 @@ -11,17 +11,74 @@ import eu.kanade.tachiyomi.data.backup.models.BackupSource 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.util.system.createFileInCacheDir +import kotlinx.coroutines.Job import okio.buffer import okio.gzip import okio.source +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( + private val context: Context, + private val notifier: BackupNotifier, +) { + + var job: Job? = null + + private var backupManager = BackupManager(context) + + 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 + } + + 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("") + } @Suppress("BlockingMethodInNonBlockingContext") - override suspend fun performRestore(uri: Uri): Boolean { - backupManager = BackupManager(context) - + private suspend fun performRestore(uri: Uri): Boolean { val backupString = context.contentResolver.openInputStream(uri)!!.source().gzip().buffer().use { it.readByteArray() } val backup = backupManager.parser.decodeFromByteArray(BackupSerializer, backupString) @@ -125,4 +182,15 @@ class BackupRestorer(context: Context, notifier: BackupNotifier) : AbstractBacku backupManager.restoreHistory(history) backupManager.restoreTracking(manga, tracks) } + + /** + * 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) + } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourcePresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourcePresenter.kt index 9052bb02d3..29f02e80af 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourcePresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourcePresenter.kt @@ -68,7 +68,7 @@ open class BrowseSourcePresenter( private val sourceId: Long, searchQuery: String? = null, private val sourceManager: SourceManager = Injekt.get(), - private val prefs: PreferencesHelper = Injekt.get(), + private val preferences: PreferencesHelper = Injekt.get(), private val coverCache: CoverCache = Injekt.get(), private val getManga: GetManga = Injekt.get(), private val getDuplicateLibraryManga: GetDuplicateLibraryManga = Injekt.get(), @@ -153,7 +153,7 @@ open class BrowseSourcePresenter( pager = createPager(query, filters) val sourceId = source.id - val sourceDisplayMode = prefs.sourceDisplayMode() + val sourceDisplayMode = preferences.sourceDisplayMode() pagerJob?.cancel() pagerJob = presenterScope.launchIO { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/more/MorePresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/more/MorePresenter.kt index 74840fb8ee..e9cc3f4906 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/more/MorePresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/more/MorePresenter.kt @@ -18,11 +18,11 @@ import uy.kohesive.injekt.api.get class MorePresenter( private val downloadManager: DownloadManager = Injekt.get(), - preferencesHelper: PreferencesHelper = Injekt.get(), + preferences: PreferencesHelper = Injekt.get(), ) : BasePresenter() { - val downloadedOnly = preferencesHelper.downloadedOnly().asState() - val incognitoMode = preferencesHelper.incognitoMode().asState() + val downloadedOnly = preferences.downloadedOnly().asState() + val incognitoMode = preferences.incognitoMode().asState() private var _state: MutableStateFlow = MutableStateFlow(DownloadQueueState.Stopped) val downloadQueueState: StateFlow = _state.asStateFlow() diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/MangaExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/MangaExtensions.kt index b2358b7f57..292d9565ea 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/MangaExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/MangaExtensions.kt @@ -50,17 +50,17 @@ fun Manga.removeCovers(coverCache: CoverCache = Injekt.get()): Int { return coverCache.deleteFromCache(this, true) } -fun DomainManga.shouldDownloadNewChapters(dbCategories: List, prefs: PreferencesHelper): Boolean { +fun DomainManga.shouldDownloadNewChapters(dbCategories: List, preferences: PreferencesHelper): Boolean { if (!favorite) return false val categories = dbCategories.ifEmpty { listOf(0L) } // Boolean to determine if user wants to automatically download new chapters. - val downloadNewChapter = prefs.downloadNewChapter().get() + val downloadNewChapter = preferences.downloadNewChapter().get() if (!downloadNewChapter) return false - val includedCategories = prefs.downloadNewChapterCategories().get().map { it.toLong() } - val excludedCategories = prefs.downloadNewChapterCategoriesExclude().get().map { it.toLong() } + val includedCategories = preferences.downloadNewChapterCategories().get().map { it.toLong() } + val excludedCategories = preferences.downloadNewChapterCategoriesExclude().get().map { it.toLong() } // Default: Download from all categories if (includedCategories.isEmpty() && excludedCategories.isEmpty()) return true diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/chapter/ChapterSettingsHelper.kt b/app/src/main/java/eu/kanade/tachiyomi/util/chapter/ChapterSettingsHelper.kt index ec8ec6c04d..c5748b96fb 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/chapter/ChapterSettingsHelper.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/chapter/ChapterSettingsHelper.kt @@ -10,7 +10,7 @@ import uy.kohesive.injekt.injectLazy object ChapterSettingsHelper { - private val prefs: PreferencesHelper by injectLazy() + private val preferences: PreferencesHelper by injectLazy() private val getFavorites: GetFavorites by injectLazy() private val setMangaChapterFlags: SetMangaChapterFlags by injectLazy() @@ -18,7 +18,7 @@ object ChapterSettingsHelper { * Updates the global Chapter Settings in Preferences. */ fun setGlobalSettings(manga: Manga) { - prefs.setChapterSettingsDefault(manga.toDbManga()) + preferences.setChapterSettingsDefault(manga.toDbManga()) } /** @@ -28,12 +28,12 @@ object ChapterSettingsHelper { launchIO { setMangaChapterFlags.awaitSetAllFlags( mangaId = manga.id, - unreadFilter = prefs.filterChapterByRead().toLong(), - downloadedFilter = prefs.filterChapterByDownloaded().toLong(), - bookmarkedFilter = prefs.filterChapterByBookmarked().toLong(), - sortingMode = prefs.sortChapterBySourceOrNumber().toLong(), - sortingDirection = prefs.sortChapterByAscendingOrDescending().toLong(), - displayMode = prefs.displayChapterByNameOrNumber().toLong(), + unreadFilter = preferences.filterChapterByRead().toLong(), + downloadedFilter = preferences.filterChapterByDownloaded().toLong(), + bookmarkedFilter = preferences.filterChapterByBookmarked().toLong(), + sortingMode = preferences.sortChapterBySourceOrNumber().toLong(), + sortingDirection = preferences.sortChapterByAscendingOrDescending().toLong(), + displayMode = preferences.displayChapterByNameOrNumber().toLong(), ) } } @@ -41,12 +41,12 @@ object ChapterSettingsHelper { suspend fun applySettingDefaults(mangaId: Long) { setMangaChapterFlags.awaitSetAllFlags( mangaId = mangaId, - unreadFilter = prefs.filterChapterByRead().toLong(), - downloadedFilter = prefs.filterChapterByDownloaded().toLong(), - bookmarkedFilter = prefs.filterChapterByBookmarked().toLong(), - sortingMode = prefs.sortChapterBySourceOrNumber().toLong(), - sortingDirection = prefs.sortChapterByAscendingOrDescending().toLong(), - displayMode = prefs.displayChapterByNameOrNumber().toLong(), + unreadFilter = preferences.filterChapterByRead().toLong(), + downloadedFilter = preferences.filterChapterByDownloaded().toLong(), + bookmarkedFilter = preferences.filterChapterByBookmarked().toLong(), + sortingMode = preferences.sortChapterBySourceOrNumber().toLong(), + sortingDirection = preferences.sortChapterByAscendingOrDescending().toLong(), + displayMode = preferences.displayChapterByNameOrNumber().toLong(), ) } @@ -59,12 +59,12 @@ object ChapterSettingsHelper { .map { manga -> setMangaChapterFlags.awaitSetAllFlags( mangaId = manga.id, - unreadFilter = prefs.filterChapterByRead().toLong(), - downloadedFilter = prefs.filterChapterByDownloaded().toLong(), - bookmarkedFilter = prefs.filterChapterByBookmarked().toLong(), - sortingMode = prefs.sortChapterBySourceOrNumber().toLong(), - sortingDirection = prefs.sortChapterByAscendingOrDescending().toLong(), - displayMode = prefs.displayChapterByNameOrNumber().toLong(), + unreadFilter = preferences.filterChapterByRead().toLong(), + downloadedFilter = preferences.filterChapterByDownloaded().toLong(), + bookmarkedFilter = preferences.filterChapterByBookmarked().toLong(), + sortingMode = preferences.sortChapterBySourceOrNumber().toLong(), + sortingDirection = preferences.sortChapterByAscendingOrDescending().toLong(), + displayMode = preferences.displayChapterByNameOrNumber().toLong(), ) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/system/ContextExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/system/ContextExtensions.kt index 95c3c330f7..b041210037 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/system/ContextExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/system/ContextExtensions.kt @@ -319,8 +319,8 @@ fun Context.isNightMode(): Boolean { * https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:appcompat/appcompat/src/main/java/androidx/appcompat/app/AppCompatDelegateImpl.java;l=348;drc=e28752c96fc3fb4d3354781469a1af3dbded4898 */ fun Context.createReaderThemeContext(): Context { - val prefs = Injekt.get() - val isDarkBackground = when (prefs.readerTheme().get()) { + val preferences = Injekt.get() + val isDarkBackground = when (preferences.readerTheme().get()) { 1, 2 -> true // Black, Gray 3 -> applicationContext.isNightMode() // Automatic bg uses activity background by default else -> false // White @@ -333,7 +333,7 @@ fun Context.createReaderThemeContext(): Context { val wrappedContext = ContextThemeWrapper(this, R.style.Theme_Tachiyomi) wrappedContext.applyOverrideConfiguration(overrideConf) - ThemingDelegate.getThemeResIds(prefs.appTheme().get(), prefs.themeDarkAmoled().get()) + ThemingDelegate.getThemeResIds(preferences.appTheme().get(), preferences.themeDarkAmoled().get()) .forEach { wrappedContext.theme.applyStyle(it, true) } return wrappedContext }