diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/AppConfigModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/AppConfigModule.kt index 90de99cd1a0d736a69c13f25682a6b4d23d23531..d076c040865e4500f5022287c34febb3fe5b2857 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/AppConfigModule.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/AppConfigModule.kt @@ -1,5 +1,6 @@ package de.rki.coronawarnapp.appconfig +import android.content.Context import dagger.Module import dagger.Provides import de.rki.coronawarnapp.appconfig.download.AppConfigApiV2 @@ -14,10 +15,13 @@ import de.rki.coronawarnapp.appconfig.mapping.PresenceTracingConfigMapper import de.rki.coronawarnapp.appconfig.mapping.SurveyConfigMapper import de.rki.coronawarnapp.environment.download.DownloadCDNHttpClient import de.rki.coronawarnapp.environment.download.DownloadCDNServerUrl +import de.rki.coronawarnapp.util.di.AppContext +import okhttp3.Cache import okhttp3.OkHttpClient import org.joda.time.Duration import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory +import java.io.File import java.util.concurrent.TimeUnit import javax.inject.Singleton @@ -27,18 +31,14 @@ class AppConfigModule { @Singleton @Provides fun provideAppConfigApi( + @RemoteAppConfigCache cache: Cache, @DownloadCDNHttpClient client: OkHttpClient, @DownloadCDNServerUrl url: String, gsonConverterFactory: GsonConverterFactory ): AppConfigApiV2 { val configHttpClient = client.newBuilder().apply { - // We no longer use the retrofit cache, due to the complexity it adds when invalidating the cache. - // The our manual local storage offers more control and should replace it functionally. - // See **[de.rki.coronawarnapp.appconfig.sources.local.LocalAppConfigSource]** - // If we ever want to use it again, the previous cache path was: - // val cacheDir = File(context.cacheDir, "http_app-config") - // cache(cache) + cache(cache) connectTimeout(HTTP_TIMEOUT_APPCONFIG.millis, TimeUnit.MILLISECONDS) readTimeout(HTTP_TIMEOUT_APPCONFIG.millis, TimeUnit.MILLISECONDS) writeTimeout(HTTP_TIMEOUT_APPCONFIG.millis, TimeUnit.MILLISECONDS) @@ -53,6 +53,14 @@ class AppConfigModule { .create(AppConfigApiV2::class.java) } + @RemoteAppConfigCache + @Provides + @Singleton + fun remoteAppConfigHttpCache(@AppContext context: Context): Cache { + val cacheDir = File(context.cacheDir, "http_app-config") + return Cache(cacheDir, DEFAULT_CACHE_SIZE) + } + @Provides fun cwaMapper(mapper: CWAConfigMapper): CWAConfig.Mapper = mapper @@ -90,5 +98,6 @@ class AppConfigModule { companion object { private val HTTP_TIMEOUT_APPCONFIG = Duration.standardSeconds(10) + private const val DEFAULT_CACHE_SIZE = 2 * 1024 * 1024L // 5MB } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/RemoteAppConfigCache.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/RemoteAppConfigCache.kt new file mode 100644 index 0000000000000000000000000000000000000000..da175c871d674c3d12870d39a0f670121c0926c8 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/RemoteAppConfigCache.kt @@ -0,0 +1,8 @@ +package de.rki.coronawarnapp.appconfig + +import javax.inject.Qualifier + +@Qualifier +@MustBeDocumented +@Retention(AnnotationRetention.RUNTIME) +annotation class RemoteAppConfigCache diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/internal/AppConfigSource.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/internal/AppConfigSource.kt index 8a3d760bfaf2309404d89a634b040b86fdc59981..13acf8a47edaf432562312da7925ef9ca071d35c 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/internal/AppConfigSource.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/internal/AppConfigSource.kt @@ -81,6 +81,7 @@ class AppConfigSource @Inject constructor( suspend fun clear() { Timber.tag(TAG).d("clear()") localAppConfigSource.clear() + remoteAppConfigSource.clear() } companion object { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/sources/remote/RemoteAppConfigSource.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/sources/remote/RemoteAppConfigSource.kt index 0af12fdf70f9bb29ecf8b7486f816d1563d63c86..81ffe0c929bb777e245d1622c6a07363fb339897 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/sources/remote/RemoteAppConfigSource.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/sources/remote/RemoteAppConfigSource.kt @@ -1,11 +1,13 @@ package de.rki.coronawarnapp.appconfig.sources.remote import de.rki.coronawarnapp.appconfig.ConfigData +import de.rki.coronawarnapp.appconfig.RemoteAppConfigCache import de.rki.coronawarnapp.appconfig.internal.ConfigDataContainer import de.rki.coronawarnapp.appconfig.mapping.ConfigParser import de.rki.coronawarnapp.appconfig.sources.local.AppConfigStorage import de.rki.coronawarnapp.util.coroutine.DispatcherProvider import kotlinx.coroutines.withContext +import okhttp3.Cache import timber.log.Timber import javax.inject.Inject import javax.inject.Singleton @@ -13,6 +15,7 @@ import javax.inject.Singleton @Singleton class RemoteAppConfigSource @Inject constructor( private val server: AppConfigServer, + @RemoteAppConfigCache private val remoteCache: Cache, private val storage: AppConfigStorage, private val parser: ConfigParser, private val dispatcherProvider: DispatcherProvider @@ -47,6 +50,11 @@ class RemoteAppConfigSource @Inject constructor( } } + fun clear() { + Timber.tag(TAG).d("clear()") + remoteCache.evictAll() + } + companion object { private const val TAG = "AppConfigRetriever" } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/internal/AppConfigSourceTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/internal/AppConfigSourceTest.kt index d52c964adcde7bc0c9f5875cc4cd998d4e09a63a..26e0e85070198351bd70f1f61232021d5166e444 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/internal/AppConfigSourceTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/internal/AppConfigSourceTest.kt @@ -11,6 +11,7 @@ import io.kotest.matchers.shouldBe import io.mockk.MockKAnnotations import io.mockk.Runs import io.mockk.coEvery +import io.mockk.coVerify import io.mockk.coVerifyOrder import io.mockk.coVerifySequence import io.mockk.every @@ -70,8 +71,14 @@ class AppConfigSourceTest : BaseTest() { fun setup() { MockKAnnotations.init(this) - coEvery { remoteSource.getConfigData() } returns remoteConfig - coEvery { localSource.getConfigData() } returns localConfig + remoteSource.apply { + coEvery { getConfigData() } returns remoteConfig + coEvery { clear() } just Runs + } + localSource.apply { + coEvery { getConfigData() } returns localConfig + coEvery { clear() } just Runs + } coEvery { defaultSource.getConfigData() } returns defaultConfig every { timeStamper.nowUTC } returns Instant.EPOCH.plus(Duration.standardHours(1)) @@ -244,4 +251,14 @@ class AppConfigSourceTest : BaseTest() { cwaSettings.lastDeviceTimeStateChangeState = ConfigData.DeviceTimeState.CORRECT } } + + @Test + fun `clear calls subroutines`() = runBlockingTest { + createInstance().clear() + + coVerify { + localSource.clear() + remoteSource.clear() + } + } } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/sources/remote/AppConfigApiTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/sources/remote/AppConfigApiTest.kt index 5f9f1e884a6d690df3212cf6d328a7d0a6d8e31a..c4e8f5f2fbae6b136c6bfba95fc26314e2185813 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/sources/remote/AppConfigApiTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/sources/remote/AppConfigApiTest.kt @@ -9,6 +9,7 @@ import io.kotest.matchers.shouldBe import io.mockk.MockKAnnotations import io.mockk.impl.annotations.MockK import kotlinx.coroutines.runBlocking +import okhttp3.Cache import okhttp3.ConnectionSpec import okhttp3.mockwebserver.MockResponse import okhttp3.mockwebserver.MockWebServer @@ -25,6 +26,7 @@ class AppConfigApiTest : BaseIOTest() { private lateinit var webServer: MockWebServer private lateinit var serverAddress: String + private lateinit var cache: Cache private val testDir = File(IO_TEST_BASEDIR, this::class.java.simpleName) @@ -32,6 +34,8 @@ class AppConfigApiTest : BaseIOTest() { fun setup() { MockKAnnotations.init(this) + cache = Cache(File(testDir, "cache"), 1024L) + webServer = MockWebServer() webServer.start() serverAddress = "http://${webServer.hostName}:${webServer.port}" @@ -57,7 +61,8 @@ class AppConfigApiTest : BaseIOTest() { return AppConfigModule().provideAppConfigApi( client = cdnHttpClient, url = serverAddress, - gsonConverterFactory = gsonConverterFactory + gsonConverterFactory = gsonConverterFactory, + cache = cache, ) } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/sources/remote/RemoteAppConfigSourceTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/sources/remote/RemoteAppConfigSourceTest.kt index 0887d78cb8b9d9dce2e12548bcafe672bfcf6ffe..01f1d75d3847cf1ec00afc151b45798e11b5cd39 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/sources/remote/RemoteAppConfigSourceTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/sources/remote/RemoteAppConfigSourceTest.kt @@ -12,6 +12,8 @@ import io.mockk.coEvery import io.mockk.coVerify import io.mockk.every import io.mockk.impl.annotations.MockK +import io.mockk.verify +import okhttp3.Cache import okio.ByteString.Companion.decodeHex import org.joda.time.Duration import org.joda.time.Instant @@ -31,6 +33,7 @@ class RemoteAppConfigSourceTest : BaseIOTest() { @MockK lateinit var configParser: ConfigParser @MockK lateinit var configData: ConfigData @MockK lateinit var timeStamper: TimeStamper + @MockK(relaxUnitFun = true) lateinit var remoteCache: Cache private val testDir = File(IO_TEST_BASEDIR, this::class.simpleName!!) @@ -71,7 +74,8 @@ class RemoteAppConfigSourceTest : BaseIOTest() { server = configServer, storage = configStorage, parser = configParser, - dispatcherProvider = TestDispatcherProvider() + dispatcherProvider = TestDispatcherProvider(), + remoteCache = remoteCache ) @Test @@ -103,6 +107,13 @@ class RemoteAppConfigSourceTest : BaseIOTest() { coVerify(exactly = 0) { configStorage.setStoredConfig(any()) } } + @Test + fun `clear clears okhttp cache`() { + createInstance().clear() + + verify { remoteCache.evictAll() } + } + companion object { private val APPCONFIG_RAW = ( "080b124d0a230a034c4f57180f221a68747470733a2f2f777777" +