fenix/app/src/main/java/org/mozilla/fenix/gleanplumb/OnDiskMessageMetadataStorag...

96 lines
2.9 KiB
Kotlin

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.gleanplumb
import android.content.Context
import android.util.AtomicFile
import androidx.annotation.VisibleForTesting
import mozilla.components.support.ktx.util.readAndDeserialize
import mozilla.components.support.ktx.util.writeString
import org.json.JSONArray
import org.json.JSONObject
import java.io.File
internal const val FILE_NAME = "nimbus_messages_metadata.json"
/**
* A storage that persists [Message.Metadata] into disk.
*/
class OnDiskMessageMetadataStorage(
private val context: Context
) : MessageMetadataStorage {
private val diskCacheLock = Any()
@VisibleForTesting
internal var metadataMap: MutableMap<String, Message.Metadata> = hashMapOf()
override suspend fun getMetadata(): Map<String, Message.Metadata> {
if (metadataMap.isEmpty()) {
metadataMap = readFromDisk().toMutableMap()
}
return metadataMap
}
override suspend fun addMetadata(metadata: Message.Metadata): Message.Metadata {
metadataMap[metadata.id] = metadata
writeToDisk()
return metadata
}
override suspend fun updateMetadata(metadata: Message.Metadata) {
addMetadata(metadata)
}
@VisibleForTesting
internal fun readFromDisk(): Map<String, Message.Metadata> {
synchronized(diskCacheLock) {
return getFile().readAndDeserialize {
JSONArray(it).toMetadataMap()
} ?: emptyMap()
}
}
@VisibleForTesting
internal fun writeToDisk() {
synchronized(diskCacheLock) {
val json = metadataMap.values.toList().fold("") { acc, next ->
if (acc.isEmpty()) {
next.toJson()
} else {
"$acc,${next.toJson()}"
}
}
getFile().writeString { "[$json]" }
}
}
private fun getFile(): AtomicFile {
return AtomicFile(File(context.filesDir, FILE_NAME))
}
}
internal fun JSONArray.toMetadataMap(): Map<String, Message.Metadata> {
return (0 until length()).map { index ->
getJSONObject(index).toMetadata()
}.associateBy {
it.id
}
}
@Suppress("MaxLineLength") // To avoid adding any extra space to the string.
internal fun Message.Metadata.toJson(): String {
return """{"id":"$id","displayCount":$displayCount,"pressed":$pressed,"dismissed":$dismissed,"lastTimeShown":$lastTimeShown}"""
}
internal fun JSONObject.toMetadata(): Message.Metadata {
return Message.Metadata(
id = optString("id"),
displayCount = optInt("displayCount"),
pressed = optBoolean("pressed"),
dismissed = optBoolean("dismissed"),
lastTimeShown = optLong("lastTimeShown")
)
}