From 147448afca654b31be46c053b0888944384900b3 Mon Sep 17 00:00:00 2001 From: "Markus M. May" Date: Sun, 7 Jan 2018 20:24:42 +0100 Subject: [PATCH] #557 - Encapsulate food plugin --- .../androidaps/Services/DataService.java | 61 +-- .../androidaps/db/DataServiceManager.java | 25 -- .../androidaps/db/DatabaseHelper.java | 29 +- .../nightscout/androidaps/db/FoodDao.java | 126 ------ .../nightscout/androidaps/db/FoodService.java | 179 --------- .../androidaps/events/NsFoodEvent.java | 35 ++ .../androidaps/{db => plugins/Food}/Food.java | 4 +- .../androidaps/plugins/Food/FoodFragment.java | 10 +- .../androidaps/plugins/Food/FoodPlugin.java | 9 + .../androidaps/plugins/Food/FoodService.java | 360 ++++++++++++++++++ 10 files changed, 433 insertions(+), 405 deletions(-) delete mode 100644 app/src/main/java/info/nightscout/androidaps/db/DataServiceManager.java delete mode 100644 app/src/main/java/info/nightscout/androidaps/db/FoodDao.java delete mode 100644 app/src/main/java/info/nightscout/androidaps/db/FoodService.java create mode 100644 app/src/main/java/info/nightscout/androidaps/events/NsFoodEvent.java rename app/src/main/java/info/nightscout/androidaps/{db => plugins/Food}/Food.java (97%) create mode 100644 app/src/main/java/info/nightscout/androidaps/plugins/Food/FoodService.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 f6787283c8..ab0b6414fb 100644 --- a/app/src/main/java/info/nightscout/androidaps/Services/DataService.java +++ b/app/src/main/java/info/nightscout/androidaps/Services/DataService.java @@ -12,16 +12,14 @@ import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.sql.SQLException; - import info.nightscout.androidaps.Config; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.data.ProfileStore; import info.nightscout.androidaps.db.BgReading; import info.nightscout.androidaps.db.CareportalEvent; -import info.nightscout.androidaps.db.DataServiceManager; import info.nightscout.androidaps.events.EventNewBasalProfile; +import info.nightscout.androidaps.events.NsFoodEvent; import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; import info.nightscout.androidaps.plugins.ConstraintsObjectives.ObjectivesPlugin; import info.nightscout.androidaps.plugins.NSClientInternal.data.NSDeviceStatus; @@ -477,62 +475,19 @@ public class DataService extends IntentService { } } - if (intent.getAction().equals(Intents.ACTION_NEW_FOOD) || intent.getAction().equals(Intents.ACTION_CHANGED_FOOD)) { - try { - if (bundles.containsKey("food")) { - String trstring = bundles.getString("food"); - handleAddChangeFoodRecord(new JSONObject(trstring)); - } - if (bundles.containsKey("foods")) { - String trstring = bundles.getString("foods"); - JSONArray jsonArray = new JSONArray(trstring); - for (int i = 0; i < jsonArray.length(); i++) { - JSONObject trJson = jsonArray.getJSONObject(i); - handleAddChangeFoodRecord(trJson); - } - } - } catch (Exception e) { - log.error("Unhandled exception", e); - } + if (intent.getAction().equals(Intents.ACTION_NEW_FOOD) + || intent.getAction().equals(Intents.ACTION_CHANGED_FOOD)) { + int mode = Intents.ACTION_NEW_FOOD.equals(intent.getAction()) ? NsFoodEvent.ADD : NsFoodEvent.UPDATE; + NsFoodEvent evt = new NsFoodEvent(mode, bundles); + MainApp.bus().post(evt); } if (intent.getAction().equals(Intents.ACTION_REMOVED_FOOD)) { - try { - if (bundles.containsKey("food")) { - String trstring = bundles.getString("food"); - JSONObject trJson = new JSONObject(trstring); - String _id = trJson.getString("_id"); - handleRemovedFoodRecord(_id); - } - - if (bundles.containsKey("foods")) { - String trstring = bundles.getString("foods"); - JSONArray jsonArray = new JSONArray(trstring); - for (int i = 0; i < jsonArray.length(); i++) { - JSONObject trJson = jsonArray.getJSONObject(i); - String _id = trJson.getString("_id"); - handleRemovedFoodRecord(_id); - } - } - } catch (Exception e) { - log.error("Unhandled exception", e); - } + NsFoodEvent evt = new NsFoodEvent(NsFoodEvent.REMOVE, bundles); + MainApp.bus().post(evt); } } - private void handleRemovedFoodRecord(String _id) { - - try { - DataServiceManager.getInstance().getFoodService().deleteByNSId(_id); - } catch (SQLException e) { - log.error("Unhandled exception", e); - } - } - - public void handleAddChangeFoodRecord(JSONObject trJson) throws JSONException { - DataServiceManager.getInstance().getFoodService().createFoodFromJsonIfNotExists(trJson); - } - private void handleRemovedRecordFromNS(String _id) { MainApp.getDbHelper().deleteTreatmentById(_id); MainApp.getDbHelper().deleteTempTargetById(_id); diff --git a/app/src/main/java/info/nightscout/androidaps/db/DataServiceManager.java b/app/src/main/java/info/nightscout/androidaps/db/DataServiceManager.java deleted file mode 100644 index dc4d71b98d..0000000000 --- a/app/src/main/java/info/nightscout/androidaps/db/DataServiceManager.java +++ /dev/null @@ -1,25 +0,0 @@ -package info.nightscout.androidaps.db; - -/** - * This class should get registered in the MainApp. - */ - -public class DataServiceManager { - - private static final DataServiceManager INSTANCE = new DataServiceManager(); - - private FoodService foodService; - - public static DataServiceManager getInstance() { - return INSTANCE; - } - - public FoodService getFoodService() { - if (this.foodService == null) { - this.foodService = new FoodService(); - } - - return foodService; - } - -} 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 871c37f66d..aabefb1307 100644 --- a/app/src/main/java/info/nightscout/androidaps/db/DatabaseHelper.java +++ b/app/src/main/java/info/nightscout/androidaps/db/DatabaseHelper.java @@ -8,6 +8,7 @@ import android.support.annotation.Nullable; import com.j256.ormlite.android.apptools.OrmLiteSqliteOpenHelper; import com.j256.ormlite.dao.CloseableIterator; import com.j256.ormlite.dao.Dao; +import com.j256.ormlite.dao.DaoManager; import com.j256.ormlite.stmt.PreparedQuery; import com.j256.ormlite.stmt.QueryBuilder; import com.j256.ormlite.stmt.Where; @@ -101,6 +102,9 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { private static final ScheduledExecutorService profileSwitchEventWorker = Executors.newSingleThreadScheduledExecutor(); private static ScheduledFuture scheduledProfileSwitchEventPost = null; + private int oldVersion = 0; + private int newVersion = 0; + public DatabaseHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); onCreate(getWritableDatabase(), getConnectionSource()); @@ -120,7 +124,6 @@ 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); @@ -130,6 +133,9 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { @Override public void onUpgrade(SQLiteDatabase database, ConnectionSource connectionSource, int oldVersion, int newVersion) { try { + this.oldVersion = oldVersion; + this.newVersion = newVersion; + if (oldVersion == 7 && newVersion == 8) { log.debug("Upgrading database from v7 to v8"); TableUtils.dropTable(connectionSource, Treatment.class, true); @@ -145,7 +151,6 @@ 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) { @@ -154,6 +159,14 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { } } + public int getOldVersion() { + return oldVersion; + } + + public int getNewVersion() { + return newVersion; + } + /** * Close the database connections and clear any cached DAOs. */ @@ -223,7 +236,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { TableUtils.createTableIfNotExists(connectionSource, ExtendedBolus.class); TableUtils.createTableIfNotExists(connectionSource, CareportalEvent.class); TableUtils.createTableIfNotExists(connectionSource, ProfileSwitch.class); - resetFood(); +// resetFood(); updateEarliestDataChange(0); } catch (SQLException e) { log.error("Unhandled exception", e); @@ -311,16 +324,6 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { scheduleProfileSwitchChange(); } - public void resetFood() { - try { - TableUtils.dropTable(this.getConnectionSource(), Food.class, true); - TableUtils.createTableIfNotExists(this.getConnectionSource(), Food.class); - } catch (SQLException e) { - log.error("Unhandled exception", e); - } - } - - // ------------------ getDao ------------------------------------------- diff --git a/app/src/main/java/info/nightscout/androidaps/db/FoodDao.java b/app/src/main/java/info/nightscout/androidaps/db/FoodDao.java deleted file mode 100644 index b3cefaa417..0000000000 --- a/app/src/main/java/info/nightscout/androidaps/db/FoodDao.java +++ /dev/null @@ -1,126 +0,0 @@ -package info.nightscout.androidaps.db; - -import android.content.Context; - -import com.j256.ormlite.android.apptools.OpenHelperManager; -import com.j256.ormlite.dao.BaseDaoImpl; -import com.j256.ormlite.dao.DaoManager; -import com.j256.ormlite.stmt.PreparedQuery; -import com.j256.ormlite.stmt.QueryBuilder; -import com.j256.ormlite.support.ConnectionSource; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.List; - -/** - * Created by triplem on 04.01.18. - */ - -public class FoodDao extends BaseDaoImpl { - - private static final Logger log = LoggerFactory.getLogger(FoodDao.class); - - public FoodDao(ConnectionSource source) throws SQLException { - super(source, Food.class); - } - - /** - * Static instantiation methods. The database connection is accessed via - * the OpenHelperManager which keeps a count of the number of objects - * using the connection. Thus every call to connect() must be matched by - * a call to release() once the session is done. - */ - public static FoodDao connect(Context context) { - return with(OpenHelperManager.getHelper(context, DatabaseHelper.class) - .getConnectionSource()); - } - - public static FoodDao with(ConnectionSource connection) { - try { - return (FoodDao) DaoManager.createDao(connection, Food.class); - } catch (SQLException e) { - throw new RuntimeException(e); - } - } - - /** - * Releasing the DAO flags the connection manager that the DAO is no - * longer using the connection. When the connection count is zero, the - * connection manager will close the database. - */ - public void release() { - OpenHelperManager.releaseHelper(); - } - - /** - * - * @return - * - * @deprecated should use queryForAll instead, which is a standard method of the ORMLite DAO - */ - public List getFoodData() { - try { - QueryBuilder queryBuilder = this.queryBuilder(); - PreparedQuery preparedQuery = queryBuilder.prepare(); - return this.query(preparedQuery); - } catch (SQLException e) { - log.error("Unhandled exception", e); - } - - return new ArrayList<>(); - } - - public boolean createOrUpdateByNS(Food food) { - try { - // find by NS _id - if (food._id != null) { - Food old = this.findByNSId(food._id); - - if (old != null) { - if (!old.isEqual(food)) { - this.delete(old); // need to delete/create because date may change too - old.copyFrom(food); - this.create(old); - log.debug("FOOD: Updating record by _id: " + old.toString()); - return true; - } else { - return false; - } - } - } - this.createOrUpdate(food); - log.debug("FOOD: New record: " + food.toString()); - return true; - } catch (SQLException e) { - log.error("Unhandled exception", e); - } - return false; - } - - /** - * finds food by its NS Id. - * - * @param _id - * @return - */ - public Food findByNSId(String _id) { - try { - List list = this.queryForEq("_id", _id); - - if (list.size() == 1) { // really? if there are more then one result, then we do not return anything... - return list.get(0); - } - } catch (SQLException e) { - log.error("Unhandled exception", e); - } - return null; - } - - - -} - diff --git a/app/src/main/java/info/nightscout/androidaps/db/FoodService.java b/app/src/main/java/info/nightscout/androidaps/db/FoodService.java deleted file mode 100644 index 56fd4b3a4f..0000000000 --- a/app/src/main/java/info/nightscout/androidaps/db/FoodService.java +++ /dev/null @@ -1,179 +0,0 @@ -package info.nightscout.androidaps.db; - -import android.content.Intent; -import android.os.IBinder; -import android.support.annotation.Nullable; - -import com.j256.ormlite.android.apptools.OrmLiteBaseService; - -import org.json.JSONException; -import org.json.JSONObject; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.sql.SQLException; -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.Event; -import info.nightscout.androidaps.events.EventFoodDatabaseChanged; - -/** - * Created by mike on 24.09.2017. - */ - -public class FoodService extends OrmLiteBaseService { - private static Logger log = LoggerFactory.getLogger(FoodService.class); - - private static final ScheduledExecutorService foodEventWorker = Executors.newSingleThreadScheduledExecutor(); - private static ScheduledFuture scheduledFoodEventPost = null; - - public FoodService() { - this.onCreate(); - } - - public FoodDao getDao() { - return FoodDao.with(this.getConnectionSource()); - } - - /** - * This service method is just taking care about the Food-Table, - * a central dataService should be use for throwing events for all - * tables. - */ - public void resetFood() { - this.getHelper().resetFood(); - scheduleFoodChange(); - } - - /** - * A place to centrally register events to be posted, if any data changed. - * This should be implemented in an abstract service-class. - * - * We do need to make sure, that ICallback is extended to be able to handle multiple - * events, or handle a list of events. - * - * on some methods the earliestDataChange event is handled separatly, in that it is checked if it is - * set to null by another event already (eg. scheduleExtendedBolusChange). - * - * @param event - * @param eventWorker - * @param callback - */ - private void scheduleEvent(final Event event, ScheduledExecutorService eventWorker, - final ICallback callback) { - - class PostRunnable implements Runnable { - public void run() { - log.debug("Firing EventFoodChange"); - MainApp.bus().post(event); - callback.setPost(null); - } - } - // prepare task for execution in 1 sec - // cancel waiting task to prevent sending multiple posts - if (callback.getPost() != null) - callback.getPost().cancel(false); - Runnable task = new PostRunnable(); - final int sec = 1; - callback.setPost(eventWorker.schedule(task, sec, TimeUnit.SECONDS)); - } - - /** - * Schedule a foodChange Event. - */ - public void scheduleFoodChange() { - this.scheduleEvent(new EventFoodDatabaseChanged(), foodEventWorker, new ICallback() { - @Override - public void setPost(ScheduledFuture post) { - scheduledFoodEventPost = post; - } - - @Override - public ScheduledFuture getPost() { - return scheduledFoodEventPost; - } - }); - } - - public List getFoodData() { - return this.getDao().getFoodData(); - } - - /* - { - "_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 = Food.createFromJson(trJson); - this.getDao().createOrUpdate(food); - } catch (JSONException | SQLException e) { - log.error("Unhandled exception", e); - } - } - - /** - * Create of update a food record by the NS (Nightscout) Id. - * - * @param food - * @return - */ - public boolean createOrUpdateByNS(Food food) { - boolean result = this.getDao().createOrUpdateByNS(food); - if (result) this.scheduleFoodChange(); - - return result; - } - - /** - * deletes an entry by its NS Id. - * - * Basically a convenience method for findByNSId and delete. - * - * @param _id - */ - public void deleteByNSId(String _id) throws SQLException { - Food stored = this.getDao().findByNSId(_id); - if (stored != null) { - log.debug("FOOD: Removing Food record from database: " + stored.toString()); - this.delete(stored); - } - } - - /** - * deletes the food and sends the foodChange Event - * - * should be moved ot a Service - * - * @param food - */ - public void delete(Food food) { - try { - this.getDao().delete(food); - this.scheduleFoodChange(); - } catch (SQLException e) { - log.error("Unhandled exception", e); - } - } - - @Nullable - @Override - public IBinder onBind(Intent intent) { - return null; - } -} diff --git a/app/src/main/java/info/nightscout/androidaps/events/NsFoodEvent.java b/app/src/main/java/info/nightscout/androidaps/events/NsFoodEvent.java new file mode 100644 index 0000000000..ff3b1e78a7 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/events/NsFoodEvent.java @@ -0,0 +1,35 @@ +package info.nightscout.androidaps.events; + +import android.os.Bundle; + +/** + * Event which is published with data fetched from NightScout specific for the + * Food-class. + * + * Payload is the from NS retrieved JSON-String which should be handled by all + * subscriber. + */ + +public class NsFoodEvent extends Event { + + public static final int ADD = 0; + public static final int UPDATE = 1; + public static final int REMOVE = 2; + + private final int mode; + + private final Bundle payload; + + public NsFoodEvent(int mode, Bundle payload) { + this.mode = mode; + this.payload = payload; + } + + public int getMode() { + return mode; + } + + public Bundle getPayload() { + return payload; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/db/Food.java b/app/src/main/java/info/nightscout/androidaps/plugins/Food/Food.java similarity index 97% rename from app/src/main/java/info/nightscout/androidaps/db/Food.java rename to app/src/main/java/info/nightscout/androidaps/plugins/Food/Food.java index 9824d1964b..9b38dbcbe9 100644 --- a/app/src/main/java/info/nightscout/androidaps/db/Food.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Food/Food.java @@ -1,4 +1,4 @@ -package info.nightscout.androidaps.db; +package info.nightscout.androidaps.plugins.Food; import com.j256.ormlite.field.DatabaseField; import com.j256.ormlite.table.DatabaseTable; @@ -16,7 +16,7 @@ import info.nightscout.utils.JsonHelper; * Created by mike on 20.09.2017. */ -@DatabaseTable(tableName = Food.TABLE_FOODS, daoClass=FoodDao.class) +@DatabaseTable(tableName = Food.TABLE_FOODS) public class Food { private static Logger log = LoggerFactory.getLogger(Food.class); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Food/FoodFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/Food/FoodFragment.java index bd3cb9fe6c..2a3bf0788a 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Food/FoodFragment.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Food/FoodFragment.java @@ -19,10 +19,8 @@ import android.widget.ImageView; import android.widget.TextView; import com.crashlytics.android.Crashlytics; -import com.google.common.util.concurrent.ServiceManager; import com.squareup.otto.Subscribe; -import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -33,8 +31,6 @@ import java.util.Set; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; -import info.nightscout.androidaps.db.DataServiceManager; -import info.nightscout.androidaps.db.Food; import info.nightscout.androidaps.events.EventFoodDatabaseChanged; import info.nightscout.androidaps.plugins.Common.SubscriberFragment; import info.nightscout.utils.NSUpload; @@ -125,7 +121,7 @@ public class FoodFragment extends SubscriberFragment { } }); - RecyclerViewAdapter adapter = new RecyclerViewAdapter(DataServiceManager.getInstance().getFoodService().getFoodData()); + RecyclerViewAdapter adapter = new RecyclerViewAdapter(MainApp.getSpecificPlugin(FoodPlugin.class).getService().getFoodData()); recyclerView.setAdapter(adapter); loadData(); @@ -148,7 +144,7 @@ public class FoodFragment extends SubscriberFragment { } void loadData() { - unfiltered = DataServiceManager.getInstance().getFoodService().getFoodData(); + unfiltered = MainApp.getSpecificPlugin(FoodPlugin.class).getService().getFoodData(); } void fillCategories() { @@ -302,7 +298,7 @@ public class FoodFragment extends SubscriberFragment { if (_id != null && !_id.equals("")) { NSUpload.removeFoodFromNS(_id); } - DataServiceManager.getInstance().getFoodService().delete(food); + MainApp.getSpecificPlugin(FoodPlugin.class).getService().delete(food); } }); builder.setNegativeButton(MainApp.sResources.getString(R.string.cancel), null); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Food/FoodPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/Food/FoodPlugin.java index 255b4fd951..64517a19a5 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Food/FoodPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Food/FoodPlugin.java @@ -13,6 +13,12 @@ public class FoodPlugin implements PluginBase { private static FoodPlugin plugin = null; + private FoodService service; + + private FoodPlugin() { + this.service = new FoodService(); + } + public static FoodPlugin getPlugin() { if (plugin == null) plugin = new FoodPlugin(); @@ -81,5 +87,8 @@ public class FoodPlugin implements PluginBase { return -1; } + public FoodService getService() { + return this.service; + } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Food/FoodService.java b/app/src/main/java/info/nightscout/androidaps/plugins/Food/FoodService.java new file mode 100644 index 0000000000..038363f972 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Food/FoodService.java @@ -0,0 +1,360 @@ +package info.nightscout.androidaps.plugins.Food; + +import android.content.Intent; +import android.os.Bundle; +import android.os.IBinder; +import android.support.annotation.Nullable; + +import com.j256.ormlite.android.apptools.OpenHelperManager; +import com.j256.ormlite.android.apptools.OrmLiteBaseService; +import com.j256.ormlite.dao.Dao; +import com.j256.ormlite.dao.DaoManager; +import com.j256.ormlite.support.ConnectionSource; +import com.j256.ormlite.table.TableUtils; +import com.squareup.otto.Subscribe; + +import org.json.JSONArray; +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.db.DatabaseHelper; +import info.nightscout.androidaps.db.ICallback; +import info.nightscout.androidaps.events.Event; +import info.nightscout.androidaps.events.EventFoodDatabaseChanged; +import info.nightscout.androidaps.events.NsFoodEvent; + +/** + * Created by mike on 24.09.2017. + */ + +public class FoodService extends OrmLiteBaseService { + private static Logger log = LoggerFactory.getLogger(FoodService.class); + + private static final ScheduledExecutorService foodEventWorker = Executors.newSingleThreadScheduledExecutor(); + private static ScheduledFuture scheduledFoodEventPost = null; + + public FoodService() { + onCreate(); + dbInitialize(); + MainApp.bus().register(this); + } + + /** + * This method is a simple re-implementation of the database create and up/downgrade functionality + * in SQLiteOpenHelper#getDatabaseLocked method. + * + * It is implemented to be able to late initialize separate plugins of the application. + */ + protected void dbInitialize() { + DatabaseHelper helper = OpenHelperManager.getHelper(this, DatabaseHelper.class); + int newVersion = helper.getNewVersion(); + int oldVersion = helper.getOldVersion(); + + if (oldVersion > newVersion) { + onDowngrade(this.getConnectionSource(), oldVersion, newVersion); + } else { + onUpgrade(this.getConnectionSource(), oldVersion, newVersion); + } + } + + public Dao getDao() { + try { + return DaoManager.createDao(this.getConnectionSource(), Food.class); + } catch (SQLException e) { + log.error("Cannot create Dao for Food.class"); + } + + return null; + } + + @Subscribe + public void handleNsEvent(NsFoodEvent event) { + int mode = event.getMode(); + Bundle payload = event.getPayload(); + + try { + if (payload.containsKey("food")) { + JSONObject json = new JSONObject(payload.getString("food")); + if (mode == NsFoodEvent.ADD || mode == NsFoodEvent.UPDATE) { + this.createFoodFromJsonIfNotExists(json); + } else { + this.deleteNS(json); + } + } + + if (payload.containsKey("foods")) { + JSONArray array = new JSONArray(payload.getString("foods")); + if (mode == NsFoodEvent.ADD || mode == NsFoodEvent.UPDATE) { + this.createFoodFromJsonIfNotExists(array); + } else { + this.deleteNS(array); + } + } + } catch (JSONException e) { + log.error("Unhandled Exception", e); + } + } + + @Override + public void onCreate() { + super.onCreate(); + try { + log.info("onCreate"); + TableUtils.createTableIfNotExists(this.getConnectionSource(), Food.class); + } catch (SQLException e) { + log.error("Can't create database", e); + throw new RuntimeException(e); + } + } + + public void onUpgrade(ConnectionSource connectionSource, int oldVersion, int newVersion) { + if (oldVersion == 7 && newVersion == 8) { + log.debug("Upgrading database from v7 to v8"); + } else { + log.info("onUpgrade"); +// this.resetFood(); + } + } + + public void onDowngrade(ConnectionSource connectionSource, int oldVersion, int newVersion) { + // this method is not supported right now + } + + public void resetFood() { + try { + TableUtils.dropTable(this.getConnectionSource(), Food.class, true); + TableUtils.createTableIfNotExists(this.getConnectionSource(), Food.class); + } catch (SQLException e) { + log.error("Unhandled exception", e); + } + scheduleFoodChange(); + } + + + /** + * A place to centrally register events to be posted, if any data changed. + * This should be implemented in an abstract service-class. + * + * We do need to make sure, that ICallback is extended to be able to handle multiple + * events, or handle a list of events. + * + * on some methods the earliestDataChange event is handled separatly, in that it is checked if it is + * set to null by another event already (eg. scheduleExtendedBolusChange). + * + * @param event + * @param eventWorker + * @param callback + */ + private void scheduleEvent(final Event event, ScheduledExecutorService eventWorker, + final ICallback callback) { + + class PostRunnable implements Runnable { + public void run() { + log.debug("Firing EventFoodChange"); + MainApp.bus().post(event); + callback.setPost(null); + } + } + // prepare task for execution in 1 sec + // cancel waiting task to prevent sending multiple posts + if (callback.getPost() != null) + callback.getPost().cancel(false); + Runnable task = new PostRunnable(); + final int sec = 1; + callback.setPost(eventWorker.schedule(task, sec, TimeUnit.SECONDS)); + } + + /** + * Schedule a foodChange Event. + */ + public void scheduleFoodChange() { + this.scheduleEvent(new EventFoodDatabaseChanged(), foodEventWorker, new ICallback() { + @Override + public void setPost(ScheduledFuture post) { + scheduledFoodEventPost = post; + } + + @Override + public ScheduledFuture getPost() { + return scheduledFoodEventPost; + } + }); + } + + public List getFoodData() { + try { + return this.getDao().queryForAll(); + } catch (SQLException e) { + log.error("Unhandled exception", e); + } + + return new ArrayList<>(); + } + + /* + { + "_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 json) { + try { + Food food = Food.createFromJson(json); + this.createFoodFromJsonIfNotExists(food); + } catch (JSONException e) { + log.error("Unhandled exception", e); + } + } + + public void createFoodFromJsonIfNotExists(JSONArray array) { + try { + for(int n = 0; n < array.length(); n++) + { + JSONObject json = array.getJSONObject(n); + Food food = Food.createFromJson(json); + this.createFoodFromJsonIfNotExists(food); + } + } catch (JSONException e) { + log.error("Unhandled exception", e); + } + } + + public void createFoodFromJsonIfNotExists(Food food) { + try { + this.getDao().createOrUpdate(food); + } catch (SQLException e) { + log.error("Unhandled exception", e); + } + } + + public void deleteNS(JSONObject json) { + try { + String _id = json.getString("_id"); + this.deleteByNSId(_id); + } catch (JSONException | SQLException e) { + log.error("Unhandled exception", e); + } + } + + public void deleteNS(JSONArray array) { + try { + for(int n = 0; n < array.length(); n++) + { + JSONObject json = array.getJSONObject(n); + this.deleteNS(json); + } + } catch (JSONException e) { + log.error("Unhandled exception", e); + } + } + + /** + * deletes an entry by its NS Id. + * + * Basically a convenience method for findByNSId and delete. + * + * @param _id + */ + public void deleteByNSId(String _id) throws SQLException { + Food stored = this.findByNSId(_id); + if (stored != null) { + log.debug("FOOD: Removing Food record from database: " + stored.toString()); + this.delete(stored); + } + } + + /** + * deletes the food and sends the foodChange Event + * + * should be moved ot a Service + * + * @param food + */ + public void delete(Food food) { + try { + this.getDao().delete(food); + this.scheduleFoodChange(); + } catch (SQLException e) { + log.error("Unhandled exception", e); + } + } + + /** + * Create of update a food record by the NS (Nightscout) Id. + * + * @param food + * @return + */ + public boolean createOrUpdateByNS(Food food) { + try { + // find by NS _id + if (food._id != null) { + Food old = this.findByNSId(food._id); + + if (old != null) { + if (!old.isEqual(food)) { + this.delete(old); // need to delete/create because date may change too + old.copyFrom(food); + this.getDao().create(old); + log.debug("FOOD: Updating record by _id: " + old.toString()); + this.scheduleFoodChange(); + return true; + } else { + return false; + } + } + } + this.getDao().createOrUpdate(food); + log.debug("FOOD: New record: " + food.toString()); + this.scheduleFoodChange(); + return true; + } catch (SQLException e) { + log.error("Unhandled exception", e); + } + return false; + } + + /** + * finds food by its NS Id. + * + * @param _id + * @return + */ + public Food findByNSId(String _id) { + try { + List list = this.getDao().queryForEq("_id", _id); + + if (list.size() == 1) { // really? if there are more then one result, then we do not return anything... + return list.get(0); + } + } catch (SQLException e) { + log.error("Unhandled exception", e); + } + return null; + } + + @Nullable + @Override + public IBinder onBind(Intent intent) { + return null; + } +}