/* * Copyright 2014 Bevbot LLC <info@bevbot.com> * * This file is part of the Kegtab package from the Kegbot project. For * more information on Kegtab or Kegbot, see <http://kegbot.org/>. * * Kegtab is free software: you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free * Software Foundation, version 2. * * Kegtab is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with Kegtab. If not, see <http://www.gnu.org/licenses/>. */ package org.kegbot.backend; import android.content.ContentResolver; import android.content.Context; import android.database.sqlite.SQLiteException; import android.provider.MediaStore; import android.util.Log; import com.google.common.base.Preconditions; import com.google.common.base.Strings; import org.codehaus.jackson.JsonNode; import org.kegbot.app.util.KegSizes; import org.kegbot.app.util.TimeSeries; import org.kegbot.proto.Api.RecordTemperatureRequest; import org.kegbot.proto.Models; import org.kegbot.proto.Models.AuthenticationToken; import org.kegbot.proto.Models.Beverage; import org.kegbot.proto.Models.BeverageProducer; import org.kegbot.proto.Models.Controller; import org.kegbot.proto.Models.Drink; import org.kegbot.proto.Models.FlowMeter; import org.kegbot.proto.Models.Image; import org.kegbot.proto.Models.Keg; import org.kegbot.proto.Models.KegTap; import org.kegbot.proto.Models.Session; import org.kegbot.proto.Models.SoundEvent; import org.kegbot.proto.Models.SystemEvent; import org.kegbot.proto.Models.ThermoLog; import org.kegbot.proto.Models.User; import java.io.File; import java.io.FileNotFoundException; import java.util.Collections; import java.util.List; import javax.annotation.Nullable; /** A {@link Backend} which uses the local filesystem for storage. */ public class LocalBackend implements Backend { private static final String TAG = LocalBackend.class.getSimpleName(); private LocalBackendDbHelper mDb; private ContentResolver mContentResolver; @Override public void start(Context context) { mDb = new LocalBackendDbHelper(context); mContentResolver = context.getContentResolver(); } @Override public KegTap startKeg(KegTap tap, String beerName, String brewerName, String styleName, String kegType) throws BackendException { if (tap.hasCurrentKeg()) { endKeg(tap.getCurrentKeg()); } final double volume = KegSizes.getVolumeMl(kegType); final Keg keg = mDb.createOrUpdateKeg(Keg.newBuilder() .setId(0) .setKegType(kegType) .setFullVolumeMl(volume) .setRemainingVolumeMl(volume) .setServedVolumeMl(0) .setSpilledVolumeMl(0) .setPercentFull(100.0) .setOnline(true) .setBeverage(Beverage.newBuilder() .setId(0) .setBeverageType("beer") .setName(beerName) .setStyle(styleName) .setProducer(BeverageProducer.newBuilder() .setId(0) .setName(brewerName) .build()) .build()) .build()); Log.d(TAG, "Created keg: " + keg); final KegTap updatedTap = mDb.createOrUpdateTap(KegTap.newBuilder(tap) .setCurrentKegId(keg.getId()) .build()); Log.d(TAG, "Updated tap: " + updatedTap); return updatedTap; } @Override public AuthenticationToken assignToken(String authDevice, String tokenValue, String username) throws BackendException { throw new OperationNotSupportedException("Local backend does not support users."); } @Override public Image attachPictureToDrink(int drinkId, File picture) throws BackendException { throw new OperationNotSupportedException("Local backend does not support photos."); } @Override public User createUser(String username, String email, String password, String imagePath) throws BackendException { throw new OperationNotSupportedException("Local backend does not support users."); } @Override public Keg endKeg(Keg keg) throws BackendException { final int kegId = keg.getId(); Log.i(TAG, "Taking keg " + kegId + " offline"); final Keg newKeg = Keg.newBuilder(keg) .setOnline(false) .build(); final Keg result; try { result = mDb.createOrUpdateKeg(newKeg); } catch (SQLiteException e) { Log.w(TAG, "SQLiteException reading keg " + kegId); throw new BackendException(e); } for (final KegTap tap : getTaps()) { if (tap.getCurrentKegId() == keg.getId()) { mDb.createOrUpdateTap(KegTap.newBuilder(tap) .setCurrentKegId(0) .build()); break; } } return result; } @Override public AuthenticationToken getAuthToken(String authDevice, String tokenValue) throws BackendException { return null; // Not Implemented } @Override public Session getCurrentSession() throws BackendException { return null; } @Override public List<SystemEvent> getEvents() throws BackendException { return Collections.emptyList(); } @Override public List<SystemEvent> getEventsSince(long sinceExventId) throws BackendException { return Collections.emptyList(); } @Override public JsonNode getSessionStats(int sessionId) throws BackendException { return null; } @Override public List<SoundEvent> getSoundEvents() throws BackendException { return Collections.emptyList(); } @Override public List<KegTap> getTaps() throws BackendException { return mDb.getAllTaps(); } @Override public KegTap createTap(String tapName) throws BackendException { final KegTap tap = KegTap.newBuilder() .setId(0) .setName(tapName) .build(); try { return mDb.createOrUpdateTap(tap); } catch (SQLiteException e) { throw new BackendException("Error updating tap", e); } } @Override public void deleteTap(KegTap tap) throws BackendException { boolean result = mDb.deleteTap(tap); Log.d(TAG, "Deleted row result: " + result); } @Override public User getUser(String username) throws BackendException { throw new NotFoundException("Local backend does not support users"); } @Override public List<User> getUsers() throws BackendException { return Collections.emptyList(); } @Override public Drink recordDrink(String tapName, long volumeMl, long ticks, @Nullable String shout, @Nullable String username, @Nullable String recordDate, long durationMillis, @Nullable TimeSeries timeSeries, @Nullable File picture) throws BackendException { final Drink drink; String pictureUrl = ""; if (picture != null) { final String imagePath = picture.getAbsolutePath(); Log.d(TAG, "Storing image, path=" + imagePath + " exists=" + picture.exists()); try { pictureUrl = MediaStore.Images.Media.insertImage(mContentResolver, imagePath, picture.getName(), "Kegbot drink snapshot"); } catch (FileNotFoundException e) { Log.w(TAG, "Storing image '" + imagePath + "' failed: " + e); } } try { drink = mDb.recordDrink(tapName, volumeMl, ticks, shout, username, recordDate, durationMillis, timeSeries, Strings.nullToEmpty(pictureUrl)); } catch (SQLiteException e) { throw new BackendException("Error recording drink", e); } return drink; } @Override public ThermoLog recordTemperature(RecordTemperatureRequest request) throws BackendException { // Ignored. return null; } @Override public FlowMeter calibrateMeter(FlowMeter meter, double ticksPerMl) throws BackendException { final FlowMeter newMeter = FlowMeter.newBuilder(meter) .setTicksPerMl(ticksPerMl) .build(); try { return mDb.createOrUpdateFlowMeter(newMeter); } catch (SQLiteException e) { throw new BackendException("Error updating meter", e); } } @Override public Controller createController(String name, String serialNumber, String deviceType) { Preconditions.checkNotNull(name); serialNumber = Strings.nullToEmpty(serialNumber); deviceType = Strings.nullToEmpty(deviceType); final Controller controller = Controller.newBuilder() .setId(0) .setName(name) .setSerialNumber(serialNumber) .setModelName(deviceType) .build(); return mDb.createOrUpdateController(controller); } @Override public List<Controller> getControllers() throws BackendException { return mDb.getAllControllers(); } @Override public Controller updateController(Controller controller) throws BackendException { return mDb.createOrUpdateController(controller); } @Override public FlowMeter createFlowMeter(Controller controller, String portName, double ticksPerMl) throws BackendException { final FlowMeter meter = FlowMeter.newBuilder() .setId(0) .setController(controller) .setPortName(portName) .setName(String.format("%s.%s", controller.getName(), portName)) .setTicksPerMl((float) ticksPerMl) .build(); return mDb.createOrUpdateFlowMeter(meter); } @Override public List<FlowMeter> getFlowMeters() throws BackendException { return mDb.getAllFlowMeters(); } @Override public FlowMeter updateFlowMeter(FlowMeter flowMeter) throws BackendException { return mDb.createOrUpdateFlowMeter(flowMeter); } @Override public KegTap connectMeter(KegTap tap, FlowMeter meter) { return mDb.connectTapToMeter(tap, meter); } @Override public KegTap disconnectMeter(KegTap tap) { return mDb.connectTapToMeter(tap, null); } @Override public List<Models.FlowToggle> getFlowToggles() throws BackendException { return mDb.getAllFlowToggles(); } @Override public Models.FlowToggle updateFlowToggle(Models.FlowToggle flowToggle) throws BackendException { return mDb.createOrUpdateFlowToggle(flowToggle); } @Override public KegTap connectToggle(KegTap tap, Models.FlowToggle flowToggle) throws BackendException { return mDb.connectTapToToggle(tap, flowToggle); } @Override public KegTap disconnectToggle(KegTap tap) throws BackendException { return mDb.connectTapToToggle(tap, null); } }