create LocalConfig DataStore

master
andrekir 2022-06-11 18:36:57 -03:00
rodzic 42755e350e
commit 54f6112908
8 zmienionych plików z 198 dodań i 31 usunięć

Wyświetl plik

@ -136,6 +136,7 @@ dependencies {
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.4.1'
implementation "androidx.room:room-runtime:$room_version"
implementation "com.google.dagger:hilt-android:$hilt_version"
implementation "androidx.datastore:datastore:$datastore_version"
kapt "androidx.room:room-compiler:$room_version"
kapt "com.google.dagger:hilt-compiler:$hilt_version"

Wyświetl plik

@ -655,7 +655,7 @@ class MainActivity : BaseActivity(), Logging,
else {
// If our app is too old/new, we probably don't understand the new DeviceConfig messages, so we don't read them until here
model.setLocalConfig(LocalOnlyProtos.LocalConfig.parseFrom(service.deviceConfig))
// model.setLocalConfig(LocalOnlyProtos.LocalConfig.parseFrom(service.deviceConfig))
model.setChannels(ChannelSet(AppOnlyProtos.ChannelSet.parseFrom(service.channels)))

Wyświetl plik

@ -15,6 +15,7 @@ import com.geeksville.android.Logging
import com.geeksville.mesh.*
import com.geeksville.mesh.database.PacketRepository
import com.geeksville.mesh.database.entity.Packet
import com.geeksville.mesh.repository.datastore.LocalConfigRepository
import com.geeksville.mesh.service.MeshService
import com.geeksville.mesh.util.positionToMeter
import dagger.hilt.android.lifecycle.HiltViewModel
@ -59,6 +60,7 @@ fun getInitials(nameIn: String): String {
class UIViewModel @Inject constructor(
private val app: Application,
private val packetRepository: PacketRepository,
private val localConfigRepository: LocalConfigRepository,
private val preferences: SharedPreferences
) : ViewModel(), Logging {
@ -67,8 +69,15 @@ class UIViewModel @Inject constructor(
init {
viewModelScope.launch {
packetRepository.getAllPackets().collect { packets ->
_allPacketState.value = packets
launch {
packetRepository.getAllPackets().collect { packets ->
_allPacketState.value = packets
}
}
launch(Dispatchers.IO) {
localConfigRepository.localConfigFlow.collect { config ->
_localConfig.postValue(config)
}
}
}
debug("ViewModel created")
@ -238,18 +247,6 @@ class UIViewModel @Inject constructor(
private fun setDeviceConfig(config: ConfigProtos.Config) {
debug("Setting new radio config!")
meshService?.deviceConfig = config.toByteArray()
// Must be done after calling the service, so we will will properly throw if the service failed (and therefore not cache invalid new settings)
_localConfig.value?.let { localConfig ->
val builder = localConfig.toBuilder()
if (config.hasDevice()) builder.device = config.device
if (config.hasPosition()) builder.position = config.position
if (config.hasPower()) builder.power = config.power
if (config.hasWifi()) builder.wifi = config.wifi
if (config.hasDisplay()) builder.display = config.display
if (config.hasLora()) builder.lora = config.lora
_localConfig.value = builder.build()
}
}
fun setLocalConfig(localConfig: LocalOnlyProtos.LocalConfig) {

Wyświetl plik

@ -0,0 +1,35 @@
package com.geeksville.mesh.repository.datastore
import android.content.Context
import androidx.datastore.core.DataStore
import androidx.datastore.core.DataStoreFactory
import androidx.datastore.core.handlers.ReplaceFileCorruptionHandler
import androidx.datastore.dataStoreFile
import com.geeksville.mesh.LocalOnlyProtos.LocalConfig
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import javax.inject.Singleton
@InstallIn(SingletonComponent::class)
@Module
object DataStoreModule {
@Singleton
@Provides
fun provideLocalConfigDataStore(@ApplicationContext appContext: Context): DataStore<LocalConfig> {
return DataStoreFactory.create(
serializer = LocalConfigSerializer,
produceFile = { appContext.dataStoreFile("local_config.pb") },
corruptionHandler = ReplaceFileCorruptionHandler(
produceNewData = { LocalConfig.getDefaultInstance() }
),
scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
)
}
}

Wyświetl plik

@ -0,0 +1,104 @@
package com.geeksville.mesh.repository.datastore
import androidx.datastore.core.DataStore
import com.geeksville.android.Logging
import com.geeksville.mesh.ConfigProtos
import com.geeksville.mesh.LocalOnlyProtos.LocalConfig
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.asSharedFlow
import java.io.IOException
import javax.inject.Inject
/**
* Class that handles saving and retrieving config settings
*/
class LocalConfigRepository @Inject constructor(
private val localConfigStore: DataStore<LocalConfig>
) : Logging {
val localConfigFlow: Flow<LocalConfig> = localConfigStore.data
.catch { exception ->
// dataStore.data throws an IOException when an error is encountered when reading data
if (exception is IOException) {
errormsg("Error reading LocalConfig settings: ${exception.message}")
emit(LocalConfig.getDefaultInstance())
} else {
throw exception
}
}
private val _sendDeviceConfigFlow = MutableSharedFlow<ConfigProtos.Config>()
val sendDeviceConfigFlow = _sendDeviceConfigFlow.asSharedFlow()
private suspend fun sendDeviceConfig(config: ConfigProtos.Config) {
debug("Sending device config!")
_sendDeviceConfigFlow.emit(config)
}
/**
* Update LocalConfig and send ConfigProtos.Config Oneof to the radio
*/
suspend fun setRadioConfig(config: ConfigProtos.Config) {
setLocalConfig(config)
sendDeviceConfig(config)
}
suspend fun clearLocalConfig() {
localConfigStore.updateData { preference ->
preference.toBuilder().clear().build()
}
}
/**
* Update LocalConfig from each ConfigProtos.Config Oneof
*/
suspend fun setLocalConfig(config: ConfigProtos.Config) {
if (config.hasDevice()) setDeviceConfig(config.device)
if (config.hasPosition()) setPositionConfig(config.position)
if (config.hasPower()) setPowerConfig(config.power)
if (config.hasWifi()) setWifiConfig(config.wifi)
if (config.hasDisplay()) setDisplayConfig(config.display)
if (config.hasLora()) setLoraConfig(config.lora)
}
private suspend fun setDeviceConfig(config: ConfigProtos.Config.DeviceConfig) {
localConfigStore.updateData { preference ->
preference.toBuilder().setDevice(config).build()
}
}
private suspend fun setPositionConfig(config: ConfigProtos.Config.PositionConfig) {
localConfigStore.updateData { preference ->
preference.toBuilder().setPosition(config).build()
}
}
private suspend fun setPowerConfig(config: ConfigProtos.Config.PowerConfig) {
localConfigStore.updateData { preference ->
preference.toBuilder().setPower(config).build()
}
}
private suspend fun setWifiConfig(config: ConfigProtos.Config.WiFiConfig) {
localConfigStore.updateData { preference ->
preference.toBuilder().setWifi(config).build()
}
}
private suspend fun setDisplayConfig(config: ConfigProtos.Config.DisplayConfig) {
localConfigStore.updateData { preference ->
preference.toBuilder().setDisplay(config).build()
}
}
private suspend fun setLoraConfig(config: ConfigProtos.Config.LoRaConfig) {
localConfigStore.updateData { preference ->
preference.toBuilder().setLora(config).build()
}
}
suspend fun fetchInitialLocalConfig() = localConfigStore.data.first()
}

Wyświetl plik

@ -0,0 +1,25 @@
package com.geeksville.mesh.repository.datastore
import androidx.datastore.core.CorruptionException
import androidx.datastore.core.Serializer
import com.geeksville.mesh.LocalOnlyProtos.LocalConfig
import com.google.protobuf.InvalidProtocolBufferException
import java.io.InputStream
import java.io.OutputStream
/**
* Serializer for the [LocalConfig] object defined in localonly.proto.
*/
object LocalConfigSerializer : Serializer<LocalConfig> {
override val defaultValue: LocalConfig = LocalConfig.getDefaultInstance()
override suspend fun readFrom(input: InputStream): LocalConfig {
try {
return LocalConfig.parseFrom(input)
} catch (exception: InvalidProtocolBufferException) {
throw CorruptionException("Cannot read proto.", exception)
}
}
override suspend fun writeTo(t: LocalConfig, output: OutputStream) = t.writeTo(output)
}

Wyświetl plik

@ -18,6 +18,7 @@ import com.geeksville.mesh.android.hasBackgroundPermission
import com.geeksville.mesh.database.PacketRepository
import com.geeksville.mesh.database.entity.Packet
import com.geeksville.mesh.model.DeviceVersion
import com.geeksville.mesh.repository.datastore.LocalConfigRepository
import com.geeksville.mesh.repository.location.LocationRepository
import com.geeksville.mesh.repository.radio.BluetoothInterface
import com.geeksville.mesh.repository.radio.RadioInterfaceService
@ -57,6 +58,9 @@ class MeshService : Service(), Logging {
@Inject
lateinit var locationRepository: LocationRepository
@Inject
lateinit var localConfigRepository: LocalConfigRepository
companion object : Logging {
/// Intents broadcast by MeshService
@ -242,6 +246,11 @@ class MeshService : Service(), Logging {
serviceScope.handledLaunch {
radioInterfaceService.receivedData.collect(::onReceiveFromRadio)
}
serviceScope.handledLaunch {
localConfigRepository.localConfigFlow.collect { config ->
localConfig = config
}
}
// the rest of our init will happen once we are in radioConnection.onServiceConnected
}
@ -498,7 +507,7 @@ class MeshService : Service(), Logging {
newPrefs.regionValue = curRegionValue
newConfig.lora = newPrefs.build()
sendDeviceConfig(newConfig.build())
if (localConfig.lora != newConfig.lora) sendDeviceConfig(newConfig.build())
channels = fixupChannelList(asChannels)
}
@ -691,7 +700,7 @@ class MeshService : Service(), Logging {
if (u.time == 0 && packet.rxTime != 0)
u = u.toBuilder().setTime(packet.rxTime).build()
handleReceivedTelemetry(packet.from, u, dataPacket.time)
}
}
// Handle new style routing info
Portnums.PortNum.ROUTING_APP_VALUE -> {
@ -935,6 +944,12 @@ class MeshService : Service(), Logging {
}
}
private fun setLocalConfig (config: ConfigProtos.Config) {
serviceScope.handledLaunch {
localConfigRepository.setLocalConfig(config)
}
}
private fun currentSecond() = (System.currentTimeMillis() / 1000).toInt()
@ -1261,7 +1276,9 @@ class MeshService : Service(), Logging {
regenMyNodeInfo()
// We'll need to get a new set of channels and settings now
localConfig = LocalOnlyProtos.LocalConfig.getDefaultInstance()
serviceScope.handledLaunch {
localConfigRepository.clearLocalConfig()
}
// prefill the channel array with null channels
channels = fixupChannelList(listOf<ChannelProtos.Channel>())
@ -1476,19 +1493,6 @@ class MeshService : Service(), Logging {
setLocalConfig(c)
}
/** Set our localConfig
*/
private fun setLocalConfig(config: ConfigProtos.Config) {
val builder = localConfig.toBuilder()
if (config.hasDevice()) builder.device = config.device
if (config.hasPosition()) builder.position = config.position
if (config.hasPower()) builder.power = config.power
if (config.hasWifi()) builder.wifi = config.wifi
if (config.hasDisplay()) builder.display = config.display
if (config.hasLora()) builder.lora = config.lora
localConfig = builder.build()
}
/**
* Set our owner with either the new or old API
*/

Wyświetl plik

@ -5,6 +5,7 @@ buildscript {
ext.coroutines_version = '1.6.0'
ext.room_version = '2.4.2'
ext.hilt_version = '2.40.5'
ext.datastore_version = '1.0.0'
repositories {
google()