diff --git a/app/src/main/java/org/schabi/newpipe/App.java b/app/src/main/java/org/schabi/newpipe/App.java index 7e3466f67..ea6c23028 100644 --- a/app/src/main/java/org/schabi/newpipe/App.java +++ b/app/src/main/java/org/schabi/newpipe/App.java @@ -22,11 +22,11 @@ import org.acra.config.CoreConfiguration; import org.acra.config.CoreConfigurationBuilder; import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.downloader.Downloader; +import org.schabi.newpipe.ktx.ExceptionUtils; import org.schabi.newpipe.report.ErrorActivity; import org.schabi.newpipe.report.ErrorInfo; import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.settings.SettingsActivity; -import org.schabi.newpipe.util.ExceptionUtils; import org.schabi.newpipe.util.Localization; import org.schabi.newpipe.util.ServiceHelper; import org.schabi.newpipe.util.StateSaver; diff --git a/app/src/main/java/org/schabi/newpipe/fragments/BaseStateFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/BaseStateFragment.java index 0d25765a4..9f1f57998 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/BaseStateFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/BaseStateFragment.java @@ -22,10 +22,10 @@ import org.schabi.newpipe.ReCaptchaActivity; import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException; import org.schabi.newpipe.extractor.exceptions.ContentNotSupportedException; import org.schabi.newpipe.extractor.exceptions.ReCaptchaException; +import org.schabi.newpipe.ktx.ExceptionUtils; import org.schabi.newpipe.report.ErrorActivity; import org.schabi.newpipe.report.ErrorInfo; import org.schabi.newpipe.report.UserAction; -import org.schabi.newpipe.util.ExceptionUtils; import org.schabi.newpipe.util.InfoCache; import java.util.Collections; diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java index 3b48d9c84..fb05ed51c 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java @@ -50,6 +50,7 @@ import org.schabi.newpipe.extractor.services.peertube.linkHandler.PeertubeSearch import org.schabi.newpipe.extractor.services.youtube.linkHandler.YoutubeSearchQueryHandlerFactory; import org.schabi.newpipe.fragments.BackPressable; import org.schabi.newpipe.fragments.list.BaseListFragment; +import org.schabi.newpipe.ktx.ExceptionUtils; import org.schabi.newpipe.local.history.HistoryRecordManager; import org.schabi.newpipe.report.ErrorActivity; import org.schabi.newpipe.report.ErrorInfo; @@ -57,7 +58,6 @@ import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.util.AnimationUtils; import org.schabi.newpipe.util.Constants; import org.schabi.newpipe.util.DeviceUtils; -import org.schabi.newpipe.util.ExceptionUtils; import org.schabi.newpipe.util.ExtractorHelper; import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.ServiceHelper; diff --git a/app/src/main/java/org/schabi/newpipe/ktx/Throwable.kt b/app/src/main/java/org/schabi/newpipe/ktx/Throwable.kt new file mode 100644 index 000000000..e05a55f3f --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/ktx/Throwable.kt @@ -0,0 +1,65 @@ +@file:JvmName("ExceptionUtils") + +package org.schabi.newpipe.ktx + +import java.io.IOException +import java.io.InterruptedIOException + +/** + * @return if throwable is related to Interrupted exceptions, or one of its causes is. + */ +val Throwable.isInterruptedCaused: Boolean + get() = hasExactCause(InterruptedIOException::class.java, InterruptedException::class.java) + +/** + * @return if throwable is related to network issues, or one of its causes is. + */ +val Throwable.isNetworkRelated: Boolean + get() = hasAssignableCause(IOException::class.java) + +/** + * Calls [hasCause] with the `checkSubtypes` parameter set to false. + */ +fun Throwable.hasExactCause(vararg causesToCheck: Class<*>) = hasCause(false, *causesToCheck) + +/** + * Calls [hasCause] with the `checkSubtypes` parameter set to true. + */ +fun Throwable?.hasAssignableCause(vararg causesToCheck: Class<*>) = hasCause(true, *causesToCheck) + +/** + * Check if the throwable has some cause from the causes to check, or is itself in it. + * + * If `checkIfAssignable` is true, not only the exact type will be considered equals, but also its subtypes. + * + * @param checkSubtypes if subtypes are also checked. + * @param causesToCheck an array of causes to check. + * + * @see Class.isAssignableFrom + */ +tailrec fun Throwable?.hasCause(checkSubtypes: Boolean, vararg causesToCheck: Class<*>): Boolean { + if (this == null) { + return false + } + + // Check if throwable is a subtype of any of the causes to check + causesToCheck.forEach { causeClass -> + if (checkSubtypes) { + if (causeClass.isAssignableFrom(this.javaClass)) { + return true + } + } else { + if (causeClass == this.javaClass) { + return true + } + } + } + + val currentCause: Throwable? = cause + // Check if cause is not pointing to the same instance, to avoid infinite loops. + if (this !== currentCause) { + return currentCause.hasCause(checkSubtypes, *causesToCheck) + } + + return false +} 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 45e8855e7..5ed7998d2 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 @@ -50,13 +50,13 @@ import org.schabi.newpipe.database.feed.model.FeedGroupEntity import org.schabi.newpipe.extractor.ListInfo 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 import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.SuccessResultEvent import org.schabi.newpipe.local.feed.service.FeedEventManager.postEvent import org.schabi.newpipe.local.subscription.SubscriptionManager -import org.schabi.newpipe.util.ExceptionUtils import org.schabi.newpipe.util.ExtractorHelper import java.io.IOException import java.time.OffsetDateTime @@ -344,7 +344,7 @@ class FeedLoadService : Service() { error is IOException -> throw error cause is IOException -> throw cause - ExceptionUtils.isNetworkRelated(error) -> throw IOException(error) + error.isNetworkRelated -> throw IOException(error) } } } diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/services/BaseImportExportService.java b/app/src/main/java/org/schabi/newpipe/local/subscription/services/BaseImportExportService.java index 34543b565..f573f4679 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/services/BaseImportExportService.java +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/services/BaseImportExportService.java @@ -36,11 +36,11 @@ import androidx.core.app.ServiceCompat; import org.reactivestreams.Publisher; import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.subscription.SubscriptionExtractor; +import org.schabi.newpipe.ktx.ExceptionUtils; import org.schabi.newpipe.local.subscription.SubscriptionManager; import org.schabi.newpipe.report.ErrorActivity; import org.schabi.newpipe.report.ErrorInfo; import org.schabi.newpipe.report.UserAction; -import org.schabi.newpipe.util.ExceptionUtils; import java.io.FileNotFoundException; import java.util.Collections; diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/services/SubscriptionsImportService.java b/app/src/main/java/org/schabi/newpipe/local/subscription/services/SubscriptionsImportService.java index 90d0afe37..af94934b2 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/services/SubscriptionsImportService.java +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/services/SubscriptionsImportService.java @@ -35,8 +35,8 @@ import org.schabi.newpipe.database.subscription.SubscriptionEntity; import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.channel.ChannelInfo; import org.schabi.newpipe.extractor.subscription.SubscriptionItem; +import org.schabi.newpipe.ktx.ExceptionUtils; import org.schabi.newpipe.util.Constants; -import org.schabi.newpipe.util.ExceptionUtils; import org.schabi.newpipe.util.ExtractorHelper; import java.io.File; diff --git a/app/src/main/java/org/schabi/newpipe/util/ExceptionUtils.kt b/app/src/main/java/org/schabi/newpipe/util/ExceptionUtils.kt deleted file mode 100644 index 0addb26fb..000000000 --- a/app/src/main/java/org/schabi/newpipe/util/ExceptionUtils.kt +++ /dev/null @@ -1,86 +0,0 @@ -package org.schabi.newpipe.util - -import java.io.IOException -import java.io.InterruptedIOException - -class ExceptionUtils { - companion object { - /** - * @return if throwable is related to Interrupted exceptions, or one of its causes is. - */ - @JvmStatic - fun isInterruptedCaused(throwable: Throwable): Boolean { - return hasExactCause( - throwable, - InterruptedIOException::class.java, - InterruptedException::class.java - ) - } - - /** - * @return if throwable is related to network issues, or one of its causes is. - */ - @JvmStatic - fun isNetworkRelated(throwable: Throwable): Boolean { - return hasAssignableCause( - throwable, - IOException::class.java - ) - } - - /** - * Calls [hasCause] with the `checkSubtypes` parameter set to false. - */ - @JvmStatic - fun hasExactCause(throwable: Throwable, vararg causesToCheck: Class<*>): Boolean { - return hasCause(throwable, false, *causesToCheck) - } - - /** - * Calls [hasCause] with the `checkSubtypes` parameter set to true. - */ - @JvmStatic - fun hasAssignableCause(throwable: Throwable?, vararg causesToCheck: Class<*>): Boolean { - return hasCause(throwable, true, *causesToCheck) - } - - /** - * Check if throwable has some cause from the causes to check, or is itself in it. - * - * If `checkIfAssignable` is true, not only the exact type will be considered equals, but also its subtypes. - * - * @param throwable throwable that will be checked. - * @param checkSubtypes if subtypes are also checked. - * @param causesToCheck an array of causes to check. - * - * @see Class.isAssignableFrom - */ - @JvmStatic - tailrec fun hasCause(throwable: Throwable?, checkSubtypes: Boolean, vararg causesToCheck: Class<*>): Boolean { - if (throwable == null) { - return false - } - - // Check if throwable is a subtype of any of the causes to check - causesToCheck.forEach { causeClass -> - if (checkSubtypes) { - if (causeClass.isAssignableFrom(throwable.javaClass)) { - return true - } - } else { - if (causeClass == throwable.javaClass) { - return true - } - } - } - - val currentCause: Throwable? = throwable.cause - // Check if cause is not pointing to the same instance, to avoid infinite loops. - if (throwable !== currentCause) { - return hasCause(currentCause, checkSubtypes, *causesToCheck) - } - - return false - } - } -} diff --git a/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java b/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java index 1f1b94545..103d9a72b 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java @@ -58,6 +58,7 @@ import org.schabi.newpipe.extractor.services.youtube.extractors.YoutubeStreamExt import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.StreamInfoItem; import org.schabi.newpipe.extractor.suggestion.SuggestionExtractor; +import org.schabi.newpipe.ktx.ExceptionUtils; import org.schabi.newpipe.report.ErrorActivity; import org.schabi.newpipe.report.ErrorInfo; import org.schabi.newpipe.report.UserAction; diff --git a/app/src/test/java/org/schabi/newpipe/ktx/ThrowableExtensionsTest.kt b/app/src/test/java/org/schabi/newpipe/ktx/ThrowableExtensionsTest.kt new file mode 100644 index 000000000..8132b8fbc --- /dev/null +++ b/app/src/test/java/org/schabi/newpipe/ktx/ThrowableExtensionsTest.kt @@ -0,0 +1,67 @@ +package org.schabi.newpipe.ktx + +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Test +import java.io.IOException +import java.io.InterruptedIOException +import java.net.SocketException +import javax.net.ssl.SSLException + +class ThrowableExtensionsTest { + @Test fun `assignable causes`() { + assertTrue(Throwable().hasAssignableCause(Throwable::class.java)) + assertTrue(Exception().hasAssignableCause(Exception::class.java)) + assertTrue(IOException().hasAssignableCause(Exception::class.java)) + + assertTrue(IOException().hasAssignableCause(IOException::class.java)) + assertTrue(Exception(SocketException()).hasAssignableCause(IOException::class.java)) + assertTrue(Exception(IllegalStateException()).hasAssignableCause(RuntimeException::class.java)) + assertTrue(Exception(Exception(IOException())).hasAssignableCause(IOException::class.java)) + assertTrue(Exception(IllegalStateException(Exception(IOException()))).hasAssignableCause(IOException::class.java)) + assertTrue(Exception(IllegalStateException(Exception(SocketException()))).hasAssignableCause(IOException::class.java)) + assertTrue(Exception(IllegalStateException(Exception(SSLException("IO")))).hasAssignableCause(IOException::class.java)) + assertTrue(Exception(IllegalStateException(Exception(InterruptedIOException()))).hasAssignableCause(IOException::class.java)) + assertTrue(Exception(IllegalStateException(Exception(InterruptedIOException()))).hasAssignableCause(RuntimeException::class.java)) + + assertTrue(IllegalStateException().hasAssignableCause(Throwable::class.java)) + assertTrue(IllegalStateException().hasAssignableCause(Exception::class.java)) + assertTrue(Exception(IllegalStateException(Exception(InterruptedIOException()))).hasAssignableCause(InterruptedIOException::class.java)) + } + + @Test fun `no assignable causes`() { + assertFalse(Throwable().hasAssignableCause(Exception::class.java)) + assertFalse(Exception().hasAssignableCause(IOException::class.java)) + assertFalse(Exception(IllegalStateException()).hasAssignableCause(IOException::class.java)) + assertFalse(Exception(NullPointerException()).hasAssignableCause(IOException::class.java)) + assertFalse(Exception(IllegalStateException(Exception(Exception()))).hasAssignableCause(IOException::class.java)) + assertFalse(Exception(IllegalStateException(Exception(SocketException()))).hasAssignableCause(InterruptedIOException::class.java)) + assertFalse(Exception(IllegalStateException(Exception(InterruptedIOException()))).hasAssignableCause(InterruptedException::class.java)) + } + + @Test fun `exact causes`() { + assertTrue(Throwable().hasExactCause(Throwable::class.java)) + assertTrue(Exception().hasExactCause(Exception::class.java)) + + assertTrue(IOException().hasExactCause(IOException::class.java)) + assertTrue(Exception(SocketException()).hasExactCause(SocketException::class.java)) + assertTrue(Exception(Exception(IOException())).hasExactCause(IOException::class.java)) + assertTrue(Exception(IllegalStateException(Exception(IOException()))).hasExactCause(IOException::class.java)) + assertTrue(Exception(IllegalStateException(Exception(SocketException()))).hasExactCause(SocketException::class.java)) + assertTrue(Exception(IllegalStateException(Exception(SSLException("IO")))).hasExactCause(SSLException::class.java)) + assertTrue(Exception(IllegalStateException(Exception(InterruptedIOException()))).hasExactCause(InterruptedIOException::class.java)) + assertTrue(Exception(IllegalStateException(Exception(InterruptedIOException()))).hasExactCause(IllegalStateException::class.java)) + } + + @Test fun `no exact causes`() { + assertFalse(Throwable().hasExactCause(Exception::class.java)) + assertFalse(Exception().hasExactCause(Throwable::class.java)) + + assertFalse(SocketException().hasExactCause(IOException::class.java)) + assertFalse(IllegalStateException().hasExactCause(RuntimeException::class.java)) + assertFalse(Exception(SocketException()).hasExactCause(IOException::class.java)) + assertFalse(Exception(IllegalStateException(Exception(IOException()))).hasExactCause(RuntimeException::class.java)) + assertFalse(Exception(IllegalStateException(Exception(SocketException()))).hasExactCause(IOException::class.java)) + assertFalse(Exception(IllegalStateException(Exception(InterruptedIOException()))).hasExactCause(IOException::class.java)) + } +} diff --git a/app/src/test/java/org/schabi/newpipe/util/ExceptionUtilsTest.kt b/app/src/test/java/org/schabi/newpipe/util/ExceptionUtilsTest.kt deleted file mode 100644 index 2d897e379..000000000 --- a/app/src/test/java/org/schabi/newpipe/util/ExceptionUtilsTest.kt +++ /dev/null @@ -1,69 +0,0 @@ -package org.schabi.newpipe.util - -import org.junit.Assert.assertFalse -import org.junit.Assert.assertTrue -import org.junit.Test -import org.schabi.newpipe.util.ExceptionUtils.Companion.hasAssignableCause -import org.schabi.newpipe.util.ExceptionUtils.Companion.hasExactCause -import java.io.IOException -import java.io.InterruptedIOException -import java.net.SocketException -import javax.net.ssl.SSLException - -class ExceptionUtilsTest { - @Test fun `assignable causes`() { - assertTrue(hasAssignableCause(Throwable(), Throwable::class.java)) - assertTrue(hasAssignableCause(Exception(), Exception::class.java)) - assertTrue(hasAssignableCause(IOException(), Exception::class.java)) - - assertTrue(hasAssignableCause(IOException(), IOException::class.java)) - assertTrue(hasAssignableCause(Exception(SocketException()), IOException::class.java)) - assertTrue(hasAssignableCause(Exception(IllegalStateException()), RuntimeException::class.java)) - assertTrue(hasAssignableCause(Exception(Exception(IOException())), IOException::class.java)) - assertTrue(hasAssignableCause(Exception(IllegalStateException(Exception(IOException()))), IOException::class.java)) - assertTrue(hasAssignableCause(Exception(IllegalStateException(Exception(SocketException()))), IOException::class.java)) - assertTrue(hasAssignableCause(Exception(IllegalStateException(Exception(SSLException("IO")))), IOException::class.java)) - assertTrue(hasAssignableCause(Exception(IllegalStateException(Exception(InterruptedIOException()))), IOException::class.java)) - assertTrue(hasAssignableCause(Exception(IllegalStateException(Exception(InterruptedIOException()))), RuntimeException::class.java)) - - assertTrue(hasAssignableCause(IllegalStateException(), Throwable::class.java)) - assertTrue(hasAssignableCause(IllegalStateException(), Exception::class.java)) - assertTrue(hasAssignableCause(Exception(IllegalStateException(Exception(InterruptedIOException()))), InterruptedIOException::class.java)) - } - - @Test fun `no assignable causes`() { - assertFalse(hasAssignableCause(Throwable(), Exception::class.java)) - assertFalse(hasAssignableCause(Exception(), IOException::class.java)) - assertFalse(hasAssignableCause(Exception(IllegalStateException()), IOException::class.java)) - assertFalse(hasAssignableCause(Exception(NullPointerException()), IOException::class.java)) - assertFalse(hasAssignableCause(Exception(IllegalStateException(Exception(Exception()))), IOException::class.java)) - assertFalse(hasAssignableCause(Exception(IllegalStateException(Exception(SocketException()))), InterruptedIOException::class.java)) - assertFalse(hasAssignableCause(Exception(IllegalStateException(Exception(InterruptedIOException()))), InterruptedException::class.java)) - } - - @Test fun `exact causes`() { - assertTrue(hasExactCause(Throwable(), Throwable::class.java)) - assertTrue(hasExactCause(Exception(), Exception::class.java)) - - assertTrue(hasExactCause(IOException(), IOException::class.java)) - assertTrue(hasExactCause(Exception(SocketException()), SocketException::class.java)) - assertTrue(hasExactCause(Exception(Exception(IOException())), IOException::class.java)) - assertTrue(hasExactCause(Exception(IllegalStateException(Exception(IOException()))), IOException::class.java)) - assertTrue(hasExactCause(Exception(IllegalStateException(Exception(SocketException()))), SocketException::class.java)) - assertTrue(hasExactCause(Exception(IllegalStateException(Exception(SSLException("IO")))), SSLException::class.java)) - assertTrue(hasExactCause(Exception(IllegalStateException(Exception(InterruptedIOException()))), InterruptedIOException::class.java)) - assertTrue(hasExactCause(Exception(IllegalStateException(Exception(InterruptedIOException()))), IllegalStateException::class.java)) - } - - @Test fun `no exact causes`() { - assertFalse(hasExactCause(Throwable(), Exception::class.java)) - assertFalse(hasExactCause(Exception(), Throwable::class.java)) - - assertFalse(hasExactCause(SocketException(), IOException::class.java)) - assertFalse(hasExactCause(IllegalStateException(), RuntimeException::class.java)) - assertFalse(hasExactCause(Exception(SocketException()), IOException::class.java)) - assertFalse(hasExactCause(Exception(IllegalStateException(Exception(IOException()))), RuntimeException::class.java)) - assertFalse(hasExactCause(Exception(IllegalStateException(Exception(SocketException()))), IOException::class.java)) - assertFalse(hasExactCause(Exception(IllegalStateException(Exception(InterruptedIOException()))), IOException::class.java)) - } -}