diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5508a0416c..b6c660f372 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -42,6 +42,7 @@ kotlinlogging = "io.github.microutils:kotlin-logging:3.0.5" okhttp-core = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" } okhttp-logging = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp" } okhttp-dnsoverhttps = { module = "com.squareup.okhttp3:okhttp-dnsoverhttps", version.ref = "okhttp" } +okhttp-brotli = { module = "com.squareup.okhttp3:okhttp-brotli", version.ref = "okhttp" } okio = "com.squareup.okio:okio:3.3.0" # Javalin api @@ -195,6 +196,7 @@ okhttp = [ "okhttp-core", "okhttp-logging", "okhttp-dnsoverhttps", + "okhttp-brotli", ] javalin = [ "javalin-core", diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/network/NetworkHelper.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/network/NetworkHelper.kt index ff6e7d14be..88a7187449 100644 --- a/server/src/main/kotlin/eu/kanade/tachiyomi/network/NetworkHelper.kt +++ b/server/src/main/kotlin/eu/kanade/tachiyomi/network/NetworkHelper.kt @@ -16,10 +16,14 @@ package eu.kanade.tachiyomi.network // import uy.kohesive.injekt.injectLazy import android.content.Context import eu.kanade.tachiyomi.network.interceptor.CloudflareInterceptor +import eu.kanade.tachiyomi.network.interceptor.UncaughtExceptionInterceptor import eu.kanade.tachiyomi.network.interceptor.UserAgentInterceptor import mu.KotlinLogging +import okhttp3.Cache import okhttp3.OkHttpClient +import okhttp3.brotli.BrotliInterceptor import okhttp3.logging.HttpLoggingInterceptor +import java.io.File import java.net.CookieHandler import java.net.CookieManager import java.net.CookiePolicy @@ -51,8 +55,17 @@ class NetworkHelper(context: Context) { .connectTimeout(30, TimeUnit.SECONDS) .readTimeout(30, TimeUnit.SECONDS) .callTimeout(2, TimeUnit.MINUTES) + .cache( + Cache( + directory = File.createTempFile("tachidesk_network_cache", null), + maxSize = 5L * 1024 * 1024, // 5 MiB + ), + ) + .addInterceptor(BrotliInterceptor) + .addInterceptor(UncaughtExceptionInterceptor()) .addInterceptor(UserAgentInterceptor()) + // if (preferences.verboseLogging().get()) { val httpLoggingInterceptor = HttpLoggingInterceptor( object : HttpLoggingInterceptor.Logger { @@ -65,12 +78,27 @@ class NetworkHelper(context: Context) { ).apply { level = HttpLoggingInterceptor.Level.BASIC } - builder.addInterceptor(httpLoggingInterceptor) + builder.addNetworkInterceptor(httpLoggingInterceptor) + // } -// when (preferences.dohProvider()) { -// PREF_DOH_CLOUDFLARE -> builder.dohCloudflare() -// PREF_DOH_GOOGLE -> builder.dohGoogle() -// } + // builder.addInterceptor( + // CloudflareInterceptor(context, cookieJar, ::defaultUserAgentProvider), + // ) + + // when (preferences.dohProvider().get()) { + // PREF_DOH_CLOUDFLARE -> builder.dohCloudflare() + // PREF_DOH_GOOGLE -> builder.dohGoogle() + // PREF_DOH_ADGUARD -> builder.dohAdGuard() + // PREF_DOH_QUAD9 -> builder.dohQuad9() + // PREF_DOH_ALIDNS -> builder.dohAliDNS() + // PREF_DOH_DNSPOD -> builder.dohDNSPod() + // PREF_DOH_360 -> builder.doh360() + // PREF_DOH_QUAD101 -> builder.dohQuad101() + // PREF_DOH_MULLVAD -> builder.dohMullvad() + // PREF_DOH_CONTROLD -> builder.dohControlD() + // PREF_DOH_NJALLA -> builder.dohNajalla() + // PREF_DOH_SHECAN -> builder.dohShecan() + // } return builder } diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/network/interceptor/UncaughtExceptionInterceptor.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/network/interceptor/UncaughtExceptionInterceptor.kt new file mode 100644 index 0000000000..958dcd85d8 --- /dev/null +++ b/server/src/main/kotlin/eu/kanade/tachiyomi/network/interceptor/UncaughtExceptionInterceptor.kt @@ -0,0 +1,27 @@ +package eu.kanade.tachiyomi.network.interceptor + +import okhttp3.Interceptor +import okhttp3.Response +import java.io.IOException + +/** + * Catches any uncaught exceptions from later in the chain and rethrows as a non-fatal + * IOException to avoid catastrophic failure. + * + * This should be the first interceptor in the client. + * + * See https://square.github.io/okhttp/4.x/okhttp/okhttp3/-interceptor/ + */ +class UncaughtExceptionInterceptor : Interceptor { + override fun intercept(chain: Interceptor.Chain): Response { + return try { + chain.proceed(chain.request()) + } catch (e: Exception) { + if (e is IOException) { + throw e + } else { + throw IOException(e) + } + } + } +} diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Manga.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Manga.kt index 977807bac5..d83f564962 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Manga.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Manga.kt @@ -14,6 +14,7 @@ import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.UpdateStrategy import eu.kanade.tachiyomi.source.online.HttpSource import mu.KotlinLogging +import okhttp3.CacheControl import org.jetbrains.exposed.sql.ResultRow import org.jetbrains.exposed.sql.SortOrder import org.jetbrains.exposed.sql.and @@ -280,7 +281,7 @@ object Manga { } source.client.newCall( - GET(thumbnailUrl, source.headers), + GET(thumbnailUrl, source.headers, cache = CacheControl.FORCE_NETWORK), ).await() } @@ -306,7 +307,7 @@ object Manga { mangaEntry[MangaTable.thumbnail_url] ?: throw NullPointerException("No thumbnail found") network.client.newCall( - GET(thumbnailUrl), + GET(thumbnailUrl, cache = CacheControl.FORCE_NETWORK), ).await() } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/extension/Extension.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/extension/Extension.kt index a06667815a..abc998025d 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/extension/Extension.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/extension/Extension.kt @@ -14,7 +14,7 @@ import eu.kanade.tachiyomi.source.CatalogueSource import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.SourceFactory import mu.KotlinLogging -import okhttp3.Request +import okhttp3.CacheControl import okio.buffer import okio.sink import okio.source @@ -272,8 +272,10 @@ object Extension { url: String, savePath: String, ) { - val request = Request.Builder().url(url).build() - val response = network.client.newCall(request).await() + val response = + network.client.newCall( + GET(url, cache = CacheControl.FORCE_NETWORK), + ).await() val downloadedFile = File(savePath) downloadedFile.sink().buffer().use { sink -> @@ -350,7 +352,7 @@ object Extension { return getImageResponse(cacheSaveDir, apkName) { network.client.newCall( - GET(iconUrl), + GET(iconUrl, cache = CacheControl.FORCE_NETWORK), ).await() } }