More backup/restore code cleanup

(cherry picked from commit 9f0052eceb8bdbe9ca1ea18a2aac18a4718387bf)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/data/backup/AbstractBackupManager.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/backup/AbstractBackupRestore.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupManager.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupRestorer.kt
This commit is contained in:
arkon 2022-08-06 15:27:25 -04:00 committed by Jobobby04
parent 16bd7853ad
commit 8b777e1e5a
10 changed files with 297 additions and 409 deletions

View File

@ -1,212 +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.manga.mangaMapper
import eu.kanade.data.toLong
import eu.kanade.domain.manga.interactor.GetFavorites
import eu.kanade.domain.manga.interactor.GetFlatMetadataById
import eu.kanade.domain.manga.interactor.GetMergedManga
import eu.kanade.domain.manga.interactor.InsertFlatMetadata
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.SourceManager
import exh.metadata.metadata.base.FlatMetadata
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()
// SY -->
private val getMergedManga: GetMergedManga = Injekt.get()
protected val customMangaManager: CustomMangaManager = Injekt.get()
private val insertFlatMetadata: InsertFlatMetadata = Injekt.get()
private val getFlatMetadataById: GetFlatMetadataById = Injekt.get()
// SY <--
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) }
}
/**
* Returns list containing manga from library
*
* @return [Manga] from library
*/
protected suspend fun getFavoriteManga(): List<DomainManga> {
return getFavorites.await()
}
// SY -->
protected suspend fun getReadManga(): List<DomainManga> {
return handler.awaitList { mangasQueries.getReadMangaNotInLibrary(mangaMapper) }
}
/**
* Returns list containing merged manga that are possibly not in the library
*
* @return merged [Manga] that are possibly not in the library
*/
protected suspend fun getMergedManga(): List<DomainManga> {
return getMergedManga.await()
}
protected suspend fun getFlatMetadata(mangaId: Long) = getFlatMetadataById.await(mangaId)
protected suspend fun insertFlatMetadata(flatMetadata: FlatMetadata) = insertFlatMetadata.await(flatMetadata)
// SY <--
/**
* 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!!,
filteredScanlators = manga.filtered_scanlators,
)
}
return manga.id!!
}
/**
* Inserts list of chapters
*/
protected suspend fun insertChapters(chapters: List<Chapter>) {
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<Chapter>) {
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<Chapter>) {
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()
}

View File

