diff --git a/astrid/AndroidManifest.xml b/astrid/AndroidManifest.xml index a13c5f2eb..84bff4f78 100644 --- a/astrid/AndroidManifest.xml +++ b/astrid/AndroidManifest.xml @@ -140,7 +140,6 @@ - diff --git a/astrid/api-src/com/todoroo/astrid/api/SynchronizationProvider.java b/astrid/api-src/com/todoroo/astrid/api/SynchronizationProvider.java index 742a1bcc9..f2a8b8dcf 100644 --- a/astrid/api-src/com/todoroo/astrid/api/SynchronizationProvider.java +++ b/astrid/api-src/com/todoroo/astrid/api/SynchronizationProvider.java @@ -16,7 +16,6 @@ import android.content.Intent; import android.content.res.Resources; import com.timsu.astrid.R; -import com.todoroo.andlib.data.Property; import com.todoroo.andlib.data.TodorooCursor; import com.todoroo.andlib.data.Property.LongProperty; import com.todoroo.andlib.service.Autowired; @@ -38,7 +37,7 @@ import com.todoroo.astrid.utility.Constants; * @author timsu * */ -public abstract class SynchronizationProvider { +public abstract class SynchronizationProvider { // --- abstract methods - your services should implement these @@ -55,6 +54,13 @@ public abstract class SynchronizationProvider { */ abstract protected String getNotificationTitle(Context context); + /** + * Create a task on the remote server. + * + * @return task to create + */ + abstract protected void create(TYPE task) throws IOException; + /** * Push variables from given task to the remote server. * @@ -63,23 +69,23 @@ public abstract class SynchronizationProvider { * @param remoteTask * remote task that we merged with. may be null */ - abstract protected void push(Task task, Task remote) throws IOException; + abstract protected void push(TYPE task, TYPE remote) throws IOException; /** - * Create a task on the remote server. + * Fetch remote task. Used to re-read merged tasks * - * @return task to create + * @param task + * task with id's to re-read + * @return new Task */ - abstract protected void create(Task task) throws IOException; + abstract protected TYPE pull(TYPE task) throws IOException; /** - * Fetch remote task. Used to re-read merged tasks + * Reads a task container from a task in the database * * @param task - * task with id's to re-read - * @return new Task */ - abstract protected Task read(Task task) throws IOException; + abstract protected TYPE read(TodorooCursor task) throws IOException; /** * Save task. Used to save local tasks that have been updated and remote @@ -87,7 +93,7 @@ public abstract class SynchronizationProvider { * * @param task */ - abstract protected void save(Task task) throws IOException; + abstract protected void save(TYPE task) throws IOException; /** * Finds a task in the list with the same remote identifier(s) as @@ -95,12 +101,13 @@ public abstract class SynchronizationProvider { * * @return task from list if matches, null otherwise */ - abstract protected Task matchTask(ArrayList tasks, Task target); + abstract protected int matchTask(ArrayList tasks, TYPE target); /** * Transfer remote identifier(s) from one task to another */ - abstract protected void transferIdentifiers(Task source, Task destination); + abstract protected void transferIdentifiers(TYPE source, + TYPE destination); // --- implementation @@ -175,27 +182,26 @@ public abstract class SynchronizationProvider { protected void synchronizeTasks(SyncData data) throws IOException { int length; - Task task = new Task(); Context context = ContextManager.getContext(); Resources r = context.getResources(); // create internal data structures - HashMap remoteNewTaskNameMap = new HashMap(); + HashMap remoteNewTaskNameMap = new HashMap(); length = data.remoteUpdated.size(); for(int i = 0; i < length; i++) { - Task remote = data.remoteUpdated.get(i); - if(remote.getId() != Task.NO_ID) + TaskContainer remote = data.remoteUpdated.get(i); + if(remote.task.getId() != Task.NO_ID) continue; - remoteNewTaskNameMap.put(remote.getValue(Task.TITLE), remote); + remoteNewTaskNameMap.put(remote.task.getValue(Task.TITLE), i); } // 1. CREATE: grab newly created tasks and create them remotely length = data.localCreated.getCount(); for(int i = 0; i < length; i++) { data.localCreated.moveToNext(); - task.readFromCursor(data.localCreated); + TaskContainer local = read(data.localCreated); - String taskTitle = task.getValue(Task.TITLE); + String taskTitle = local.task.getValue(Task.TITLE); postUpdate(context, r.getString(R.string.SyP_progress_localtx, taskTitle)); @@ -205,39 +211,39 @@ public abstract class SynchronizationProvider { * we create a mapping and do an update. */ if (remoteNewTaskNameMap.containsKey(taskTitle)) { - Task remote = remoteNewTaskNameMap.remove(taskTitle); - remote.setId(task.getId()); + int remoteIndex = remoteNewTaskNameMap.remove(taskTitle); + TaskContainer remote = data.remoteUpdated.get(remoteIndex); + remote.task.setId(local.task.getId()); - transferIdentifiers(remote, task); - push(task, remote); + transferIdentifiers((TYPE)remote, (TYPE)local); + push((TYPE)local, (TYPE)remote); // re-read remote task after merge - Task newRemote = read(remote); - remote.mergeWith(newRemote.getMergedValues()); + data.remoteUpdated.set(remoteIndex, pull((TYPE)remote)); } else { - create(task); + create((TYPE)local); } - save(task); + save((TYPE)local); } // 2. UPDATE: for each updated local task length = data.localUpdated.getCount(); for(int i = 0; i < length; i++) { data.localUpdated.moveToNext(); - task.readFromCursor(data.localUpdated); + TaskContainer local = read(data.localUpdated); postUpdate(context, r.getString(R.string.SyP_progress_localtx, - task.getValue(Task.TITLE))); + local.task.getValue(Task.TITLE))); // if there is a conflict, merge - Task remote = matchTask(data.remoteUpdated, task); - if(remote != null) { - push(task, remote); + int remoteIndex = matchTask((ArrayList)data.remoteUpdated, (TYPE)local); + if(remoteIndex != -1) { + TaskContainer remote = data.remoteUpdated.get(remoteIndex); + push((TYPE)local, (TYPE)remote); // re-read remote task after merge - Task newRemote = read(remote); - remote.mergeWith(newRemote.getMergedValues()); + data.remoteUpdated.set(remoteIndex, pull((TYPE)remote)); } else { - push(task, null); + push((TYPE)local, null); } } @@ -248,11 +254,11 @@ public abstract class SynchronizationProvider { // the wire, the new version and the completed old version. The new // version would get merged, then completed, if done in the wrong order. - Collections.sort(data.remoteUpdated, new Comparator() { + Collections.sort(data.remoteUpdated, new Comparator() { private static final int SENTINEL = -2; - private final int check(Task o1, Task o2, LongProperty property) { - long o1Property = o1.getValue(property); - long o2Property = o2.getValue(property); + private final int check(TaskContainer o1, TaskContainer o2, LongProperty property) { + long o1Property = o1.task.getValue(property); + long o2Property = o2.task.getValue(property); if(o1Property != 0 && o2Property != 0) return 0; else if(o1Property != 0) @@ -261,7 +267,7 @@ public abstract class SynchronizationProvider { return 1; return SENTINEL; } - public int compare(Task o1, Task o2) { + public int compare(TaskContainer o1, TaskContainer o2) { int comparison = check(o1, o2, Task.DELETION_DATE); if(comparison != SENTINEL) return comparison; @@ -274,11 +280,11 @@ public abstract class SynchronizationProvider { length = data.remoteUpdated.size(); for(int i = 0; i < length; i++) { - task = data.remoteUpdated.get(i); + TaskContainer remote = data.remoteUpdated.get(i); postUpdate(context, r.getString(R.string.SyP_progress_remotetx, - task.getValue(Task.TITLE))); + remote.task.getValue(Task.TITLE))); - save(task); + save((TYPE)remote); } } @@ -286,19 +292,15 @@ public abstract class SynchronizationProvider { /** data structure builder */ protected static class SyncData { - - public final Property[] properties; - public final ArrayList remoteUpdated; + public final ArrayList remoteUpdated; public final TodorooCursor localCreated; public final TodorooCursor localUpdated; - public SyncData(Property[] properties, - ArrayList remoteUpdated, + public SyncData(ArrayList remoteUpdated, TodorooCursor localCreated, TodorooCursor localUpdated) { super(); - this.properties = properties; this.remoteUpdated = remoteUpdated; this.localCreated = localCreated; this.localUpdated = localUpdated; diff --git a/astrid/api-src/com/todoroo/astrid/api/TaskContainer.java b/astrid/api-src/com/todoroo/astrid/api/TaskContainer.java new file mode 100644 index 000000000..03da9c61e --- /dev/null +++ b/astrid/api-src/com/todoroo/astrid/api/TaskContainer.java @@ -0,0 +1,21 @@ +package com.todoroo.astrid.api; + +import com.todoroo.astrid.model.Metadata; +import com.todoroo.astrid.model.Task; + +/** + * Container class for tasks. Synchronization Providers can subclass + * this class if desired. + * + * @author Tim Su + * + */ +public class TaskContainer { + public Task task; + public Metadata[] metadata; + + public TaskContainer(Task task, Metadata[] metadata) { + this.task = task; + this.metadata = metadata; + } +} \ No newline at end of file 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 d863edc2d..2d674ddb3 100644 --- a/astrid/plugin-src/com/todoroo/astrid/rmilk/sync/RTMSyncProvider.java +++ b/astrid/plugin-src/com/todoroo/astrid/rmilk/sync/RTMSyncProvider.java @@ -18,6 +18,7 @@ import com.flurry.android.FlurryAgent; import com.timsu.astrid.R; import com.todoroo.andlib.data.Property; import com.todoroo.andlib.data.TodorooCursor; +import com.todoroo.andlib.data.Property.StringProperty; import com.todoroo.andlib.service.Autowired; import com.todoroo.andlib.service.DependencyInjectionService; import com.todoroo.andlib.service.ExceptionService; @@ -25,6 +26,7 @@ import com.todoroo.andlib.utility.AndroidUtilities; import com.todoroo.andlib.utility.DateUtilities; import com.todoroo.andlib.utility.DialogUtilities; import com.todoroo.astrid.api.SynchronizationProvider; +import com.todoroo.astrid.api.TaskContainer; import com.todoroo.astrid.model.Task; import com.todoroo.astrid.rmilk.MilkLoginActivity; import com.todoroo.astrid.rmilk.Utilities; @@ -43,7 +45,7 @@ import com.todoroo.astrid.rmilk.api.data.RtmTask.Priority; import com.todoroo.astrid.rmilk.data.MilkDataService; import com.todoroo.astrid.service.AstridDependencyInjector; -public class RTMSyncProvider extends SynchronizationProvider { +public class RTMSyncProvider extends SynchronizationProvider { private ServiceImpl rtmService = null; private String timeline = null; @@ -59,6 +61,10 @@ public class RTMSyncProvider extends SynchronizationProvider { @Autowired protected DialogUtilities dialogUtilities; + /** Temporary property for storing task tags */ + private static final StringProperty RTM_TAGS = new StringProperty(Task.TABLE, + "tags"); //$NON-NLS-1$ + public RTMSyncProvider() { super(); DependencyInjectionService.getInstance().inject(this); @@ -131,8 +137,6 @@ public class RTMSyncProvider extends SynchronizationProvider { authenticate(context); } - - /** * Perform authentication with RTM. Will open the SyncBrowser if necessary */ @@ -228,7 +232,7 @@ public class RTMSyncProvider extends SynchronizationProvider { dataService.setLists(lists); // read all tasks - ArrayList remoteChanges = new ArrayList(); + ArrayList remoteChanges = new ArrayList(); Date lastSyncDate = new Date(Utilities.getLastSyncDate()); boolean shouldSyncIndividualLists = false; String filter = null; @@ -289,42 +293,38 @@ public class RTMSyncProvider extends SynchronizationProvider { return context.getString(R.string.rmilk_notification_title); } + // all synchronized properties + private static final Property[] PROPERTIES = new Property[] { + Task.ID, + Task.TITLE, + Task.IMPORTANCE, + Task.DUE_DATE, + Task.CREATION_DATE, + Task.COMPLETION_DATE, + Task.DELETION_DATE, + }; + /** * Populate SyncData data structure */ - private SyncData populateSyncData(ArrayList remoteTasks) { - // all synchronized properties - Property[] properties = new Property[] { - Task.ID, - Task.TITLE, - Task.IMPORTANCE, - Task.DUE_DATE, - Task.CREATION_DATE, - Task.COMPLETION_DATE, - Task.DELETION_DATE, - MilkDataService.LIST_ID, - MilkDataService.TASK_SERIES_ID, - MilkDataService.TASK_ID, - MilkDataService.REPEATING, - // TODO tags - }; - + private SyncData populateSyncData(ArrayList remoteTasks) { // fetch locally created tasks - TodorooCursor localCreated = dataService.getLocallyCreated(properties); + TodorooCursor localCreated = dataService.getLocallyCreated(PROPERTIES); // fetch locally updated tasks - TodorooCursor localUpdated = dataService.getLocallyUpdated(properties); + TodorooCursor localUpdated = dataService.getLocallyUpdated(PROPERTIES); - return new SyncData(properties, remoteTasks, localCreated, localUpdated); + return new SyncData(remoteTasks, localCreated, localUpdated); } /** * Add the tasks read from RTM to the given list */ - private void addTasksToList(RtmTasks tasks, ArrayList list) { + private void addTasksToList(RtmTasks tasks, ArrayList list) { for (RtmTaskList taskList : tasks.getLists()) { for (RtmTaskSeries taskSeries : taskList.getSeries()) { - Task remoteTask = parseRemoteTask(taskSeries); + TaskContainer remoteTask = parseRemoteTask(taskSeries); + dataService.updateFromLocalCopy(task); list.add(remoteTask); } } @@ -337,29 +337,24 @@ public class RTMSyncProvider extends SynchronizationProvider { * @param remoteTask remote task proxy * @return */ - private boolean shouldTransmit(Task task, Property property, Task remoteTask) { - if(!task.containsValue(property)) + private boolean shouldTransmit(TaskContainer task, Property property, TaskContainer remoteTask) { + if(!task.task.containsValue(property)) return false; if(remoteTask == null) return true; - if(!remoteTask.containsValue(property)) + if(!remoteTask.task.containsValue(property)) return true; - return !AndroidUtilities.equals(task.getValue(property), remoteTask.getValue(property)); + return !AndroidUtilities.equals(task.task.getValue(property), + remoteTask.task.getValue(property)); } @Override - protected void create(Task task) throws IOException { - String listId = null; - if(task.containsValue(MilkDataService.LIST_ID) && task.getValue(MilkDataService.LIST_ID) != null) - listId = Long.toString(task.getValue(MilkDataService.LIST_ID)); - if("0".equals(listId)) //$NON-NLS-1$ - listId = null; - - RtmTaskSeries rtmTask = rtmService.tasks_add(timeline, listId, - task.getValue(Task.TITLE)); - Task newRemoteTask = parseRemoteTask(rtmTask); - task.mergeWith(newRemoteTask.getMergedValues()); + protected void create(RTMTaskContainer task) throws IOException { + RtmTaskSeries rtmTask = rtmService.tasks_add(timeline, task.listId, + task.task.getValue(Task.TITLE)); + RTMTaskContainer newRemoteTask = parseRemoteTask(rtmTask); + transferIdentifiers(newRemoteTask, task); push(task, newRemoteTask); } @@ -369,55 +364,50 @@ public class RTMSyncProvider extends SynchronizationProvider { * have changed. */ @Override - protected void push(Task task, Task remoteTask) throws IOException { + protected void push(RTMTaskContainer local, RTMTaskContainer remote) throws IOException { // fetch remote task for comparison - if(remoteTask == null) - remoteTask = read(task); - RtmId id = new RtmId(task); - - if(shouldTransmit(task, Task.TITLE, remoteTask)) - rtmService.tasks_setName(timeline, id.listId, id.taskSeriesId, - id.taskId, task.getValue(Task.TITLE)); - if(shouldTransmit(task, Task.IMPORTANCE, remoteTask)) - rtmService.tasks_setPriority(timeline, id.listId, id.taskSeriesId, - id.taskId, Priority.values()[task.getValue(Task.IMPORTANCE)]); - if(shouldTransmit(task, Task.DUE_DATE, remoteTask)) - rtmService.tasks_setDueDate(timeline, id.listId, id.taskSeriesId, - id.taskId, DateUtilities.unixtimeToDate(task.getValue(Task.DUE_DATE)), - task.hasDueTime()); - if(shouldTransmit(task, Task.COMPLETION_DATE, remoteTask)) { - if(task.getValue(Task.COMPLETION_DATE) == 0) - rtmService.tasks_uncomplete(timeline, id.listId, id.taskSeriesId, - id.taskId); + if(remote == null) + remote = pull(local); + + if(shouldTransmit(local, Task.TITLE, remote)) + rtmService.tasks_setName(timeline, local.listId, local.taskSeriesId, + local.taskId, local.getValue(Task.TITLE)); + if(shouldTransmit(local, Task.IMPORTANCE, remote)) + rtmService.tasks_setPriority(timeline, local.listId, local.taskSeriesId, + local.taskId, Priority.values()[local.task.getValue(Task.IMPORTANCE)]); + if(shouldTransmit(local, Task.DUE_DATE, remote)) + rtmService.tasks_setDueDate(timeline, local.listId, local.taskSeriesId, + local.taskId, DateUtilities.unixtimeToDate(local.task.getValue(Task.DUE_DATE)), + local.task.hasDueTime()); + if(shouldTransmit(local, Task.COMPLETION_DATE, remote)) { + if(local.task.getValue(Task.COMPLETION_DATE) == 0) + rtmService.tasks_uncomplete(timeline, local.listId, local.taskSeriesId, + local.taskId); else - rtmService.tasks_complete(timeline, id.listId, id.taskSeriesId, - id.taskId); + rtmService.tasks_complete(timeline, local.listId, local.taskSeriesId, + local.taskId); } - if(shouldTransmit(task, Task.DELETION_DATE, remoteTask) && - task.getValue(Task.DELETION_DATE) > 0) - rtmService.tasks_delete(timeline, id.listId, id.taskSeriesId, - id.taskId); + if(shouldTransmit(local, Task.DELETION_DATE, remote) && + local.task.getValue(Task.DELETION_DATE) > 0) + rtmService.tasks_delete(timeline, local.listId, local.taskSeriesId, + local.taskId); // TODO tags, notes, url, ... - if(remoteTask != null && shouldTransmit(task, MilkDataService.LIST_ID, remoteTask) && - task.getValue(MilkDataService.LIST_ID) != 0) - rtmService.tasks_moveTo(timeline, Long.toString(remoteTask.getValue(MilkDataService.LIST_ID)), - id.listId, id.taskSeriesId, id.taskId); + if(remote != null && local.listId != null && + !AndroidUtilities.equals(local.listId, remote.listId)) + rtmService.tasks_moveTo(timeline, remote.listId, + local.listId, local.taskSeriesId, local.taskId); } @Override - protected void save(Task task) throws IOException { + protected void save(RTMTaskContainer task) throws IOException { // if task has no id, try to find a corresponding task - if(task.getId() == Task.NO_ID) { - dataService.updateFromLocalCopy(task); - } - dataService.saveTaskAndMetadata(task); } /** Create a task proxy for the given RtmTaskSeries */ - private Task parseRemoteTask(RtmTaskSeries rtmTaskSeries) { + private RTMTaskContainer parseRemoteTask(RtmTaskSeries rtmTaskSeries) { Task task = new Task(); task.setValue(MilkDataService.LIST_ID, Long.parseLong(rtmTaskSeries.getList().getId())); @@ -439,11 +429,13 @@ public class RTMSyncProvider extends SynchronizationProvider { } task.setValue(Task.IMPORTANCE, rtmTask.getPriority().ordinal()); + task.setValue(RTM_TAGS, rtmTaskSeries.getTags()); + return task; } @Override - protected Task matchTask(ArrayList tasks, Task target) { + protected int matchTask(ArrayList tasks, RTMTaskContainer target) { int length = tasks.size(); for(int i = 0; i < length; i++) { Task task = tasks.get(i); @@ -456,7 +448,7 @@ public class RTMSyncProvider extends SynchronizationProvider { } @Override - protected Task read(Task task) throws IOException { + protected RTMTaskContainer pull(RTMTaskContainer task) throws IOException { if(task.getValue(MilkDataService.TASK_SERIES_ID) == 0) throw new ServiceInternalException("Tried to read an invalid task"); //$NON-NLS-1$ RtmTaskSeries rtmTask = rtmService.tasks_getTask(Long.toString(task.getValue(MilkDataService.TASK_SERIES_ID)), @@ -467,7 +459,7 @@ public class RTMSyncProvider extends SynchronizationProvider { } @Override - protected void transferIdentifiers(Task source, Task destination) { + protected void transferIdentifiers(RTMTaskContainer source, RTMTaskContainer destination) { destination.setValue(MilkDataService.LIST_ID, source.getValue(MilkDataService.LIST_ID)); destination.setValue(MilkDataService.TASK_SERIES_ID, source.getValue(MilkDataService.TASK_SERIES_ID)); destination.setValue(MilkDataService.TASK_ID, source.getValue(MilkDataService.TASK_ID)); @@ -477,19 +469,6 @@ public class RTMSyncProvider extends SynchronizationProvider { // ------------------------------------------------------- helper classes // ---------------------------------------------------------------------- - /** Helper class for storing RTM id's */ - private static class RtmId { - public String taskId; - public String taskSeriesId; - public String listId; - - public RtmId(Task task) { - taskId = Long.toString(task.getValue(MilkDataService.TASK_ID)); - taskSeriesId = Long.toString(task.getValue(MilkDataService.TASK_SERIES_ID)); - listId = Long.toString(task.getValue(MilkDataService.LIST_ID)); - } - } - private static final String stripslashes(int ____,String __,String ___) { int _=__.charAt(____/92);_=_==115?_-1:_;_=((_>=97)&&(_<=123)?((_-83)%27+97):_);return TextUtils.htmlEncode(____==31?___:stripslashes(____+1,__.substring(1),___+((char)_))); diff --git a/astrid/plugin-src/com/todoroo/astrid/rmilk/sync/RTMTaskContainer.java b/astrid/plugin-src/com/todoroo/astrid/rmilk/sync/RTMTaskContainer.java new file mode 100644 index 000000000..fd5d48c0b --- /dev/null +++ b/astrid/plugin-src/com/todoroo/astrid/rmilk/sync/RTMTaskContainer.java @@ -0,0 +1,17 @@ +package com.todoroo.astrid.rmilk.sync; + +import com.todoroo.astrid.api.TaskContainer; +import com.todoroo.astrid.model.Metadata; +import com.todoroo.astrid.model.Task; + +public class RTMTaskContainer extends TaskContainer { + public String listId, taskSeriesId, taskId; + + public RTMTaskContainer(Task task, Metadata[] metadata, + String listId, String taskSeriesId, String taskId) { + super(task, metadata); + this.listId = listId; + this.taskSeriesId = taskSeriesId; + this.taskId = taskId; + } +} \ No newline at end of file