Skip to content
Snippets Groups Projects
Unverified Commit a6335e7f authored by Matthias Urhahn's avatar Matthias Urhahn Committed by GitHub
Browse files

Handle failure to persist GSON serialized JSON data (EXPOSUREAPP-3777) (#1604)

* Flush the json data after writing it, otherwise if the app process dies, we end up with an empty file.
GSON deserializes this into a null object that looks like a non-null object until it is evaluated some time later.

Force an evaluation via `require(it.size >= 0)` and delete the corrupt data file when catching the exception.

* Show a visible error dialog if we fail to save data.

* Used buffered versions of readers and writer.
parent 5f55d207
No related branches found
No related tags found
No related merge requests found
...@@ -2,6 +2,8 @@ package de.rki.coronawarnapp.nearby.modules.calculationtracker ...@@ -2,6 +2,8 @@ package de.rki.coronawarnapp.nearby.modules.calculationtracker
import android.content.Context import android.content.Context
import com.google.gson.Gson import com.google.gson.Gson
import de.rki.coronawarnapp.exception.ExceptionCategory
import de.rki.coronawarnapp.exception.reporting.report
import de.rki.coronawarnapp.util.di.AppContext import de.rki.coronawarnapp.util.di.AppContext
import de.rki.coronawarnapp.util.gson.fromJson import de.rki.coronawarnapp.util.gson.fromJson
import de.rki.coronawarnapp.util.gson.toJson import de.rki.coronawarnapp.util.gson.toJson
...@@ -36,11 +38,13 @@ class CalculationTrackerStorage @Inject constructor( ...@@ -36,11 +38,13 @@ class CalculationTrackerStorage @Inject constructor(
if (!storageFile.exists()) return@withLock emptyMap() if (!storageFile.exists()) return@withLock emptyMap()
gson.fromJson<Map<String, Calculation>>(storageFile).also { gson.fromJson<Map<String, Calculation>>(storageFile).also {
require(it.size >= 0)
Timber.v("Loaded calculation data: %s", it) Timber.v("Loaded calculation data: %s", it)
lastCalcuationData = it lastCalcuationData = it
} }
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e, "Failed to load tracked calculations.") Timber.e(e, "Failed to load tracked calculations.")
if (storageFile.delete()) Timber.w("Storage file was deleted.")
emptyMap() emptyMap()
} }
} }
...@@ -55,6 +59,7 @@ class CalculationTrackerStorage @Inject constructor( ...@@ -55,6 +59,7 @@ class CalculationTrackerStorage @Inject constructor(
gson.toJson(data, storageFile) gson.toJson(data, storageFile)
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e, "Failed to save tracked calculations.") Timber.e(e, "Failed to save tracked calculations.")
e.report(ExceptionCategory.INTERNAL)
} }
} }
} }
...@@ -9,10 +9,11 @@ inline fun <reified T> Gson.fromJson(json: String): T = fromJson( ...@@ -9,10 +9,11 @@ inline fun <reified T> Gson.fromJson(json: String): T = fromJson(
object : TypeToken<T>() {}.type object : TypeToken<T>() {}.type
) )
inline fun <reified T> Gson.fromJson(file: File): T = file.reader().use { inline fun <reified T> Gson.fromJson(file: File): T = file.bufferedReader().use {
fromJson(it, object : TypeToken<T>() {}.type) fromJson(it, object : TypeToken<T>() {}.type)
} }
inline fun <reified T> Gson.toJson(data: T, file: File) = file.writer().use { writer -> inline fun <reified T> Gson.toJson(data: T, file: File) = file.bufferedWriter().use { writer ->
toJson(data, writer) toJson(data, writer)
writer.flush()
} }
...@@ -130,4 +130,20 @@ class CalculationTrackerStorageTest : BaseIOTest() { ...@@ -130,4 +130,20 @@ class CalculationTrackerStorageTest : BaseIOTest() {
storedData.getValue("b2b98400-058d-43e6-b952-529a5255248b").isCalculating shouldBe true storedData.getValue("b2b98400-058d-43e6-b952-529a5255248b").isCalculating shouldBe true
storedData.getValue("aeb15509-fb34-42ce-8795-7a9ae0c2f389").isCalculating shouldBe false storedData.getValue("aeb15509-fb34-42ce-8795-7a9ae0c2f389").isCalculating shouldBe false
} }
@Test
fun `we catch empty json data and prevent unsafely initialized maps`() = runBlockingTest {
storageDir.mkdirs()
storageFile.writeText("")
storageFile.exists() shouldBe true
createStorage().apply {
val value = load()
value.size shouldBe 0
value shouldBe emptyMap()
storageFile.exists() shouldBe false
}
}
} }
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment