diff --git a/app/src/main/java/info/nightscout/androidaps/workflow/WorkerClassesImpl.kt b/app/src/main/java/info/nightscout/androidaps/workflow/WorkerClassesImpl.kt index 80a696e6ce..15553e045a 100644 --- a/app/src/main/java/info/nightscout/androidaps/workflow/WorkerClassesImpl.kt +++ b/app/src/main/java/info/nightscout/androidaps/workflow/WorkerClassesImpl.kt @@ -1,7 +1,6 @@ package info.nightscout.androidaps.workflow import info.nightscout.interfaces.workflow.WorkerClasses -import info.nightscout.plugins.general.food.FoodPlugin import info.nightscout.plugins.profile.ProfilePlugin import info.nightscout.source.NSClientSourcePlugin import javax.inject.Inject @@ -10,5 +9,4 @@ class WorkerClassesImpl @Inject constructor(): WorkerClasses{ override val nsClientSourceWorker = NSClientSourcePlugin.NSClientSourceWorker::class.java override val nsProfileWorker = ProfilePlugin.NSProfileWorker::class.java - override val foodWorker = FoodPlugin.FoodWorker::class.java } \ No newline at end of file diff --git a/connectwsa.bat b/connectwsa.bat new file mode 100644 index 0000000000..c9bb23c6b9 --- /dev/null +++ b/connectwsa.bat @@ -0,0 +1 @@ +adb connect 127.0.0.1:58526 \ No newline at end of file diff --git a/core/interfaces/src/main/java/info/nightscout/interfaces/nsclient/StoreDataForDb.kt b/core/interfaces/src/main/java/info/nightscout/interfaces/nsclient/StoreDataForDb.kt index 7bb85952c0..216f81f856 100644 --- a/core/interfaces/src/main/java/info/nightscout/interfaces/nsclient/StoreDataForDb.kt +++ b/core/interfaces/src/main/java/info/nightscout/interfaces/nsclient/StoreDataForDb.kt @@ -27,11 +27,11 @@ interface StoreDataForDb { val temporaryBasals: MutableList val profileSwitches: MutableList val offlineEvents: MutableList + val foods: MutableList val nsIdGlucoseValues: MutableList val nsIdBoluses: MutableList val nsIdCarbs: MutableList - val nsIdFoods: MutableList val nsIdTemporaryTargets: MutableList val nsIdEffectiveProfileSwitches: MutableList val nsIdBolusCalculatorResults: MutableList @@ -41,8 +41,10 @@ interface StoreDataForDb { val nsIdProfileSwitches: MutableList val nsIdOfflineEvents: MutableList val nsIdDeviceStatuses: MutableList + val nsIdFoods: MutableList fun storeTreatmentsToDb() fun storeGlucoseValuesToDb() + fun storeFoodsToDb() fun scheduleNsIdUpdate() } \ No newline at end of file diff --git a/core/interfaces/src/main/java/info/nightscout/interfaces/sync/NsClient.kt b/core/interfaces/src/main/java/info/nightscout/interfaces/sync/NsClient.kt index 4761fb6dc8..6de50eae90 100644 --- a/core/interfaces/src/main/java/info/nightscout/interfaces/sync/NsClient.kt +++ b/core/interfaces/src/main/java/info/nightscout/interfaces/sync/NsClient.kt @@ -16,7 +16,7 @@ interface NsClient : Sync { fun textLog(): Spanned fun clearLog() - enum class Collection { ENTRIES, TREATMENTS} + enum class Collection { ENTRIES, TREATMENTS, FOODS } /** * NSC v3 does first load of all data * next loads are using srvModified property for sync diff --git a/core/interfaces/src/main/java/info/nightscout/interfaces/workflow/WorkerClasses.kt b/core/interfaces/src/main/java/info/nightscout/interfaces/workflow/WorkerClasses.kt index 6d139e2fdf..1c8a98e7b4 100644 --- a/core/interfaces/src/main/java/info/nightscout/interfaces/workflow/WorkerClasses.kt +++ b/core/interfaces/src/main/java/info/nightscout/interfaces/workflow/WorkerClasses.kt @@ -5,5 +5,4 @@ import androidx.work.ListenableWorker interface WorkerClasses { val nsClientSourceWorker: Class val nsProfileWorker: Class - val foodWorker: Class } \ No newline at end of file diff --git a/core/ns-sdk/src/main/java/info/nightscout/sdk/NSAndroidClientImpl.kt b/core/ns-sdk/src/main/java/info/nightscout/sdk/NSAndroidClientImpl.kt index 900344bff1..7455738303 100644 --- a/core/ns-sdk/src/main/java/info/nightscout/sdk/NSAndroidClientImpl.kt +++ b/core/ns-sdk/src/main/java/info/nightscout/sdk/NSAndroidClientImpl.kt @@ -4,14 +4,17 @@ import android.content.Context import info.nightscout.sdk.exceptions.DateHeaderOutOfToleranceException import info.nightscout.sdk.exceptions.InvalidAccessTokenException import info.nightscout.sdk.exceptions.InvalidFormatNightscoutException -import info.nightscout.sdk.exceptions.UnsuccessfullNightscoutException import info.nightscout.sdk.exceptions.UnknownResponseNightscoutException +import info.nightscout.sdk.exceptions.UnsuccessfullNightscoutException import info.nightscout.sdk.interfaces.NSAndroidClient import info.nightscout.sdk.localmodel.Status 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.NSTreatment 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.toSgv 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.RemoteDeviceStatus import info.nightscout.sdk.remotemodel.RemoteEntry +import info.nightscout.sdk.remotemodel.RemoteFood import info.nightscout.sdk.remotemodel.RemoteTreatment import info.nightscout.sdk.utils.retry import info.nightscout.sdk.utils.toNotNull @@ -140,9 +144,9 @@ class NSAndroidClientImpl( } } - override suspend fun getTreatmentsNewerThan(from: Long, limit: Long): List = callWrapper(dispatcher) { + override suspend fun getTreatmentsNewerThan(createdAt: String, limit: Long): List = callWrapper(dispatcher) { - val response = api.getTreatmentsNewerThan(from, limit) + val response = api.getTreatmentsNewerThan(createdAt, limit) if (response.isSuccessful) { return@callWrapper response.body()?.result?.map(RemoteTreatment::toTreatment).toNotNull() } else { @@ -154,7 +158,8 @@ class NSAndroidClientImpl( val response = api.getTreatmentsModifiedSince(from, limit) 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) { return@callWrapper NSAndroidClient.ReadResponse(eTag, response.body()?.result?.map(RemoteTreatment::toTreatment).toNotNull()) } else { @@ -207,6 +212,64 @@ class NSAndroidClientImpl( } } + override suspend fun getFoods(limit: Long): List = 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> = 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 callWrapper(dispatcher: CoroutineDispatcher, block: suspend () -> T): T = withContext(dispatcher) { retry( diff --git a/core/ns-sdk/src/main/java/info/nightscout/sdk/interfaces/NSAndroidClient.kt b/core/ns-sdk/src/main/java/info/nightscout/sdk/interfaces/NSAndroidClient.kt index 86dd1fc23d..274e1efeb4 100644 --- a/core/ns-sdk/src/main/java/info/nightscout/sdk/interfaces/NSAndroidClient.kt +++ b/core/ns-sdk/src/main/java/info/nightscout/sdk/interfaces/NSAndroidClient.kt @@ -2,6 +2,7 @@ package info.nightscout.sdk.interfaces import info.nightscout.sdk.localmodel.Status 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.NSTreatment import info.nightscout.sdk.remotemodel.LastModified @@ -23,9 +24,13 @@ interface NSAndroidClient { suspend fun getSgvs(): List suspend fun getSgvsModifiedSince(from: Long, limit: Long): ReadResponse> suspend fun getSgvsNewerThan(from: Long, limit: Long): List - suspend fun getTreatmentsNewerThan(from: Long, limit: Long): List + suspend fun getTreatmentsNewerThan(createdAt: String, limit: Long): List suspend fun getTreatmentsModifiedSince(from: Long, limit: Long): ReadResponse> suspend fun getDeviceStatusModifiedSince(from: Long): List suspend fun createTreatment(nsTreatment: NSTreatment): CreateUpdateResponse suspend fun updateTreatment(nsTreatment: NSTreatment): CreateUpdateResponse + suspend fun getFoods(limit: Long): List + //suspend fun getFoodsModifiedSince(from: Long, limit: Long): ReadResponse> + suspend fun createFood(nsFood: NSFood): CreateUpdateResponse + suspend fun updateFood(nsFood: NSFood): CreateUpdateResponse } \ No newline at end of file diff --git a/core/ns-sdk/src/main/java/info/nightscout/sdk/localmodel/food/NSFood.kt b/core/ns-sdk/src/main/java/info/nightscout/sdk/localmodel/food/NSFood.kt new file mode 100644 index 0000000000..f663786088 --- /dev/null +++ b/core/ns-sdk/src/main/java/info/nightscout/sdk/localmodel/food/NSFood.kt @@ -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 +) diff --git a/core/ns-sdk/src/main/java/info/nightscout/sdk/localmodel/treatment/EventType.kt b/core/ns-sdk/src/main/java/info/nightscout/sdk/localmodel/treatment/EventType.kt index 4f6d8620e5..f5b912ada0 100644 --- a/core/ns-sdk/src/main/java/info/nightscout/sdk/localmodel/treatment/EventType.kt +++ b/core/ns-sdk/src/main/java/info/nightscout/sdk/localmodel/treatment/EventType.kt @@ -34,6 +34,7 @@ enum class EventType(val text: String) { @SerializedName("Temp Basal Start") TEMPORARY_BASAL_START("Temp Basal Start"), @SerializedName("Temp Basal End") TEMPORARY_BASAL_END("Temp Basal End"), + @SerializedName("") ERROR(""), @SerializedName("") NONE(""); companion object { diff --git a/core/ns-sdk/src/main/java/info/nightscout/sdk/mapper/FoodMapper.kt b/core/ns-sdk/src/main/java/info/nightscout/sdk/mapper/FoodMapper.kt new file mode 100644 index 0000000000..15bb3978c9 --- /dev/null +++ b/core/ns-sdk/src/main/java/info/nightscout/sdk/mapper/FoodMapper.kt @@ -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 + ) \ No newline at end of file diff --git a/core/ns-sdk/src/main/java/info/nightscout/sdk/networking/NightscoutRemoteService.kt b/core/ns-sdk/src/main/java/info/nightscout/sdk/networking/NightscoutRemoteService.kt index e1b93860a2..35ac06b700 100644 --- a/core/ns-sdk/src/main/java/info/nightscout/sdk/networking/NightscoutRemoteService.kt +++ b/core/ns-sdk/src/main/java/info/nightscout/sdk/networking/NightscoutRemoteService.kt @@ -6,6 +6,7 @@ import info.nightscout.sdk.remotemodel.NSResponse import info.nightscout.sdk.remotemodel.RemoteCreateUpdateResponse import info.nightscout.sdk.remotemodel.RemoteDeviceStatus 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 retrofit2.Response @@ -48,7 +49,7 @@ internal interface NightscoutRemoteService { suspend fun getSgvsModifiedSince(@Path("from") from: Long, @Query("limit") limit: Long): Response>> @GET("v3/treatments") - suspend fun getTreatmentsNewerThan(@Query(value = "date\$gt", encoded = true) date: Long, @Query("limit") limit: Long): Response>> + suspend fun getTreatmentsNewerThan(@Query(value = "created_at\$gt", encoded = true) createdAt: String, @Query("limit") limit: Long): Response>> @GET("v3/treatments/history/{from}") suspend fun getTreatmentsModifiedSince(@Path("from") from: Long, @Query("limit") limit: Long): Response>> @@ -62,4 +63,16 @@ internal interface NightscoutRemoteService { @PUT("v3/treatments") suspend fun updateTreatment(@Body remoteTreatment: RemoteTreatment): Response> + @GET("v3/food") + suspend fun getFoods(@Query("limit") limit: Long): Response>> +/* + @GET("v3/food/history/{from}") + suspend fun getFoodsModifiedSince(@Path("from") from: Long, @Query("limit") limit: Long): Response>> +*/ + @POST("v3/food") + suspend fun createFood(@Body remoteFood: RemoteFood): Response> + + @PUT("v3/food") + suspend fun updateFood(@Body remoteFood: RemoteFood): Response> + } diff --git a/core/ns-sdk/src/main/java/info/nightscout/sdk/remotemodel/LastModified.kt b/core/ns-sdk/src/main/java/info/nightscout/sdk/remotemodel/LastModified.kt index 0256499f2f..450760ae3a 100644 --- a/core/ns-sdk/src/main/java/info/nightscout/sdk/remotemodel/LastModified.kt +++ b/core/ns-sdk/src/main/java/info/nightscout/sdk/remotemodel/LastModified.kt @@ -18,6 +18,7 @@ data class LastModified( @SerializedName("devicestatus") var devicestatus: Long = 0, // devicestatus collection @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("treatments") var treatments: Long = 0, // treatments collection + @SerializedName("foods") var foods: Long = 0 // foods collection ) } diff --git a/core/ns-sdk/src/main/java/info/nightscout/sdk/remotemodel/RemoteFood.kt b/core/ns-sdk/src/main/java/info/nightscout/sdk/remotemodel/RemoteFood.kt new file mode 100644 index 0000000000..7382d5ca03 --- /dev/null +++ b/core/ns-sdk/src/main/java/info/nightscout/sdk/remotemodel/RemoteFood.kt @@ -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. +) diff --git a/database/impl/src/main/java/info/nightscout/database/impl/transactions/SyncNsFoodTransaction.kt b/database/impl/src/main/java/info/nightscout/database/impl/transactions/SyncNsFoodTransaction.kt index 33c1f68eb1..f13dd84749 100644 --- a/database/impl/src/main/java/info/nightscout/database/impl/transactions/SyncNsFoodTransaction.kt +++ b/database/impl/src/main/java/info/nightscout/database/impl/transactions/SyncNsFoodTransaction.kt @@ -3,32 +3,33 @@ package info.nightscout.database.impl.transactions import info.nightscout.database.entities.Food /** - * Sync the TherapyEvents from NS + * Sync the Foods from NS */ -class SyncNsFoodTransaction(private val food: Food) : Transaction() { +class SyncNsFoodTransaction(private val foods: List) : Transaction() { override fun run(): TransactionResult { val result = TransactionResult() - val current: Food? = - food.interfaceIDs.nightscoutId?.let { - database.foodDao.findByNSId(it) - } + for (food in foods) { + val current: Food? = + food.interfaceIDs.nightscoutId?.let { + database.foodDao.findByNSId(it) + } - if (current != null) { - // nsId exists, update if different - if (!current.contentEqualsTo(food)) { - current.copyFrom(food) - database.foodDao.updateExistingEntry(current) - if (food.isValid && current.isValid) result.updated.add(current) - else if (!food.isValid && current.isValid) result.invalidated.add(current) + if (current != null) { + // nsId exists, update if different + if (!current.contentEqualsTo(food)) { + current.copyFrom(food) + database.foodDao.updateExistingEntry(current) + if (food.isValid && current.isValid) result.updated.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 } diff --git a/plugins/main/src/main/java/info/nightscout/plugins/di/FoodModule.kt b/plugins/main/src/main/java/info/nightscout/plugins/di/FoodModule.kt index 60b5b68a52..9cb0238f97 100644 --- a/plugins/main/src/main/java/info/nightscout/plugins/di/FoodModule.kt +++ b/plugins/main/src/main/java/info/nightscout/plugins/di/FoodModule.kt @@ -3,12 +3,10 @@ package info.nightscout.plugins.di import dagger.Module import dagger.android.ContributesAndroidInjector import info.nightscout.plugins.general.food.FoodFragment -import info.nightscout.plugins.general.food.FoodPlugin @Module @Suppress("unused") abstract class FoodModule { @ContributesAndroidInjector abstract fun contributesFoodFragment(): FoodFragment - @ContributesAndroidInjector abstract fun contributesFoodWorker(): FoodPlugin.FoodWorker } \ No newline at end of file diff --git a/plugins/main/src/main/java/info/nightscout/plugins/general/food/FoodPlugin.kt b/plugins/main/src/main/java/info/nightscout/plugins/general/food/FoodPlugin.kt index 7f8b689328..0f7e008f65 100644 --- a/plugins/main/src/main/java/info/nightscout/plugins/general/food/FoodPlugin.kt +++ b/plugins/main/src/main/java/info/nightscout/plugins/general/food/FoodPlugin.kt @@ -1,25 +1,12 @@ package info.nightscout.plugins.general.food -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.database.impl.transactions.SyncNsFoodTransaction import info.nightscout.interfaces.plugin.PluginBase import info.nightscout.interfaces.plugin.PluginDescription import info.nightscout.interfaces.plugin.PluginType -import info.nightscout.interfaces.utils.JsonHelper import info.nightscout.plugins.R import info.nightscout.rx.logging.AAPSLogger -import info.nightscout.rx.logging.LTag import info.nightscout.shared.interfaces.ResourceHelper -import info.nightscout.shared.sharedPreferences.SP -import org.json.JSONObject import javax.inject.Inject import javax.inject.Singleton @@ -37,73 +24,4 @@ class FoodPlugin @Inject constructor( .shortName(R.string.food_short) .description(R.string.description_food), 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 - } - } -} \ No newline at end of file +) \ No newline at end of file diff --git a/plugins/sync/src/main/java/info/nightscout/plugins/sync/di/SyncModule.kt b/plugins/sync/src/main/java/info/nightscout/plugins/sync/di/SyncModule.kt index 6ecb8a379a..61cf0a2327 100644 --- a/plugins/sync/src/main/java/info/nightscout/plugins/sync/di/SyncModule.kt +++ b/plugins/sync/src/main/java/info/nightscout/plugins/sync/di/SyncModule.kt @@ -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.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.LoadStatusWorker 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.tidepool.TidepoolFragment @@ -46,11 +48,14 @@ abstract class SyncModule { @ContributesAndroidInjector abstract fun contributesLoadStatusWorker(): LoadStatusWorker @ContributesAndroidInjector abstract fun contributesLoadLastModificationWorker(): LoadLastModificationWorker @ContributesAndroidInjector abstract fun contributesLoadBgWorker(): LoadBgWorker + @ContributesAndroidInjector abstract fun contributesLoadFoodsWorker(): LoadFoodsWorker @ContributesAndroidInjector abstract fun contributesStoreBgWorker(): StoreDataForDbImpl.StoreBgWorker + @ContributesAndroidInjector abstract fun contributesStoreFoodWorker(): StoreDataForDbImpl.StoreFoodWorker @ContributesAndroidInjector abstract fun contributesTreatmentWorker(): LoadTreatmentsWorker @ContributesAndroidInjector abstract fun contributesProcessTreatmentsWorker(): ProcessTreatmentsWorker @ContributesAndroidInjector abstract fun contributesLoadDeviceStatusWorker(): LoadDeviceStatusWorker @ContributesAndroidInjector abstract fun contributesDataSyncWorker(): DataSyncWorker + @ContributesAndroidInjector abstract fun contributesFoodWorker(): ProcessFoodWorker @ContributesAndroidInjector abstract fun contributesTidepoolFragment(): TidepoolFragment diff --git a/plugins/sync/src/main/java/info/nightscout/plugins/sync/nsShared/StoreDataForDbImpl.kt b/plugins/sync/src/main/java/info/nightscout/plugins/sync/nsShared/StoreDataForDbImpl.kt index b02c2f2a23..3be9b6a9dd 100644 --- a/plugins/sync/src/main/java/info/nightscout/plugins/sync/nsShared/StoreDataForDbImpl.kt +++ b/plugins/sync/src/main/java/info/nightscout/plugins/sync/nsShared/StoreDataForDbImpl.kt @@ -26,6 +26,7 @@ import info.nightscout.database.impl.transactions.SyncNsBolusTransaction import info.nightscout.database.impl.transactions.SyncNsCarbsTransaction import info.nightscout.database.impl.transactions.SyncNsEffectiveProfileSwitchTransaction 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.SyncNsProfileSwitchTransaction import info.nightscout.database.impl.transactions.SyncNsTemporaryBasalTransaction @@ -92,11 +93,11 @@ class StoreDataForDbImpl @Inject constructor( override val temporaryBasals: MutableList = mutableListOf() override val profileSwitches: MutableList = mutableListOf() override val offlineEvents: MutableList = mutableListOf() + override val foods: MutableList = mutableListOf() override val nsIdGlucoseValues: MutableList = mutableListOf() override val nsIdBoluses: MutableList = mutableListOf() override val nsIdCarbs: MutableList = mutableListOf() - override val nsIdFoods: MutableList = mutableListOf() override val nsIdTemporaryTargets: MutableList = mutableListOf() override val nsIdEffectiveProfileSwitches: MutableList = mutableListOf() override val nsIdBolusCalculatorResults: MutableList = mutableListOf() @@ -106,6 +107,7 @@ class StoreDataForDbImpl @Inject constructor( override val nsIdProfileSwitches: MutableList = mutableListOf() override val nsIdOfflineEvents: MutableList = mutableListOf() override val nsIdDeviceStatuses: MutableList = mutableListOf() + override val nsIdFoods: MutableList = mutableListOf() private val userEntries: MutableList = 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 HashMap.inc(key: T) = if (containsKey(key)) merge(key, 1, Long::plus) else put(key, 1) @@ -171,6 +186,36 @@ class StoreDataForDbImpl @Inject constructor( 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() { rxBus.send(EventNSClientNewLog("PROCESSING TR", "")) diff --git a/plugins/sync/src/main/java/info/nightscout/plugins/sync/nsclient/services/NSClientService.kt b/plugins/sync/src/main/java/info/nightscout/plugins/sync/nsclient/services/NSClientService.kt index 5c4d1506d3..f3718fab22 100644 --- a/plugins/sync/src/main/java/info/nightscout/plugins/sync/nsclient/services/NSClientService.kt +++ b/plugins/sync/src/main/java/info/nightscout/plugins/sync/nsclient/services/NSClientService.kt @@ -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.workers.NSClientAddUpdateWorker 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.bus.RxBus import info.nightscout.rx.events.EventAppExit @@ -522,11 +522,14 @@ class NSClientService : DaggerService() { if (data.has("food")) { val foods = data.getJSONArray("food") if (foods.length() > 0) rxBus.send(EventNSClientNewLog("DATA", "received " + foods.length() + " foods")) - dataWorkerStorage.enqueue( - OneTimeWorkRequest.Builder(workerClasses.foodWorker) - .setInputData(dataWorkerStorage.storeInputData(foods)) - .build() - ) + dataWorkerStorage + .beginUniqueWork( + "ProcessFoods", + OneTimeWorkRequest.Builder(ProcessFoodWorker::class.java) + .setInputData(dataWorkerStorage.storeInputData(foods)) + .build() + ).then(OneTimeWorkRequest.Builder(StoreDataForDbImpl.StoreFoodWorker::class.java).build()) + .enqueue() } if (data.has("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) dataWorkerStorage .beginUniqueWork( - NSClientV3Plugin.JOB_NAME, + "ProcessBg", OneTimeWorkRequest.Builder(workerClasses.nsClientSourceWorker) .setInputData(dataWorkerStorage.storeInputData(sgvs)) .build() diff --git a/plugins/sync/src/main/java/info/nightscout/plugins/sync/nsclientV3/NSClientV3Plugin.kt b/plugins/sync/src/main/java/info/nightscout/plugins/sync/nsclientV3/NSClientV3Plugin.kt index 4d74f95373..5a6567541a 100644 --- a/plugins/sync/src/main/java/info/nightscout/plugins/sync/nsclientV3/NSClientV3Plugin.kt +++ b/plugins/sync/src/main/java/info/nightscout/plugins/sync/nsclientV3/NSClientV3Plugin.kt @@ -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.toNSCarbs 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.toNSTemporaryBasal import info.nightscout.plugins.sync.nsclientV3.extensions.toNSTemporaryTarget @@ -293,6 +294,7 @@ class NSClientV3Plugin @Inject constructor( when (collection) { NsClient.Collection.ENTRIES -> lastLoadedSrvModified.collections.entries == 0L NsClient.Collection.TREATMENTS -> lastLoadedSrvModified.collections.treatments == 0L + NsClient.Collection.FOODS -> lastLoadedSrvModified.collections.foods == 0L } override fun updateLatestBgReceivedIfNewer(latestReceived: Long) { @@ -321,35 +323,76 @@ class NSClientV3Plugin @Inject constructor( private val gson: Gson = GsonBuilder().create() private fun dbOperation(collection: String, dataPair: DataSyncSelector.DataPair, progress: String, operation: Operation) { - 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.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) + if (collection == "food") { + val call = when (operation) { + Operation.CREATE -> nsAndroidClient?.let { return@let it::createFood } + Operation.UPDATE -> nsAndroidClient?.let { return@let it::updateFood } } - // 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 { - if (collection == "treatments") { + when (dataPair) { + is DataSyncSelector.PairFood -> dataPair.value.toNSFood() + else -> null + }?.let { data -> + runBlocking { + try { + val id = if (dataPair.value is TraceableDBEntry) (dataPair.value as TraceableDBEntry).interfaceIDs.nightscoutId else "" + 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 { val id = if (dataPair.value is TraceableDBEntry) (dataPair.value as TraceableDBEntry).interfaceIDs.nightscoutId else "" rxBus.send( @@ -401,7 +444,6 @@ class NSClientV3Plugin @Inject constructor( } dataSyncSelector.confirmLastTempTargetsIdIfGreater(dataPair.id) } - // is DataSyncSelector.PairFood -> dataPair.value.toJson(false) // is DataSyncSelector.PairGlucoseValue -> dataPair.value.toJson(false, dateUtil) is DataSyncSelector.PairTherapyEvent -> { if (result.response == 201) { // created @@ -487,6 +529,7 @@ class NSClientV3Plugin @Inject constructor( .then(OneTimeWorkRequest.Builder(LoadBgWorker::class.java).build()) // Other Workers are enqueued after BG finish // LoadTreatmentsWorker + // LoadFoodsWorker // LoadDeviceStatusWorker // DataSyncWorker .enqueue() diff --git a/plugins/sync/src/main/java/info/nightscout/plugins/sync/nsclientV3/extensions/FoodExtension.kt b/plugins/sync/src/main/java/info/nightscout/plugins/sync/nsclientV3/extensions/FoodExtension.kt new file mode 100644 index 0000000000..2cd4ff466b --- /dev/null +++ b/plugins/sync/src/main/java/info/nightscout/plugins/sync/nsclientV3/extensions/FoodExtension.kt @@ -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, + ) diff --git a/plugins/sync/src/main/java/info/nightscout/plugins/sync/nsclientV3/workers/LoadFoodsWorker.kt b/plugins/sync/src/main/java/info/nightscout/plugins/sync/nsclientV3/workers/LoadFoodsWorker.kt new file mode 100644 index 0000000000..3daca3a700 --- /dev/null +++ b/plugins/sync/src/main/java/info/nightscout/plugins/sync/nsclientV3/workers/LoadFoodsWorker.kt @@ -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 = 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() + } +} \ No newline at end of file diff --git a/plugins/sync/src/main/java/info/nightscout/plugins/sync/nsclientV3/workers/LoadTreatmentsWorker.kt b/plugins/sync/src/main/java/info/nightscout/plugins/sync/nsclientV3/workers/LoadTreatmentsWorker.kt index 670f1e5e29..243a13f0b8 100644 --- a/plugins/sync/src/main/java/info/nightscout/plugins/sync/nsclientV3/workers/LoadTreatmentsWorker.kt +++ b/plugins/sync/src/main/java/info/nightscout/plugins/sync/nsclientV3/workers/LoadTreatmentsWorker.kt @@ -46,7 +46,8 @@ class LoadTreatmentsWorker( val treatments: List val response: NSAndroidClient.ReadResponse>? if (isFirstLoad) { - treatments = nsAndroidClient.getTreatmentsNewerThan(lastLoaded, 500) + val lastLoadedIso = dateUtil.toISOString(lastLoaded) + treatments = nsAndroidClient.getTreatmentsNewerThan(lastLoadedIso, 500) response = NSAndroidClient.ReadResponse(0, treatments) } else { @@ -75,14 +76,13 @@ class LoadTreatmentsWorker( nsClientV3Plugin.lastLoadedSrvModified.collections.treatments = lastLoaded nsClientV3Plugin.storeLastFetched() } - rxBus.send(EventNSClientNewLog("RCV END", "No TRs from ${dateUtil - .dateAndTimeAndSecondsString(lastLoaded)}")) + rxBus.send(EventNSClientNewLog("RCV END", "No TRs from ${dateUtil.dateAndTimeAndSecondsString(lastLoaded)}")) storeDataForDb.storeTreatmentsToDb() WorkManager.getInstance(context) .enqueueUniqueWork( NSClientV3Plugin.JOB_NAME, ExistingWorkPolicy.APPEND_OR_REPLACE, - OneTimeWorkRequest.Builder(LoadDeviceStatusWorker::class.java).build() + OneTimeWorkRequest.Builder(LoadFoodsWorker::class.java).build() ) } } catch (error: Exception) { @@ -95,14 +95,13 @@ class LoadTreatmentsWorker( nsClientV3Plugin.lastLoadedSrvModified.collections.treatments = lastLoaded nsClientV3Plugin.storeLastFetched() } - rxBus.send(EventNSClientNewLog("RCV END", "No new TRs from ${dateUtil - .dateAndTimeAndSecondsString(lastLoaded)}")) + rxBus.send(EventNSClientNewLog("RCV END", "No new TRs from ${dateUtil.dateAndTimeAndSecondsString(lastLoaded)}")) storeDataForDb.storeTreatmentsToDb() WorkManager.getInstance(context) .enqueueUniqueWork( NSClientV3Plugin.JOB_NAME, ExistingWorkPolicy.APPEND_OR_REPLACE, - OneTimeWorkRequest.Builder(LoadDeviceStatusWorker::class.java).build() + OneTimeWorkRequest.Builder(LoadFoodsWorker::class.java).build() ) } } diff --git a/plugins/sync/src/main/java/info/nightscout/plugins/sync/nsclientV3/workers/ProcessFoodWorker.kt b/plugins/sync/src/main/java/info/nightscout/plugins/sync/nsclientV3/workers/ProcessFoodWorker.kt new file mode 100644 index 0000000000..20baacaa1c --- /dev/null +++ b/plugins/sync/src/main/java/info/nightscout/plugins/sync/nsclientV3/workers/ProcessFoodWorker.kt @@ -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() + + 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 + } +} diff --git a/plugins/sync/src/test/java/info/nightscout/plugins/sync/nsclientV3/extensions/FoodExtensionKtTest.kt b/plugins/sync/src/test/java/info/nightscout/plugins/sync/nsclientV3/extensions/FoodExtensionKtTest.kt new file mode 100644 index 0000000000..db9ee6f1f1 --- /dev/null +++ b/plugins/sync/src/test/java/info/nightscout/plugins/sync/nsclientV3/extensions/FoodExtensionKtTest.kt @@ -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)) + } +} \ No newline at end of file