Remove abstract backup classes

This commit is contained in:
Jays2Kings 2023-10-09 14:28:57 -07:00
parent 884f46ba19
commit 1559c317d7
4 changed files with 97 additions and 251 deletions

View file

@ -1,99 +0,0 @@
package eu.kanade.tachiyomi.data.backup
import android.content.Context
import android.net.Uri
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.library.CustomMangaManager
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
abstract class AbstractBackupManager(protected val context: Context) {
internal val db: DatabaseHelper = Injekt.get()
internal val sourceManager: SourceManager = Injekt.get()
internal val trackManager: TrackManager = Injekt.get()
protected val preferences: PreferencesHelper = Injekt.get()
protected val customMangaManager: CustomMangaManager = Injekt.get()
abstract fun createBackup(uri: Uri, flags: Int, isAutoBackup: Boolean): String?
/**
* Returns manga
*
* @return [Manga], null if not found
*/
internal fun getMangaFromDatabase(manga: Manga): Manga? =
db.getManga(manga.url, manga.source).executeAsBlocking()
/**
* Fetches chapter information.
*
* @param source source of manga
* @param manga manga that needs updating
* @param chapters list of chapters in the backup
* @return Updated manga chapters.
*/
internal suspend fun restoreChapters(source: Source, manga: Manga, chapters: List<Chapter>): Pair<List<Chapter>, List<Chapter>> {
val fetchedChapters = source.getChapterList(manga)
val syncedChapters = syncChaptersWithSource(db, fetchedChapters, manga, source)
if (syncedChapters.first.isNotEmpty()) {
chapters.forEach { it.manga_id = manga.id }
updateChapters(chapters)
}
return syncedChapters
}
/**
* Returns list containing manga from library
*
* @return [Manga] from library
*/
protected fun getFavoriteManga(): List<Manga> =
db.getFavoriteMangas().executeAsBlocking()
protected fun getReadManga(): List<Manga> =
db.getReadNotInLibraryMangas().executeAsBlocking()
/**
* Inserts manga and returns id
*
* @return id of [Manga], null if not found
*/
internal fun insertManga(manga: Manga): Long? =
db.insertManga(manga).executeAsBlocking().insertedId()
/**
* Inserts list of chapters
*/
protected fun insertChapters(chapters: List<Chapter>) {
db.insertChapters(chapters).executeAsBlocking()
}
/**
* Updates a list of chapters
*/
protected fun updateChapters(chapters: List<Chapter>) {
db.updateChaptersBackup(chapters).executeAsBlocking()
}
/**
* Updates a list of chapters with known database ids
*/
protected fun updateKnownChapters(chapters: List<Chapter>) {
db.updateKnownChaptersBackup(chapters).executeAsBlocking()
}
/**
* Return number of backups.
*
* @return number of backups selected by user
*/
protected fun numberOfBackups(): Int = preferences.numberOfBackups().get()
}

View file

@ -1,137 +0,0 @@
package eu.kanade.tachiyomi.data.backup
import android.content.Context
import android.net.Uri
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.library.CustomMangaManager
import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.util.chapter.NoChaptersException
import eu.kanade.tachiyomi.util.system.createFileInCacheDir
import uy.kohesive.injekt.injectLazy
import java.io.File
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
abstract class AbstractBackupRestore<T : AbstractBackupManager>(protected val context: Context, protected val notifier: BackupNotifier) {
protected val db: DatabaseHelper by injectLazy()
protected val trackManager: TrackManager by injectLazy()
protected val customMangaManager: CustomMangaManager by injectLazy()
protected lateinit var backupManager: T
protected var restoreAmount = 0
protected var restoreProgress = 0
/**
* Mapping of source ID to source name from backup data
*/
protected var sourceMapping: Map<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)
if (service != null && service.isLogged) {
try {
val updatedTrack = service.refresh(track)
db.insertTrack(updatedTrack).executeAsBlocking()
} catch (e: Exception) {
errors.add(Date() to "${manga.title} - ${e.message}")
}
} else {
val serviceName = service?.nameRes()?.let { context.getString(it) }
errors.add(Date() to "${manga.title} - ${context.getString(R.string.not_logged_into_, serviceName)}")
}
}
}
/**
* Called to update dialog in [BackupConst]
*
* @param progress restore progress
* @param amount total restoreAmount of manga
* @param title title of restored manga
*/
internal fun showRestoreProgress(
progress: Int,
amount: Int,
title: String,
) {
notifier.showRestoreProgress(title, progress, amount)
}
internal fun writeErrorLog(): File {
try {
if (errors.isNotEmpty()) {
val file = context.createFileInCacheDir("tachiyomi_restore.txt")
val sdf = SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.getDefault())
file.bufferedWriter().use { out ->
errors.forEach { (date, message) ->
out.write("[${sdf.format(date)}] $message\n")
}
}
return file
}
} catch (e: Exception) {
// Empty
}
return File("")
}
}

