/** AirCasting - Share your Air! Copyright (C) 2011-2012 HabitatMap, Inc. This program 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. This program 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 this program. If not, see <http://www.gnu.org/licenses/>. You can contact the authors by email at <info@habitatmap.org> */ package pl.llp.aircasting.storage.repository; import pl.llp.aircasting.android.Logger; import pl.llp.aircasting.helper.NoOp; import pl.llp.aircasting.model.Measurement; import pl.llp.aircasting.model.MeasurementStream; import pl.llp.aircasting.model.Note; import pl.llp.aircasting.model.Sensor; import pl.llp.aircasting.model.Session; import pl.llp.aircasting.storage.ProgressListener; import pl.llp.aircasting.storage.db.AirCastingDB; import pl.llp.aircasting.storage.db.DBConstants; import pl.llp.aircasting.storage.db.ReadOnlyDatabaseTask; import pl.llp.aircasting.storage.db.WritableDatabaseTask; import android.content.ContentValues; import android.database.Cursor; import android.database.SQLException; import android.database.sqlite.SQLiteDatabase; import com.google.common.collect.Lists; import com.google.inject.Inject; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.UUID; import static com.google.common.collect.Lists.newArrayList; import static pl.llp.aircasting.storage.DBHelper.*; import static pl.llp.aircasting.storage.db.DBConstants.*; public class SessionRepository { @Inject AirCastingDB dbAccessor; @Inject NoteRepository notes; @Inject StreamDAO streams; @Inject SessionDAO sessionDAO; @Inject SessionTrackerDAO trackedSessionsDAO; @API public Session save(@NotNull final Session session) { final ContentValues values = sessionDAO.asValues(session); dbAccessor.executeWritableTask(new WritableDatabaseTask<Object>() { @Override public Object execute(SQLiteDatabase writableDatabase) { long sessionKey = writableDatabase.insertOrThrow(SESSION_TABLE_NAME, null, values); session.setId(sessionKey); Collection<MeasurementStream> streamsToSave = session.getMeasurementStreams(); streams.saveAll(streamsToSave, sessionKey, writableDatabase); notes.save(session.getNotes(), sessionKey, writableDatabase); return null; } }); dbAccessor.executeReadOnlyTask(new ReadOnlyDatabaseTask<Object>() { @Override public Object execute(SQLiteDatabase readOnlyDatabase) { Cursor c; c = readOnlyDatabase.rawQuery("select count(*) from " + MEASUREMENT_TABLE_NAME + " WHERE " + MEASUREMENT_SESSION_ID + "=" + session.getId(), null); c.moveToFirst(); long aLong = c.getLong(0); Logger.d("Actually written " + aLong); c.close(); return null; } }); return session; } private void prepareHeader(Session session, ContentValues values) { values.put(SESSION_TITLE, session.getTitle()); values.put(SESSION_DESCRIPTION, session.getDescription()); values.put(SESSION_TAGS, session.getTags()); values.put(SESSION_LOCATION, session.getLocation()); } public Session loadShallow(Cursor cursor) { Session session = new Session(); sessionDAO.loadDetails(cursor, session); List<Note> loadedNotes = notes.load(session); session.addAll(loadedNotes); loadStreams(session); return session; } @Internal private Session loadStreams(final Session session) { Long id = session.getId(); List<MeasurementStream> streams = streams().findAllForSession(id); for (MeasurementStream stream : streams) { session.add(stream); } return session; } @API public List<Session> loadShallow(List<Long> sessionIds) { List<Session> result = newArrayList(); for (Long sessionId : sessionIds) { result.add(loadShallow(sessionId)); } return result; } @API public Session loadShallow(final long sessionID) { return dbAccessor.executeReadOnlyTask( new ReadOnlyDatabaseTask<Session>() { @Override public Session execute(SQLiteDatabase readOnlyDB) { Cursor cursor = readOnlyDB.query(SESSION_TABLE_NAME, null, SESSION_ID + " = " + sessionID, null, null, null, null); try { if (cursor.getCount() > 0) { cursor.moveToFirst(); return loadShallow(cursor); } else { return null; } } finally { cursor.close(); } } }); } @Internal private Session loadShallow(final UUID uuid) { return dbAccessor.executeReadOnlyTask(new ReadOnlyDatabaseTask<Session>() { @Override public Session execute(SQLiteDatabase readOnlyDatabase) { Cursor cursor = readOnlyDatabase .rawQuery("SELECT * FROM " + SESSION_TABLE_NAME + " WHERE " + SESSION_UUID + " = ?", new String[]{uuid.toString()}); try { if (cursor.getCount() == 0) return null; cursor.moveToFirst(); return loadShallow(cursor); } finally { cursor.close(); } } }); } @API public void markSessionForRemoval(UUID uuid) { final ContentValues values = new ContentValues(); values.put(SESSION_MARKED_FOR_REMOVAL, 1); final String whereClause = SESSION_UUID + " = '" + uuid.toString() + "'"; dbAccessor.executeWritableTask(new WritableDatabaseTask<Void>() { @Override public Void execute(SQLiteDatabase writableDatabase) { writableDatabase.update(SESSION_TABLE_NAME, values, whereClause, null); return null; } }); logSessionDeletion(whereClause); } @API public void markSessionForRemoval(final long id) { final ContentValues values = new ContentValues(); values.put(SESSION_MARKED_FOR_REMOVAL, 1); final String whereClause = SESSION_ID + " = " + id; dbAccessor.executeWritableTask(new WritableDatabaseTask<Void>() { @Override public Void execute(SQLiteDatabase writableDatabase) { writableDatabase.update(SESSION_TABLE_NAME, values, whereClause, null); return null; } }); logSessionDeletion(whereClause); } @API public List<Session> allCompleteSessions() { return dbAccessor.executeReadOnlyTask(new ReadOnlyDatabaseTask<List<Session>>() { @Override public List<Session> execute(SQLiteDatabase readOnlyDatabase) { List<Session> result = Lists.newArrayList(); String condition = DBConstants.SESSION_INCOMPLETE + " = 0 OR " + DBConstants.SESSION_TYPE + " = 'FixedSession'"; Cursor cursor = readOnlyDatabase .query(SESSION_TABLE_NAME, null, condition, null, null, null, SESSION_START + " DESC"); cursor.moveToFirst(); while (!cursor.isAfterLast()) { Session load = loadShallow(cursor); result.add(load); cursor.moveToNext(); } cursor.close(); return result; } }); } @Internal Cursor notDeletedCursor(@Nullable Sensor sensor, SQLiteDatabase readOnlyDatabase) { return sessionDAO.notDeletedCursor(sensor, readOnlyDatabase); } @API public void deleteSubmitted() { dbAccessor.executeWritableTask(new WritableDatabaseTask<Object>() { @Override public Object execute(SQLiteDatabase writableDatabase) { String condition = SESSION_SUBMITTED_FOR_REMOVAL + " = 1"; delete(condition, writableDatabase); streams().deleteSubmitted(writableDatabase); return null; } }); } @Internal private void delete(String condition, SQLiteDatabase writableDb) { Cursor cursor = writableDb.query(SESSION_TABLE_NAME, null, condition, null, null, null, null); cursor.moveToFirst(); while (!cursor.isAfterLast()) { Long id = getLong(cursor, SESSION_ID); delete(id, writableDb); cursor.moveToNext(); } cursor.close(); } @API public void deleteUploaded() { dbAccessor.executeWritableTask(new WritableDatabaseTask<Object>() { @Override public Object execute(SQLiteDatabase writableDatabase) { String condition = SESSION_LOCATION + " NOT NULL"; delete(condition, writableDatabase); return null; } }); } private void delete(Long sessionId, SQLiteDatabase writableDb) { try { writableDb.delete(SESSION_TABLE_NAME, SESSION_ID + " = " + sessionId, null); writableDb.delete(MEASUREMENT_TABLE_NAME, MEASUREMENT_SESSION_ID + " = " + sessionId, null); writableDb.delete(STREAM_TABLE_NAME, STREAM_SESSION_ID + " = " + sessionId, null); writableDb.delete(NOTE_TABLE_NAME, NOTE_SESSION_ID + " = " + sessionId, null); } catch (SQLException e) { Logger.e("Error deleting session [ " + sessionId + " ]", e); } } @API public Session loadFully(UUID uuid) { Session session = loadShallow(uuid); if(session != null) { fill(session, NoOp.progressListener()); } return session; } @API public Session loadFully(long id, ProgressListener progressListener) { Session session = loadShallow(id); if(session != null) { fill(session, progressListener); } return session; } private Session fill(final Session session, ProgressListener progressListener) { if(session == null) { return session; } final Iterable<MeasurementStream> streams = session.getActiveMeasurementStreams(); final MeasurementDAO r = new MeasurementDAO(progressListener); return dbAccessor.executeReadOnlyTask(new ReadOnlyDatabaseTask<Session>() { @Override public Session execute(SQLiteDatabase readOnlyDatabase) { Map<Long, List<Measurement>> load = r.load(session, readOnlyDatabase); for (MeasurementStream stream : streams) { if (load.containsKey(stream.getId())) { List<Measurement> measurements = load.get(stream.getId()); if(measurements == null || measurements.isEmpty()) { } else { stream.setMeasurements(measurements); session.add(stream); } } } return session; } }); } @API public void update(final Session session) { final ContentValues values = new ContentValues(); prepareHeader(session, values); final String whereClause = SESSION_ID + " = " + session.getId(); dbAccessor.executeWritableTask(new WritableDatabaseTask() { @Override public Object execute(SQLiteDatabase writableDatabase) { try { notes.save(session.getNotes(), session.getId(), writableDatabase); writableDatabase.update(SESSION_TABLE_NAME, values, whereClause, null); } catch (SQLException e) { Logger.e("Error updating session [ " + session.getId() + " ]", e); } return null; } }); logSessionDeletion(whereClause); } private void logSessionDeletion(final String whereClause) { dbAccessor.executeReadOnlyTask(new ReadOnlyDatabaseTask<Object>() { @Override public Object execute(SQLiteDatabase readOnlyDatabase) { Cursor c; c = readOnlyDatabase.query(SESSION_TABLE_NAME, new String[]{SESSION_ID, SESSION_MARKED_FOR_REMOVAL, SESSION_SUBMITTED_FOR_REMOVAL}, whereClause, null, null, null, null); if(c.moveToFirst()) { String string = c.getString(0); String string1 = c.getString(1); String string2 = c.getString(2); Logger.d("Session " + string + ", marked " + string1 + ", submitted " + string2); } else { Logger.e("Session [" + whereClause + "] not found in database"); } c.close(); return ""; } }); } @API public void deleteStream(final Session session, final MeasurementStream stream) { dbAccessor.executeWritableTask(new WritableDatabaseTask<Object>() { @Override public Object execute(SQLiteDatabase writableDatabase) { streams.markForRemoval(stream, session.getId(), writableDatabase); return null; } }); } @API public StreamDAO streams() { return streams; } public List<Session> notDeletedSessions(final Sensor selectedSensor) { return dbAccessor.executeReadOnlyTask(new ReadOnlyDatabaseTask<List<Session>>() { @Override public List<Session> execute(SQLiteDatabase readOnlyDatabase) { List<Session> result = newArrayList(); Cursor cursor = notDeletedCursor(selectedSensor, readOnlyDatabase); cursor.moveToFirst(); while (!cursor.isAfterLast()) { Session session = loadShallow(cursor); result.add(session); cursor.moveToNext(); } cursor.close(); return result; } }); } public void deleteLocationless() { dbAccessor.executeWritableTask(new WritableDatabaseTask<Object>() { @Override public Object execute(SQLiteDatabase writableDatabase) { String condition = DBConstants.SESSION_LOCAL_ONLY + " = 1"; delete(condition, writableDatabase); streams().deleteSubmitted(writableDatabase); return null; } }); } public void deleteCompletely(final long sessionId) { dbAccessor.executeWritableTask(new WritableDatabaseTask<Object>() { @Override public Object execute(SQLiteDatabase writableDatabase) { String condition = SESSION_ID + " = " + sessionId; delete(condition, writableDatabase); streams().deleteSubmitted(writableDatabase); return null; } }); } public List<Session> unfinishedSessions() { return trackedSessionsDAO.unfinishedSessions(); } public WritableDatabaseTask<Void> addStreamTask(final MeasurementStream stream, final Session session) { WritableDatabaseTask<Void> writableDatabaseTask = new WritableDatabaseTask<Void>() { @Override public Void execute(SQLiteDatabase writableDatabase) { ContentValues values = StreamDAO.values(stream); values.put(STREAM_SESSION_ID, session.getId()); long streamId = writableDatabase.insertOrThrow(STREAM_TABLE_NAME, null, values); stream.setId(streamId); stream.setSessionId(session.getId()); return null; } }; return writableDatabaseTask; } public void updateStream(MeasurementStream stream) { streams().update(stream); } public void complete(long sessionId) { trackedSessionsDAO.complete(sessionId); } }