mirror of
https://github.com/tachiyomiorg/tachiyomi.git
synced 2024-12-23 00:51:49 +01:00
Restore tracking on backup (#1097)
This commit is contained in:
parent
08baf798aa
commit
e745836404
@ -23,6 +23,7 @@ import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
|||||||
import eu.kanade.tachiyomi.data.database.models.*
|
import eu.kanade.tachiyomi.data.database.models.*
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
||||||
|
import eu.kanade.tachiyomi.data.track.TrackManager
|
||||||
import eu.kanade.tachiyomi.source.Source
|
import eu.kanade.tachiyomi.source.Source
|
||||||
import eu.kanade.tachiyomi.source.SourceManager
|
import eu.kanade.tachiyomi.source.SourceManager
|
||||||
import eu.kanade.tachiyomi.util.syncChaptersWithSource
|
import eu.kanade.tachiyomi.util.syncChaptersWithSource
|
||||||
@ -41,6 +42,11 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
|
|||||||
*/
|
*/
|
||||||
internal val sourceManager: SourceManager by injectLazy()
|
internal val sourceManager: SourceManager by injectLazy()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tracking manager
|
||||||
|
*/
|
||||||
|
internal val trackManager: TrackManager by injectLazy()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Version of parser
|
* Version of parser
|
||||||
*/
|
*/
|
||||||
@ -67,18 +73,16 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
|
|||||||
parser = initParser()
|
parser = initParser()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initParser(): Gson {
|
private fun initParser(): Gson = when (version) {
|
||||||
return when (version) {
|
1 -> GsonBuilder().create()
|
||||||
1 -> GsonBuilder().create()
|
2 -> GsonBuilder()
|
||||||
2 -> GsonBuilder()
|
.registerTypeAdapter<MangaImpl>(MangaTypeAdapter.build())
|
||||||
.registerTypeAdapter<MangaImpl>(MangaTypeAdapter.build())
|
.registerTypeHierarchyAdapter<ChapterImpl>(ChapterTypeAdapter.build())
|
||||||
.registerTypeHierarchyAdapter<ChapterImpl>(ChapterTypeAdapter.build())
|
.registerTypeAdapter<CategoryImpl>(CategoryTypeAdapter.build())
|
||||||
.registerTypeAdapter<CategoryImpl>(CategoryTypeAdapter.build())
|
.registerTypeAdapter<DHistory>(HistoryTypeAdapter.build())
|
||||||
.registerTypeAdapter<DHistory>(HistoryTypeAdapter.build())
|
.registerTypeHierarchyAdapter<TrackImpl>(TrackTypeAdapter.build())
|
||||||
.registerTypeHierarchyAdapter<TrackImpl>(TrackTypeAdapter.build())
|
.create()
|
||||||
.create()
|
else -> throw Exception("Json version unknown")
|
||||||
else -> throw Exception("Json version unknown")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -300,23 +304,26 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
|
|||||||
val trackToUpdate = ArrayList<Track>()
|
val trackToUpdate = ArrayList<Track>()
|
||||||
|
|
||||||
for (track in tracks) {
|
for (track in tracks) {
|
||||||
var isInDatabase = false
|
val service = trackManager.getService(track.sync_id)
|
||||||
for (dbTrack in dbTracks) {
|
if (service != null && service.isLogged) {
|
||||||
if (track.sync_id == dbTrack.sync_id) {
|
var isInDatabase = false
|
||||||
// The sync is already in the db, only update its fields
|
for (dbTrack in dbTracks) {
|
||||||
if (track.remote_id != dbTrack.remote_id) {
|
if (track.sync_id == dbTrack.sync_id) {
|
||||||
dbTrack.remote_id = track.remote_id
|
// The sync is already in the db, only update its fields
|
||||||
|
if (track.remote_id != dbTrack.remote_id) {
|
||||||
|
dbTrack.remote_id = track.remote_id
|
||||||
|
}
|
||||||
|
dbTrack.last_chapter_read = Math.max(dbTrack.last_chapter_read, track.last_chapter_read)
|
||||||
|
isInDatabase = true
|
||||||
|
trackToUpdate.add(dbTrack)
|
||||||
|
break
|
||||||
}
|
}
|
||||||
dbTrack.last_chapter_read = Math.max(dbTrack.last_chapter_read, track.last_chapter_read)
|
|
||||||
isInDatabase = true
|
|
||||||
trackToUpdate.add(dbTrack)
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
if (!isInDatabase) {
|
||||||
if (!isInDatabase) {
|
// Insert new sync. Let the db assign the id
|
||||||
// Insert new sync. Let the db assign the id
|
track.id = null
|
||||||
track.id = null
|
trackToUpdate.add(track)
|
||||||
trackToUpdate.add(track)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Update database
|
// Update database
|
||||||
@ -361,32 +368,29 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
|
|||||||
*
|
*
|
||||||
* @return [Manga], null if not found
|
* @return [Manga], null if not found
|
||||||
*/
|
*/
|
||||||
internal fun getMangaFromDatabase(manga: Manga): Manga? {
|
internal fun getMangaFromDatabase(manga: Manga): Manga? =
|
||||||
return databaseHelper.getManga(manga.url, manga.source).executeAsBlocking()
|
databaseHelper.getManga(manga.url, manga.source).executeAsBlocking()
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns list containing manga from library
|
* Returns list containing manga from library
|
||||||
*
|
*
|
||||||
* @return [Manga] from library
|
* @return [Manga] from library
|
||||||
*/
|
*/
|
||||||
internal fun getFavoriteManga(): List<Manga> {
|
internal fun getFavoriteManga(): List<Manga> =
|
||||||
return databaseHelper.getFavoriteMangas().executeAsBlocking()
|
databaseHelper.getFavoriteMangas().executeAsBlocking()
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inserts manga and returns id
|
* Inserts manga and returns id
|
||||||
*
|
*
|
||||||
* @return id of [Manga], null if not found
|
* @return id of [Manga], null if not found
|
||||||
*/
|
*/
|
||||||
internal fun insertManga(manga: Manga): Long? {
|
internal fun insertManga(manga: Manga): Long? =
|
||||||
return databaseHelper.insertManga(manga).executeAsBlocking().insertedId()
|
databaseHelper.insertManga(manga).executeAsBlocking().insertedId()
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inserts list of chapters
|
* Inserts list of chapters
|
||||||
*/
|
*/
|
||||||
internal fun insertChapters(chapters: List<Chapter>) {
|
private fun insertChapters(chapters: List<Chapter>) {
|
||||||
databaseHelper.updateChaptersBackup(chapters).executeAsBlocking()
|
databaseHelper.updateChaptersBackup(chapters).executeAsBlocking()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -395,7 +399,5 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
|
|||||||
*
|
*
|
||||||
* @return number of backups selected by user
|
* @return number of backups selected by user
|
||||||
*/
|
*/
|
||||||
fun numberOfBackups(): Int {
|
fun numberOfBackups(): Int = preferences.numberOfBackups().getOrDefault()
|
||||||
return preferences.numberOfBackups().getOrDefault()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@ import eu.kanade.tachiyomi.data.backup.models.Backup.VERSION
|
|||||||
import eu.kanade.tachiyomi.data.backup.models.DHistory
|
import eu.kanade.tachiyomi.data.backup.models.DHistory
|
||||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||||
import eu.kanade.tachiyomi.data.database.models.*
|
import eu.kanade.tachiyomi.data.database.models.*
|
||||||
|
import eu.kanade.tachiyomi.data.track.TrackManager
|
||||||
import eu.kanade.tachiyomi.source.Source
|
import eu.kanade.tachiyomi.source.Source
|
||||||
import eu.kanade.tachiyomi.util.chop
|
import eu.kanade.tachiyomi.util.chop
|
||||||
import eu.kanade.tachiyomi.util.isServiceRunning
|
import eu.kanade.tachiyomi.util.isServiceRunning
|
||||||
@ -49,9 +50,8 @@ class BackupRestoreService : Service() {
|
|||||||
* @param context the application context.
|
* @param context the application context.
|
||||||
* @return true if the service is running, false otherwise.
|
* @return true if the service is running, false otherwise.
|
||||||
*/
|
*/
|
||||||
fun isRunning(context: Context): Boolean {
|
private fun isRunning(context: Context): Boolean =
|
||||||
return context.isServiceRunning(BackupRestoreService::class.java)
|
context.isServiceRunning(BackupRestoreService::class.java)
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts a service to restore a backup from Json
|
* Starts a service to restore a backup from Json
|
||||||
@ -113,7 +113,13 @@ class BackupRestoreService : Service() {
|
|||||||
*/
|
*/
|
||||||
private val db: DatabaseHelper by injectLazy()
|
private val db: DatabaseHelper by injectLazy()
|
||||||
|
|
||||||
lateinit var executor: ExecutorService
|
/**
|
||||||
|
* Tracking manager
|
||||||
|
*/
|
||||||
|
internal val trackManager: TrackManager by injectLazy()
|
||||||
|
|
||||||
|
|
||||||
|
private lateinit var executor: ExecutorService
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method called when the service is created. It injects dependencies and acquire the wake lock.
|
* Method called when the service is created. It injects dependencies and acquire the wake lock.
|
||||||
@ -142,9 +148,7 @@ class BackupRestoreService : Service() {
|
|||||||
/**
|
/**
|
||||||
* This method needs to be implemented, but it's not used/needed.
|
* This method needs to be implemented, but it's not used/needed.
|
||||||
*/
|
*/
|
||||||
override fun onBind(intent: Intent): IBinder? {
|
override fun onBind(intent: Intent): IBinder? = null
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method called when the service receives an intent.
|
* Method called when the service receives an intent.
|
||||||
@ -164,7 +168,7 @@ class BackupRestoreService : Service() {
|
|||||||
|
|
||||||
subscription = Observable.using(
|
subscription = Observable.using(
|
||||||
{ db.lowLevel().beginTransaction() },
|
{ db.lowLevel().beginTransaction() },
|
||||||
{ getRestoreObservable(uri).doOnNext{ db.lowLevel().setTransactionSuccessful() } },
|
{ getRestoreObservable(uri).doOnNext { db.lowLevel().setTransactionSuccessful() } },
|
||||||
{ executor.execute { db.lowLevel().endTransaction() } })
|
{ executor.execute { db.lowLevel().endTransaction() } })
|
||||||
.doAfterTerminate { stopSelf(startId) }
|
.doAfterTerminate { stopSelf(startId) }
|
||||||
.subscribeOn(Schedulers.from(executor))
|
.subscribeOn(Schedulers.from(executor))
|
||||||
@ -294,14 +298,14 @@ class BackupRestoreService : Service() {
|
|||||||
val source = backupManager.sourceManager.get(manga.source) ?: return null
|
val source = backupManager.sourceManager.get(manga.source) ?: return null
|
||||||
val dbManga = backupManager.getMangaFromDatabase(manga)
|
val dbManga = backupManager.getMangaFromDatabase(manga)
|
||||||
|
|
||||||
if (dbManga == null) {
|
return if (dbManga == null) {
|
||||||
// Manga not in database
|
// Manga not in database
|
||||||
return mangaFetchObservable(source, manga, chapters, categories, history, tracks)
|
mangaFetchObservable(source, manga, chapters, categories, history, tracks)
|
||||||
} else { // Manga in database
|
} else { // Manga in database
|
||||||
// Copy information from manga already in database
|
// Copy information from manga already in database
|
||||||
backupManager.restoreMangaNoFetch(manga, dbManga)
|
backupManager.restoreMangaNoFetch(manga, dbManga)
|
||||||
// Fetch rest of manga information
|
// Fetch rest of manga information
|
||||||
return mangaNoFetchObservable(source, manga, chapters, categories, history, tracks)
|
mangaNoFetchObservable(source, manga, chapters, categories, history, tracks)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -327,14 +331,12 @@ class BackupRestoreService : Service() {
|
|||||||
.map { manga }
|
.map { manga }
|
||||||
}
|
}
|
||||||
.doOnNext {
|
.doOnNext {
|
||||||
// Restore categories
|
restoreExtraForManga(it, categories, history, tracks)
|
||||||
backupManager.restoreCategoriesForManga(it, categories)
|
}
|
||||||
|
.flatMap {
|
||||||
// Restore history
|
trackingFetchObservable(it, tracks)
|
||||||
backupManager.restoreHistoryForManga(history)
|
// Convert to the manga that contains new chapters.
|
||||||
|
.map { manga }
|
||||||
// Restore tracking
|
|
||||||
backupManager.restoreTrackForManga(it, tracks)
|
|
||||||
}
|
}
|
||||||
.doOnCompleted {
|
.doOnCompleted {
|
||||||
restoreProgress += 1
|
restoreProgress += 1
|
||||||
@ -356,14 +358,12 @@ class BackupRestoreService : Service() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.doOnNext {
|
.doOnNext {
|
||||||
// Restore categories
|
restoreExtraForManga(it, categories, history, tracks)
|
||||||
backupManager.restoreCategoriesForManga(it, categories)
|
}
|
||||||
|
.flatMap { manga ->
|
||||||
// Restore history
|
trackingFetchObservable(manga, tracks)
|
||||||
backupManager.restoreHistoryForManga(history)
|
// Convert to the manga that contains new chapters.
|
||||||
|
.map { manga }
|
||||||
// Restore tracking
|
|
||||||
backupManager.restoreTrackForManga(it, tracks)
|
|
||||||
}
|
}
|
||||||
.doOnCompleted {
|
.doOnCompleted {
|
||||||
restoreProgress += 1
|
restoreProgress += 1
|
||||||
@ -371,6 +371,17 @@ class BackupRestoreService : Service() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun restoreExtraForManga(manga: Manga, categories: List<String>, history: List<DHistory>, tracks: List<Track>) {
|
||||||
|
// Restore categories
|
||||||
|
backupManager.restoreCategoriesForManga(manga, categories)
|
||||||
|
|
||||||
|
// Restore history
|
||||||
|
backupManager.restoreHistoryForManga(history)
|
||||||
|
|
||||||
|
// Restore tracking
|
||||||
|
backupManager.restoreTrackForManga(manga, tracks)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* [Observable] that fetches chapter information
|
* [Observable] that fetches chapter information
|
||||||
*
|
*
|
||||||
@ -383,10 +394,33 @@ class BackupRestoreService : Service() {
|
|||||||
// If there's any error, return empty update and continue.
|
// If there's any error, return empty update and continue.
|
||||||
.onErrorReturn {
|
.onErrorReturn {
|
||||||
errors.add(Date() to "${manga.title} - ${it.message}")
|
errors.add(Date() to "${manga.title} - ${it.message}")
|
||||||
Pair(emptyList<Chapter>(), emptyList<Chapter>())
|
Pair(emptyList(), emptyList())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [Observable] that refreshes tracking information
|
||||||
|
* @param manga manga that needs updating.
|
||||||
|
* @param tracks list containing tracks from restore file.
|
||||||
|
* @return [Observable] that contains updated track item
|
||||||
|
*/
|
||||||
|
private fun trackingFetchObservable(manga: Manga, tracks: List<Track>): Observable<Track> {
|
||||||
|
return Observable.from(tracks)
|
||||||
|
.concatMap { track ->
|
||||||
|
val service = trackManager.getService(track.sync_id)
|
||||||
|
if (service != null && service.isLogged) {
|
||||||
|
service.refresh(track)
|
||||||
|
.doOnNext { db.insertTrack(it).executeAsBlocking() }
|
||||||
|
.onErrorReturn {
|
||||||
|
errors.add(Date() to "${manga.title} - ${it.message}")
|
||||||
|
track
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
errors.add(Date() to "${manga.title} - ${service?.name} not logged in")
|
||||||
|
Observable.empty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called to update dialog in [BackupConst]
|
* Called to update dialog in [BackupConst]
|
||||||
|
@ -201,7 +201,7 @@
|
|||||||
<string name="dialog_restoring_backup">Backup wird wiederhergestellt
|
<string name="dialog_restoring_backup">Backup wird wiederhergestellt
|
||||||
%1$s zur Bibliothek hinzugefügt</string>
|
%1$s zur Bibliothek hinzugefügt</string>
|
||||||
<string name="source_not_found">Quelle nicht gefunden</string>
|
<string name="source_not_found">Quelle nicht gefunden</string>
|
||||||
<string name="dialog_restoring_source_not_found">Backup wird wiederhergestellt %1%s
|
<string name="dialog_restoring_source_not_found">Backup wird wiederhergestellt %1$s
|
||||||
\nQuelle nicht gefunden</string>
|
\nQuelle nicht gefunden</string>
|
||||||
<string name="backup_created">Backup erstellt</string>
|
<string name="backup_created">Backup erstellt</string>
|
||||||
<string name="restore_completed">Wiederherstellen erfolgreich</string>
|
<string name="restore_completed">Wiederherstellen erfolgreich</string>
|
||||||
|
Loading…
Reference in New Issue
Block a user