(cherry picked from commit f0eb42e72d1e267049777a303bd97d96517a9a1f) # Conflicts: # app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupRestore.kt # app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/Backup.kt # app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupManga.kt # app/src/main/java/eu/kanade/tachiyomi/data/backup/legacy/models/Backup.kt # app/src/main/java/eu/kanade/tachiyomi/extension/model/Extension.kt # app/src/main/java/eu/kanade/tachiyomi/source/LocalSource.kt # app/src/main/java/eu/kanade/tachiyomi/ui/base/changehandler/OneWayFadeChangeHandler.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SearchController.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SearchPresenter.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/sources/SelectionHeader.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourceItem.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourcePresenter.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/SourceFilterSheet.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/GlobalSearchController.kt # app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt # app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryHolder.kt # app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryListHolder.kt # app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt # app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt # app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPageSheet.kt # app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ChapterLoader.kt # app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/HttpPageLoader.kt # app/src/main/java/eu/kanade/tachiyomi/ui/reader/model/ReaderPage.kt # app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerPageHolder.kt # app/src/main/java/eu/kanade/tachiyomi/util/view/ViewExtensions.kt
92 lines
3.0 KiB
Kotlin
92 lines
3.0 KiB
Kotlin
package exh.util
|
|
|
|
import kotlinx.coroutines.suspendCancellableCoroutine
|
|
import okhttp3.Call
|
|
import okhttp3.Callback
|
|
import okhttp3.Response
|
|
import okhttp3.internal.closeQuietly
|
|
import rx.Observable
|
|
import rx.Producer
|
|
import rx.Subscription
|
|
import java.io.IOException
|
|
import java.util.concurrent.atomic.AtomicBoolean
|
|
import kotlin.coroutines.resume
|
|
import kotlin.coroutines.resumeWithException
|
|
|
|
fun Call.asObservableWithAsyncStacktrace(): Observable<Pair<Exception, Response>> {
|
|
// Record stacktrace at creation time for easier debugging
|
|
// asObservable is involved in a lot of crashes so this is worth the performance hit
|
|
val asyncStackTrace = Exception("Async stacktrace")
|
|
|
|
return Observable.unsafeCreate { subscriber ->
|
|
// Since Call is a one-shot type, clone it for each new subscriber.
|
|
val call = clone()
|
|
|
|
// Wrap the call in a helper which handles both unsubscription and backpressure.
|
|
val requestArbiter = object : AtomicBoolean(), Producer, Subscription {
|
|
val executed = AtomicBoolean(false)
|
|
|
|
override fun request(n: Long) {
|
|
if (n == 0L || !compareAndSet(false, true)) return
|
|
|
|
try {
|
|
val response = call.execute()
|
|
executed.set(true)
|
|
if (!subscriber.isUnsubscribed) {
|
|
subscriber.onNext(asyncStackTrace to response)
|
|
subscriber.onCompleted()
|
|
}
|
|
} catch (error: Throwable) {
|
|
if (!subscriber.isUnsubscribed) {
|
|
subscriber.onError(error.withRootCause(asyncStackTrace))
|
|
}
|
|
}
|
|
}
|
|
|
|
override fun unsubscribe() {
|
|
if (!executed.get()) {
|
|
call.cancel()
|
|
}
|
|
}
|
|
|
|
override fun isUnsubscribed(): Boolean {
|
|
return call.isCanceled()
|
|
}
|
|
}
|
|
|
|
subscriber.add(requestArbiter)
|
|
subscriber.setProducer(requestArbiter)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Similar to [Call.await] but it doesn't throw when the response is not successful
|
|
*/
|
|
suspend fun Call.awaitResponse(): Response {
|
|
return suspendCancellableCoroutine { continuation ->
|
|
enqueue(
|
|
object : Callback {
|
|
override fun onResponse(call: Call, response: Response) {
|
|
continuation.resume(response) {
|
|
response.closeQuietly()
|
|
}
|
|
}
|
|
|
|
override fun onFailure(call: Call, e: IOException) {
|
|
// Don't bother with resuming the continuation if it is already cancelled.
|
|
if (continuation.isCancelled) return
|
|
continuation.resumeWithException(e)
|
|
}
|
|
},
|
|
)
|
|
|
|
continuation.invokeOnCancellation {
|
|
try {
|
|
cancel()
|
|
} catch (ex: Throwable) {
|
|
// Ignore cancel exception
|
|
}
|
|
}
|
|
}
|
|
}
|