NSCv3: process ProfileStore

This commit is contained in:
Milos Kozak 2023-01-08 10:52:28 +01:00
parent 9412f88672
commit 3bc62e9a01
16 changed files with 344 additions and 51 deletions

View file

@ -180,7 +180,7 @@ class MainApp : DaggerApplication() {
Thread.currentThread().uncaughtExceptionHandler?.uncaughtException(Thread.currentThread(), e)
return@setErrorHandler
}
aapsLogger.warn(LTag.CORE, "Undeliverable exception received, not sure what to do", e.toString())
aapsLogger.warn(LTag.CORE, "Undeliverable exception received, not sure what to do", e.localizedMessage)
}
}

View file

@ -25,9 +25,9 @@ import info.nightscout.interfaces.iob.IobTotal
interface OverviewData {
var rangeToDisplay: Int // for graph
var toTime: Long
var fromTime: Long
var endTime: Long
var toTime: Long // current time rounded up to 1 hour
var fromTime: Long // toTime - range
var endTime: Long // toTime + predictions
fun reset()
fun initRange()

View file

@ -1,6 +1,7 @@
package info.nightscout.sdk
import android.content.Context
import com.google.gson.JsonParser
import info.nightscout.sdk.exceptions.DateHeaderOutOfToleranceException
import info.nightscout.sdk.exceptions.InvalidAccessTokenException
import info.nightscout.sdk.exceptions.InvalidFormatNightscoutException
@ -30,6 +31,7 @@ import info.nightscout.sdk.utils.toNotNull
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.json.JSONObject
/**
*
@ -190,6 +192,14 @@ class NSAndroidClientImpl(
deduplicatedIdentifier = null,
lastModified = null
)
} else if (response.code() == 404) { // not found
return@callWrapper CreateUpdateResponse(
response = response.code(),
identifier = null,
isDeduplication = false,
deduplicatedIdentifier = null,
lastModified = null
)
} else {
throw UnsuccessfullNightscoutException()
}
@ -307,6 +317,14 @@ class NSAndroidClientImpl(
deduplicatedIdentifier = null,
lastModified = null
)
} else if (response.code() == 404) { // not found
return@callWrapper CreateUpdateResponse(
response = response.code(),
identifier = null,
isDeduplication = false,
deduplicatedIdentifier = null,
lastModified = null
)
} else {
throw UnsuccessfullNightscoutException()
}
@ -375,11 +393,58 @@ class NSAndroidClientImpl(
deduplicatedIdentifier = null,
lastModified = null
)
} else if (response.code() == 404) { // not found
return@callWrapper CreateUpdateResponse(
response = response.code(),
identifier = null,
isDeduplication = false,
deduplicatedIdentifier = null,
lastModified = null
)
} else {
throw UnsuccessfullNightscoutException()
}
}
override suspend fun createProfileStore(remoteProfileStore: JSONObject): CreateUpdateResponse = callWrapper(dispatcher) {
remoteProfileStore.put("app", "AAPS")
val response = api.createProfile(JsonParser.parseString(remoteProfileStore.toString()).asJsonObject)
if (response.isSuccessful) {
if (response.code() == 200) {
return@callWrapper CreateUpdateResponse(
response = response.code(),
identifier = null,
isDeduplication = true,
deduplicatedIdentifier = null,
lastModified = null
)
} else if (response.code() == 201) {
return@callWrapper CreateUpdateResponse(
response = response.code(),
identifier = response.body()?.result?.identifier,
isDeduplication = response.body()?.result?.isDeduplication ?: false,
deduplicatedIdentifier = response.body()?.result?.deduplicatedIdentifier,
lastModified = response.body()?.result?.lastModified
)
} else throw UnsuccessfullNightscoutException()
} else {
throw UnsuccessfullNightscoutException()
}
}
override suspend fun getLastProfileStore(): NSAndroidClient.ReadResponse<List<JSONObject>> = callWrapper(dispatcher) {
val response = api.getLastProfile()
if (response.isSuccessful) {
val eTagString = response.headers()["ETag"]
val eTag = eTagString?.substring(3, eTagString.length - 1)?.toLong()
return@callWrapper NSAndroidClient.ReadResponse(eTag, response.body()?.result.toNotNull())
} else {
throw UnsuccessfullNightscoutException()
}
}
private suspend fun <T> callWrapper(dispatcher: CoroutineDispatcher, block: suspend () -> T): T =
withContext(dispatcher) {
retry(

View file

@ -7,6 +7,7 @@ import info.nightscout.sdk.localmodel.treatment.CreateUpdateResponse
import info.nightscout.sdk.localmodel.treatment.NSTreatment
import info.nightscout.sdk.remotemodel.LastModified
import info.nightscout.sdk.remotemodel.RemoteDeviceStatus
import org.json.JSONObject
interface NSAndroidClient {
@ -32,6 +33,9 @@ interface NSAndroidClient {
suspend fun createDeviceStatus(remoteDeviceStatus: RemoteDeviceStatus): CreateUpdateResponse
suspend fun getDeviceStatusModifiedSince(from: Long): List<RemoteDeviceStatus>
suspend fun createProfileStore(remoteProfileStore: JSONObject): CreateUpdateResponse
suspend fun getLastProfileStore(): ReadResponse<List<JSONObject>>
suspend fun createTreatment(nsTreatment: NSTreatment): CreateUpdateResponse
suspend fun updateTreatment(nsTreatment: NSTreatment): CreateUpdateResponse
suspend fun getFoods(limit: Long): List<NSFood>

View file

@ -3,9 +3,11 @@ package info.nightscout.sdk.networking
import android.content.Context
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.JsonDeserializer
import okhttp3.Cache
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import org.json.JSONObject
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import java.util.concurrent.TimeUnit
@ -85,7 +87,13 @@ internal object NetworkStackBuilder {
return build()
}
private fun provideGson(): Gson = GsonBuilder().create()
private val deserializer: JsonDeserializer<JSONObject?> =
JsonDeserializer<JSONObject?> { json, _, _ ->
JSONObject(json.asJsonObject.toString())
}
private fun provideGson(): Gson = GsonBuilder().also {
it.registerTypeAdapter(JSONObject::class.java, deserializer)
}.create()
private const val OK_HTTP_CACHE_SIZE = 10L * 1024 * 1024
private const val OK_HTTP_READ_TIMEOUT = 60L * 1000

View file

@ -1,5 +1,6 @@
package info.nightscout.sdk.networking
import com.google.gson.JsonObject
import info.nightscout.sdk.remotemodel.LastModified
import info.nightscout.sdk.remotemodel.NSResponse
import info.nightscout.sdk.remotemodel.RemoteCreateUpdateResponse
@ -8,6 +9,7 @@ import info.nightscout.sdk.remotemodel.RemoteEntry
import info.nightscout.sdk.remotemodel.RemoteFood
import info.nightscout.sdk.remotemodel.RemoteStatusResponse
import info.nightscout.sdk.remotemodel.RemoteTreatment
import org.json.JSONObject
import retrofit2.Response
import retrofit2.http.Body
import retrofit2.http.GET
@ -70,14 +72,21 @@ internal interface NightscoutRemoteService {
@GET("v3/food")
suspend fun getFoods(@Query("limit") limit: Long): Response<NSResponse<List<RemoteFood>>>
/*
/*
@GET("v3/food/history/{from}")
suspend fun getFoodsModifiedSince(@Path("from") from: Long, @Query("limit") limit: Long): Response<NSResponse<List<RemoteFood>>>
*/
*/
@POST("v3/food")
suspend fun createFood(@Body remoteFood: RemoteFood): Response<NSResponse<RemoteCreateUpdateResponse>>
@PATCH("v3/food")
suspend fun updateFood(@Body remoteFood: RemoteFood): Response<NSResponse<RemoteCreateUpdateResponse>>
@GET("v3/profile?sort\$desc=date&limit=1")
suspend fun getLastProfile(): Response<NSResponse<List<JSONObject>>>
@POST("v3/profile")
suspend fun createProfile(@Body profile: JsonObject): Response<NSResponse<RemoteCreateUpdateResponse>>
}

