diff --git a/app/src/main/java/com/todoroo/andlib/data/DatabaseDao.java b/app/src/main/java/com/todoroo/andlib/data/DatabaseDao.java deleted file mode 100644 index eaf2037d2..000000000 --- a/app/src/main/java/com/todoroo/andlib/data/DatabaseDao.java +++ /dev/null @@ -1,172 +0,0 @@ -/** - * Copyright (c) 2012 Todoroo Inc - * - * See the file "LICENSE" for the full license governing this code. - */ -package com.todoroo.andlib.data; - -import android.content.ContentValues; -import android.database.Cursor; - -import com.todoroo.andlib.sql.Criterion; -import com.todoroo.andlib.sql.Query; -import com.todoroo.astrid.dao.Database; -import com.todoroo.astrid.data.Task; -import com.todoroo.astrid.helper.UUIDHelper; - -import org.tasks.BuildConfig; - -import java.util.List; -import java.util.concurrent.atomic.AtomicBoolean; - -import timber.log.Timber; - -/** - * DAO for reading data from an instance of {@link Database}. If you - * are writing an add-on for Astrid, you probably want to be using a subclass - * of ContentResolverDao instead. - * - * @author Tim Su - * - */ -public class DatabaseDao { - - private final Table table = Task.TABLE; - private final Database database; - - public DatabaseDao(Database database) { - this.database = database; - } - - public List toList(Query query) { - return query(query).toList(); - } - - /** - * Construct a query with SQL DSL objects - */ - public TodorooCursor query(Query query) { - query.from(table); - String queryString = query.toString(); - if (BuildConfig.DEBUG) { - Timber.v(queryString); - } - Cursor cursor = database.rawQuery(queryString); - return new TodorooCursor(cursor, query.getFields()); - } - - /** - * Delete the given id - * @return true if delete was successful - */ - public boolean delete(long id) { - return database.delete(table.name, - AbstractModel.ID_PROPERTY.eq(id).toString(), null) > 0; - } - - /** - * Delete all matching a clause - * @param where predicate for deletion - * @return # of deleted items - */ - public int deleteWhere(Criterion where) { - Timber.v("deleteWhere(%s)", where); - return database.delete(table.name, - where.toString(), null); - } - - /** - * Update all matching a clause to have the values set on template object. - *

- * Example (updates "joe" => "bob" in metadata value1): - * {code} - * Metadata item = new Metadata(); - * item.setVALUE1("bob"); - * update(item, Metadata.VALUE1.eq("joe")); - * {code} - * @param where sql criteria - * @param template set fields on this object in order to set them in the db. - * @return # of updated items - */ - public int update(Criterion where, Task template) { - return database.update(table.name, template.getSetValues(), - where.toString()); - } - - private interface DatabaseChangeOp { - boolean makeChange(); - } - - private boolean insertOrUpdateAndRecordChanges(Task item, DatabaseChangeOp op) { - final AtomicBoolean result = new AtomicBoolean(false); - synchronized(database) { - result.set(op.makeChange()); - if (result.get()) { - item.markSaved(); - if (BuildConfig.DEBUG) { - Timber.v("%s %s", op, item.toString()); - } - } - } - return result.get(); - } - - /** - * Creates the given item. - * @param item - * item model - * @return returns true on success. - */ - public boolean createNew(final Task item) { - if (!item.containsValue(Task.UUID) || Task.isUuidEmpty(item.getUuid())) { - item.setUuid(UUIDHelper.newUUID()); - } - - DatabaseChangeOp insert = new DatabaseChangeOp() { - @Override - public boolean makeChange() { - ContentValues mergedValues = item.getMergedValues(); - mergedValues.remove(AbstractModel.ID_PROPERTY.name); - long newRow = database.insert(table.name, mergedValues); - boolean result = newRow >= 0; - if (result) { - item.setId(newRow); - } - return result; - } - - @Override - public String toString() { - return "INSERT"; - } - }; - return insertOrUpdateAndRecordChanges(item, insert); - } - - /** - * Saves the given item. Will not create a new item! - * @param item - * item model - * @return returns true on success. - */ - public boolean saveExisting(final Task item) { - final ContentValues values = item.getSetValues(); - if(values == null || values.size() == 0) // nothing changed - { - return true; - } - DatabaseChangeOp update = new DatabaseChangeOp() { - @Override - public boolean makeChange() { - return database.update(table.name, values, - AbstractModel.ID_PROPERTY.eq(item.getId()).toString()) > 0; - } - - @Override - public String toString() { - return "UPDATE"; - } - }; - return insertOrUpdateAndRecordChanges(item, update); - } -} diff --git a/app/src/main/java/com/todoroo/astrid/core/OldTaskPreferences.java b/app/src/main/java/com/todoroo/astrid/core/OldTaskPreferences.java index f48a1f1a7..eb568d7d4 100644 --- a/app/src/main/java/com/todoroo/astrid/core/OldTaskPreferences.java +++ b/app/src/main/java/com/todoroo/astrid/core/OldTaskPreferences.java @@ -70,13 +70,12 @@ public class OldTaskPreferences extends InjectingPreferenceActivity { .setPositiveButton(android.R.string.ok, (dialog, which) -> new ProgressDialogAsyncTask(OldTaskPreferences.this, dialogBuilder) { @Override protected Integer doInBackground(Void... params) { - Query query = Query.select(Task.ID, Task.CALENDAR_URI) - .where(Criterion.and(Task.DELETION_DATE.gt(0))); - for (Task task : taskDao.toList(query)) { + List deleted = taskDao.getDeleted(); + for (Task task : deleted) { calendarEventProvider.deleteEvent(task); taskDao.delete(task.getId()); } - return taskDao.deleteWhere(Task.DELETION_DATE.gt(0)); + return deleted.size(); } @Override @@ -94,7 +93,9 @@ public class OldTaskPreferences extends InjectingPreferenceActivity { @Override protected Integer doInBackground(Void... params) { - return deleteCalendarEvents(Criterion.and(Task.COMPLETION_DATE.gt(0), Task.CALENDAR_URI.isNotNull())); + int result = deleteCalendarEvents(Criterion.and(Task.COMPLETION_DATE.gt(0), Task.CALENDAR_URI.isNotNull())); + taskDao.clearCompletedCalendarEvents(); + return result; } @Override @@ -111,7 +112,9 @@ public class OldTaskPreferences extends InjectingPreferenceActivity { .setPositiveButton(android.R.string.ok, (dialog, which) -> new ProgressDialogAsyncTask(OldTaskPreferences.this, dialogBuilder) { @Override protected Integer doInBackground(Void... params) { - return deleteCalendarEvents(Task.CALENDAR_URI.isNotNull()); + int result = deleteCalendarEvents(Task.CALENDAR_URI.isNotNull()); + taskDao.clearAllCalendarEvents(); + return result; } @Override @@ -131,11 +134,6 @@ public class OldTaskPreferences extends InjectingPreferenceActivity { deletedEventCount++; } } - // mass update the CALENDAR_URI here, - // since the GCalHelper doesnt save it due to performance-reasons - Task template = new Task(); - template.setCalendarUri(""); //$NON-NLS-1$ - taskDao.update(criterion, template); return deletedEventCount; } diff --git a/app/src/main/java/com/todoroo/astrid/dao/Database.java b/app/src/main/java/com/todoroo/astrid/dao/Database.java index 38030975e..69b444396 100644 --- a/app/src/main/java/com/todoroo/astrid/dao/Database.java +++ b/app/src/main/java/com/todoroo/astrid/dao/Database.java @@ -80,6 +80,8 @@ public abstract class Database extends RoomDatabase { public static final String NAME = "database"; + private static final String TABLE_NAME = Task.TABLE.name; + private SupportSQLiteDatabase database; private Runnable onDatabaseUpdated; @@ -175,10 +177,10 @@ public abstract class Database extends RoomDatabase { return getDatabase().query(sql, null); } - public long insert(String table, ContentValues values) { + public long insert(ContentValues values) { long result; try { - result = getDatabase().insert(table, SQLiteDatabase.CONFLICT_REPLACE, values); + result = getDatabase().insert(TABLE_NAME, SQLiteDatabase.CONFLICT_REPLACE, values); } catch (SQLiteConstraintException e) { // Throw these exceptions throw e; } catch (Exception e) { // Suppress others @@ -189,14 +191,8 @@ public abstract class Database extends RoomDatabase { return result; } - public int delete(String table, String whereClause, String[] whereArgs) { - int result = getDatabase().delete(table, whereClause, whereArgs); - onDatabaseUpdated(); - return result; - } - - public int update(String table, ContentValues values, String whereClause) { - int result = getDatabase().update(table, SQLiteDatabase.CONFLICT_REPLACE, values, whereClause, null); + public int update(ContentValues values, String whereClause) { + int result = getDatabase().update(TABLE_NAME, SQLiteDatabase.CONFLICT_REPLACE, values, whereClause, null); onDatabaseUpdated(); return result; } diff --git a/app/src/main/java/com/todoroo/astrid/dao/TaskDao.java b/app/src/main/java/com/todoroo/astrid/dao/TaskDao.java index 5c91b9093..fd8551a31 100644 --- a/app/src/main/java/com/todoroo/astrid/dao/TaskDao.java +++ b/app/src/main/java/com/todoroo/astrid/dao/TaskDao.java @@ -10,7 +10,7 @@ import android.content.ContentValues; import android.content.Context; import android.database.Cursor; -import com.todoroo.andlib.data.DatabaseDao; +import com.todoroo.andlib.data.AbstractModel; import com.todoroo.andlib.data.Property; import com.todoroo.andlib.data.TodorooCursor; import com.todoroo.andlib.sql.Criterion; @@ -22,7 +22,9 @@ import com.todoroo.astrid.api.PermaSql; import com.todoroo.astrid.data.SyncFlags; import com.todoroo.astrid.data.Task; import com.todoroo.astrid.data.TaskApiDao; +import com.todoroo.astrid.helper.UUIDHelper; +import org.tasks.BuildConfig; import org.tasks.LocalBroadcastManager; import org.tasks.R; import org.tasks.data.AlarmDao; @@ -34,6 +36,9 @@ import org.tasks.preferences.Preferences; import org.tasks.receivers.PushReceiver; import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + +import timber.log.Timber; /** * Data Access layer for {@link Task}-related operations. @@ -46,7 +51,7 @@ public abstract class TaskDao { public static final String TRANS_SUPPRESS_REFRESH = "suppress-refresh"; - private final DatabaseDao dao; + private final Database database; private LocalBroadcastManager localBroadcastManager; private Preferences preferences; @@ -57,7 +62,7 @@ public abstract class TaskDao { private Context context; public TaskDao(Database database) { - dao = new DatabaseDao(database); + this.database = database; } public void initialize(Context context, Preferences preferences, LocalBroadcastManager localBroadcastManager, @@ -71,12 +76,8 @@ public abstract class TaskDao { this.googleTaskDao = googleTaskDao; } - public TodorooCursor query(Query query) { - return dao.query(query); - } - public List selectActive(Criterion criterion) { - return dao.toList(Query.select(Task.PROPERTIES).where(Criterion.and(TaskCriteria.isActive(), criterion))); + return query(Query.select(Task.PROPERTIES).where(Criterion.and(TaskCriteria.isActive(), criterion))).toList(); } @android.arch.persistence.room.Query("SELECT * FROM tasks WHERE _id = :id LIMIT 1") @@ -88,7 +89,7 @@ public abstract class TaskDao { } public int count(Query query) { - Cursor cursor = dao.query(query); + Cursor cursor = query(query); try { return cursor.getCount(); } finally { @@ -98,34 +99,17 @@ public abstract class TaskDao { public List query(Filter filter) { String query = PermaSql.replacePlaceholders(filter.getSqlQuery()); - return dao.toList(Query.select(Task.PROPERTIES).withQueryTemplate(query)); - } - - /** - * Update all matching a clause to have the values set on template object. - *

- * Example (updates "joe" => "bob" in metadata value1): - * {code} - * Metadata item = new Metadata(); - * item.setVALUE1("bob"); - * update(item, Metadata.VALUE1.eq("joe")); - * {code} - * @param where sql criteria - * @param template set fields on this object in order to set them in the db. - * @return # of updated items - */ - public int update(Criterion where, Task template) { - return dao.update(where, template); - } - - public int deleteWhere(Criterion criterion) { - return dao.deleteWhere(criterion); + return query(Query.select(Task.PROPERTIES).withQueryTemplate(query)).toList(); } public List toList(Query query) { - return dao.toList(query); + return query(query).toList(); } + @android.arch.persistence.room.Query("UPDATE tasks SET completed = :completionDate " + + "WHERE remoteId = :remoteId") + public abstract void setCompletionDate(String remoteId, long completionDate); + // --- SQL clause generators /** @@ -169,6 +153,20 @@ public abstract class TaskDao { @android.arch.persistence.room.Query("SELECT remoteId FROM tasks WHERE _id = :localId") public abstract String uuidFromLocalId(long localId); + @android.arch.persistence.room.Query("UPDATE tasks SET calendarUri = '' " + + "WHERE calendarUri NOT NULL AND calendarUri != ''") + public abstract void clearAllCalendarEvents(); + + @android.arch.persistence.room.Query("UPDATE tasks SET calendarUri = '' " + + "WHERE completed > 0 AND calendarUri NOT NULL AND calendarUri != ''") + public abstract void clearCompletedCalendarEvents(); + + @android.arch.persistence.room.Query("SELECT * FROM tasks WHERE deleted > 0") + public abstract List getDeleted(); + + @android.arch.persistence.room.Query("DELETE FROM tasks WHERE _id = :id") + abstract int deleteById(long id); + // --- delete /** @@ -177,7 +175,7 @@ public abstract class TaskDao { * @return true if delete was successful */ public boolean delete(long id) { - boolean result = dao.delete(id); + boolean result = deleteById(id) > 0; if(!result) { return false; } @@ -236,7 +234,30 @@ public abstract class TaskDao { setDefaultReminders(preferences, item); ContentValues values = item.getSetValues(); - if(dao.createNew(item)) { + + if (!item.containsValue(Task.UUID) || Task.isUuidEmpty(item.getUuid())) { + item.setUuid(UUIDHelper.newUUID()); + } + + DatabaseChangeOp insert = new DatabaseChangeOp() { + @Override + public boolean makeChange() { + ContentValues mergedValues = item.getMergedValues(); + mergedValues.remove(AbstractModel.ID_PROPERTY.name); + long newRow = database.insert(mergedValues); + boolean result = newRow >= 0; + if (result) { + item.setId(newRow); + } + return result; + } + + @Override + public String toString() { + return "INSERT"; + } + }; + if (insertOrUpdateAndRecordChanges(item, insert)) { return values; } return null; @@ -274,7 +295,19 @@ public abstract class TaskDao { item.setModificationDate(DateUtilities.now()); } } - if(dao.saveExisting(item)) { + DatabaseChangeOp update = new DatabaseChangeOp() { + @Override + public boolean makeChange() { + return database.update(values, + AbstractModel.ID_PROPERTY.eq(item.getId()).toString()) > 0; + } + + @Override + public String toString() { + return "UPDATE"; + } + }; + if (insertOrUpdateAndRecordChanges(item, update)) { return values; } return null; @@ -298,5 +331,36 @@ public abstract class TaskDao { ? Query.selectDistinct(properties) : Query.select(properties).withQueryTemplate(PermaSql.replacePlaceholders(queryTemplate))); } + + /** + * Construct a query with SQL DSL objects + */ + public TodorooCursor query(Query query) { + query.from(Task.TABLE); + String queryString = query.toString(); + if (BuildConfig.DEBUG) { + Timber.v(queryString); + } + Cursor cursor = database.rawQuery(queryString); + return new TodorooCursor(cursor, query.getFields()); + } + + private interface DatabaseChangeOp { + boolean makeChange(); + } + + private boolean insertOrUpdateAndRecordChanges(Task item, DatabaseChangeOp op) { + final AtomicBoolean result = new AtomicBoolean(false); + synchronized(database) { + result.set(op.makeChange()); + if (result.get()) { + item.markSaved(); + if (BuildConfig.DEBUG) { + Timber.v("%s %s", op, item.toString()); + } + } + } + return result.get(); + } } diff --git a/app/src/main/java/com/todoroo/astrid/subtasks/AstridOrderedListFragmentHelper.java b/app/src/main/java/com/todoroo/astrid/subtasks/AstridOrderedListFragmentHelper.java index bc150c13f..73553de32 100644 --- a/app/src/main/java/com/todoroo/astrid/subtasks/AstridOrderedListFragmentHelper.java +++ b/app/src/main/java/com/todoroo/astrid/subtasks/AstridOrderedListFragmentHelper.java @@ -130,9 +130,7 @@ class AstridOrderedListFragmentHelper { ArrayList chained = chainedCompletions.get(itemId); if(chained != null) { for(String taskId : chained) { - Task model = new Task(); - model.setCompletionDate(completionDate); - taskDao.update(Task.UUID.eq(taskId), model); + taskDao.setCompletionDate(taskId, completionDate); } taskAdapter.notifyDataSetInvalidated(); } @@ -142,9 +140,7 @@ class AstridOrderedListFragmentHelper { final ArrayList chained = new ArrayList<>(); updater.applyToDescendants(itemId, node -> { String uuid = node.uuid; - Task model = new Task(); - model.setCompletionDate(completionDate); - taskDao.update(Task.UUID.eq(uuid), model); + taskDao.setCompletionDate(uuid, completionDate); chained.add(node.uuid); });