diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/all/MangaDex.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/all/MangaDex.kt index b39d18316..83c4ea6f7 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/source/online/all/MangaDex.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/online/all/MangaDex.kt @@ -43,6 +43,7 @@ import exh.md.utils.MdUtil import exh.metadata.metadata.MangaDexSearchMetadata import exh.source.DelegatedHttpSource import exh.ui.metadata.adapters.MangaDexDescriptionAdapter +import exh.util.asObservable import exh.util.urlImportFetchSearchManga import exh.widget.preference.MangadexLoginDialog import kotlinx.coroutines.Dispatchers @@ -266,20 +267,22 @@ class MangaDex(delegate: HttpSource, val context: Context) : private fun importIdToMdId(query: String, fail: () -> Observable): Observable = when { query.toIntOrNull() != null -> { - Observable.fromCallable { - // MdUtil. - val res = GalleryAdder().addGallery(context, MdUtil.baseUrl + MdUtil.mapMdIdToMangaUrl(query.toInt()), false, this) - MangasPage( - ( - if (res is GalleryAddEvent.Success) { - listOf(res.manga) - } else { - emptyList() - } - ), - false - ) + flow { + emit(GalleryAdder().addGallery(context, MdUtil.baseUrl + MdUtil.mapMdIdToMangaUrl(query.toInt()), false, this@MangaDex)) } + .asObservable() + .map { res -> + MangasPage( + ( + if (res is GalleryAddEvent.Success) { + listOf(res.manga) + } else { + emptyList() + } + ), + false + ) + } } else -> fail() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ChapterLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ChapterLoader.kt index 3a17b88f5..55dc3cdd5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ChapterLoader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ChapterLoader.kt @@ -91,9 +91,9 @@ class ChapterLoader( return when { // SY --> source is MergedSource -> { - val mangaReference = mergedReferences.firstOrNull { it.mangaId == chapter.chapter.manga_id } ?: throw Exception("Merge reference null") - val source = sourceManager.get(mangaReference.mangaSourceId) ?: throw Exception("Source ${mangaReference.mangaSourceId} was null") - val manga = mergedManga.firstOrNull { it.id == chapter.chapter.manga_id } ?: throw Exception("Manga for merged chapter was null") + val mangaReference = mergedReferences.firstOrNull { it.mangaId == chapter.chapter.manga_id } ?: error("Merge reference null") + val source = sourceManager.get(mangaReference.mangaSourceId) ?: error("Source ${mangaReference.mangaSourceId} was null") + val manga = mergedManga.firstOrNull { it.id == chapter.chapter.manga_id } ?: error("Manga for merged chapter was null") val isMergedMangaDownloaded = downloadManager.isChapterDownloaded(chapter.chapter, manga, true) when { isMergedMangaDownloaded -> DownloadPageLoader(chapter, manga, source, downloadManager) diff --git a/app/src/main/java/exh/GalleryAdder.kt b/app/src/main/java/exh/GalleryAdder.kt index e9cb10082..4e66153a6 100755 --- a/app/src/main/java/exh/GalleryAdder.kt +++ b/app/src/main/java/exh/GalleryAdder.kt @@ -11,6 +11,8 @@ import eu.kanade.tachiyomi.source.online.UrlImportableSource import eu.kanade.tachiyomi.source.online.all.EHentai import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource import exh.source.getMainSource +import exh.util.await +import exh.util.awaitSingle import uy.kohesive.injekt.injectLazy import java.util.Date @@ -34,7 +36,7 @@ class GalleryAdder { } } - fun addGallery( + suspend fun addGallery( context: Context, url: String, fav: Boolean = false, @@ -84,7 +86,7 @@ class GalleryAdder { } ?: return GalleryAddEvent.Fail.UnknownType(url, context) // Use manga in DB if possible, otherwise, make a new manga - val manga = db.getManga(cleanedUrl, source.id).executeAsBlocking() + val manga = db.getManga(cleanedUrl, source.id).await() ?: Manga.create(source.id).apply { this.url = cleanedUrl title = realUrl @@ -93,13 +95,13 @@ class GalleryAdder { // Insert created manga if not in DB before fetching details // This allows us to keep the metadata when fetching details if (manga.id == null) { - db.insertManga(manga).executeAsBlocking().insertedId()?.let { + db.insertManga(manga).await().insertedId()?.let { manga.id = it } } // Fetch and copy details - val newManga = source.fetchMangaDetails(manga).toBlocking().first() + val newManga = source.fetchMangaDetails(manga).awaitSingle() manga.copyFrom(newManga) manga.initialized = true @@ -108,7 +110,7 @@ class GalleryAdder { manga.date_added = Date().time } - db.insertManga(manga).executeAsBlocking() + db.insertManga(manga).await() // Fetch and copy chapters try { @@ -119,7 +121,7 @@ class GalleryAdder { } chapterListObs.map { syncChaptersWithSource(db, it, manga, source) - }.toBlocking().first() + }.awaitSingle() } catch (e: Exception) { XLog.w(context.getString(R.string.gallery_adder_chapter_fetch_error, manga.title), e) return GalleryAddEvent.Fail.Error(url, context.getString(R.string.gallery_adder_chapter_fetch_error, url)) diff --git a/app/src/main/java/exh/favorites/FavoritesSyncHelper.kt b/app/src/main/java/exh/favorites/FavoritesSyncHelper.kt index 4b4ed69e9..090d3db8c 100644 --- a/app/src/main/java/exh/favorites/FavoritesSyncHelper.kt +++ b/app/src/main/java/exh/favorites/FavoritesSyncHelper.kt @@ -10,6 +10,7 @@ import eu.kanade.tachiyomi.data.database.models.Category import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.MangaCategory import eu.kanade.tachiyomi.data.preference.PreferencesHelper +import eu.kanade.tachiyomi.network.await import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.online.all.EHentai import eu.kanade.tachiyomi.util.lang.launchUI @@ -21,22 +22,28 @@ import exh.GalleryAddEvent import exh.GalleryAdder import exh.eh.EHentaiThrottleManager import exh.eh.EHentaiUpdateWorker +import exh.util.await import exh.util.ignore import exh.util.trans import exh.util.wifiManager +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch import okhttp3.FormBody import okhttp3.Request import rx.subjects.BehaviorSubject import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import uy.kohesive.injekt.injectLazy -import kotlin.concurrent.thread class FavoritesSyncHelper(val context: Context) { private val db: DatabaseHelper by injectLazy() private val prefs: PreferencesHelper by injectLazy() + private val scope = CoroutineScope(Job() + Dispatchers.Main) + private val exh by lazy { Injekt.get().get(EXH_SOURCE_ID) as? EHentai ?: EHentai(0, true, context) @@ -53,7 +60,7 @@ class FavoritesSyncHelper(val context: Context) { private val logger = XLog.tag("EHFavSync").build() - val status: BehaviorSubject = BehaviorSubject.create(FavoritesSyncStatus.Idle(context)) + val status: BehaviorSubject = BehaviorSubject.create(FavoritesSyncStatus.Idle(context)) @Synchronized fun runSync() { @@ -63,10 +70,10 @@ class FavoritesSyncHelper(val context: Context) { status.onNext(FavoritesSyncStatus.Initializing(context)) - thread { beginSync() } + scope.launch(Dispatchers.IO) { beginSync() } } - private fun beginSync() { + private suspend fun beginSync() { // Check if logged in if (!prefs.enableExhentai().get()) { status.onNext(FavoritesSyncStatus.Error(context.getString(R.string.please_login))) @@ -75,13 +82,13 @@ class FavoritesSyncHelper(val context: Context) { // Validate library state status.onNext(FavoritesSyncStatus.Processing(context.getString(R.string.favorites_sync_verifying_library), context = context)) - val libraryManga = db.getLibraryMangas().executeAsBlocking() + val libraryManga = db.getLibraryMangas().await() val seenManga = HashSet(libraryManga.size) libraryManga.forEach { if (it.source != EXH_SOURCE_ID && it.source != EH_SOURCE_ID) return@forEach if (it.id in seenManga) { - val inCategories = db.getCategoriesForManga(it).executeAsBlocking() + val inCategories = db.getCategoriesForManga(it).await() status.onNext( FavoritesSyncStatus.BadLibraryState .MangaInMultipleCategories(it, inCategories, context) @@ -153,9 +160,8 @@ class FavoritesSyncHelper(val context: Context) { } } - val theContext = context launchUI { - theContext.toast(context.getString(R.string.favorites_sync_complete)) + context.toast(context.getString(R.string.favorites_sync_complete)) } } catch (e: IgnoredException) { // Do not display error as this error has already been reported @@ -187,8 +193,8 @@ class FavoritesSyncHelper(val context: Context) { } } - private fun applyRemoteCategories(categories: List) { - val localCategories = db.getCategories().executeAsBlocking() + private suspend fun applyRemoteCategories(categories: List) { + val localCategories = db.getCategories().await() val newLocalCategories = localCategories.toMutableList() @@ -226,11 +232,11 @@ class FavoritesSyncHelper(val context: Context) { // Only insert categories if changed if (changed) { - db.insertCategories(newLocalCategories).executeAsBlocking() + db.insertCategories(newLocalCategories).await() } } - private fun addGalleryRemote(errorList: MutableList, gallery: FavoriteEntry) { + private suspend fun addGalleryRemote(errorList: MutableList, gallery: FavoriteEntry) { val url = "${exh.baseUrl}/gallerypopups.php?gid=${gallery.gid}&t=${gallery.token}&act=addfav" val request = Request.Builder() @@ -257,12 +263,12 @@ class FavoritesSyncHelper(val context: Context) { } } - private fun explicitlyRetryExhRequest(retryCount: Int, request: Request): Boolean { + private suspend fun explicitlyRetryExhRequest(retryCount: Int, request: Request): Boolean { var success = false for (i in 1..retryCount) { try { - val resp = exh.client.newCall(request).execute() + val resp = exh.client.newCall(request).await() if (resp.isSuccessful) { success = true @@ -276,7 +282,7 @@ class FavoritesSyncHelper(val context: Context) { return success } - private fun applyChangeSetToRemote(errorList: MutableList, changeSet: ChangeSet) { + private suspend fun applyChangeSetToRemote(errorList: MutableList, changeSet: ChangeSet) { // Apply removals if (changeSet.removed.isNotEmpty()) { status.onNext(FavoritesSyncStatus.Processing(context.getString(R.string.favorites_sync_removing_galleries, changeSet.removed.size), context = context)) @@ -324,7 +330,7 @@ class FavoritesSyncHelper(val context: Context) { } } - private fun applyChangeSetToLocal(errorList: MutableList, changeSet: ChangeSet) { + private suspend fun applyChangeSetToLocal(errorList: MutableList, changeSet: ChangeSet) { val removedManga = mutableListOf() // Apply removals @@ -337,12 +343,12 @@ class FavoritesSyncHelper(val context: Context) { db.getManga(url, EXH_SOURCE_ID), db.getManga(url, EH_SOURCE_ID) ).forEach { - val manga = it.executeAsBlocking() + val manga = it.await() if (manga?.favorite == true) { manga.favorite = false manga.date_added = 0 - db.updateMangaFavorite(manga).executeAsBlocking() + db.updateMangaFavorite(manga).await() removedManga += manga } } @@ -350,11 +356,11 @@ class FavoritesSyncHelper(val context: Context) { // Can't do too many DB OPs in one go removedManga.chunked(10).forEach { - db.deleteOldMangasCategories(it).executeAsBlocking() + db.deleteOldMangasCategories(it).await() } val insertedMangaCategories = mutableListOf>() - val categories = db.getCategories().executeAsBlocking() + val categories = db.getCategories().await() // Apply additions throttleManager.resetThrottle() diff --git a/app/src/main/java/exh/uconfig/ConfiguringDialogController.kt b/app/src/main/java/exh/uconfig/ConfiguringDialogController.kt index fd34042a2..dff8663fc 100644 --- a/app/src/main/java/exh/uconfig/ConfiguringDialogController.kt +++ b/app/src/main/java/exh/uconfig/ConfiguringDialogController.kt @@ -9,22 +9,27 @@ import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.ui.base.controller.DialogController import eu.kanade.tachiyomi.util.lang.launchUI import eu.kanade.tachiyomi.util.system.toast -import kotlin.concurrent.thread +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext class ConfiguringDialogController : DialogController() { private var materialDialog: MaterialDialog? = null + val scope = CoroutineScope(Job() + Dispatchers.Main) override fun onCreateDialog(savedViewState: Bundle?): Dialog { if (savedViewState == null) { - thread { + scope.launch(Dispatchers.IO) { try { EHConfigurator(activity!!).configureAll() launchUI { activity?.toast(activity?.getString(R.string.eh_settings_successfully_uploaded)) } } catch (e: Exception) { - activity?.let { - it.runOnUiThread { + withContext(Dispatchers.Main) { + activity?.let { MaterialDialog(it) .title(R.string.eh_settings_configuration_failed) .message(text = it.getString(R.string.eh_settings_configuration_failed_message, e.message)) diff --git a/app/src/main/java/exh/uconfig/EHConfigurator.kt b/app/src/main/java/exh/uconfig/EHConfigurator.kt index ba1c8e878..a1f08fcf0 100644 --- a/app/src/main/java/exh/uconfig/EHConfigurator.kt +++ b/app/src/main/java/exh/uconfig/EHConfigurator.kt @@ -4,6 +4,7 @@ import android.content.Context import com.elvishew.xlog.XLog import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.preference.PreferencesHelper +import eu.kanade.tachiyomi.network.await import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.online.all.EHentai import eu.kanade.tachiyomi.util.asJsoup @@ -26,7 +27,7 @@ class EHConfigurator(val context: Context) { private fun EHentai.requestWithCreds(sp: Int = 1) = Request.Builder() .addHeader("Cookie", cookiesHeader(sp)) - private fun EHentai.execProfileActions( + private suspend fun EHentai.execProfileActions( action: String, name: String, set: String, @@ -44,11 +45,11 @@ class EHConfigurator(val context: Context) { ) .build() ) - .execute() + .await() private val EHentai.uconfigUrl get() = baseUrl + UCONFIG_URL - fun configureAll() { + suspend fun configureAll() { val ehSource = sources.get(EH_SOURCE_ID) as EHentai val exhSource = sources.get(EXH_SOURCE_ID) as EHentai @@ -58,7 +59,7 @@ class EHConfigurator(val context: Context) { .url(HATH_PERKS_URL) .build() ) - .execute().asJsoup() + .await().asJsoup() val hathPerks = EHHathPerksResponse() @@ -85,10 +86,10 @@ class EHConfigurator(val context: Context) { configure(exhSource, hathPerks) } - private fun configure(source: EHentai, hathPerks: EHHathPerksResponse) { + private suspend fun configure(source: EHentai, hathPerks: EHHathPerksResponse) { // Delete old app profiles val scanReq = source.requestWithCreds().url(source.uconfigUrl).build() - val resp = configuratorClient.newCall(scanReq).execute().asJsoup() + val resp = configuratorClient.newCall(scanReq).await().asJsoup() var lastDoc = resp resp.select(PROFILE_SELECTOR).forEach { if (it.text() == PROFILE_NAME) { @@ -127,7 +128,7 @@ class EHConfigurator(val context: Context) { .url(source.uconfigUrl) .post(form) .build() - ).execute() + ).await() // Persist slot + sk source.spPref().set(slot) diff --git a/app/src/main/java/exh/ui/batchadd/BatchAddPresenter.kt b/app/src/main/java/exh/ui/batchadd/BatchAddPresenter.kt index 43dee8bd0..c3f59c8e2 100644 --- a/app/src/main/java/exh/ui/batchadd/BatchAddPresenter.kt +++ b/app/src/main/java/exh/ui/batchadd/BatchAddPresenter.kt @@ -1,6 +1,7 @@ package exh.ui.batchadd import android.content.Context +import com.elvishew.xlog.XLog import com.jakewharton.rxrelay.BehaviorRelay import com.jakewharton.rxrelay.ReplayRelay import eu.kanade.tachiyomi.R @@ -9,13 +10,20 @@ import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter import exh.GalleryAddEvent import exh.GalleryAdder import exh.util.trimOrNull +import kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.ensureActive +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get -import kotlin.concurrent.thread class BatchAddPresenter : BasePresenter() { private val galleryAdder by lazy { GalleryAdder() } + private val scope = CoroutineScope(Job() + Dispatchers.Main) val progressTotalRelay = BehaviorRelay.create(0)!! val progressRelay = BehaviorRelay.create(0)!! @@ -50,12 +58,17 @@ class BatchAddPresenter : BasePresenter() { currentlyAddingRelay.call(STATE_INPUT_TO_PROGRESS) - thread { + val handler = CoroutineExceptionHandler { _, throwable -> + XLog.e(throwable) + } + + scope.launch(Dispatchers.IO + handler) { val succeeded = mutableListOf() val failed = mutableListOf() splitGalleries.forEachIndexed { i, s -> - val result = galleryAdder.addGallery(context, s, true) + ensureActive() + val result = withContext(Dispatchers.IO) { galleryAdder.addGallery(context, s, true) } if (result is GalleryAddEvent.Success) { succeeded.add(s) } else { diff --git a/app/src/main/java/exh/ui/captcha/BrowserActionActivity.kt b/app/src/main/java/exh/ui/captcha/BrowserActionActivity.kt index 620a2ed85..57e80d160 100644 --- a/app/src/main/java/exh/ui/captcha/BrowserActionActivity.kt +++ b/app/src/main/java/exh/ui/captcha/BrowserActionActivity.kt @@ -25,6 +25,9 @@ import exh.source.DelegatedHttpSource import exh.util.melt import kotlinx.android.synthetic.main.eh_activity_captcha.toolbar import kotlinx.android.synthetic.main.eh_activity_captcha.webview +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withContext import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonObject @@ -179,13 +182,13 @@ class BrowserActionActivity : AppCompatActivity() { return true } - fun captchaSolveFail() { + suspend fun captchaSolveFail() { currentLoopId = null validateCurrentLoopId = null XLog.e(IllegalStateException("Captcha solve failure!")) - runOnUiThread { + withContext(Dispatchers.Main) { webview.evaluateJavascript(SOLVE_UI_SCRIPT_HIDE, null) - MaterialDialog(this) + MaterialDialog(this@BrowserActionActivity) .title(R.string.captcha_solve_failure) .message(R.string.captcha_solve_failure_message) .cancelable(true) @@ -196,7 +199,7 @@ class BrowserActionActivity : AppCompatActivity() { } @JavascriptInterface - fun callback(result: String?, loopId: String, stage: Int) { + suspend fun callback(result: String?, loopId: String, stage: Int) { if (loopId != currentLoopId) return when (stage) { @@ -259,7 +262,7 @@ class BrowserActionActivity : AppCompatActivity() { } }, { - captchaSolveFail() + runBlocking { captchaSolveFail() } } ) } else { @@ -456,7 +459,7 @@ class BrowserActionActivity : AppCompatActivity() { } @JavascriptInterface - fun validateCaptchaCallback(result: Boolean, loopId: String) { + suspend fun validateCaptchaCallback(result: Boolean, loopId: String) { if (loopId != validateCurrentLoopId) return if (result) { diff --git a/app/src/main/java/exh/ui/intercept/InterceptActivity.kt b/app/src/main/java/exh/ui/intercept/InterceptActivity.kt index 3bbf8d846..584de727d 100755 --- a/app/src/main/java/exh/ui/intercept/InterceptActivity.kt +++ b/app/src/main/java/exh/ui/intercept/InterceptActivity.kt @@ -16,10 +16,11 @@ import eu.kanade.tachiyomi.ui.main.MainActivity import eu.kanade.tachiyomi.ui.manga.MangaController import exh.GalleryAddEvent import exh.GalleryAdder +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch import rx.Subscription import rx.android.schedulers.AndroidSchedulers import rx.subjects.BehaviorSubject -import kotlin.concurrent.thread class InterceptActivity : BaseActivity() { private var statusSubscription: Subscription? = null @@ -119,14 +120,14 @@ class InterceptActivity : BaseActivity() { @Synchronized private fun loadGalleryEnd(gallery: String, source: UrlImportableSource? = null) { // Load gallery async - thread { - val result = galleryAdder.addGallery(this, gallery, forceSource = source) + scope.launch(Dispatchers.IO) { + val result = galleryAdder.addGallery(this@InterceptActivity, gallery, forceSource = source) status.onNext( when (result) { is GalleryAddEvent.Success -> result.manga.id?.let { InterceptResult.Success(it) - } ?: InterceptResult.Failure(this.getString(R.string.manga_id_is_null)) + } ?: InterceptResult.Failure(this@InterceptActivity.getString(R.string.manga_id_is_null)) is GalleryAddEvent.Fail -> InterceptResult.Failure(result.logMessage) } ) diff --git a/app/src/main/java/exh/util/SearchOverride.kt b/app/src/main/java/exh/util/SearchOverride.kt index 98c87055c..9ce80244d 100644 --- a/app/src/main/java/exh/util/SearchOverride.kt +++ b/app/src/main/java/exh/util/SearchOverride.kt @@ -5,6 +5,7 @@ import eu.kanade.tachiyomi.source.model.MangasPage import eu.kanade.tachiyomi.source.online.UrlImportableSource import exh.GalleryAddEvent import exh.GalleryAdder +import kotlinx.coroutines.flow.flow import rx.Observable private val galleryAdder by lazy { @@ -17,19 +18,24 @@ private val galleryAdder by lazy { fun UrlImportableSource.urlImportFetchSearchManga(context: Context, query: String, fail: () -> Observable): Observable = when { query.startsWith("http://") || query.startsWith("https://") -> { - Observable.fromCallable { - val res = galleryAdder.addGallery(context, query, false, this) - MangasPage( - ( - if (res is GalleryAddEvent.Success) { - listOf(res.manga) - } else { - emptyList() - } - ), - false + flow { + emit( + galleryAdder.addGallery(context, query, false, this@urlImportFetchSearchManga) ) } + .asObservable() + .map { res -> + MangasPage( + ( + if (res is GalleryAddEvent.Success) { + listOf(res.manga) + } else { + emptyList() + } + ), + false + ) + } } else -> fail() }