View file

@ -19,6 +19,7 @@ data class LastModified(
@SerializedName("entries") var entries: Long = 0, // entries collection
@SerializedName("profile") var profile: Long = 0, // profile collection
@SerializedName("treatments") var treatments: Long = 0, // treatments collection
@SerializedName("foods") var foods: Long = 0 // foods collection
@SerializedName("foods") var foods: Long = 0, // foods collection
@SerializedName("settings") var settings: Long = 0 // settings collection
)
}

View file

@ -0,0 +1,49 @@
package info.nightscout.sdk.remotemodel
import com.google.gson.annotations.SerializedName
import kotlinx.serialization.Contextual
import kotlinx.serialization.Serializable
import org.json.JSONObject
/**
* DeviceStatus coming from uploader or AAPS
*
**/
@Serializable
data class RemoteProfileStore(
@SerializedName("app") var app: String? = null,
@SerializedName("identifier") val identifier: String? = null, // string Main addressing, required field that identifies document in the collection. The client should not create the identifier, the server automatically assigns it when the document is inserted.
@SerializedName("srvCreated") val srvCreated: Long? = null, // integer($int64) example: 1525383610088 The server's timestamp of document insertion into the database (Unix epoch in ms). This field appears only for documents which were inserted by API v3.
@SerializedName("srvModified") val srvModified: Long? = null, // integer($int64) example: 1525383610088 The server's timestamp of the last document modification in the database (Unix epoch in ms). This field appears only for documents which were somehow modified by API v3 (inserted, updated or deleted).
@SerializedName("created_at") val createdAt: String? = null, // string or string timestamp on previous version of api, in my examples, a lot of treatments don't have date, only created_at, some of them with string others with long...
@SerializedName("date") val date: Long?, // date as milliseconds
@SerializedName("startDate") val startDate: Long?, // record valid from
@SerializedName("defaultProfile") val defaultProfile: String,// default profile in store
//@Serializable(with = JSONSerializer::class)
@Contextual @SerializedName("store") val store: JSONObject
) {
/*
@Serializable data class Store(
val names: ArrayList<String>,
val profiles: ArrayList<SimpleProfile>
)
@Serializable data class SimpleProfile(
@SerializedName("dia") val dia: Double,
@SerializedName("carbratio") val carbratio: ArrayList<ProfileEntry>,
@SerializedName("sens") val sens: ArrayList<ProfileEntry>,
@SerializedName("basal") val basal: ArrayList<ProfileEntry>,
@SerializedName("target_low") val target_low: ArrayList<ProfileEntry>,
@SerializedName("target_high") val target_high: ArrayList<ProfileEntry>,
@SerializedName("units") val units: String, // string The units for the glucose value, mg/dl or mmoll
@SerializedName("timezone") val timezone: String
)
@Serializable data class ProfileEntry(
@SerializedName("time") val time: String,
@SerializedName("timeAsSeconds") val timeAsSeconds: Long?,
@SerializedName("value") val value: Double
)
*/
}

