Fix feed loading and show a dialog for each invalid subscription
This commit is contained in:
parent
c5dd3dc7a9
commit
89317d4abc
2 changed files with 61 additions and 75 deletions
|
@ -19,7 +19,6 @@
|
||||||
|
|
||||||
package org.schabi.newpipe.local.feed
|
package org.schabi.newpipe.local.feed
|
||||||
|
|
||||||
import android.content.DialogInterface
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
|
@ -256,8 +255,6 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
|
||||||
listState = null
|
listState = null
|
||||||
}
|
}
|
||||||
|
|
||||||
oldestSubscriptionUpdate = loadedState.oldestUpdate
|
|
||||||
|
|
||||||
val feedsNotLoaded = loadedState.notLoadedCount > 0
|
val feedsNotLoaded = loadedState.notLoadedCount > 0
|
||||||
feedBinding.refreshSubtitleText.isVisible = feedsNotLoaded
|
feedBinding.refreshSubtitleText.isVisible = feedsNotLoaded
|
||||||
if (feedsNotLoaded) {
|
if (feedsNotLoaded) {
|
||||||
|
@ -267,6 +264,12 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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()) {
|
if (loadedState.items.isEmpty()) {
|
||||||
showEmptyState()
|
showEmptyState()
|
||||||
} else {
|
} else {
|
||||||
|
@ -279,51 +282,59 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
|
||||||
hideLoading()
|
hideLoading()
|
||||||
false
|
false
|
||||||
} else {
|
} else {
|
||||||
if (errorState.error is FeedLoadService.RequestException) {
|
showError(ErrorInfo(errorState.error, UserAction.REQUESTED_FEED, "Loading feed"))
|
||||||
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"))
|
|
||||||
}
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleItemsErrors(errors: List<Throwable>) {
|
||||||
|
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(
|
private fun handleFeedNotAvailable(
|
||||||
subscriptionEntity: SubscriptionEntity,
|
subscriptionEntity: SubscriptionEntity,
|
||||||
@Nullable cause: Throwable?
|
@Nullable cause: Throwable?,
|
||||||
|
nextItemsErrors: List<Throwable>
|
||||||
) {
|
) {
|
||||||
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||||
val isFastFeedModeEnabled = sharedPreferences.getBoolean(
|
val isFastFeedModeEnabled = sharedPreferences.getBoolean(
|
||||||
getString(R.string.feed_use_dedicated_fetch_method_key), false
|
getString(R.string.feed_use_dedicated_fetch_method_key), false
|
||||||
)
|
)
|
||||||
|
|
||||||
val builder = AlertDialog.Builder(requireContext())
|
val builder = AlertDialog.Builder(requireContext())
|
||||||
.setTitle(R.string.feed_load_error)
|
.setTitle(R.string.feed_load_error)
|
||||||
.setPositiveButton(
|
.setPositiveButton(
|
||||||
R.string.unsubscribe,
|
R.string.unsubscribe
|
||||||
DialogInterface.OnClickListener {
|
) { _, _ ->
|
||||||
_, _ ->
|
SubscriptionManager(requireContext()).deleteSubscription(
|
||||||
SubscriptionManager(requireContext()).deleteSubscription(
|
subscriptionEntity.serviceId, subscriptionEntity.url
|
||||||
subscriptionEntity.serviceId, subscriptionEntity.url
|
).subscribe()
|
||||||
).subscribe()
|
handleItemsErrors(nextItemsErrors)
|
||||||
}
|
}
|
||||||
)
|
.setNegativeButton(R.string.cancel) { _, _ -> }
|
||||||
.setNegativeButton(R.string.cancel, DialogInterface.OnClickListener { _, _ -> })
|
|
||||||
var message = getString(R.string.feed_load_error_account_info, subscriptionEntity.name)
|
var message = getString(R.string.feed_load_error_account_info, subscriptionEntity.name)
|
||||||
if (cause is AccountTerminatedException) {
|
if (cause is AccountTerminatedException) {
|
||||||
message += "\n" + getString(R.string.feed_load_error_terminated)
|
message += "\n" + getString(R.string.feed_load_error_terminated)
|
||||||
|
|
|
@ -48,10 +48,7 @@ import org.schabi.newpipe.MainActivity.DEBUG
|
||||||
import org.schabi.newpipe.R
|
import org.schabi.newpipe.R
|
||||||
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
|
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
|
||||||
import org.schabi.newpipe.extractor.ListInfo
|
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.extractor.stream.StreamInfoItem
|
||||||
import org.schabi.newpipe.ktx.isNetworkRelated
|
|
||||||
import org.schabi.newpipe.local.feed.FeedDatabaseManager
|
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.ErrorResultEvent
|
||||||
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.ProgressEvent
|
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.feed.service.FeedEventManager.postEvent
|
||||||
import org.schabi.newpipe.local.subscription.SubscriptionManager
|
import org.schabi.newpipe.local.subscription.SubscriptionManager
|
||||||
import org.schabi.newpipe.util.ExtractorHelper
|
import org.schabi.newpipe.util.ExtractorHelper
|
||||||
import java.io.IOException
|
|
||||||
import java.time.OffsetDateTime
|
import java.time.OffsetDateTime
|
||||||
import java.time.ZoneOffset
|
import java.time.ZoneOffset
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
@ -163,7 +159,7 @@ class FeedLoadService : Service() {
|
||||||
// Loading & Handling
|
// 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 {
|
companion object {
|
||||||
fun wrapList(subscriptionId: Long, info: ListInfo<StreamInfoItem>): List<Throwable> {
|
fun wrapList(subscriptionId: Long, info: ListInfo<StreamInfoItem>): List<Throwable> {
|
||||||
val toReturn = ArrayList<Throwable>(info.errors.size)
|
val toReturn = ArrayList<Throwable>(info.errors.size)
|
||||||
|
@ -210,29 +206,40 @@ class FeedLoadService : Service() {
|
||||||
.filter { !cancelSignal.get() }
|
.filter { !cancelSignal.get() }
|
||||||
|
|
||||||
.map { subscriptionEntity ->
|
.map { subscriptionEntity ->
|
||||||
|
var error: Throwable? = null
|
||||||
try {
|
try {
|
||||||
val listInfo = if (useFeedExtractor) {
|
val listInfo = if (useFeedExtractor) {
|
||||||
ExtractorHelper
|
ExtractorHelper
|
||||||
.getFeedInfoFallbackToChannelInfo(subscriptionEntity.serviceId, subscriptionEntity.url)
|
.getFeedInfoFallbackToChannelInfo(subscriptionEntity.serviceId, subscriptionEntity.url)
|
||||||
|
.onErrorReturn {
|
||||||
|
error = it // store error, otherwise wrapped into RuntimeException
|
||||||
|
throw it
|
||||||
|
}
|
||||||
.blockingGet()
|
.blockingGet()
|
||||||
} else {
|
} else {
|
||||||
ExtractorHelper
|
ExtractorHelper
|
||||||
.getChannelInfo(subscriptionEntity.serviceId, subscriptionEntity.url, true)
|
.getChannelInfo(subscriptionEntity.serviceId, subscriptionEntity.url, true)
|
||||||
|
.onErrorReturn {
|
||||||
|
error = it // store error, otherwise wrapped into RuntimeException
|
||||||
|
throw it
|
||||||
|
}
|
||||||
.blockingGet()
|
.blockingGet()
|
||||||
} as ListInfo<StreamInfoItem>
|
} as ListInfo<StreamInfoItem>
|
||||||
|
|
||||||
return@map Notification.createOnNext(Pair(subscriptionEntity.uid, listInfo))
|
return@map Notification.createOnNext(Pair(subscriptionEntity.uid, listInfo))
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
|
if (error == null) {
|
||||||
|
// do this to prevent blockingGet() from wrapping into RuntimeException
|
||||||
|
error = e
|
||||||
|
}
|
||||||
|
|
||||||
val request = "${subscriptionEntity.serviceId}:${subscriptionEntity.url}"
|
val request = "${subscriptionEntity.serviceId}:${subscriptionEntity.url}"
|
||||||
val wrapper = RequestException(subscriptionEntity.uid, request, e)
|
val wrapper = RequestException(subscriptionEntity.uid, request, error!!)
|
||||||
return@map Notification.createOnError<Pair<Long, ListInfo<StreamInfoItem>>>(wrapper)
|
return@map Notification.createOnError<Pair<Long, ListInfo<StreamInfoItem>>>(wrapper)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.sequential()
|
.sequential()
|
||||||
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.doOnNext(errorHandlingConsumer)
|
|
||||||
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.doOnNext(notificationsConsumer)
|
.doOnNext(notificationsConsumer)
|
||||||
|
|
||||||
|
@ -332,38 +339,6 @@ class FeedLoadService : Service() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val errorHandlingConsumer: Consumer<Notification<Pair<Long, ListInfo<StreamInfoItem>>>>
|
|
||||||
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<Notification<Pair<Long, ListInfo<StreamInfoItem>>>>
|
private val notificationsConsumer: Consumer<Notification<Pair<Long, ListInfo<StreamInfoItem>>>>
|
||||||
get() = Consumer { onItemCompleted(it.value?.second?.name) }
|
get() = Consumer { onItemCompleted(it.value?.second?.name) }
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue