mirror of
https://github.com/tachiyomiorg/tachiyomi.git
synced 2024-11-14 04:15:09 +01:00
Fixing crashes in migration + rx bridge from upstream
Co-Authored-By: arkon <4098258+arkon@users.noreply.github.com>
This commit is contained in:
parent
0a3d6bd8f5
commit
c724a9e022
@ -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()) {
|
||||||
|
@ -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()
|
||||||
|
@ -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() }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user