1
0
mirror of https://github.com/TeamNewPipe/NewPipe synced 2024-12-23 16:40:32 +00:00

Better error handling of terminated channels when loading feed

This commit is contained in:
TobiGr 2021-04-02 21:41:06 +02:00
parent 761e01c3b9
commit 6ad4b425e4
4 changed files with 95 additions and 8 deletions

View File

@ -183,7 +183,7 @@ dependencies {
/** NewPipe libraries **/ /** NewPipe libraries **/
// You can use a local version by uncommenting a few lines in settings.gradle // You can use a local version by uncommenting a few lines in settings.gradle
implementation 'com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751' implementation 'com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751'
implementation 'com.github.TeamNewPipe:NewPipeExtractor:v0.21.4' implementation 'com.github.TeamNewPipe:NewPipeExtractor:d4186d100b6c6dddfcf3cf4b004f5960a8bf441d'
/** Checkstyle **/ /** Checkstyle **/
checkstyle "com.puppycrawl.tools:checkstyle:${checkstyleVersion}" checkstyle "com.puppycrawl.tools:checkstyle:${checkstyleVersion}"

View File

@ -19,6 +19,7 @@
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
@ -28,6 +29,7 @@ import android.view.MenuInflater
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.annotation.Nullable
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.content.edit import androidx.core.content.edit
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
@ -35,15 +37,24 @@ import androidx.core.view.isVisible
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import icepick.State import icepick.State
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.core.Single
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.schedulers.Schedulers
import org.schabi.newpipe.NewPipeDatabase
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.database.subscription.SubscriptionEntity
import org.schabi.newpipe.databinding.FragmentFeedBinding import org.schabi.newpipe.databinding.FragmentFeedBinding
import org.schabi.newpipe.error.ErrorInfo import org.schabi.newpipe.error.ErrorInfo
import org.schabi.newpipe.error.UserAction import org.schabi.newpipe.error.UserAction
import org.schabi.newpipe.extractor.exceptions.AccountTerminatedException
import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException
import org.schabi.newpipe.fragments.list.BaseListFragment import org.schabi.newpipe.fragments.list.BaseListFragment
import org.schabi.newpipe.ktx.animate import org.schabi.newpipe.ktx.animate
import org.schabi.newpipe.ktx.animateHideRecyclerViewAllowingScrolling import org.schabi.newpipe.ktx.animateHideRecyclerViewAllowingScrolling
import org.schabi.newpipe.local.feed.service.FeedLoadService import org.schabi.newpipe.local.feed.service.FeedLoadService
import org.schabi.newpipe.local.subscription.SubscriptionManager
import org.schabi.newpipe.util.Localization import org.schabi.newpipe.util.Localization
import java.time.OffsetDateTime import java.time.OffsetDateTime
@ -51,6 +62,8 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
private var _feedBinding: FragmentFeedBinding? = null private var _feedBinding: FragmentFeedBinding? = null
private val feedBinding get() = _feedBinding!! private val feedBinding get() = _feedBinding!!
private val disposables = CompositeDisposable()
private lateinit var viewModel: FeedViewModel private lateinit var viewModel: FeedViewModel
@State @State
@JvmField @JvmField
@ -158,6 +171,7 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
} }
override fun onDestroy() { override fun onDestroy() {
disposables.dispose()
super.onDestroy() super.onDestroy()
activity?.supportActionBar?.subtitle = null activity?.supportActionBar?.subtitle = null
} }
@ -243,9 +257,9 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
oldestSubscriptionUpdate = loadedState.oldestUpdate oldestSubscriptionUpdate = loadedState.oldestUpdate
val loadedCount = loadedState.notLoadedCount > 0 val feedsNotLoaded = loadedState.notLoadedCount > 0
feedBinding.refreshSubtitleText.isVisible = loadedCount feedBinding.refreshSubtitleText.isVisible = feedsNotLoaded
if (loadedCount) { if (feedsNotLoaded) {
feedBinding.refreshSubtitleText.text = getString( feedBinding.refreshSubtitleText.text = getString(
R.string.feed_subscription_not_loaded_count, R.string.feed_subscription_not_loaded_count,
loadedState.notLoadedCount loadedState.notLoadedCount
@ -263,12 +277,65 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
return if (errorState.error == null) { return if (errorState.error == null) {
hideLoading() hideLoading()
false 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 { } else {
showError(ErrorInfo(errorState.error, UserAction.REQUESTED_FEED, "Loading feed")) showError(ErrorInfo(errorState.error, UserAction.REQUESTED_FEED, "Loading feed"))
}
true true
} }
} }
private fun handleFeedNotAvailable(
subscriptionEntity: SubscriptionEntity,
@Nullable cause: Throwable?
) {
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 { _, _ -> })
if (cause is AccountTerminatedException) {
builder.setMessage(R.string.feed_load_error_terminated)
} else if (cause is ContentNotAvailableException && isFastFeedModeEnabled) {
builder.setMessage(R.string.feed_load_error_fast_unknown)
.setNeutralButton(R.string.feed_use_dedicated_fetch_method_disable_button) { _, _ ->
sharedPreferences.edit {
putBoolean(getString(R.string.feed_use_dedicated_fetch_method_key), false)
}
}
}
builder.create().show()
}
private fun updateRelativeTimeViews() { private fun updateRelativeTimeViews() {
updateRefreshViewState() updateRefreshViewState()
infoListAdapter.notifyDataSetChanged() infoListAdapter.notifyDataSetChanged()

View File

@ -48,6 +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.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.ktx.isNetworkRelated
@ -162,7 +163,7 @@ class FeedLoadService : Service() {
// Loading & Handling // Loading & Handling
// ///////////////////////////////////////////////////////////////////////// // /////////////////////////////////////////////////////////////////////////
private class RequestException(val subscriptionId: Long, message: String, cause: Throwable) : Exception(message, cause) { public 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)
@ -334,8 +335,9 @@ class FeedLoadService : Service() {
private val errorHandlingConsumer: Consumer<Notification<Pair<Long, ListInfo<StreamInfoItem>>>> private val errorHandlingConsumer: Consumer<Notification<Pair<Long, ListInfo<StreamInfoItem>>>>
get() = Consumer { get() = Consumer {
if (it.isOnError) { if (it.isOnError) {
var error = it.error!! var maybeWrapper = it.error!!
if (error is RequestException) error = error.cause!! val error = if (maybeWrapper is RequestException) maybeWrapper.cause!!
else maybeWrapper
val cause = error.cause val cause = error.cause
when { when {
@ -345,6 +347,19 @@ class FeedLoadService : Service() {
error is IOException -> throw error error is IOException -> throw error
cause is IOException -> throw cause cause is IOException -> throw cause
error.isNetworkRelated -> throw IOException(error) 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
}
}
} }
} }
} }

View File

@ -690,11 +690,16 @@
<string name="feed_update_threshold_title">Feed update threshold</string> <string name="feed_update_threshold_title">Feed update threshold</string>
<string name="feed_update_threshold_summary">Time after last update before a subscription is considered outdated — %s</string> <string name="feed_update_threshold_summary">Time after last update before a subscription is considered outdated — %s</string>
<string name="feed_update_threshold_option_always_update">Always update</string> <string name="feed_update_threshold_option_always_update">Always update</string>
<string name="feed_load_error">Error loading feed</string>
<string name="feed_load_error_account_info">Could not load feed for \'%s\'.</string>
<string name="feed_load_error_terminated">The author\'s account has been terminated.\nNewPipe will not be able to load this feed in the future.\Do you want to unsubscribe from this channel?</string>
<string name="feed_load_error_fast_unknown">The fast feed mode does not provide more info on this.</string>
<string name="feed_use_dedicated_fetch_method_title">Fetch from dedicated feed when available</string> <string name="feed_use_dedicated_fetch_method_title">Fetch from dedicated feed when available</string>
<string name="feed_use_dedicated_fetch_method_summary">Available in some services, it is usually much faster but may return a limited amount of items and often incomplete information (e.g. no duration, item type, no live status).</string> <string name="feed_use_dedicated_fetch_method_summary">Available in some services, it is usually much faster but may return a limited amount of items and often incomplete information (e.g. no duration, item type, no live status).</string>
<string name="feed_use_dedicated_fetch_method_enable_button">Enable fast mode</string> <string name="feed_use_dedicated_fetch_method_enable_button">Enable fast mode</string>
<string name="feed_use_dedicated_fetch_method_disable_button">Disable fast mode</string> <string name="feed_use_dedicated_fetch_method_disable_button">Disable fast mode</string>
<string name="feed_use_dedicated_fetch_method_help_text">Do you think feed loading is too slow? If so, try enabling fast loading (you can change it in settings or by pressing the button below).\n\nNewPipe offers two feed loading strategies:\n• Fetching the whole subscription channel, which is slow but complete.\n• Using a dedicated service endpoint, which is fast but usually not complete.\n\nThe difference between the two is that the fast one usually lacks some information, like the item\'s duration or type (can\'t distinguish between live videos and normal ones) and it may return less items.\n\nYouTube is an example of a service that offers this fast method with its RSS feed.\n\nSo the choice boils down to what you prefer: speed or precise information.</string> <string name="feed_use_dedicated_fetch_method_help_text">Do you think feed loading is too slow? If so, try enabling fast loading (you can change it in settings or by pressing the button below).\n\nNewPipe offers two feed loading strategies:\n• Fetching the whole subscription channel, which is slow but complete.\n• Using a dedicated service endpoint, which is fast but usually not complete.\n\nThe difference between the two is that the fast one usually lacks some information, like the item\'s duration or type (can\'t distinguish between live videos and normal ones) and it may return less items.\n\nYouTube is an example of a service that offers this fast method with its RSS feed.\n\nSo the choice boils down to what you prefer: speed or precise information.</string>
<string name="content_not_supported">This content is not yet supported by NewPipe.\n\nIt will hopefully be supported in a future version.</string> <string name="content_not_supported">This content is not yet supported by NewPipe.\n\nIt will hopefully be supported in a future version.</string>
<string name="detail_sub_channel_thumbnail_view_description">Channel\'s avatar thumbnail</string> <string name="detail_sub_channel_thumbnail_view_description">Channel\'s avatar thumbnail</string>
<string name="channel_created_by">Created by %s</string> <string name="channel_created_by">Created by %s</string>