mirror of
https://github.com/aniyomiorg/aniyomi.git
synced 2024-11-29 01:29:02 +03:00
parent
5be3309d81
commit
45a50a7e91
14 changed files with 709 additions and 37 deletions
|
@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.data.backup
|
|||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import eu.kanade.tachiyomi.animesource.AnimeSource
|
||||
import eu.kanade.tachiyomi.animesource.AnimeSourceManager
|
||||
import eu.kanade.tachiyomi.animesource.model.toSEpisode
|
||||
import eu.kanade.tachiyomi.data.database.AnimeDatabaseHelper
|
||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||
|
@ -26,6 +27,7 @@ abstract class AbstractBackupManager(protected val context: Context) {
|
|||
internal val databaseHelper: DatabaseHelper by injectLazy()
|
||||
internal val animedatabaseHelper: AnimeDatabaseHelper by injectLazy()
|
||||
internal val sourceManager: SourceManager by injectLazy()
|
||||
internal val animesourceManager: AnimeSourceManager by injectLazy()
|
||||
internal val trackManager: TrackManager by injectLazy()
|
||||
protected val preferences: PreferencesHelper by injectLazy()
|
||||
|
||||
|
@ -70,8 +72,8 @@ abstract class AbstractBackupManager(protected val context: Context) {
|
|||
* Fetches chapter information.
|
||||
*
|
||||
* @param source source of manga
|
||||
* @param manga manga that needs updating
|
||||
* @param chapters list of chapters in the backup
|
||||
* @param anime anime that needs updating
|
||||
* @param episodes list of episodes in the backup
|
||||
* @return Updated manga chapters.
|
||||
*/
|
||||
internal suspend fun restoreEpisodes(source: AnimeSource, anime: Anime, episodes: List<Episode>): Pair<List<Episode>, List<Episode>> {
|
||||
|
@ -94,9 +96,9 @@ abstract class AbstractBackupManager(protected val context: Context) {
|
|||
databaseHelper.getFavoriteMangas().executeAsBlocking()
|
||||
|
||||
/**
|
||||
* Returns list containing manga from library
|
||||
* Returns list containing anime from library
|
||||
*
|
||||
* @return [Manga] from library
|
||||
* @return [Anime] from library
|
||||
*/
|
||||
protected fun getFavoriteAnime(): List<Anime> =
|
||||
animedatabaseHelper.getFavoriteAnimes().executeAsBlocking()
|
||||
|
|
|
@ -3,8 +3,13 @@ package eu.kanade.tachiyomi.data.backup
|
|||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.animesource.AnimeSource
|
||||
import eu.kanade.tachiyomi.data.database.AnimeDatabaseHelper
|
||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||
import eu.kanade.tachiyomi.data.database.models.Anime
|
||||
import eu.kanade.tachiyomi.data.database.models.AnimeTrack
|
||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||
import eu.kanade.tachiyomi.data.database.models.Episode
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.data.database.models.Track
|
||||
import eu.kanade.tachiyomi.data.track.TrackManager
|
||||
|
@ -21,6 +26,7 @@ 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 animedb: AnimeDatabaseHelper by injectLazy()
|
||||
protected val trackManager: TrackManager by injectLazy()
|
||||
|
||||
var job: Job? = null
|
||||
|
@ -79,6 +85,28 @@ abstract class AbstractBackupRestore<T : AbstractBackupManager>(protected val co
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches chapter information.
|
||||
*
|
||||
* @param source source of manga
|
||||
* @param anime manga that needs updating
|
||||
* @return Updated manga chapters.
|
||||
*/
|
||||
internal suspend fun updateEpisodes(source: AnimeSource, anime: Anime, episodes: List<Episode>): Pair<List<Episode>, List<Episode>> {
|
||||
return try {
|
||||
backupManager.restoreEpisodes(source, anime, episodes)
|
||||
} 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 "${anime.title} - $errorMessage")
|
||||
Pair(emptyList(), emptyList())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes tracking information.
|
||||
*
|
||||
|
@ -102,6 +130,29 @@ abstract class AbstractBackupRestore<T : AbstractBackupManager>(protected val co
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes tracking information.
|
||||
*
|
||||
* @param anime manga that needs updating.
|
||||
* @param tracks list containing tracks from restore file.
|
||||
*/
|
||||
internal suspend fun updateAnimeTracking(anime: Anime, tracks: List<AnimeTrack>) {
|
||||
tracks.forEach { track ->
|
||||
val service = trackManager.getService(track.sync_id)
|
||||
if (service != null && service.isLogged) {
|
||||
try {
|
||||
val updatedTrack = service.refresh(track)
|
||||
animedb.insertTrack(updatedTrack).executeAsBlocking()
|
||||
} catch (e: Exception) {
|
||||
errors.add(Date() to "${anime.title} - ${e.message}")
|
||||
}
|
||||
} else {
|
||||
val serviceName = service?.nameRes()?.let { context.getString(it) }
|
||||
errors.add(Date() to "${anime.title} - ${context.getString(R.string.tracker_not_logged_in, serviceName)}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to update dialog in [BackupConst]
|
||||
*
|
||||
|
@ -120,7 +171,7 @@ abstract class AbstractBackupRestore<T : AbstractBackupManager>(protected val co
|
|||
internal fun writeErrorLog(): File {
|
||||
try {
|
||||
if (errors.isNotEmpty()) {
|
||||
val file = context.createFileInCacheDir("tachiyomi_restore.txt")
|
||||
val file = context.createFileInCacheDir("aniyomi_restore.txt")
|
||||
val sdf = SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.getDefault())
|
||||
|
||||
file.bufferedWriter().use { out ->
|
||||
|
|
|
@ -2,12 +2,14 @@ package eu.kanade.tachiyomi.data.backup
|
|||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import eu.kanade.tachiyomi.animesource.AnimeSourceManager
|
||||
import eu.kanade.tachiyomi.data.track.TrackManager
|
||||
import eu.kanade.tachiyomi.source.SourceManager
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
|
||||
abstract class AbstractBackupRestoreValidator {
|
||||
protected val sourceManager: SourceManager by injectLazy()
|
||||
protected val animesourceManager: AnimeSourceManager by injectLazy()
|
||||
protected val trackManager: TrackManager by injectLazy()
|
||||
|
||||
abstract fun validate(context: Context, uri: Uri): Results
|
||||
|
|
|
@ -22,13 +22,9 @@ class BackupCreateService : Service() {
|
|||
companion object {
|
||||
// Filter options
|
||||
internal const val BACKUP_CATEGORY = 0x1
|
||||
internal const val BACKUP_ANIMECATEGORY = 0x1
|
||||
internal const val BACKUP_CATEGORY_MASK = 0x1
|
||||
internal const val BACKUP_ANIMECATEGORY_MASK = 0x1
|
||||
internal const val BACKUP_CHAPTER = 0x2
|
||||
internal const val BACKUP_EPISODE = 0x2
|
||||
internal const val BACKUP_CHAPTER_MASK = 0x2
|
||||
internal const val BACKUP_EPISODE_MASK = 0x2
|
||||
internal const val BACKUP_HISTORY = 0x4
|
||||
internal const val BACKUP_HISTORY_MASK = 0x4
|
||||
internal const val BACKUP_TRACK = 0x8
|
||||
|
|
|
@ -12,20 +12,8 @@ import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_HIST
|
|||
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_HISTORY_MASK
|
||||
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_TRACK
|
||||
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_TRACK_MASK
|
||||
import eu.kanade.tachiyomi.data.backup.full.models.Backup
|
||||
import eu.kanade.tachiyomi.data.backup.full.models.BackupCategory
|
||||
import eu.kanade.tachiyomi.data.backup.full.models.BackupChapter
|
||||
import eu.kanade.tachiyomi.data.backup.full.models.BackupFull
|
||||
import eu.kanade.tachiyomi.data.backup.full.models.BackupHistory
|
||||
import eu.kanade.tachiyomi.data.backup.full.models.BackupManga
|
||||
import eu.kanade.tachiyomi.data.backup.full.models.BackupSerializer
|
||||
import eu.kanade.tachiyomi.data.backup.full.models.BackupSource
|
||||
import eu.kanade.tachiyomi.data.backup.full.models.BackupTracking
|
||||
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.backup.full.models.*
|
||||
import eu.kanade.tachiyomi.data.database.models.*
|
||||
import kotlinx.serialization.protobuf.ProtoBuf
|
||||
import okio.buffer
|
||||
import okio.gzip
|
||||
|
@ -49,11 +37,15 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
|
|||
|
||||
databaseHelper.inTransaction {
|
||||
val databaseManga = getFavoriteManga()
|
||||
val databaseAnime = getFavoriteAnime()
|
||||
|
||||
backup = Backup(
|
||||
backupManga(databaseManga, flags),
|
||||
backupCategories(),
|
||||
backupExtensionInfo(databaseManga)
|
||||
backupAnime(databaseAnime, flags),
|
||||
backupCategoriesAnime(),
|
||||
backupExtensionInfo(databaseManga),
|
||||
backupAnimeExtensionInfo(databaseAnime)
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -66,7 +58,7 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
|
|||
|
||||
// Delete older backups
|
||||
val numberOfBackups = numberOfBackups()
|
||||
val backupRegex = Regex("""tachiyomi_\d+-\d+-\d+_\d+-\d+.proto.gz""")
|
||||
val backupRegex = Regex("""aniyomi_\d+-\d+-\d+_\d+-\d+.proto.gz""")
|
||||
dir.listFiles { _, filename -> backupRegex.matches(filename) }
|
||||
.orEmpty()
|
||||
.sortedByDescending { it.name }
|
||||
|
@ -96,6 +88,12 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
|
|||
}
|
||||
}
|
||||
|
||||
private fun backupAnime(animes: List<Anime>, flags: Int): List<BackupAnime> {
|
||||
return animes.map {
|
||||
backupAnimeObject(it, flags)
|
||||
}
|
||||
}
|
||||
|
||||
private fun backupExtensionInfo(mangas: List<Manga>): List<BackupSource> {
|
||||
return mangas
|
||||
.asSequence()
|
||||
|
@ -106,6 +104,16 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
|
|||
.toList()
|
||||
}
|
||||
|
||||
private fun backupAnimeExtensionInfo(animes: List<Anime>): List<BackupAnimeSource> {
|
||||
return animes
|
||||
.asSequence()
|
||||
.map { it.source }
|
||||
.distinct()
|
||||
.map { animesourceManager.getOrStub(it) }
|
||||
.map { BackupAnimeSource.copyFrom(it) }
|
||||
.toList()
|
||||
}
|
||||
|
||||
/**
|
||||
* Backup the categories of library
|
||||
*
|
||||
|
@ -117,6 +125,17 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
|
|||
.map { BackupCategory.copyFrom(it) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Backup the categories of library
|
||||
*
|
||||
* @return list of [BackupCategory] to be backed up
|
||||
*/
|
||||
private fun backupCategoriesAnime(): List<BackupCategory> {
|
||||
return animedatabaseHelper.getCategories()
|
||||
.executeAsBlocking()
|
||||
.map { BackupCategory.copyFrom(it) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a manga to Json
|
||||
*
|
||||
|
@ -171,6 +190,60 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
|
|||
return mangaObject
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a manga to Json
|
||||
*
|
||||
* @param anime manga that gets converted
|
||||
* @param options options for the backup
|
||||
* @return [BackupAnime] containing anime in a serializable form
|
||||
*/
|
||||
private fun backupAnimeObject(anime: Anime, options: Int): BackupAnime {
|
||||
// Entry for this manga
|
||||
val animeObject = BackupAnime.copyFrom(anime)
|
||||
|
||||
// Check if user wants chapter information in backup
|
||||
if (options and BACKUP_CHAPTER_MASK == BACKUP_CHAPTER) {
|
||||
// Backup all the chapters
|
||||
val episodes = animedatabaseHelper.getEpisodes(anime).executeAsBlocking()
|
||||
if (episodes.isNotEmpty()) {
|
||||
animeObject.episodes = episodes.map { BackupEpisode.copyFrom(it) }
|
||||
}
|
||||
}
|
||||
|
||||
// Check if user wants category information in backup
|
||||
if (options and BACKUP_CATEGORY_MASK == BACKUP_CATEGORY) {
|
||||
// Backup categories for this manga
|
||||
val categoriesForAnime = animedatabaseHelper.getCategoriesForAnime(anime).executeAsBlocking()
|
||||
if (categoriesForAnime.isNotEmpty()) {
|
||||
animeObject.categories = categoriesForAnime.mapNotNull { it.order }
|
||||
}
|
||||
}
|
||||
|
||||
// Check if user wants track information in backup
|
||||
if (options and BACKUP_TRACK_MASK == BACKUP_TRACK) {
|
||||
val tracks = animedatabaseHelper.getTracks(anime).executeAsBlocking()
|
||||
if (tracks.isNotEmpty()) {
|
||||
animeObject.tracking = tracks.map { BackupAnimeTracking.copyFrom(it) }
|
||||
}
|
||||
}
|
||||
|
||||
// Check if user wants history information in backup
|
||||
if (options and BACKUP_HISTORY_MASK == BACKUP_HISTORY) {
|
||||
val historyForAnime = animedatabaseHelper.getHistoryByAnimeId(anime.id!!).executeAsBlocking()
|
||||
if (historyForAnime.isNotEmpty()) {
|
||||
val history = historyForAnime.mapNotNull { history ->
|
||||
val url = animedatabaseHelper.getEpisode(history.episode_id).executeAsBlocking()?.url
|
||||
url?.let { BackupAnimeHistory(url, history.last_seen) }
|
||||
}
|
||||
if (history.isNotEmpty()) {
|
||||
animeObject.history = history
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return animeObject
|
||||
}
|
||||
|
||||
fun restoreMangaNoFetch(manga: Manga, dbManga: Manga) {
|
||||
manga.id = dbManga.id
|
||||
manga.copyFrom(dbManga)
|
||||
|
@ -190,6 +263,25 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
|
|||
}
|
||||
}
|
||||
|
||||
fun restoreAnimeNoFetch(anime: Anime, dbAnime: Anime) {
|
||||
anime.id = dbAnime.id
|
||||
anime.copyFrom(dbAnime)
|
||||
insertAnime(anime)
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches anime information
|
||||
*
|
||||
* @param anime anime that needs updating
|
||||
* @return Updated anime info.
|
||||
*/
|
||||
fun restoreAnime(anime: Anime): Anime {
|
||||
return anime.also {
|
||||
it.initialized = it.description != null
|
||||
it.id = insertAnime(it)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the categories from Json
|
||||
*
|
||||
|
@ -223,6 +315,39 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the categories from Json
|
||||
*
|
||||
* @param backupCategories list containing categories
|
||||
*/
|
||||
internal fun restoreCategoriesAnime(backupCategories: List<BackupCategory>) {
|
||||
// Get categories from file and from db
|
||||
val dbCategories = animedatabaseHelper.getCategories().executeAsBlocking()
|
||||
|
||||
// Iterate over them
|
||||
backupCategories.map { it.getCategoryImpl() }.forEach { category ->
|
||||
// Used to know if the category is already in the db
|
||||
var found = false
|
||||
for (dbCategory in dbCategories) {
|
||||
// If the category is already in the db, assign the id to the file's category
|
||||
// and do nothing
|
||||
if (category.name == dbCategory.name) {
|
||||
category.id = dbCategory.id
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
// If the category isn't in the db, remove the id and insert a new category
|
||||
// Store the inserted id in the category
|
||||
if (!found) {
|
||||
// Let the db assign the id
|
||||
category.id = null
|
||||
val result = animedatabaseHelper.insertCategory(category).executeAsBlocking()
|
||||
category.id = result.insertedId()?.toInt()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores the categories a manga is in.
|
||||
*
|
||||
|
@ -251,6 +376,34 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores the categories an anime is in.
|
||||
*
|
||||
* @param anime the anime whose categories have to be restored.
|
||||
* @param categories the categories to restore.
|
||||
*/
|
||||
internal fun restoreCategoriesForAnime(anime: Anime, categories: List<Int>, backupCategories: List<BackupCategory>) {
|
||||
val dbCategories = animedatabaseHelper.getCategories().executeAsBlocking()
|
||||
val animeCategoriesToUpdate = ArrayList<AnimeCategory>(categories.size)
|
||||
categories.forEach { backupCategoryOrder ->
|
||||
backupCategories.firstOrNull {
|
||||
it.order == backupCategoryOrder
|
||||
}?.let { backupCategory ->
|
||||
dbCategories.firstOrNull { dbCategory ->
|
||||
dbCategory.name == backupCategory.name
|
||||
}?.let { dbCategory ->
|
||||
animeCategoriesToUpdate += AnimeCategory.create(anime, dbCategory)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update database
|
||||
if (animeCategoriesToUpdate.isNotEmpty()) {
|
||||
animedatabaseHelper.deleteOldAnimesCategories(listOf(anime)).executeAsBlocking()
|
||||
animedatabaseHelper.insertAnimesCategories(animeCategoriesToUpdate).executeAsBlocking()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore history from Json
|
||||
*
|
||||
|
@ -280,6 +433,35 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
|
|||
databaseHelper.updateHistoryLastRead(historyToBeUpdated).executeAsBlocking()
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore history from Json
|
||||
*
|
||||
* @param history list containing history to be restored
|
||||
*/
|
||||
internal fun restoreHistoryForAnime(history: List<BackupAnimeHistory>) {
|
||||
// List containing history to be updated
|
||||
val historyToBeUpdated = ArrayList<AnimeHistory>(history.size)
|
||||
for ((url, lastSeen) in history) {
|
||||
val dbHistory = animedatabaseHelper.getHistoryByEpisodeUrl(url).executeAsBlocking()
|
||||
// Check if history already in database and update
|
||||
if (dbHistory != null) {
|
||||
dbHistory.apply {
|
||||
last_seen = max(lastSeen, dbHistory.last_seen)
|
||||
}
|
||||
historyToBeUpdated.add(dbHistory)
|
||||
} else {
|
||||
// If not in database create
|
||||
animedatabaseHelper.getEpisode(url).executeAsBlocking()?.let {
|
||||
val historyToAdd = AnimeHistory.create(it).apply {
|
||||
last_seen = lastSeen
|
||||
}
|
||||
historyToBeUpdated.add(historyToAdd)
|
||||
}
|
||||
}
|
||||
}
|
||||
animedatabaseHelper.updateAnimeHistoryLastSeen(historyToBeUpdated).executeAsBlocking()
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores the sync of a manga.
|
||||
*
|
||||
|
@ -323,6 +505,49 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores the sync of a manga.
|
||||
*
|
||||
* @param anime the anime whose sync have to be restored.
|
||||
* @param tracks the track list to restore.
|
||||
*/
|
||||
internal fun restoreTrackForAnime(anime: Anime, tracks: List<AnimeTrack>) {
|
||||
// Fix foreign keys with the current anime id
|
||||
tracks.map { it.anime_id = anime.id!! }
|
||||
|
||||
// Get tracks from database
|
||||
val dbTracks = animedatabaseHelper.getTracks(anime).executeAsBlocking()
|
||||
val trackToUpdate = mutableListOf<AnimeTrack>()
|
||||
|
||||
tracks.forEach { track ->
|
||||
var isInDatabase = false
|
||||
for (dbTrack in dbTracks) {
|
||||
if (track.sync_id == dbTrack.sync_id) {
|
||||
// The sync is already in the db, only update its fields
|
||||
if (track.media_id != dbTrack.media_id) {
|
||||
dbTrack.media_id = track.media_id
|
||||
}
|
||||
if (track.library_id != dbTrack.library_id) {
|
||||
dbTrack.library_id = track.library_id
|
||||
}
|
||||
dbTrack.last_episode_seen = max(dbTrack.last_episode_seen, track.last_episode_seen)
|
||||
isInDatabase = true
|
||||
trackToUpdate.add(dbTrack)
|
||||
break
|
||||
}
|
||||
}
|
||||
if (!isInDatabase) {
|
||||
// Insert new sync. Let the db assign the id
|
||||
track.id = null
|
||||
trackToUpdate.add(track)
|
||||
}
|
||||
}
|
||||
// Update database
|
||||
if (trackToUpdate.isNotEmpty()) {
|
||||
animedatabaseHelper.insertTracks(trackToUpdate).executeAsBlocking()
|
||||
}
|
||||
}
|
||||
|
||||
internal fun restoreChaptersForManga(manga: Manga, chapters: List<Chapter>) {
|
||||
val dbChapters = databaseHelper.getChapters(manga).executeAsBlocking()
|
||||
|
||||
|
@ -349,4 +574,31 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
|
|||
newChapters[true]?.let { updateKnownChapters(it) }
|
||||
newChapters[false]?.let { insertChapters(it) }
|
||||
}
|
||||
|
||||
internal fun restoreEpisodesForAnime(anime: Anime, episodes: List<Episode>) {
|
||||
val dbEpisodes = animedatabaseHelper.getEpisodes(anime).executeAsBlocking()
|
||||
|
||||
episodes.forEach { episode ->
|
||||
val dbEpisode = dbEpisodes.find { it.url == episode.url }
|
||||
if (dbEpisode != null) {
|
||||
episode.id = dbEpisode.id
|
||||
episode.copyFrom(dbEpisode)
|
||||
if (dbEpisode.seen && !episode.seen) {
|
||||
episode.seen = dbEpisode.seen
|
||||
episode.last_second_seen = dbEpisode.last_second_seen
|
||||
} else if (episode.last_second_seen == 0L && dbEpisode.last_second_seen != 0L) {
|
||||
episode.last_second_seen = dbEpisode.last_second_seen
|
||||
}
|
||||
if (!episode.bookmark && dbEpisode.bookmark) {
|
||||
episode.bookmark = dbEpisode.bookmark
|
||||
}
|
||||
}
|
||||
|
||||
episode.anime_id = anime.id
|
||||
}
|
||||
|
||||
val newEpisodes = episodes.groupBy { it.id != null }
|
||||
newEpisodes[true]?.let { updateKnownEpisodes(it) }
|
||||
newEpisodes[false]?.let { insertEpisodes(it) }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,13 +5,8 @@ import android.net.Uri
|
|||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.backup.AbstractBackupRestore
|
||||
import eu.kanade.tachiyomi.data.backup.BackupNotifier
|
||||
import eu.kanade.tachiyomi.data.backup.full.models.BackupCategory
|
||||
import eu.kanade.tachiyomi.data.backup.full.models.BackupHistory
|
||||
import eu.kanade.tachiyomi.data.backup.full.models.BackupManga
|
||||
import eu.kanade.tachiyomi.data.backup.full.models.BackupSerializer
|
||||
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.backup.full.models.*
|
||||
import eu.kanade.tachiyomi.data.database.models.*
|
||||
import okio.buffer
|
||||
import okio.gzip
|
||||
import okio.source
|
||||
|
@ -25,15 +20,21 @@ class FullBackupRestore(context: Context, notifier: BackupNotifier) : AbstractBa
|
|||
val backupString = context.contentResolver.openInputStream(uri)!!.source().gzip().buffer().use { it.readByteArray() }
|
||||
val backup = backupManager.parser.decodeFromByteArray(BackupSerializer, backupString)
|
||||
|
||||
restoreAmount = backup.backupManga.size + 1 // +1 for categories
|
||||
restoreAmount = backup.backupManga.size + backup.backupAnime.size + 2 // +2 for categories
|
||||
|
||||
// Restore categories
|
||||
if (backup.backupCategories.isNotEmpty()) {
|
||||
restoreCategories(backup.backupCategories)
|
||||
}
|
||||
|
||||
// Restore categories
|
||||
if (backup.backupCategoriesAnime.isNotEmpty()) {
|
||||
restoreCategoriesAnime(backup.backupCategoriesAnime)
|
||||
}
|
||||
|
||||
// Store source mapping for error messages
|
||||
sourceMapping = backup.backupSources.map { it.sourceId to it.name }.toMap()
|
||||
sourceMapping = backup.backupSources.map { it.sourceId to it.name }.toMap() +
|
||||
backup.backupAnimeSources.map { it.sourceId to it.name }.toMap()
|
||||
|
||||
// Restore individual manga
|
||||
backup.backupManga.forEach {
|
||||
|
@ -44,6 +45,15 @@ class FullBackupRestore(context: Context, notifier: BackupNotifier) : AbstractBa
|
|||
restoreManga(it, backup.backupCategories)
|
||||
}
|
||||
|
||||
// Restore individual anime
|
||||
backup.backupAnime.forEach {
|
||||
if (job?.isActive != true) {
|
||||
return false
|
||||
}
|
||||
|
||||
restoreAnime(it, backup.backupCategoriesAnime)
|
||||
}
|
||||
|
||||
// TODO: optionally trigger online library + tracker update
|
||||
|
||||
return true
|
||||
|
@ -58,6 +68,15 @@ class FullBackupRestore(context: Context, notifier: BackupNotifier) : AbstractBa
|
|||
showRestoreProgress(restoreProgress, restoreAmount, context.getString(R.string.categories))
|
||||
}
|
||||
|
||||
private fun restoreCategoriesAnime(backupCategories: List<BackupCategory>) {
|
||||
animedb.inTransaction {
|
||||
backupManager.restoreCategoriesAnime(backupCategories)
|
||||
}
|
||||
|
||||
restoreProgress += 1
|
||||
showRestoreProgress(restoreProgress, restoreAmount, context.getString(R.string.categories))
|
||||
}
|
||||
|
||||
private fun restoreManga(backupManga: BackupManga, backupCategories: List<BackupCategory>) {
|
||||
val manga = backupManga.getMangaImpl()
|
||||
val chapters = backupManga.getChaptersImpl()
|
||||
|
@ -76,6 +95,24 @@ class FullBackupRestore(context: Context, notifier: BackupNotifier) : AbstractBa
|
|||
showRestoreProgress(restoreProgress, restoreAmount, manga.title)
|
||||
}
|
||||
|
||||
private fun restoreAnime(backupAnime: BackupAnime, backupCategories: List<BackupCategory>) {
|
||||
val anime = backupAnime.getAnimeImpl()
|
||||
val episodes = backupAnime.getEpisodesImpl()
|
||||
val categories = backupAnime.categories
|
||||
val history = backupAnime.history
|
||||
val tracks = backupAnime.getTrackingImpl()
|
||||
|
||||
try {
|
||||
restoreAnimeData(anime, episodes, categories, history, tracks, backupCategories)
|
||||
} catch (e: Exception) {
|
||||
val sourceName = sourceMapping[anime.source] ?: anime.source.toString()
|
||||
errors.add(Date() to "${anime.title} [$sourceName]: ${e.message}")
|
||||
}
|
||||
|
||||
restoreProgress += 1
|
||||
showRestoreProgress(restoreProgress, restoreAmount, anime.title)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a manga restore observable
|
||||
*
|
||||
|
@ -108,6 +145,38 @@ class FullBackupRestore(context: Context, notifier: BackupNotifier) : AbstractBa
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a manga restore observable
|
||||
*
|
||||
* @param manga manga data from json
|
||||
* @param chapters chapters data from json
|
||||
* @param categories categories data from json
|
||||
* @param history history data from json
|
||||
* @param tracks tracking data from json
|
||||
*/
|
||||
private fun restoreAnimeData(
|
||||
anime: Anime,
|
||||
episodes: List<Episode>,
|
||||
categories: List<Int>,
|
||||
history: List<BackupAnimeHistory>,
|
||||
tracks: List<AnimeTrack>,
|
||||
backupCategories: List<BackupCategory>
|
||||
) {
|
||||
animedb.inTransaction {
|
||||
val dbAnime = backupManager.getAnimeFromDatabase(anime)
|
||||
if (dbAnime == null) {
|
||||
// Manga not in database
|
||||
restoreAnimeFetch(anime, episodes, categories, history, tracks, backupCategories)
|
||||
} else {
|
||||
// Manga in database
|
||||
// Copy information from manga already in database
|
||||
backupManager.restoreAnimeNoFetch(anime, dbAnime)
|
||||
// Fetch rest of manga information
|
||||
restoreAnimeNoFetch(anime, episodes, categories, history, tracks, backupCategories)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches manga information
|
||||
*
|
||||
|
@ -135,6 +204,33 @@ class FullBackupRestore(context: Context, notifier: BackupNotifier) : AbstractBa
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches anime information
|
||||
*
|
||||
* @param anime anime that needs updating
|
||||
* @param episodes episodes of anime that needs updating
|
||||
* @param categories categories that need updating
|
||||
*/
|
||||
private fun restoreAnimeFetch(
|
||||
anime: Anime,
|
||||
episodes: List<Episode>,
|
||||
categories: List<Int>,
|
||||
history: List<BackupAnimeHistory>,
|
||||
tracks: List<AnimeTrack>,
|
||||
backupCategories: List<BackupCategory>
|
||||
) {
|
||||
try {
|
||||
val fetchedAnime = backupManager.restoreAnime(anime)
|
||||
fetchedAnime.id ?: return
|
||||
|
||||
backupManager.restoreEpisodesForAnime(fetchedAnime, episodes)
|
||||
|
||||
restoreExtraForAnime(fetchedAnime, categories, history, tracks, backupCategories)
|
||||
} catch (e: Exception) {
|
||||
errors.add(Date() to "${anime.title} - ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun restoreMangaNoFetch(
|
||||
backupManga: Manga,
|
||||
chapters: List<Chapter>,
|
||||
|
@ -148,6 +244,19 @@ class FullBackupRestore(context: Context, notifier: BackupNotifier) : AbstractBa
|
|||
restoreExtraForManga(backupManga, categories, history, tracks, backupCategories)
|
||||
}
|
||||
|
||||
private fun restoreAnimeNoFetch(
|
||||
backupAnime: Anime,
|
||||
episodes: List<Episode>,
|
||||
categories: List<Int>,
|
||||
history: List<BackupAnimeHistory>,
|
||||
tracks: List<AnimeTrack>,
|
||||
backupCategories: List<BackupCategory>
|
||||
) {
|
||||
backupManager.restoreEpisodesForAnime(backupAnime, episodes)
|
||||
|
||||
restoreExtraForAnime(backupAnime, categories, history, tracks, backupCategories)
|
||||
}
|
||||
|
||||
private fun restoreExtraForManga(manga: Manga, categories: List<Int>, history: List<BackupHistory>, tracks: List<Track>, backupCategories: List<BackupCategory>) {
|
||||
// Restore categories
|
||||
backupManager.restoreCategoriesForManga(manga, categories, backupCategories)
|
||||
|
@ -158,4 +267,15 @@ class FullBackupRestore(context: Context, notifier: BackupNotifier) : AbstractBa
|
|||
// Restore tracking
|
||||
backupManager.restoreTrackForManga(manga, tracks)
|
||||
}
|
||||
|
||||
private fun restoreExtraForAnime(anime: Anime, categories: List<Int>, history: List<BackupAnimeHistory>, tracks: List<AnimeTrack>, backupCategories: List<BackupCategory>) {
|
||||
// Restore categories
|
||||
backupManager.restoreCategoriesForAnime(anime, categories, backupCategories)
|
||||
|
||||
// Restore history
|
||||
backupManager.restoreHistoryForAnime(history)
|
||||
|
||||
// Restore tracking
|
||||
backupManager.restoreTrackForAnime(anime, tracks)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,17 +22,25 @@ class FullBackupRestoreValidator : AbstractBackupRestoreValidator() {
|
|||
val backupString = context.contentResolver.openInputStream(uri)!!.source().gzip().buffer().use { it.readByteArray() }
|
||||
val backup = backupManager.parser.decodeFromByteArray(BackupSerializer, backupString)
|
||||
|
||||
if (backup.backupManga.isEmpty()) {
|
||||
if (backup.backupManga.isEmpty() && backup.backupAnime.isEmpty()) {
|
||||
throw Exception(context.getString(R.string.invalid_backup_file_missing_manga))
|
||||
}
|
||||
|
||||
val sources = backup.backupSources.map { it.sourceId to it.name }.toMap()
|
||||
val animesources = backup.backupAnimeSources.map { it.sourceId to it.name }.toMap()
|
||||
val missingSources = sources
|
||||
.filter { sourceManager.get(it.key) == null }
|
||||
.values
|
||||
.sorted()
|
||||
.sorted() +
|
||||
animesources
|
||||
.filter { animesourceManager.get(it.key) == null }
|
||||
.values
|
||||
.sorted()
|
||||
|
||||
val trackers = backup.backupManga
|
||||
.flatMap { it.tracking }
|
||||
.map { it.syncId }
|
||||
.distinct() + backup.backupAnime
|
||||
.flatMap { it.tracking }
|
||||
.map { it.syncId }
|
||||
.distinct()
|
||||
|
|
|
@ -7,6 +7,9 @@ import kotlinx.serialization.protobuf.ProtoNumber
|
|||
data class Backup(
|
||||
@ProtoNumber(1) val backupManga: List<BackupManga>,
|
||||
@ProtoNumber(2) var backupCategories: List<BackupCategory> = emptyList(),
|
||||
@ProtoNumber(3) val backupAnime: List<BackupAnime>,
|
||||
@ProtoNumber(4) var backupCategoriesAnime: List<BackupCategory> = emptyList(),
|
||||
// Bump by 100 to specify this is a 0.x value
|
||||
@ProtoNumber(100) var backupSources: List<BackupSource> = emptyList(),
|
||||
@ProtoNumber(101) var backupAnimeSources: List<BackupAnimeSource> = emptyList(),
|
||||
)
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
package eu.kanade.tachiyomi.data.backup.full.models
|
||||
|
||||
import eu.kanade.tachiyomi.data.database.models.Anime
|
||||
import eu.kanade.tachiyomi.data.database.models.AnimeImpl
|
||||
import eu.kanade.tachiyomi.data.database.models.AnimeTrackImpl
|
||||
import eu.kanade.tachiyomi.data.database.models.EpisodeImpl
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.protobuf.ProtoNumber
|
||||
|
||||
@Serializable
|
||||
data class BackupAnime(
|
||||
// in 1.x some of these values have different names
|
||||
@ProtoNumber(1) var source: Long,
|
||||
// url is called key in 1.x
|
||||
@ProtoNumber(2) var url: String,
|
||||
@ProtoNumber(3) var title: String = "",
|
||||
@ProtoNumber(4) var artist: String? = null,
|
||||
@ProtoNumber(5) var author: String? = null,
|
||||
@ProtoNumber(6) var description: String? = null,
|
||||
@ProtoNumber(7) var genre: List<String> = emptyList(),
|
||||
@ProtoNumber(8) var status: Int = 0,
|
||||
// thumbnailUrl is called cover in 1.x
|
||||
@ProtoNumber(9) var thumbnailUrl: String? = null,
|
||||
// @ProtoNumber(10) val customCover: String = "", 1.x value, not used in 0.x
|
||||
// @ProtoNumber(11) val lastUpdate: Long = 0, 1.x value, not used in 0.x
|
||||
// @ProtoNumber(12) val lastInit: Long = 0, 1.x value, not used in 0.x
|
||||
@ProtoNumber(13) var dateAdded: Long = 0,
|
||||
// @ProtoNumber(15) val flags: Int = 0, 1.x value, not used in 0.x
|
||||
@ProtoNumber(16) var episodes: List<BackupEpisode> = emptyList(),
|
||||
@ProtoNumber(17) var categories: List<Int> = emptyList(),
|
||||
@ProtoNumber(18) var tracking: List<BackupAnimeTracking> = emptyList(),
|
||||
// Bump by 100 for values that are not saved/implemented in 1.x but are used in 0.x
|
||||
@ProtoNumber(100) var favorite: Boolean = true,
|
||||
@ProtoNumber(101) var episodeFlags: Int = 0,
|
||||
@ProtoNumber(102) var history: List<BackupAnimeHistory> = emptyList(),
|
||||
@ProtoNumber(103) var viewer_flags: Int = 0
|
||||
) {
|
||||
fun getAnimeImpl(): AnimeImpl {
|
||||
return AnimeImpl().apply {
|
||||
url = this@BackupAnime.url
|
||||
title = this@BackupAnime.title
|
||||
artist = this@BackupAnime.artist
|
||||
author = this@BackupAnime.author
|
||||
description = this@BackupAnime.description
|
||||
genre = this@BackupAnime.genre.joinToString()
|
||||
status = this@BackupAnime.status
|
||||
thumbnail_url = this@BackupAnime.thumbnailUrl
|
||||
favorite = this@BackupAnime.favorite
|
||||
source = this@BackupAnime.source
|
||||
date_added = this@BackupAnime.dateAdded
|
||||
viewer_flags = this@BackupAnime.viewer_flags
|
||||
episode_flags = this@BackupAnime.episodeFlags
|
||||
}
|
||||
}
|
||||
|
||||
fun getEpisodesImpl(): List<EpisodeImpl> {
|
||||
return episodes.map {
|
||||
it.toEpisodeImpl()
|
||||
}
|
||||
}
|
||||
|
||||
fun getTrackingImpl(): List<AnimeTrackImpl> {
|
||||
return tracking.map {
|
||||
it.getTrackingImpl()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun copyFrom(anime: Anime): BackupAnime {
|
||||
return BackupAnime(
|
||||
url = anime.url,
|
||||
title = anime.title,
|
||||
artist = anime.artist,
|
||||
author = anime.author,
|
||||
description = anime.description,
|
||||
genre = anime.getGenres() ?: emptyList(),
|
||||
status = anime.status,
|
||||
thumbnailUrl = anime.thumbnail_url,
|
||||
favorite = anime.favorite,
|
||||
source = anime.source,
|
||||
dateAdded = anime.date_added,
|
||||
viewer_flags = anime.viewer_flags,
|
||||
episodeFlags = anime.episode_flags
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package eu.kanade.tachiyomi.data.backup.full.models
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.protobuf.ProtoNumber
|
||||
|
||||
@Serializable
|
||||
data class BackupAnimeHistory(
|
||||
@ProtoNumber(0) var url: String,
|
||||
@ProtoNumber(1) var lastSeen: Long
|
||||
)
|
|
@ -0,0 +1,20 @@
|
|||
package eu.kanade.tachiyomi.data.backup.full.models
|
||||
|
||||
import eu.kanade.tachiyomi.animesource.AnimeSource
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.protobuf.ProtoNumber
|
||||
|
||||
@Serializable
|
||||
data class BackupAnimeSource(
|
||||
@ProtoNumber(0) var name: String = "",
|
||||
@ProtoNumber(1) var sourceId: Long
|
||||
) {
|
||||
companion object {
|
||||
fun copyFrom(source: AnimeSource): BackupAnimeSource {
|
||||
return BackupAnimeSource(
|
||||
name = source.name,
|
||||
sourceId = source.id
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
package eu.kanade.tachiyomi.data.backup.full.models
|
||||
|
||||
import eu.kanade.tachiyomi.data.database.models.AnimeTrack
|
||||
import eu.kanade.tachiyomi.data.database.models.AnimeTrackImpl
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.protobuf.ProtoNumber
|
||||
|
||||
@Serializable
|
||||
data class BackupAnimeTracking(
|
||||
// in 1.x some of these values have different types or names
|
||||
// syncId is called siteId in 1,x
|
||||
@ProtoNumber(1) var syncId: Int,
|
||||
// LibraryId is not null in 1.x
|
||||
@ProtoNumber(2) var libraryId: Long,
|
||||
@ProtoNumber(3) var mediaId: Int = 0,
|
||||
// trackingUrl is called mediaUrl in 1.x
|
||||
@ProtoNumber(4) var trackingUrl: String = "",
|
||||
@ProtoNumber(5) var title: String = "",
|
||||
// lastChapterRead is called last read, and it has been changed to a float in 1.x
|
||||
@ProtoNumber(6) var lastChapterRead: Float = 0F,
|
||||
@ProtoNumber(7) var totalChapters: Int = 0,
|
||||
@ProtoNumber(8) var score: Float = 0F,
|
||||
@ProtoNumber(9) var status: Int = 0,
|
||||
// startedReadingDate is called startReadTime in 1.x
|
||||
@ProtoNumber(10) var startedWatchingDate: Long = 0,
|
||||
// finishedReadingDate is called endReadTime in 1.x
|
||||
@ProtoNumber(11) var finishedWatchingDate: Long = 0,
|
||||
) {
|
||||
fun getTrackingImpl(): AnimeTrackImpl {
|
||||
return AnimeTrackImpl().apply {
|
||||
sync_id = this@BackupAnimeTracking.syncId
|
||||
media_id = this@BackupAnimeTracking.mediaId
|
||||
library_id = this@BackupAnimeTracking.libraryId
|
||||
title = this@BackupAnimeTracking.title
|
||||
// convert from float to int because of 1.x types
|
||||
last_episode_seen = this@BackupAnimeTracking.lastChapterRead.toInt()
|
||||
total_episodes = this@BackupAnimeTracking.totalChapters
|
||||
score = this@BackupAnimeTracking.score
|
||||
status = this@BackupAnimeTracking.status
|
||||
started_watching_date = this@BackupAnimeTracking.startedWatchingDate
|
||||
finished_watching_date = this@BackupAnimeTracking.finishedWatchingDate
|
||||
tracking_url = this@BackupAnimeTracking.trackingUrl
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun copyFrom(track: AnimeTrack): BackupAnimeTracking {
|
||||
return BackupAnimeTracking(
|
||||
syncId = track.sync_id,
|
||||
mediaId = track.media_id,
|
||||
// forced not null so its compatible with 1.x backup system
|
||||
libraryId = track.library_id!!,
|
||||
title = track.title,
|
||||
// convert to float for 1.x
|
||||
lastChapterRead = track.last_episode_seen.toFloat(),
|
||||
totalChapters = track.total_episodes,
|
||||
score = track.score,
|
||||
status = track.status,
|
||||
startedWatchingDate = track.started_watching_date,
|
||||
finishedWatchingDate = track.finished_watching_date,
|
||||
trackingUrl = track.tracking_url
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
package eu.kanade.tachiyomi.data.backup.full.models
|
||||
|
||||
import eu.kanade.tachiyomi.data.database.models.Episode
|
||||
import eu.kanade.tachiyomi.data.database.models.EpisodeImpl
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.protobuf.ProtoNumber
|
||||
|
||||
@Serializable
|
||||
data class BackupEpisode(
|
||||
// in 1.x some of these values have different names
|
||||
// url is called key in 1.x
|
||||
@ProtoNumber(1) var url: String,
|
||||
@ProtoNumber(2) var name: String,
|
||||
@ProtoNumber(3) var scanlator: String? = null,
|
||||
@ProtoNumber(4) var seen: Boolean = false,
|
||||
@ProtoNumber(5) var bookmark: Boolean = false,
|
||||
// lastPageRead is called progress in 1.x
|
||||
@ProtoNumber(6) var lastSecondSeen: Long = 0,
|
||||
@ProtoNumber(7) var dateFetch: Long = 0,
|
||||
@ProtoNumber(8) var dateUpload: Long = 0,
|
||||
// episodeNumber is called number is 1.x
|
||||
@ProtoNumber(9) var episodeNumber: Float = 0F,
|
||||
@ProtoNumber(10) var sourceOrder: Int = 0,
|
||||
) {
|
||||
fun toEpisodeImpl(): EpisodeImpl {
|
||||
return EpisodeImpl().apply {
|
||||
url = this@BackupEpisode.url
|
||||
name = this@BackupEpisode.name
|
||||
episode_number = this@BackupEpisode.episodeNumber
|
||||
scanlator = this@BackupEpisode.scanlator
|
||||
seen = this@BackupEpisode.seen
|
||||
bookmark = this@BackupEpisode.bookmark
|
||||
last_second_seen = this@BackupEpisode.lastSecondSeen
|
||||
date_fetch = this@BackupEpisode.dateFetch
|
||||
date_upload = this@BackupEpisode.dateUpload
|
||||
source_order = this@BackupEpisode.sourceOrder
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun copyFrom(episode: Episode): BackupEpisode {
|
||||
return BackupEpisode(
|
||||
url = episode.url,
|
||||
name = episode.name,
|
||||
episodeNumber = episode.episode_number,
|
||||
scanlator = episode.scanlator,
|
||||
seen = episode.seen,
|
||||
bookmark = episode.bookmark,
|
||||
lastSecondSeen = episode.last_second_seen,
|
||||
dateFetch = episode.date_fetch,
|
||||
dateUpload = episode.date_upload,
|
||||
sourceOrder = episode.source_order
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,6 +7,6 @@ import java.util.Locale
|
|||
object BackupFull {
|
||||
fun getDefaultFilename(): String {
|
||||
val date = SimpleDateFormat("yyyy-MM-dd_HH-mm", Locale.getDefault()).format(Date())
|
||||
return "tachiyomi_$date.proto.gz"
|
||||
return "aniyomi_$date.proto.gz"
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue