mirror of
https://github.com/tachiyomiorg/tachiyomi.git
synced 2024-12-21 19:21:51 +01:00
More backup/restore code cleanup
This commit is contained in:
parent
19eb4aaac9
commit
9f0052eceb
@ -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<Chapter>): Pair<List<Chapter>, List<Chapter>> {
|
||||
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<DomainManga> {
|
||||
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<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()
|
||||
}
|
@ -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<T : AbstractBackupManager>(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<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
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<Chapter>): Pair<List<Chapter>, List<Chapter>> {
|
||||
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<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("")
|
||||
}
|
||||
}
|
@ -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<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 {
|
||||
@ -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<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()
|
||||
@ -267,7 +278,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 ->
|
||||
@ -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<Manga_sync>()
|
||||
val toInsert = mutableListOf<Track>()
|
||||
@ -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<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!!,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -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<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)
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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<MoreController>() {
|
||||
|
||||
val downloadedOnly = preferencesHelper.downloadedOnly().asState()
|
||||
val incognitoMode = preferencesHelper.incognitoMode().asState()
|
||||
val downloadedOnly = preferences.downloadedOnly().asState()
|
||||
val incognitoMode = preferences.incognitoMode().asState()
|
||||
|
||||
private var _state: MutableStateFlow<DownloadQueueState> = MutableStateFlow(DownloadQueueState.Stopped)
|
||||
val downloadQueueState: StateFlow<DownloadQueueState> = _state.asStateFlow()
|
||||
|
@ -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
|
||||
|
@ -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(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user