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:
parent
761e01c3b9
commit
6ad4b425e4
@ -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}"
|
||||||
|
@ -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()
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
|
Loading…
Reference in New Issue
Block a user