@@ -0,0 +1,10 @@
|
|||||||
|
package org.koitharu.kotatsu.core.exceptions
|
||||||
|
|
||||||
|
import org.koitharu.kotatsu.details.ui.pager.EmptyMangaReason
|
||||||
|
import org.koitharu.kotatsu.parsers.model.Manga
|
||||||
|
|
||||||
|
class EmptyMangaException(
|
||||||
|
val reason: EmptyMangaReason?,
|
||||||
|
val manga: Manga,
|
||||||
|
cause: Throwable?
|
||||||
|
) : IllegalStateException(cause)
|
||||||
@@ -16,6 +16,7 @@ import org.koitharu.kotatsu.R
|
|||||||
import org.koitharu.kotatsu.browser.BrowserActivity
|
import org.koitharu.kotatsu.browser.BrowserActivity
|
||||||
import org.koitharu.kotatsu.browser.cloudflare.CloudFlareActivity
|
import org.koitharu.kotatsu.browser.cloudflare.CloudFlareActivity
|
||||||
import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException
|
import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException
|
||||||
|
import org.koitharu.kotatsu.core.exceptions.EmptyMangaException
|
||||||
import org.koitharu.kotatsu.core.exceptions.InteractiveActionRequiredException
|
import org.koitharu.kotatsu.core.exceptions.InteractiveActionRequiredException
|
||||||
import org.koitharu.kotatsu.core.exceptions.ProxyConfigException
|
import org.koitharu.kotatsu.core.exceptions.ProxyConfigException
|
||||||
import org.koitharu.kotatsu.core.exceptions.UnsupportedSourceException
|
import org.koitharu.kotatsu.core.exceptions.UnsupportedSourceException
|
||||||
@@ -25,6 +26,7 @@ import org.koitharu.kotatsu.core.prefs.AppSettings
|
|||||||
import org.koitharu.kotatsu.core.ui.dialog.buildAlertDialog
|
import org.koitharu.kotatsu.core.ui.dialog.buildAlertDialog
|
||||||
import org.koitharu.kotatsu.core.util.ext.isHttpUrl
|
import org.koitharu.kotatsu.core.util.ext.isHttpUrl
|
||||||
import org.koitharu.kotatsu.core.util.ext.restartApplication
|
import org.koitharu.kotatsu.core.util.ext.restartApplication
|
||||||
|
import org.koitharu.kotatsu.details.ui.pager.EmptyMangaReason
|
||||||
import org.koitharu.kotatsu.parsers.exception.AuthRequiredException
|
import org.koitharu.kotatsu.parsers.exception.AuthRequiredException
|
||||||
import org.koitharu.kotatsu.parsers.exception.NotFoundException
|
import org.koitharu.kotatsu.parsers.exception.NotFoundException
|
||||||
import org.koitharu.kotatsu.parsers.model.Manga
|
import org.koitharu.kotatsu.parsers.model.Manga
|
||||||
@@ -83,6 +85,16 @@ class ExceptionResolver private constructor(
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is EmptyMangaException -> {
|
||||||
|
when (e.reason) {
|
||||||
|
EmptyMangaReason.NO_CHAPTERS -> openAlternatives(e.manga)
|
||||||
|
EmptyMangaReason.LOADING_ERROR -> Unit
|
||||||
|
EmptyMangaReason.RESTRICTED -> host.router.openBrowser(e.manga)
|
||||||
|
else -> Unit
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
is UnsupportedSourceException -> {
|
is UnsupportedSourceException -> {
|
||||||
e.manga?.let { openAlternatives(it) }
|
e.manga?.let { openAlternatives(it) }
|
||||||
false
|
false
|
||||||
@@ -229,6 +241,12 @@ class ExceptionResolver private constructor(
|
|||||||
|
|
||||||
is InteractiveActionRequiredException -> R.string._continue
|
is InteractiveActionRequiredException -> R.string._continue
|
||||||
|
|
||||||
|
is EmptyMangaException -> when (e.reason) {
|
||||||
|
EmptyMangaReason.RESTRICTED -> if (e.manga.publicUrl.isHttpUrl()) R.string.open_in_browser else 0
|
||||||
|
EmptyMangaReason.NO_CHAPTERS -> R.string.alternatives
|
||||||
|
else -> 0
|
||||||
|
}
|
||||||
|
|
||||||
else -> 0
|
else -> 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -22,6 +22,7 @@ import org.koitharu.kotatsu.core.exceptions.CaughtException
|
|||||||
import org.koitharu.kotatsu.core.exceptions.CloudFlareBlockedException
|
import org.koitharu.kotatsu.core.exceptions.CloudFlareBlockedException
|
||||||
import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException
|
import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException
|
||||||
import org.koitharu.kotatsu.core.exceptions.EmptyHistoryException
|
import org.koitharu.kotatsu.core.exceptions.EmptyHistoryException
|
||||||
|
import org.koitharu.kotatsu.core.exceptions.EmptyMangaException
|
||||||
import org.koitharu.kotatsu.core.exceptions.IncompatiblePluginException
|
import org.koitharu.kotatsu.core.exceptions.IncompatiblePluginException
|
||||||
import org.koitharu.kotatsu.core.exceptions.InteractiveActionRequiredException
|
import org.koitharu.kotatsu.core.exceptions.InteractiveActionRequiredException
|
||||||
import org.koitharu.kotatsu.core.exceptions.NoDataReceivedException
|
import org.koitharu.kotatsu.core.exceptions.NoDataReceivedException
|
||||||
@@ -62,216 +63,219 @@ private const val IMAGE_FORMAT_NOT_SUPPORTED = "Image format not supported"
|
|||||||
private val FNFE_MESSAGE_REGEX = Regex("^(/[^\\s:]+)?.+?\\s([A-Z]{2,6})?\\s.+$")
|
private val FNFE_MESSAGE_REGEX = Regex("^(/[^\\s:]+)?.+?\\s([A-Z]{2,6})?\\s.+$")
|
||||||
|
|
||||||
fun Throwable.getDisplayMessage(resources: Resources): String = getDisplayMessageOrNull(resources)
|
fun Throwable.getDisplayMessage(resources: Resources): String = getDisplayMessageOrNull(resources)
|
||||||
?: resources.getString(R.string.error_occurred)
|
?: resources.getString(R.string.error_occurred)
|
||||||
|
|
||||||
private fun Throwable.getDisplayMessageOrNull(resources: Resources): String? = when (this) {
|
private fun Throwable.getDisplayMessageOrNull(resources: Resources): String? = when (this) {
|
||||||
is CancellationException -> cause?.getDisplayMessageOrNull(resources) ?: message
|
is CancellationException -> cause?.getDisplayMessageOrNull(resources) ?: message
|
||||||
is CaughtException -> cause.getDisplayMessageOrNull(resources)
|
is CaughtException -> cause.getDisplayMessageOrNull(resources)
|
||||||
is WrapperIOException -> cause.getDisplayMessageOrNull(resources)
|
is WrapperIOException -> cause.getDisplayMessageOrNull(resources)
|
||||||
is ScrobblerAuthRequiredException -> resources.getString(
|
is ScrobblerAuthRequiredException -> resources.getString(
|
||||||
R.string.scrobbler_auth_required,
|
R.string.scrobbler_auth_required,
|
||||||
resources.getString(scrobbler.titleResId),
|
resources.getString(scrobbler.titleResId),
|
||||||
)
|
)
|
||||||
|
|
||||||
is AuthRequiredException -> resources.getString(R.string.auth_required)
|
is AuthRequiredException -> resources.getString(R.string.auth_required)
|
||||||
is InteractiveActionRequiredException -> resources.getString(R.string.additional_action_required)
|
is InteractiveActionRequiredException -> resources.getString(R.string.additional_action_required)
|
||||||
is CloudFlareProtectedException -> resources.getString(R.string.captcha_required_message)
|
is CloudFlareProtectedException -> resources.getString(R.string.captcha_required_message)
|
||||||
is CloudFlareBlockedException -> resources.getString(R.string.blocked_by_server_message)
|
is CloudFlareBlockedException -> resources.getString(R.string.blocked_by_server_message)
|
||||||
is ActivityNotFoundException,
|
is ActivityNotFoundException,
|
||||||
is UnsupportedOperationException,
|
is UnsupportedOperationException,
|
||||||
-> resources.getString(R.string.operation_not_supported)
|
-> resources.getString(R.string.operation_not_supported)
|
||||||
|
|
||||||
is TooManyRequestExceptions -> {
|
is TooManyRequestExceptions -> {
|
||||||
val delay = getRetryDelay()
|
val delay = getRetryDelay()
|
||||||
val formattedTime = if (delay > 0L && delay < Long.MAX_VALUE) {
|
val formattedTime = if (delay > 0L && delay < Long.MAX_VALUE) {
|
||||||
resources.formatDurationShort(delay)
|
resources.formatDurationShort(delay)
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
if (formattedTime != null) {
|
if (formattedTime != null) {
|
||||||
resources.getString(R.string.too_many_requests_message_retry, formattedTime)
|
resources.getString(R.string.too_many_requests_message_retry, formattedTime)
|
||||||
} else {
|
} else {
|
||||||
resources.getString(R.string.too_many_requests_message)
|
resources.getString(R.string.too_many_requests_message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is ZipException -> resources.getString(R.string.error_corrupted_zip, this.message.orEmpty())
|
is ZipException -> resources.getString(R.string.error_corrupted_zip, this.message.orEmpty())
|
||||||
is SQLiteFullException -> resources.getString(R.string.error_no_space_left)
|
is SQLiteFullException -> resources.getString(R.string.error_no_space_left)
|
||||||
is UnsupportedFileException -> resources.getString(R.string.text_file_not_supported)
|
is UnsupportedFileException -> resources.getString(R.string.text_file_not_supported)
|
||||||
is BadBackupFormatException -> resources.getString(R.string.unsupported_backup_message)
|
is BadBackupFormatException -> resources.getString(R.string.unsupported_backup_message)
|
||||||
is FileNotFoundException -> parseMessage(resources) ?: message
|
is FileNotFoundException -> parseMessage(resources) ?: message
|
||||||
is AccessDeniedException -> resources.getString(R.string.no_access_to_file)
|
is AccessDeniedException -> resources.getString(R.string.no_access_to_file)
|
||||||
is NonFileUriException -> resources.getString(R.string.error_non_file_uri)
|
is NonFileUriException -> resources.getString(R.string.error_non_file_uri)
|
||||||
is EmptyHistoryException -> resources.getString(R.string.history_is_empty)
|
is EmptyHistoryException -> resources.getString(R.string.history_is_empty)
|
||||||
is ProxyConfigException -> resources.getString(R.string.invalid_proxy_configuration)
|
is EmptyMangaException -> reason?.let { resources.getString(it.msgResId) } ?: cause?.getDisplayMessage(resources)
|
||||||
is SyncApiException,
|
is ProxyConfigException -> resources.getString(R.string.invalid_proxy_configuration)
|
||||||
is ContentUnavailableException -> message
|
is SyncApiException,
|
||||||
|
is ContentUnavailableException -> message
|
||||||
|
|
||||||
is ParseException -> shortMessage
|
is ParseException -> shortMessage
|
||||||
is ConnectException,
|
is ConnectException,
|
||||||
is UnknownHostException,
|
is UnknownHostException,
|
||||||
is NoRouteToHostException,
|
is NoRouteToHostException,
|
||||||
is SocketTimeoutException -> resources.getString(R.string.network_error)
|
is SocketTimeoutException -> resources.getString(R.string.network_error)
|
||||||
|
|
||||||
is ImageDecodeException -> {
|
is ImageDecodeException -> {
|
||||||
val type = format?.substringBefore('/')
|
val type = format?.substringBefore('/')
|
||||||
val formatString = format.ifNullOrEmpty { resources.getString(R.string.unknown).lowercase(Locale.getDefault()) }
|
val formatString = format.ifNullOrEmpty { resources.getString(R.string.unknown).lowercase(Locale.getDefault()) }
|
||||||
if (type.isNullOrEmpty() || type == "image") {
|
if (type.isNullOrEmpty() || type == "image") {
|
||||||
resources.getString(R.string.error_image_format, formatString)
|
resources.getString(R.string.error_image_format, formatString)
|
||||||
} else {
|
} else {
|
||||||
resources.getString(R.string.error_not_image, formatString)
|
resources.getString(R.string.error_not_image, formatString)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is NoDataReceivedException -> resources.getString(R.string.error_no_data_received)
|
is NoDataReceivedException -> resources.getString(R.string.error_no_data_received)
|
||||||
is IncompatiblePluginException -> {
|
is IncompatiblePluginException -> {
|
||||||
cause?.getDisplayMessageOrNull(resources)?.let {
|
cause?.getDisplayMessageOrNull(resources)?.let {
|
||||||
resources.getString(R.string.plugin_incompatible_with_cause, it)
|
resources.getString(R.string.plugin_incompatible_with_cause, it)
|
||||||
} ?: resources.getString(R.string.plugin_incompatible)
|
} ?: resources.getString(R.string.plugin_incompatible)
|
||||||
}
|
}
|
||||||
|
|
||||||
is WrongPasswordException -> resources.getString(R.string.wrong_password)
|
is WrongPasswordException -> resources.getString(R.string.wrong_password)
|
||||||
is NotFoundException -> resources.getString(R.string.not_found_404)
|
is NotFoundException -> resources.getString(R.string.not_found_404)
|
||||||
is UnsupportedSourceException -> resources.getString(R.string.unsupported_source)
|
is UnsupportedSourceException -> resources.getString(R.string.unsupported_source)
|
||||||
|
|
||||||
is HttpException -> getHttpDisplayMessage(response.code, resources)
|
is HttpException -> getHttpDisplayMessage(response.code, resources)
|
||||||
is HttpStatusException -> getHttpDisplayMessage(statusCode, resources)
|
is HttpStatusException -> getHttpDisplayMessage(statusCode, resources)
|
||||||
|
|
||||||
else -> mapDisplayMessage(message, resources) ?: message
|
else -> mapDisplayMessage(message, resources) ?: message
|
||||||
}.takeUnless { it.isNullOrBlank() }
|
}.takeUnless { it.isNullOrBlank() }
|
||||||
|
|
||||||
@DrawableRes
|
@DrawableRes
|
||||||
fun Throwable.getDisplayIcon(): Int = when (this) {
|
fun Throwable.getDisplayIcon(): Int = when (this) {
|
||||||
is AuthRequiredException -> R.drawable.ic_auth_key_large
|
is AuthRequiredException -> R.drawable.ic_auth_key_large
|
||||||
is CloudFlareProtectedException -> R.drawable.ic_bot_large
|
is CloudFlareProtectedException -> R.drawable.ic_bot_large
|
||||||
is UnknownHostException,
|
is UnknownHostException,
|
||||||
is SocketTimeoutException,
|
is SocketTimeoutException,
|
||||||
is ConnectException,
|
is ConnectException,
|
||||||
is NoRouteToHostException,
|
is NoRouteToHostException,
|
||||||
is ProtocolException -> R.drawable.ic_plug_large
|
is ProtocolException -> R.drawable.ic_plug_large
|
||||||
|
|
||||||
is CloudFlareBlockedException -> R.drawable.ic_denied_large
|
is CloudFlareBlockedException -> R.drawable.ic_denied_large
|
||||||
|
|
||||||
is InteractiveActionRequiredException -> R.drawable.ic_interaction_large
|
is InteractiveActionRequiredException -> R.drawable.ic_interaction_large
|
||||||
else -> R.drawable.ic_error_large
|
else -> R.drawable.ic_error_large
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Throwable.getCauseUrl(): String? = when (this) {
|
fun Throwable.getCauseUrl(): String? = when (this) {
|
||||||
is ParseException -> url
|
is ParseException -> url
|
||||||
is NotFoundException -> url
|
is NotFoundException -> url
|
||||||
is TooManyRequestExceptions -> url
|
is TooManyRequestExceptions -> url
|
||||||
is CaughtException -> cause.getCauseUrl()
|
is CaughtException -> cause.getCauseUrl()
|
||||||
is WrapperIOException -> cause.getCauseUrl()
|
is WrapperIOException -> cause.getCauseUrl()
|
||||||
is NoDataReceivedException -> url
|
is NoDataReceivedException -> url
|
||||||
is CloudFlareBlockedException -> url
|
is CloudFlareBlockedException -> url
|
||||||
is CloudFlareProtectedException -> url
|
is CloudFlareProtectedException -> url
|
||||||
is InteractiveActionRequiredException -> url
|
is InteractiveActionRequiredException -> url
|
||||||
is HttpStatusException -> url
|
is HttpStatusException -> url
|
||||||
is HttpException -> (response.delegate as? Response)?.request?.url?.toString()
|
is UnsupportedSourceException -> manga?.publicUrl?.takeIf { it.isHttpUrl() }
|
||||||
else -> null
|
is EmptyMangaException -> manga.publicUrl.takeIf { it.isHttpUrl() }
|
||||||
|
is HttpException -> (response.delegate as? Response)?.request?.url?.toString()
|
||||||
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getHttpDisplayMessage(statusCode: Int, resources: Resources): String? = when (statusCode) {
|
private fun getHttpDisplayMessage(statusCode: Int, resources: Resources): String? = when (statusCode) {
|
||||||
HttpURLConnection.HTTP_NOT_FOUND -> resources.getString(R.string.not_found_404)
|
HttpURLConnection.HTTP_NOT_FOUND -> resources.getString(R.string.not_found_404)
|
||||||
HttpURLConnection.HTTP_FORBIDDEN -> resources.getString(R.string.access_denied_403)
|
HttpURLConnection.HTTP_FORBIDDEN -> resources.getString(R.string.access_denied_403)
|
||||||
HttpURLConnection.HTTP_GATEWAY_TIMEOUT -> resources.getString(R.string.network_unavailable)
|
HttpURLConnection.HTTP_GATEWAY_TIMEOUT -> resources.getString(R.string.network_unavailable)
|
||||||
in 500..599 -> resources.getString(R.string.server_error, statusCode)
|
in 500..599 -> resources.getString(R.string.server_error, statusCode)
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun mapDisplayMessage(msg: String?, resources: Resources): String? = when {
|
private fun mapDisplayMessage(msg: String?, resources: Resources): String? = when {
|
||||||
msg.isNullOrEmpty() -> null
|
msg.isNullOrEmpty() -> null
|
||||||
msg.contains(MSG_NO_SPACE_LEFT) -> resources.getString(R.string.error_no_space_left)
|
msg.contains(MSG_NO_SPACE_LEFT) -> resources.getString(R.string.error_no_space_left)
|
||||||
msg.contains(IMAGE_FORMAT_NOT_SUPPORTED) -> resources.getString(R.string.error_corrupted_file)
|
msg.contains(IMAGE_FORMAT_NOT_SUPPORTED) -> resources.getString(R.string.error_corrupted_file)
|
||||||
msg == MSG_CONNECTION_RESET -> resources.getString(R.string.error_connection_reset)
|
msg == MSG_CONNECTION_RESET -> resources.getString(R.string.error_connection_reset)
|
||||||
msg == FILTER_MULTIPLE_GENRES_NOT_SUPPORTED -> resources.getString(R.string.error_multiple_genres_not_supported)
|
msg == FILTER_MULTIPLE_GENRES_NOT_SUPPORTED -> resources.getString(R.string.error_multiple_genres_not_supported)
|
||||||
msg == FILTER_MULTIPLE_STATES_NOT_SUPPORTED -> resources.getString(R.string.error_multiple_states_not_supported)
|
msg == FILTER_MULTIPLE_STATES_NOT_SUPPORTED -> resources.getString(R.string.error_multiple_states_not_supported)
|
||||||
msg == SEARCH_NOT_SUPPORTED -> resources.getString(R.string.error_search_not_supported)
|
msg == SEARCH_NOT_SUPPORTED -> resources.getString(R.string.error_search_not_supported)
|
||||||
msg == FILTER_BOTH_LOCALE_GENRES_NOT_SUPPORTED -> resources.getString(R.string.error_filter_locale_genre_not_supported)
|
msg == FILTER_BOTH_LOCALE_GENRES_NOT_SUPPORTED -> resources.getString(R.string.error_filter_locale_genre_not_supported)
|
||||||
msg == FILTER_BOTH_STATES_GENRES_NOT_SUPPORTED -> resources.getString(R.string.error_filter_states_genre_not_supported)
|
msg == FILTER_BOTH_STATES_GENRES_NOT_SUPPORTED -> resources.getString(R.string.error_filter_states_genre_not_supported)
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Throwable.isReportable(): Boolean {
|
fun Throwable.isReportable(): Boolean {
|
||||||
if (this is Error) {
|
if (this is Error) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if (this is CaughtException) {
|
if (this is CaughtException) {
|
||||||
return cause.isReportable()
|
return cause.isReportable()
|
||||||
}
|
}
|
||||||
if (this is WrapperIOException) {
|
if (this is WrapperIOException) {
|
||||||
return cause.isReportable()
|
return cause.isReportable()
|
||||||
}
|
}
|
||||||
if (ExceptionResolver.canResolve(this)) {
|
if (ExceptionResolver.canResolve(this)) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if (this is ParseException
|
if (this is ParseException
|
||||||
|| this.isNetworkError()
|
|| this.isNetworkError()
|
||||||
|| this is CloudFlareBlockedException
|
|| this is CloudFlareBlockedException
|
||||||
|| this is CloudFlareProtectedException
|
|| this is CloudFlareProtectedException
|
||||||
|| this is BadBackupFormatException
|
|| this is BadBackupFormatException
|
||||||
|| this is WrongPasswordException
|
|| this is WrongPasswordException
|
||||||
|| this is TooManyRequestExceptions
|
|| this is TooManyRequestExceptions
|
||||||
|| this is HttpStatusException
|
|| this is HttpStatusException
|
||||||
) {
|
) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Throwable.isNetworkError(): Boolean {
|
fun Throwable.isNetworkError(): Boolean {
|
||||||
return this is UnknownHostException
|
return this is UnknownHostException
|
||||||
|| this is SocketTimeoutException
|
|| this is SocketTimeoutException
|
||||||
|| this is StreamResetException
|
|| this is StreamResetException
|
||||||
|| this is SocketException
|
|| this is SocketException
|
||||||
|| this is HttpException && response.code == HttpURLConnection.HTTP_GATEWAY_TIMEOUT
|
|| this is HttpException && response.code == HttpURLConnection.HTTP_GATEWAY_TIMEOUT
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Throwable.report(silent: Boolean = false) {
|
fun Throwable.report(silent: Boolean = false) {
|
||||||
val exception = CaughtException(this)
|
val exception = CaughtException(this)
|
||||||
if (!silent) {
|
if (!silent) {
|
||||||
exception.sendWithAcra()
|
exception.sendWithAcra()
|
||||||
} else if (!BuildConfig.DEBUG) {
|
} else if (!BuildConfig.DEBUG) {
|
||||||
exception.sendSilentlyWithAcra()
|
exception.sendSilentlyWithAcra()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Throwable.isWebViewUnavailable(): Boolean {
|
fun Throwable.isWebViewUnavailable(): Boolean {
|
||||||
val trace = stackTraceToString()
|
val trace = stackTraceToString()
|
||||||
return trace.contains("android.webkit.WebView.<init>")
|
return trace.contains("android.webkit.WebView.<init>")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("FunctionName")
|
@Suppress("FunctionName")
|
||||||
fun NoSpaceLeftException() = IOException(MSG_NO_SPACE_LEFT)
|
fun NoSpaceLeftException() = IOException(MSG_NO_SPACE_LEFT)
|
||||||
|
|
||||||
fun FileNotFoundException.getFile(): File? {
|
fun FileNotFoundException.getFile(): File? {
|
||||||
val groups = FNFE_MESSAGE_REGEX.matchEntire(message ?: return null)?.groupValues ?: return null
|
val groups = FNFE_MESSAGE_REGEX.matchEntire(message ?: return null)?.groupValues ?: return null
|
||||||
return groups.getOrNull(1)?.let { File(it) }
|
return groups.getOrNull(1)?.let { File(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun FileNotFoundException.parseMessage(resources: Resources): String? {
|
fun FileNotFoundException.parseMessage(resources: Resources): String? {
|
||||||
/*
|
/*
|
||||||
Examples:
|
Examples:
|
||||||
/storage/0000-0000/Android/media/d1f08350-0c25-460b-8f50-008e49de3873.jpg.tmp: open failed: EROFS (Read-only file system)
|
/storage/0000-0000/Android/media/d1f08350-0c25-460b-8f50-008e49de3873.jpg.tmp: open failed: EROFS (Read-only file system)
|
||||||
/storage/emulated/0/Android/data/org.koitharu.kotatsu/cache/pages/fe06e192fa371e55918980f7a24c91ea.jpg: open failed: ENOENT (No such file or directory)
|
/storage/emulated/0/Android/data/org.koitharu.kotatsu/cache/pages/fe06e192fa371e55918980f7a24c91ea.jpg: open failed: ENOENT (No such file or directory)
|
||||||
/storage/0000-0000/Android/data/org.koitharu.kotatsu/files/manga/e57d3af4-216e-48b2-8432-1541d58eea1e.tmp (I/O error)
|
/storage/0000-0000/Android/data/org.koitharu.kotatsu/files/manga/e57d3af4-216e-48b2-8432-1541d58eea1e.tmp (I/O error)
|
||||||
*/
|
*/
|
||||||
val groups = FNFE_MESSAGE_REGEX.matchEntire(message ?: return null)?.groupValues ?: return null
|
val groups = FNFE_MESSAGE_REGEX.matchEntire(message ?: return null)?.groupValues ?: return null
|
||||||
val path = groups.getOrNull(1)
|
val path = groups.getOrNull(1)
|
||||||
val error = groups.getOrNull(2)
|
val error = groups.getOrNull(2)
|
||||||
val baseMessageIs = when (error) {
|
val baseMessageIs = when (error) {
|
||||||
"EROFS" -> R.string.no_write_permission_to_file
|
"EROFS" -> R.string.no_write_permission_to_file
|
||||||
"ENOENT" -> R.string.file_not_found
|
"ENOENT" -> R.string.file_not_found
|
||||||
else -> return null
|
else -> return null
|
||||||
}
|
}
|
||||||
return if (path.isNullOrEmpty()) {
|
return if (path.isNullOrEmpty()) {
|
||||||
resources.getString(baseMessageIs)
|
resources.getString(baseMessageIs)
|
||||||
} else {
|
} else {
|
||||||
resources.getString(
|
resources.getString(
|
||||||
R.string.inline_preference_pattern,
|
R.string.inline_preference_pattern,
|
||||||
resources.getString(baseMessageIs),
|
resources.getString(baseMessageIs),
|
||||||
path,
|
path,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,111 +7,115 @@ import org.koitharu.kotatsu.core.ui.model.MangaOverride
|
|||||||
import org.koitharu.kotatsu.local.domain.model.LocalManga
|
import org.koitharu.kotatsu.local.domain.model.LocalManga
|
||||||
import org.koitharu.kotatsu.parsers.model.Manga
|
import org.koitharu.kotatsu.parsers.model.Manga
|
||||||
import org.koitharu.kotatsu.parsers.model.MangaChapter
|
import org.koitharu.kotatsu.parsers.model.MangaChapter
|
||||||
|
import org.koitharu.kotatsu.parsers.model.MangaState
|
||||||
import org.koitharu.kotatsu.parsers.util.ifNullOrEmpty
|
import org.koitharu.kotatsu.parsers.util.ifNullOrEmpty
|
||||||
import org.koitharu.kotatsu.parsers.util.nullIfEmpty
|
import org.koitharu.kotatsu.parsers.util.nullIfEmpty
|
||||||
import org.koitharu.kotatsu.reader.data.filterChapters
|
import org.koitharu.kotatsu.reader.data.filterChapters
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
data class MangaDetails(
|
data class MangaDetails(
|
||||||
private val manga: Manga,
|
private val manga: Manga,
|
||||||
private val localManga: LocalManga?,
|
private val localManga: LocalManga?,
|
||||||
private val override: MangaOverride?,
|
private val override: MangaOverride?,
|
||||||
val description: CharSequence?,
|
val description: CharSequence?,
|
||||||
val isLoaded: Boolean,
|
val isLoaded: Boolean,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
constructor(manga: Manga) : this(
|
constructor(manga: Manga) : this(
|
||||||
manga = manga,
|
manga = manga,
|
||||||
localManga = null,
|
localManga = null,
|
||||||
override = null,
|
override = null,
|
||||||
description = null,
|
description = null,
|
||||||
isLoaded = false,
|
isLoaded = false,
|
||||||
)
|
)
|
||||||
|
|
||||||
val id: Long
|
val id: Long
|
||||||
get() = manga.id
|
get() = manga.id
|
||||||
|
|
||||||
val allChapters: List<MangaChapter> by lazy { mergeChapters() }
|
val allChapters: List<MangaChapter> by lazy { mergeChapters() }
|
||||||
|
|
||||||
val chapters: Map<String?, List<MangaChapter>> by lazy {
|
val chapters: Map<String?, List<MangaChapter>> by lazy {
|
||||||
allChapters.groupBy { it.branch }
|
allChapters.groupBy { it.branch }
|
||||||
}
|
}
|
||||||
|
|
||||||
val isLocal
|
val isLocal
|
||||||
get() = manga.isLocal
|
get() = manga.isLocal
|
||||||
|
|
||||||
val local: LocalManga?
|
val local: LocalManga?
|
||||||
get() = localManga ?: if (manga.isLocal) LocalManga(manga) else null
|
get() = localManga ?: if (manga.isLocal) LocalManga(manga) else null
|
||||||
|
|
||||||
val coverUrl: String?
|
val coverUrl: String?
|
||||||
get() = override?.coverUrl
|
get() = override?.coverUrl
|
||||||
.ifNullOrEmpty { manga.largeCoverUrl }
|
.ifNullOrEmpty { manga.largeCoverUrl }
|
||||||
.ifNullOrEmpty { manga.coverUrl }
|
.ifNullOrEmpty { manga.coverUrl }
|
||||||
.ifNullOrEmpty { localManga?.manga?.coverUrl }
|
.ifNullOrEmpty { localManga?.manga?.coverUrl }
|
||||||
?.nullIfEmpty()
|
?.nullIfEmpty()
|
||||||
|
|
||||||
private val mergedManga by lazy {
|
val isRestricted: Boolean
|
||||||
if (localManga == null) {
|
get() = manga.state == MangaState.RESTRICTED
|
||||||
// fast path
|
|
||||||
manga.withOverride(override)
|
|
||||||
} else {
|
|
||||||
manga.copy(
|
|
||||||
title = override?.title.ifNullOrEmpty { manga.title },
|
|
||||||
coverUrl = override?.coverUrl.ifNullOrEmpty { manga.coverUrl },
|
|
||||||
largeCoverUrl = override?.coverUrl.ifNullOrEmpty { manga.largeCoverUrl },
|
|
||||||
contentRating = override?.contentRating ?: manga.contentRating,
|
|
||||||
chapters = allChapters,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun toManga() = mergedManga
|
private val mergedManga by lazy {
|
||||||
|
if (localManga == null) {
|
||||||
|
// fast path
|
||||||
|
manga.withOverride(override)
|
||||||
|
} else {
|
||||||
|
manga.copy(
|
||||||
|
title = override?.title.ifNullOrEmpty { manga.title },
|
||||||
|
coverUrl = override?.coverUrl.ifNullOrEmpty { manga.coverUrl },
|
||||||
|
largeCoverUrl = override?.coverUrl.ifNullOrEmpty { manga.largeCoverUrl },
|
||||||
|
contentRating = override?.contentRating ?: manga.contentRating,
|
||||||
|
chapters = allChapters,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun getLocale(): Locale? {
|
fun toManga() = mergedManga
|
||||||
findAppropriateLocale(chapters.keys.singleOrNull())?.let {
|
|
||||||
return it
|
|
||||||
}
|
|
||||||
return manga.source.getLocale()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun filterChapters(branch: String?) = copy(
|
fun getLocale(): Locale? {
|
||||||
manga = manga.filterChapters(branch),
|
findAppropriateLocale(chapters.keys.singleOrNull())?.let {
|
||||||
localManga = localManga?.run {
|
return it
|
||||||
copy(manga = manga.filterChapters(branch))
|
}
|
||||||
},
|
return manga.source.getLocale()
|
||||||
)
|
}
|
||||||
|
|
||||||
private fun mergeChapters(): List<MangaChapter> {
|
fun filterChapters(branch: String?) = copy(
|
||||||
val chapters = manga.chapters
|
manga = manga.filterChapters(branch),
|
||||||
val localChapters = local?.manga?.chapters.orEmpty()
|
localManga = localManga?.run {
|
||||||
if (chapters.isNullOrEmpty()) {
|
copy(manga = manga.filterChapters(branch))
|
||||||
return localChapters
|
},
|
||||||
}
|
)
|
||||||
val localMap = if (localChapters.isNotEmpty()) {
|
|
||||||
localChapters.associateByTo(LinkedHashMap(localChapters.size)) { it.id }
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
val result = ArrayList<MangaChapter>(chapters.size)
|
|
||||||
for (chapter in chapters) {
|
|
||||||
val local = localMap?.remove(chapter.id)
|
|
||||||
result += local ?: chapter
|
|
||||||
}
|
|
||||||
if (!localMap.isNullOrEmpty()) {
|
|
||||||
result.addAll(localMap.values)
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun findAppropriateLocale(name: String?): Locale? {
|
private fun mergeChapters(): List<MangaChapter> {
|
||||||
if (name.isNullOrEmpty()) {
|
val chapters = manga.chapters
|
||||||
return null
|
val localChapters = local?.manga?.chapters.orEmpty()
|
||||||
}
|
if (chapters.isNullOrEmpty()) {
|
||||||
return Locale.getAvailableLocales().find { lc ->
|
return localChapters
|
||||||
name.contains(lc.getDisplayName(lc), ignoreCase = true) ||
|
}
|
||||||
name.contains(lc.getDisplayName(Locale.ENGLISH), ignoreCase = true) ||
|
val localMap = if (localChapters.isNotEmpty()) {
|
||||||
name.contains(lc.getDisplayLanguage(lc), ignoreCase = true) ||
|
localChapters.associateByTo(LinkedHashMap(localChapters.size)) { it.id }
|
||||||
name.contains(lc.getDisplayLanguage(Locale.ENGLISH), ignoreCase = true)
|
} else {
|
||||||
}
|
null
|
||||||
}
|
}
|
||||||
|
val result = ArrayList<MangaChapter>(chapters.size)
|
||||||
|
for (chapter in chapters) {
|
||||||
|
val local = localMap?.remove(chapter.id)
|
||||||
|
result += local ?: chapter
|
||||||
|
}
|
||||||
|
if (!localMap.isNullOrEmpty()) {
|
||||||
|
result.addAll(localMap.values)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun findAppropriateLocale(name: String?): Locale? {
|
||||||
|
if (name.isNullOrEmpty()) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return Locale.getAvailableLocales().find { lc ->
|
||||||
|
name.contains(lc.getDisplayName(lc), ignoreCase = true) ||
|
||||||
|
name.contains(lc.getDisplayName(Locale.ENGLISH), ignoreCase = true) ||
|
||||||
|
name.contains(lc.getDisplayLanguage(lc), ignoreCase = true) ||
|
||||||
|
name.contains(lc.getDisplayLanguage(Locale.ENGLISH), ignoreCase = true)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user