From 9bb76cc0b2fca17de5afb420d8d7fd8f18362049 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Sat, 26 Oct 2024 09:03:50 +0300 Subject: [PATCH 1/4] Update parsers (fix json iterator) --- app/build.gradle | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index afbbe062b..c2923127b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -16,8 +16,8 @@ android { applicationId 'org.koitharu.kotatsu' minSdk = 21 targetSdk = 35 - versionCode = 678 - versionName = '7.6.5' + versionCode = 679 + versionName = '7.6.6' generatedDensities = [] testInstrumentationRunner 'org.koitharu.kotatsu.HiltTestRunner' ksp { @@ -82,7 +82,7 @@ afterEvaluate { } } dependencies { - implementation('com.github.KotatsuApp:kotatsu-parsers:3d5cc5ceff') { + implementation('com.github.KotatsuApp:kotatsu-parsers:4c5ed57958') { exclude group: 'org.json', module: 'json' } From b036a8ed942114d2cb385812445493a6b82985cb Mon Sep 17 00:00:00 2001 From: Koitharu Date: Mon, 4 Nov 2024 10:02:08 +0200 Subject: [PATCH 2/4] Fixes batch --- .../org/koitharu/kotatsu/StrictModeNotifier.kt | 15 ++++++++++++--- app/src/debug/res/drawable-anydpi-v24/ic_bug.xml | 15 +++++++++++++++ app/src/debug/res/drawable-hdpi/ic_bug.png | Bin 0 -> 417 bytes app/src/debug/res/drawable-mdpi/ic_bug.png | Bin 0 -> 308 bytes app/src/debug/res/drawable-xhdpi/ic_bug.png | Bin 0 -> 480 bytes app/src/debug/res/drawable-xxhdpi/ic_bug.png | Bin 0 -> 792 bytes .../browser/cloudflare/CaptchaNotifier.kt | 6 +++--- .../exceptions/resolve/DialogErrorObserver.kt | 3 ++- .../exceptions/resolve/SnackbarErrorObserver.kt | 3 ++- .../koitharu/kotatsu/core/io/NullOutputStream.kt | 13 +++++++++++++ .../koitharu/kotatsu/core/util/ShareHelper.kt | 13 ++++++------- .../org/koitharu/kotatsu/core/util/ext/Bundle.kt | 4 ++++ .../koitharu/kotatsu/core/util/ext/Throwable.kt | 8 ++++++++ .../kotatsu/details/ui/DetailsErrorObserver.kt | 3 ++- .../reader/ui/pager/standard/PageHolder.kt | 2 ++ .../reader/ui/pager/webtoon/WebtoonHolder.kt | 2 ++ app/src/main/res/menu/opt_local.xml | 8 +++++++- 17 files changed, 78 insertions(+), 17 deletions(-) create mode 100644 app/src/debug/res/drawable-anydpi-v24/ic_bug.xml create mode 100644 app/src/debug/res/drawable-hdpi/ic_bug.png create mode 100644 app/src/debug/res/drawable-mdpi/ic_bug.png create mode 100644 app/src/debug/res/drawable-xhdpi/ic_bug.png create mode 100644 app/src/debug/res/drawable-xxhdpi/ic_bug.png create mode 100644 app/src/main/kotlin/org/koitharu/kotatsu/core/io/NullOutputStream.kt diff --git a/app/src/debug/kotlin/org/koitharu/kotatsu/StrictModeNotifier.kt b/app/src/debug/kotlin/org/koitharu/kotatsu/StrictModeNotifier.kt index 8847cb601..058e53c09 100644 --- a/app/src/debug/kotlin/org/koitharu/kotatsu/StrictModeNotifier.kt +++ b/app/src/debug/kotlin/org/koitharu/kotatsu/StrictModeNotifier.kt @@ -9,11 +9,12 @@ import android.os.Build import android.os.StrictMode import android.os.strictmode.Violation import androidx.annotation.RequiresApi +import androidx.core.app.PendingIntentCompat import androidx.core.content.getSystemService import androidx.fragment.app.strictmode.FragmentStrictMode import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.asExecutor -import org.koitharu.kotatsu.core.ErrorReporterReceiver +import org.koitharu.kotatsu.core.util.ShareHelper import kotlin.math.absoluteValue import androidx.fragment.app.strictmode.Violation as FragmentViolation @@ -42,7 +43,7 @@ class StrictModeNotifier( override fun onViolation(violation: FragmentViolation) = showNotification(violation) private fun showNotification(violation: Throwable) = Notification.Builder(context, CHANNEL_ID) - .setSmallIcon(android.R.drawable.stat_notify_error) + .setSmallIcon(R.drawable.ic_bug) .setContentTitle(context.getString(R.string.strict_mode)) .setContentText(violation.message) .setStyle( @@ -51,7 +52,15 @@ class StrictModeNotifier( .setSummaryText(violation.message) .bigText(violation.stackTraceToString()), ).setShowWhen(true) - .setContentIntent(ErrorReporterReceiver.getPendingIntent(context, violation)) + .setContentIntent( + PendingIntentCompat.getActivity( + context, + 0, + ShareHelper(context).getShareTextIntent(violation.stackTraceToString()), + 0, + false, + ), + ) .setAutoCancel(true) .setGroup(CHANNEL_ID) .build() diff --git a/app/src/debug/res/drawable-anydpi-v24/ic_bug.xml b/app/src/debug/res/drawable-anydpi-v24/ic_bug.xml new file mode 100644 index 000000000..dc7fd8955 --- /dev/null +++ b/app/src/debug/res/drawable-anydpi-v24/ic_bug.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/debug/res/drawable-hdpi/ic_bug.png b/app/src/debug/res/drawable-hdpi/ic_bug.png new file mode 100644 index 0000000000000000000000000000000000000000..b4aa5f3c7668c6ccde4029bfff1698e52e64b757 GIT binary patch literal 417 zcmV;S0bc%zP)5bKg|3bQeVG^1yvlkp(=^9&}`N8|G!WIV+EQ8PE5*FA4;_ zhS?+7=U|`ZjeRqNuy;;3u+PCh%Ln_Sknolb!t4zH=J?9PWTabGmYEK;4sBU=!@LYP zXXRo36x@7+`=St?ms;!MVk#A2mJ}00000 LNkvXXu0mjfv5~q4 literal 0 HcmV?d00001 diff --git a/app/src/debug/res/drawable-mdpi/ic_bug.png b/app/src/debug/res/drawable-mdpi/ic_bug.png new file mode 100644 index 0000000000000000000000000000000000000000..726da962cd9619a328dee868d1107adb60a94e56 GIT binary patch literal 308 zcmV-40n7f0P)(Nj9bU!89{(rqGZ? z(a&RyX|X3DYqJ*w1}4x8N(!>XW@FB3gYw4Y%%FkQRQYS}1%ZK;8e^7xAB4$&A|Gis zH(wxQTJuf)E>xF_`bLHF%j<;4%8WCTlNpC+s z?>SI07~XTh&D@F+zbJo*&wE}>*ne>2+zXEPR{Ru+FVdXE=RF512E%&}xLGLGCF}5B zDJb+!^}DG0^-u-|y3mn^Qw~sv?;C@$7#f-H_aNr{Ph;L-@i~8nIl(pn0000sa42*cGuk3hHNr7 zDJrnbMkUx`-dIqP4x0_Z19Ot25=++m%8ZITyU5&RSnp>rlrD3Vpurbx62Lk0qoMFC zPD{uo`;^A=0zu>jexcANr})rM=m#hCM@K{MIn{@LLO-~rKRO!n#Hl{?6Z%1i{^;1G zEnt)W5L;d#h$suRrB6?T@V)&s2;bXgpI~g#9%5osu)rOs`p{442TA_OjyTnaenLMu zq(2y&f?uJ$Kmbt|a4)8w`zSWdcWSXuG!%a3PYUh&=_vz!?MW$QLkUW zL+ihxO5x$3PFve@KK+VgK0Mvhk(Iq-K7XP7YJuq$@j!K!ehxVw<>WuTpY!;!(tDtB z_UAo0{z}+AZf2e5eE)|+Vc+jGR;HNeE`l>3*F`odh+9Omr0w+Irf|mDPrUK>9d+v? ze|N_}P7(ac(p!8#N8o>{w@=v4e6a&Rg8e2m(DIxe~zW!l{OJPW{?n7A2&JOj{ar zSUvVO%hyAuA>kJO*&d&>=kJX<+`jJVS;oBL-gAvBRm&t5o;HZ>bbEf_iZz&huK*-2 z3Sk4)2ue?2KD&J3Wzno(SC2_*UjN{E-OO=s0q<24pv^Z*18i@#>`rUp)o1`C2W@66 zCfU^&o;OVlv`~C1W%u%y+KbD4>lRv_sf==dW3@tVBjZ)(;QX7Z4O5t3J>7dlXgAM< zB8Rw!Mk literal 0 HcmV?d00001 diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/browser/cloudflare/CaptchaNotifier.kt b/app/src/main/kotlin/org/koitharu/kotatsu/browser/cloudflare/CaptchaNotifier.kt index 83318f8ea..5ac7421e6 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/browser/cloudflare/CaptchaNotifier.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/browser/cloudflare/CaptchaNotifier.kt @@ -28,7 +28,7 @@ class CaptchaNotifier( return } val manager = NotificationManagerCompat.from(context) - val channel = NotificationChannelCompat.Builder(CHANNEL_ID, NotificationManagerCompat.IMPORTANCE_DEFAULT) + val channel = NotificationChannelCompat.Builder(CHANNEL_ID, NotificationManagerCompat.IMPORTANCE_LOW) .setName(context.getString(R.string.captcha_required)) .setShowBadge(true) .setVibrationEnabled(false) @@ -41,8 +41,8 @@ class CaptchaNotifier( .setData(exception.url.toUri()) val notification = NotificationCompat.Builder(context, CHANNEL_ID) .setContentTitle(channel.name) - .setPriority(NotificationCompat.PRIORITY_DEFAULT) - .setDefaults(NotificationCompat.DEFAULT_SOUND) + .setPriority(NotificationCompat.PRIORITY_LOW) + .setDefaults(0) .setSmallIcon(android.R.drawable.stat_notify_error) .setGroup(GROUP_CAPTCHA) .setAutoCancel(true) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/resolve/DialogErrorObserver.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/resolve/DialogErrorObserver.kt index 1edf3b662..8d2e34b9f 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/resolve/DialogErrorObserver.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/resolve/DialogErrorObserver.kt @@ -8,6 +8,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.ui.dialog.ErrorDetailsDialog import org.koitharu.kotatsu.core.util.ext.getDisplayMessage +import org.koitharu.kotatsu.core.util.ext.isSerializable import org.koitharu.kotatsu.parsers.exception.ParseException class DialogErrorObserver( @@ -32,7 +33,7 @@ class DialogErrorObserver( dialogBuilder.setPositiveButton(ExceptionResolver.getResolveStringId(value), listener) } else if (value is ParseException) { val fm = fragmentManager - if (fm != null) { + if (fm != null && value.isSerializable()) { dialogBuilder.setPositiveButton(R.string.details) { _, _ -> ErrorDetailsDialog.show(fm, value, value.url) } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/resolve/SnackbarErrorObserver.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/resolve/SnackbarErrorObserver.kt index e39897cfc..d5b55b750 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/resolve/SnackbarErrorObserver.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/resolve/SnackbarErrorObserver.kt @@ -7,6 +7,7 @@ import com.google.android.material.snackbar.Snackbar import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.ui.dialog.ErrorDetailsDialog import org.koitharu.kotatsu.core.util.ext.getDisplayMessage +import org.koitharu.kotatsu.core.util.ext.isSerializable import org.koitharu.kotatsu.main.ui.owners.BottomNavOwner import org.koitharu.kotatsu.parsers.exception.ParseException @@ -33,7 +34,7 @@ class SnackbarErrorObserver( } } else if (value is ParseException) { val fm = fragmentManager - if (fm != null) { + if (fm != null && value.isSerializable()) { snackbar.setAction(R.string.details) { ErrorDetailsDialog.show(fm, value, value.url) } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/io/NullOutputStream.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/io/NullOutputStream.kt new file mode 100644 index 000000000..d02cdf97b --- /dev/null +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/io/NullOutputStream.kt @@ -0,0 +1,13 @@ +package org.koitharu.kotatsu.core.io + +import java.io.OutputStream +import java.util.Objects + +class NullOutputStream : OutputStream() { + + override fun write(b: Int) = Unit + + override fun write(b: ByteArray, off: Int, len: Int) { + Objects.checkFromIndexSize(off, len, b.size) + } +} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ShareHelper.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ShareHelper.kt index fc486cb4f..3fa1b6e3f 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ShareHelper.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ShareHelper.kt @@ -1,6 +1,7 @@ package org.koitharu.kotatsu.core.util import android.content.Context +import android.content.Intent import android.net.Uri import androidx.core.app.ShareCompat import androidx.core.content.FileProvider @@ -75,11 +76,9 @@ class ShareHelper(private val context: Context) { .startChooser() } - fun shareText(text: String) { - ShareCompat.IntentBuilder(context) - .setText(text) - .setType(TYPE_TEXT) - .setChooserTitle(R.string.share) - .startChooser() - } + fun getShareTextIntent(text: String): Intent = ShareCompat.IntentBuilder(context) + .setText(text) + .setType(TYPE_TEXT) + .setChooserTitle(R.string.share) + .createChooserIntent() } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Bundle.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Bundle.kt index 34f3f440e..0a90ef83a 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Bundle.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Bundle.kt @@ -20,6 +20,10 @@ inline fun Bundle.getParcelableCompat(key: String): T? return BundleCompat.getParcelable(this, key, T::class.java) } +inline fun Bundle.requireParcelable(key: String): T = checkNotNull(getParcelableCompat(key)) { + "Parcelable of type \"${T::class.java.name}\" not found at \"$key\"" +} + inline fun Intent.getParcelableExtraCompat(key: String): T? { return IntentCompat.getParcelableExtra(this, key, T::class.java) } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Throwable.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Throwable.kt index 41a51ce63..afb2117fe 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Throwable.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Throwable.kt @@ -24,6 +24,7 @@ import org.koitharu.kotatsu.core.exceptions.UnsupportedFileException import org.koitharu.kotatsu.core.exceptions.UnsupportedSourceException import org.koitharu.kotatsu.core.exceptions.WrongPasswordException import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver +import org.koitharu.kotatsu.core.io.NullOutputStream import org.koitharu.kotatsu.parsers.ErrorMessages.FILTER_BOTH_LOCALE_GENRES_NOT_SUPPORTED import org.koitharu.kotatsu.parsers.ErrorMessages.FILTER_BOTH_STATES_GENRES_NOT_SUPPORTED import org.koitharu.kotatsu.parsers.ErrorMessages.FILTER_MULTIPLE_GENRES_NOT_SUPPORTED @@ -35,6 +36,7 @@ import org.koitharu.kotatsu.parsers.exception.NotFoundException import org.koitharu.kotatsu.parsers.exception.ParseException import org.koitharu.kotatsu.parsers.exception.TooManyRequestExceptions import org.koitharu.kotatsu.scrobbling.common.domain.ScrobblerAuthRequiredException +import java.io.ObjectOutputStream import java.net.SocketTimeoutException import java.net.UnknownHostException @@ -181,3 +183,9 @@ fun Throwable.isWebViewUnavailable(): Boolean { @Suppress("FunctionName") fun NoSpaceLeftException() = IOException(MSG_NO_SPACE_LEFT) + +fun Throwable.isSerializable() = runCatching { + val oos = ObjectOutputStream(NullOutputStream()) + oos.writeObject(this) + oos.flush() +}.isSuccess diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsErrorObserver.kt b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsErrorObserver.kt index 482618a0a..745703330 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsErrorObserver.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsErrorObserver.kt @@ -8,6 +8,7 @@ import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver import org.koitharu.kotatsu.core.ui.dialog.ErrorDetailsDialog import org.koitharu.kotatsu.core.util.ext.getDisplayMessage import org.koitharu.kotatsu.core.util.ext.isNetworkError +import org.koitharu.kotatsu.core.util.ext.isSerializable import org.koitharu.kotatsu.parsers.exception.NotFoundException import org.koitharu.kotatsu.parsers.exception.ParseException @@ -38,7 +39,7 @@ class DetailsErrorObserver( value is ParseException -> { val fm = fragmentManager - if (fm != null) { + if (fm != null && value.isSerializable()) { snackbar.setAction(R.string.details) { ErrorDetailsDialog.show(fm, value, value.url) } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/standard/PageHolder.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/standard/PageHolder.kt index 21a0d5639..e901d9079 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/standard/PageHolder.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/standard/PageHolder.kt @@ -17,6 +17,7 @@ import org.koitharu.kotatsu.core.os.NetworkState import org.koitharu.kotatsu.core.ui.widgets.ZoomControl import org.koitharu.kotatsu.core.util.ext.getDisplayMessage import org.koitharu.kotatsu.core.util.ext.isLowRamDevice +import org.koitharu.kotatsu.core.util.ext.isSerializable import org.koitharu.kotatsu.databinding.ItemPageBinding import org.koitharu.kotatsu.parsers.util.ifZero import org.koitharu.kotatsu.reader.domain.PageLoader @@ -154,6 +155,7 @@ open class PageHolder( bindingInfo.buttonRetry.setText( ExceptionResolver.getResolveStringId(e).ifZero { R.string.try_again }, ) + bindingInfo.buttonErrorDetails.isVisible = e.isSerializable() bindingInfo.layoutError.isVisible = true bindingInfo.progressBar.hide() } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/webtoon/WebtoonHolder.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/webtoon/WebtoonHolder.kt index e2629e4a1..31501e97d 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/webtoon/WebtoonHolder.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/webtoon/WebtoonHolder.kt @@ -11,6 +11,7 @@ import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver import org.koitharu.kotatsu.core.os.NetworkState import org.koitharu.kotatsu.core.util.GoneOnInvisibleListener import org.koitharu.kotatsu.core.util.ext.getDisplayMessage +import org.koitharu.kotatsu.core.util.ext.isSerializable import org.koitharu.kotatsu.databinding.ItemPageWebtoonBinding import org.koitharu.kotatsu.parsers.util.ifZero import org.koitharu.kotatsu.reader.domain.PageLoader @@ -128,6 +129,7 @@ class WebtoonHolder( bindingInfo.buttonRetry.setText( ExceptionResolver.getResolveStringId(e).ifZero { R.string.try_again }, ) + bindingInfo.buttonErrorDetails.isVisible = e.isSerializable() bindingInfo.layoutError.isVisible = true bindingInfo.progressBar.hide() } diff --git a/app/src/main/res/menu/opt_local.xml b/app/src/main/res/menu/opt_local.xml index e00b4b7d3..0403421a9 100644 --- a/app/src/main/res/menu/opt_local.xml +++ b/app/src/main/res/menu/opt_local.xml @@ -9,9 +9,15 @@ android:title="@string/_import" app:showAsAction="never" /> + + From 38b342b721fbf74697ebbe309da1f6170a1e6ebe Mon Sep 17 00:00:00 2001 From: Koitharu Date: Mon, 4 Nov 2024 15:41:16 +0200 Subject: [PATCH 3/4] Update dependencies, fix covers restoring --- app/build.gradle | 8 ++++---- .../kotatsu/main/domain/CoverRestoreInterceptor.kt | 13 +------------ 2 files changed, 5 insertions(+), 16 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index c2923127b..32e4e1e9f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -16,8 +16,8 @@ android { applicationId 'org.koitharu.kotatsu' minSdk = 21 targetSdk = 35 - versionCode = 679 - versionName = '7.6.6' + versionCode = 680 + versionName = '7.6.7' generatedDensities = [] testInstrumentationRunner 'org.koitharu.kotatsu.HiltTestRunner' ksp { @@ -82,7 +82,7 @@ afterEvaluate { } } dependencies { - implementation('com.github.KotatsuApp:kotatsu-parsers:4c5ed57958') { + implementation('com.github.KotatsuApp:kotatsu-parsers:f80b586081') { exclude group: 'org.json', module: 'json' } @@ -93,7 +93,7 @@ dependencies { implementation 'androidx.appcompat:appcompat:1.7.0' implementation 'androidx.core:core-ktx:1.13.1' implementation 'androidx.activity:activity-ktx:1.9.3' - implementation 'androidx.fragment:fragment-ktx:1.8.4' + implementation 'androidx.fragment:fragment-ktx:1.8.5' implementation 'androidx.transition:transition-ktx:1.5.1' implementation 'androidx.collection:collection-ktx:1.4.4' implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.6' diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/main/domain/CoverRestoreInterceptor.kt b/app/src/main/kotlin/org/koitharu/kotatsu/main/domain/CoverRestoreInterceptor.kt index 9f27b6147..5b0264d78 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/main/domain/CoverRestoreInterceptor.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/main/domain/CoverRestoreInterceptor.kt @@ -2,11 +2,8 @@ package org.koitharu.kotatsu.main.domain import androidx.collection.ArraySet import coil.intercept.Interceptor -import coil.network.HttpException import coil.request.ErrorResult import coil.request.ImageResult -import okio.FileNotFoundException -import org.jsoup.HttpStatusException import org.koitharu.kotatsu.bookmarks.domain.Bookmark import org.koitharu.kotatsu.bookmarks.domain.BookmarksRepository import org.koitharu.kotatsu.core.model.findById @@ -15,13 +12,10 @@ import org.koitharu.kotatsu.core.parser.MangaDataRepository import org.koitharu.kotatsu.core.parser.MangaRepository import org.koitharu.kotatsu.core.util.ext.ifNullOrEmpty import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug -import org.koitharu.kotatsu.parsers.exception.ParseException import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.util.runCatchingCancellable -import java.net.UnknownHostException import java.util.Collections import javax.inject.Inject -import javax.net.ssl.SSLException class CoverRestoreInterceptor @Inject constructor( private val dataRepository: MangaDataRepository, @@ -116,11 +110,6 @@ class CoverRestoreInterceptor @Inject constructor( } private fun Throwable.shouldRestore(): Boolean { - return this is HttpException - || this is HttpStatusException - || this is SSLException - || this is ParseException - || this is UnknownHostException - || this is FileNotFoundException + return this is Exception // any Exception but not Error } } From e2f8d8e022f99f4f83299a93e32a1145ccd86e0a Mon Sep 17 00:00:00 2001 From: Koitharu Date: Mon, 4 Nov 2024 16:04:18 +0200 Subject: [PATCH 4/4] Fix downloading slowdown --- .../ui/worker/DownloadSlowdownDispatcher.kt | 12 +++++--- .../download/ui/worker/DownloadWorker.kt | 3 +- .../kotatsu/reader/domain/PageLoader.kt | 29 +++++++++++++------ 3 files changed, 29 insertions(+), 15 deletions(-) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/download/ui/worker/DownloadSlowdownDispatcher.kt b/app/src/main/kotlin/org/koitharu/kotatsu/download/ui/worker/DownloadSlowdownDispatcher.kt index 9f2f2e3b2..4df33a736 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/download/ui/worker/DownloadSlowdownDispatcher.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/download/ui/worker/DownloadSlowdownDispatcher.kt @@ -1,16 +1,20 @@ package org.koitharu.kotatsu.download.ui.worker +import android.os.SystemClock import androidx.collection.MutableObjectLongMap import kotlinx.coroutines.delay import org.koitharu.kotatsu.core.parser.MangaRepository import org.koitharu.kotatsu.core.parser.ParserMangaRepository import org.koitharu.kotatsu.parsers.model.MangaSource +import javax.inject.Inject +import javax.inject.Singleton -class DownloadSlowdownDispatcher( +@Singleton +class DownloadSlowdownDispatcher @Inject constructor( private val mangaRepositoryFactory: MangaRepository.Factory, - private val defaultDelay: Long, ) { private val timeMap = MutableObjectLongMap() + private val defaultDelay = 1_600L suspend fun delay(source: MangaSource) { val repo = mangaRepositoryFactory.create(source) as? ParserMangaRepository ?: return @@ -19,11 +23,11 @@ class DownloadSlowdownDispatcher( } val lastRequest = synchronized(timeMap) { val res = timeMap.getOrDefault(source, 0L) - timeMap[source] = System.currentTimeMillis() + timeMap[source] = SystemClock.elapsedRealtime() res } if (lastRequest != 0L) { - delay(lastRequest + defaultDelay - System.currentTimeMillis()) + delay(lastRequest + defaultDelay - SystemClock.elapsedRealtime()) } } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/download/ui/worker/DownloadWorker.kt b/app/src/main/kotlin/org/koitharu/kotatsu/download/ui/worker/DownloadWorker.kt index e29f6de14..8438dd8a9 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/download/ui/worker/DownloadWorker.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/download/ui/worker/DownloadWorker.kt @@ -101,6 +101,7 @@ class DownloadWorker @AssistedInject constructor( private val mangaRepositoryFactory: MangaRepository.Factory, private val settings: AppSettings, @LocalStorageChanges private val localStorageChanges: MutableSharedFlow, + private val slowdownDispatcher: DownloadSlowdownDispatcher, private val imageProxyInterceptor: ImageProxyInterceptor, notificationFactoryFactory: DownloadNotificationFactory.Factory, ) : CoroutineWorker(appContext, params) { @@ -110,7 +111,6 @@ class DownloadWorker @AssistedInject constructor( isSilent = params.inputData.getBoolean(IS_SILENT, false), ) private val notificationManager = appContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager - private val slowdownDispatcher = DownloadSlowdownDispatcher(mangaRepositoryFactory, SLOWDOWN_DELAY) @Volatile private var lastPublishedState: DownloadState? = null @@ -569,7 +569,6 @@ class DownloadWorker @AssistedInject constructor( const val MAX_PAGES_PARALLELISM = 4 const val DOWNLOAD_ERROR_DELAY = 2_000L const val MAX_RETRY_DELAY = 7_200_000L // 2 hours - const val SLOWDOWN_DELAY = 200L const val MANGA_ID = "manga_id" const val CHAPTERS_IDS = "chapters" const val IS_SILENT = "silent" diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/domain/PageLoader.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/domain/PageLoader.kt index 8406ff161..a4bd8a5fd 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/domain/PageLoader.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/domain/PageLoader.kt @@ -1,10 +1,8 @@ package org.koitharu.kotatsu.reader.domain -import android.content.ContentResolver.MimeTypeInfo import android.content.Context import android.graphics.Rect import android.net.Uri -import android.webkit.MimeTypeMap import androidx.annotation.AnyThread import androidx.collection.LongSparseArray import androidx.collection.set @@ -29,6 +27,7 @@ import kotlinx.coroutines.sync.withPermit import okhttp3.OkHttpClient import okhttp3.Request import okio.use +import org.koitharu.kotatsu.core.image.BitmapDecoderCompat import org.koitharu.kotatsu.core.network.CommonHeaders import org.koitharu.kotatsu.core.network.MangaHttpClient import org.koitharu.kotatsu.core.network.imageproxy.ImageProxyInterceptor @@ -46,11 +45,13 @@ import org.koitharu.kotatsu.core.util.ext.exists import org.koitharu.kotatsu.core.util.ext.getCompletionResultOrNull import org.koitharu.kotatsu.core.util.ext.isPowerSaveMode import org.koitharu.kotatsu.core.util.ext.isTargetNotEmpty +import org.koitharu.kotatsu.core.util.ext.mimeType import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug import org.koitharu.kotatsu.core.util.ext.ramAvailable import org.koitharu.kotatsu.core.util.ext.use import org.koitharu.kotatsu.core.util.ext.withProgress import org.koitharu.kotatsu.core.util.progress.ProgressDeferred +import org.koitharu.kotatsu.download.ui.worker.DownloadSlowdownDispatcher import org.koitharu.kotatsu.local.data.PagesCache import org.koitharu.kotatsu.local.data.isFileUri import org.koitharu.kotatsu.local.data.isZipUri @@ -59,8 +60,6 @@ import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.util.mimeType import org.koitharu.kotatsu.parsers.util.requireBody import org.koitharu.kotatsu.parsers.util.runCatchingCancellable -import org.koitharu.kotatsu.core.image.BitmapDecoderCompat -import org.koitharu.kotatsu.core.util.ext.mimeType import org.koitharu.kotatsu.reader.ui.pager.ReaderPage import java.util.LinkedList import java.util.concurrent.atomic.AtomicInteger @@ -79,6 +78,7 @@ class PageLoader @Inject constructor( private val settings: AppSettings, private val mangaRepositoryFactory: MangaRepository.Factory, private val imageProxyInterceptor: ImageProxyInterceptor, + private val downloadSlowdownDispatcher: DownloadSlowdownDispatcher, ) { val loaderScope = RetainedLifecycleCoroutineScope(lifecycle) + InternalErrorHandler() + Dispatchers.Default @@ -127,7 +127,7 @@ class PageLoader @Inject constructor( } else if (task?.isCancelled == false) { return task } - task = loadPageAsyncImpl(page, force) + task = loadPageAsyncImpl(page, skipCache = force, isPrefetch = false) synchronized(tasks) { tasks[page.id] = task } @@ -186,7 +186,7 @@ class PageLoader @Inject constructor( val page = prefetchQueue.pollFirst() ?: return@launch if (cache.get(page.url) == null) { synchronized(tasks) { - tasks[page.id] = loadPageAsyncImpl(page, false) + tasks[page.id] = loadPageAsyncImpl(page, skipCache = false, isPrefetch = true) } return@launch } @@ -194,7 +194,11 @@ class PageLoader @Inject constructor( } } - private fun loadPageAsyncImpl(page: MangaPage, skipCache: Boolean): ProgressDeferred { + private fun loadPageAsyncImpl( + page: MangaPage, + skipCache: Boolean, + isPrefetch: Boolean, + ): ProgressDeferred { val progress = MutableStateFlow(PROGRESS_UNDEFINED) val deferred = loaderScope.async { if (!skipCache) { @@ -202,7 +206,7 @@ class PageLoader @Inject constructor( } counter.incrementAndGet() try { - loadPageImpl(page, progress) + loadPageImpl(page, progress, isPrefetch) } finally { if (counter.decrementAndGet() == 0) { onIdle() @@ -222,7 +226,11 @@ class PageLoader @Inject constructor( } } - private suspend fun loadPageImpl(page: MangaPage, progress: MutableStateFlow): Uri = semaphore.withPermit { + private suspend fun loadPageImpl( + page: MangaPage, + progress: MutableStateFlow, + isPrefetch: Boolean, + ): Uri = semaphore.withPermit { val pageUrl = getPageUrl(page) check(pageUrl.isNotBlank()) { "Cannot obtain full image url for $page" } val uri = Uri.parse(pageUrl) @@ -235,6 +243,9 @@ class PageLoader @Inject constructor( uri.isFileUri() -> uri else -> { + if (isPrefetch) { + downloadSlowdownDispatcher.delay(page.source) + } val request = createPageRequest(pageUrl, page.source) imageProxyInterceptor.interceptPageRequest(request, okHttp).ensureSuccess().use { response -> response.requireBody().withProgress(progress).use {