@ -1,133 +0,0 @@
package eu.kanade.tachiyomi.data.backup
import android.content.Context
import android.net.Uri
import eu.kanade.data.DatabaseHandler
import eu.kanade.tachiyomi.R
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.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<T : AbstractBackupManager>(protected val context: Context, protected val notifier: BackupNotifier) {
protected val handler: DatabaseHandler by injectLazy()
protected val trackManager: TrackManager by injectLazy()
// SY -->
protected val customMangaManager: CustomMangaManager by injectLazy()
// SY <--
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<Long, String> = emptyMap()
protected val errors = mutableListOf<Pair<Date, String>>()
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
}
/**
* 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<Track>) {
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("")
}
}

View File

@ -5,11 +5,17 @@ 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.exh.mergedMangaReferenceMapper
import eu.kanade.data.manga.mangaMapper
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.domain.manga.interactor.GetFlatMetadataById
import eu.kanade.domain.manga.interactor.GetMergedManga
import eu.kanade.domain.manga.interactor.InsertFlatMetadata
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_CATEGORY
import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_CATEGORY_MASK
@ -40,6 +46,9 @@ 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.library.CustomMangaManager
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.online.MetadataSource
import eu.kanade.tachiyomi.util.system.logcat
import exh.source.MERGED_SOURCE_ID
@ -50,33 +59,47 @@ 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()
// SY -->
private val getMergedManga: GetMergedManga = Injekt.get()
private val customMangaManager: CustomMangaManager = Injekt.get()
private val insertFlatMetadata: InsertFlatMetadata = Injekt.get()
private val getFlatMetadataById: GetFlatMetadataById = Injekt.get()
// SY <--
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() /* SY --> */ + if (flags and BACKUP_READ_MANGA_MASK == BACKUP_READ_MANGA) {
getReadManga()
suspend fun createBackup(uri: Uri, flags: Int, isAutoBackup: Boolean): String {
val databaseManga = getFavorites.await() /* SY --> */ + if (flags and BACKUP_READ_MANGA_MASK == BACKUP_READ_MANGA) {
handler.awaitList { mangasQueries.getReadMangaNotInLibrary(mangaMapper) }
} else {
emptyList()
} + getMergedManga() // SY <--
} + getMergedManga.await() // SY <--
backup = Backup(
val backup = Backup(
backupMangas(databaseManga, flags),
backupCategories(flags),
emptyList(),
@ -95,7 +118,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()
@ -115,7 +138,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))
}
@ -155,7 +178,7 @@ class BackupManager(context: Context) : AbstractBackupManager(context) {
private suspend fun backupCategories(options: Int): List<BackupCategory> {
// 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 {
@ -198,7 +221,7 @@ class BackupManager(context: Context) : AbstractBackupManager(context) {
val source = sourceManager.get(manga.source)?.getMainSource<MetadataSource<*, *>>()
if (source != null) {
getFlatMetadata(manga.id)?.let { flatMetadata ->
getFlatMetadataById.await(manga.id)?.let { flatMetadata ->
mangaObject.flatMetadata = BackupFlatMetadata.copyFrom(flatMetadata)
}
}
@ -216,7 +239,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 }
}
@ -247,7 +270,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)
@ -259,7 +282,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)
@ -273,7 +296,7 @@ class BackupManager(context: Context) : AbstractBackupManager(context) {
*/
internal suspend fun restoreCategories(backupCategories: List<BackupCategory>) {
// 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()
@ -313,7 +336,7 @@ class BackupManager(context: Context) : AbstractBackupManager(context) {
* @param categories the categories to restore.
*/
internal suspend fun restoreCategories(manga: Manga, categories: List<Int>, backupCategories: List<BackupCategory>) {
val dbCategories = handler.awaitList { categoriesQueries.getCategories() }
val dbCategories = getCategories.await()
val mangaCategoriesToUpdate = mutableListOf<Pair<Long, Long>>()
categories.forEach { backupCategoryOrder ->
@ -399,7 +422,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<Manga_sync>()
val toInsert = mutableListOf<Track>()
@ -499,6 +521,144 @@ class BackupManager(context: Context) : AbstractBackupManager(context) {
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!!,
// SY -->
filteredScanlators = manga.filtered_scanlators,
// SY <--
)
}
return manga.id!!
}
/**
* Inserts list of chapters
*/
private suspend fun insertChapters(chapters: List<Chapter>) {
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<Chapter>) {
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<Chapter>) {
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!!,
)
}
}
}
// SY -->
internal suspend fun restoreSavedSearches(backupSavedSearches: List<BackupSavedSearch>) {
val currentSavedSearches = handler.awaitList {
@ -560,9 +720,14 @@ class BackupManager(context: Context) : AbstractBackupManager(context) {
}
internal suspend fun restoreFlatMetadata(mangaId: Long, backupFlatMetadata: BackupFlatMetadata) {
if (getFlatMetadata(mangaId) == null) {
insertFlatMetadata(backupFlatMetadata.getFlatMetadata(mangaId))
if (getFlatMetadataById.await(mangaId) == null) {
insertFlatMetadata.await(backupFlatMetadata.getFlatMetadata(mangaId))
}
}
internal fun restoreEditedInfo(mangaJson: CustomMangaManager.MangaJson?) {
mangaJson ?: return
customMangaManager.saveMangaInfo(mangaJson)
}
// SY <--
}

View File

@ -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() {

View File

@ -15,19 +15,76 @@ 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.util.system.createFileInCacheDir
import exh.EXHMigrations
import exh.source.MERGED_SOURCE_ID
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<BackupManager>(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<Long, String> = emptyMap()
private val errors = mutableListOf<Pair<Date, String>>()
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)
@ -179,7 +236,18 @@ class BackupRestorer(context: Context, notifier: BackupNotifier) : AbstractBacku
backupManager.restoreMergedMangaReferencesForManga(manga.id!!, mergedMangaReferences)
flatMetadata?.let { backupManager.restoreFlatMetadata(manga.id!!, it) }
customManga?.id = manga.id!!
customManga?.let { customMangaManager.saveMangaInfo(it) }
backupManager.restoreEditedInfo(customManga)
// SY <--
}
/**
* 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)
}
}

View File

@ -86,7 +86,7 @@ open class BrowseSourcePresenter(
private val savedSearch: Long? = null,
// SY <--
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(),
@ -208,7 +208,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 {

View File

@ -18,15 +18,15 @@ import uy.kohesive.injekt.api.get
class MorePresenter(
private val downloadManager: DownloadManager = Injekt.get(),
preferencesHelper: PreferencesHelper = Injekt.get(),
preferences: PreferencesHelper = Injekt.get(),
) : BasePresenter<MoreController>() {
val downloadedOnly = preferencesHelper.downloadedOnly().asState()
val incognitoMode = preferencesHelper.incognitoMode().asState()
val downloadedOnly = preferences.downloadedOnly().asState()
val incognitoMode = preferences.incognitoMode().asState()
// SY -->
val showNavUpdates = preferencesHelper.showNavUpdates().asState()
val showNavHistory = preferencesHelper.showNavHistory().asState()
val showNavUpdates = preferences.showNavUpdates().asState()
val showNavHistory = preferences.showNavHistory().asState()
// SY <--
private var _state: MutableStateFlow<DownloadQueueState> = MutableStateFlow(DownloadQueueState.Stopped)

View File

@ -50,17 +50,17 @@ fun Manga.removeCovers(coverCache: CoverCache = Injekt.get()): Int {
return coverCache.deleteFromCache(this, true)
}
fun DomainManga.shouldDownloadNewChapters(dbCategories: List<Long>, prefs: PreferencesHelper): Boolean {
fun DomainManga.shouldDownloadNewChapters(dbCategories: List<Long>, 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

View File

@ -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(),
)
}
}

View File

@ -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<PreferencesHelper>()
val isDarkBackground = when (prefs.readerTheme().get()) {
val preferences = Injekt.get<PreferencesHelper>()
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
}