View file

@ -22,9 +22,9 @@ import info.nightscout.interfaces.plugin.ActivePlugin
import info.nightscout.interfaces.plugin.PluginBase
import info.nightscout.interfaces.plugin.PluginDescription
import info.nightscout.interfaces.plugin.PluginType
import info.nightscout.interfaces.profile.Instantiator
import info.nightscout.interfaces.profile.Profile
import info.nightscout.interfaces.profile.ProfileFunction
import info.nightscout.interfaces.profile.Instantiator
import info.nightscout.interfaces.profile.ProfileSource
import info.nightscout.interfaces.profile.ProfileStore
import info.nightscout.interfaces.profile.PureProfile
@ -413,6 +413,7 @@ class ProfilePlugin @Inject constructor(
}
if (numOfProfiles > 0) json.put("defaultProfile", currentProfile()?.name)
val startDate = sp.getLong(info.nightscout.core.utils.R.string.key_local_profile_last_change, dateUtil.now())
json.put("date", startDate)
json.put("startDate", dateUtil.toISOAsUTC(startDate))
json.put("store", store)
} catch (e: JSONException) {

View file

@ -22,6 +22,7 @@ import info.nightscout.plugins.sync.nsclientV3.workers.LoadBgWorker
import info.nightscout.plugins.sync.nsclientV3.workers.LoadDeviceStatusWorker
import info.nightscout.plugins.sync.nsclientV3.workers.LoadFoodsWorker
import info.nightscout.plugins.sync.nsclientV3.workers.LoadLastModificationWorker
import info.nightscout.plugins.sync.nsclientV3.workers.LoadProfileStoreWorker
import info.nightscout.plugins.sync.nsclientV3.workers.LoadStatusWorker
import info.nightscout.plugins.sync.nsclientV3.workers.LoadTreatmentsWorker
import info.nightscout.plugins.sync.nsclientV3.workers.ProcessFoodWorker
@ -49,6 +50,7 @@ abstract class SyncModule {
@ContributesAndroidInjector abstract fun contributesLoadLastModificationWorker(): LoadLastModificationWorker
@ContributesAndroidInjector abstract fun contributesLoadBgWorker(): LoadBgWorker
@ContributesAndroidInjector abstract fun contributesLoadFoodsWorker(): LoadFoodsWorker
@ContributesAndroidInjector abstract fun contributesLoadProfileStoreWorker(): LoadProfileStoreWorker
@ContributesAndroidInjector abstract fun contributesStoreBgWorker(): StoreDataForDbImpl.StoreBgWorker
@ContributesAndroidInjector abstract fun contributesStoreFoodWorker(): StoreDataForDbImpl.StoreFoodWorker
@ContributesAndroidInjector abstract fun contributesTreatmentWorker(): LoadTreatmentsWorker

View file

@ -5,6 +5,7 @@ import info.nightscout.database.impl.AppRepository
import info.nightscout.interfaces.plugin.ActivePlugin
import info.nightscout.interfaces.profile.ProfileFunction
import info.nightscout.interfaces.sync.DataSyncSelector
import info.nightscout.interfaces.utils.JsonHelper
import info.nightscout.plugins.sync.R
import info.nightscout.rx.logging.AAPSLogger
import info.nightscout.rx.logging.LTag
@ -764,7 +765,11 @@ class DataSyncSelectorImplementation @Inject constructor(
if (lastChange == 0L) return
if (lastChange > lastSync) {
if (activePlugin.activeProfileSource.profile?.allProfilesValid != true) return
val profileJson = activePlugin.activeProfileSource.profile?.data ?: return
val profileStore = activePlugin.activeProfileSource.profile
val profileJson = profileStore?.data ?: return
// add for v3
if (JsonHelper.safeGetLongAllowNull(profileJson, "date") == null)
profileJson.put("date", profileStore.getStartDate())
activePlugin.activeNsClient?.nsAdd("profile", DataSyncSelector.PairProfileStore(profileJson, dateUtil.now()), "")
}
}

View file

@ -16,11 +16,12 @@ import com.google.gson.GsonBuilder
import dagger.android.HasAndroidInjector
import info.nightscout.core.utils.fabric.FabricPrivacy
import info.nightscout.core.validators.ValidatingEditTextPreference
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.nsclient.NSAlarm
import info.nightscout.interfaces.nsclient.StoreDataForDb
import info.nightscout.interfaces.plugin.PluginBase
import info.nightscout.interfaces.plugin.PluginDescription
import info.nightscout.interfaces.plugin.PluginType
@ -98,9 +99,9 @@ class NSClientV3Plugin @Inject constructor(
private val config: Config,
private val dateUtil: DateUtil,
private val uiInteraction: UiInteraction,
private val storeDataForDb: StoreDataForDb,
private val dataSyncSelector: DataSyncSelector,
private val profileFunction: ProfileFunction
private val profileFunction: ProfileFunction,
private val repository: AppRepository
) : NsClient, Sync, PluginBase(
PluginDescription()
.mainType(PluginType.SYNC)
@ -116,7 +117,7 @@ class NSClientV3Plugin @Inject constructor(
companion object {
val JOB_NAME: String = this::class.java.simpleName
val REFRESH_INTERVAL = T.mins(5).msecs()
val REFRESH_INTERVAL = T.secs(30).msecs()
}
private val disposable = CompositeDisposable()
@ -196,18 +197,26 @@ class NSClientV3Plugin @Inject constructor(
disposable += rxBus
.toObservable(EventNewBG::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({ scheduleExecution("NEW_BG") }, fabricPrivacy::logException)
.subscribe({ delayAndScheduleExecution("NEW_BG") }, fabricPrivacy::logException)
disposable += rxBus
.toObservable(EventNewHistoryData::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({ scheduleExecution("NEW_DATA") }, fabricPrivacy::logException)
.subscribe({ delayAndScheduleExecution("NEW_DATA") }, fabricPrivacy::logException)
runLoop = Runnable {
handler.postDelayed(runLoop, REFRESH_INTERVAL)
executeLoop("MAIN_LOOP")
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())
executeLoop("MAIN_LOOP", forceNew = false)
else
rxBus.send(EventNSClientNewLog("RECENT", "No need to load"))
} else executeLoop("MAIN_LOOP", forceNew = false)
}
}
handler.postDelayed(runLoop, REFRESH_INTERVAL)
executeLoop("START")
executeLoop("START", forceNew = false)
}
override fun onStop() {
@ -273,7 +282,7 @@ class NSClientV3Plugin @Inject constructor(
}
override fun resend(reason: String) {
executeLoop("RESEND")
executeLoop("RESEND", forceNew = false)
}
override fun pause(newState: Boolean) {
@ -331,6 +340,36 @@ class NSClientV3Plugin @Inject constructor(
enum class Operation { CREATE, UPDATE }
private val gson: Gson = GsonBuilder().create()
private fun dbOperationProfileStore(collection: String = "profile", dataPair: DataSyncSelector.DataPair, progress: String) {
val data = (dataPair as DataSyncSelector.PairProfileStore).value
scope.launch {
try {
rxBus.send(EventNSClientNewLog("ADD $collection", "Sent ${dataPair.javaClass.simpleName} $data $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}"))
else -> {
rxBus.send(EventNSClientNewLog("ERROR", "ProfileStore"))
return@launch
}
}
// if (result.response == 201) { // created
// dataPair.value.interfaceIDs.nightscoutId = result.identifier
// storeDataForDb.nsIdDeviceStatuses.add(dataPair.value)
// storeDataForDb.scheduleNsIdUpdate()
// }
dataSyncSelector.confirmLastProfileStore(dataPair.id)
dataSyncSelector.processChangedProfileStore()
}
} catch (e: Exception) {
aapsLogger.error(LTag.NSCLIENT, "Upload exception", e)
}
}
}
private fun dbOperationDeviceStatus(collection: String = "devicestatus", dataPair: DataSyncSelector.DataPair, progress: String) {
val data = (dataPair as DataSyncSelector.PairDeviceStatus).value.toRemoteDeviceStatus()
scope.launch {
@ -340,6 +379,7 @@ class NSClientV3Plugin @Inject constructor(
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}"))
else -> {
rxBus.send(EventNSClientNewLog("ERROR", "${dataPair.value.javaClass.simpleName} "))
@ -389,6 +429,7 @@ class NSClientV3Plugin @Inject constructor(
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} "))
@ -443,6 +484,7 @@ class NSClientV3Plugin @Inject constructor(
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} "))
@ -525,6 +567,7 @@ class NSClientV3Plugin @Inject constructor(
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} "))
@ -642,6 +685,7 @@ class NSClientV3Plugin @Inject constructor(
private fun dbOperation(collection: String, dataPair: DataSyncSelector.DataPair, progress: String, operation: Operation) {
when (collection) {
"profile" -> dbOperationProfileStore(dataPair = dataPair, progress = progress)
"devicestatus" -> dbOperationDeviceStatus(dataPair = dataPair, progress = progress)
"entries" -> dbOperationEntries(dataPair = dataPair, progress = progress, operation = operation)
"food" -> dbOperationFood(dataPair = dataPair, progress = progress, operation = operation)
@ -653,18 +697,20 @@ class NSClientV3Plugin @Inject constructor(
sp.putString(R.string.key_ns_client_v3_last_modified, Json.encodeToString(LastModified.serializer(), lastLoadedSrvModified))
}
fun scheduleNewExecution() {
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) }, toTime - dateUtil.now())
handler.postDelayed({ executeLoop(origin, forceNew = forceNew) }, toTime - dateUtil.now())
rxBus.send(EventNSClientNewLog("NEXT", dateUtil.dateAndTimeAndSecondsString(toTime)))
}
private fun executeLoop(origin: String) {
private fun executeLoop(origin: String, forceNew: Boolean) {
if (sp.getBoolean(R.string.key_ns_client_paused, false)) {
rxBus.send(EventNSClientNewLog("RUN", "paused"))
return
@ -673,13 +719,16 @@ class NSClientV3Plugin @Inject constructor(
rxBus.send(EventNSClientNewLog("RUN", blockingReason))
return
}
if (workIsRunning(arrayOf(JOB_NAME)))
if (workIsRunning(arrayOf(JOB_NAME))) {
rxBus.send(EventNSClientNewLog("RUN", "Already running $origin"))
else {
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"))
WorkManager.getInstance(context)
.beginUniqueWork(
"NSCv3Load",
JOB_NAME,
ExistingWorkPolicy.REPLACE,
OneTimeWorkRequest.Builder(LoadStatusWorker::class.java).build()
)
@ -688,11 +737,11 @@ class NSClientV3Plugin @Inject constructor(
// Other Workers are enqueued after BG finish
// LoadTreatmentsWorker
// LoadFoodsWorker
// LoadProfileStoreWorker
// LoadDeviceStatusWorker
// DataSyncWorker
.enqueue()
}
}
private fun workIsRunning(workNames: Array<String>): Boolean {
for (workName in workNames)
@ -704,12 +753,12 @@ class NSClientV3Plugin @Inject constructor(
private val eventWorker = Executors.newSingleThreadScheduledExecutor()
private var scheduledEventPost: ScheduledFuture<*>? = null
private fun scheduleExecution(origin: String) {
private fun delayAndScheduleExecution(origin: String) {
class PostRunnable : Runnable {
override fun run() {
scheduledEventPost = null
executeLoop(origin)
executeLoop(origin, forceNew = true)
}
}
// cancel waiting task to prevent sending multiple posts

View file

@ -57,7 +57,7 @@ class LoadBgWorker(
sgvs = response.values
response.lastServerModified?.let { nsClientV3Plugin.lastLoadedSrvModified.collections.entries = it }
nsClientV3Plugin.storeLastLoadedSrvModified()
nsClientV3Plugin.scheduleNewExecution() // Idea is to run after 5 min after last BG
nsClientV3Plugin.scheduleIrregularExecution() // Idea is to run after 5 min after last BG
}
aapsLogger.debug("SGVS: $sgvs")
if (sgvs.isNotEmpty()) {
@ -77,8 +77,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 END", "No SGVs from ${dateUtil.dateAndTimeAndSecondsString(lastLoaded)}"))
WorkManager.getInstance(context)
.beginUniqueWork(
NSClientV3Plugin.JOB_NAME,

View file

@ -9,7 +9,6 @@ import androidx.work.workDataOf
import info.nightscout.core.utils.receivers.DataWorkerStorage
import info.nightscout.core.utils.worker.LoggingWorker
import info.nightscout.interfaces.nsclient.StoreDataForDb
import info.nightscout.interfaces.workflow.WorkerClasses
import info.nightscout.plugins.sync.nsShared.StoreDataForDbImpl
import info.nightscout.plugins.sync.nsclientV3.NSClientV3Plugin
import info.nightscout.rx.bus.RxBus
@ -30,7 +29,6 @@ class LoadFoodsWorker(
@Inject lateinit var nsClientV3Plugin: NSClientV3Plugin
@Inject lateinit var dateUtil: DateUtil
@Inject lateinit var storeDataForDb: StoreDataForDb
@Inject lateinit var workerClasses: WorkerClasses
override fun doWorkAndLog(): Result {
val nsAndroidClient = nsClientV3Plugin.nsAndroidClient ?: return Result.failure(workDataOf("Error" to "AndroidClient is null"))
@ -51,7 +49,7 @@ class LoadFoodsWorker(
.setInputData(dataWorkerStorage.storeInputData(foods))
.build()
).then(OneTimeWorkRequest.Builder(StoreDataForDbImpl.StoreFoodWorker::class.java).build())
.then(OneTimeWorkRequest.Builder(LoadDeviceStatusWorker::class.java).build())
.then(OneTimeWorkRequest.Builder(LoadProfileStoreWorker::class.java).build())
.enqueue()
} else {
rxBus.send(EventNSClientNewLog("RCV", "FOOD skipped"))
@ -59,7 +57,7 @@ class LoadFoodsWorker(
.enqueueUniqueWork(
NSClientV3Plugin.JOB_NAME,
ExistingWorkPolicy.APPEND_OR_REPLACE,
OneTimeWorkRequest.Builder(LoadDeviceStatusWorker::class.java).build()
OneTimeWorkRequest.Builder(LoadProfileStoreWorker::class.java).build()
)
}
}

View file

@ -0,0 +1,85 @@
package info.nightscout.plugins.sync.nsclientV3.workers
import android.content.Context
import androidx.work.ExistingWorkPolicy
import androidx.work.OneTimeWorkRequest
import androidx.work.WorkManager
import androidx.work.WorkerParameters
import androidx.work.workDataOf
import info.nightscout.core.utils.receivers.DataWorkerStorage
import info.nightscout.core.utils.worker.LoggingWorker
import info.nightscout.interfaces.utils.JsonHelper
import info.nightscout.interfaces.workflow.WorkerClasses
import info.nightscout.plugins.sync.nsclientV3.NSClientV3Plugin
import info.nightscout.rx.bus.RxBus
import info.nightscout.rx.events.EventNSClientNewLog
import info.nightscout.sdk.interfaces.NSAndroidClient
import info.nightscout.shared.utils.DateUtil
import kotlinx.coroutines.runBlocking
import org.json.JSONObject
import javax.inject.Inject
import kotlin.math.max
class LoadProfileStoreWorker(
context: Context,
params: WorkerParameters
) : LoggingWorker(context, params) {
@Inject lateinit var dataWorkerStorage: DataWorkerStorage
@Inject lateinit var rxBus: RxBus
@Inject lateinit var context: Context
@Inject lateinit var nsClientV3Plugin: NSClientV3Plugin
@Inject lateinit var dateUtil: DateUtil
@Inject lateinit var workerClasses: WorkerClasses
override fun doWorkAndLog(): Result {
val nsAndroidClient = nsClientV3Plugin.nsAndroidClient ?: return Result.failure(workDataOf("Error" to "AndroidClient is null"))
var ret = Result.success()
val lastLoaded = max(nsClientV3Plugin.lastLoadedSrvModified.collections.profile, 0)
runBlocking {
if ((nsClientV3Plugin.newestDataOnServer?.collections?.profile ?: Long.MAX_VALUE) > lastLoaded)
try {
val response: NSAndroidClient.ReadResponse<List<JSONObject>> = nsAndroidClient.getLastProfileStore()
val profiles = response.values
if (profiles.size == 1) {
val profile = profiles[0]
JsonHelper.safeGetLongAllowNull(profile, "srvModified")?.let { nsClientV3Plugin.lastLoadedSrvModified.collections.profile = it }
nsClientV3Plugin.storeLastLoadedSrvModified()
aapsLogger.debug("PROFILE: $profile")
rxBus.send(EventNSClientNewLog("RCV", "1 PROFILE from ${dateUtil.dateAndTimeAndSecondsString(lastLoaded)}"))
WorkManager.getInstance(context)
.beginUniqueWork(
NSClientV3Plugin.JOB_NAME,
ExistingWorkPolicy.APPEND_OR_REPLACE,
OneTimeWorkRequest.Builder((workerClasses.nsProfileWorker))
.setInputData(dataWorkerStorage.storeInputData(profile))
.build()
).then(OneTimeWorkRequest.Builder(LoadDeviceStatusWorker::class.java).build())
.enqueue()
} else {
rxBus.send(EventNSClientNewLog("RCV END", "No new PROFILE from ${dateUtil.dateAndTimeAndSecondsString(lastLoaded)}"))
WorkManager.getInstance(context)
.enqueueUniqueWork(
NSClientV3Plugin.JOB_NAME,
ExistingWorkPolicy.APPEND_OR_REPLACE,
OneTimeWorkRequest.Builder(LoadDeviceStatusWorker::class.java).build()
)
}
} catch (error: Exception) {
aapsLogger.error("Error: ", error)
ret = Result.failure(workDataOf("Error" to error.toString()))
}
else {
rxBus.send(EventNSClientNewLog("RCV END", "No PROFILE from ${dateUtil.dateAndTimeAndSecondsString(lastLoaded)}"))
WorkManager.getInstance(context)
.enqueueUniqueWork(
NSClientV3Plugin.JOB_NAME,
ExistingWorkPolicy.APPEND_OR_REPLACE,
OneTimeWorkRequest.Builder(LoadDeviceStatusWorker::class.java).build()
)
}
}
return ret
}
}

View file

@ -69,7 +69,7 @@ internal class NSClientV3PluginTest : TestBaseWithProfile() {
sut =
NSClientV3Plugin(
injector, aapsLogger, aapsSchedulers, rxBus, rh, context, fabricPrivacy,
sp, nsClientReceiverDelegate, config, dateUtil, uiInteraction, storeDataForDb, dataSyncSelector, mockedProfileFunction
sp, nsClientReceiverDelegate, config, dateUtil, uiInteraction, dataSyncSelector, mockedProfileFunction, repository
)
sut.nsAndroidClient = nsAndroidClient
`when`(mockedProfileFunction.getProfile(anyLong())).thenReturn(validProfile)
@ -519,4 +519,22 @@ internal class NSClientV3PluginTest : TestBaseWithProfile() {
verify(dataSyncSelector, Times(2)).confirmLastTherapyEventIdIfGreater(1000)
verify(dataSyncSelector, Times(2)).processChangedTherapyEvents()
}
@Test
@OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
fun nsAddProfile() = runTest {
sut.scope = CoroutineScope(UnconfinedTestDispatcher(testScheduler))
val dataPair = DataSyncSelector.PairProfileStore(getValidProfileStore().data, 1000)
// create
`when`(nsAndroidClient.createProfileStore(anyObject())).thenReturn(CreateUpdateResponse(201, null))
sut.nsAdd("profile", dataPair, "1/3")
verify(dataSyncSelector, Times(1)).confirmLastProfileStore(1000)
verify(dataSyncSelector, Times(1)).processChangedProfileStore()
// update
`when`(nsAndroidClient.updateTreatment(anyObject())).thenReturn(CreateUpdateResponse(200, null))
sut.nsUpdate("profile", dataPair, "1/3")
verify(dataSyncSelector, Times(2)).confirmLastProfileStore(1000)
verify(dataSyncSelector, Times(2)).processChangedProfileStore()
}
}