From 4fbe7d1000ce0092306c0f527c68fcb5e7ccbb61 Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Sun, 24 Sep 2017 22:47:18 +0200 Subject: [PATCH] food database support --- .../androidaps/Services/DataService.java | 22 +- .../androidaps/Services/Intents.java | 2 + .../androidaps/db/DatabaseHelper.java | 9 + .../info/nightscout/androidaps/db/Food.java | 107 +++++++++ .../nightscout/androidaps/db/FoodHelper.java | 205 ++++++++++++++++++ .../events/EventFoodDatabaseChanged.java | 8 + .../broadcasts/BroadcastFood.java | 104 +++++++++ .../services/NSClientService.java | 38 ++++ 8 files changed, 474 insertions(+), 21 deletions(-) create mode 100644 app/src/main/java/info/nightscout/androidaps/db/Food.java create mode 100644 app/src/main/java/info/nightscout/androidaps/db/FoodHelper.java create mode 100644 app/src/main/java/info/nightscout/androidaps/events/EventFoodDatabaseChanged.java create mode 100644 app/src/main/java/info/nightscout/androidaps/plugins/NSClientInternal/broadcasts/BroadcastFood.java diff --git a/app/src/main/java/info/nightscout/androidaps/Services/DataService.java b/app/src/main/java/info/nightscout/androidaps/Services/DataService.java index 05af04230d..dc52107c26 100644 --- a/app/src/main/java/info/nightscout/androidaps/Services/DataService.java +++ b/app/src/main/java/info/nightscout/androidaps/Services/DataService.java @@ -316,28 +316,8 @@ public class DataService extends IntentService { log.error("Unhandled exception", e); } } - if (intent.getAction().equals(Intents.ACTION_NEW_TREATMENT)) { - try { - if (bundles.containsKey("treatment")) { - String trstring = bundles.getString("treatment"); - handleAddChangeDataFromNS(trstring); - } - if (bundles.containsKey("treatments")) { - String trstring = bundles.getString("treatments"); - JSONArray jsonArray = new JSONArray(trstring); - for (int i = 0; i < jsonArray.length(); i++) { - JSONObject trJson = jsonArray.getJSONObject(i); - String trstr = trJson.toString(); - handleAddChangeDataFromNS(trstr); - } - } - } catch (Exception e) { - log.error("Unhandled exception", e); - } - } - - if (intent.getAction().equals(Intents.ACTION_CHANGED_TREATMENT)) { + if (intent.getAction().equals(Intents.ACTION_NEW_TREATMENT) || intent.getAction().equals(Intents.ACTION_CHANGED_TREATMENT)) { try { if (bundles.containsKey("treatment")) { String trstring = bundles.getString("treatment"); diff --git a/app/src/main/java/info/nightscout/androidaps/Services/Intents.java b/app/src/main/java/info/nightscout/androidaps/Services/Intents.java index 2f5f44afe3..5e2335862b 100644 --- a/app/src/main/java/info/nightscout/androidaps/Services/Intents.java +++ b/app/src/main/java/info/nightscout/androidaps/Services/Intents.java @@ -9,6 +9,8 @@ public interface Intents { String ACTION_NEW_SGV = "info.nightscout.client.NEW_SGV"; String ACTION_NEW_DEVICESTATUS = "info.nightscout.client.NEW_DEVICESTATUS"; String ACTION_NEW_FOOD = "info.nightscout.client.NEW_FOOD"; + String ACTION_CHANGED_FOOD = "info.nightscout.client.CHANGED_FOOD"; + String ACTION_REMOVED_FOOD = "info.nightscout.client.REMOVED_FOOD"; String ACTION_NEW_MBG = "info.nightscout.client.NEW_MBG"; String ACTION_NEW_CAL = "info.nightscout.client.NEW_CAL"; String ACTION_NEW_STATUS = "info.nightscout.client.NEW_STATUS"; diff --git a/app/src/main/java/info/nightscout/androidaps/db/DatabaseHelper.java b/app/src/main/java/info/nightscout/androidaps/db/DatabaseHelper.java index 55533aeb4e..70848cfe71 100644 --- a/app/src/main/java/info/nightscout/androidaps/db/DatabaseHelper.java +++ b/app/src/main/java/info/nightscout/androidaps/db/DatabaseHelper.java @@ -33,6 +33,7 @@ import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.events.EventCareportalEventChange; import info.nightscout.androidaps.events.EventExtendedBolusChange; +import info.nightscout.androidaps.events.EventFoodDatabaseChanged; import info.nightscout.androidaps.events.EventNewBG; import info.nightscout.androidaps.events.EventProfileSwitchChange; import info.nightscout.androidaps.events.EventRefreshOverview; @@ -59,6 +60,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { public static final String DATABASE_DBREQUESTS = "DBRequests"; public static final String DATABASE_CAREPORTALEVENTS = "CareportalEvents"; public static final String DATABASE_PROFILESWITCHES = "ProfileSwitches"; + public static final String DATABASE_FOODS = "Foods"; private static final int DATABASE_VERSION = 8; @@ -85,6 +87,8 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { private static final ScheduledExecutorService profileSwitchEventWorker = Executors.newSingleThreadScheduledExecutor(); private static ScheduledFuture scheduledProfileSwitchEventPost = null; + public FoodHelper foodHelper = new FoodHelper(this); + public DatabaseHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); onCreate(getWritableDatabase(), getConnectionSource()); @@ -104,6 +108,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { TableUtils.createTableIfNotExists(connectionSource, ExtendedBolus.class); TableUtils.createTableIfNotExists(connectionSource, CareportalEvent.class); TableUtils.createTableIfNotExists(connectionSource, ProfileSwitch.class); + TableUtils.createTableIfNotExists(connectionSource, Food.class); } catch (SQLException e) { log.error("Can't create database", e); throw new RuntimeException(e); @@ -128,6 +133,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { TableUtils.dropTable(connectionSource, ExtendedBolus.class, true); TableUtils.dropTable(connectionSource, CareportalEvent.class, true); TableUtils.dropTable(connectionSource, ProfileSwitch.class, true); + TableUtils.dropTable(connectionSource, Food.class, true); onCreate(database, connectionSource); } } catch (SQLException e) { @@ -205,6 +211,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { TableUtils.createTableIfNotExists(connectionSource, ExtendedBolus.class); TableUtils.createTableIfNotExists(connectionSource, CareportalEvent.class); TableUtils.createTableIfNotExists(connectionSource, ProfileSwitch.class); + foodHelper.resetFood(); updateEarliestDataChange(0); } catch (SQLException e) { log.error("Unhandled exception", e); @@ -217,6 +224,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { scheduleTemporaryTargetChange(); scheduleCareportalEventChange(); scheduleProfileSwitchChange(); + foodHelper.scheduleFoodChange(); new java.util.Timer().schedule( new java.util.TimerTask() { @Override @@ -1672,4 +1680,5 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { return null; } + // ---------------- Food handling --------------- } \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/db/Food.java b/app/src/main/java/info/nightscout/androidaps/db/Food.java new file mode 100644 index 0000000000..cb856df073 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/db/Food.java @@ -0,0 +1,107 @@ +package info.nightscout.androidaps.db; + +import com.j256.ormlite.field.DatabaseField; +import com.j256.ormlite.table.DatabaseTable; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Objects; + +/** + * Created by mike on 20.09.2017. + */ + + +@DatabaseTable(tableName = DatabaseHelper.DATABASE_FOODS) +public class Food { + private static Logger log = LoggerFactory.getLogger(Food.class); + + @DatabaseField(id = true) + public long key; + + @DatabaseField + public boolean isValid = true; + + @DatabaseField + public String _id; // NS _id + + @DatabaseField + public String name; + + @DatabaseField + public String category; + + @DatabaseField + public String subcategory; + + // Example: + // name="juice" portion=250 units="ml" carbs=12 + // means 250ml of juice has 12g of carbs + + @DatabaseField + public double portion; // common portion in "units" + + @DatabaseField + public int carbs; // in grams + + @DatabaseField + public int fat = 0; // in grams + + @DatabaseField + public int protein = 0; // in grams + + @DatabaseField + public int energy = 0; // in kJ + + @DatabaseField + public String units = "g"; + + @DatabaseField + public int gi; // not used yet + + public Food() { + key = System.currentTimeMillis(); + } + + public boolean isEqual(Food other) { + if (portion != other.portion) + return false; + if (carbs != other.carbs) + return false; + if (fat != other.fat) + return false; + if (protein != other.protein) + return false; + if (energy != other.energy) + return false; + if (gi != other.gi) + return false; + if (!Objects.equals(_id, other._id)) + return false; + if (!Objects.equals(name, other.name)) + return false; + if (!Objects.equals(category, other.category)) + return false; + if (!Objects.equals(subcategory, other.subcategory)) + return false; + if (!Objects.equals(units, other.units)) + return false; + return true; + } + + public void copyFrom(Food other) { + isValid = other.isValid; + _id = other._id; + name = other.name; + category = other.category; + subcategory = other.subcategory; + portion = other.portion; + carbs = other.carbs; + fat = other.fat; + protein = other.protein; + energy = other.energy; + units = other.units; + gi = other.gi; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/db/FoodHelper.java b/app/src/main/java/info/nightscout/androidaps/db/FoodHelper.java new file mode 100644 index 0000000000..fd5406f974 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/db/FoodHelper.java @@ -0,0 +1,205 @@ +package info.nightscout.androidaps.db; + +import com.j256.ormlite.android.AndroidConnectionSource; +import com.j256.ormlite.dao.Dao; +import com.j256.ormlite.stmt.PreparedQuery; +import com.j256.ormlite.stmt.QueryBuilder; +import com.j256.ormlite.stmt.Where; +import com.j256.ormlite.table.TableUtils; + +import org.json.JSONException; +import org.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.events.EventFoodDatabaseChanged; + +/** + * Created by mike on 24.09.2017. + */ + +public class FoodHelper { + private static Logger log = LoggerFactory.getLogger(FoodHelper.class); + + DatabaseHelper databaseHelper; + + private static final ScheduledExecutorService foodEventWorker = Executors.newSingleThreadScheduledExecutor(); + private static ScheduledFuture scheduledFoodEventPost = null; + + public FoodHelper(DatabaseHelper databaseHelper) { + this.databaseHelper = databaseHelper; + } + + private Dao getDaoFood() throws SQLException { + return databaseHelper.getDao(Food.class); + } + + public void resetFood() { + try { + TableUtils.dropTable(databaseHelper.getConnectionSource(), Food.class, true); + TableUtils.createTableIfNotExists(databaseHelper.getConnectionSource(), Food.class); + } catch (SQLException e) { + log.error("Unhandled exception", e); + } + scheduleFoodChange(); + } + + public List getFoodData() { + try { + Dao daoFood = getDaoFood(); + List foods; + QueryBuilder queryBuilder = daoFood.queryBuilder(); + PreparedQuery preparedQuery = queryBuilder.prepare(); + foods = daoFood.query(preparedQuery); + return foods; + } catch (SQLException e) { + log.error("Unhandled exception", e); + } + return new ArrayList<>(); + } + + public boolean createOrUpdate(Food food) { + try { + // find by NS _id + if (food._id != null) { + Food old; + + QueryBuilder queryBuilder = getDaoFood().queryBuilder(); + Where where = queryBuilder.where(); + where.eq("_id", food._id); + PreparedQuery preparedQuery = queryBuilder.prepare(); + List found = getDaoFood().query(preparedQuery); + if (found.size() > 0) { + old = found.get(0); + if (!old.isEqual(food)) { + getDaoFood().delete(old); // need to delete/create because date may change too + old.copyFrom(food); + getDaoFood().create(old); + log.debug("FOOD: Updating record by _id: " + old.toString()); + scheduleFoodChange(); + return true; + } + } + } + getDaoFood().createOrUpdate(food); + log.debug("FOOD: New record: " + food.toString()); + scheduleFoodChange(); + return true; + } catch (SQLException e) { + log.error("Unhandled exception", e); + } + return false; + } + + public void delete(Food food) { + try { + getDaoFood().delete(food); + scheduleFoodChange(); + } catch (SQLException e) { + log.error("Unhandled exception", e); + } + } + + public static void scheduleFoodChange() { + class PostRunnable implements Runnable { + public void run() { + log.debug("Firing EventFoodChange"); + MainApp.bus().post(new EventFoodDatabaseChanged()); + scheduledFoodEventPost = null; + } + } + // prepare task for execution in 1 sec + // cancel waiting task to prevent sending multiple posts + if (scheduledFoodEventPost != null) + scheduledFoodEventPost.cancel(false); + Runnable task = new PostRunnable(); + final int sec = 1; + scheduledFoodEventPost = foodEventWorker.schedule(task, sec, TimeUnit.SECONDS); + + } + + /* + { + "_id": "551ee3ad368e06e80856e6a9", + "type": "food", + "category": "Zakladni", + "subcategory": "Napoje", + "name": "Mleko", + "portion": 250, + "carbs": 12, + "gi": 1, + "created_at": "2015-04-14T06:59:16.500Z", + "unit": "ml" + } + */ + public void createFoodFromJsonIfNotExists(JSONObject trJson) { + try { + Food food = new Food(); + if (trJson.has("type") && trJson.getString("type").equals("food")) { + if (trJson.has("_id")) + food._id = trJson.getString("_id"); + if (trJson.has("category")) + food.category = trJson.getString("category"); + if (trJson.has("subcategory")) + food.subcategory = trJson.getString("subcategory"); + if (trJson.has("name")) + food.name = trJson.getString("name"); + if (trJson.has("unit")) + food.units = trJson.getString("unit"); + if (trJson.has("portion")) + food.portion = trJson.getDouble("portion"); + if (trJson.has("carbs")) + food.carbs = trJson.getInt("carbs"); + if (trJson.has("gi")) + food.gi = trJson.getInt("gi"); + if (trJson.has("energy")) + food.energy = trJson.getInt("energy"); + if (trJson.has("protein")) + food.protein = trJson.getInt("protein"); + if (trJson.has("fat")) + food.fat = trJson.getInt("fat"); + } + createOrUpdate(food); + } catch (JSONException e) { + log.error("Unhandled exception", e); + } + } + + public void deleteFoodById(String _id) { + Food stored = findFoodById(_id); + if (stored != null) { + log.debug("FOOD: Removing Food record from database: " + stored.toString()); + delete(stored); + scheduleFoodChange(); + } + } + + public Food findFoodById(String _id) { + try { + QueryBuilder queryBuilder = getDaoFood().queryBuilder(); + Where where = queryBuilder.where(); + where.eq("_id", _id); + PreparedQuery preparedQuery = queryBuilder.prepare(); + List list = getDaoFood().query(preparedQuery); + + if (list.size() == 1) { + return list.get(0); + } else { + return null; + } + } catch (SQLException e) { + log.error("Unhandled exception", e); + } + return null; + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/events/EventFoodDatabaseChanged.java b/app/src/main/java/info/nightscout/androidaps/events/EventFoodDatabaseChanged.java new file mode 100644 index 0000000000..075993c530 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/events/EventFoodDatabaseChanged.java @@ -0,0 +1,8 @@ +package info.nightscout.androidaps.events; + +/** + * Created by mike on 20.09.2017. + */ + +public class EventFoodDatabaseChanged { +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/NSClientInternal/broadcasts/BroadcastFood.java b/app/src/main/java/info/nightscout/androidaps/plugins/NSClientInternal/broadcasts/BroadcastFood.java new file mode 100644 index 0000000000..dbb3385fbb --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/NSClientInternal/broadcasts/BroadcastFood.java @@ -0,0 +1,104 @@ +package info.nightscout.androidaps.plugins.NSClientInternal.broadcasts; + +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.support.v4.content.LocalBroadcastManager; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; + +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.Services.Intents; +import info.nightscout.androidaps.plugins.NSClientInternal.data.NSTreatment; +import info.nightscout.utils.SP; + +/** + * Created by mike on 20.02.2016. + */ +public class BroadcastFood { + private static Logger log = LoggerFactory.getLogger(BroadcastFood.class); + + public static void handleNewFood(JSONArray foods, Context context, boolean isDelta) { + + List splitted = BroadcastTreatment.splitArray(foods); + for (JSONArray part : splitted) { + Bundle bundle = new Bundle(); + bundle.putString("foods", part.toString()); + bundle.putBoolean("delta", isDelta); + Intent intent = new Intent(Intents.ACTION_NEW_FOOD); + intent.putExtras(bundle); + intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES); + LocalBroadcastManager.getInstance(MainApp.instance()).sendBroadcast(intent); + } + + if (SP.getBoolean(R.string.key_nsclient_localbroadcasts, true)) { + for (JSONArray part : splitted) { + Bundle bundle = new Bundle(); + bundle.putString("foods", part.toString()); + bundle.putBoolean("delta", isDelta); + Intent intent = new Intent(Intents.ACTION_NEW_FOOD); + intent.putExtras(bundle); + intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES); + context.sendBroadcast(intent); + } + } + } + + public static void handleChangedFood(JSONArray foods, Context context, boolean isDelta) { + + List splitted = BroadcastTreatment.splitArray(foods); + for (JSONArray part : splitted) { + Bundle bundle = new Bundle(); + bundle.putString("foods", part.toString()); + bundle.putBoolean("delta", isDelta); + Intent intent = new Intent(Intents.ACTION_CHANGED_FOOD); + intent.putExtras(bundle); + intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES); + LocalBroadcastManager.getInstance(MainApp.instance()).sendBroadcast(intent); + } + + if (SP.getBoolean(R.string.key_nsclient_localbroadcasts, true)) { + for (JSONArray part : splitted) { + Bundle bundle = new Bundle(); + bundle.putString("foods", part.toString()); + bundle.putBoolean("delta", isDelta); + Intent intent = new Intent(Intents.ACTION_CHANGED_FOOD); + intent.putExtras(bundle); + intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES); + context.sendBroadcast(intent); + } + } + } + + public static void handleRemovedFood(JSONArray foods, Context context, boolean isDelta) { + + Bundle bundle = new Bundle(); + bundle.putString("foods", foods.toString()); + bundle.putBoolean("delta", isDelta); + Intent intent = new Intent(Intents.ACTION_REMOVED_FOOD); + intent.putExtras(bundle); + intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES); + LocalBroadcastManager.getInstance(MainApp.instance()).sendBroadcast(intent); + + + if (SP.getBoolean(R.string.key_nsclient_localbroadcasts, true)) { + bundle = new Bundle(); + bundle.putString("foods", foods.toString()); + bundle.putBoolean("delta", isDelta); + intent = new Intent(Intents.ACTION_REMOVED_FOOD); + intent.putExtras(bundle); + intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES); + context.sendBroadcast(intent); + } + } + + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/NSClientInternal/services/NSClientService.java b/app/src/main/java/info/nightscout/androidaps/plugins/NSClientInternal/services/NSClientService.java index 1cc4d394bc..3fd608475f 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/NSClientInternal/services/NSClientService.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/NSClientInternal/services/NSClientService.java @@ -43,6 +43,7 @@ import info.nightscout.androidaps.plugins.NSClientInternal.broadcasts.BroadcastA import info.nightscout.androidaps.plugins.NSClientInternal.broadcasts.BroadcastCals; import info.nightscout.androidaps.plugins.NSClientInternal.broadcasts.BroadcastClearAlarm; import info.nightscout.androidaps.plugins.NSClientInternal.broadcasts.BroadcastDeviceStatus; +import info.nightscout.androidaps.plugins.NSClientInternal.broadcasts.BroadcastFood; import info.nightscout.androidaps.plugins.NSClientInternal.broadcasts.BroadcastMbgs; import info.nightscout.androidaps.plugins.NSClientInternal.broadcasts.BroadcastProfile; import info.nightscout.androidaps.plugins.NSClientInternal.broadcasts.BroadcastSgvs; @@ -520,6 +521,43 @@ public class NSClientService extends Service { } } if (data.has("food")) { + JSONArray foods = data.getJSONArray("food"); + JSONArray removedFoods = new JSONArray(); + JSONArray updatedFoods = new JSONArray(); + JSONArray addedFoods = new JSONArray(); + if (foods.length() > 0) + MainApp.bus().post(new EventNSClientNewLog("DATA", "received " + foods.length() + " foods")); + for (Integer index = 0; index < foods.length(); index++) { + JSONObject jsonFood = foods.getJSONObject(index); + NSTreatment treatment = new NSTreatment(jsonFood); + + // remove from upload queue if Ack is failing + UploadQueue.removeID(jsonFood); + //Find latest date in treatment + if (treatment.getMills() != null && treatment.getMills() < System.currentTimeMillis()) + if (treatment.getMills() > latestDateInReceivedData) + latestDateInReceivedData = treatment.getMills(); + + if (treatment.getAction() == null) { + addedFoods.put(jsonFood); + } else if (treatment.getAction().equals("update")) { + updatedFoods.put(jsonFood); + } else if (treatment.getAction().equals("remove")) { + if (treatment.getMills() != null && treatment.getMills() > System.currentTimeMillis() - 24 * 60 * 60 * 1000L) // handle 1 day old deletions only + removedFoods.put(jsonFood); + } + } + if (removedFoods.length() > 0) { + BroadcastFood.handleRemovedFood(removedFoods, MainApp.instance().getApplicationContext(), isDelta); + } + if (updatedFoods.length() > 0) { + BroadcastFood.handleChangedFood(updatedFoods, MainApp.instance().getApplicationContext(), isDelta); + } + if (addedFoods.length() > 0) { + BroadcastFood.handleNewFood(addedFoods, MainApp.instance().getApplicationContext(), isDelta); + } + } + if (data.has("")) { JSONArray foods = data.getJSONArray("food"); if (foods.length() > 0) { MainApp.bus().post(new EventNSClientNewLog("DATA", "received " + foods.length() + " foods"));