Fixing crashes in migration + rx bridge from upstream

Co-Authored-By: arkon <4098258+arkon@users.noreply.github.com>
This commit is contained in:
Jays2Kings 2021-04-10 19:36:46 -04:00
parent 0a3d6bd8f5
commit c724a9e022
5 changed files with 104 additions and 19 deletions

View File

@ -16,6 +16,7 @@ import eu.kanade.tachiyomi.data.database.models.Category
import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.LibraryManga import eu.kanade.tachiyomi.data.database.models.LibraryManga
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.toMangaInfo
import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.download.DownloadService import eu.kanade.tachiyomi.data.download.DownloadService
import eu.kanade.tachiyomi.data.image.coil.MangaFetcher import eu.kanade.tachiyomi.data.image.coil.MangaFetcher
@ -28,6 +29,7 @@ import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.model.toMangaInfo import eu.kanade.tachiyomi.source.model.toMangaInfo
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.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
@ -396,8 +398,8 @@ class LibraryUpdateService(
notifier.showProgressNotification(manga, progress, mangaToUpdate.size) notifier.showProgressNotification(manga, progress, mangaToUpdate.size)
val source = sourceManager.get(manga.source) as? HttpSource ?: return false val source = sourceManager.get(manga.source) as? HttpSource ?: return false
val fetchedChapters = withContext(Dispatchers.IO) { val fetchedChapters = withContext(Dispatchers.IO) {
source.fetchChapterList(manga).toBlocking().single() source.getChapterList(manga.toMangaInfo()).map { it.toSChapter() }
} ?: emptyList() }
if (fetchedChapters.isNotEmpty()) { if (fetchedChapters.isNotEmpty()) {
val newChapters = syncChaptersWithSource(db, fetchedChapters, manga, source) val newChapters = syncChaptersWithSource(db, fetchedChapters, manga, source)
if (newChapters.first.isNotEmpty()) { if (newChapters.first.isNotEmpty()) {

View File

@ -5,12 +5,15 @@ import com.google.gson.Gson
import com.google.gson.GsonBuilder import com.google.gson.GsonBuilder
import com.google.gson.JsonObject import com.google.gson.JsonObject
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.toMangaInfo
import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.model.toMangaInfo
import eu.kanade.tachiyomi.source.model.toSChapter
import eu.kanade.tachiyomi.util.chapter.ChapterRecognition import eu.kanade.tachiyomi.util.chapter.ChapterRecognition
import eu.kanade.tachiyomi.util.lang.compareToCaseInsensitiveNaturalOrder import eu.kanade.tachiyomi.util.lang.compareToCaseInsensitiveNaturalOrder
import eu.kanade.tachiyomi.util.storage.DiskUtil import eu.kanade.tachiyomi.util.storage.DiskUtil
@ -18,6 +21,7 @@ import eu.kanade.tachiyomi.util.storage.EpubFile
import eu.kanade.tachiyomi.util.system.ImageUtil import eu.kanade.tachiyomi.util.system.ImageUtil
import junrar.Archive import junrar.Archive
import junrar.rarfile.FileHeader import junrar.rarfile.FileHeader
import kotlinx.coroutines.runBlocking
import rx.Observable import rx.Observable
import timber.log.Timber import timber.log.Timber
import java.io.File import java.io.File
@ -124,7 +128,7 @@ class LocalSource(private val context: Context) : CatalogueSource {
// Copy the cover from the first chapter found. // Copy the cover from the first chapter found.
if (thumbnail_url == null) { if (thumbnail_url == null) {
val chapters = fetchChapterList(this).toBlocking().first() val chapters = runBlocking { getChapterList(toMangaInfo()).map { it.toSChapter() } }
if (chapters.isNotEmpty()) { if (chapters.isNotEmpty()) {
try { try {
val dest = updateCover(chapters.last(), this) val dest = updateCover(chapters.last(), this)
@ -172,7 +176,7 @@ class LocalSource(private val context: Context) : CatalogueSource {
// Copy the cover from the first chapter found. // Copy the cover from the first chapter found.
if (manga.thumbnail_url == null) { if (manga.thumbnail_url == null) {
val chapters = fetchChapterList(manga).toBlocking().first() val chapters = runBlocking { getChapterList(manga.toMangaInfo()).map { it.toSChapter() } }
if (chapters.isNotEmpty()) { if (chapters.isNotEmpty()) {
try { try {
val dest = updateCover(chapters.last(), manga) val dest = updateCover(chapters.last(), manga)
@ -186,7 +190,7 @@ class LocalSource(private val context: Context) : CatalogueSource {
} }
fun updateMangaInfo(manga: SManga) { fun updateMangaInfo(manga: SManga) {
val directory = getBaseDirectories(context).mapNotNull { File(it, manga.url) }.find { val directory = getBaseDirectories(context).map { File(it, manga.url) }.find {
it.exists() it.exists()
} ?: return } ?: return
val gson = GsonBuilder().setPrettyPrinting().create() val gson = GsonBuilder().setPrettyPrinting().create()

View File

@ -10,6 +10,7 @@ import eu.kanade.tachiyomi.source.model.toMangaInfo
import eu.kanade.tachiyomi.source.model.toPageUrl import eu.kanade.tachiyomi.source.model.toPageUrl
import eu.kanade.tachiyomi.source.model.toSChapter 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.system.awaitSingle
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import rx.Observable import rx.Observable
@ -65,12 +66,10 @@ interface Source : tachiyomi.source.Source {
*/ */
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
override suspend fun getMangaDetails(manga: MangaInfo): MangaInfo { override suspend fun getMangaDetails(manga: MangaInfo): MangaInfo {
return withContext(Dispatchers.IO) {
val sManga = manga.toSManga() val sManga = manga.toSManga()
val networkManga = fetchMangaDetails(sManga).toBlocking().single() val networkManga = fetchMangaDetails(sManga).awaitSingle()
sManga.copyFrom(networkManga) sManga.copyFrom(networkManga)
sManga.toMangaInfo() return sManga.toMangaInfo()
}
} }
/** /**
@ -78,9 +77,7 @@ interface Source : tachiyomi.source.Source {
*/ */
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
override suspend fun getChapterList(manga: MangaInfo): List<ChapterInfo> { override suspend fun getChapterList(manga: MangaInfo): List<ChapterInfo> {
return withContext(Dispatchers.IO) { return fetchChapterList(manga.toSManga()).awaitSingle().map { it.toChapterInfo() }
fetchChapterList(manga.toSManga()).toBlocking().single().map { it.toChapterInfo() }
}
} }
/** /**
@ -88,10 +85,7 @@ interface Source : tachiyomi.source.Source {
*/ */
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
override suspend fun getPageList(chapter: ChapterInfo): List<tachiyomi.source.model.Page> { override suspend fun getPageList(chapter: ChapterInfo): List<tachiyomi.source.model.Page> {
return withContext(Dispatchers.IO) { return fetchPageList(chapter.toSChapter()).awaitSingle().map { it.toPageUrl() }
fetchPageList(chapter.toSChapter()).toBlocking().single()
.map { it.toPageUrl() }
}
} }
} }

View File

@ -356,8 +356,8 @@ class MigrationListController(bundle: Bundle? = null) :
launchUI { launchUI {
val result = CoroutineScope(migratingManga.manga.migrationJob).async { val result = CoroutineScope(migratingManga.manga.migrationJob).async {
val localManga = smartSearchEngine.networkToLocalManga(manga, source.id) val localManga = smartSearchEngine.networkToLocalManga(manga, source.id)
val chapters = source.getChapterList(localManga.toMangaInfo()).map { it.toSChapter() }
try { try {
val chapters = source.getChapterList(localManga.toMangaInfo()).map { it.toSChapter() }
syncChaptersWithSource(db, chapters, localManga, source) syncChaptersWithSource(db, chapters, localManga, source)
} catch (e: Exception) { } catch (e: Exception) {
return@async null return@async null

View File

@ -0,0 +1,85 @@
package eu.kanade.tachiyomi.util.system
import kotlinx.coroutines.CancellableContinuation
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine
import rx.Emitter
import rx.Observable
import rx.Subscriber
import rx.Subscription
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
/*
* Util functions for bridging RxJava and coroutines. Taken from TachiyomiEH/SY.
*/
suspend fun <T> Observable<T>.awaitSingle(): T = single().awaitOne()
private suspend fun <T> Observable<T>.awaitOne(): T = suspendCancellableCoroutine { cont ->
cont.unsubscribeOnCancellation(
subscribe(
object : Subscriber<T>() {
override fun onStart() {
request(1)
}
override fun onNext(t: T) {
cont.resume(t)
}
override fun onCompleted() {
if (cont.isActive) cont.resumeWithException(
IllegalStateException(
"Should have invoked onNext"
)
)
}
override fun onError(e: Throwable) {
/*
* Rx1 observable throws NoSuchElementException if cancellation happened before
* element emission. To mitigate this we try to atomically resume continuation with exception:
* if resume failed, then we know that continuation successfully cancelled itself
*/
val token = cont.tryResumeWithException(e)
if (token != null) {
cont.completeResume(token)
}
}
}
)
)
}
internal fun <T> CancellableContinuation<T>.unsubscribeOnCancellation(sub: Subscription) =
invokeOnCancellation { sub.unsubscribe() }
fun <T> runAsObservable(
block: suspend () -> T,
backpressureMode: Emitter.BackpressureMode = Emitter.BackpressureMode.NONE
): Observable<T> {
return Observable.create(
{ emitter ->
val job = GlobalScope.launch(Dispatchers.Unconfined, start = CoroutineStart.ATOMIC) {
try {
emitter.onNext(block())
emitter.onCompleted()
} catch (e: Throwable) {
// Ignore `CancellationException` as error, since it indicates "normal cancellation"
if (e !is CancellationException) {
emitter.onError(e)
} else {
emitter.onCompleted()
}
}
}
emitter.setCancellation { job.cancel() }
},
backpressureMode
)
}