diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/sources/local/AppConfigStorage.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/sources/local/AppConfigStorage.kt index dda995f9b17ddd06d498c0292e125490eed35eeb..4f0db33f244672f31344b0217142d6ef1305ac21 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/sources/local/AppConfigStorage.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/sources/local/AppConfigStorage.kt @@ -61,8 +61,9 @@ class AppConfigStorage @Inject constructor( } return@withLock try { - gson.fromJson<InternalConfigData>(configFile).also { + gson.fromJson<InternalConfigData>(configFile)?.also { requireNotNull(it.rawData) + Timber.v("Loaded stored config, serverTime=%s", it.serverTime) } } catch (e: Exception) { Timber.e(e, "Couldn't load config.") 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 e5006f0279a83676f128d16bb1b1a5f68d7caa92..f77c2a8de1ef49852e4af005ce8eacbc09669c97 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 @@ -19,7 +19,7 @@ class RemoteAppConfigSource @Inject constructor( ) { suspend fun getConfigData(): ConfigData? = withContext(dispatcherProvider.IO) { - Timber.tag(TAG).v("retrieveConfig()") + Timber.tag(TAG).v("getConfigData()") val configDownload = try { server.downloadAppConfig() diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/detectiontracker/ExposureDetectionTrackerStorage.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/detectiontracker/ExposureDetectionTrackerStorage.kt index c4299cafe56f85562c86d276a10d910bb5e39287..8ca647c1d71529065afd20c959a659986bf94c38 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/detectiontracker/ExposureDetectionTrackerStorage.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/detectiontracker/ExposureDetectionTrackerStorage.kt @@ -43,13 +43,11 @@ class ExposureDetectionTrackerStorage @Inject constructor( suspend fun load(): Map<String, TrackedExposureDetection> = mutex.withLock { return@withLock try { - if (!storageFile.exists()) return@withLock emptyMap() - - gson.fromJson<Map<String, TrackedExposureDetection>>(storageFile).also { + gson.fromJson<Map<String, TrackedExposureDetection>>(storageFile)?.also { require(it.size >= 0) Timber.v("Loaded detection data: %s", it) lastCalcuationData = it - } + } ?: emptyMap() } catch (e: Exception) { Timber.e(e, "Failed to load tracked detections.") if (storageFile.delete()) Timber.w("Storage file was deleted.") diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/serialization/GsonExtensions.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/serialization/GsonExtensions.kt index f014dc54c28ca112b2a49db88917add6c83fedca..04234b4c5ff3ff7d5ca3350731b2d5715ddbe140 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/serialization/GsonExtensions.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/serialization/GsonExtensions.kt @@ -3,6 +3,7 @@ package de.rki.coronawarnapp.util.serialization import com.google.gson.Gson import com.google.gson.TypeAdapter import com.google.gson.reflect.TypeToken +import timber.log.Timber import java.io.File import kotlin.reflect.KClass @@ -11,8 +12,28 @@ inline fun <reified T> Gson.fromJson(json: String): T = fromJson( object : TypeToken<T>() {}.type ) -inline fun <reified T> Gson.fromJson(file: File): T = file.bufferedReader().use { - fromJson(it, object : TypeToken<T>() {}.type) +/** + * Returns null if the file doesn't exist, otherwise returns the parsed object. + * Throws an exception if the object can't be parsed. + * An empty file, that was deserialized to a null value is deleted. + */ +inline fun <reified T : Any> Gson.fromJson(file: File): T? { + if (!file.exists()) { + Timber.v("fromJson(): File doesn't exist %s", file) + return null + } + + return file.bufferedReader().use { + val value: T? = fromJson(it, object : TypeToken<T>() {}.type) + if (value != null) { + Timber.v("Json read from %s", file) + value + } else { + Timber.w("Tried to parse json from file that exists, but was empty: %s", file) + if (file.delete()) Timber.w("Deleted empty json file: %s", file) + null + } + } } inline fun <reified T> Gson.toJson(data: T, file: File) = file.bufferedWriter().use { writer -> diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/modules/detectiontracker/ExposureDetectionTrackerStorageTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/modules/detectiontracker/ExposureDetectionTrackerStorageTest.kt index 702291ceaeaacdea384fe8f930a56caec0590ede..61e3bcab5f453800bc1bd2b581cdd4f4ed0c6fbf 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/modules/detectiontracker/ExposureDetectionTrackerStorageTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/modules/detectiontracker/ExposureDetectionTrackerStorageTest.kt @@ -113,11 +113,10 @@ class ExposureDetectionTrackerStorageTest : BaseIOTest() { @Test fun `saving data creates a json file`() = runBlockingTest { - createStorage().save(demoData) storageFile.exists() shouldBe true - val storedData: Map<String, TrackedExposureDetection> = gson.fromJson(storageFile) + val storedData: Map<String, TrackedExposureDetection> = gson.fromJson(storageFile)!! storedData shouldBe demoData gson.toJson(storedData) shouldBe demoJsonString diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/serialization/GsonExtensionsTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/serialization/GsonExtensionsTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..72b6ed30afc63566e18ffef7f03839246faaeafe --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/serialization/GsonExtensionsTest.kt @@ -0,0 +1,62 @@ +package de.rki.coronawarnapp.util.serialization + +import com.google.gson.Gson +import com.google.gson.JsonSyntaxException +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.matchers.shouldBe +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import testhelpers.BaseIOTest +import java.io.File +import java.util.UUID + +class GsonExtensionsTest : BaseIOTest() { + + private val testDir = File(IO_TEST_BASEDIR, this::class.java.simpleName) + private val testFile = File(testDir, "testfile") + private val gson = Gson() + + @BeforeEach + fun setup() { + testDir.mkdirs() + } + + @AfterEach + fun teardown() { + testDir.deleteRecursively() + } + + data class TestData( + val value: String + ) + + @Test + fun `serialize and deserialize`() { + val testData = TestData(value = UUID.randomUUID().toString()) + gson.toJson(testData, testFile) + + gson.fromJson<TestData>(testFile) shouldBe testData + } + + @Test + fun `deserialize an empty file`() { + testFile.createNewFile() + testFile.exists() shouldBe true + + val testData: TestData? = gson.fromJson(testFile) + + testData shouldBe null + + testFile.exists() shouldBe false + } + + @Test + fun `deserialize a malformed file`() { + testFile.writeText("{") + + shouldThrow<JsonSyntaxException> { + gson.fromJson(testFile) + } + } +}