Restore tracking on backup (#1097)

This commit is contained in:
Bram van de Kerkhof 2017-12-04 22:55:57 +01:00 committed by inorichi
parent 08baf798aa
commit e745836404
3 changed files with 105 additions and 69 deletions

View File

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

View File

@ -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]

View File

@ -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>