From 02bcbff4e2b1c846cbe237cf13d34b1e1f60a884 Mon Sep 17 00:00:00 2001 From: Sam Bosley Date: Fri, 2 Mar 2012 12:03:18 -0800 Subject: [PATCH] Added code to push on save to check for sql constraint exceptions and try to recover --- .../astrid/actfm/sync/ActFmSyncService.java | 2 +- .../astrid/gtasks/sync/GtasksSyncService.java | 2 +- .../src/com/todoroo/astrid/dao/TaskDao.java | 64 +++++++++++++++++++ 3 files changed, 66 insertions(+), 2 deletions(-) diff --git a/astrid/plugin-src/com/todoroo/astrid/actfm/sync/ActFmSyncService.java b/astrid/plugin-src/com/todoroo/astrid/actfm/sync/ActFmSyncService.java index 179ac2cba..9dbbfab61 100644 --- a/astrid/plugin-src/com/todoroo/astrid/actfm/sync/ActFmSyncService.java +++ b/astrid/plugin-src/com/todoroo/astrid/actfm/sync/ActFmSyncService.java @@ -441,7 +441,7 @@ public final class ActFmSyncService { } task.putTransitory(SyncFlags.ACTFM_SUPPRESS_SYNC, true); - taskDao.saveExisting(task); + taskDao.saveExistingWithSqlConstraintCheck(task); } /** diff --git a/astrid/plugin-src/com/todoroo/astrid/gtasks/sync/GtasksSyncService.java b/astrid/plugin-src/com/todoroo/astrid/gtasks/sync/GtasksSyncService.java index 6e9b41714..e17b1d044 100644 --- a/astrid/plugin-src/com/todoroo/astrid/gtasks/sync/GtasksSyncService.java +++ b/astrid/plugin-src/com/todoroo/astrid/gtasks/sync/GtasksSyncService.java @@ -234,7 +234,7 @@ public final class GtasksSyncService { gtasksMetadata.setValue(GtasksMetadata.LAST_SYNC, DateUtilities.now() + 1000L); metadataService.save(gtasksMetadata); task.putTransitory(SyncFlags.GTASKS_SUPPRESS_SYNC, true); - taskDao.saveExisting(task); + taskDao.saveExistingWithSqlConstraintCheck(task); } public void pushMetadataOnSave(Metadata model, GtasksInvoker invoker) throws IOException { diff --git a/astrid/src/com/todoroo/astrid/dao/TaskDao.java b/astrid/src/com/todoroo/astrid/dao/TaskDao.java index b242fa886..4d3c608db 100644 --- a/astrid/src/com/todoroo/astrid/dao/TaskDao.java +++ b/astrid/src/com/todoroo/astrid/dao/TaskDao.java @@ -10,6 +10,7 @@ import android.database.sqlite.SQLiteConstraintException; import com.timsu.astrid.R; import com.todoroo.andlib.data.DatabaseDao; +import com.todoroo.andlib.data.Property; import com.todoroo.andlib.data.TodorooCursor; import com.todoroo.andlib.service.Autowired; import com.todoroo.andlib.service.DependencyInjectionService; @@ -19,6 +20,7 @@ import com.todoroo.andlib.sql.Query; import com.todoroo.andlib.utility.DateUtilities; import com.todoroo.andlib.utility.Preferences; import com.todoroo.astrid.dao.MetadataDao.MetadataCriteria; +import com.todoroo.astrid.data.SyncFlags; import com.todoroo.astrid.data.Task; import com.todoroo.astrid.data.TaskApiDao; import com.todoroo.astrid.reminders.Notifications; @@ -282,6 +284,68 @@ public class TaskDao extends DatabaseDao { return result; } + private static final Property[] SQL_CONSTRAINT_MERGE_PROPERTIES = new Property[] { + Task.ID, + Task.REMOTE_ID, + Task.TITLE, + Task.IMPORTANCE, + Task.DUE_DATE, + Task.CREATION_DATE, + Task.DELETION_DATE, + Task.NOTES, + Task.HIDE_UNTIL, + Task.RECURRENCE + }; + + public void saveExistingWithSqlConstraintCheck(Task item) { + try { + saveExisting(item); + } catch (SQLiteConstraintException e) { + Long remoteId = item.getValue(Task.REMOTE_ID); + TodorooCursor tasksWithRemoteId = query(Query.select(SQL_CONSTRAINT_MERGE_PROPERTIES) + .where(Task.REMOTE_ID.eq(remoteId))); + try { + if (tasksWithRemoteId.getCount() > 0) { + for (tasksWithRemoteId.moveToFirst(); + !tasksWithRemoteId.isAfterLast(); tasksWithRemoteId.moveToNext()) { + Task curr = new Task(tasksWithRemoteId); + if (curr.getId() == item.getId()) + continue; + + compareAndMergeAfterConflict(curr, fetch(item.getId(), SQL_CONSTRAINT_MERGE_PROPERTIES)); + return; + } + } else { + // We probably want to know about this case, because + // it means that the constraint error isn't caused by + // REMOTE_ID + throw e; + } + } finally { + tasksWithRemoteId.close(); + } + } + } + + private void compareAndMergeAfterConflict(Task existing, Task newConflict) { + boolean match = true; + for (Property p : SQL_CONSTRAINT_MERGE_PROPERTIES) { + if (!existing.getValue(p).equals(newConflict.getValue(p))) { + match = false; + } + } + if (!match) { + if (existing.getValue(Task.CREATION_DATE).equals(newConflict.getValue(Task.CREATION_DATE))) + newConflict.setValue(Task.CREATION_DATE, newConflict.getValue(Task.CREATION_DATE) + 1000L); + newConflict.clearValue(Task.REMOTE_ID); + newConflict.clearTransitory(SyncFlags.ACTFM_SUPPRESS_SYNC); + newConflict.clearTransitory(SyncFlags.GTASKS_SUPPRESS_SYNC); + saveExisting(newConflict); + } else { + delete(newConflict.getId()); + } + } + /** * Called after the task is saved. This differs from the call in * TaskApiDao in that it runs hooks that need to be run from within