Remove usage of RxJava from LibraryUpdateService

(cherry picked from commit 86b9d7e843c90c37f7e7374a20cbbcbf89caf10d)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt
This commit is contained in:
arkon 2021-01-23 11:20:16 -05:00 committed by Jobobby04
parent 355170b8ff
commit e3ee3159fc

View File

@ -32,7 +32,7 @@ import eu.kanade.tachiyomi.ui.library.LibraryGroup
import eu.kanade.tachiyomi.ui.manga.track.TrackItem import eu.kanade.tachiyomi.ui.manga.track.TrackItem
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
@ -47,14 +47,14 @@ import exh.source.getMainSource
import exh.source.mangaDexSourceIds import exh.source.mangaDexSourceIds
import exh.util.executeOnIO import exh.util.executeOnIO
import exh.util.nullIfBlank import exh.util.nullIfBlank
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
import java.io.File import java.io.File
import java.util.Date
import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicInteger
/** /**
@ -74,17 +74,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.
@ -177,6 +171,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)
@ -188,7 +183,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()
} }
@ -216,16 +212,15 @@ 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)
@ -235,19 +230,13 @@ class LibraryUpdateService(
Target.PUSH_FAVORITES -> pushFavorites() Target.PUSH_FAVORITES -> pushFavorites()
// SY <-- // SY <--
} }
} 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
} }
@ -332,7 +321,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
@ -342,74 +331,63 @@ 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) .mapNotNull { 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 ->
// SY --> // SY -->
if (manga.source in LIBRARY_UPDATE_EXCLUDED_SOURCES) { if (manga.source in LIBRARY_UPDATE_EXCLUDED_SOURCES) return@mapNotNull null
// Ignore EXH manga, updating chapters for every manga will get you banned // SY <--
Observable.empty() // Update the chapters of the manga
} else { try {
// SY <-- val newChapters = updateManga(manga).first
updateManga(manga) Pair(manga, newChapters)
// If there's any error, return empty update and continue. } catch (e: Throwable) {
.onErrorReturn { // If there's any error, return empty update and continue.
val errorMessage = if (it is NoChaptersException) { val errorMessage = if (e is NoChaptersException) {
getString(R.string.no_chapters_error) getString(R.string.no_chapters_error)
} else { } else {
it.message e.message
} }
failedUpdates.add(Pair(manga, errorMessage)) failedUpdates.add(Pair(manga, errorMessage))
Pair(emptyList(), emptyList()) Pair(manga, 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()
)
)
}
} }
// 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>) {
@ -429,71 +407,55 @@ 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).getMainSource() val source = sourceManager.getOrStub(manga.source).getMainSource()
// 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()
} }
// SY --> scope.launchIO {
if (source is MangaDex && trackManager.mdList.isLogged) { if (source is MangaDex && trackManager.mdList.isLogged) {
runAsObservable({
val tracks = db.getTracks(manga).executeOnIO() val tracks = db.getTracks(manga).executeOnIO()
if (tracks.isEmpty() || tracks.none { it.sync_id == TrackManager.MDLIST }) { if (tracks.isEmpty() || tracks.none { it.sync_id == TrackManager.MDLIST }) {
var track = trackManager.mdList.createInitialTracker(manga) var track = trackManager.mdList.createInitialTracker(manga)
track = trackManager.mdList.refresh(track) track = trackManager.mdList.refresh(track)
db.insertTrack(track).executeOnIO() db.insertTrack(track).executeOnIO()
} }
}) }
.onErrorResumeNext { Observable.just(Unit) } }
.subscribeOn(Schedulers.io())
.subscribe() // SY -->
if (source is MergedSource) {
return source.fetchChaptersAndSync(manga, false)
} }
// SY <-- // SY <--
return runAsObservable({ val chapters = source.getChapterList(manga.toMangaInfo())
// SY --> .map { it.toSChapter() }
if (source is MergedSource) {
source.fetchChaptersAndSync(manga, false) return syncChaptersWithSource(db, chapters, manga, source)
} else {
val sourceChapters = source.getChapterList(manga.toMangaInfo())
.map { it.toSChapter() }
syncChaptersWithSource(db, sourceChapters, manga, source)
}
})
// SY <--
} }
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)
@ -501,103 +463,96 @@ 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()
} }
// SY --> // SY -->
/** /**
* filter all follows from Mangadex and only add reading or rereading manga to library * filter all follows from Mangadex and only add reading or rereading manga to library
*/ */
private fun syncFollows(): Observable<LibraryManga> { private suspend fun syncFollows() {
val count = AtomicInteger(0) val count = AtomicInteger(0)
val mangaDex = MdUtil.getEnabledMangaDex(preferences, sourceManager) ?: return Observable.empty() val mangaDex = MdUtil.getEnabledMangaDex(preferences, sourceManager) ?: return
return runAsObservable({ mangaDex.fetchAllFollows(true) })
.map { listManga ->
listManga.filter { (_, metadata) ->
metadata.follow_status == FollowStatus.RE_READING.int || metadata.follow_status == FollowStatus.READING.int
}
}
.doOnNext { listManga ->
listManga.forEach { (networkManga, metadata) ->
notifier.showProgressNotification(networkManga, count.andIncrement, listManga.size)
var dbManga = db.getManga(networkManga.url, mangaDex.id)
.executeAsBlocking()
if (dbManga == null) {
dbManga = Manga.create(
networkManga.url,
networkManga.title,
mangaDex.id
)
dbManga.date_added = Date().time
}
dbManga.copyFrom(networkManga) val mangadexFollows = mangaDex.fetchAllFollows(true)
dbManga.favorite = true .filter { (_, metadata) ->
val id = db.insertManga(dbManga).executeAsBlocking().insertedId() metadata.follow_status == FollowStatus.RE_READING.int || metadata.follow_status == FollowStatus.READING.int
if (id != null) {
metadata.mangaId = id
db.insertFlatMetadata(metadata.flatten()).await()
}
}
} }
.doOnCompleted {
notifier.cancelProgressNotification() mangadexFollows.forEach { (networkManga, metadata) ->
notifier.showProgressNotification(networkManga, count.andIncrement, mangadexFollows.size)
var dbManga = db.getManga(networkManga.url, mangaDex.id)
.executeOnIO()
if (dbManga == null) {
dbManga = Manga.create(
networkManga.url,
networkManga.title,
mangaDex.id
)
dbManga.date_added = System.currentTimeMillis()
} }
.flatMap { Observable.empty() }
dbManga.copyFrom(networkManga)
dbManga.favorite = true
val id = db.insertManga(dbManga).executeOnIO().insertedId()
if (id != null) {
metadata.mangaId = id
db.insertFlatMetadata(metadata.flatten()).await()
}
}
notifier.cancelProgressNotification()
} }
/** /**
* Method that updates the all mangas which are not tracked as "reading" on mangadex * Method that updates the all mangas which are not tracked as "reading" on mangadex
*/ */
private fun pushFavorites(): Observable<LibraryManga> { private suspend fun pushFavorites() {
val count = AtomicInteger(0) val count = AtomicInteger(0)
val listManga = db.getFavoriteMangas().executeAsBlocking().filter { it.source in mangaDexSourceIds } val listManga = db.getFavoriteMangas().executeAsBlocking().filter { it.source in mangaDexSourceIds }
// filter all follows from Mangadex and only add reading or rereading manga to library // filter all follows from Mangadex and only add reading or rereading manga to library
return Observable.from(if (trackManager.mdList.isLogged) listManga else emptyList()) if (trackManager.mdList.isLogged) {
.flatMap { manga -> listManga.forEach { manga ->
notifier.showProgressNotification(manga, count.andIncrement, listManga.size) notifier.showProgressNotification(manga, count.andIncrement, listManga.size)
// Get this manga's trackers from the database // Get this manga's trackers from the database
@ -608,18 +563,13 @@ class LibraryUpdateService(
if (tracker.track?.status == FollowStatus.UNFOLLOWED.int) { if (tracker.track?.status == FollowStatus.UNFOLLOWED.int) {
tracker.track.status = FollowStatus.READING.int tracker.track.status = FollowStatus.READING.int
runAsObservable({ tracker.service.update(tracker.track) }) val updatedTrack = tracker.service.update(tracker.track)
} else Observable.just(null) db.insertTrack(updatedTrack).executeOnIO()
}
.doOnNext { returnedTracker ->
returnedTracker?.let {
db.insertTrack(returnedTracker)
} }
} }
.doOnCompleted { }
notifier.cancelProgressNotification()
} notifier.cancelProgressNotification()
.flatMap { Observable.empty() }
} }
// SY <-- // SY <--