From 89317d4abc9afd99e1dffb2630f0159becd7dbdf Mon Sep 17 00:00:00 2001 From: Stypox Date: Fri, 2 Apr 2021 18:16:24 +0200 Subject: [PATCH] Fix feed loading and show a dialog for each invalid subscription --- .../schabi/newpipe/local/feed/FeedFragment.kt | 79 +++++++++++-------- .../local/feed/service/FeedLoadService.kt | 57 ++++--------- 2 files changed, 61 insertions(+), 75 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt b/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt index 3662c3654..a8171530f 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt @@ -19,7 +19,6 @@ package org.schabi.newpipe.local.feed -import android.content.DialogInterface import android.content.Intent import android.os.Bundle import android.os.Parcelable @@ -256,8 +255,6 @@ class FeedFragment : BaseListFragment() { listState = null } - oldestSubscriptionUpdate = loadedState.oldestUpdate - val feedsNotLoaded = loadedState.notLoadedCount > 0 feedBinding.refreshSubtitleText.isVisible = feedsNotLoaded if (feedsNotLoaded) { @@ -267,6 +264,12 @@ class FeedFragment : BaseListFragment() { ) } + if (oldestSubscriptionUpdate != loadedState.oldestUpdate) { + // ignore errors if they have already been handled for the current update + handleItemsErrors(loadedState.itemsErrors) + } + oldestSubscriptionUpdate = loadedState.oldestUpdate + if (loadedState.items.isEmpty()) { showEmptyState() } else { @@ -279,51 +282,59 @@ class FeedFragment : BaseListFragment() { hideLoading() false } else { - if (errorState.error is FeedLoadService.RequestException) { - disposables.add( - Single.fromCallable { - NewPipeDatabase.getInstance(requireContext()).subscriptionDAO() - .getSubscription(errorState.error.subscriptionId) - }.subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe( - { - subscriptionEntity -> - handleFeedNotAvailable( - subscriptionEntity, - errorState.error.cause?.cause - ) - }, - { throwable -> throwable.printStackTrace() } - ) - ) - } else { - showError(ErrorInfo(errorState.error, UserAction.REQUESTED_FEED, "Loading feed")) - } + showError(ErrorInfo(errorState.error, UserAction.REQUESTED_FEED, "Loading feed")) true } } + private fun handleItemsErrors(errors: List) { + errors.forEachIndexed() { i, t -> + if (t is FeedLoadService.RequestException && + t.cause is ContentNotAvailableException + ) { + Single.fromCallable { + NewPipeDatabase.getInstance(requireContext()).subscriptionDAO() + .getSubscription(t.subscriptionId) + }.subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + { + subscriptionEntity -> + handleFeedNotAvailable( + subscriptionEntity, + t.cause?.cause, + errors.subList(i + 1, errors.size) + ) + }, + { throwable -> throwable.printStackTrace() } + ) + return // this will be called on the remaining errors by handleFeedNotAvailable() + } + } + } + private fun handleFeedNotAvailable( subscriptionEntity: SubscriptionEntity, - @Nullable cause: Throwable? + @Nullable cause: Throwable?, + nextItemsErrors: List ) { val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext()) val isFastFeedModeEnabled = sharedPreferences.getBoolean( getString(R.string.feed_use_dedicated_fetch_method_key), false ) + val builder = AlertDialog.Builder(requireContext()) .setTitle(R.string.feed_load_error) .setPositiveButton( - R.string.unsubscribe, - DialogInterface.OnClickListener { - _, _ -> - SubscriptionManager(requireContext()).deleteSubscription( - subscriptionEntity.serviceId, subscriptionEntity.url - ).subscribe() - } - ) - .setNegativeButton(R.string.cancel, DialogInterface.OnClickListener { _, _ -> }) + R.string.unsubscribe + ) { _, _ -> + SubscriptionManager(requireContext()).deleteSubscription( + subscriptionEntity.serviceId, subscriptionEntity.url + ).subscribe() + handleItemsErrors(nextItemsErrors) + } + .setNegativeButton(R.string.cancel) { _, _ -> } + var message = getString(R.string.feed_load_error_account_info, subscriptionEntity.name) if (cause is AccountTerminatedException) { message += "\n" + getString(R.string.feed_load_error_terminated) diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadService.kt b/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadService.kt index 7ca583317..3638b4c0e 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadService.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadService.kt @@ -48,10 +48,7 @@ import org.schabi.newpipe.MainActivity.DEBUG import org.schabi.newpipe.R import org.schabi.newpipe.database.feed.model.FeedGroupEntity import org.schabi.newpipe.extractor.ListInfo -import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException -import org.schabi.newpipe.extractor.exceptions.ReCaptchaException import org.schabi.newpipe.extractor.stream.StreamInfoItem -import org.schabi.newpipe.ktx.isNetworkRelated import org.schabi.newpipe.local.feed.FeedDatabaseManager import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.ErrorResultEvent import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.ProgressEvent @@ -59,7 +56,6 @@ import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.SuccessResul import org.schabi.newpipe.local.feed.service.FeedEventManager.postEvent import org.schabi.newpipe.local.subscription.SubscriptionManager import org.schabi.newpipe.util.ExtractorHelper -import java.io.IOException import java.time.OffsetDateTime import java.time.ZoneOffset import java.util.concurrent.TimeUnit @@ -163,7 +159,7 @@ class FeedLoadService : Service() { // Loading & Handling // ///////////////////////////////////////////////////////////////////////// - public class RequestException(val subscriptionId: Long, message: String, cause: Throwable) : Exception(message, cause) { + class RequestException(val subscriptionId: Long, message: String, cause: Throwable) : Exception(message, cause) { companion object { fun wrapList(subscriptionId: Long, info: ListInfo): List { val toReturn = ArrayList(info.errors.size) @@ -210,29 +206,40 @@ class FeedLoadService : Service() { .filter { !cancelSignal.get() } .map { subscriptionEntity -> + var error: Throwable? = null try { val listInfo = if (useFeedExtractor) { ExtractorHelper .getFeedInfoFallbackToChannelInfo(subscriptionEntity.serviceId, subscriptionEntity.url) + .onErrorReturn { + error = it // store error, otherwise wrapped into RuntimeException + throw it + } .blockingGet() } else { ExtractorHelper .getChannelInfo(subscriptionEntity.serviceId, subscriptionEntity.url, true) + .onErrorReturn { + error = it // store error, otherwise wrapped into RuntimeException + throw it + } .blockingGet() } as ListInfo return@map Notification.createOnNext(Pair(subscriptionEntity.uid, listInfo)) } catch (e: Throwable) { + if (error == null) { + // do this to prevent blockingGet() from wrapping into RuntimeException + error = e + } + val request = "${subscriptionEntity.serviceId}:${subscriptionEntity.url}" - val wrapper = RequestException(subscriptionEntity.uid, request, e) + val wrapper = RequestException(subscriptionEntity.uid, request, error!!) return@map Notification.createOnError>>(wrapper) } } .sequential() - .observeOn(AndroidSchedulers.mainThread()) - .doOnNext(errorHandlingConsumer) - .observeOn(AndroidSchedulers.mainThread()) .doOnNext(notificationsConsumer) @@ -332,38 +339,6 @@ class FeedLoadService : Service() { } } - private val errorHandlingConsumer: Consumer>>> - get() = Consumer { - if (it.isOnError) { - var maybeWrapper = it.error!! - val error = if (maybeWrapper is RequestException) maybeWrapper.cause!! - else maybeWrapper - val cause = error.cause - - when { - error is ReCaptchaException -> throw error - cause is ReCaptchaException -> throw cause - - error is IOException -> throw error - cause is IOException -> throw cause - error.isNetworkRelated -> throw IOException(error) - - cause is ContentNotAvailableException -> { - // maybeWrapper is definitely a RequestException, - // because this is an exception thrown in the extractor - if (maybeWrapper is RequestException) { - throw maybeWrapper - } else { - if (DEBUG) { - Log.d(TAG, "Cause is ContentNotAvailableException, but maybeWrapper is not a RequestException") - } - throw cause // should never be the case - } - } - } - } - } - private val notificationsConsumer: Consumer>>> get() = Consumer { onItemCompleted(it.value?.second?.name) }