Remove usage of RxJava from LibraryUpdateService

This commit is contained in:
arkon 2021-01-23 11:20:16 -05:00
parent 628bd5d6b4
commit 86b9d7e843

View file

@ -27,15 +27,16 @@ import eu.kanade.tachiyomi.source.model.toSChapter
import eu.kanade.tachiyomi.source.model.toSManga import eu.kanade.tachiyomi.source.model.toSManga
import eu.kanade.tachiyomi.util.chapter.NoChaptersException import eu.kanade.tachiyomi.util.chapter.NoChaptersException
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
import eu.kanade.tachiyomi.util.lang.runAsObservable import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.prepUpdateCover import eu.kanade.tachiyomi.util.prepUpdateCover
import eu.kanade.tachiyomi.util.shouldDownloadNewChapters import eu.kanade.tachiyomi.util.shouldDownloadNewChapters
import eu.kanade.tachiyomi.util.storage.getUriCompat import eu.kanade.tachiyomi.util.storage.getUriCompat
import eu.kanade.tachiyomi.util.system.acquireWakeLock import eu.kanade.tachiyomi.util.system.acquireWakeLock
import eu.kanade.tachiyomi.util.system.isServiceRunning import eu.kanade.tachiyomi.util.system.isServiceRunning
import rx.Observable import kotlinx.coroutines.CoroutineScope
import rx.Subscription import kotlinx.coroutines.Job
import rx.schedulers.Schedulers import kotlinx.coroutines.MainScope
import kotlinx.coroutines.cancel
import timber.log.Timber import timber.log.Timber
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
@ -59,17 +60,11 @@ class LibraryUpdateService(
val coverCache: CoverCache = Injekt.get() val coverCache: CoverCache = Injekt.get()
) : Service() { ) : Service() {
/**
* Wake lock that will be held until the service is destroyed.
*/
private lateinit var wakeLock: PowerManager.WakeLock private lateinit var wakeLock: PowerManager.WakeLock
private lateinit var notifier: LibraryUpdateNotifier private lateinit var notifier: LibraryUpdateNotifier
private lateinit var scope: CoroutineScope
/** private var updateJob: Job? = null
* Subscription where the update is done.
*/
private var subscription: Subscription? = null
/** /**
* Defines what should be updated within a service execution. * Defines what should be updated within a service execution.
@ -144,6 +139,7 @@ class LibraryUpdateService(
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
scope = MainScope()
notifier = LibraryUpdateNotifier(this) notifier = LibraryUpdateNotifier(this)
wakeLock = acquireWakeLock(javaClass.name) wakeLock = acquireWakeLock(javaClass.name)
@ -155,7 +151,8 @@ class LibraryUpdateService(
* lock. * lock.
*/ */
override fun onDestroy() { override fun onDestroy() {
subscription?.unsubscribe() scope?.cancel()
updateJob?.cancel()
if (wakeLock.isHeld) { if (wakeLock.isHeld) {
wakeLock.release() wakeLock.release()
} }
@ -183,34 +180,27 @@ class LibraryUpdateService(
?: return START_NOT_STICKY ?: return START_NOT_STICKY
// Unsubscribe from any previous subscription if needed. // Unsubscribe from any previous subscription if needed.
subscription?.unsubscribe() updateJob?.cancel()
// Update favorite manga. Destroy service when completed or in case of an error. // Update favorite manga. Destroy service when completed or in case of an error.
subscription = Observable val selectedScheme = preferences.libraryUpdatePrioritization().get()
.defer { val mangaList = getMangaToUpdate(intent, target)
val selectedScheme = preferences.libraryUpdatePrioritization().get() .sortedWith(rankingScheme[selectedScheme])
val mangaList = getMangaToUpdate(intent, target)
.sortedWith(rankingScheme[selectedScheme])
// Update either chapter list or manga details. updateJob = scope.launchIO {
try {
when (target) { when (target) {
Target.CHAPTERS -> updateChapterList(mangaList) Target.CHAPTERS -> updateChapterList(mangaList)
Target.COVERS -> updateCovers(mangaList) Target.COVERS -> updateCovers(mangaList)
Target.TRACKING -> updateTrackings(mangaList) Target.TRACKING -> updateTrackings(mangaList)
} }
} catch (e: Throwable) {
Timber.e(e)
stopSelf(startId)
} finally {
stopSelf(startId)
} }
.subscribeOn(Schedulers.io()) }
.subscribe(
{
},
{
Timber.e(it)
stopSelf(startId)
},
{
stopSelf(startId)
}
)
return START_REDELIVER_INTENT return START_REDELIVER_INTENT
} }
@ -253,7 +243,7 @@ class LibraryUpdateService(
* @param mangaToUpdate the list to update * @param mangaToUpdate the list to update
* @return an observable delivering the progress of each update. * @return an observable delivering the progress of each update.
*/ */
fun updateChapterList(mangaToUpdate: List<LibraryManga>): Observable<LibraryManga> { suspend fun updateChapterList(mangaToUpdate: List<LibraryManga>) {
// Initialize the variables holding the progress of the updates. // Initialize the variables holding the progress of the updates.
val count = AtomicInteger(0) val count = AtomicInteger(0)
// List containing new updates // List containing new updates
@ -263,67 +253,60 @@ class LibraryUpdateService(
// Boolean to determine if DownloadManager has downloads // Boolean to determine if DownloadManager has downloads
var hasDownloads = false var hasDownloads = false
// Emit each manga and update it sequentially. mangaToUpdate
return Observable.from(mangaToUpdate) .map { manga ->
// Notify manga that will update. // Notify manga that will update.
.doOnNext { notifier.showProgressNotification(it, count.andIncrement, mangaToUpdate.size) } notifier.showProgressNotification(manga, count.andIncrement, mangaToUpdate.size)
// Update the chapters of the manga
.concatMap { manga -> // Update the chapters of the manga
updateManga(manga) try {
val newChapters = updateManga(manga).first
Pair(manga, newChapters)
} catch (e: Throwable) {
// If there's any error, return empty update and continue. // If there's any error, return empty update and continue.
.onErrorReturn { val errorMessage = if (e is NoChaptersException) {
val errorMessage = if (it is NoChaptersException) { getString(R.string.no_chapters_error)
getString(R.string.no_chapters_error) } else {
} else { e.message
it.message
}
failedUpdates.add(Pair(manga, errorMessage))
Pair(emptyList(), emptyList())
}
// Filter out mangas without new chapters (or failed).
.filter { (first) -> first.isNotEmpty() }
.doOnNext {
if (manga.shouldDownloadNewChapters(db, preferences)) {
downloadChapters(manga, it.first)
hasDownloads = true
}
}
// Convert to the manga that contains new chapters.
.map {
Pair(
manga,
(
it.first.sortedByDescending { ch -> ch.source_order }
.toTypedArray()
)
)
} }
failedUpdates.add(Pair(manga, errorMessage))
Pair(manga, emptyList())
}
} }
// Add manga with new chapters to the list. // Filter out mangas without new chapters (or failed).
.doOnNext { manga -> .filter { (_, newChapters) -> newChapters.isNotEmpty() }
// Add to the list .forEach { (manga, newChapters) ->
newUpdates.add(manga) if (manga.shouldDownloadNewChapters(db, preferences)) {
} downloadChapters(manga, newChapters)
// Notify result of the overall update. hasDownloads = true
.doOnCompleted {
notifier.cancelProgressNotification()
if (newUpdates.isNotEmpty()) {
notifier.showUpdateNotifications(newUpdates)
if (hasDownloads) {
DownloadService.start(this)
}
} }
if (preferences.showLibraryUpdateErrors() && failedUpdates.isNotEmpty()) { // Convert to the manga that contains new chapters.
val errorFile = writeErrorFile(failedUpdates) newUpdates.add(
notifier.showUpdateErrorNotification( Pair(
failedUpdates.map { it.first.title }, manga,
errorFile.getUriCompat(this) newChapters.sortedByDescending { ch -> ch.source_order }.toTypedArray()
) )
} )
} }
.map { (first) -> first }
// Notify result of the overall update.
notifier.cancelProgressNotification()
if (newUpdates.isNotEmpty()) {
notifier.showUpdateNotifications(newUpdates)
if (hasDownloads) {
DownloadService.start(this)
}
}
if (preferences.showLibraryUpdateErrors() && failedUpdates.isNotEmpty()) {
val errorFile = writeErrorFile(failedUpdates)
notifier.showUpdateErrorNotification(
failedUpdates.map { it.first.title },
errorFile.getUriCompat(this)
)
}
} }
private fun downloadChapters(manga: Manga, chapters: List<Chapter>) { private fun downloadChapters(manga: Manga, chapters: List<Chapter>) {
@ -338,49 +321,38 @@ class LibraryUpdateService(
* @param manga the manga to update. * @param manga the manga to update.
* @return a pair of the inserted and removed chapters. * @return a pair of the inserted and removed chapters.
*/ */
fun updateManga(manga: Manga): Observable<Pair<List<Chapter>, List<Chapter>>> { suspend fun updateManga(manga: Manga): Pair<List<Chapter>, List<Chapter>> {
val source = sourceManager.getOrStub(manga.source) val source = sourceManager.getOrStub(manga.source)
// Update manga details metadata in the background // Update manga details metadata in the background
if (preferences.autoUpdateMetadata()) { if (preferences.autoUpdateMetadata()) {
runAsObservable({ val updatedManga = source.getMangaDetails(manga.toMangaInfo())
val updatedManga = source.getMangaDetails(manga.toMangaInfo()) val sManga = updatedManga.toSManga()
val sManga = updatedManga.toSManga() // Avoid "losing" existing cover
// Avoid "losing" existing cover if (!sManga.thumbnail_url.isNullOrEmpty()) {
if (!sManga.thumbnail_url.isNullOrEmpty()) { manga.prepUpdateCover(coverCache, sManga, false)
manga.prepUpdateCover(coverCache, sManga, false) } else {
} else { sManga.thumbnail_url = manga.thumbnail_url
sManga.thumbnail_url = manga.thumbnail_url }
}
manga.copyFrom(sManga) manga.copyFrom(sManga)
db.insertManga(manga).executeAsBlocking() db.insertManga(manga).executeAsBlocking()
manga
})
.onErrorResumeNext { Observable.just(manga) }
.subscribeOn(Schedulers.io())
.subscribe()
} }
return runAsObservable({ val chapters = source.getChapterList(manga.toMangaInfo())
source.getChapterList(manga.toMangaInfo()) .map { it.toSChapter() }
.map { it.toSChapter() }
}) return syncChaptersWithSource(db, chapters, manga, source)
.map { syncChaptersWithSource(db, it, manga, source) }
} }
private fun updateCovers(mangaToUpdate: List<LibraryManga>): Observable<LibraryManga> { private suspend fun updateCovers(mangaToUpdate: List<LibraryManga>) {
var count = 0 var count = 0
return Observable.from(mangaToUpdate) mangaToUpdate.forEach { manga ->
.doOnNext { notifier.showProgressNotification(manga, count++, mangaToUpdate.size)
notifier.showProgressNotification(it, count++, mangaToUpdate.size)
}
.flatMap { manga ->
val source = sourceManager.get(manga.source)
?: return@flatMap Observable.empty<LibraryManga>()
runAsObservable({ sourceManager.get(manga.source)?.let { source ->
try {
val networkManga = source.getMangaDetails(manga.toMangaInfo()) val networkManga = source.getMangaDetails(manga.toMangaInfo())
val sManga = networkManga.toSManga() val sManga = networkManga.toSManga()
manga.prepUpdateCover(coverCache, sManga, true) manga.prepUpdateCover(coverCache, sManga, true)
@ -388,49 +360,46 @@ class LibraryUpdateService(
manga.thumbnail_url = it manga.thumbnail_url = it
db.insertManga(manga).executeAsBlocking() db.insertManga(manga).executeAsBlocking()
} }
manga } catch (e: Throwable) {
}) // Ignore errors and continue
.onErrorReturn { manga } Timber.e(e)
} }
.doOnCompleted {
notifier.cancelProgressNotification()
} }
}
notifier.cancelProgressNotification()
} }
/** /**
* Method that updates the metadata of the connected tracking services. It's called in a * Method that updates the metadata of the connected tracking services. It's called in a
* background thread, so it's safe to do heavy operations or network calls here. * background thread, so it's safe to do heavy operations or network calls here.
*/ */
private fun updateTrackings(mangaToUpdate: List<LibraryManga>): Observable<LibraryManga> { private suspend fun updateTrackings(mangaToUpdate: List<LibraryManga>) {
// Initialize the variables holding the progress of the updates. // Initialize the variables holding the progress of the updates.
var count = 0 var count = 0
val loggedServices = trackManager.services.filter { it.isLogged } val loggedServices = trackManager.services.filter { it.isLogged }
// Emit each manga and update it sequentially. mangaToUpdate.forEach { manga ->
return Observable.from(mangaToUpdate)
// Notify manga that will update. // Notify manga that will update.
.doOnNext { notifier.showProgressNotification(it, count++, mangaToUpdate.size) } notifier.showProgressNotification(manga, count++, mangaToUpdate.size)
// Update the tracking details.
.concatMap { manga ->
val tracks = db.getTracks(manga).executeAsBlocking()
Observable.from(tracks) // Update the tracking details.
.concatMap { track -> db.getTracks(manga).executeAsBlocking().forEach { track ->
val service = trackManager.getService(track.sync_id) val service = trackManager.getService(track.sync_id)
if (service != null && service in loggedServices) { if (service != null && service in loggedServices) {
runAsObservable({ service.refresh(track) }) try {
.doOnNext { db.insertTrack(it).executeAsBlocking() } val updatedTrack = service.refresh(track)
.onErrorReturn { track } db.insertTrack(updatedTrack).executeAsBlocking()
} else { } catch (e: Throwable) {
Observable.empty() // Ignore errors and continue
} Timber.e(e)
} }
.map { manga } }
}
.doOnCompleted {
notifier.cancelProgressNotification()
} }
}
notifier.cancelProgressNotification()
} }
/** /**