View file

@ -36,14 +36,19 @@ import eu.kanade.tachiyomi.data.backup.models.IntPreferenceValue
import eu.kanade.tachiyomi.data.backup.models.LongPreferenceValue
import eu.kanade.tachiyomi.data.backup.models.StringPreferenceValue
import eu.kanade.tachiyomi.data.backup.models.StringSetPreferenceValue
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.History
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.MangaCategory
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.library.CustomMangaManager
import eu.kanade.tachiyomi.data.preference.Preference
import eu.kanade.tachiyomi.data.preference.PreferenceStore
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.source.ConfigurableSource
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.preferenceKey
import eu.kanade.tachiyomi.source.sourcePreferences
@ -57,10 +62,15 @@ import uy.kohesive.injekt.api.get
import java.io.FileOutputStream
import kotlin.math.max
class BackupManager(context: Context) : AbstractBackupManager(context) {
class BackupManager(val context: Context) {
private val preferenceStore: PreferenceStore = Injekt.get()
val parser = ProtoBuf
private val db: DatabaseHelper = Injekt.get()
private val sourceManager: SourceManager = Injekt.get()
private val trackManager: TrackManager = Injekt.get()
private val preferences: PreferencesHelper = Injekt.get()
private val customMangaManager: CustomMangaManager = Injekt.get()
/**
* Create backup Json file from database
@ -68,16 +78,17 @@ class BackupManager(context: Context) : AbstractBackupManager(context) {
* @param uri path of Uri
* @param isAutoBackup backup called from scheduled backup job
*/
override fun createBackup(uri: Uri, flags: Int, isAutoBackup: Boolean): String {
fun createBackup(uri: Uri, flags: Int, isAutoBackup: Boolean): String {
// Create root object
var backup: Backup? = null
db.inTransaction {
val databaseManga = getFavoriteManga() + if (flags and BACKUP_READ_MANGA_MASK == BACKUP_READ_MANGA) {
getReadManga()
} else {
emptyList()
}
val databaseManga = db.getFavoriteMangas().executeAsBlocking() +
if (flags and BACKUP_READ_MANGA_MASK == BACKUP_READ_MANGA) {
db.getReadNotInLibraryMangas().executeAsBlocking()
} else {
emptyList()
}
backup = Backup(
backupMangas(databaseManga, flags),
@ -98,7 +109,7 @@ class BackupManager(context: Context) : AbstractBackupManager(context) {
dir = dir.createDirectory("automatic")
// Delete older backups
val numberOfBackups = numberOfBackups()
val numberOfBackups = preferences.numberOfBackups().get()
dir.listFiles { _, filename -> Backup.filenameRegex.matches(filename) }
.orEmpty()
.sortedByDescending { it.name }
@ -258,7 +269,7 @@ class BackupManager(context: Context) : AbstractBackupManager(context) {
fun restoreExistingManga(manga: Manga, dbManga: Manga) {
manga.id = dbManga.id
manga.copyFrom(dbManga)
insertManga(manga)
db.insertManga(manga).executeAsBlocking()
}
/**
@ -270,7 +281,7 @@ class BackupManager(context: Context) : AbstractBackupManager(context) {
fun restoreNewManga(manga: Manga): Manga {
return manga.also {
it.initialized = it.description != null
it.id = insertManga(it)
it.id = db.insertManga(it).executeAsBlocking().insertedId()
}
}
@ -432,7 +443,7 @@ class BackupManager(context: Context) : AbstractBackupManager(context) {
}
val newChapters = chapters.groupBy { it.id != null }
newChapters[true]?.let { updateKnownChapters(it) }
newChapters[false]?.let { insertChapters(it) }
newChapters[true]?.let { db.updateKnownChaptersBackup(it).executeAsBlocking() }
newChapters[false]?.let { db.insertChapters(it).executeAsBlocking() }
}
}

View file

@ -17,6 +17,7 @@ import eu.kanade.tachiyomi.data.backup.models.IntPreferenceValue
import eu.kanade.tachiyomi.data.backup.models.LongPreferenceValue
import eu.kanade.tachiyomi.data.backup.models.StringPreferenceValue
import eu.kanade.tachiyomi.data.backup.models.StringSetPreferenceValue
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.Track
@ -25,6 +26,7 @@ import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
import eu.kanade.tachiyomi.data.preference.AndroidPreferenceStore
import eu.kanade.tachiyomi.data.preference.PreferenceStore
import eu.kanade.tachiyomi.source.sourcePreferences
import eu.kanade.tachiyomi.util.system.createFileInCacheDir
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.isActive
import okio.buffer
@ -32,14 +34,49 @@ import okio.gzip
import okio.source
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import java.io.File
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
class BackupRestorer(context: Context, notifier: BackupNotifier) : AbstractBackupRestore<BackupManager>(context, notifier) {
class BackupRestorer(val context: Context, val notifier: BackupNotifier) {
private lateinit var backupManager: BackupManager
private val db: DatabaseHelper by injectLazy()
private val customMangaManager: CustomMangaManager by injectLazy()
private val preferenceStore: PreferenceStore = Injekt.get()
private var restoreAmount = 0
private var restoreProgress = 0
/**
* Mapping of source ID to source name from backup data
*/
private var sourceMapping: Map<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
}
@SuppressLint("Recycle")
override suspend fun performRestore(uri: Uri): Boolean {
suspend fun performRestore(uri: Uri): Boolean {
backupManager = BackupManager(context)
val stream = context.contentResolver.openInputStream(uri)
@ -93,7 +130,7 @@ class BackupRestorer(context: Context, notifier: BackupNotifier) : AbstractBacku
val customManga = backupManga.getCustomMangaInfo()
try {
val dbManga = backupManager.getMangaFromDatabase(manga)
val dbManga = db.getManga(manga.url, manga.source).executeAsBlocking()
if (dbManga == null) {
// Manga not in database
restoreExistingManga(manga, chapters, categories, history, tracks, backupCategories, customManga)
@ -216,4 +253,38 @@ class BackupRestorer(context: Context, notifier: BackupNotifier) : AbstractBacku
}
}
}
/**
* Called to update dialog in [BackupConst]
*
* @param progress restore progress
* @param amount total restoreAmount of manga
* @param title title of restored manga
*/
private fun showRestoreProgress(
progress: Int,
amount: Int,
title: String,
) {
notifier.showRestoreProgress(title, progress, amount)
}
internal fun writeErrorLog(): File {
try {
if (errors.isNotEmpty()) {
val file = context.createFileInCacheDir("tachiyomi_restore.txt")
val sdf = SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.getDefault())
file.bufferedWriter().use { out ->
errors.forEach { (date, message) ->
out.write("[${sdf.format(date)}] $message\n")
}
}
return file
}
} catch (e: Exception) {
// Empty
}
return File("")
}
}