NSCv3: process Food, first load based on created_at

This commit is contained in:
Milos Kozak 2022-12-28 20:17:04 +01:00
parent 10e8e32e3b
commit d9c6cd6342
25 changed files with 603 additions and 159 deletions

View file

@ -1,7 +1,6 @@
package info.nightscout.androidaps.workflow package info.nightscout.androidaps.workflow
import info.nightscout.interfaces.workflow.WorkerClasses import info.nightscout.interfaces.workflow.WorkerClasses
import info.nightscout.plugins.general.food.FoodPlugin
import info.nightscout.plugins.profile.ProfilePlugin import info.nightscout.plugins.profile.ProfilePlugin
import info.nightscout.source.NSClientSourcePlugin import info.nightscout.source.NSClientSourcePlugin
import javax.inject.Inject import javax.inject.Inject
@ -10,5 +9,4 @@ class WorkerClassesImpl @Inject constructor(): WorkerClasses{
override val nsClientSourceWorker = NSClientSourcePlugin.NSClientSourceWorker::class.java override val nsClientSourceWorker = NSClientSourcePlugin.NSClientSourceWorker::class.java
override val nsProfileWorker = ProfilePlugin.NSProfileWorker::class.java override val nsProfileWorker = ProfilePlugin.NSProfileWorker::class.java
override val foodWorker = FoodPlugin.FoodWorker::class.java
} }

1
connectwsa.bat Normal file
View file

@ -0,0 +1 @@
adb connect 127.0.0.1:58526

View file

@ -27,11 +27,11 @@ interface StoreDataForDb {
val temporaryBasals: MutableList<TemporaryBasal> val temporaryBasals: MutableList<TemporaryBasal>
val profileSwitches: MutableList<ProfileSwitch> val profileSwitches: MutableList<ProfileSwitch>
val offlineEvents: MutableList<OfflineEvent> val offlineEvents: MutableList<OfflineEvent>
val foods: MutableList<Food>
val nsIdGlucoseValues: MutableList<GlucoseValue> val nsIdGlucoseValues: MutableList<GlucoseValue>
val nsIdBoluses: MutableList<Bolus> val nsIdBoluses: MutableList<Bolus>
val nsIdCarbs: MutableList<Carbs> val nsIdCarbs: MutableList<Carbs>
val nsIdFoods: MutableList<Food>
val nsIdTemporaryTargets: MutableList<TemporaryTarget> val nsIdTemporaryTargets: MutableList<TemporaryTarget>
val nsIdEffectiveProfileSwitches: MutableList<EffectiveProfileSwitch> val nsIdEffectiveProfileSwitches: MutableList<EffectiveProfileSwitch>
val nsIdBolusCalculatorResults: MutableList<BolusCalculatorResult> val nsIdBolusCalculatorResults: MutableList<BolusCalculatorResult>
@ -41,8 +41,10 @@ interface StoreDataForDb {
val nsIdProfileSwitches: MutableList<ProfileSwitch> val nsIdProfileSwitches: MutableList<ProfileSwitch>
val nsIdOfflineEvents: MutableList<OfflineEvent> val nsIdOfflineEvents: MutableList<OfflineEvent>
val nsIdDeviceStatuses: MutableList<DeviceStatus> val nsIdDeviceStatuses: MutableList<DeviceStatus>
val nsIdFoods: MutableList<Food>
fun storeTreatmentsToDb() fun storeTreatmentsToDb()
fun storeGlucoseValuesToDb() fun storeGlucoseValuesToDb()
fun storeFoodsToDb()
fun scheduleNsIdUpdate() fun scheduleNsIdUpdate()
} }

View file

