/** * Copyright (C) 2013 - 2015 the enviroCar community * <p> * This file is part of the enviroCar app. * <p> * The enviroCar app 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, either version 3 of the License, or * (at your option) any later version. * <p> * The enviroCar app 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. * <p> * You should have received a copy of the GNU General Public License along * with the enviroCar app. If not, see http://www.gnu.org/licenses/. */ package org.envirocar.storage; import android.content.ContentValues; import android.database.Cursor; import com.squareup.sqlbrite.BriteDatabase; import org.envirocar.core.entity.Measurement; import org.envirocar.core.entity.Track; import org.envirocar.core.exception.MeasurementSerializationException; import org.envirocar.core.exception.TrackSerializationException; import org.envirocar.core.logging.Logger; import org.envirocar.core.util.TrackMetadata; import org.json.JSONException; import java.util.List; import javax.inject.Inject; import javax.inject.Singleton; import rx.Observable; import rx.Subscriber; import rx.functions.Func1; /** * TODO JavaDoc * * @author dewall */ @Singleton public class EnviroCarDBImpl implements EnviroCarDB { private static final Logger LOG = Logger.getLogger(EnviroCarDBImpl.class); protected BriteDatabase briteDatabase; /** * Constructor. * * @param briteDatabase the Database instance. */ @Inject public EnviroCarDBImpl(BriteDatabase briteDatabase) { this.briteDatabase = briteDatabase; } @Override public Observable<Track> getTrack(Track.TrackId trackId) { return getTrack(trackId, false); } @Override public Observable<Track> getTrack(Track.TrackId trackId, boolean lazy) { return fetchTrackObservable( "SELECT * FROM " + TrackTable.TABLE_TRACK + " WHERE " + TrackTable.KEY_TRACK_ID + "=" + trackId, lazy); } @Override public Observable<List<Track>> getAllTracks() { return getAllTracks(false); } @Override public Observable<List<Track>> getAllTracks(final boolean lazy) { return fetchTracksObservable( "SELECT * FROM " + TrackTable.TABLE_TRACK, lazy); } @Override public Observable<List<Track>> getAllTracksByCar(String carID, boolean lazy) { return fetchTracksObservable("SELECT * FROM " + TrackTable.TABLE_TRACK + " WHERE " + TrackTable.KEY_TRACK_CAR_ID + "='" + carID + "'", lazy); } @Override public Observable<List<Track>> getAllLocalTracks() { return getAllLocalTracks(false); } @Override public Observable<List<Track>> getAllLocalTracks(boolean lazy) { return fetchTracksObservable( "SELECT * FROM " + TrackTable.TABLE_TRACK + " WHERE " + TrackTable.KEY_REMOTE_ID + " IS NULL", lazy); } @Override public Observable<List<Track>> getAllRemoteTracks() { return getAllRemoteTracks(false); } @Override public Observable<List<Track>> getAllRemoteTracks(boolean lazy) { return fetchTracksObservable( "SELECT * FROM " + TrackTable.TABLE_TRACK + " WHERE " + TrackTable.KEY_REMOTE_ID + " IS NOT NULL", lazy); } @Override public Observable<Void> clearTables() { return Observable.create(new Observable.OnSubscribe<Void>() { @Override public void call(Subscriber<? super Void> subscriber) { BriteDatabase.Transaction transaction = briteDatabase.newTransaction(); // TODO } }); } public void insertTrack(final Track track) throws TrackSerializationException { LOG.info("insertTrack(): trying to insert a new track"); BriteDatabase.Transaction transaction = briteDatabase.newTransaction(); try { long result = briteDatabase.insert(TrackTable.TABLE_TRACK, TrackTable.toContentValues(track)); Track.TrackId trackId = new Track.TrackId(result); track.setTrackID(trackId); LOG.info(String.format("insertTrack(): " + "track has been successfully inserted ->[id = %s]", "" + result)); if (track.getMeasurements().size() > 0) { for (Measurement measurement : track.getMeasurements()) { measurement.setTrackId(trackId); briteDatabase.insert(MeasurementTable.TABLE_NAME, MeasurementTable.toContentValues(measurement)); } } transaction.markSuccessful(); } catch (MeasurementSerializationException e) { LOG.error(e.getMessage(), e); throw new TrackSerializationException(e); } finally { transaction.close(); } } @Override public Observable<Track> insertTrackObservable(final Track track) { return Observable.create(new Observable.OnSubscribe<Track>() { @Override public void call(Subscriber<? super Track> subscriber) { try { insertTrack(track); subscriber.onNext(track); } catch (TrackSerializationException e) { subscriber.onError(e); } subscriber.onCompleted(); } }); } @Override public boolean updateTrack(Track track) { LOG.info(String.format("updateTrack(%s)", track.getTrackID())); ContentValues trackValues = TrackTable.toContentValues(track); int update = briteDatabase.update(TrackTable.TABLE_TRACK, trackValues, TrackTable.KEY_TRACK_ID + "=" + track.getTrackID()); return update != -1; } @Override public Observable<Track> updateTrackObservable(Track track) { return Observable.create(new Observable.OnSubscribe<Track>() { @Override public void call(Subscriber<? super Track> subscriber) { subscriber.onStart(); if (updateTrack(track)) { LOG.info("Track [%s] has been successfully updated.", track.toString()); subscriber.onNext(track); } // subscriber.onCompleted(); } }).first(); } @Override public boolean updateCarIdOfTracks(String currentId, String newId) { ContentValues values = new ContentValues(); values.put(TrackTable.KEY_TRACK_CAR_ID, newId); briteDatabase.update(TrackTable.TABLE_TRACK, values, TrackTable.KEY_TRACK_CAR_ID + "=?", new String[]{currentId}); return true; } @Override public void deleteTrack(Track.TrackId trackId) { briteDatabase.delete(TrackTable.TABLE_TRACK, TrackTable.KEY_TRACK_ID + "='" + trackId + "'"); deleteMeasurementsOfTrack(trackId); } @Override public void deleteTrack(Track track) { deleteTrack(track.getTrackID()); } @Override public Observable<Track> deleteTrackObservable(Track track) { return Observable.create(new Observable.OnSubscribe<Track>() { @Override public void call(Subscriber<? super Track> subscriber) { deleteTrack(track); subscriber.onNext(track); subscriber.onCompleted(); } }); } @Override public Observable<Void> deleteAllRemoteTracks() { return briteDatabase.createQuery(TrackTable.TABLE_TRACK, "SELECT " + TrackTable.KEY_TRACK_ID + ", " + TrackTable.KEY_REMOTE_ID + " FROM " + TrackTable.TABLE_TRACK + " WHERE " + TrackTable.KEY_REMOTE_ID + " IS NOT NULL") .map(query -> query.run()) .map(TrackTable.TO_TRACK_ID_LIST_MAPPER) .map(trackIds -> { for (Track.TrackId trackId : trackIds) deleteTrack(trackId); return null; }); } @Override public void insertMeasurement(final Measurement measurement) throws MeasurementSerializationException { LOG.info("inserted measurement into track " + measurement.getTrackId()); briteDatabase.insert(MeasurementTable.TABLE_NAME, MeasurementTable.toContentValues(measurement)); } @Override public Observable<Void> insertMeasurementObservable(final Measurement measurement) { return Observable.create(new Observable.OnSubscribe<Void>() { @Override public void call(Subscriber<? super Void> subscriber) { try { insertMeasurement(measurement); } catch (MeasurementSerializationException e) { LOG.error(e.getMessage(), e); subscriber.onError(e); } finally { subscriber.onCompleted(); } } }); } @Override public void updateTrackRemoteID(final Track track, final String remoteID) { ContentValues newValues = new ContentValues(); newValues.put(TrackTable.KEY_REMOTE_ID, remoteID); briteDatabase.update(TrackTable.TABLE_TRACK, newValues, TrackTable.KEY_TRACK_ID + "=?", new String[]{Long.toString(track.getTrackID().getId())}); } @Override public Observable<Void> updateTrackRemoteIDObservable(final Track track, final String remoteID) { return Observable.create(new Observable.OnSubscribe<Void>() { @Override public void call(Subscriber<? super Void> subscriber) { updateTrackRemoteID(track, remoteID); subscriber.onCompleted(); } }); } public void updateTrackMetadata(final Track track, final TrackMetadata trackMetadata) throws TrackSerializationException { try { ContentValues newValues = new ContentValues(); newValues.put(TrackTable.KEY_TRACK_METADATA, trackMetadata.toJsonString()); briteDatabase.update(TrackTable.TABLE_TRACK, newValues, TrackTable.KEY_TRACK_ID + "=?", new String[]{Long.toString(track.getTrackID().getId())}); } catch (JSONException e) { LOG.error(e.getMessage(), e); throw new TrackSerializationException(e); } } public Observable<TrackMetadata> updateTrackMetadataObservable( final Track track, final TrackMetadata trackMetadata) { return Observable.create(new Observable.OnSubscribe<TrackMetadata>() { @Override public void call(Subscriber<? super TrackMetadata> subscriber) { try { updateTrackMetadata(track, trackMetadata); } catch (TrackSerializationException e) { LOG.error(e.getMessage(), e); subscriber.onError(e); } finally { subscriber.onCompleted(); } } }); } @Override public Observable<Track> fetchTracks( Observable<List<Track>> tracks, final boolean lazy) { return fetchTrack(tracks .flatMap(new Func1<List<Track>, Observable<Track>>() { @Override public Observable<Track> call(List<Track> tracks) { return Observable.from(tracks); } }), lazy); } @Override public Observable<Track> fetchTrack(Observable<Track> trackObservable, final boolean lazy) { return trackObservable .flatMap(new Func1<Track, Observable<Track>>() { @Override public Observable<Track> call(Track track) { return lazy ? fetchStartTime(track) : fetchMeasurements(track); } }); } @Override public Observable<Track> getActiveTrackObservable(boolean lazy) { return fetchTrackObservable( "SELECT * FROM " + TrackTable.TABLE_TRACK + " WHERE " + TrackTable.KEY_TRACK_STATE + "='" + Track.TrackStatus.ONGOING + "'" + " ORDER BY " + TrackTable.KEY_TRACK_ID + " DESC" + " LIMIT 1", lazy); } private void deleteMeasurementsOfTrack(Track.TrackId trackId) { BriteDatabase.Transaction transaction = briteDatabase.newTransaction(); try { briteDatabase.delete(MeasurementTable.TABLE_NAME, MeasurementTable.KEY_TRACK + "='" + trackId + "'"); transaction.markSuccessful(); } finally { transaction.end(); } } private Observable<Track> fetchMeasurements(final Track track) { return briteDatabase.createQuery( MeasurementTable.TABLE_NAME, "SELECT * FROM " + MeasurementTable.TABLE_NAME + " WHERE " + MeasurementTable.KEY_TRACK + "=\"" + track.getTrackID() + "\"" + " ORDER BY " + MeasurementTable.KEY_TIME + " ASC") .mapToList(MeasurementTable.MAPPER) .map(new Func1<List<Measurement>, Track>() { @Override public Track call(List<Measurement> measurements) { track.setMeasurements(measurements); track.setLazyMeasurements(false); return track; } }); } private Observable<Track> fetchStartTime(final Track track) { return briteDatabase.createQuery( MeasurementTable.TABLE_NAME, "SELECT * FROM " + MeasurementTable.TABLE_NAME + " WHERE " + MeasurementTable.KEY_TRACK + "=\"" + track.getTrackID() + "\"" + " ORDER BY " + MeasurementTable.KEY_TIME + " ASC" + " LIMIT 1") .mapToOne(MeasurementTable.MAPPER) .map(new Func1<Measurement, Track>() { @Override public Track call(Measurement measurement) { track.setStartTime(measurement.getTime()); track.setLazyMeasurements(true); return track; } }); } private Observable<Track> fetchTrackObservable(String sql, boolean lazy) { return briteDatabase .createQuery(TrackTable.TABLE_TRACK, sql) .mapToOneOrDefault(TrackTable.MAPPER, null) .take(1) .compose(fetchTrackObservable(lazy)); } private Observable.Transformer<Track, Track> fetchTrackObservable(final boolean lazy) { return trackObservable -> trackObservable.map(track -> { if (track == null) return null; // return the track either leither or completly fetched. return lazy ? fetchStartEndTimeSilent(track) : fetchMeasurementsSilent(track); }); } private Observable<List<Track>> fetchTracksObservable(String sql, boolean lazy) { return briteDatabase.createQuery(TrackTable.TABLE_TRACK, sql) .mapToList(TrackTable.MAPPER) .compose(fetchTracks(lazy)); } private Observable.Transformer<List<Track>, List<Track>> fetchTracks(boolean lazy) { return trackObservable -> trackObservable.map(tracks -> { for (Track track : tracks) { if (lazy) { fetchStartEndTimeSilent(track); } else { fetchMeasurementsSilent(track); } } return tracks; }); } private Track fetchMeasurementsSilent(final Track track) { track.setMeasurements(MeasurementTable.fromCursorToList(briteDatabase.query( "SELECT * FROM " + MeasurementTable.TABLE_NAME + " WHERE " + MeasurementTable.KEY_TRACK + "=\"" + track.getTrackID() + "\"" + " ORDER BY " + MeasurementTable.KEY_TIME + " ASC", null))); track.setLazyMeasurements(false); return track; } private Track fetchStartEndTimeSilent(final Track track) { Cursor startTime = briteDatabase.query( "SELECT " + MeasurementTable.KEY_TIME + " FROM " + MeasurementTable.TABLE_NAME + " WHERE " + MeasurementTable.KEY_TRACK + "=\"" + track.getTrackID() + "\"" + " ORDER BY " + MeasurementTable.KEY_TIME + " ASC LIMIT 1"); if (startTime.moveToFirst()) { track.setStartTime( startTime.getLong( startTime.getColumnIndex(MeasurementTable.KEY_TIME))); } Cursor endTime = briteDatabase.query( "SELECT " + MeasurementTable.KEY_TIME + " FROM " + MeasurementTable.TABLE_NAME + " WHERE " + MeasurementTable.KEY_TRACK + "=\"" + track.getTrackID() + "\"" + " ORDER BY " + MeasurementTable.KEY_TIME + " DESC LIMIT 1"); if (endTime.moveToFirst()) { track.setEndTime( endTime.getLong( startTime.getColumnIndex(MeasurementTable.KEY_TIME))); } return track; } }