Share OkHttpClients

This commit is contained in:
Koitharu
2023-05-20 08:34:22 +03:00
parent e4c2797f06
commit 3729b5f2f0
15 changed files with 161 additions and 132 deletions

View File

@@ -4,7 +4,6 @@ import android.app.Application
import android.content.Context
import android.provider.SearchRecentSuggestions
import android.text.Html
import android.util.AndroidRuntimeException
import androidx.collection.arraySetOf
import androidx.room.InvalidationTracker
import coil.ComponentRegistry
@@ -23,8 +22,6 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import okhttp3.Cache
import okhttp3.CookieJar
import okhttp3.OkHttpClient
import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.core.cache.ContentCache
@@ -32,15 +29,11 @@ import org.koitharu.kotatsu.core.cache.MemoryContentCache
import org.koitharu.kotatsu.core.cache.StubContentCache
import org.koitharu.kotatsu.core.db.MangaDatabase
import org.koitharu.kotatsu.core.network.*
import org.koitharu.kotatsu.core.network.cookies.AndroidCookieJar
import org.koitharu.kotatsu.core.network.cookies.MutableCookieJar
import org.koitharu.kotatsu.core.network.cookies.PreferencesCookieJar
import org.koitharu.kotatsu.core.os.NetworkState
import org.koitharu.kotatsu.core.os.ShortcutsUpdater
import org.koitharu.kotatsu.core.parser.MangaLoaderContextImpl
import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.parser.favicon.FaviconFetcher
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.ui.image.CoilImageGetter
import org.koitharu.kotatsu.core.ui.util.ActivityRecreationHandle
import org.koitharu.kotatsu.core.util.IncognitoModeIndicator
@@ -51,7 +44,6 @@ import org.koitharu.kotatsu.local.data.CacheDir
import org.koitharu.kotatsu.local.data.CbzFetcher
import org.koitharu.kotatsu.local.data.LocalManga
import org.koitharu.kotatsu.local.data.LocalStorageChanges
import org.koitharu.kotatsu.local.data.LocalStorageManager
import org.koitharu.kotatsu.local.data.PagesCache
import org.koitharu.kotatsu.main.ui.protect.AppProtectHelper
import org.koitharu.kotatsu.parsers.MangaLoaderContext
@@ -60,16 +52,12 @@ import org.koitharu.kotatsu.search.ui.MangaSuggestionsProvider
import org.koitharu.kotatsu.settings.backup.BackupObserver
import org.koitharu.kotatsu.sync.domain.SyncController
import org.koitharu.kotatsu.widget.WidgetUpdater
import java.util.concurrent.TimeUnit
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
interface AppModule {
@Binds
fun bindCookieJar(androidCookieJar: MutableCookieJar): CookieJar
@Binds
fun bindMangaLoaderContext(mangaLoaderContextImpl: MangaLoaderContextImpl): MangaLoaderContext
@@ -78,53 +66,6 @@ interface AppModule {
companion object {
@Provides
@Singleton
fun provideCookieJar(
@ApplicationContext context: Context
): MutableCookieJar = try {
AndroidCookieJar()
} catch (e: AndroidRuntimeException) {
// WebView is not available
PreferencesCookieJar(context)
}
@Provides
@Singleton
fun provideHttpCache(
localStorageManager: LocalStorageManager,
): Cache = localStorageManager.createHttpCache()
@Provides
@Singleton
fun provideOkHttpClient(
cache: Cache,
commonHeadersInterceptor: CommonHeadersInterceptor,
mirrorSwitchInterceptor: MirrorSwitchInterceptor,
cookieJar: CookieJar,
settings: AppSettings,
): OkHttpClient {
return OkHttpClient.Builder().apply {
connectTimeout(20, TimeUnit.SECONDS)
readTimeout(60, TimeUnit.SECONDS)
writeTimeout(20, TimeUnit.SECONDS)
cookieJar(cookieJar)
dns(DoHManager(cache, settings))
if (settings.isSSLBypassEnabled) {
bypassSSLErrors()
}
cache(cache)
addNetworkInterceptor(CacheLimitInterceptor())
addInterceptor(GZipInterceptor())
addInterceptor(commonHeadersInterceptor)
addInterceptor(CloudFlareInterceptor())
addInterceptor(mirrorSwitchInterceptor)
if (BuildConfig.DEBUG) {
addInterceptor(CurlLoggingInterceptor())
}
}.build()
}
@Provides
@Singleton
fun provideNetworkState(
@@ -143,15 +84,10 @@ interface AppModule {
@Singleton
fun provideCoil(
@ApplicationContext context: Context,
okHttpClient: OkHttpClient,
@MangaHttpClient okHttpClient: OkHttpClient,
mangaRepositoryFactory: MangaRepository.Factory,
pagesCache: PagesCache,
): ImageLoader {
val httpClientFactory = {
okHttpClient.newBuilder()
.cache(null)
.build()
}
val diskCacheFactory = {
val rootDir = context.externalCacheDir ?: context.cacheDir
DiskCache.Builder()
@@ -159,7 +95,7 @@ interface AppModule {
.build()
}
return ImageLoader.Builder(context)
.okHttpClient(httpClientFactory)
.okHttpClient(okHttpClient.newBuilder().cache(null).build())
.interceptorDispatcher(Dispatchers.Default)
.fetcherDispatcher(Dispatchers.IO)
.decoderDispatcher(Dispatchers.Default)

View File

@@ -13,6 +13,7 @@ import okhttp3.Request
import org.json.JSONArray
import org.json.JSONObject
import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.core.network.BaseHttpClient
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.util.ext.asArrayList
import org.koitharu.kotatsu.parsers.util.await
@@ -36,7 +37,7 @@ private const val CONTENT_TYPE_APK = "application/vnd.android.package-archive"
class AppUpdateRepository @Inject constructor(
@ApplicationContext private val context: Context,
private val settings: AppSettings,
private val okHttp: OkHttpClient,
@BaseHttpClient private val okHttp: OkHttpClient,
) {
private val availableUpdate = MutableStateFlow<AppVersion?>(null)

View File

@@ -3,9 +3,9 @@ package org.koitharu.kotatsu.core.logs
import javax.inject.Qualifier
@Qualifier
@Retention(AnnotationRetention.BINARY)
@Retention(AnnotationRetention.SOURCE)
annotation class TrackerLogger
@Qualifier
@Retention(AnnotationRetention.BINARY)
@Retention(AnnotationRetention.SOURCE)
annotation class SyncLogger

View File

@@ -0,0 +1,11 @@
package org.koitharu.kotatsu.core.network
import javax.inject.Qualifier
@Qualifier
@Retention(AnnotationRetention.SOURCE)
annotation class BaseHttpClient
@Qualifier
@Retention(AnnotationRetention.SOURCE)
annotation class MangaHttpClient

View File

@@ -0,0 +1,87 @@
package org.koitharu.kotatsu.core.network
import android.content.Context
import android.util.AndroidRuntimeException
import dagger.Binds
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import okhttp3.Cache
import okhttp3.CookieJar
import okhttp3.OkHttpClient
import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.core.network.cookies.AndroidCookieJar
import org.koitharu.kotatsu.core.network.cookies.MutableCookieJar
import org.koitharu.kotatsu.core.network.cookies.PreferencesCookieJar
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.local.data.LocalStorageManager
import java.util.concurrent.TimeUnit
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
interface NetworkModule {
@Binds
fun bindCookieJar(androidCookieJar: MutableCookieJar): CookieJar
companion object {
@Provides
@Singleton
fun provideCookieJar(
@ApplicationContext context: Context
): MutableCookieJar = try {
AndroidCookieJar()
} catch (e: AndroidRuntimeException) {
// WebView is not available
PreferencesCookieJar(context)
}
@Provides
@Singleton
fun provideHttpCache(
localStorageManager: LocalStorageManager,
): Cache = localStorageManager.createHttpCache()
@Provides
@Singleton
@BaseHttpClient
fun provideBaseHttpClient(
cache: Cache,
cookieJar: CookieJar,
settings: AppSettings,
): OkHttpClient = OkHttpClient.Builder().apply {
connectTimeout(20, TimeUnit.SECONDS)
readTimeout(60, TimeUnit.SECONDS)
writeTimeout(20, TimeUnit.SECONDS)
cookieJar(cookieJar)
dns(DoHManager(cache, settings))
if (settings.isSSLBypassEnabled) {
bypassSSLErrors()
}
cache(cache)
addInterceptor(GZipInterceptor())
addInterceptor(CloudFlareInterceptor())
if (BuildConfig.DEBUG) {
addInterceptor(CurlLoggingInterceptor())
}
}.build()
@Provides
@Singleton
@MangaHttpClient
fun provideMangaHttpClient(
@BaseHttpClient baseClient: OkHttpClient,
commonHeadersInterceptor: CommonHeadersInterceptor,
mirrorSwitchInterceptor: MirrorSwitchInterceptor,
): OkHttpClient = baseClient.newBuilder().apply {
addNetworkInterceptor(CacheLimitInterceptor())
addInterceptor(commonHeadersInterceptor)
addInterceptor(mirrorSwitchInterceptor)
}.build()
}
}

View File

@@ -19,6 +19,7 @@ import org.koitharu.kotatsu.core.db.entity.toEntity
import org.koitharu.kotatsu.core.db.entity.toManga
import org.koitharu.kotatsu.core.db.entity.toMangaTags
import org.koitharu.kotatsu.core.network.CommonHeaders
import org.koitharu.kotatsu.core.network.MangaHttpClient
import org.koitharu.kotatsu.core.prefs.ReaderMode
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaPage
@@ -34,7 +35,7 @@ import kotlin.math.roundToInt
@Reusable
class MangaDataRepository @Inject constructor(
private val okHttpClient: OkHttpClient,
@MangaHttpClient private val okHttpClient: OkHttpClient,
private val db: MangaDatabase,
) {

View File

@@ -9,6 +9,7 @@ import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
import org.koitharu.kotatsu.core.network.MangaHttpClient
import org.koitharu.kotatsu.core.network.cookies.MutableCookieJar
import org.koitharu.kotatsu.core.prefs.SourceSettings
import org.koitharu.kotatsu.core.util.ext.toList
@@ -24,7 +25,7 @@ import kotlin.coroutines.suspendCoroutine
@Singleton
class MangaLoaderContextImpl @Inject constructor(
override val httpClient: OkHttpClient,
@MangaHttpClient override val httpClient: OkHttpClient,
override val cookieJar: MutableCookieJar,
@ApplicationContext private val androidContext: Context,
) : MangaLoaderContext() {

View File

@@ -36,6 +36,7 @@ import okio.buffer
import okio.sink
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.network.CommonHeaders
import org.koitharu.kotatsu.core.network.MangaHttpClient
import org.koitharu.kotatsu.core.parser.MangaDataRepository
import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.prefs.AppSettings
@@ -69,7 +70,7 @@ import javax.inject.Inject
class DownloadWorker @AssistedInject constructor(
@Assisted appContext: Context,
@Assisted params: WorkerParameters,
private val okHttp: OkHttpClient,
@MangaHttpClient private val okHttp: OkHttpClient,
private val cache: PagesCache,
private val localMangaRepository: LocalMangaRepository,
private val mangaDataRepository: MangaDataRepository,

View File

@@ -3,5 +3,5 @@ package org.koitharu.kotatsu.local.data
import javax.inject.Qualifier
@Qualifier
@Retention(AnnotationRetention.BINARY)
@Retention(AnnotationRetention.SOURCE)
annotation class LocalStorageChanges

View File

@@ -22,6 +22,7 @@ import okhttp3.OkHttpClient
import okhttp3.Request
import okio.source
import org.koitharu.kotatsu.core.network.CommonHeaders
import org.koitharu.kotatsu.core.network.MangaHttpClient
import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
import org.koitharu.kotatsu.core.prefs.AppSettings
@@ -46,7 +47,7 @@ import kotlin.coroutines.CoroutineContext
@ActivityRetainedScoped
class PageLoader @Inject constructor(
lifecycle: ActivityRetainedLifecycle,
private val okHttp: OkHttpClient,
@MangaHttpClient private val okHttp: OkHttpClient,
private val cache: PagesCache,
private val settings: AppSettings,
private val mangaRepositoryFactory: MangaRepository.Factory,

View File

@@ -8,12 +8,9 @@ import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import dagger.multibindings.ElementsIntoSet
import okhttp3.OkHttpClient
import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.core.db.MangaDatabase
import org.koitharu.kotatsu.core.network.CurlLoggingInterceptor
import org.koitharu.kotatsu.core.network.BaseHttpClient
import org.koitharu.kotatsu.scrobbling.anilist.data.AniListAuthenticator
import org.koitharu.kotatsu.scrobbling.anilist.data.AniListInterceptor
import org.koitharu.kotatsu.scrobbling.anilist.data.AniListRepository
import org.koitharu.kotatsu.scrobbling.anilist.domain.AniListScrobbler
import org.koitharu.kotatsu.scrobbling.common.data.ScrobblerStorage
import org.koitharu.kotatsu.scrobbling.common.domain.Scrobbler
@@ -21,11 +18,9 @@ import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerService
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerType
import org.koitharu.kotatsu.scrobbling.mal.data.MALAuthenticator
import org.koitharu.kotatsu.scrobbling.mal.data.MALInterceptor
import org.koitharu.kotatsu.scrobbling.mal.data.MALRepository
import org.koitharu.kotatsu.scrobbling.mal.domain.MALScrobbler
import org.koitharu.kotatsu.scrobbling.shikimori.data.ShikimoriAuthenticator
import org.koitharu.kotatsu.scrobbling.shikimori.data.ShikimoriInterceptor
import org.koitharu.kotatsu.scrobbling.shikimori.data.ShikimoriRepository
import org.koitharu.kotatsu.scrobbling.shikimori.domain.ShikimoriScrobbler
import javax.inject.Singleton
@@ -35,57 +30,39 @@ object ScrobblingModule {
@Provides
@Singleton
fun provideShikimoriRepository(
@ApplicationContext context: Context,
@ScrobblerType(ScrobblerService.SHIKIMORI) storage: ScrobblerStorage,
database: MangaDatabase,
@ScrobblerType(ScrobblerService.SHIKIMORI)
fun provideShikimoriHttpClient(
@BaseHttpClient baseHttpClient: OkHttpClient,
authenticator: ShikimoriAuthenticator,
): ShikimoriRepository {
val okHttp = OkHttpClient.Builder().apply {
authenticator(authenticator)
addInterceptor(ShikimoriInterceptor(storage))
if (BuildConfig.DEBUG) {
addInterceptor(CurlLoggingInterceptor())
}
}.build()
return ShikimoriRepository(context, okHttp, storage, database)
}
@ScrobblerType(ScrobblerService.SHIKIMORI) storage: ScrobblerStorage,
): OkHttpClient = baseHttpClient.newBuilder().apply {
authenticator(authenticator)
addInterceptor(ShikimoriInterceptor(storage))
}.build()
@Provides
@Singleton
fun provideMALRepository(
@ApplicationContext context: Context,
@ScrobblerType(ScrobblerService.MAL) storage: ScrobblerStorage,
database: MangaDatabase,
@ScrobblerType(ScrobblerService.MAL)
fun provideMALHttpClient(
@BaseHttpClient baseHttpClient: OkHttpClient,
authenticator: MALAuthenticator,
): MALRepository {
val okHttp = OkHttpClient.Builder().apply {
authenticator(authenticator)
addInterceptor(MALInterceptor(storage))
if (BuildConfig.DEBUG) {
addInterceptor(CurlLoggingInterceptor())
}
}.build()
return MALRepository(context, okHttp, storage, database)
}
@ScrobblerType(ScrobblerService.MAL) storage: ScrobblerStorage,
): OkHttpClient = baseHttpClient.newBuilder().apply {
authenticator(authenticator)
addInterceptor(MALInterceptor(storage))
}.build()
@Provides
@Singleton
fun provideAniListRepository(
@ApplicationContext context: Context,
@ScrobblerType(ScrobblerService.ANILIST) storage: ScrobblerStorage,
database: MangaDatabase,
@ScrobblerType(ScrobblerService.ANILIST)
fun provideAniListHttpClient(
@BaseHttpClient baseHttpClient: OkHttpClient,
authenticator: AniListAuthenticator,
): AniListRepository {
val okHttp = OkHttpClient.Builder().apply {
authenticator(authenticator)
addInterceptor(AniListInterceptor(storage))
if (BuildConfig.DEBUG) {
addInterceptor(CurlLoggingInterceptor())
}
}.build()
return AniListRepository(context, okHttp, storage, database)
}
@ScrobblerType(ScrobblerService.ANILIST) storage: ScrobblerStorage,
): OkHttpClient = baseHttpClient.newBuilder().apply {
authenticator(authenticator)
addInterceptor(AniListInterceptor(storage))
}.build()
@Provides
@Singleton

View File

@@ -23,7 +23,10 @@ import org.koitharu.kotatsu.scrobbling.common.data.ScrobblingEntity
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerManga
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerMangaInfo
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerService
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerType
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerUser
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.math.roundToInt
private const val REDIRECT_URI = "kotatsu://anilist-auth"
@@ -34,10 +37,11 @@ private const val REQUEST_QUERY = "query"
private const val REQUEST_MUTATION = "mutation"
private const val KEY_SCORE_FORMAT = "score_format"
class AniListRepository(
@Singleton
class AniListRepository @Inject constructor(
@ApplicationContext context: Context,
private val okHttp: OkHttpClient,
private val storage: ScrobblerStorage,
@ScrobblerType(ScrobblerService.ANILIST) private val okHttp: OkHttpClient,
@ScrobblerType(ScrobblerService.ANILIST) private val storage: ScrobblerStorage,
private val db: MangaDatabase,
) : ScrobblerRepository {

View File

@@ -20,18 +20,22 @@ import org.koitharu.kotatsu.scrobbling.common.data.ScrobblingEntity
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerManga
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerMangaInfo
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerService
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerType
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerUser
import java.security.SecureRandom
import javax.inject.Inject
import javax.inject.Singleton
private const val REDIRECT_URI = "kotatsu://mal-auth"
private const val BASE_WEB_URL = "https://myanimelist.net"
private const val BASE_API_URL = "https://api.myanimelist.net/v2"
private const val AVATAR_STUB = "https://cdn.myanimelist.net/images/questionmark_50.gif"
class MALRepository(
@Singleton
class MALRepository @Inject constructor(
@ApplicationContext context: Context,
private val okHttp: OkHttpClient,
private val storage: ScrobblerStorage,
@ScrobblerType(ScrobblerService.MAL) private val okHttp: OkHttpClient,
@ScrobblerType(ScrobblerService.MAL) private val storage: ScrobblerStorage,
private val db: MangaDatabase,
) : ScrobblerRepository {

View File

@@ -23,16 +23,20 @@ import org.koitharu.kotatsu.scrobbling.common.data.ScrobblingEntity
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerManga
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerMangaInfo
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerService
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerType
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerUser
import javax.inject.Inject
import javax.inject.Singleton
private const val REDIRECT_URI = "kotatsu://shikimori-auth"
private const val BASE_URL = "https://shikimori.me/"
private const val MANGA_PAGE_SIZE = 10
class ShikimoriRepository(
@Singleton
class ShikimoriRepository @Inject constructor(
@ApplicationContext context: Context,
private val okHttp: OkHttpClient,
private val storage: ScrobblerStorage,
@ScrobblerType(ScrobblerService.SHIKIMORI) private val okHttp: OkHttpClient,
@ScrobblerType(ScrobblerService.SHIKIMORI) private val storage: ScrobblerStorage,
private val db: MangaDatabase,
) : ScrobblerRepository {

View File

@@ -5,6 +5,7 @@ import okhttp3.OkHttpClient
import okhttp3.Request
import org.json.JSONObject
import org.koitharu.kotatsu.core.exceptions.SyncApiException
import org.koitharu.kotatsu.core.network.BaseHttpClient
import org.koitharu.kotatsu.core.util.ext.toRequestBody
import org.koitharu.kotatsu.parsers.util.await
import org.koitharu.kotatsu.parsers.util.parseJson
@@ -13,7 +14,7 @@ import javax.inject.Inject
@Reusable
class SyncAuthApi @Inject constructor(
private val okHttpClient: OkHttpClient,
@BaseHttpClient private val okHttpClient: OkHttpClient,
) {
suspend fun authenticate(host: String, email: String, password: String): String {