mirror of
				https://github.com/TeamNewPipe/NewPipe
				synced 2025-10-31 23:32:59 +00:00 
			
		
		
		
	Rewrite ExceptionUtils methods as extension functions.
This commit is contained in:
		| @@ -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; | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
							
								
								
									
										65
									
								
								app/src/main/java/org/schabi/newpipe/ktx/Throwable.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								app/src/main/java/org/schabi/newpipe/ktx/Throwable.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
| } | ||||
| @@ -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) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
| @@ -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 | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -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; | ||||
|   | ||||
| @@ -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)) | ||||
|     } | ||||
| } | ||||
| @@ -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)) | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user
	 Isira Seneviratne
					Isira Seneviratne