diff --git a/astrid/api-src/com/todoroo/astrid/api/SynchronizationProvider.java b/astrid/api-src/com/todoroo/astrid/api/SynchronizationProvider.java index 3eb40d929..f01761f1a 100644 --- a/astrid/api-src/com/todoroo/astrid/api/SynchronizationProvider.java +++ b/astrid/api-src/com/todoroo/astrid/api/SynchronizationProvider.java @@ -44,16 +44,32 @@ public abstract class SynchronizationProvider { /** * Perform authenticate and other pre-synchronization steps, then * synchronize. - * @param context either the parent activity, or a background service + * + * @param context + * either the parent activity, or a background service */ abstract protected void initiate(Context context); /** + * Gets title of the notification bar notification + * * @param context * @return title of notification */ abstract protected String getNotificationTitle(Context context); + /** + * Deal with an exception that occurs during synchronization + * + * @param tag + * short string description of where error occurred + * @param e + * exception + * @param displayError + * whether to display error to the user + */ + abstract protected void handleException(String tag, Exception e, boolean displayError); + /** * Create a task on the remote server. * @@ -207,52 +223,60 @@ public abstract class SynchronizationProvider { length = data.localCreated.getCount(); for(int i = 0; i < length; i++) { data.localCreated.moveToNext(); - TYPE local = read(data.localCreated); - - String taskTitle = local.task.getValue(Task.TITLE); - - /* If there exists an incoming remote task with the same name and no - * mapping, we don't want to create this on the remote server, - * because user could have synchronized like this before. Instead, - * we create a mapping and do an update. - */ - if (remoteNewTaskNameMap.containsKey(taskTitle)) { - int remoteIndex = remoteNewTaskNameMap.remove(taskTitle); - TYPE remote = data.remoteUpdated.get(remoteIndex); - - transferIdentifiers(remote, local); - push(local, remote); - - // re-read remote task after merge, update remote task list - remote = pull(remote); - remote.task.setId(local.task.getId()); - data.remoteUpdated.set(remoteIndex, remote); - } else { - create(local); + try { + TYPE local = read(data.localCreated); + + String taskTitle = local.task.getValue(Task.TITLE); + + /* If there exists an incoming remote task with the same name and no + * mapping, we don't want to create this on the remote server, + * because user could have synchronized like this before. Instead, + * we create a mapping and do an update. + */ + if (remoteNewTaskNameMap.containsKey(taskTitle)) { + int remoteIndex = remoteNewTaskNameMap.remove(taskTitle); + TYPE remote = data.remoteUpdated.get(remoteIndex); + + transferIdentifiers(remote, local); + push(local, remote); + + // re-read remote task after merge, update remote task list + remote = pull(remote); + remote.task.setId(local.task.getId()); + data.remoteUpdated.set(remoteIndex, remote); + } else { + create(local); + } + write(local); + } catch (Exception e) { + handleException("sync-local-created", e, false); //$NON-NLS-1$ } - write(local); } // 2. UPDATE: for each updated local task length = data.localUpdated.getCount(); for(int i = 0; i < length; i++) { data.localUpdated.moveToNext(); - TYPE local = read(data.localUpdated); - if(local.task == null) - continue; - - // if there is a conflict, merge - int remoteIndex = matchTask((ArrayList)data.remoteUpdated, local); - if(remoteIndex != -1) { - TYPE remote = data.remoteUpdated.get(remoteIndex); - push(local, remote); - - // re-read remote task after merge - remote = pull(remote); - remote.task.setId(local.task.getId()); - data.remoteUpdated.set(remoteIndex, remote); - } else { - push(local, null); + try { + TYPE local = read(data.localUpdated); + if(local.task == null) + continue; + + // if there is a conflict, merge + int remoteIndex = matchTask((ArrayList)data.remoteUpdated, local); + if(remoteIndex != -1) { + TYPE remote = data.remoteUpdated.get(remoteIndex); + push(local, remote); + + // re-read remote task after merge + remote = pull(remote); + remote.task.setId(local.task.getId()); + data.remoteUpdated.set(remoteIndex, remote); + } else { + push(local, null); + } + } catch (Exception e) { + handleException("sync-local-updated", e, false); //$NON-NLS-1$ } } @@ -290,7 +314,11 @@ public abstract class SynchronizationProvider { length = data.remoteUpdated.size(); for(int i = 0; i < length; i++) { TYPE remote = data.remoteUpdated.get(i); - write(remote); + try { + write(remote); + } catch (Exception e) { + handleException("sync-remote-updated", e, false); //$NON-NLS-1$ + } } } diff --git a/astrid/common-src/com/todoroo/andlib/data/AbstractModel.java b/astrid/common-src/com/todoroo/andlib/data/AbstractModel.java index 5a0f68418..b478c06ca 100644 --- a/astrid/common-src/com/todoroo/andlib/data/AbstractModel.java +++ b/astrid/common-src/com/todoroo/andlib/data/AbstractModel.java @@ -219,6 +219,19 @@ public abstract class AbstractModel implements Parcelable { return false; } + /** + * @param property + * @return true if setValues or values contains this property, and the value + * stored is not null + */ + public boolean containsNonNullValue(Property property) { + if(setValues != null && setValues.containsKey(property.name)) + return setValues.get(property.name) != null; + if(values != null && values.containsKey(property.name)) + return values.get(property.name) != null; + return false; + } + // --- data storage /** diff --git a/astrid/plugin-src/com/todoroo/astrid/rmilk/MilkFilterExposer.java b/astrid/plugin-src/com/todoroo/astrid/rmilk/MilkFilterExposer.java index 2178ffb95..eebdf9c6c 100644 --- a/astrid/plugin-src/com/todoroo/astrid/rmilk/MilkFilterExposer.java +++ b/astrid/plugin-src/com/todoroo/astrid/rmilk/MilkFilterExposer.java @@ -39,6 +39,9 @@ public class MilkFilterExposer extends BroadcastReceiver { ContentValues values = new ContentValues(); values.put(Metadata.KEY.name, MilkTask.METADATA_KEY); values.put(MilkTask.LIST_ID.name, list.id); + values.put(MilkTask.TASK_SERIES_ID.name, 0); + values.put(MilkTask.TASK_ID.name, 0); + values.put(MilkTask.REPEATING.name, 0); Filter filter = new Filter(listTitle, title, new QueryTemplate().join( MilkDataService.METADATA_JOIN).where(Criterion.and( MetadataCriteria.withKey(MilkTask.METADATA_KEY), diff --git a/astrid/plugin-src/com/todoroo/astrid/rmilk/sync/RTMSyncProvider.java b/astrid/plugin-src/com/todoroo/astrid/rmilk/sync/RTMSyncProvider.java index 110c4513b..abeef4883 100644 --- a/astrid/plugin-src/com/todoroo/astrid/rmilk/sync/RTMSyncProvider.java +++ b/astrid/plugin-src/com/todoroo/astrid/rmilk/sync/RTMSyncProvider.java @@ -22,6 +22,7 @@ import com.timsu.astrid.R; import com.todoroo.andlib.data.Property; import com.todoroo.andlib.data.TodorooCursor; import com.todoroo.andlib.service.Autowired; +import com.todoroo.andlib.service.ContextManager; import com.todoroo.andlib.service.DependencyInjectionService; import com.todoroo.andlib.service.ExceptionService; import com.todoroo.andlib.utility.AndroidUtilities; @@ -100,12 +101,11 @@ public class RTMSyncProvider extends SynchronizationProvider { * error tag * @param e * exception - * @param showErrorIfNeeded + * @param showError * whether to display a dialog */ - private void handleRtmException(Context context, String tag, Exception e, - boolean showErrorIfNeeded) { - + @Override + protected void handleException(String tag, Exception e, boolean showError) { Utilities.setLastError(e.toString()); // occurs when application was closed @@ -118,7 +118,8 @@ public class RTMSyncProvider extends SynchronizationProvider { IOException) { Exception enclosedException = ((ServiceInternalException)e).getEnclosedException(); exceptionService.reportError(tag + "-ioexception", enclosedException); //$NON-NLS-1$ - if(showErrorIfNeeded) { + if(showError) { + Context context = ContextManager.getContext(); showError(context, enclosedException, context.getString(R.string.rmilk_ioerror)); } @@ -126,8 +127,10 @@ public class RTMSyncProvider extends SynchronizationProvider { if(e instanceof ServiceInternalException) e = ((ServiceInternalException)e).getEnclosedException(); exceptionService.reportError(tag + "-unhandled", e); //$NON-NLS-1$ - if(showErrorIfNeeded) + if(showError) { + Context context = ContextManager.getContext(); showError(context, e, null); + } } } @@ -169,7 +172,7 @@ public class RTMSyncProvider extends SynchronizationProvider { try { String token = rtmService.completeAuthorization(); Utilities.setToken(token); - performSync(context); + performSync(); return; } catch (Exception e) { @@ -211,12 +214,12 @@ public class RTMSyncProvider extends SynchronizationProvider { context.startActivity(intent); } else { - performSync(context); + performSync(); } } catch (IllegalStateException e) { // occurs when application was closed } catch (Exception e) { - handleRtmException(context, "rtm-authenticate", e, true); + handleException("rtm-authenticate", e, true); } finally { Utilities.stopOngoing(); } @@ -226,7 +229,7 @@ public class RTMSyncProvider extends SynchronizationProvider { // ----------------------------------------------------- synchronization! // ---------------------------------------------------------------------- - protected void performSync(final Context context) { + protected void performSync() { try { // get RTM timeline timeline = rtmService.timelines_create(); @@ -249,7 +252,7 @@ public class RTMSyncProvider extends SynchronizationProvider { RtmTasks tasks = rtmService.tasks_getList(null, filter, lastSyncDate); addTasksToList(tasks, remoteChanges); } catch (Exception e) { - handleRtmException(context, "rtm-quick-sync", e, false); //$NON-NLS-1$ + handleException("rtm-quick-sync", e, false); //$NON-NLS-1$ remoteChanges.clear(); shouldSyncIndividualLists = true; } @@ -264,7 +267,7 @@ public class RTMSyncProvider extends SynchronizationProvider { filter, lastSyncDate); addTasksToList(tasks, remoteChanges); } catch (Exception e) { - handleRtmException(context, "rtm-indiv-sync", e, true); //$NON-NLS-1$ + handleException("rtm-indiv-sync", e, true); //$NON-NLS-1$ continue; } } @@ -284,7 +287,7 @@ public class RTMSyncProvider extends SynchronizationProvider { } catch (IllegalStateException e) { // occurs when application was closed } catch (Exception e) { - handleRtmException(context, "rtm-sync", e, true); //$NON-NLS-1$ + handleException("rtm-sync", e, true); //$NON-NLS-1$ } } diff --git a/astrid/plugin-src/com/todoroo/astrid/rmilk/sync/RTMTaskContainer.java b/astrid/plugin-src/com/todoroo/astrid/rmilk/sync/RTMTaskContainer.java index 0324ed976..82e8cfaac 100644 --- a/astrid/plugin-src/com/todoroo/astrid/rmilk/sync/RTMTaskContainer.java +++ b/astrid/plugin-src/com/todoroo/astrid/rmilk/sync/RTMTaskContainer.java @@ -44,13 +44,13 @@ public class RTMTaskContainer extends TaskContainer { for(Iterator iterator = metadata.iterator(); iterator.hasNext(); ) { Metadata item = iterator.next(); if(MilkTask.METADATA_KEY.equals(item.getValue(Metadata.KEY))) { - if(item.containsValue(MilkTask.LIST_ID)) + if(item.containsNonNullValue(MilkTask.LIST_ID)) listId = item.getValue(MilkTask.LIST_ID); - if(item.containsValue(MilkTask.TASK_SERIES_ID)) + if(item.containsNonNullValue(MilkTask.TASK_SERIES_ID)) taskSeriesId = item.getValue(MilkTask.TASK_SERIES_ID); - if(item.containsValue(MilkTask.TASK_ID)) + if(item.containsNonNullValue(MilkTask.TASK_ID)) taskId = item.getValue(MilkTask.TASK_ID); - if(item.containsValue(MilkTask.REPEATING)) + if(item.containsNonNullValue(MilkTask.REPEATING)) repeating = item.getValue(MilkTask.REPEATING) == 1; iterator.remove(); break;