mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-11-04 08:08:55 +01:00 
			
		
		
		
	Restore tracking on backup (#1097)
This commit is contained in:
		
				
					committed by
					
						
						inorichi
					
				
			
			
				
	
			
			
			
						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.preference.PreferencesHelper
 | 
			
		||||
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.SourceManager
 | 
			
		||||
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()
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Tracking manager
 | 
			
		||||
     */
 | 
			
		||||
    internal val trackManager: TrackManager by injectLazy()
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Version of parser
 | 
			
		||||
     */
 | 
			
		||||
@@ -67,18 +73,16 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
 | 
			
		||||
        parser = initParser()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun initParser(): Gson {
 | 
			
		||||
        return when (version) {
 | 
			
		||||
            1 -> GsonBuilder().create()
 | 
			
		||||
            2 -> GsonBuilder()
 | 
			
		||||
                    .registerTypeAdapter<MangaImpl>(MangaTypeAdapter.build())
 | 
			
		||||
                    .registerTypeHierarchyAdapter<ChapterImpl>(ChapterTypeAdapter.build())
 | 
			
		||||
                    .registerTypeAdapter<CategoryImpl>(CategoryTypeAdapter.build())
 | 
			
		||||
                    .registerTypeAdapter<DHistory>(HistoryTypeAdapter.build())
 | 
			
		||||
                    .registerTypeHierarchyAdapter<TrackImpl>(TrackTypeAdapter.build())
 | 
			
		||||
                    .create()
 | 
			
		||||
            else -> throw Exception("Json version unknown")
 | 
			
		||||
        }
 | 
			
		||||
    private fun initParser(): Gson = when (version) {
 | 
			
		||||
        1 -> GsonBuilder().create()
 | 
			
		||||
        2 -> GsonBuilder()
 | 
			
		||||
                .registerTypeAdapter<MangaImpl>(MangaTypeAdapter.build())
 | 
			
		||||
                .registerTypeHierarchyAdapter<ChapterImpl>(ChapterTypeAdapter.build())
 | 
			
		||||
                .registerTypeAdapter<CategoryImpl>(CategoryTypeAdapter.build())
 | 
			
		||||
                .registerTypeAdapter<DHistory>(HistoryTypeAdapter.build())
 | 
			
		||||
                .registerTypeHierarchyAdapter<TrackImpl>(TrackTypeAdapter.build())
 | 
			
		||||
                .create()
 | 
			
		||||
        else -> throw Exception("Json version unknown")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -300,23 +304,26 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
 | 
			
		||||
        val trackToUpdate = ArrayList<Track>()
 | 
			
		||||
 | 
			
		||||
        for (track in tracks) {
 | 
			
		||||
            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.remote_id != dbTrack.remote_id) {
 | 
			
		||||
                        dbTrack.remote_id = track.remote_id
 | 
			
		||||
            val service = trackManager.getService(track.sync_id)
 | 
			
		||||
            if (service != null && service.isLogged) {
 | 
			
		||||
                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.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) {
 | 
			
		||||
                // Insert new sync. Let the db assign the id
 | 
			
		||||
                track.id = null
 | 
			
		||||
                trackToUpdate.add(track)
 | 
			
		||||
                if (!isInDatabase) {
 | 
			
		||||
                    // Insert new sync. Let the db assign the id
 | 
			
		||||
                    track.id = null
 | 
			
		||||
                    trackToUpdate.add(track)
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        // Update database
 | 
			
		||||
@@ -361,32 +368,29 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
 | 
			
		||||
     *
 | 
			
		||||
     * @return [Manga], null if not found
 | 
			
		||||
     */
 | 
			
		||||
    internal fun getMangaFromDatabase(manga: Manga): Manga? {
 | 
			
		||||
        return databaseHelper.getManga(manga.url, manga.source).executeAsBlocking()
 | 
			
		||||
    }
 | 
			
		||||
    internal fun getMangaFromDatabase(manga: Manga): Manga? =
 | 
			
		||||
            databaseHelper.getManga(manga.url, manga.source).executeAsBlocking()
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns list containing manga from library
 | 
			
		||||
     *
 | 
			
		||||
     * @return [Manga] from library
 | 
			
		||||
     */
 | 
			
		||||
    internal fun getFavoriteManga(): List<Manga> {
 | 
			
		||||
        return databaseHelper.getFavoriteMangas().executeAsBlocking()
 | 
			
		||||
    }
 | 
			
		||||
    internal fun getFavoriteManga(): List<Manga> =
 | 
			
		||||
            databaseHelper.getFavoriteMangas().executeAsBlocking()
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Inserts manga and returns id
 | 
			
		||||
     *
 | 
			
		||||
     * @return id of [Manga], null if not found
 | 
			
		||||
     */
 | 
			
		||||
    internal fun insertManga(manga: Manga): Long? {
 | 
			
		||||
        return databaseHelper.insertManga(manga).executeAsBlocking().insertedId()
 | 
			
		||||
    }
 | 
			
		||||
    internal fun insertManga(manga: Manga): Long? =
 | 
			
		||||
            databaseHelper.insertManga(manga).executeAsBlocking().insertedId()
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Inserts list of chapters
 | 
			
		||||
     */
 | 
			
		||||
    internal fun insertChapters(chapters: List<Chapter>) {
 | 
			
		||||
    private fun insertChapters(chapters: List<Chapter>) {
 | 
			
		||||
        databaseHelper.updateChaptersBackup(chapters).executeAsBlocking()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -395,7 +399,5 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
 | 
			
		||||
     *
 | 
			
		||||
     * @return number of backups selected by user
 | 
			
		||||
     */
 | 
			
		||||
    fun numberOfBackups(): Int {
 | 
			
		||||
        return preferences.numberOfBackups().getOrDefault()
 | 
			
		||||
    }
 | 
			
		||||
    fun numberOfBackups(): Int = 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.database.DatabaseHelper
 | 
			
		||||
import eu.kanade.tachiyomi.data.database.models.*
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.TrackManager
 | 
			
		||||
import eu.kanade.tachiyomi.source.Source
 | 
			
		||||
import eu.kanade.tachiyomi.util.chop
 | 
			
		||||
import eu.kanade.tachiyomi.util.isServiceRunning
 | 
			
		||||
@@ -49,9 +50,8 @@ class BackupRestoreService : Service() {
 | 
			
		||||
         * @param context the application context.
 | 
			
		||||
         * @return true if the service is running, false otherwise.
 | 
			
		||||
         */
 | 
			
		||||
        fun isRunning(context: Context): Boolean {
 | 
			
		||||
            return context.isServiceRunning(BackupRestoreService::class.java)
 | 
			
		||||
        }
 | 
			
		||||
        private fun isRunning(context: Context): Boolean =
 | 
			
		||||
                context.isServiceRunning(BackupRestoreService::class.java)
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Starts a service to restore a backup from Json
 | 
			
		||||
@@ -113,7 +113,13 @@ class BackupRestoreService : Service() {
 | 
			
		||||
     */
 | 
			
		||||
    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.
 | 
			
		||||
@@ -142,9 +148,7 @@ class BackupRestoreService : Service() {
 | 
			
		||||
    /**
 | 
			
		||||
     * This method needs to be implemented, but it's not used/needed.
 | 
			
		||||
     */
 | 
			
		||||
    override fun onBind(intent: Intent): IBinder? {
 | 
			
		||||
        return null
 | 
			
		||||
    }
 | 
			
		||||
    override fun onBind(intent: Intent): IBinder? = null
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Method called when the service receives an intent.
 | 
			
		||||
@@ -164,7 +168,7 @@ class BackupRestoreService : Service() {
 | 
			
		||||
 | 
			
		||||
        subscription = Observable.using(
 | 
			
		||||
                { db.lowLevel().beginTransaction() },
 | 
			
		||||
                { getRestoreObservable(uri).doOnNext{ db.lowLevel().setTransactionSuccessful() } },
 | 
			
		||||
                { getRestoreObservable(uri).doOnNext { db.lowLevel().setTransactionSuccessful() } },
 | 
			
		||||
                { executor.execute { db.lowLevel().endTransaction() } })
 | 
			
		||||
                .doAfterTerminate { stopSelf(startId) }
 | 
			
		||||
                .subscribeOn(Schedulers.from(executor))
 | 
			
		||||
@@ -294,14 +298,14 @@ class BackupRestoreService : Service() {
 | 
			
		||||
        val source = backupManager.sourceManager.get(manga.source) ?: return null
 | 
			
		||||
        val dbManga = backupManager.getMangaFromDatabase(manga)
 | 
			
		||||
 | 
			
		||||
        if (dbManga == null) {
 | 
			
		||||
        return if (dbManga == null) {
 | 
			
		||||
            // Manga not in database
 | 
			
		||||
            return mangaFetchObservable(source, manga, chapters, categories, history, tracks)
 | 
			
		||||
            mangaFetchObservable(source, manga, chapters, categories, history, tracks)
 | 
			
		||||
        } else { // Manga in database
 | 
			
		||||
            // Copy information from manga already in database
 | 
			
		||||
            backupManager.restoreMangaNoFetch(manga, dbManga)
 | 
			
		||||
            // 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 }
 | 
			
		||||
                }
 | 
			
		||||
                .doOnNext {
 | 
			
		||||
                    // Restore categories
 | 
			
		||||
                    backupManager.restoreCategoriesForManga(it, categories)
 | 
			
		||||
 | 
			
		||||
                    // Restore history
 | 
			
		||||
                    backupManager.restoreHistoryForManga(history)
 | 
			
		||||
 | 
			
		||||
                    // Restore tracking
 | 
			
		||||
                    backupManager.restoreTrackForManga(it, tracks)
 | 
			
		||||
                    restoreExtraForManga(it, categories, history, tracks)
 | 
			
		||||
                }
 | 
			
		||||
                .flatMap {
 | 
			
		||||
                    trackingFetchObservable(it, tracks)
 | 
			
		||||
                            // Convert to the manga that contains new chapters.
 | 
			
		||||
                            .map { manga }
 | 
			
		||||
                }
 | 
			
		||||
                .doOnCompleted {
 | 
			
		||||
                    restoreProgress += 1
 | 
			
		||||
@@ -356,14 +358,12 @@ class BackupRestoreService : Service() {
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                .doOnNext {
 | 
			
		||||
                    // Restore categories
 | 
			
		||||
                    backupManager.restoreCategoriesForManga(it, categories)
 | 
			
		||||
 | 
			
		||||
                    // Restore history
 | 
			
		||||
                    backupManager.restoreHistoryForManga(history)
 | 
			
		||||
 | 
			
		||||
                    // Restore tracking
 | 
			
		||||
                    backupManager.restoreTrackForManga(it, tracks)
 | 
			
		||||
                    restoreExtraForManga(it, categories, history, tracks)
 | 
			
		||||
                }
 | 
			
		||||
                .flatMap { manga ->
 | 
			
		||||
                    trackingFetchObservable(manga, tracks)
 | 
			
		||||
                            // Convert to the manga that contains new chapters.
 | 
			
		||||
                            .map { manga }
 | 
			
		||||
                }
 | 
			
		||||
                .doOnCompleted {
 | 
			
		||||
                    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
 | 
			
		||||
     *
 | 
			
		||||
@@ -383,10 +394,33 @@ class BackupRestoreService : Service() {
 | 
			
		||||
                // If there's any error, return empty update and continue.
 | 
			
		||||
                .onErrorReturn {
 | 
			
		||||
                    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]
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user