NSCv3: websockets support
This commit is contained in:
parent
9757428577
commit
89ad9e21dd
29 changed files with 576 additions and 210 deletions
|
@ -1,5 +1,6 @@
|
|||
package info.nightscout.sdk.mapper
|
||||
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.JsonParser
|
||||
import info.nightscout.sdk.localmodel.devicestatus.NSDeviceStatus
|
||||
import info.nightscout.sdk.remotemodel.RemoteDeviceStatus
|
||||
|
@ -8,6 +9,9 @@ import org.json.JSONObject
|
|||
fun NSDeviceStatus.convertToRemoteAndBack(): NSDeviceStatus =
|
||||
toRemoteDeviceStatus().toNSDeviceStatus()
|
||||
|
||||
fun String.toNSDeviceStatus(): NSDeviceStatus =
|
||||
Gson().fromJson(this, RemoteDeviceStatus::class.java).toNSDeviceStatus()
|
||||
|
||||
internal fun RemoteDeviceStatus.toNSDeviceStatus(): NSDeviceStatus =
|
||||
NSDeviceStatus(
|
||||
app = app,
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package info.nightscout.sdk.mapper
|
||||
|
||||
import com.google.gson.Gson
|
||||
import info.nightscout.sdk.localmodel.food.NSFood
|
||||
import info.nightscout.sdk.remotemodel.RemoteFood
|
||||
|
||||
|
@ -12,6 +13,9 @@ import info.nightscout.sdk.remotemodel.RemoteFood
|
|||
fun NSFood.convertToRemoteAndBack(): NSFood? =
|
||||
toRemoteFood().toNSFood()
|
||||
|
||||
fun String.toNSFood(): NSFood? =
|
||||
Gson().fromJson(this, RemoteFood::class.java).toNSFood()
|
||||
|
||||
internal fun RemoteFood.toNSFood(): NSFood? {
|
||||
when (type) {
|
||||
"food" ->
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package info.nightscout.sdk.mapper
|
||||
|
||||
import com.google.gson.Gson
|
||||
import info.nightscout.sdk.localmodel.entry.Direction
|
||||
import info.nightscout.sdk.localmodel.entry.NSSgvV3
|
||||
import info.nightscout.sdk.localmodel.entry.NsUnits
|
||||
|
@ -8,6 +9,9 @@ import info.nightscout.sdk.remotemodel.RemoteEntry
|
|||
fun NSSgvV3.convertToRemoteAndBack(): NSSgvV3? =
|
||||
toRemoteEntry().toSgv()
|
||||
|
||||
fun String.toNSSgvV3(): NSSgvV3? =
|
||||
Gson().fromJson(this, RemoteEntry::class.java).toSgv()
|
||||
|
||||
internal fun RemoteEntry.toSgv(): NSSgvV3? {
|
||||
|
||||
this.sgv ?: return null
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package info.nightscout.sdk.mapper
|
||||
|
||||
import com.google.gson.Gson
|
||||
import info.nightscout.sdk.localmodel.entry.NsUnits
|
||||
import info.nightscout.sdk.localmodel.treatment.EventType
|
||||
import info.nightscout.sdk.localmodel.treatment.NSBolus
|
||||
|
@ -26,6 +27,9 @@ import java.util.concurrent.TimeUnit
|
|||
fun NSTreatment.convertToRemoteAndBack(): NSTreatment? =
|
||||
toRemoteTreatment()?.toTreatment()
|
||||
|
||||
fun String.toNSTreatment(): NSTreatment? =
|
||||
Gson().fromJson(this, RemoteTreatment::class.java).toTreatment()
|
||||
|
||||
internal fun RemoteTreatment.toTreatment(): NSTreatment? {
|
||||
val treatmentTimestamp = timestamp()
|
||||
when {
|
||||
|
|
|
@ -22,4 +22,15 @@ data class LastModified(
|
|||
@SerializedName("foods") var foods: Long = 0, // foods collection
|
||||
@SerializedName("settings") var settings: Long = 0 // settings collection
|
||||
)
|
||||
|
||||
fun set(colName: String, value: Long) {
|
||||
when (colName) {
|
||||
"devicestatus" -> collections.devicestatus = value
|
||||
"entries" -> collections.entries = value
|
||||
"profile" -> collections.profile = value
|
||||
"treatments" -> collections.treatments = value
|
||||
"foods" -> collections.foods = value
|
||||
"settings" -> collections.settings = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -85,6 +85,7 @@
|
|||
<string name="key_ns_receive_profile_store" translatable="false">ns_receive_profile_store</string>
|
||||
<string name="key_nsclientinternal_url" translatable="false">nsclientinternal_url</string>
|
||||
<string name="key_nsclientinternal_api_secret" translatable="false">nsclientinternal_api_secret</string>
|
||||
<string name="key_ns_use_ws" translatable="false">ns_use_ws</string>
|
||||
<string name="key_ns_receive_insulin" translatable="false">ns_receive_insulin</string>
|
||||
<string name="key_ns_receive_carbs" translatable="false">ns_receive_carbs</string>
|
||||
<string name="key_ns_receive_therapy_events" translatable="false">ns_receive_therapy_events</string>
|
||||
|
|
|
@ -75,7 +75,7 @@ class RunningConfigurationImpl @Inject constructor(
|
|||
assert(config.NSCLIENT)
|
||||
|
||||
configuration.version?.let {
|
||||
rxBus.send(EventNSClientNewLog("VERSION", "Received AAPS version $it"))
|
||||
rxBus.send(EventNSClientNewLog("◄ VERSION", "Received AAPS version $it"))
|
||||
if (config.VERSION_NAME.startsWith(it).not())
|
||||
uiInteraction.addNotification(Notification.NSCLIENT_VERSION_DOES_NOT_MATCH, rh.gs(R.string.nsclient_version_does_not_match), Notification.NORMAL)
|
||||
}
|
||||
|
|
|
@ -116,7 +116,7 @@ class NSClientSourcePlugin @Inject constructor(
|
|||
return TransactionGlucoseValue(
|
||||
timestamp = sgv.date ?: throw InvalidParameterException(),
|
||||
value = sgv.sgv,
|
||||
noise = sgv.noise?.toDouble(),
|
||||
noise = sgv.noise,
|
||||
raw = sgv.filtered,
|
||||
trendArrow = GlucoseValue.TrendArrow.fromString(sgv.direction?.nsName),
|
||||
nightscoutId = sgv.identifier,
|
||||
|
|
|
@ -60,6 +60,7 @@ abstract class SyncModule {
|
|||
@ContributesAndroidInjector abstract fun contributesLoadProfileStoreWorker(): LoadProfileStoreWorker
|
||||
@ContributesAndroidInjector abstract fun contributesStoreBgWorker(): StoreDataForDbImpl.StoreBgWorker
|
||||
@ContributesAndroidInjector abstract fun contributesStoreFoodWorker(): StoreDataForDbImpl.StoreFoodWorker
|
||||
@ContributesAndroidInjector abstract fun contributesStoreTreatmentsWorker(): StoreDataForDbImpl.StoreTreatmentsWorker
|
||||
@ContributesAndroidInjector abstract fun contributesTreatmentWorker(): LoadTreatmentsWorker
|
||||
@ContributesAndroidInjector abstract fun contributesProcessTreatmentsWorker(): ProcessTreatmentsWorker
|
||||
@ContributesAndroidInjector abstract fun contributesLoadDeviceStatusWorker(): LoadDeviceStatusWorker
|
||||
|
|
|
@ -57,7 +57,7 @@ class DataSyncSelectorImplementation @Inject constructor(
|
|||
}
|
||||
|
||||
private val queueCounter = QueueCounter()
|
||||
private val isPaused get() = sp.getBoolean(R.string.key_ns_client_paused, false)
|
||||
private val isPaused get() = sp.getBoolean(R.string.key_ns_paused, false)
|
||||
|
||||
override fun queueSize(): Long = queueCounter.size()
|
||||
|
||||
|
|
|
@ -81,7 +81,7 @@ class NSClientFragment : DaggerFragment(), MenuProvider, PluginFragment {
|
|||
updateGui()
|
||||
}
|
||||
|
||||
binding.paused.isChecked = sp.getBoolean(R.string.key_ns_client_paused, false)
|
||||
binding.paused.isChecked = sp.getBoolean(R.string.key_ns_paused, false)
|
||||
binding.paused.setOnCheckedChangeListener { _, isChecked ->
|
||||
uel.log(if (isChecked) UserEntry.Action.NS_PAUSED else UserEntry.Action.NS_RESUME, UserEntry.Sources.NSClient)
|
||||
nsClientPlugin?.pause(isChecked)
|
||||
|
@ -143,7 +143,7 @@ class NSClientFragment : DaggerFragment(), MenuProvider, PluginFragment {
|
|||
|
||||
private fun updateGui() {
|
||||
if (_binding == null) return
|
||||
binding.paused.isChecked = sp.getBoolean(R.string.key_ns_client_paused, false)
|
||||
binding.paused.isChecked = sp.getBoolean(R.string.key_ns_paused, false)
|
||||
binding.log.text = nsClientPlugin?.textLog()
|
||||
if (sp.getBoolean(R.string.key_ns_client_autoscroll, true)) binding.logScrollview.fullScroll(ScrollView.FOCUS_DOWN)
|
||||
binding.url.text = nsClientPlugin?.address
|
||||
|
|
|
@ -147,13 +147,24 @@ class StoreDataForDbImpl @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
class StoreTreatmentsWorker(
|
||||
context: Context,
|
||||
params: WorkerParameters
|
||||
) : LoggingWorker(context, params, Dispatchers.Default) {
|
||||
|
||||
@Inject lateinit var storeDataForDb: StoreDataForDb
|
||||
|
||||
override suspend fun doWorkAndLog(): Result {
|
||||
storeDataForDb.storeTreatmentsToDb()
|
||||
return Result.success()
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> HashMap<T, Long>.inc(key: T) =
|
||||
if (containsKey(key)) merge(key, 1, Long::plus)
|
||||
else put(key, 1)
|
||||
|
||||
override fun storeGlucoseValuesToDb() {
|
||||
rxBus.send(EventNSClientNewLog("PROCESSING BG", ""))
|
||||
|
||||
if (glucoseValues.isNotEmpty())
|
||||
repository.runTransactionForResult(CgmSourceTransaction(glucoseValues, emptyList(), null))
|
||||
.doOnError {
|
||||
|
@ -184,12 +195,10 @@ class StoreDataForDbImpl @Inject constructor(
|
|||
|
||||
sendLog("GlucoseValue", GlucoseValue::class.java.simpleName)
|
||||
SystemClock.sleep(pause)
|
||||
rxBus.send(EventNSClientNewLog("DONE BG", ""))
|
||||
rxBus.send(EventNSClientNewLog("● DONE PROCESSING BG", ""))
|
||||
}
|
||||
|
||||
override fun storeFoodsToDb() {
|
||||
rxBus.send(EventNSClientNewLog("PROCESSING FOOD", ""))
|
||||
|
||||
if (foods.isNotEmpty())
|
||||
repository.runTransactionForResult(SyncNsFoodTransaction(foods))
|
||||
.doOnError {
|
||||
|
@ -214,12 +223,10 @@ class StoreDataForDbImpl @Inject constructor(
|
|||
|
||||
sendLog("Food", Food::class.java.simpleName)
|
||||
SystemClock.sleep(pause)
|
||||
rxBus.send(EventNSClientNewLog("DONE FOOD", ""))
|
||||
rxBus.send(EventNSClientNewLog("● DONE PROCESSING FOOD", ""))
|
||||
}
|
||||
|
||||
override fun storeTreatmentsToDb() {
|
||||
rxBus.send(EventNSClientNewLog("PROCESSING TR", ""))
|
||||
|
||||
if (boluses.isNotEmpty())
|
||||
repository.runTransactionForResult(SyncNsBolusTransaction(boluses))
|
||||
.doOnError {
|
||||
|
@ -796,7 +803,7 @@ class StoreDataForDbImpl @Inject constructor(
|
|||
SystemClock.sleep(pause)
|
||||
|
||||
uel.log(userEntries)
|
||||
rxBus.send(EventNSClientNewLog("DONE TR", ""))
|
||||
rxBus.send(EventNSClientNewLog("● DONE PROCESSING TR", ""))
|
||||
}
|
||||
|
||||
private val eventWorker = Executors.newSingleThreadScheduledExecutor()
|
||||
|
@ -996,7 +1003,7 @@ class StoreDataForDbImpl @Inject constructor(
|
|||
sendLog("TherapyEvent", TherapyEvent::class.java.simpleName)
|
||||
sendLog("OfflineEvent", OfflineEvent::class.java.simpleName)
|
||||
sendLog("ExtendedBolus", ExtendedBolus::class.java.simpleName)
|
||||
rxBus.send(EventNSClientNewLog("DONE NSIDs", ""))
|
||||
rxBus.send(EventNSClientNewLog("● DONE NSIDs", ""))
|
||||
}
|
||||
|
||||
private fun sendLog(item: String, clazz: String) {
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
package info.nightscout.plugins.sync.nsShared.events
|
||||
|
||||
import info.nightscout.rx.events.Event
|
||||
|
||||
class EventNSConnectivityOptionChanged(val blockingReason: String) : Event()
|
|
@ -14,7 +14,6 @@ import androidx.preference.SwitchPreference
|
|||
import dagger.android.HasAndroidInjector
|
||||
import info.nightscout.core.extensions.toJson
|
||||
import info.nightscout.core.utils.fabric.FabricPrivacy
|
||||
import info.nightscout.core.validators.ValidatingEditTextPreference
|
||||
import info.nightscout.interfaces.Config
|
||||
import info.nightscout.interfaces.Constants
|
||||
import info.nightscout.interfaces.nsclient.NSAlarm
|
||||
|
@ -41,9 +40,7 @@ import info.nightscout.plugins.sync.nsclient.services.NSClientService
|
|||
import info.nightscout.rx.AapsSchedulers
|
||||
import info.nightscout.rx.bus.RxBus
|
||||
import info.nightscout.rx.events.EventAppExit
|
||||
import info.nightscout.rx.events.EventChargingState
|
||||
import info.nightscout.rx.events.EventNSClientNewLog
|
||||
import info.nightscout.rx.events.EventNetworkChange
|
||||
import info.nightscout.rx.events.EventPreferenceChange
|
||||
import info.nightscout.rx.events.EventSWSyncStatus
|
||||
import info.nightscout.rx.logging.AAPSLogger
|
||||
|
@ -109,14 +106,6 @@ class NSClientPlugin @Inject constructor(
|
|||
// Pass to setup wizard
|
||||
rxBus.send(EventSWSyncStatus(event.getStatus(context)))
|
||||
}, fabricPrivacy::logException)
|
||||
disposable += rxBus
|
||||
.toObservable(EventNetworkChange::class.java)
|
||||
.observeOn(aapsSchedulers.io)
|
||||
.subscribe({ ev -> nsClientReceiverDelegate.onStatusEvent(ev) }, fabricPrivacy::logException)
|
||||
disposable += rxBus
|
||||
.toObservable(EventPreferenceChange::class.java)
|
||||
.observeOn(aapsSchedulers.io)
|
||||
.subscribe({ ev -> nsClientReceiverDelegate.onStatusEvent(ev) }, fabricPrivacy::logException)
|
||||
disposable += rxBus
|
||||
.toObservable(EventAppExit::class.java)
|
||||
.observeOn(aapsSchedulers.io)
|
||||
|
@ -128,10 +117,6 @@ class NSClientPlugin @Inject constructor(
|
|||
addToLog(event)
|
||||
aapsLogger.debug(LTag.NSCLIENT, event.action + " " + event.logText)
|
||||
}, fabricPrivacy::logException)
|
||||
disposable += rxBus
|
||||
.toObservable(EventChargingState::class.java)
|
||||
.observeOn(aapsSchedulers.io)
|
||||
.subscribe({ ev -> nsClientReceiverDelegate.onStatusEvent(ev) }, fabricPrivacy::logException)
|
||||
disposable += rxBus
|
||||
.toObservable(EventNSClientResend::class.java)
|
||||
.observeOn(aapsSchedulers.io)
|
||||
|
@ -181,7 +166,7 @@ class NSClientPlugin @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override fun detectedNsVersion(): String? = nsSettingsStatus.getVersion()
|
||||
override fun detectedNsVersion(): String = nsSettingsStatus.getVersion()
|
||||
|
||||
private fun addToLog(ev: EventNSClientNewLog) {
|
||||
synchronized(listLog) {
|
||||
|
@ -212,8 +197,8 @@ class NSClientPlugin @Inject constructor(
|
|||
}
|
||||
|
||||
override fun pause(newState: Boolean) {
|
||||
sp.putBoolean(R.string.key_ns_client_paused, newState)
|
||||
rxBus.send(EventPreferenceChange(rh.gs(R.string.key_ns_client_paused)))
|
||||
sp.putBoolean(R.string.key_ns_paused, newState)
|
||||
rxBus.send(EventPreferenceChange(rh.gs(R.string.key_ns_paused)))
|
||||
}
|
||||
|
||||
override val address: String get() = nsClientService?.nsURL ?: ""
|
||||
|
|
|
@ -1,14 +1,19 @@
|
|||
package info.nightscout.plugins.sync.nsclient
|
||||
|
||||
import info.nightscout.androidaps.annotations.OpenForTesting
|
||||
import info.nightscout.core.utils.fabric.FabricPrivacy
|
||||
import info.nightscout.interfaces.receivers.ReceiverStatusStore
|
||||
import info.nightscout.plugins.sync.R
|
||||
import info.nightscout.plugins.sync.nsShared.events.EventNSConnectivityOptionChanged
|
||||
import info.nightscout.rx.AapsSchedulers
|
||||
import info.nightscout.rx.bus.RxBus
|
||||
import info.nightscout.rx.events.EventChargingState
|
||||
import info.nightscout.rx.events.EventNetworkChange
|
||||
import info.nightscout.rx.events.EventPreferenceChange
|
||||
import info.nightscout.shared.interfaces.ResourceHelper
|
||||
import info.nightscout.shared.sharedPreferences.SP
|
||||
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
||||
import io.reactivex.rxjava3.kotlin.plusAssign
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
|
@ -18,26 +23,45 @@ class NsClientReceiverDelegate @Inject constructor(
|
|||
private val rxBus: RxBus,
|
||||
private val rh: ResourceHelper,
|
||||
private val sp: SP,
|
||||
private val receiverStatusStore: ReceiverStatusStore
|
||||
private val receiverStatusStore: ReceiverStatusStore,
|
||||
aapsSchedulers: AapsSchedulers,
|
||||
fabricPrivacy: FabricPrivacy
|
||||
) {
|
||||
|
||||
private var allowedChargingState = true
|
||||
private var allowedNetworkState = true
|
||||
var allowed = true
|
||||
var blockingReason = ""
|
||||
private var allowedChargingState: Boolean? = null
|
||||
private var allowedNetworkState: Boolean? = null
|
||||
var allowed: Boolean = false
|
||||
var blockingReason = "Status not available"
|
||||
|
||||
private val disposable = CompositeDisposable()
|
||||
|
||||
init {
|
||||
disposable += rxBus
|
||||
.toObservable(EventPreferenceChange::class.java)
|
||||
.observeOn(aapsSchedulers.io)
|
||||
.subscribe({ ev -> onPreferenceChange(ev) }, fabricPrivacy::logException)
|
||||
disposable += rxBus
|
||||
.toObservable(EventNetworkChange::class.java)
|
||||
.observeOn(aapsSchedulers.io)
|
||||
.subscribe({ ev -> onNetworkChange(ev) }, fabricPrivacy::logException)
|
||||
disposable += rxBus
|
||||
.toObservable(EventChargingState::class.java)
|
||||
.observeOn(aapsSchedulers.io)
|
||||
.subscribe({ ev -> onChargingStateChange(ev) }, fabricPrivacy::logException)
|
||||
}
|
||||
|
||||
fun grabReceiversState() {
|
||||
receiverStatusStore.updateNetworkStatus()
|
||||
}
|
||||
|
||||
fun onStatusEvent(ev: EventPreferenceChange) {
|
||||
private fun onPreferenceChange(ev: EventPreferenceChange) {
|
||||
when {
|
||||
ev.isChanged(rh.gs(R.string.key_ns_wifi)) ||
|
||||
ev.isChanged(rh.gs(R.string.key_ns_cellular)) ||
|
||||
ev.isChanged(rh.gs(R.string.key_ns_wifi_ssids)) ||
|
||||
ev.isChanged(rh.gs(R.string.key_ns_allow_roaming)) -> {
|
||||
receiverStatusStore.updateNetworkStatus()
|
||||
receiverStatusStore.lastNetworkEvent?.let { onStatusEvent(it) }
|
||||
receiverStatusStore.lastNetworkEvent?.let { onNetworkChange(it) }
|
||||
}
|
||||
|
||||
ev.isChanged(rh.gs(R.string.key_ns_charging)) ||
|
||||
|
@ -47,29 +71,30 @@ class NsClientReceiverDelegate @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
fun onStatusEvent(ev: EventChargingState) {
|
||||
private fun onChargingStateChange(ev: EventChargingState) {
|
||||
val newChargingState = calculateStatus(ev)
|
||||
if (newChargingState != allowedChargingState) {
|
||||
allowedChargingState = newChargingState
|
||||
blockingReason = rh.gs(R.string.blocked_by_charging)
|
||||
if (!newChargingState) blockingReason = rh.gs(R.string.blocked_by_charging)
|
||||
processStateChange()
|
||||
}
|
||||
}
|
||||
|
||||
fun onStatusEvent(ev: EventNetworkChange) {
|
||||
private fun onNetworkChange(ev: EventNetworkChange) {
|
||||
val newNetworkState = calculateStatus(ev)
|
||||
if (newNetworkState != allowedNetworkState) {
|
||||
allowedNetworkState = newNetworkState
|
||||
blockingReason = rh.gs(R.string.blocked_by_connectivity)
|
||||
if (!newNetworkState) blockingReason = rh.gs(R.string.blocked_by_connectivity)
|
||||
processStateChange()
|
||||
}
|
||||
}
|
||||
|
||||
private fun processStateChange() {
|
||||
val newAllowedState = allowedChargingState && allowedNetworkState
|
||||
val newAllowedState = allowedChargingState == true && allowedNetworkState == true
|
||||
if (newAllowedState != allowed) {
|
||||
allowed = newAllowedState
|
||||
rxBus.send(EventPreferenceChange(rh.gs(R.string.key_ns_client_paused)))
|
||||
if (allowed) blockingReason = ""
|
||||
rxBus.send(EventNSConnectivityOptionChanged(blockingReason))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -33,6 +33,7 @@ import info.nightscout.plugins.sync.R
|
|||
import info.nightscout.plugins.sync.nsShared.StoreDataForDbImpl
|
||||
import info.nightscout.plugins.sync.nsShared.events.EventNSClientStatus
|
||||
import info.nightscout.plugins.sync.nsShared.events.EventNSClientUpdateGUI
|
||||
import info.nightscout.plugins.sync.nsShared.events.EventNSConnectivityOptionChanged
|
||||
import info.nightscout.plugins.sync.nsclient.NSClientPlugin
|
||||
import info.nightscout.plugins.sync.nsclient.acks.NSAddAck
|
||||
import info.nightscout.plugins.sync.nsclient.acks.NSAuthAck
|
||||
|
@ -140,13 +141,21 @@ class NSClientService : DaggerService() {
|
|||
.subscribe({ event: EventPreferenceChange ->
|
||||
if (event.isChanged(rh.gs(info.nightscout.core.utils.R.string.key_nsclientinternal_url)) ||
|
||||
event.isChanged(rh.gs(info.nightscout.core.utils.R.string.key_nsclientinternal_api_secret)) ||
|
||||
event.isChanged(rh.gs(R.string.key_ns_client_paused))
|
||||
event.isChanged(rh.gs(R.string.key_ns_paused))
|
||||
) {
|
||||
latestDateInReceivedData = 0
|
||||
destroy()
|
||||
initialize()
|
||||
}
|
||||
}, fabricPrivacy::logException)
|
||||
disposable += rxBus
|
||||
.toObservable(EventNSConnectivityOptionChanged::class.java)
|
||||
.observeOn(aapsSchedulers.io)
|
||||
.subscribe({
|
||||
latestDateInReceivedData = 0
|
||||
destroy()
|
||||
initialize()
|
||||
}, fabricPrivacy::logException)
|
||||
disposable += rxBus
|
||||
.toObservable(EventAppExit::class.java)
|
||||
.observeOn(aapsSchedulers.io)
|
||||
|
@ -218,10 +227,10 @@ class NSClientService : DaggerService() {
|
|||
@Suppress("DEPRECATION")
|
||||
if (nsAPISecret != "") nsApiHashCode = Hashing.sha1().hashString(nsAPISecret, Charsets.UTF_8).toString()
|
||||
rxBus.send(EventNSClientStatus("Initializing"))
|
||||
if (!nsClientPlugin.isAllowed) {
|
||||
if (nsClientPlugin.isAllowed != true) {
|
||||
rxBus.send(EventNSClientNewLog("NSCLIENT", nsClientPlugin.blockingReason))
|
||||
rxBus.send(EventNSClientStatus(nsClientPlugin.blockingReason))
|
||||
} else if (sp.getBoolean(R.string.key_ns_client_paused, false)) {
|
||||
} else if (sp.getBoolean(R.string.key_ns_paused, false)) {
|
||||
rxBus.send(EventNSClientNewLog("NSCLIENT", "paused"))
|
||||
rxBus.send(EventNSClientStatus("Paused"))
|
||||
} else if (!nsEnabled) {
|
||||
|
@ -230,9 +239,7 @@ class NSClientService : DaggerService() {
|
|||
} else if (nsURL != "" && (nsURL.lowercase(Locale.getDefault()).startsWith("https://"))) {
|
||||
try {
|
||||
rxBus.send(EventNSClientStatus("Connecting ..."))
|
||||
val opt = IO.Options()
|
||||
opt.forceNew = true
|
||||
opt.reconnection = true
|
||||
val opt = IO.Options().also { it.forceNew = true }
|
||||
socket = IO.socket(nsURL, opt).also { socket ->
|
||||
socket.on(Socket.EVENT_CONNECT, onConnect)
|
||||
socket.on(Socket.EVENT_DISCONNECT, onDisconnect)
|
||||
|
|
|
@ -3,6 +3,7 @@ package info.nightscout.plugins.sync.nsclientV3
|
|||
import android.content.Context
|
||||
import android.os.Handler
|
||||
import android.os.HandlerThread
|
||||
import android.os.SystemClock
|
||||
import android.text.Spanned
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import androidx.preference.PreferenceScreen
|
||||
|
@ -16,26 +17,34 @@ import com.google.gson.GsonBuilder
|
|||
import dagger.android.HasAndroidInjector
|
||||
import info.nightscout.androidaps.annotations.OpenForTesting
|
||||
import info.nightscout.core.utils.fabric.FabricPrivacy
|
||||
import info.nightscout.core.utils.receivers.DataWorkerStorage
|
||||
import info.nightscout.database.ValueWrapper
|
||||
import info.nightscout.database.entities.interfaces.TraceableDBEntry
|
||||
import info.nightscout.database.impl.AppRepository
|
||||
import info.nightscout.interfaces.Config
|
||||
import info.nightscout.interfaces.Constants
|
||||
import info.nightscout.interfaces.notifications.Notification
|
||||
import info.nightscout.interfaces.nsclient.NSAlarm
|
||||
import info.nightscout.interfaces.plugin.PluginBase
|
||||
import info.nightscout.interfaces.plugin.PluginDescription
|
||||
import info.nightscout.interfaces.plugin.PluginType
|
||||
import info.nightscout.interfaces.profile.ProfileFunction
|
||||
import info.nightscout.interfaces.source.NSClientSource
|
||||
import info.nightscout.interfaces.sync.DataSyncSelector
|
||||
import info.nightscout.interfaces.sync.NsClient
|
||||
import info.nightscout.interfaces.sync.Sync
|
||||
import info.nightscout.interfaces.ui.UiInteraction
|
||||
import info.nightscout.interfaces.utils.HtmlHelper
|
||||
import info.nightscout.interfaces.utils.JsonHelper
|
||||
import info.nightscout.interfaces.workflow.WorkerClasses
|
||||
import info.nightscout.plugins.sync.R
|
||||
import info.nightscout.plugins.sync.nsShared.NSClientFragment
|
||||
import info.nightscout.plugins.sync.nsShared.StoreDataForDbImpl
|
||||
import info.nightscout.plugins.sync.nsShared.events.EventNSClientResend
|
||||
import info.nightscout.plugins.sync.nsShared.events.EventNSClientUpdateGUI
|
||||
import info.nightscout.plugins.sync.nsShared.events.EventNSConnectivityOptionChanged
|
||||
import info.nightscout.plugins.sync.nsclient.NsClientReceiverDelegate
|
||||
import info.nightscout.plugins.sync.nsclient.data.NSDeviceStatusHandler
|
||||
import info.nightscout.plugins.sync.nsclientV3.extensions.toNSBolus
|
||||
import info.nightscout.plugins.sync.nsclientV3.extensions.toNSBolusWizard
|
||||
import info.nightscout.plugins.sync.nsclientV3.extensions.toNSCarbs
|
||||
|
@ -49,16 +58,17 @@ import info.nightscout.plugins.sync.nsclientV3.extensions.toNSSvgV3
|
|||
import info.nightscout.plugins.sync.nsclientV3.extensions.toNSTemporaryBasal
|
||||
import info.nightscout.plugins.sync.nsclientV3.extensions.toNSTemporaryTarget
|
||||
import info.nightscout.plugins.sync.nsclientV3.extensions.toNSTherapyEvent
|
||||
import info.nightscout.plugins.sync.nsclientV3.workers.DataSyncWorker
|
||||
import info.nightscout.plugins.sync.nsclientV3.workers.LoadBgWorker
|
||||
import info.nightscout.plugins.sync.nsclientV3.workers.LoadLastModificationWorker
|
||||
import info.nightscout.plugins.sync.nsclientV3.workers.LoadStatusWorker
|
||||
import info.nightscout.plugins.sync.nsclientV3.workers.ProcessFoodWorker
|
||||
import info.nightscout.plugins.sync.nsclientV3.workers.ProcessTreatmentsWorker
|
||||
import info.nightscout.rx.AapsSchedulers
|
||||
import info.nightscout.rx.bus.RxBus
|
||||
import info.nightscout.rx.events.EventAppExit
|
||||
import info.nightscout.rx.events.EventChargingState
|
||||
import info.nightscout.rx.events.EventDismissNotification
|
||||
import info.nightscout.rx.events.EventNSClientNewLog
|
||||
import info.nightscout.rx.events.EventNetworkChange
|
||||
import info.nightscout.rx.events.EventNewBG
|
||||
import info.nightscout.rx.events.EventNewHistoryData
|
||||
import info.nightscout.rx.events.EventPreferenceChange
|
||||
import info.nightscout.rx.events.EventSWSyncStatus
|
||||
|
@ -66,6 +76,10 @@ import info.nightscout.rx.logging.AAPSLogger
|
|||
import info.nightscout.rx.logging.LTag
|
||||
import info.nightscout.sdk.NSAndroidClientImpl
|
||||
import info.nightscout.sdk.interfaces.NSAndroidClient
|
||||
import info.nightscout.sdk.mapper.toNSDeviceStatus
|
||||
import info.nightscout.sdk.mapper.toNSFood
|
||||
import info.nightscout.sdk.mapper.toNSSgvV3
|
||||
import info.nightscout.sdk.mapper.toNSTreatment
|
||||
import info.nightscout.sdk.remotemodel.LastModified
|
||||
import info.nightscout.shared.interfaces.ResourceHelper
|
||||
import info.nightscout.shared.sharedPreferences.SP
|
||||
|
@ -73,15 +87,19 @@ import info.nightscout.shared.utils.DateUtil
|
|||
import info.nightscout.shared.utils.T
|
||||
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
||||
import io.reactivex.rxjava3.kotlin.plusAssign
|
||||
import io.socket.client.Ack
|
||||
import io.socket.client.IO
|
||||
import io.socket.client.Socket
|
||||
import io.socket.emitter.Emitter
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.ScheduledFuture
|
||||
import java.util.concurrent.TimeUnit
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
import java.net.URISyntaxException
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
|
@ -102,7 +120,12 @@ class NSClientV3Plugin @Inject constructor(
|
|||
private val uiInteraction: UiInteraction,
|
||||
private val dataSyncSelector: DataSyncSelector,
|
||||
private val profileFunction: ProfileFunction,
|
||||
private val repository: AppRepository
|
||||
private val repository: AppRepository,
|
||||
private val nsDeviceStatusHandler: NSDeviceStatusHandler,
|
||||
private val workManager: WorkManager,
|
||||
private val workerClasses: WorkerClasses,
|
||||
private val dataWorkerStorage: DataWorkerStorage,
|
||||
private val nsClientSource: NSClientSource
|
||||
) : NsClient, Sync, PluginBase(
|
||||
PluginDescription()
|
||||
.mainType(PluginType.SYNC)
|
||||
|
@ -118,7 +141,6 @@ class NSClientV3Plugin @Inject constructor(
|
|||
companion object {
|
||||
|
||||
val JOB_NAME: String = this::class.java.simpleName
|
||||
val REFRESH_INTERVAL = T.secs(30).msecs()
|
||||
const val RECORDS_TO_LOAD = 500L
|
||||
}
|
||||
|
||||
|
@ -130,8 +152,10 @@ class NSClientV3Plugin @Inject constructor(
|
|||
override val status
|
||||
get() =
|
||||
when {
|
||||
sp.getBoolean(R.string.key_ns_client_paused, false) -> rh.gs(info.nightscout.core.ui.R.string.paused)
|
||||
sp.getBoolean(R.string.key_ns_paused, false) -> rh.gs(info.nightscout.core.ui.R.string.paused)
|
||||
isAllowed.not() -> blockingReason
|
||||
sp.getBoolean(info.nightscout.core.utils.R.string.key_ns_use_ws, true) && wsConnected -> "WS: " + rh.gs(info.nightscout.shared.R.string.connected)
|
||||
sp.getBoolean(info.nightscout.core.utils.R.string.key_ns_use_ws, true) && !wsConnected -> "WS: " + rh.gs(R.string.not_connected)
|
||||
lastOperationError != null -> rh.gs(info.nightscout.core.ui.R.string.error)
|
||||
nsAndroidClient?.lastStatus == null -> rh.gs(R.string.not_connected)
|
||||
workIsRunning(arrayOf(JOB_NAME)) -> rh.gs(R.string.working)
|
||||
|
@ -161,26 +185,30 @@ class NSClientV3Plugin @Inject constructor(
|
|||
)
|
||||
)
|
||||
|
||||
setClient()
|
||||
setClient("START")
|
||||
|
||||
nsClientReceiverDelegate.grabReceiversState()
|
||||
disposable += rxBus
|
||||
.toObservable(EventNetworkChange::class.java)
|
||||
.toObservable(EventNSConnectivityOptionChanged::class.java)
|
||||
.observeOn(aapsSchedulers.io)
|
||||
.subscribe({ ev ->
|
||||
nsClientReceiverDelegate.onStatusEvent(ev)
|
||||
setClient()
|
||||
rxBus.send(EventNSClientUpdateGUI())
|
||||
rxBus.send(EventNSClientNewLog("● CONNECTIVITY CHANGE", ev.blockingReason))
|
||||
setClient("CONNECTIVITY_CHANGE")
|
||||
if (isAllowed) executeLoop("CONNECTIVITY_CHANGE", forceNew = false)
|
||||
}, fabricPrivacy::logException)
|
||||
disposable += rxBus
|
||||
.toObservable(EventPreferenceChange::class.java)
|
||||
.observeOn(aapsSchedulers.io)
|
||||
.subscribe({ ev ->
|
||||
nsClientReceiverDelegate.onStatusEvent(ev)
|
||||
if (ev.isChanged(rh.gs(R.string.key_ns_client_token)) || ev.isChanged(rh.gs(info.nightscout.core.utils.R.string.key_nsclientinternal_url)))
|
||||
setClient()
|
||||
if (ev.isChanged(rh.gs(R.string.key_ns_client_token)) ||
|
||||
ev.isChanged(rh.gs(info.nightscout.core.utils.R.string.key_nsclientinternal_url)) ||
|
||||
ev.isChanged(rh.gs(info.nightscout.core.utils.R.string.key_ns_use_ws)) ||
|
||||
ev.isChanged(rh.gs(R.string.key_ns_paused))
|
||||
)
|
||||
setClient("SETTING CHANGE")
|
||||
if (ev.isChanged(rh.gs(info.nightscout.core.utils.R.string.key_local_profile_last_change)))
|
||||
delayAndScheduleExecution("PROFILE_CHANGE")
|
||||
executeUpload("PROFILE_CHANGE", forceNew = true)
|
||||
|
||||
}, fabricPrivacy::logException)
|
||||
disposable += rxBus
|
||||
.toObservable(EventAppExit::class.java)
|
||||
|
@ -193,47 +221,55 @@ class NSClientV3Plugin @Inject constructor(
|
|||
addToLog(event)
|
||||
aapsLogger.debug(LTag.NSCLIENT, event.action + " " + event.logText)
|
||||
}, fabricPrivacy::logException)
|
||||
disposable += rxBus
|
||||
.toObservable(EventChargingState::class.java)
|
||||
.observeOn(aapsSchedulers.io)
|
||||
.subscribe({ ev ->
|
||||
nsClientReceiverDelegate.onStatusEvent(ev)
|
||||
rxBus.send(EventNSClientUpdateGUI())
|
||||
}, fabricPrivacy::logException)
|
||||
disposable += rxBus
|
||||
.toObservable(EventNSClientResend::class.java)
|
||||
.observeOn(aapsSchedulers.io)
|
||||
.subscribe({ event -> resend(event.reason) }, fabricPrivacy::logException)
|
||||
disposable += rxBus
|
||||
.toObservable(EventNewBG::class.java)
|
||||
.observeOn(aapsSchedulers.io)
|
||||
.subscribe({ delayAndScheduleExecution("NEW_BG") }, fabricPrivacy::logException)
|
||||
disposable += rxBus
|
||||
.toObservable(EventNewHistoryData::class.java)
|
||||
.observeOn(aapsSchedulers.io)
|
||||
.subscribe({ delayAndScheduleExecution("NEW_DATA") }, fabricPrivacy::logException)
|
||||
.subscribe({ executeUpload("NEW_DATA", forceNew = false) }, fabricPrivacy::logException)
|
||||
|
||||
runLoop = Runnable {
|
||||
handler.postDelayed(runLoop, REFRESH_INTERVAL)
|
||||
var refreshInterval = T.mins(5).msecs()
|
||||
repository.getLastGlucoseValueWrapped().blockingGet().let {
|
||||
// if last value is older than 5 min or there is no bg
|
||||
if (it is ValueWrapper.Existing) {
|
||||
if (it.value.timestamp < dateUtil.now() - T.mins(5).plus(T.secs(20)).msecs())
|
||||
if (it.value.timestamp < dateUtil.now() - T.mins(5).plus(T.secs(20)).msecs()) {
|
||||
refreshInterval = T.mins(1).msecs()
|
||||
executeLoop("MAIN_LOOP", forceNew = false)
|
||||
else {
|
||||
if (isAllowed) rxBus.send(EventNSClientNewLog("RECENT", "No need to load"))
|
||||
}
|
||||
} else executeLoop("MAIN_LOOP", forceNew = false)
|
||||
}
|
||||
handler.postDelayed(runLoop, refreshInterval)
|
||||
rxBus.send(EventNSClientNewLog("● TICK", ""))
|
||||
}
|
||||
handler.postDelayed(runLoop, T.mins(2).msecs())
|
||||
}
|
||||
|
||||
fun scheduleIrregularExecution(refreshToken: Boolean = false) {
|
||||
if (refreshToken) {
|
||||
handler.post { executeLoop("REFRESH TOKEN", forceNew = true) }
|
||||
return
|
||||
}
|
||||
if (config.NSCLIENT || nsClientSource.isEnabled()) {
|
||||
var origin = "5_MIN_AFTER_BG"
|
||||
var forceNew = true
|
||||
var toTime = lastLoadedSrvModified.collections.entries + T.mins(5).plus(T.secs(10)).msecs()
|
||||
if (toTime < dateUtil.now()) {
|
||||
toTime = dateUtil.now() + T.mins(1).plus(T.secs(0)).msecs()
|
||||
origin = "1_MIN_OLD_DATA"
|
||||
forceNew = false
|
||||
}
|
||||
handler.postDelayed({ executeLoop(origin, forceNew = forceNew) }, toTime - dateUtil.now())
|
||||
rxBus.send(EventNSClientNewLog("● NEXT", dateUtil.dateAndTimeAndSecondsString(toTime)))
|
||||
}
|
||||
handler.postDelayed(runLoop, REFRESH_INTERVAL)
|
||||
executeLoop("START", forceNew = false)
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
// context.applicationContext.unbindService(mConnection)
|
||||
handler.removeCallbacksAndMessages(null)
|
||||
disposable.clear()
|
||||
socket?.disconnect()
|
||||
super.onStop()
|
||||
}
|
||||
|
||||
|
@ -257,16 +293,269 @@ class NSClientV3Plugin @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun setClient() {
|
||||
private fun setClient(reason: String) {
|
||||
nsAndroidClient = NSAndroidClientImpl(
|
||||
baseUrl = sp.getString(info.nightscout.core.utils.R.string.key_nsclientinternal_url, "").lowercase().replace("https://", "").replace(Regex("/$"), ""),
|
||||
accessToken = sp.getString(R.string.key_ns_client_token, ""),
|
||||
context = context,
|
||||
logging = true
|
||||
)
|
||||
if (wsConnected)
|
||||
socket?.disconnect()
|
||||
SystemClock.sleep(2000)
|
||||
initializeWebSockets(reason)
|
||||
rxBus.send(EventSWSyncStatus(status))
|
||||
}
|
||||
|
||||
/**********************
|
||||
WS code
|
||||
**********************/
|
||||
private var connectCounter = 0
|
||||
private var socket: Socket? = null
|
||||
var wsConnected = false
|
||||
internal var initialLoadFinished = false
|
||||
private fun initializeWebSockets(reason: String) {
|
||||
if (!sp.getBoolean(info.nightscout.core.utils.R.string.key_ns_use_ws, true)) return
|
||||
val url = sp.getString(info.nightscout.core.utils.R.string.key_nsclientinternal_url, "").lowercase().replace(Regex("/$"), "") + "/storage"
|
||||
if (!isAllowed) {
|
||||
rxBus.send(EventNSClientNewLog("● WS", blockingReason))
|
||||
} else if (sp.getBoolean(R.string.key_ns_paused, false)) {
|
||||
rxBus.send(EventNSClientNewLog("● WS", "paused"))
|
||||
} else if (url != "") {
|
||||
try {
|
||||
//rxBus.send(EventNSClientStatus("Connecting ..."))
|
||||
val opt = IO.Options().also {
|
||||
it.forceNew = true
|
||||
//it.webSocketFactory = nsAndroidClient.
|
||||
}
|
||||
socket = IO.socket(url, opt).also { socket ->
|
||||
socket.on(Socket.EVENT_CONNECT, onConnect)
|
||||
socket.on(Socket.EVENT_DISCONNECT, onDisconnect)
|
||||
rxBus.send(EventNSClientNewLog("► WS", "do connect $reason"))
|
||||
socket.connect()
|
||||
socket.on("create", onDataCreateUpdate)
|
||||
socket.on("update", onDataCreateUpdate)
|
||||
socket.on("delete", onDataDelete)
|
||||
socket.on("announcement", onAnnouncement)
|
||||
socket.on("alarm", onAlarm)
|
||||
socket.on("urgent_alarm", onUrgentAlarm)
|
||||
socket.on("clear_alarm", onClearAlarm)
|
||||
}
|
||||
} catch (e: URISyntaxException) {
|
||||
rxBus.send(EventNSClientNewLog("● WS", "Wrong URL syntax"))
|
||||
} catch (e: RuntimeException) {
|
||||
rxBus.send(EventNSClientNewLog("● WS", "RuntimeException"))
|
||||
}
|
||||
} else {
|
||||
rxBus.send(EventNSClientNewLog("● WS", "No NS URL specified"))
|
||||
}
|
||||
}
|
||||
|
||||
private val onConnect = Emitter.Listener {
|
||||
connectCounter++
|
||||
val socketId = socket?.id() ?: "NULL"
|
||||
rxBus.send(EventNSClientNewLog("◄ WS", "connect #$connectCounter event. ID: $socketId"))
|
||||
if (socket != null) {
|
||||
val authMessage = JSONObject().also {
|
||||
it.put("accessToken", sp.getString(R.string.key_ns_client_token, ""))
|
||||
it.put("collections", JSONArray(arrayOf<String>("devicestatus", "entries", "profile", "treatments", "foods", "settings")))
|
||||
}
|
||||
rxBus.send(EventNSClientNewLog("► WS", "requesting auth"))
|
||||
socket?.emit("subscribe", authMessage, Ack { args ->
|
||||
val response = args[0] as JSONObject
|
||||
wsConnected = if (response.optBoolean("success")) {
|
||||
rxBus.send(EventNSClientNewLog("◄ WS", "Subscribed for: ${response.optString("collections")}"))
|
||||
true
|
||||
} else {
|
||||
rxBus.send(EventNSClientNewLog("◄ WS", "Auth failed"))
|
||||
false
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private val onDisconnect = Emitter.Listener { args ->
|
||||
aapsLogger.debug(LTag.NSCLIENT, "disconnect reason: ${args[0]}")
|
||||
rxBus.send(EventNSClientNewLog("◄ WS", "disconnect event"))
|
||||
wsConnected = false
|
||||
initialLoadFinished = false
|
||||
socket = null
|
||||
}
|
||||
|
||||
private val onDataCreateUpdate = Emitter.Listener { args ->
|
||||
val response = args[0] as JSONObject
|
||||
aapsLogger.debug(LTag.NSCLIENT, "onDataCreateUpdate: $response")
|
||||
val collection = response.getString("colName")
|
||||
val docJson = response.getJSONObject("doc")
|
||||
val docString = response.getString("doc")
|
||||
rxBus.send(EventNSClientNewLog("◄ WS CREATE/UPDATE", "$collection <i>$docString</i>"))
|
||||
val srvModified = docJson.getLong("srvModified")
|
||||
lastLoadedSrvModified.set(collection, srvModified)
|
||||
storeLastLoadedSrvModified()
|
||||
when (collection) {
|
||||
"devicestatus" -> docString.toNSDeviceStatus().let { nsDeviceStatusHandler.handleNewData(arrayOf(it)) }
|
||||
"entries" -> docString.toNSSgvV3()?.let {
|
||||
workManager.beginUniqueWork(
|
||||
JOB_NAME + collection,
|
||||
ExistingWorkPolicy.APPEND_OR_REPLACE,
|
||||
OneTimeWorkRequest.Builder(workerClasses.nsClientSourceWorker).setInputData(dataWorkerStorage.storeInputData(listOf(it))).build()
|
||||
)
|
||||
.then(OneTimeWorkRequest.Builder(StoreDataForDbImpl.StoreBgWorker::class.java).build())
|
||||
.enqueue()
|
||||
}
|
||||
|
||||
"profile" ->
|
||||
workManager.enqueueUniqueWork(
|
||||
JOB_NAME + collection,
|
||||
ExistingWorkPolicy.APPEND_OR_REPLACE,
|
||||
OneTimeWorkRequest.Builder(workerClasses.nsProfileWorker).setInputData(dataWorkerStorage.storeInputData(docJson)).build()
|
||||
)
|
||||
|
||||
"treatments" -> docString.toNSTreatment()?.let {
|
||||
workManager.beginUniqueWork(
|
||||
JOB_NAME + collection,
|
||||
ExistingWorkPolicy.APPEND_OR_REPLACE,
|
||||
OneTimeWorkRequest.Builder(ProcessTreatmentsWorker::class.java).setInputData(dataWorkerStorage.storeInputData(listOf(it))).build()
|
||||
)
|
||||
.then(OneTimeWorkRequest.Builder(StoreDataForDbImpl.StoreTreatmentsWorker::class.java).build())
|
||||
.enqueue()
|
||||
}
|
||||
|
||||
"foods" -> docString.toNSFood()?.let {
|
||||
workManager.beginUniqueWork(
|
||||
JOB_NAME + collection,
|
||||
ExistingWorkPolicy.APPEND_OR_REPLACE,
|
||||
OneTimeWorkRequest.Builder(ProcessFoodWorker::class.java).setInputData(dataWorkerStorage.storeInputData(listOf(it))).build()
|
||||
)
|
||||
.then(OneTimeWorkRequest.Builder(StoreDataForDbImpl.StoreFoodWorker::class.java).build())
|
||||
.enqueue()
|
||||
}
|
||||
|
||||
"settings" -> {}
|
||||
}
|
||||
}
|
||||
|
||||
private val onDataDelete = Emitter.Listener { args ->
|
||||
val response = args[0] as JSONObject
|
||||
aapsLogger.debug(LTag.NSCLIENT, "onDataDelete: $response")
|
||||
rxBus.send(EventNSClientNewLog("◄ WS DELETE", "${response.optString("collection")} ${response.optString("doc")}"))
|
||||
}
|
||||
|
||||
private val onAnnouncement = Emitter.Listener { args ->
|
||||
|
||||
/*
|
||||
{
|
||||
"level":0,
|
||||
"title":"Announcement",
|
||||
"message":"test",
|
||||
"plugin":{"name":"treatmentnotify","label":"Treatment Notifications","pluginType":"notification","enabled":true},
|
||||
"group":"Announcement",
|
||||
"isAnnouncement":true,
|
||||
"key":"9ac46ad9a1dcda79dd87dae418fce0e7955c68da"
|
||||
}
|
||||
*/
|
||||
val data: JSONObject
|
||||
try {
|
||||
data = args[0] as JSONObject
|
||||
handleAnnouncement(data)
|
||||
} catch (e: Exception) {
|
||||
aapsLogger.error("Unhandled exception", e)
|
||||
}
|
||||
}
|
||||
private val onAlarm = Emitter.Listener { args ->
|
||||
|
||||
/*
|
||||
{
|
||||
"level":1,
|
||||
"title":"Warning HIGH",
|
||||
"message":"BG Now: 5 -0.2 → mmol\/L\nRaw BG: 4.8 mmol\/L Čistý\nBG 15m: 4.8 mmol\/L\nIOB: -0.02U\nCOB: 0g",
|
||||
"eventName":"high",
|
||||
"plugin":{"name":"simplealarms","label":"Simple Alarms","pluginType":"notification","enabled":true},
|
||||
"pushoverSound":"climb",
|
||||
"debug":{"lastSGV":5,"thresholds":{"bgHigh":180,"bgTargetTop":75,"bgTargetBottom":72,"bgLow":70}},
|
||||
"group":"default",
|
||||
"key":"simplealarms_1"
|
||||
}
|
||||
*/
|
||||
val data: JSONObject
|
||||
try {
|
||||
data = args[0] as JSONObject
|
||||
handleAlarm(data)
|
||||
} catch (e: Exception) {
|
||||
aapsLogger.error("Unhandled exception", e)
|
||||
}
|
||||
}
|
||||
private val onUrgentAlarm = Emitter.Listener { args: Array<Any> ->
|
||||
val data: JSONObject
|
||||
try {
|
||||
data = args[0] as JSONObject
|
||||
handleUrgentAlarm(data)
|
||||
} catch (e: Exception) {
|
||||
aapsLogger.error("Unhandled exception", e)
|
||||
}
|
||||
}
|
||||
private val onClearAlarm = Emitter.Listener { args ->
|
||||
|
||||
/*
|
||||
{
|
||||
"clear":true,
|
||||
"title":"All Clear",
|
||||
"message":"default - Urgent was ack'd",
|
||||
"group":"default"
|
||||
}
|
||||
*/
|
||||
val data: JSONObject
|
||||
try {
|
||||
data = args[0] as JSONObject
|
||||
rxBus.send(EventNSClientNewLog("CLEARALARM", "received"))
|
||||
rxBus.send(EventDismissNotification(Notification.NS_ALARM))
|
||||
rxBus.send(EventDismissNotification(Notification.NS_URGENT_ALARM))
|
||||
aapsLogger.debug(LTag.NSCLIENT, data.toString())
|
||||
} catch (e: Exception) {
|
||||
aapsLogger.error("Unhandled exception", e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleAnnouncement(announcement: JSONObject) {
|
||||
val defaultVal = config.NSCLIENT
|
||||
if (sp.getBoolean(info.nightscout.core.utils.R.string.key_ns_announcements, defaultVal)) {
|
||||
val nsAlarm = NSAlarm(announcement)
|
||||
uiInteraction.addNotificationWithAction(injector, nsAlarm)
|
||||
rxBus.send(EventNSClientNewLog("ANNOUNCEMENT", JsonHelper.safeGetString(announcement, "message", "received")))
|
||||
aapsLogger.debug(LTag.NSCLIENT, announcement.toString())
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleAlarm(alarm: JSONObject) {
|
||||
val defaultVal = config.NSCLIENT
|
||||
if (sp.getBoolean(info.nightscout.core.utils.R.string.key_ns_alarms, defaultVal)) {
|
||||
val snoozedTo = sp.getLong(info.nightscout.core.utils.R.string.key_snoozed_to, 0L)
|
||||
if (snoozedTo == 0L || System.currentTimeMillis() > snoozedTo) {
|
||||
val nsAlarm = NSAlarm(alarm)
|
||||
uiInteraction.addNotificationWithAction(injector, nsAlarm)
|
||||
}
|
||||
rxBus.send(EventNSClientNewLog("ALARM", JsonHelper.safeGetString(alarm, "message", "received")))
|
||||
aapsLogger.debug(LTag.NSCLIENT, alarm.toString())
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleUrgentAlarm(alarm: JSONObject) {
|
||||
val defaultVal = config.NSCLIENT
|
||||
if (sp.getBoolean(info.nightscout.core.utils.R.string.key_ns_alarms, defaultVal)) {
|
||||
val snoozedTo = sp.getLong(info.nightscout.core.utils.R.string.key_snoozed_to, 0L)
|
||||
if (snoozedTo == 0L || System.currentTimeMillis() > snoozedTo) {
|
||||
val nsAlarm = NSAlarm(alarm)
|
||||
uiInteraction.addNotificationWithAction(injector, nsAlarm)
|
||||
}
|
||||
rxBus.send(EventNSClientNewLog("URGENTALARM", JsonHelper.safeGetString(alarm, "message", "received")))
|
||||
aapsLogger.debug(LTag.NSCLIENT, alarm.toString())
|
||||
}
|
||||
}
|
||||
|
||||
/**********************
|
||||
WS code end
|
||||
**********************/
|
||||
|
||||
private fun addToLog(ev: EventNSClientNewLog) {
|
||||
synchronized(listLog) {
|
||||
listLog.add(ev)
|
||||
|
@ -292,12 +581,15 @@ class NSClientV3Plugin @Inject constructor(
|
|||
}
|
||||
|
||||
override fun resend(reason: String) {
|
||||
if (sp.getBoolean(info.nightscout.core.utils.R.string.key_ns_use_ws, true))
|
||||
executeUpload("RESEND", forceNew = false)
|
||||
else
|
||||
executeLoop("RESEND", forceNew = false)
|
||||
}
|
||||
|
||||
override fun pause(newState: Boolean) {
|
||||
sp.putBoolean(R.string.key_ns_client_paused, newState)
|
||||
rxBus.send(EventPreferenceChange(rh.gs(R.string.key_ns_client_paused)))
|
||||
sp.putBoolean(R.string.key_ns_paused, newState)
|
||||
rxBus.send(EventPreferenceChange(rh.gs(R.string.key_ns_paused)))
|
||||
}
|
||||
|
||||
override fun detectedNsVersion(): String? = nsAndroidClient?.lastStatus?.version
|
||||
|
@ -337,6 +629,7 @@ class NSClientV3Plugin @Inject constructor(
|
|||
override fun resetToFullSync() {
|
||||
firstLoadContinueTimestamp = LastModified(LastModified.Collections())
|
||||
lastLoadedSrvModified = LastModified(LastModified.Collections())
|
||||
initialLoadFinished = false
|
||||
storeLastLoadedSrvModified()
|
||||
}
|
||||
|
||||
|
@ -355,15 +648,15 @@ class NSClientV3Plugin @Inject constructor(
|
|||
val data = (dataPair as DataSyncSelector.PairProfileStore).value
|
||||
scope.launch {
|
||||
try {
|
||||
rxBus.send(EventNSClientNewLog("ADD $collection", "Sent ${dataPair.javaClass.simpleName} $data $progress"))
|
||||
rxBus.send(EventNSClientNewLog("► ADD $collection", "Sent ${dataPair.javaClass.simpleName} <i>$data</i> $progress"))
|
||||
nsAndroidClient?.createProfileStore(data)?.let { result ->
|
||||
when (result.response) {
|
||||
200 -> rxBus.send(EventNSClientNewLog("UPDATED", "OK ProfileStore"))
|
||||
201 -> rxBus.send(EventNSClientNewLog("ADDED", "OK ProfileStore"))
|
||||
404 -> rxBus.send(EventNSClientNewLog("NOT_FOUND", "${dataPair.value.javaClass.simpleName} ${result.errorResponse}"))
|
||||
200 -> rxBus.send(EventNSClientNewLog("◄ UPDATED", "OK ProfileStore"))
|
||||
201 -> rxBus.send(EventNSClientNewLog("◄ ADDED", "OK ProfileStore"))
|
||||
404 -> rxBus.send(EventNSClientNewLog("◄ NOT_FOUND", "${dataPair.value.javaClass.simpleName} ${result.errorResponse}"))
|
||||
|
||||
else -> {
|
||||
rxBus.send(EventNSClientNewLog("ERROR", "ProfileStore"))
|
||||
rxBus.send(EventNSClientNewLog("◄ ERROR", "ProfileStore"))
|
||||
return@launch
|
||||
}
|
||||
}
|
||||
|
@ -385,15 +678,15 @@ class NSClientV3Plugin @Inject constructor(
|
|||
val data = (dataPair as DataSyncSelector.PairDeviceStatus).value.toNSDeviceStatus()
|
||||
scope.launch {
|
||||
try {
|
||||
rxBus.send(EventNSClientNewLog("ADD $collection", "Sent ${dataPair.javaClass.simpleName} ${gson.toJson(data)} $progress"))
|
||||
rxBus.send(EventNSClientNewLog("► ADD $collection", "Sent ${dataPair.javaClass.simpleName} <i>${gson.toJson(data)}</i> $progress"))
|
||||
nsAndroidClient?.createDeviceStatus(data)?.let { result ->
|
||||
when (result.response) {
|
||||
200 -> rxBus.send(EventNSClientNewLog("UPDATED", "OK ${dataPair.value.javaClass.simpleName}"))
|
||||
201 -> rxBus.send(EventNSClientNewLog("ADDED", "OK ${dataPair.value.javaClass.simpleName} ${result.identifier}"))
|
||||
404 -> rxBus.send(EventNSClientNewLog("NOT_FOUND", "${dataPair.value.javaClass.simpleName} ${result.errorResponse}"))
|
||||
200 -> rxBus.send(EventNSClientNewLog("◄ UPDATED", "OK ${dataPair.value.javaClass.simpleName}"))
|
||||
201 -> rxBus.send(EventNSClientNewLog("◄ ADDED", "OK ${dataPair.value.javaClass.simpleName} ${result.identifier}"))
|
||||
404 -> rxBus.send(EventNSClientNewLog("◄ NOT_FOUND", "${dataPair.value.javaClass.simpleName} ${result.errorResponse}"))
|
||||
|
||||
else -> {
|
||||
rxBus.send(EventNSClientNewLog("ERROR", "${dataPair.value.javaClass.simpleName} "))
|
||||
rxBus.send(EventNSClientNewLog("◄ ERROR", "${dataPair.value.javaClass.simpleName} "))
|
||||
return@launch
|
||||
}
|
||||
}
|
||||
|
@ -426,24 +719,24 @@ class NSClientV3Plugin @Inject constructor(
|
|||
rxBus.send(
|
||||
EventNSClientNewLog(
|
||||
when (operation) {
|
||||
Operation.CREATE -> "ADD $collection"
|
||||
Operation.UPDATE -> "UPDATE $collection"
|
||||
Operation.CREATE -> "► ADD $collection"
|
||||
Operation.UPDATE -> "► UPDATE $collection"
|
||||
},
|
||||
when (operation) {
|
||||
Operation.CREATE -> "Sent ${dataPair.javaClass.simpleName} ${gson.toJson(data)} $progress"
|
||||
Operation.UPDATE -> "Sent ${dataPair.javaClass.simpleName} $id ${gson.toJson(data)} $progress"
|
||||
Operation.CREATE -> "Sent ${dataPair.javaClass.simpleName} <i>${gson.toJson(data)}</i> $progress"
|
||||
Operation.UPDATE -> "Sent ${dataPair.javaClass.simpleName} $id <i>${gson.toJson(data)}</i> $progress"
|
||||
}
|
||||
)
|
||||
)
|
||||
call?.let { it(data) }?.let { result ->
|
||||
when (result.response) {
|
||||
200 -> rxBus.send(EventNSClientNewLog("UPDATED", "OK ${dataPair.value.javaClass.simpleName}"))
|
||||
201 -> rxBus.send(EventNSClientNewLog("ADDED", "OK ${dataPair.value.javaClass.simpleName}"))
|
||||
400 -> rxBus.send(EventNSClientNewLog("FAIL", "${dataPair.value.javaClass.simpleName} ${result.errorResponse}"))
|
||||
404 -> rxBus.send(EventNSClientNewLog("NOT_FOUND", "${dataPair.value.javaClass.simpleName} ${result.errorResponse}"))
|
||||
200 -> rxBus.send(EventNSClientNewLog("◄ UPDATED", "OK ${dataPair.value.javaClass.simpleName}"))
|
||||
201 -> rxBus.send(EventNSClientNewLog("◄ ADDED", "OK ${dataPair.value.javaClass.simpleName}"))
|
||||
400 -> rxBus.send(EventNSClientNewLog("◄ FAIL", "${dataPair.value.javaClass.simpleName} ${result.errorResponse}"))
|
||||
404 -> rxBus.send(EventNSClientNewLog("◄ NOT_FOUND", "${dataPair.value.javaClass.simpleName} ${result.errorResponse}"))
|
||||
|
||||
else -> {
|
||||
rxBus.send(EventNSClientNewLog("ERROR", "${dataPair.value.javaClass.simpleName} "))
|
||||
rxBus.send(EventNSClientNewLog("◄ ERROR", "${dataPair.value.javaClass.simpleName} "))
|
||||
return@launch
|
||||
}
|
||||
}
|
||||
|
@ -481,24 +774,24 @@ class NSClientV3Plugin @Inject constructor(
|
|||
rxBus.send(
|
||||
EventNSClientNewLog(
|
||||
when (operation) {
|
||||
Operation.CREATE -> "ADD $collection"
|
||||
Operation.UPDATE -> "UPDATE $collection"
|
||||
Operation.CREATE -> "► ADD $collection"
|
||||
Operation.UPDATE -> "► UPDATE $collection"
|
||||
},
|
||||
when (operation) {
|
||||
Operation.CREATE -> "Sent ${dataPair.javaClass.simpleName} ${gson.toJson(data)} $progress"
|
||||
Operation.UPDATE -> "Sent ${dataPair.javaClass.simpleName} $id ${gson.toJson(data)} $progress"
|
||||
Operation.CREATE -> "Sent ${dataPair.javaClass.simpleName} <i>${gson.toJson(data)}</i> $progress"
|
||||
Operation.UPDATE -> "Sent ${dataPair.javaClass.simpleName} $id <i>${gson.toJson(data)}</i> $progress"
|
||||
}
|
||||
)
|
||||
)
|
||||
call?.let { it(data) }?.let { result ->
|
||||
when (result.response) {
|
||||
200 -> rxBus.send(EventNSClientNewLog("UPDATED", "OK ${dataPair.value.javaClass.simpleName}"))
|
||||
201 -> rxBus.send(EventNSClientNewLog("ADDED", "OK ${dataPair.value.javaClass.simpleName}"))
|
||||
400 -> rxBus.send(EventNSClientNewLog("FAIL", "${dataPair.value.javaClass.simpleName} ${result.errorResponse}"))
|
||||
404 -> rxBus.send(EventNSClientNewLog("NOT_FOUND", "${dataPair.value.javaClass.simpleName} ${result.errorResponse}"))
|
||||
200 -> rxBus.send(EventNSClientNewLog("◄ UPDATED", "OK ${dataPair.value.javaClass.simpleName}"))
|
||||
201 -> rxBus.send(EventNSClientNewLog("◄ ADDED", "OK ${dataPair.value.javaClass.simpleName}"))
|
||||
400 -> rxBus.send(EventNSClientNewLog("◄ FAIL", "${dataPair.value.javaClass.simpleName} ${result.errorResponse}"))
|
||||
404 -> rxBus.send(EventNSClientNewLog("◄ NOT_FOUND", "${dataPair.value.javaClass.simpleName} ${result.errorResponse}"))
|
||||
|
||||
else -> {
|
||||
rxBus.send(EventNSClientNewLog("ERROR", "${dataPair.value.javaClass.simpleName} "))
|
||||
rxBus.send(EventNSClientNewLog("◄ ERROR", "${dataPair.value.javaClass.simpleName} "))
|
||||
return@launch
|
||||
}
|
||||
}
|
||||
|
@ -564,24 +857,24 @@ class NSClientV3Plugin @Inject constructor(
|
|||
rxBus.send(
|
||||
EventNSClientNewLog(
|
||||
when (operation) {
|
||||
Operation.CREATE -> "ADD $collection"
|
||||
Operation.UPDATE -> "UPDATE $collection"
|
||||
Operation.CREATE -> "► ADD $collection"
|
||||
Operation.UPDATE -> "► UPDATE $collection"
|
||||
},
|
||||
when (operation) {
|
||||
Operation.CREATE -> "Sent ${dataPair.javaClass.simpleName} ${gson.toJson(data)} $progress"
|
||||
Operation.UPDATE -> "Sent ${dataPair.javaClass.simpleName} $id ${gson.toJson(data)} $progress"
|
||||
Operation.CREATE -> "Sent ${dataPair.javaClass.simpleName} <i>${gson.toJson(data)}</i> $progress"
|
||||
Operation.UPDATE -> "Sent ${dataPair.javaClass.simpleName} $id <i>${gson.toJson(data)}</i> $progress"
|
||||
}
|
||||
)
|
||||
)
|
||||
call?.let { it(data) }?.let { result ->
|
||||
when (result.response) {
|
||||
200 -> rxBus.send(EventNSClientNewLog("UPDATED", "OK ${dataPair.value.javaClass.simpleName}"))
|
||||
201 -> rxBus.send(EventNSClientNewLog("ADDED", "OK ${dataPair.value.javaClass.simpleName}"))
|
||||
400 -> rxBus.send(EventNSClientNewLog("FAIL", "${dataPair.value.javaClass.simpleName} ${result.errorResponse}"))
|
||||
404 -> rxBus.send(EventNSClientNewLog("NOT_FOUND", "${dataPair.value.javaClass.simpleName} ${result.errorResponse}"))
|
||||
200 -> rxBus.send(EventNSClientNewLog("◄ UPDATED", "OK ${dataPair.value.javaClass.simpleName}"))
|
||||
201 -> rxBus.send(EventNSClientNewLog("◄ ADDED", "OK ${dataPair.value.javaClass.simpleName}"))
|
||||
400 -> rxBus.send(EventNSClientNewLog("◄ FAIL", "${dataPair.value.javaClass.simpleName} ${result.errorResponse}"))
|
||||
404 -> rxBus.send(EventNSClientNewLog("◄ NOT_FOUND", "${dataPair.value.javaClass.simpleName} ${result.errorResponse}"))
|
||||
|
||||
else -> {
|
||||
rxBus.send(EventNSClientNewLog("ERROR", "${dataPair.value.javaClass.simpleName} "))
|
||||
rxBus.send(EventNSClientNewLog("◄ ERROR", "${dataPair.value.javaClass.simpleName} "))
|
||||
return@launch
|
||||
}
|
||||
}
|
||||
|
@ -708,35 +1001,23 @@ class NSClientV3Plugin @Inject constructor(
|
|||
sp.putString(R.string.key_ns_client_v3_last_modified, Json.encodeToString(LastModified.serializer(), lastLoadedSrvModified))
|
||||
}
|
||||
|
||||
fun scheduleIrregularExecution() {
|
||||
var origin = "5_MIN_AFTER_BG"
|
||||
var forceNew = true
|
||||
var toTime = lastLoadedSrvModified.collections.entries + T.mins(5).plus(T.secs(10)).msecs()
|
||||
if (toTime < dateUtil.now()) {
|
||||
toTime = dateUtil.now() + T.mins(1).plus(T.secs(0)).msecs()
|
||||
origin = "1_MIN_OLD_DATA"
|
||||
forceNew = false
|
||||
}
|
||||
handler.postDelayed({ executeLoop(origin, forceNew = forceNew) }, toTime - dateUtil.now())
|
||||
rxBus.send(EventNSClientNewLog("NEXT", dateUtil.dateAndTimeAndSecondsString(toTime)))
|
||||
}
|
||||
|
||||
private fun executeLoop(origin: String, forceNew: Boolean) {
|
||||
if (sp.getBoolean(R.string.key_ns_client_paused, false)) {
|
||||
rxBus.send(EventNSClientNewLog("RUN", "paused"))
|
||||
if (sp.getBoolean(info.nightscout.core.utils.R.string.key_ns_use_ws, true) && initialLoadFinished) return
|
||||
if (sp.getBoolean(R.string.key_ns_paused, false)) {
|
||||
rxBus.send(EventNSClientNewLog("● RUN", "paused $origin"))
|
||||
return
|
||||
}
|
||||
if (!isAllowed) {
|
||||
rxBus.send(EventNSClientNewLog("RUN", blockingReason))
|
||||
rxBus.send(EventNSClientNewLog("● RUN", "$blockingReason $origin"))
|
||||
return
|
||||
}
|
||||
if (workIsRunning(arrayOf(JOB_NAME))) {
|
||||
rxBus.send(EventNSClientNewLog("RUN", "Already running $origin"))
|
||||
rxBus.send(EventNSClientNewLog("● RUN", "Already running $origin"))
|
||||
if (!forceNew) return
|
||||
// Wait for end and start new cycle
|
||||
while (workIsRunning(arrayOf(JOB_NAME))) Thread.sleep(5000)
|
||||
}
|
||||
rxBus.send(EventNSClientNewLog("RUN", "Starting next round $origin"))
|
||||
rxBus.send(EventNSClientNewLog("● RUN", "Starting next round $origin"))
|
||||
WorkManager.getInstance(context)
|
||||
.beginUniqueWork(
|
||||
JOB_NAME,
|
||||
|
@ -754,6 +1035,30 @@ class NSClientV3Plugin @Inject constructor(
|
|||
.enqueue()
|
||||
}
|
||||
|
||||
private fun executeUpload(origin: String, forceNew: Boolean) {
|
||||
if (sp.getBoolean(R.string.key_ns_paused, false)) {
|
||||
rxBus.send(EventNSClientNewLog("● RUN", "paused"))
|
||||
return
|
||||
}
|
||||
if (!isAllowed) {
|
||||
rxBus.send(EventNSClientNewLog("● RUN", blockingReason))
|
||||
return
|
||||
}
|
||||
if (workIsRunning(arrayOf(JOB_NAME))) {
|
||||
rxBus.send(EventNSClientNewLog("● RUN", "Already running $origin"))
|
||||
if (!forceNew) return
|
||||
// Wait for end and start new cycle
|
||||
while (workIsRunning(arrayOf(JOB_NAME))) Thread.sleep(5000)
|
||||
}
|
||||
rxBus.send(EventNSClientNewLog("● RUN", "Starting upload $origin"))
|
||||
WorkManager.getInstance(context)
|
||||
.enqueueUniqueWork(
|
||||
JOB_NAME,
|
||||
ExistingWorkPolicy.REPLACE,
|
||||
OneTimeWorkRequest.Builder(DataSyncWorker::class.java).build()
|
||||
)
|
||||
}
|
||||
|
||||
private fun workIsRunning(workNames: Array<String>): Boolean {
|
||||
for (workName in workNames)
|
||||
for (workInfo in WorkManager.getInstance(context).getWorkInfosForUniqueWork(workName).get())
|
||||
|
@ -761,20 +1066,4 @@ class NSClientV3Plugin @Inject constructor(
|
|||
return true
|
||||
return false
|
||||
}
|
||||
|
||||
private val eventWorker = Executors.newSingleThreadScheduledExecutor()
|
||||
private var scheduledEventPost: ScheduledFuture<*>? = null
|
||||
private fun delayAndScheduleExecution(origin: String) {
|
||||
class PostRunnable : Runnable {
|
||||
|
||||
override fun run() {
|
||||
scheduledEventPost = null
|
||||
executeLoop(origin, forceNew = true)
|
||||
}
|
||||
}
|
||||
// cancel waiting task to prevent sending multiple posts
|
||||
scheduledEventPost?.cancel(false)
|
||||
val task: Runnable = PostRunnable()
|
||||
scheduledEventPost = eventWorker.schedule(task, 10, TimeUnit.SECONDS)
|
||||
}
|
||||
}
|
|
@ -7,6 +7,7 @@ import info.nightscout.core.utils.worker.LoggingWorker
|
|||
import info.nightscout.interfaces.plugin.ActivePlugin
|
||||
import info.nightscout.interfaces.sync.DataSyncSelector
|
||||
import info.nightscout.plugins.sync.nsShared.events.EventNSClientUpdateGUI
|
||||
import info.nightscout.plugins.sync.nsclientV3.NSClientV3Plugin
|
||||
import info.nightscout.rx.bus.RxBus
|
||||
import info.nightscout.rx.events.EventNSClientNewLog
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
@ -20,12 +21,17 @@ class DataSyncWorker(
|
|||
@Inject lateinit var dataSyncSelector: DataSyncSelector
|
||||
@Inject lateinit var activePlugin: ActivePlugin
|
||||
@Inject lateinit var rxBus: RxBus
|
||||
@Inject lateinit var nsClientV3Plugin: NSClientV3Plugin
|
||||
|
||||
override suspend fun doWorkAndLog(): Result {
|
||||
if (activePlugin.activeNsClient?.hasWritePermission == true) {
|
||||
rxBus.send(EventNSClientNewLog("UPL", "Start"))
|
||||
if (activePlugin.activeNsClient?.hasWritePermission == true || nsClientV3Plugin.wsConnected) {
|
||||
rxBus.send(EventNSClientNewLog("► UPL", "Start"))
|
||||
dataSyncSelector.doUpload()
|
||||
rxBus.send(EventNSClientNewLog("UPL", "End"))
|
||||
rxBus.send(EventNSClientNewLog("► UPL", "End"))
|
||||
} else {
|
||||
rxBus.send(EventNSClientNewLog("► ERROR", "Not connected or write permission"))
|
||||
// refresh token
|
||||
nsClientV3Plugin.scheduleIrregularExecution(refreshToken = true)
|
||||
}
|
||||
rxBus.send(EventNSClientUpdateGUI())
|
||||
return Result.success()
|
||||
|
|
|
@ -70,7 +70,7 @@ class LoadBgWorker(
|
|||
aapsLogger.debug(LTag.NSCLIENT, "SGVS: $sgvs")
|
||||
if (sgvs.isNotEmpty()) {
|
||||
val action = if (isFirstLoad) "RCV-FIRST" else "RCV"
|
||||
rxBus.send(EventNSClientNewLog(action, "${sgvs.size} SVGs from ${dateUtil.dateAndTimeAndSecondsString(lastLoaded)}"))
|
||||
rxBus.send(EventNSClientNewLog("◄ $action", "${sgvs.size} SVGs from ${dateUtil.dateAndTimeAndSecondsString(lastLoaded)}"))
|
||||
// Objective0
|
||||
sp.putBoolean(info.nightscout.core.utils.R.string.key_objectives_bg_is_available_in_ns, true)
|
||||
// Schedule processing of fetched data and continue of loading
|
||||
|
@ -90,7 +90,7 @@ class LoadBgWorker(
|
|||
nsClientV3Plugin.lastLoadedSrvModified.collections.entries = lastLoaded
|
||||
nsClientV3Plugin.storeLastLoadedSrvModified()
|
||||
}
|
||||
rxBus.send(EventNSClientNewLog("RCV END", "No SGVs from ${dateUtil.dateAndTimeAndSecondsString(lastLoaded)}"))
|
||||
rxBus.send(EventNSClientNewLog("◄ RCV BG END", "No data from ${dateUtil.dateAndTimeAndSecondsString(lastLoaded)}"))
|
||||
workManager
|
||||
.beginUniqueWork(
|
||||
NSClientV3Plugin.JOB_NAME,
|
||||
|
@ -106,7 +106,7 @@ class LoadBgWorker(
|
|||
nsClientV3Plugin.lastLoadedSrvModified.collections.entries = lastLoaded
|
||||
nsClientV3Plugin.storeLastLoadedSrvModified()
|
||||
}
|
||||
rxBus.send(EventNSClientNewLog("RCV END", "No new SGVs from ${dateUtil.dateAndTimeAndSecondsString(lastLoaded)}"))
|
||||
rxBus.send(EventNSClientNewLog("◄ RCV BG END", "No new data from ${dateUtil.dateAndTimeAndSecondsString(lastLoaded)}"))
|
||||
workManager
|
||||
.beginUniqueWork(
|
||||
NSClientV3Plugin.JOB_NAME,
|
||||
|
@ -118,7 +118,7 @@ class LoadBgWorker(
|
|||
}
|
||||
} catch (error: Exception) {
|
||||
aapsLogger.error("Error: ", error)
|
||||
rxBus.send(EventNSClientNewLog("ERROR", error.localizedMessage))
|
||||
rxBus.send(EventNSClientNewLog("◄ ERROR", error.localizedMessage))
|
||||
nsClientV3Plugin.lastOperationError = error.localizedMessage
|
||||
return Result.failure(workDataOf("Error" to error.localizedMessage))
|
||||
}
|
||||
|
|
|
@ -33,15 +33,18 @@ class LoadDeviceStatusWorker(
|
|||
val nsAndroidClient = nsClientV3Plugin.nsAndroidClient ?: return Result.failure(workDataOf("Error" to "AndroidClient is null"))
|
||||
|
||||
try {
|
||||
// Notify plugin we loaded al missed data
|
||||
nsClientV3Plugin.initialLoadFinished = true
|
||||
|
||||
val from = dateUtil.now() - T.mins(7).msecs()
|
||||
val deviceStatuses = nsAndroidClient.getDeviceStatusModifiedSince(from)
|
||||
aapsLogger.debug("DEVICESTATUSES: $deviceStatuses")
|
||||
if (deviceStatuses.isNotEmpty()) {
|
||||
rxBus.send(EventNSClientNewLog("RCV", "${deviceStatuses.size} DSs from ${dateUtil.dateAndTimeAndSecondsString(from)}"))
|
||||
rxBus.send(EventNSClientNewLog("◄ RCV", "${deviceStatuses.size} DSs from ${dateUtil.dateAndTimeAndSecondsString(from)}"))
|
||||
nsDeviceStatusHandler.handleNewData(deviceStatuses.toTypedArray())
|
||||
rxBus.send(EventNSClientNewLog("DONE DS", ""))
|
||||
rxBus.send(EventNSClientNewLog("● DONE PROCESSING DS", ""))
|
||||
} else {
|
||||
rxBus.send(EventNSClientNewLog("RCV END", "No DSs from ${dateUtil.dateAndTimeAndSecondsString(from)}"))
|
||||
rxBus.send(EventNSClientNewLog("◄ RCV DS END", "No data from ${dateUtil.dateAndTimeAndSecondsString(from)}"))
|
||||
}
|
||||
WorkManager.getInstance(context)
|
||||
.enqueueUniqueWork(
|
||||
|
@ -51,7 +54,7 @@ class LoadDeviceStatusWorker(
|
|||
)
|
||||
} catch (error: Exception) {
|
||||
aapsLogger.error("Error: ", error)
|
||||
rxBus.send(EventNSClientNewLog("ERROR", error.localizedMessage))
|
||||
rxBus.send(EventNSClientNewLog("◄ ERROR", error.localizedMessage))
|
||||
nsClientV3Plugin.lastOperationError = error.localizedMessage
|
||||
return Result.failure(workDataOf("Error" to error.localizedMessage))
|
||||
}
|
||||
|
|
|
@ -40,7 +40,7 @@ class LoadFoodsWorker(
|
|||
if (nsClientV3Plugin.lastLoadedSrvModified.collections.foods++ % 5 == 0L) {
|
||||
val foods: List<NSFood> = nsAndroidClient.getFoods(1000).values
|
||||
aapsLogger.debug(LTag.NSCLIENT, "FOODS: $foods")
|
||||
rxBus.send(EventNSClientNewLog("RCV", "${foods.size} FOODs"))
|
||||
rxBus.send(EventNSClientNewLog("◄ RCV", "${foods.size} FOODs"))
|
||||
// Schedule processing of fetched data
|
||||
WorkManager.getInstance(context)
|
||||
.beginUniqueWork(
|
||||
|
@ -53,7 +53,7 @@ class LoadFoodsWorker(
|
|||
.then(OneTimeWorkRequest.Builder(LoadProfileStoreWorker::class.java).build())
|
||||
.enqueue()
|
||||
} else {
|
||||
rxBus.send(EventNSClientNewLog("RCV", "FOOD skipped"))
|
||||
rxBus.send(EventNSClientNewLog("● RCV FOOD", "skipped"))
|
||||
WorkManager.getInstance(context)
|
||||
.enqueueUniqueWork(
|
||||
NSClientV3Plugin.JOB_NAME,
|
||||
|
@ -63,7 +63,7 @@ class LoadFoodsWorker(
|
|||
}
|
||||
} catch (error: Exception) {
|
||||
aapsLogger.error("Error: ", error)
|
||||
rxBus.send(EventNSClientNewLog("ERROR", error.localizedMessage))
|
||||
rxBus.send(EventNSClientNewLog("◄ ERROR", error.localizedMessage))
|
||||
nsClientV3Plugin.lastOperationError = error.localizedMessage
|
||||
return Result.failure(workDataOf("Error" to error.localizedMessage))
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ class LoadLastModificationWorker(
|
|||
aapsLogger.debug(LTag.NSCLIENT, "LAST MODIFIED: ${nsClientV3Plugin.newestDataOnServer}")
|
||||
} catch (error: Exception) {
|
||||
aapsLogger.error(LTag.NSCLIENT, "Error: ", error)
|
||||
rxBus.send(EventNSClientNewLog("ERROR", error.localizedMessage))
|
||||
rxBus.send(EventNSClientNewLog("◄ ERROR", error.localizedMessage))
|
||||
nsClientV3Plugin.lastOperationError = error.localizedMessage
|
||||
return Result.failure(workDataOf("Error" to error.localizedMessage))
|
||||
}
|
||||
|
|
|
@ -57,7 +57,7 @@ class LoadProfileStoreWorker(
|
|||
{ nsClientV3Plugin.lastLoadedSrvModified.collections.profile = dateUtil.now() }
|
||||
nsClientV3Plugin.storeLastLoadedSrvModified()
|
||||
aapsLogger.debug(LTag.NSCLIENT, "PROFILE: $profile")
|
||||
rxBus.send(EventNSClientNewLog("RCV", "1 PROFILE from ${dateUtil.dateAndTimeAndSecondsString(lastLoaded)}"))
|
||||
rxBus.send(EventNSClientNewLog("◄ RCV", "1 PROFILE from ${dateUtil.dateAndTimeAndSecondsString(lastLoaded)}"))
|
||||
WorkManager.getInstance(context)
|
||||
.beginUniqueWork(
|
||||
NSClientV3Plugin.JOB_NAME,
|
||||
|
@ -68,7 +68,7 @@ class LoadProfileStoreWorker(
|
|||
).then(OneTimeWorkRequest.Builder(LoadDeviceStatusWorker::class.java).build())
|
||||
.enqueue()
|
||||
} else {
|
||||
rxBus.send(EventNSClientNewLog("RCV END", "No new PROFILE from ${dateUtil.dateAndTimeAndSecondsString(lastLoaded)}"))
|
||||
rxBus.send(EventNSClientNewLog("◄ RCV PROFILE END", "No new data from ${dateUtil.dateAndTimeAndSecondsString(lastLoaded)}"))
|
||||
WorkManager.getInstance(context)
|
||||
.enqueueUniqueWork(
|
||||
NSClientV3Plugin.JOB_NAME,
|
||||
|
@ -77,7 +77,7 @@ class LoadProfileStoreWorker(
|
|||
)
|
||||
}
|
||||
} else {
|
||||
rxBus.send(EventNSClientNewLog("RCV END", "No PROFILE from ${dateUtil.dateAndTimeAndSecondsString(lastLoaded)}"))
|
||||
rxBus.send(EventNSClientNewLog("◄ RCV PROFILE END", "No data from ${dateUtil.dateAndTimeAndSecondsString(lastLoaded)}"))
|
||||
WorkManager.getInstance(context)
|
||||
.enqueueUniqueWork(
|
||||
NSClientV3Plugin.JOB_NAME,
|
||||
|
@ -87,7 +87,7 @@ class LoadProfileStoreWorker(
|
|||
}
|
||||
} catch (error: Exception) {
|
||||
aapsLogger.error("Error: ", error)
|
||||
rxBus.send(EventNSClientNewLog("ERROR", error.localizedMessage))
|
||||
rxBus.send(EventNSClientNewLog("◄ ERROR", error.localizedMessage))
|
||||
return Result.failure(workDataOf("Error" to error.localizedMessage))
|
||||
}
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ class LoadStatusWorker(
|
|||
aapsLogger.debug(LTag.NSCLIENT, "STATUS: $status")
|
||||
} catch (error: Exception) {
|
||||
aapsLogger.error("Error: ", error)
|
||||
rxBus.send(EventNSClientNewLog("ERROR", error.localizedMessage))
|
||||
rxBus.send(EventNSClientNewLog("◄ ERROR", error.localizedMessage))
|
||||
nsClientV3Plugin.lastOperationError = error.localizedMessage
|
||||
return Result.failure(workDataOf("Error" to error.localizedMessage))
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import info.nightscout.core.utils.worker.LoggingWorker
|
|||
import info.nightscout.core.utils.worker.then
|
||||
import info.nightscout.interfaces.nsclient.StoreDataForDb
|
||||
import info.nightscout.interfaces.sync.NsClient
|
||||
import info.nightscout.plugins.sync.nsShared.StoreDataForDbImpl
|
||||
import info.nightscout.plugins.sync.nsclientV3.NSClientV3Plugin
|
||||
import info.nightscout.rx.bus.RxBus
|
||||
import info.nightscout.rx.events.EventNSClientNewLog
|
||||
|
@ -57,14 +58,14 @@ class LoadTreatmentsWorker(
|
|||
aapsLogger.debug(LTag.NSCLIENT, "TREATMENTS: $treatments")
|
||||
if (treatments.isNotEmpty()) {
|
||||
val action = if (isFirstLoad) "RCV-FIRST" else "RCV"
|
||||
rxBus.send(EventNSClientNewLog(action, "${treatments.size} TRs from ${dateUtil.dateAndTimeAndSecondsString(lastLoaded)}"))
|
||||
rxBus.send(EventNSClientNewLog("◄ $action", "${treatments.size} TRs from ${dateUtil.dateAndTimeAndSecondsString(lastLoaded)}"))
|
||||
// Schedule processing of fetched data and continue of loading
|
||||
WorkManager.getInstance(context)
|
||||
.beginUniqueWork(
|
||||
NSClientV3Plugin.JOB_NAME,
|
||||
ExistingWorkPolicy.APPEND_OR_REPLACE,
|
||||
OneTimeWorkRequest.Builder(ProcessTreatmentsWorker::class.java)
|
||||
.setInputData(dataWorkerStorage.storeInputData(response))
|
||||
.setInputData(dataWorkerStorage.storeInputData(response.values))
|
||||
.build()
|
||||
)
|
||||
// response 304 == Not modified (happens when date > srvModified => bad time on phone or server during upload
|
||||
|
@ -77,14 +78,15 @@ class LoadTreatmentsWorker(
|
|||
nsClientV3Plugin.lastLoadedSrvModified.collections.treatments = lastLoaded
|
||||
nsClientV3Plugin.storeLastLoadedSrvModified()
|
||||
}
|
||||
rxBus.send(EventNSClientNewLog("RCV END", "No TRs from ${dateUtil.dateAndTimeAndSecondsString(lastLoaded)}"))
|
||||
storeDataForDb.storeTreatmentsToDb()
|
||||
rxBus.send(EventNSClientNewLog("◄ RCV TR END", "No data from ${dateUtil.dateAndTimeAndSecondsString(lastLoaded)}"))
|
||||
WorkManager.getInstance(context)
|
||||
.enqueueUniqueWork(
|
||||
.beginUniqueWork(
|
||||
NSClientV3Plugin.JOB_NAME,
|
||||
ExistingWorkPolicy.APPEND_OR_REPLACE,
|
||||
OneTimeWorkRequest.Builder(LoadFoodsWorker::class.java).build()
|
||||
OneTimeWorkRequest.Builder(StoreDataForDbImpl.StoreTreatmentsWorker::class.java).build()
|
||||
)
|
||||
.then(OneTimeWorkRequest.Builder(LoadFoodsWorker::class.java).build())
|
||||
.enqueue()
|
||||
}
|
||||
} else {
|
||||
// End first load
|
||||
|
@ -92,18 +94,19 @@ class LoadTreatmentsWorker(
|
|||
nsClientV3Plugin.lastLoadedSrvModified.collections.treatments = lastLoaded
|
||||
nsClientV3Plugin.storeLastLoadedSrvModified()
|
||||
}
|
||||
rxBus.send(EventNSClientNewLog("RCV END", "No new TRs from ${dateUtil.dateAndTimeAndSecondsString(lastLoaded)}"))
|
||||
storeDataForDb.storeTreatmentsToDb()
|
||||
rxBus.send(EventNSClientNewLog("◄ RCV TR END", "No new data from ${dateUtil.dateAndTimeAndSecondsString(lastLoaded)}"))
|
||||
WorkManager.getInstance(context)
|
||||
.enqueueUniqueWork(
|
||||
.beginUniqueWork(
|
||||
NSClientV3Plugin.JOB_NAME,
|
||||
ExistingWorkPolicy.APPEND_OR_REPLACE,
|
||||
OneTimeWorkRequest.Builder(LoadFoodsWorker::class.java).build()
|
||||
OneTimeWorkRequest.Builder(StoreDataForDbImpl.StoreTreatmentsWorker::class.java).build()
|
||||
)
|
||||
.then(OneTimeWorkRequest.Builder(LoadFoodsWorker::class.java).build())
|
||||
.enqueue()
|
||||
}
|
||||
} catch (error: Exception) {
|
||||
aapsLogger.error("Error: ", error)
|
||||
rxBus.send(EventNSClientNewLog("ERROR", error.localizedMessage))
|
||||
rxBus.send(EventNSClientNewLog("◄ ERROR", error.localizedMessage))
|
||||
nsClientV3Plugin.lastOperationError = error.localizedMessage
|
||||
return Result.failure(workDataOf("Error" to error.localizedMessage))
|
||||
}
|
||||
|
|
|
@ -72,7 +72,7 @@ class ProcessFoodWorker(
|
|||
storeDataForDb.foods.addAll(foods)
|
||||
} catch (error: Exception) {
|
||||
aapsLogger.error("Error: ", error)
|
||||
rxBus.send(EventNSClientNewLog("ERROR", error.localizedMessage))
|
||||
rxBus.send(EventNSClientNewLog("◄ ERROR", error.localizedMessage))
|
||||
return Result.failure(workDataOf("Error" to error.localizedMessage))
|
||||
}
|
||||
|
||||
|
|
|
@ -24,7 +24,6 @@ import info.nightscout.plugins.sync.nsclientV3.extensions.toTherapyEvent
|
|||
import info.nightscout.rx.bus.RxBus
|
||||
import info.nightscout.rx.events.EventNSClientNewLog
|
||||
import info.nightscout.rx.logging.LTag
|
||||
import info.nightscout.sdk.interfaces.NSAndroidClient
|
||||
import info.nightscout.sdk.localmodel.treatment.NSBolus
|
||||
import info.nightscout.sdk.localmodel.treatment.NSBolusWizard
|
||||
import info.nightscout.sdk.localmodel.treatment.NSCarbs
|
||||
|
@ -57,12 +56,12 @@ class ProcessTreatmentsWorker(
|
|||
|
||||
override suspend fun doWorkAndLog(): Result {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val treatments = dataWorkerStorage.pickupObject(inputData.getLong(DataWorkerStorage.STORE_KEY, -1)) as NSAndroidClient.ReadResponse<List<NSTreatment>>?
|
||||
val treatments = dataWorkerStorage.pickupObject(inputData.getLong(DataWorkerStorage.STORE_KEY, -1)) as List<NSTreatment>?
|
||||
?: return Result.failure(workDataOf("Error" to "missing input data"))
|
||||
|
||||
try {
|
||||
var latestDateInReceivedData: Long = 0
|
||||
for (treatment in treatments.values) {
|
||||
for (treatment in treatments) {
|
||||
aapsLogger.debug(LTag.DATABASE, "Received NS treatment: $treatment")
|
||||
val date = treatment.date ?: continue
|
||||
if (date > latestDateInReceivedData) latestDateInReceivedData = date
|
||||
|
@ -139,7 +138,7 @@ class ProcessTreatmentsWorker(
|
|||
// xDripBroadcast.sendTreatments(treatments)
|
||||
} catch (error: Exception) {
|
||||
aapsLogger.error("Error: ", error)
|
||||
rxBus.send(EventNSClientNewLog("ERROR", error.localizedMessage))
|
||||
rxBus.send(EventNSClientNewLog("◄ ERROR", error.localizedMessage))
|
||||
return Result.failure(workDataOf("Error" to error.localizedMessage))
|
||||
}
|
||||
return Result.success()
|
||||
|
|
|
@ -48,7 +48,7 @@
|
|||
|
||||
<!-- NSClient -->
|
||||
<string name="key_ns_client_autoscroll" translatable="false">ns_client_autoscroll</string>
|
||||
<string name="key_ns_client_paused" translatable="false">ns_client_paused</string>
|
||||
<string name="key_ns_paused" translatable="false">ns_client_paused</string>
|
||||
<string name="key_ns_log_app_started_event" translatable="false">ns_log_app_started_event</string>
|
||||
|
||||
<string name="no_write_permission">NSCLIENT has no write permission. Wrong API secret?</string>
|
||||
|
@ -171,5 +171,7 @@
|
|||
|
||||
<!-- DataBroadcast-->
|
||||
<string name="data_broadcaster" translatable="false">Data Broadcaster</string>
|
||||
<string name="ns_use_ws_title">Connect to websockets</string>
|
||||
<string name="ns_use_ws_summary">Enabling means: faster updates, receiving alarms and announcements and higher battery consumption similar to v1. All other uploaderds to NS must use v3 protocol.</string>
|
||||
|
||||
</resources>
|
|
@ -9,7 +9,7 @@
|
|||
app:initialExpandedChildrenCount="0">
|
||||
|
||||
<info.nightscout.core.validators.ValidatingEditTextPreference
|
||||
android:defaultValue="https://{YOUR-SITE}.azurewebsites.net/"
|
||||
android:defaultValue="https://{YOUR-SITE}.azurewebsites.net"
|
||||
android:dialogMessage="@string/ns_client_url_dialog_message"
|
||||
android:inputType="textUri"
|
||||
android:key="@string/key_nsclientinternal_url"
|
||||
|
@ -24,7 +24,13 @@
|
|||
android:key="@string/key_ns_client_token"
|
||||
android:title="@string/nsclient_token_title"
|
||||
validate:minLength="17"
|
||||
validate:testType="minLength"/>
|
||||
validate:testType="minLength" />
|
||||
|
||||
<SwitchPreference
|
||||
android:defaultValue="true"
|
||||
android:key="@string/key_ns_use_ws"
|
||||
android:summary="@string/ns_use_ws_summary"
|
||||
android:title="@string/ns_use_ws_title" />
|
||||
|
||||
<androidx.preference.PreferenceScreen
|
||||
android:key="@string/ns_sync_options"
|
||||
|
|
Loading…
Reference in a new issue