@ -16,7 +16,7 @@ interface NsClient : Sync {
fun textLog(): Spanned fun textLog(): Spanned
fun clearLog() fun clearLog()
enum class Collection { ENTRIES, TREATMENTS} enum class Collection { ENTRIES, TREATMENTS, FOODS }
/** /**
* NSC v3 does first load of all data * NSC v3 does first load of all data
* next loads are using srvModified property for sync * next loads are using srvModified property for sync

View file

@ -5,5 +5,4 @@ import androidx.work.ListenableWorker
interface WorkerClasses { interface WorkerClasses {
val nsClientSourceWorker: Class<out ListenableWorker> val nsClientSourceWorker: Class<out ListenableWorker>
val nsProfileWorker: Class<out ListenableWorker> val nsProfileWorker: Class<out ListenableWorker>
val foodWorker: Class<out ListenableWorker>
} }

View file

@ -4,14 +4,17 @@ import android.content.Context
import info.nightscout.sdk.exceptions.DateHeaderOutOfToleranceException import info.nightscout.sdk.exceptions.DateHeaderOutOfToleranceException
import info.nightscout.sdk.exceptions.InvalidAccessTokenException import info.nightscout.sdk.exceptions.InvalidAccessTokenException
import info.nightscout.sdk.exceptions.InvalidFormatNightscoutException import info.nightscout.sdk.exceptions.InvalidFormatNightscoutException
import info.nightscout.sdk.exceptions.UnsuccessfullNightscoutException
import info.nightscout.sdk.exceptions.UnknownResponseNightscoutException import info.nightscout.sdk.exceptions.UnknownResponseNightscoutException
import info.nightscout.sdk.exceptions.UnsuccessfullNightscoutException
import info.nightscout.sdk.interfaces.NSAndroidClient import info.nightscout.sdk.interfaces.NSAndroidClient
import info.nightscout.sdk.localmodel.Status import info.nightscout.sdk.localmodel.Status
import info.nightscout.sdk.localmodel.entry.NSSgvV3 import info.nightscout.sdk.localmodel.entry.NSSgvV3
import info.nightscout.sdk.localmodel.food.NSFood
import info.nightscout.sdk.localmodel.treatment.CreateUpdateResponse import info.nightscout.sdk.localmodel.treatment.CreateUpdateResponse
import info.nightscout.sdk.localmodel.treatment.NSTreatment import info.nightscout.sdk.localmodel.treatment.NSTreatment
import info.nightscout.sdk.mapper.toLocal import info.nightscout.sdk.mapper.toLocal
import info.nightscout.sdk.mapper.toNSFood
import info.nightscout.sdk.mapper.toRemoteFood
import info.nightscout.sdk.mapper.toRemoteTreatment import info.nightscout.sdk.mapper.toRemoteTreatment
import info.nightscout.sdk.mapper.toSgv import info.nightscout.sdk.mapper.toSgv
import info.nightscout.sdk.mapper.toTreatment import info.nightscout.sdk.mapper.toTreatment
@ -19,6 +22,7 @@ import info.nightscout.sdk.networking.NetworkStackBuilder
import info.nightscout.sdk.remotemodel.LastModified import info.nightscout.sdk.remotemodel.LastModified
import info.nightscout.sdk.remotemodel.RemoteDeviceStatus import info.nightscout.sdk.remotemodel.RemoteDeviceStatus
import info.nightscout.sdk.remotemodel.RemoteEntry import info.nightscout.sdk.remotemodel.RemoteEntry
import info.nightscout.sdk.remotemodel.RemoteFood
import info.nightscout.sdk.remotemodel.RemoteTreatment import info.nightscout.sdk.remotemodel.RemoteTreatment
import info.nightscout.sdk.utils.retry import info.nightscout.sdk.utils.retry
import info.nightscout.sdk.utils.toNotNull import info.nightscout.sdk.utils.toNotNull
@ -140,9 +144,9 @@ class NSAndroidClientImpl(
} }
} }
override suspend fun getTreatmentsNewerThan(from: Long, limit: Long): List<NSTreatment> = callWrapper(dispatcher) { override suspend fun getTreatmentsNewerThan(createdAt: String, limit: Long): List<NSTreatment> = callWrapper(dispatcher) {
val response = api.getTreatmentsNewerThan(from, limit) val response = api.getTreatmentsNewerThan(createdAt, limit)
if (response.isSuccessful) { if (response.isSuccessful) {
return@callWrapper response.body()?.result?.map(RemoteTreatment::toTreatment).toNotNull() return@callWrapper response.body()?.result?.map(RemoteTreatment::toTreatment).toNotNull()
} else { } else {
@ -154,7 +158,8 @@ class NSAndroidClientImpl(
val response = api.getTreatmentsModifiedSince(from, limit) val response = api.getTreatmentsModifiedSince(from, limit)
val eTagString = response.headers()["ETag"] val eTagString = response.headers()["ETag"]
val eTag = eTagString?.substring(3, eTagString.length - 1)?.toLong() ?: throw UnsuccessfullNightscoutException() val eTag = eTagString?.substring(3, eTagString.length - 1)?.toLong()
?: throw UnsuccessfullNightscoutException()
if (response.isSuccessful) { if (response.isSuccessful) {
return@callWrapper NSAndroidClient.ReadResponse(eTag, response.body()?.result?.map(RemoteTreatment::toTreatment).toNotNull()) return@callWrapper NSAndroidClient.ReadResponse(eTag, response.body()?.result?.map(RemoteTreatment::toTreatment).toNotNull())
} else { } else {
@ -207,6 +212,64 @@ class NSAndroidClientImpl(
} }
} }
override suspend fun getFoods(limit: Long): List<NSFood> = callWrapper(dispatcher) {
val response = api.getFoods(limit)
if (response.isSuccessful) {
return@callWrapper response.body()?.result?.map(RemoteFood::toNSFood).toNotNull()
} else {
throw UnsuccessfullNightscoutException()
}
}
/*
override suspend fun getFoodsModifiedSince(from: Long, limit: Long): NSAndroidClient.ReadResponse<List<NSFood>> = callWrapper(dispatcher) {
val response = api.getFoodsModifiedSince(from, limit)
val eTagString = response.headers()["ETag"]
val eTag = eTagString?.substring(3, eTagString.length - 1)?.toLong() ?: throw UnsuccessfullNightscoutException()
if (response.isSuccessful) {
return@callWrapper NSAndroidClient.ReadResponse(eTag, response.body()?.result?.map(RemoteFood::toNSFood).toNotNull())
} else {
throw UnsuccessfullNightscoutException()
}
}
*/
override suspend fun createFood(nsFood: NSFood): CreateUpdateResponse = callWrapper(dispatcher) {
val remoteFood = nsFood.toRemoteFood()
remoteFood.app = "AAPS"
val response = api.createFood(remoteFood)
if (response.isSuccessful) {
return@callWrapper CreateUpdateResponse(
response = response.code(),
identifier = response.body()?.result?.identifier ?: throw UnknownResponseNightscoutException(),
isDeduplication = response.body()?.result?.isDeduplication ?: false,
deduplicatedIdentifier = response.body()?.result?.deduplicatedIdentifier,
lastModified = response.body()?.result?.lastModified
)
} else {
throw UnsuccessfullNightscoutException()
}
}
override suspend fun updateFood(nsFood: NSFood): CreateUpdateResponse = callWrapper(dispatcher) {
val remoteFood = nsFood.toRemoteFood()
val response = api.updateFood(remoteFood)
if (response.isSuccessful) {
return@callWrapper CreateUpdateResponse(
response = response.code(),
identifier = response.body()?.result?.identifier ?: throw UnknownResponseNightscoutException(),
isDeduplication = response.body()?.result?.isDeduplication ?: false,
deduplicatedIdentifier = response.body()?.result?.deduplicatedIdentifier,
lastModified = response.body()?.result?.lastModified
)
} else {
throw UnsuccessfullNightscoutException()
}
}
private suspend fun <T> callWrapper(dispatcher: CoroutineDispatcher, block: suspend () -> T): T = private suspend fun <T> callWrapper(dispatcher: CoroutineDispatcher, block: suspend () -> T): T =
withContext(dispatcher) { withContext(dispatcher) {
retry( retry(

View file

@ -2,6 +2,7 @@ package info.nightscout.sdk.interfaces
import info.nightscout.sdk.localmodel.Status import info.nightscout.sdk.localmodel.Status
import info.nightscout.sdk.localmodel.entry.NSSgvV3 import info.nightscout.sdk.localmodel.entry.NSSgvV3
import info.nightscout.sdk.localmodel.food.NSFood
import info.nightscout.sdk.localmodel.treatment.CreateUpdateResponse import info.nightscout.sdk.localmodel.treatment.CreateUpdateResponse
import info.nightscout.sdk.localmodel.treatment.NSTreatment import info.nightscout.sdk.localmodel.treatment.NSTreatment
import info.nightscout.sdk.remotemodel.LastModified import info.nightscout.sdk.remotemodel.LastModified
@ -23,9 +24,13 @@ interface NSAndroidClient {
suspend fun getSgvs(): List<NSSgvV3> suspend fun getSgvs(): List<NSSgvV3>
suspend fun getSgvsModifiedSince(from: Long, limit: Long): ReadResponse<List<NSSgvV3>> suspend fun getSgvsModifiedSince(from: Long, limit: Long): ReadResponse<List<NSSgvV3>>
suspend fun getSgvsNewerThan(from: Long, limit: Long): List<NSSgvV3> suspend fun getSgvsNewerThan(from: Long, limit: Long): List<NSSgvV3>
suspend fun getTreatmentsNewerThan(from: Long, limit: Long): List<NSTreatment> suspend fun getTreatmentsNewerThan(createdAt: String, limit: Long): List<NSTreatment>
suspend fun getTreatmentsModifiedSince(from: Long, limit: Long): ReadResponse<List<NSTreatment>> suspend fun getTreatmentsModifiedSince(from: Long, limit: Long): ReadResponse<List<NSTreatment>>
suspend fun getDeviceStatusModifiedSince(from: Long): List<RemoteDeviceStatus> suspend fun getDeviceStatusModifiedSince(from: Long): List<RemoteDeviceStatus>
suspend fun createTreatment(nsTreatment: NSTreatment): CreateUpdateResponse suspend fun createTreatment(nsTreatment: NSTreatment): CreateUpdateResponse
suspend fun updateTreatment(nsTreatment: NSTreatment): CreateUpdateResponse suspend fun updateTreatment(nsTreatment: NSTreatment): CreateUpdateResponse
suspend fun getFoods(limit: Long): List<NSFood>
//suspend fun getFoodsModifiedSince(from: Long, limit: Long): ReadResponse<List<NSFood>>
suspend fun createFood(nsFood: NSFood): CreateUpdateResponse
suspend fun updateFood(nsFood: NSFood): CreateUpdateResponse
} }

View file

@ -0,0 +1,30 @@
package info.nightscout.sdk.localmodel.food
import info.nightscout.sdk.localmodel.entry.NsUnits
data class NSFood(
val date: Long,
val device: String? = null,
val identifier: String?,
val units: NsUnits? = null,
val srvModified: Long? = null,
val srvCreated: Long? = null,
val subject: String? = null,
var isReadOnly: Boolean = false,
val isValid: Boolean,
var app: String? = null,
var name: String,
var category: String? = null,
var subCategory: String? = null,
// Example:
// name="juice" portion=250 units="ml" carbs=12
// means 250ml of juice has 12g of carbs
var portion: Double, // common portion in "units"
var carbs: Int, // in grams
var fat: Int? = null, // in grams
var protein: Int? = null, // in grams
var energy: Int? = null, // in kJ
var unit: String = "g",
var gi: Int? = null // not used yet
)

View file

@ -34,6 +34,7 @@ enum class EventType(val text: String) {
@SerializedName("Temp Basal Start") TEMPORARY_BASAL_START("Temp Basal Start"), @SerializedName("Temp Basal Start") TEMPORARY_BASAL_START("Temp Basal Start"),
@SerializedName("Temp Basal End") TEMPORARY_BASAL_END("Temp Basal End"), @SerializedName("Temp Basal End") TEMPORARY_BASAL_END("Temp Basal End"),
@SerializedName("") ERROR(""),
@SerializedName("<none>") NONE("<none>"); @SerializedName("<none>") NONE("<none>");
companion object { companion object {

View file

@ -0,0 +1,64 @@
package info.nightscout.sdk.mapper
import info.nightscout.sdk.localmodel.food.NSFood
import info.nightscout.sdk.remotemodel.RemoteFood
/**
* Convert to [RemoteFood] and back to [NSFood]
* testing purpose only
*
* @return treatment after double conversion
*/
fun NSFood.convertToRemoteAndBack(): NSFood? =
toRemoteFood().toNSFood()
internal fun RemoteFood.toNSFood(): NSFood? {
when (type) {
"food" ->
return NSFood(
date = date ?: 0L,
device = device,
identifier = identifier,
unit = unit ?: "g",
srvModified = srvModified,
srvCreated = srvCreated,
subject = subject,
isReadOnly = isReadOnly ?: false,
isValid = isValid ?: true,
name = name,
category = category,
subCategory = subcategory,
portion = portion,
carbs = carbs,
fat = fat,
protein = protein,
energy = energy,
gi = gi
)
else -> return null
}
}
internal fun NSFood.toRemoteFood(): RemoteFood =
RemoteFood(
type = "food",
date = date,
device = device,
identifier = identifier,
unit = unit,
srvModified = srvModified,
srvCreated = srvCreated,
subject = subject,
isReadOnly = isReadOnly,
isValid = isValid,
name = name,
category = category,
subcategory = subCategory,
portion = portion,
carbs = carbs,
fat = fat,
protein = protein,
energy = energy,
gi = gi
)

View file

@ -6,6 +6,7 @@ import info.nightscout.sdk.remotemodel.NSResponse
import info.nightscout.sdk.remotemodel.RemoteCreateUpdateResponse import info.nightscout.sdk.remotemodel.RemoteCreateUpdateResponse
import info.nightscout.sdk.remotemodel.RemoteDeviceStatus import info.nightscout.sdk.remotemodel.RemoteDeviceStatus
import info.nightscout.sdk.remotemodel.RemoteEntry import info.nightscout.sdk.remotemodel.RemoteEntry
import info.nightscout.sdk.remotemodel.RemoteFood
import info.nightscout.sdk.remotemodel.RemoteStatusResponse import info.nightscout.sdk.remotemodel.RemoteStatusResponse
import info.nightscout.sdk.remotemodel.RemoteTreatment import info.nightscout.sdk.remotemodel.RemoteTreatment
import retrofit2.Response import retrofit2.Response
@ -48,7 +49,7 @@ internal interface NightscoutRemoteService {
suspend fun getSgvsModifiedSince(@Path("from") from: Long, @Query("limit") limit: Long): Response<NSResponse<List<RemoteEntry>>> suspend fun getSgvsModifiedSince(@Path("from") from: Long, @Query("limit") limit: Long): Response<NSResponse<List<RemoteEntry>>>
@GET("v3/treatments") @GET("v3/treatments")
suspend fun getTreatmentsNewerThan(@Query(value = "date\$gt", encoded = true) date: Long, @Query("limit") limit: Long): Response<NSResponse<List<RemoteTreatment>>> suspend fun getTreatmentsNewerThan(@Query(value = "created_at\$gt", encoded = true) createdAt: String, @Query("limit") limit: Long): Response<NSResponse<List<RemoteTreatment>>>
@GET("v3/treatments/history/{from}") @GET("v3/treatments/history/{from}")
suspend fun getTreatmentsModifiedSince(@Path("from") from: Long, @Query("limit") limit: Long): Response<NSResponse<List<RemoteTreatment>>> suspend fun getTreatmentsModifiedSince(@Path("from") from: Long, @Query("limit") limit: Long): Response<NSResponse<List<RemoteTreatment>>>
@ -62,4 +63,16 @@ internal interface NightscoutRemoteService {
@PUT("v3/treatments") @PUT("v3/treatments")
suspend fun updateTreatment(@Body remoteTreatment: RemoteTreatment): Response<NSResponse<RemoteCreateUpdateResponse>> suspend fun updateTreatment(@Body remoteTreatment: RemoteTreatment): Response<NSResponse<RemoteCreateUpdateResponse>>
@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>>
@PUT("v3/food")
suspend fun updateFood(@Body remoteFood: RemoteFood): Response<NSResponse<RemoteCreateUpdateResponse>>
} }

View file

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

View file

@ -0,0 +1,41 @@
package info.nightscout.sdk.remotemodel
import com.google.gson.annotations.SerializedName
/**
* Depending on the type, different other fields are present.
* Those technically need to be optional.
*
* On upload a sanity check still needs to be done to verify that all mandatory fields for that type are there.
*
**/
internal data class RemoteFood(
@SerializedName("type") val type: String, // we are interesting in type "food"
@SerializedName("date") val date: Long?,
@SerializedName("name") val name: String,
@SerializedName("category") val category: String?,
@SerializedName("subcategory") val subcategory: String?,
@SerializedName("unit") val unit: String?,
@SerializedName("portion") val portion: Double,
@SerializedName("carbs") val carbs: Int,
@SerializedName("gi") val gi: Int?,
@SerializedName("energy") val energy: Int?,
@SerializedName("protein") val protein: Int?,
@SerializedName("fat") val fat: Int?,
@SerializedName("identifier")
val identifier: String?, // 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("isValid")
val isValid: Boolean?, // A flag set by the server only for deleted documents. This field appears only within history operation and for documents which were deleted by API v3 (and they always have a false value)
@SerializedName("isReadOnly")
val isReadOnly: Boolean?, // A flag set by client that locks the document from any changes. Every document marked with isReadOnly=true is forever immutable and cannot even be deleted.
@SerializedName("app") var app: String? = null, // Application or system in which the record was entered by human or device for the first time.
@SerializedName("device") val device: String? = null, // string The device from which the data originated (including serial number of the device, if it is relevant and safe).
@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("subject")
val subject: String? = null, // string Name of the security subject (within Nightscout scope) which has created the document. This field is automatically set by the server from the passed token or JWT.
@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("modifiedBy")
val modifiedBy: String? = null // string Name of the security subject (within Nightscout scope) which has patched or deleted the document for the last time. This field is automatically set by the server.
)

View file

@ -3,32 +3,33 @@ package info.nightscout.database.impl.transactions
import info.nightscout.database.entities.Food import info.nightscout.database.entities.Food
/** /**
* Sync the TherapyEvents from NS * Sync the Foods from NS
*/ */
class SyncNsFoodTransaction(private val food: Food) : Transaction<SyncNsFoodTransaction.TransactionResult>() { class SyncNsFoodTransaction(private val foods: List<Food>) : Transaction<SyncNsFoodTransaction.TransactionResult>() {
override fun run(): TransactionResult { override fun run(): TransactionResult {
val result = TransactionResult() val result = TransactionResult()
val current: Food? = for (food in foods) {
food.interfaceIDs.nightscoutId?.let { val current: Food? =
database.foodDao.findByNSId(it) food.interfaceIDs.nightscoutId?.let {
} database.foodDao.findByNSId(it)
}
if (current != null) { if (current != null) {
// nsId exists, update if different // nsId exists, update if different
if (!current.contentEqualsTo(food)) { if (!current.contentEqualsTo(food)) {
current.copyFrom(food) current.copyFrom(food)
database.foodDao.updateExistingEntry(current) database.foodDao.updateExistingEntry(current)
if (food.isValid && current.isValid) result.updated.add(current) if (food.isValid && current.isValid) result.updated.add(current)
else if (!food.isValid && current.isValid) result.invalidated.add(current) else if (!food.isValid && current.isValid) result.invalidated.add(current)
}
} else {
// not known nsId, add
database.foodDao.insertNewEntry(food)
result.inserted.add(food)
} }
return result
} }
// not known nsId, add
database.foodDao.insertNewEntry(food)
result.inserted.add(food)
return result return result
} }

View file

@ -3,12 +3,10 @@ package info.nightscout.plugins.di
import dagger.Module import dagger.Module
import dagger.android.ContributesAndroidInjector import dagger.android.ContributesAndroidInjector
import info.nightscout.plugins.general.food.FoodFragment import info.nightscout.plugins.general.food.FoodFragment
import info.nightscout.plugins.general.food.FoodPlugin
@Module @Module
@Suppress("unused") @Suppress("unused")
abstract class FoodModule { abstract class FoodModule {
@ContributesAndroidInjector abstract fun contributesFoodFragment(): FoodFragment @ContributesAndroidInjector abstract fun contributesFoodFragment(): FoodFragment
@ContributesAndroidInjector abstract fun contributesFoodWorker(): FoodPlugin.FoodWorker
} }

View file

@ -1,25 +1,12 @@
package info.nightscout.plugins.general.food package info.nightscout.plugins.general.food
import android.content.Context
import androidx.work.WorkerParameters
import androidx.work.workDataOf
import dagger.android.HasAndroidInjector import dagger.android.HasAndroidInjector
import info.nightscout.core.extensions.foodFromJson
import info.nightscout.core.utils.receivers.DataWorkerStorage
import info.nightscout.core.utils.worker.LoggingWorker
import info.nightscout.database.entities.Food
import info.nightscout.database.impl.AppRepository
import info.nightscout.database.impl.transactions.SyncNsFoodTransaction
import info.nightscout.interfaces.plugin.PluginBase import info.nightscout.interfaces.plugin.PluginBase
import info.nightscout.interfaces.plugin.PluginDescription import info.nightscout.interfaces.plugin.PluginDescription
import info.nightscout.interfaces.plugin.PluginType import info.nightscout.interfaces.plugin.PluginType
import info.nightscout.interfaces.utils.JsonHelper
import info.nightscout.plugins.R import info.nightscout.plugins.R
import info.nightscout.rx.logging.AAPSLogger import info.nightscout.rx.logging.AAPSLogger
import info.nightscout.rx.logging.LTag
import info.nightscout.shared.interfaces.ResourceHelper import info.nightscout.shared.interfaces.ResourceHelper
import info.nightscout.shared.sharedPreferences.SP
import org.json.JSONObject
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@ -37,73 +24,4 @@ class FoodPlugin @Inject constructor(
.shortName(R.string.food_short) .shortName(R.string.food_short)
.description(R.string.description_food), .description(R.string.description_food),
aapsLogger, rh, injector aapsLogger, rh, injector
) { )
// cannot be inner class because of needed injection
class FoodWorker(
context: Context,
params: WorkerParameters
) : LoggingWorker(context, params) {
@Inject lateinit var injector: HasAndroidInjector
@Inject lateinit var repository: AppRepository
@Inject lateinit var sp: SP
@Inject lateinit var dataWorkerStorage: DataWorkerStorage
override fun doWorkAndLog(): Result {
val foods = dataWorkerStorage.pickupJSONArray(inputData.getLong(DataWorkerStorage.STORE_KEY, -1))
?: return Result.failure(workDataOf("Error" to "missing input data"))
aapsLogger.debug(LTag.DATABASE, "Received Food Data: $foods")
var ret = Result.success()
for (index in 0 until foods.length()) {
val jsonFood: JSONObject = foods.getJSONObject(index)
if (JsonHelper.safeGetString(jsonFood, "type") != "food") continue
when (JsonHelper.safeGetString(jsonFood, "action")) {
"remove" -> {
val delFood = Food(
name = "",
portion = 0.0,
carbs = 0,
isValid = false
).also { it.interfaceIDs.nightscoutId = JsonHelper.safeGetString(jsonFood, "_id") }
repository.runTransactionForResult(SyncNsFoodTransaction(delFood))
.doOnError {
aapsLogger.error(LTag.DATABASE, "Error while removing food", it)
ret = Result.failure(workDataOf("Error" to it.toString()))
}
.blockingGet()
.also {
it.invalidated.forEach { f -> aapsLogger.debug(LTag.DATABASE, "Invalidated food ${f.interfaceIDs.nightscoutId}") }
}
}
else -> {
val food = foodFromJson(jsonFood)
if (food != null) {
repository.runTransactionForResult(SyncNsFoodTransaction(food))
.doOnError {
aapsLogger.error(LTag.DATABASE, "Error while adding/updating food", it)
ret = Result.failure(workDataOf("Error" to it.toString()))
}
.blockingGet()
.also { result ->
result.inserted.forEach { aapsLogger.debug(LTag.DATABASE, "Inserted food $it") }
result.updated.forEach { aapsLogger.debug(LTag.DATABASE, "Updated food $it") }
result.invalidated.forEach { aapsLogger.debug(LTag.DATABASE, "Invalidated food $it") }
}
} else {
aapsLogger.error(LTag.DATABASE, "Error parsing food", jsonFood.toString())
ret = Result.failure(workDataOf("Error" to "Error parsing food"))
}
}
}
}
return ret
}
}
}

View file

@ -20,9 +20,11 @@ import info.nightscout.plugins.sync.nsclient.workers.NSClientUpdateRemoveAckWork
import info.nightscout.plugins.sync.nsclientV3.workers.DataSyncWorker import info.nightscout.plugins.sync.nsclientV3.workers.DataSyncWorker
import info.nightscout.plugins.sync.nsclientV3.workers.LoadBgWorker import info.nightscout.plugins.sync.nsclientV3.workers.LoadBgWorker
import info.nightscout.plugins.sync.nsclientV3.workers.LoadDeviceStatusWorker 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.LoadLastModificationWorker
import info.nightscout.plugins.sync.nsclientV3.workers.LoadStatusWorker import info.nightscout.plugins.sync.nsclientV3.workers.LoadStatusWorker
import info.nightscout.plugins.sync.nsclientV3.workers.LoadTreatmentsWorker import info.nightscout.plugins.sync.nsclientV3.workers.LoadTreatmentsWorker
import info.nightscout.plugins.sync.nsclientV3.workers.ProcessFoodWorker
import info.nightscout.plugins.sync.nsclientV3.workers.ProcessTreatmentsWorker import info.nightscout.plugins.sync.nsclientV3.workers.ProcessTreatmentsWorker
import info.nightscout.plugins.sync.tidepool.TidepoolFragment import info.nightscout.plugins.sync.tidepool.TidepoolFragment
@ -46,11 +48,14 @@ abstract class SyncModule {
@ContributesAndroidInjector abstract fun contributesLoadStatusWorker(): LoadStatusWorker @ContributesAndroidInjector abstract fun contributesLoadStatusWorker(): LoadStatusWorker
@ContributesAndroidInjector abstract fun contributesLoadLastModificationWorker(): LoadLastModificationWorker @ContributesAndroidInjector abstract fun contributesLoadLastModificationWorker(): LoadLastModificationWorker
@ContributesAndroidInjector abstract fun contributesLoadBgWorker(): LoadBgWorker @ContributesAndroidInjector abstract fun contributesLoadBgWorker(): LoadBgWorker
@ContributesAndroidInjector abstract fun contributesLoadFoodsWorker(): LoadFoodsWorker
@ContributesAndroidInjector abstract fun contributesStoreBgWorker(): StoreDataForDbImpl.StoreBgWorker @ContributesAndroidInjector abstract fun contributesStoreBgWorker(): StoreDataForDbImpl.StoreBgWorker
@ContributesAndroidInjector abstract fun contributesStoreFoodWorker(): StoreDataForDbImpl.StoreFoodWorker
@ContributesAndroidInjector abstract fun contributesTreatmentWorker(): LoadTreatmentsWorker @ContributesAndroidInjector abstract fun contributesTreatmentWorker(): LoadTreatmentsWorker
@ContributesAndroidInjector abstract fun contributesProcessTreatmentsWorker(): ProcessTreatmentsWorker @ContributesAndroidInjector abstract fun contributesProcessTreatmentsWorker(): ProcessTreatmentsWorker
@ContributesAndroidInjector abstract fun contributesLoadDeviceStatusWorker(): LoadDeviceStatusWorker @ContributesAndroidInjector abstract fun contributesLoadDeviceStatusWorker(): LoadDeviceStatusWorker
@ContributesAndroidInjector abstract fun contributesDataSyncWorker(): DataSyncWorker @ContributesAndroidInjector abstract fun contributesDataSyncWorker(): DataSyncWorker
@ContributesAndroidInjector abstract fun contributesFoodWorker(): ProcessFoodWorker
@ContributesAndroidInjector abstract fun contributesTidepoolFragment(): TidepoolFragment @ContributesAndroidInjector abstract fun contributesTidepoolFragment(): TidepoolFragment

View file

@ -26,6 +26,7 @@ import info.nightscout.database.impl.transactions.SyncNsBolusTransaction
import info.nightscout.database.impl.transactions.SyncNsCarbsTransaction import info.nightscout.database.impl.transactions.SyncNsCarbsTransaction
import info.nightscout.database.impl.transactions.SyncNsEffectiveProfileSwitchTransaction import info.nightscout.database.impl.transactions.SyncNsEffectiveProfileSwitchTransaction
import info.nightscout.database.impl.transactions.SyncNsExtendedBolusTransaction import info.nightscout.database.impl.transactions.SyncNsExtendedBolusTransaction
import info.nightscout.database.impl.transactions.SyncNsFoodTransaction
import info.nightscout.database.impl.transactions.SyncNsOfflineEventTransaction import info.nightscout.database.impl.transactions.SyncNsOfflineEventTransaction
import info.nightscout.database.impl.transactions.SyncNsProfileSwitchTransaction import info.nightscout.database.impl.transactions.SyncNsProfileSwitchTransaction
import info.nightscout.database.impl.transactions.SyncNsTemporaryBasalTransaction import info.nightscout.database.impl.transactions.SyncNsTemporaryBasalTransaction
@ -92,11 +93,11 @@ class StoreDataForDbImpl @Inject constructor(
override val temporaryBasals: MutableList<TemporaryBasal> = mutableListOf() override val temporaryBasals: MutableList<TemporaryBasal> = mutableListOf()
override val profileSwitches: MutableList<ProfileSwitch> = mutableListOf() override val profileSwitches: MutableList<ProfileSwitch> = mutableListOf()
override val offlineEvents: MutableList<OfflineEvent> = mutableListOf() override val offlineEvents: MutableList<OfflineEvent> = mutableListOf()
override val foods: MutableList<Food> = mutableListOf()
override val nsIdGlucoseValues: MutableList<GlucoseValue> = mutableListOf() override val nsIdGlucoseValues: MutableList<GlucoseValue> = mutableListOf()
override val nsIdBoluses: MutableList<Bolus> = mutableListOf() override val nsIdBoluses: MutableList<Bolus> = mutableListOf()
override val nsIdCarbs: MutableList<Carbs> = mutableListOf() override val nsIdCarbs: MutableList<Carbs> = mutableListOf()
override val nsIdFoods: MutableList<Food> = mutableListOf()
override val nsIdTemporaryTargets: MutableList<TemporaryTarget> = mutableListOf() override val nsIdTemporaryTargets: MutableList<TemporaryTarget> = mutableListOf()
override val nsIdEffectiveProfileSwitches: MutableList<EffectiveProfileSwitch> = mutableListOf() override val nsIdEffectiveProfileSwitches: MutableList<EffectiveProfileSwitch> = mutableListOf()
override val nsIdBolusCalculatorResults: MutableList<BolusCalculatorResult> = mutableListOf() override val nsIdBolusCalculatorResults: MutableList<BolusCalculatorResult> = mutableListOf()
@ -106,6 +107,7 @@ class StoreDataForDbImpl @Inject constructor(
override val nsIdProfileSwitches: MutableList<ProfileSwitch> = mutableListOf() override val nsIdProfileSwitches: MutableList<ProfileSwitch> = mutableListOf()
override val nsIdOfflineEvents: MutableList<OfflineEvent> = mutableListOf() override val nsIdOfflineEvents: MutableList<OfflineEvent> = mutableListOf()
override val nsIdDeviceStatuses: MutableList<DeviceStatus> = mutableListOf() override val nsIdDeviceStatuses: MutableList<DeviceStatus> = mutableListOf()
override val nsIdFoods: MutableList<Food> = mutableListOf()
private val userEntries: MutableList<UserEntry> = mutableListOf() private val userEntries: MutableList<UserEntry> = mutableListOf()
@ -131,6 +133,19 @@ class StoreDataForDbImpl @Inject constructor(
} }
} }
class StoreFoodWorker(
context: Context,
params: WorkerParameters
) : LoggingWorker(context, params) {
@Inject lateinit var storeDataForDb: StoreDataForDb
override fun doWorkAndLog(): Result {
storeDataForDb.storeFoodsToDb()
return Result.success()
}
}
fun <T> HashMap<T, Long>.inc(key: T) = fun <T> HashMap<T, Long>.inc(key: T) =
if (containsKey(key)) merge(key, 1, Long::plus) if (containsKey(key)) merge(key, 1, Long::plus)
else put(key, 1) else put(key, 1)
@ -171,6 +186,36 @@ class StoreDataForDbImpl @Inject constructor(
rxBus.send(EventNSClientNewLog("DONE BG", "")) rxBus.send(EventNSClientNewLog("DONE BG", ""))
} }
override fun storeFoodsToDb() {
rxBus.send(EventNSClientNewLog("PROCESSING FOOD", ""))
if (foods.isNotEmpty())
repository.runTransactionForResult(SyncNsFoodTransaction(foods))
.doOnError {
aapsLogger.error(LTag.DATABASE, "Error while saving foods", it)
}
.blockingGet()
.also { result ->
foods.clear()
result.updated.forEach {
aapsLogger.debug(LTag.DATABASE, "Updated food $it")
updated.inc(Food::class.java.simpleName)
}
result.inserted.forEach {
aapsLogger.debug(LTag.DATABASE, "Inserted food $it")
inserted.inc(Food::class.java.simpleName)
}
result.invalidated.forEach {
aapsLogger.debug(LTag.DATABASE, "Invalidated food $it")
nsIdUpdated.inc(Food::class.java.simpleName)
}
}
sendLog("Food", Food::class.java.simpleName)
SystemClock.sleep(pause)
rxBus.send(EventNSClientNewLog("DONE FOOD", ""))
}
override fun storeTreatmentsToDb() { override fun storeTreatmentsToDb() {
rxBus.send(EventNSClientNewLog("PROCESSING TR", "")) rxBus.send(EventNSClientNewLog("PROCESSING TR", ""))

View file

@ -41,7 +41,7 @@ import info.nightscout.plugins.sync.nsclient.data.AlarmAck
import info.nightscout.plugins.sync.nsclient.data.NSDeviceStatusHandler import info.nightscout.plugins.sync.nsclient.data.NSDeviceStatusHandler
import info.nightscout.plugins.sync.nsclient.workers.NSClientAddUpdateWorker import info.nightscout.plugins.sync.nsclient.workers.NSClientAddUpdateWorker
import info.nightscout.plugins.sync.nsclient.workers.NSClientMbgWorker import info.nightscout.plugins.sync.nsclient.workers.NSClientMbgWorker
import info.nightscout.plugins.sync.nsclientV3.NSClientV3Plugin import info.nightscout.plugins.sync.nsclientV3.workers.ProcessFoodWorker
import info.nightscout.rx.AapsSchedulers import info.nightscout.rx.AapsSchedulers
import info.nightscout.rx.bus.RxBus import info.nightscout.rx.bus.RxBus
import info.nightscout.rx.events.EventAppExit import info.nightscout.rx.events.EventAppExit
@ -522,11 +522,14 @@ class NSClientService : DaggerService() {
if (data.has("food")) { if (data.has("food")) {
val foods = data.getJSONArray("food") val foods = data.getJSONArray("food")
if (foods.length() > 0) rxBus.send(EventNSClientNewLog("DATA", "received " + foods.length() + " foods")) if (foods.length() > 0) rxBus.send(EventNSClientNewLog("DATA", "received " + foods.length() + " foods"))
dataWorkerStorage.enqueue( dataWorkerStorage
OneTimeWorkRequest.Builder(workerClasses.foodWorker) .beginUniqueWork(
.setInputData(dataWorkerStorage.storeInputData(foods)) "ProcessFoods",
.build() OneTimeWorkRequest.Builder(ProcessFoodWorker::class.java)
) .setInputData(dataWorkerStorage.storeInputData(foods))
.build()
).then(OneTimeWorkRequest.Builder(StoreDataForDbImpl.StoreFoodWorker::class.java).build())
.enqueue()
} }
if (data.has("mbgs")) { if (data.has("mbgs")) {
val mbgArray = data.getJSONArray("mbgs") val mbgArray = data.getJSONArray("mbgs")
@ -550,7 +553,7 @@ class NSClientService : DaggerService() {
sp.putBoolean(info.nightscout.core.utils.R.string.key_objectives_bg_is_available_in_ns, true) sp.putBoolean(info.nightscout.core.utils.R.string.key_objectives_bg_is_available_in_ns, true)
dataWorkerStorage dataWorkerStorage
.beginUniqueWork( .beginUniqueWork(
NSClientV3Plugin.JOB_NAME, "ProcessBg",
OneTimeWorkRequest.Builder(workerClasses.nsClientSourceWorker) OneTimeWorkRequest.Builder(workerClasses.nsClientSourceWorker)
.setInputData(dataWorkerStorage.storeInputData(sgvs)) .setInputData(dataWorkerStorage.storeInputData(sgvs))
.build() .build()

View file

@ -38,6 +38,7 @@ import info.nightscout.plugins.sync.nsclientV3.extensions.toNSBolus
import info.nightscout.plugins.sync.nsclientV3.extensions.toNSBolusWizard import info.nightscout.plugins.sync.nsclientV3.extensions.toNSBolusWizard
import info.nightscout.plugins.sync.nsclientV3.extensions.toNSCarbs import info.nightscout.plugins.sync.nsclientV3.extensions.toNSCarbs
import info.nightscout.plugins.sync.nsclientV3.extensions.toNSEffectiveProfileSwitch import info.nightscout.plugins.sync.nsclientV3.extensions.toNSEffectiveProfileSwitch
import info.nightscout.plugins.sync.nsclientV3.extensions.toNSFood
import info.nightscout.plugins.sync.nsclientV3.extensions.toNSProfileSwitch import info.nightscout.plugins.sync.nsclientV3.extensions.toNSProfileSwitch
import info.nightscout.plugins.sync.nsclientV3.extensions.toNSTemporaryBasal import info.nightscout.plugins.sync.nsclientV3.extensions.toNSTemporaryBasal
import info.nightscout.plugins.sync.nsclientV3.extensions.toNSTemporaryTarget import info.nightscout.plugins.sync.nsclientV3.extensions.toNSTemporaryTarget
@ -293,6 +294,7 @@ class NSClientV3Plugin @Inject constructor(
when (collection) { when (collection) {
NsClient.Collection.ENTRIES -> lastLoadedSrvModified.collections.entries == 0L NsClient.Collection.ENTRIES -> lastLoadedSrvModified.collections.entries == 0L
NsClient.Collection.TREATMENTS -> lastLoadedSrvModified.collections.treatments == 0L NsClient.Collection.TREATMENTS -> lastLoadedSrvModified.collections.treatments == 0L
NsClient.Collection.FOODS -> lastLoadedSrvModified.collections.foods == 0L
} }
override fun updateLatestBgReceivedIfNewer(latestReceived: Long) { override fun updateLatestBgReceivedIfNewer(latestReceived: Long) {
@ -321,35 +323,76 @@ class NSClientV3Plugin @Inject constructor(
private val gson: Gson = GsonBuilder().create() private val gson: Gson = GsonBuilder().create()
private fun dbOperation(collection: String, dataPair: DataSyncSelector.DataPair, progress: String, operation: Operation) { private fun dbOperation(collection: String, dataPair: DataSyncSelector.DataPair, progress: String, operation: Operation) {
val call = when (operation) { if (collection == "food") {
Operation.CREATE -> nsAndroidClient?.let { return@let it::createTreatment } val call = when (operation) {
Operation.UPDATE -> nsAndroidClient?.let { return@let it::updateTreatment } Operation.CREATE -> nsAndroidClient?.let { return@let it::createFood }
} Operation.UPDATE -> nsAndroidClient?.let { return@let it::updateFood }
when (dataPair) {
is DataSyncSelector.PairBolus -> dataPair.value.toNSBolus()
is DataSyncSelector.PairCarbs -> dataPair.value.toNSCarbs()
is DataSyncSelector.PairBolusCalculatorResult -> dataPair.value.toNSBolusWizard()
is DataSyncSelector.PairTemporaryTarget -> dataPair.value.toNSTemporaryTarget()
// is DataSyncSelector.PairFood -> dataPair.value.toJson(false)
// is DataSyncSelector.PairGlucoseValue -> dataPair.value.toJson(false, dateUtil)
is DataSyncSelector.PairTherapyEvent -> dataPair.value.toNSTherapyEvent()
is DataSyncSelector.PairTemporaryBasal -> {
val profile = profileFunction.getProfile(dataPair.value.timestamp)
if (profile == null) {
dataSyncSelector.confirmLastTemporaryBasalIdIfGreater(dataPair.id)
return
}
dataPair.value.toNSTemporaryBasal(profile)
} }
// is DataSyncSelector.PairExtendedBolus -> dataPair.value.toJson(false, profileFunction.getProfile(dataPair.value.timestamp), dateUtil) when (dataPair) {
is DataSyncSelector.PairProfileSwitch -> dataPair.value.toNSProfileSwitch(dateUtil) is DataSyncSelector.PairFood -> dataPair.value.toNSFood()
is DataSyncSelector.PairEffectiveProfileSwitch -> dataPair.value.toNSEffectiveProfileSwitch(dateUtil) else -> null
// is DataSyncSelector.PairOfflineEvent -> dataPair.value.toJson(false, dateUtil) }?.let { data ->
else -> null runBlocking {
}?.let { data -> try {
runBlocking { val id = if (dataPair.value is TraceableDBEntry) (dataPair.value as TraceableDBEntry).interfaceIDs.nightscoutId else ""
if (collection == "treatments") { rxBus.send(
EventNSClientNewLog(
when (operation) {
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"
}
)
)
call?.let { it(data) }?.let { result ->
when (dataPair) {
is DataSyncSelector.PairFood -> {
if (result.response == 201) { // created
dataPair.value.interfaceIDs.nightscoutId = result.identifier
storeDataForDb.nsIdFoods.add(dataPair.value)
storeDataForDb.scheduleNsIdUpdate()
}
dataSyncSelector.confirmLastFoodIdIfGreater(dataPair.id)
}
}
}
} catch (e: Exception) {
aapsLogger.error(LTag.NSCLIENT, "Upload exception", e)
}
}
}
}
if (collection == "treatments") {
val call = when (operation) {
Operation.CREATE -> nsAndroidClient?.let { return@let it::createTreatment }
Operation.UPDATE -> nsAndroidClient?.let { return@let it::updateTreatment }
}
when (dataPair) {
is DataSyncSelector.PairBolus -> dataPair.value.toNSBolus()
is DataSyncSelector.PairCarbs -> dataPair.value.toNSCarbs()
is DataSyncSelector.PairBolusCalculatorResult -> dataPair.value.toNSBolusWizard()
is DataSyncSelector.PairTemporaryTarget -> dataPair.value.toNSTemporaryTarget()
// is DataSyncSelector.PairGlucoseValue -> dataPair.value.toJson(false, dateUtil)
is DataSyncSelector.PairTherapyEvent -> dataPair.value.toNSTherapyEvent()
is DataSyncSelector.PairTemporaryBasal -> {
val profile = profileFunction.getProfile(dataPair.value.timestamp)
if (profile == null) {
dataSyncSelector.confirmLastTemporaryBasalIdIfGreater(dataPair.id)
return
}
dataPair.value.toNSTemporaryBasal(profile)
}
// is DataSyncSelector.PairExtendedBolus -> dataPair.value.toJson(false, profileFunction.getProfile(dataPair.value.timestamp), dateUtil)
is DataSyncSelector.PairProfileSwitch -> dataPair.value.toNSProfileSwitch(dateUtil)
is DataSyncSelector.PairEffectiveProfileSwitch -> dataPair.value.toNSEffectiveProfileSwitch(dateUtil)
// is DataSyncSelector.PairOfflineEvent -> dataPair.value.toJson(false, dateUtil)
else -> null
}?.let { data ->
runBlocking {
try { try {
val id = if (dataPair.value is TraceableDBEntry) (dataPair.value as TraceableDBEntry).interfaceIDs.nightscoutId else "" val id = if (dataPair.value is TraceableDBEntry) (dataPair.value as TraceableDBEntry).interfaceIDs.nightscoutId else ""
rxBus.send( rxBus.send(
@ -401,7 +444,6 @@ class NSClientV3Plugin @Inject constructor(
} }
dataSyncSelector.confirmLastTempTargetsIdIfGreater(dataPair.id) dataSyncSelector.confirmLastTempTargetsIdIfGreater(dataPair.id)
} }
// is DataSyncSelector.PairFood -> dataPair.value.toJson(false)
// is DataSyncSelector.PairGlucoseValue -> dataPair.value.toJson(false, dateUtil) // is DataSyncSelector.PairGlucoseValue -> dataPair.value.toJson(false, dateUtil)
is DataSyncSelector.PairTherapyEvent -> { is DataSyncSelector.PairTherapyEvent -> {
if (result.response == 201) { // created if (result.response == 201) { // created
@ -487,6 +529,7 @@ class NSClientV3Plugin @Inject constructor(
.then(OneTimeWorkRequest.Builder(LoadBgWorker::class.java).build()) .then(OneTimeWorkRequest.Builder(LoadBgWorker::class.java).build())
// Other Workers are enqueued after BG finish // Other Workers are enqueued after BG finish
// LoadTreatmentsWorker // LoadTreatmentsWorker
// LoadFoodsWorker
// LoadDeviceStatusWorker // LoadDeviceStatusWorker
// DataSyncWorker // DataSyncWorker
.enqueue() .enqueue()

View file

@ -0,0 +1,38 @@
package info.nightscout.plugins.sync.nsclientV3.extensions
import info.nightscout.database.entities.Food
import info.nightscout.database.entities.embedments.InterfaceIDs
import info.nightscout.sdk.localmodel.food.NSFood
fun NSFood.toFood(): Food =
Food(
isValid = isValid,
name = name,
category = category,
subCategory = subCategory,
portion = portion,
carbs = carbs,
fat = fat,
protein = protein,
energy = energy,
unit = unit,
gi = gi,
interfaceIDs_backing = InterfaceIDs(nightscoutId = identifier)
)
fun Food.toNSFood(): NSFood =
NSFood(
date = System.currentTimeMillis(),
isValid = isValid,
name = name,
category = category,
subCategory = subCategory,
portion = portion,
carbs = carbs,
fat = fat,
protein = protein,
energy = energy,
unit = unit,
gi = gi,
identifier = interfaceIDs.nightscoutId,
)

View file

@ -0,0 +1,68 @@
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.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
import info.nightscout.rx.events.EventNSClientNewLog
import info.nightscout.sdk.localmodel.food.NSFood
import info.nightscout.shared.utils.DateUtil
import kotlinx.coroutines.runBlocking
import javax.inject.Inject
class LoadFoodsWorker(
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 storeDataForDb: StoreDataForDb
@Inject lateinit var workerClasses: WorkerClasses
override fun doWorkAndLog(): Result {
val nsAndroidClient = nsClientV3Plugin.nsAndroidClient ?: return Result.failure(workDataOf("Error" to "AndroidClient is null"))
// Food database doesn't provide last record modification
// Read full collection every 5th attempt
runBlocking {
if (nsClientV3Plugin.lastLoadedSrvModified.collections.foods++ % 5 == 0L) {
val foods: List<NSFood> = nsAndroidClient.getFoods(1000)
aapsLogger.debug("FOODS: $foods")
rxBus.send(EventNSClientNewLog("RCV", "${foods.size} FOODs"))
// Schedule processing of fetched data
WorkManager.getInstance(context)
.beginUniqueWork(
NSClientV3Plugin.JOB_NAME,
ExistingWorkPolicy.APPEND_OR_REPLACE,
OneTimeWorkRequest.Builder(ProcessFoodWorker::class.java)
.setInputData(dataWorkerStorage.storeInputData(foods))
.build()
).then(OneTimeWorkRequest.Builder(StoreDataForDbImpl.StoreFoodWorker::class.java).build())
.then(OneTimeWorkRequest.Builder(LoadDeviceStatusWorker::class.java).build())
.enqueue()
} else {
rxBus.send(EventNSClientNewLog("RCV", "FOOD skipped"))
WorkManager.getInstance(context)
.enqueueUniqueWork(
NSClientV3Plugin.JOB_NAME,
ExistingWorkPolicy.APPEND_OR_REPLACE,
OneTimeWorkRequest.Builder(LoadDeviceStatusWorker::class.java).build()
)
}
}
return Result.success()
}
}

View file

@ -46,7 +46,8 @@ class LoadTreatmentsWorker(
val treatments: List<NSTreatment> val treatments: List<NSTreatment>
val response: NSAndroidClient.ReadResponse<List<NSTreatment>>? val response: NSAndroidClient.ReadResponse<List<NSTreatment>>?
if (isFirstLoad) { if (isFirstLoad) {
treatments = nsAndroidClient.getTreatmentsNewerThan(lastLoaded, 500) val lastLoadedIso = dateUtil.toISOString(lastLoaded)
treatments = nsAndroidClient.getTreatmentsNewerThan(lastLoadedIso, 500)
response = NSAndroidClient.ReadResponse(0, treatments) response = NSAndroidClient.ReadResponse(0, treatments)
} }
else { else {
@ -75,14 +76,13 @@ class LoadTreatmentsWorker(
nsClientV3Plugin.lastLoadedSrvModified.collections.treatments = lastLoaded nsClientV3Plugin.lastLoadedSrvModified.collections.treatments = lastLoaded
nsClientV3Plugin.storeLastFetched() nsClientV3Plugin.storeLastFetched()
} }
rxBus.send(EventNSClientNewLog("RCV END", "No TRs from ${dateUtil rxBus.send(EventNSClientNewLog("RCV END", "No TRs from ${dateUtil.dateAndTimeAndSecondsString(lastLoaded)}"))
.dateAndTimeAndSecondsString(lastLoaded)}"))
storeDataForDb.storeTreatmentsToDb() storeDataForDb.storeTreatmentsToDb()
WorkManager.getInstance(context) WorkManager.getInstance(context)
.enqueueUniqueWork( .enqueueUniqueWork(
NSClientV3Plugin.JOB_NAME, NSClientV3Plugin.JOB_NAME,
ExistingWorkPolicy.APPEND_OR_REPLACE, ExistingWorkPolicy.APPEND_OR_REPLACE,
OneTimeWorkRequest.Builder(LoadDeviceStatusWorker::class.java).build() OneTimeWorkRequest.Builder(LoadFoodsWorker::class.java).build()
) )
} }
} catch (error: Exception) { } catch (error: Exception) {
@ -95,14 +95,13 @@ class LoadTreatmentsWorker(
nsClientV3Plugin.lastLoadedSrvModified.collections.treatments = lastLoaded nsClientV3Plugin.lastLoadedSrvModified.collections.treatments = lastLoaded
nsClientV3Plugin.storeLastFetched() nsClientV3Plugin.storeLastFetched()
} }
rxBus.send(EventNSClientNewLog("RCV END", "No new TRs from ${dateUtil rxBus.send(EventNSClientNewLog("RCV END", "No new TRs from ${dateUtil.dateAndTimeAndSecondsString(lastLoaded)}"))
.dateAndTimeAndSecondsString(lastLoaded)}"))
storeDataForDb.storeTreatmentsToDb() storeDataForDb.storeTreatmentsToDb()
WorkManager.getInstance(context) WorkManager.getInstance(context)
.enqueueUniqueWork( .enqueueUniqueWork(
NSClientV3Plugin.JOB_NAME, NSClientV3Plugin.JOB_NAME,
ExistingWorkPolicy.APPEND_OR_REPLACE, ExistingWorkPolicy.APPEND_OR_REPLACE,
OneTimeWorkRequest.Builder(LoadDeviceStatusWorker::class.java).build() OneTimeWorkRequest.Builder(LoadFoodsWorker::class.java).build()
) )
} }
} }

View file

@ -0,0 +1,72 @@
package info.nightscout.plugins.sync.nsclientV3.workers
import android.content.Context
import androidx.work.WorkerParameters
import androidx.work.workDataOf
import dagger.android.HasAndroidInjector
import info.nightscout.core.extensions.foodFromJson
import info.nightscout.core.utils.receivers.DataWorkerStorage
import info.nightscout.core.utils.worker.LoggingWorker
import info.nightscout.database.entities.Food
import info.nightscout.database.impl.AppRepository
import info.nightscout.interfaces.nsclient.StoreDataForDb
import info.nightscout.interfaces.utils.JsonHelper
import info.nightscout.plugins.sync.nsclientV3.extensions.toFood
import info.nightscout.rx.logging.LTag
import info.nightscout.sdk.localmodel.food.NSFood
import info.nightscout.shared.sharedPreferences.SP
import org.json.JSONArray
import org.json.JSONObject
import javax.inject.Inject
class ProcessFoodWorker(
context: Context,
params: WorkerParameters
) : LoggingWorker(context, params) {
@Inject lateinit var injector: HasAndroidInjector
@Inject lateinit var repository: AppRepository
@Inject lateinit var sp: SP
@Inject lateinit var dataWorkerStorage: DataWorkerStorage
@Inject lateinit var storeDataForDb: StoreDataForDb
override fun doWorkAndLog(): Result {
val data = dataWorkerStorage.pickupObject(inputData.getLong(DataWorkerStorage.STORE_KEY, -1))
?: return Result.failure(workDataOf("Error" to "missing input data"))
aapsLogger.debug(LTag.DATABASE, "Received Food Data: $data")
val ret = Result.success()
val foods = mutableListOf<Food>()
if (data is JSONArray) {
for (index in 0 until data.length()) {
val jsonFood: JSONObject = data.getJSONObject(index)
if (JsonHelper.safeGetString(jsonFood, "type") != "food") continue
when (JsonHelper.safeGetString(jsonFood, "action")) {
"remove" -> {
val delFood = Food(
name = "",
portion = 0.0,
carbs = 0,
isValid = false
).also { it.interfaceIDs.nightscoutId = JsonHelper.safeGetString(jsonFood, "_id") }
foods += delFood
}
else -> {
val food = foodFromJson(jsonFood)
if (food != null) foods += food
else aapsLogger.error(LTag.DATABASE, "Error parsing food", jsonFood.toString())
}
}
}
} else if (data is List<*>) {
for (i in 0 until data.size)
foods += (data[i] as NSFood).toFood()
}
storeDataForDb.foods.addAll(foods)
return ret
}
}

View file

@ -0,0 +1,36 @@
package info.nightscout.plugins.sync.nsclientV3.extensions
import info.nightscout.database.entities.Food
import info.nightscout.database.entities.embedments.InterfaceIDs
import info.nightscout.sdk.localmodel.food.NSFood
import info.nightscout.sdk.mapper.convertToRemoteAndBack
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test
@Suppress("SpellCheckingInspection")
internal class FoodExtensionKtTest {
@Test
fun toFood() {
val food = Food(
isValid = true,
name = "name",
category = "category",
subCategory = "subcategory",
portion = 2.0,
carbs = 20,
fat = 21,
protein = 22,
energy = 23,
unit = "g",
gi = 25,
interfaceIDs_backing = InterfaceIDs(
nightscoutId = "nightscoutId"
)
)
val food2 = (food.toNSFood().convertToRemoteAndBack() as NSFood).toFood()
Assertions.assertTrue(food.contentEqualsTo(food2))
Assertions.assertTrue(food.interfaceIdsEqualsTo(food2))
}
}