- Moved estimated time back to 1st page of edit box.

- Persisting sort status
  - Sync:
    - per-list sync happens only if one-list sync fails
    - adding some delay between grabbing the task list
    - tags
    - estimated time
    - reuse taskcontroller crash solved
    - sync dialog gets the name of the task
pull/14/head
Tim Su 17 years ago
parent b9aaa8e95f
commit 3edc4163b9

@ -1,13 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.timsu.astrid"
android:versionCode="61"
android:versionName="2.0.0-rc2">
android:versionCode="62"
android:versionName="2.0.0-rc3">
<meta-data android:name="com.a0soft.gphone.aTrackDog.webURL"
android:value="http://www.weloveastrid.com" />
<meta-data android:name="com.a0soft.gphone.aTrackDog.testVersion"
android:value="61" />
android:value="62" />
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.INTERNET"/>

@ -76,6 +76,16 @@
android:layout_height="1dip"
android:background="@android:drawable/divider_horizontal_dark"
/>
<TextView android:id="@+id/estimatedDuration_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/estimatedDuration_label"
style="@style/TextAppearance.EditEvent_Label"/>
<Button android:id="@+id/estimatedDuration"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
<TextView android:id="@+id/notes_label"
android:paddingTop="5dip"
@ -258,17 +268,7 @@
android:background="@android:drawable/divider_horizontal_dark"
/>
<TextView android:id="@+id/estimatedDuration_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/estimatedDuration_label"
style="@style/TextAppearance.EditEvent_Label"/>
<Button android:id="@+id/estimatedDuration"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
<TextView android:id="@+id/elapsedDuration_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"

@ -75,7 +75,7 @@
<string name="taskList_titlePrefix">Astrid: </string>
<string name="taskList_titleTagPrefix">Tagged \"%s\": </string>
<string name="taskList_hiddenSuffix"> hidden</string>
<string name="taskList_dueIn">Due in</string>
<string name="taskList_dueIn">Due:</string>
<string name="taskList_goalPrefix">Goal</string>
<string name="taskList_completedPrefix">Finished</string>
<string name="taskList_overdueBy">Overdue by</string>
@ -220,8 +220,7 @@ When finished, restart Astrid and come back here.
<string name="sync_rtm_notes">
Welcome to Astrid\'s RTM sync!
\n\n
- RTM tags are not read. Instead, RTM's lists are mapped to Astrid\'s tags.\n
- Task notes are read from RTM, but edits are not written back.\n
- Task notes are read from RTM, but edits are not sent back.\n
- Notifications and repeats are not synchronized.\n
- Moving tasks in RTM to another list and then renaming them causes dupes.\n
- Deleting tasks in RTM is not detected.\n

@ -96,7 +96,7 @@ public class Invoker {
public static String API_SIG_PARAM = "api_sig";
public static final long INVOCATION_INTERVAL = 500;
public static final long INVOCATION_INTERVAL = 750;
private long lastInvocation;

@ -35,7 +35,7 @@ import com.mdt.rtm.data.RtmTask.Priority;
/**
* Represents the Remember the Milk service API.
*
*
* @author Will Ross Jun 21, 2007
*/
public interface Service
@ -44,7 +44,7 @@ public interface Service
/**
* Checks whether the service is authorized to communicate with the RTM server. Depends on the user's login info, and whether or not that user has
* authorized the service wrapper to communicate with RTM.
*
*
* @return true if the service API has permission to interact with full permissions (including delete) with RTM
* @throws ServiceException
* if there is a problem checking for authorization
@ -54,7 +54,7 @@ public interface Service
/**
* Begins the process of obtaining authorization for the service API to communicate with RTM on behalf of a particular user.
*
*
* @return the URL that the user should be prompted to log in to to complete authorization
* @throws ServiceException
* if the authorization process cannot be started
@ -65,7 +65,7 @@ public interface Service
/**
* The same method as the previous {@link #beginAuthorization(com.mdt.rtm.data.RtmAuth.Perms)}, except that you need to invoke yourself the
* {@link #auth_getFrob()} beforehand.
*
*
* This has been introduced, in order to provide better control over the API.
*/
String beginAuthorization(RtmFrob frob, RtmAuth.Perms permissions)
@ -73,10 +73,10 @@ public interface Service
/**
* Completes the process of obtaining authorization for the service API to communicate with RTM on behalf of a particular user.
*
*
* Once this is called successfully, <code>isServiceAuthorized()</code> should return true until the user goes to RTM and explicitly denies the
* service access. It also might be possible for authorization to time out, in which case this process would need to be started again.
*
*
* @return the newly created authentication token
* @throws ServiceException
* if the authorization process cannot be completed
@ -163,7 +163,7 @@ public interface Service
void tasks_addTags()
throws ServiceException;
public RtmTaskSeries tasks_complete(String timelineId, String listId, String taskSeriesId, String taskId)
public void tasks_complete(String timelineId, String listId, String taskSeriesId, String taskId)
throws ServiceException;
void tasks_delete(String timelineId, String listId, String taskSeriesId, String taskId)
@ -196,28 +196,28 @@ public interface Service
/**
* THINK: Would it not be better to have a {@link GregorianCalendar} parameter instead?
*/
RtmTaskSeries tasks_setDueDate(String timelineId, String listId, String taskSeriesId, String taskId, Date due, boolean hasDueTime)
void tasks_setDueDate(String timelineId, String listId, String taskSeriesId, String taskId, Date due, boolean hasDueTime)
throws ServiceException;
void tasks_setEstimate()
void tasks_setEstimate(String timelineId, String listId, String taskSeriesId, String taskId, String newEstimate)
throws ServiceException;
RtmTaskSeries tasks_setName(String timelineId, String listId, String taskSeriesId, String taskId, String newName)
void tasks_setName(String timelineId, String listId, String taskSeriesId, String taskId, String newName)
throws ServiceException;
RtmTaskSeries tasks_setPriority(String timelineId, String listId, String taskSeriesId, String taskId, Priority priority)
void tasks_setPriority(String timelineId, String listId, String taskSeriesId, String taskId, Priority priority)
throws ServiceException;
void tasks_setRecurrence()
throws ServiceException;
void tasks_setTags()
void tasks_setTags(String timelineId, String listId, String taskSeriesId, String taskId, String[] tags)
throws ServiceException;
void tasks_setURL()
throws ServiceException;
RtmTaskSeries tasks_uncomplete(String timelineId, String listId, String taskSeriesId, String taskId)
void tasks_uncomplete(String timelineId, String listId, String taskSeriesId, String taskId)
throws ServiceException;
RtmTaskNote tasks_notes_add(String timelineId, String listId, String taskSeriesId, String taskId, String title, String text)

@ -48,8 +48,7 @@ import com.mdt.rtm.data.RtmTask.Priority;
* @author Will Ross Jun 21, 2007
* @author Edouard Mercier, since 2008.04.15
*/
public class ServiceImpl
implements Service
public class ServiceImpl implements Service
{
public final static String SERVER_HOST_NAME = "api.rememberthemilk.com"; //"74.86.175.154"; // api.rememberthemilk.com
@ -326,14 +325,12 @@ public class ServiceImpl
throw new UnsupportedOperationException("Not supported yet.");
}
public RtmTaskSeries tasks_complete(String timelineId, String listId, String taskSeriesId, String taskId)
public void tasks_complete(String timelineId, String listId, String taskSeriesId, String taskId)
throws ServiceException
{
Element response = invoker.invoke(new Param("method", "rtm.tasks.complete"), new Param("timeline", timelineId), new Param("list_id", listId),
invoker.invoke(new Param("method", "rtm.tasks.complete"), new Param("timeline", timelineId), new Param("list_id", listId),
new Param("taskseries_id", taskSeriesId), new Param("task_id", taskId), new Param("auth_token", currentAuthToken),
new Param("api_key", applicationInfo.getApiKey()));
RtmTaskList rtmTaskList = new RtmTaskList(response);
return findTask(taskSeriesId, taskId, rtmTaskList);
}
public void tasks_delete(String timelineId, String listId, String taskSeriesId, String taskId)
@ -372,7 +369,7 @@ public class ServiceImpl
return tasks_getTask(null, taskName);
}
public RtmTaskSeries tasks_getTask(String taskId, String taskName)
public RtmTaskSeries tasks_getTask(String taskSeriesId, String taskName)
throws ServiceException
{
Set<Param> params = new HashSet<Param>();
@ -381,7 +378,7 @@ public class ServiceImpl
params.add(new Param("api_key", applicationInfo.getApiKey()));
params.add(new Param("filter", "name:" + taskName));
RtmTasks rtmTasks = new RtmTasks(invoker.invoke(params.toArray(new Param[params.size()])));
return findTask(taskId, rtmTasks);
return findTask(taskSeriesId, rtmTasks);
}
private RtmTaskSeries findTask(String taskId, RtmTasks rtmTasks)
@ -431,40 +428,38 @@ public class ServiceImpl
throw new UnsupportedOperationException("Not supported yet.");
}
public RtmTaskSeries tasks_setDueDate(String timelineId, String listId, String taskSeriesId, String taskId, Date due, boolean hasDueTime)
public void tasks_setDueDate(String timelineId, String listId, String taskSeriesId, String taskId, Date due, boolean hasDueTime)
throws ServiceException
{
final boolean setDueDate = (due != null);
final Element elt;
if (setDueDate == true)
{
elt = invoker.invoke(new Param("method", "rtm.tasks.setDueDate"), new Param("timeline", timelineId), new Param("list_id", listId),
invoker.invoke(new Param("method", "rtm.tasks.setDueDate"), new Param("timeline", timelineId), new Param("list_id", listId),
new Param("taskseries_id", taskSeriesId), new Param("task_id", taskId), new Param("due", due), new Param("has_due_time", hasDueTime ? "1" : "0"),
new Param("auth_token", currentAuthToken), new Param("api_key", applicationInfo.getApiKey()));
}
else
{
elt = invoker.invoke(new Param("method", "rtm.tasks.setDueDate"), new Param("timeline", timelineId), new Param("list_id", listId),
invoker.invoke(new Param("method", "rtm.tasks.setDueDate"), new Param("timeline", timelineId), new Param("list_id", listId),
new Param("taskseries_id", taskSeriesId), new Param("task_id", taskId), new Param("auth_token", currentAuthToken),
new Param("api_key", applicationInfo.getApiKey()));
}
final RtmTaskList rtmTaskList = new RtmTaskList(elt);
return findTask(taskSeriesId, taskId, rtmTaskList);
}
public void tasks_setEstimate()
public void tasks_setEstimate(String timelineId, String listId, String taskSeriesId, String taskId, String newEstimate)
throws ServiceException
{
throw new UnsupportedOperationException("Not supported yet.");
invoker.invoke(new Param("method", "rtm.tasks.setEstimate"), new Param("timeline", timelineId), new Param("list_id", listId),
new Param("taskseries_id", taskSeriesId), new Param("task_id", taskId), new Param("estimate", newEstimate), new Param("auth_token", currentAuthToken),
new Param("api_key", applicationInfo.getApiKey()));
}
public RtmTaskSeries tasks_setName(String timelineId, String listId, String taskSeriesId, String taskId, String newName)
public void tasks_setName(String timelineId, String listId, String taskSeriesId, String taskId, String newName)
throws ServiceException
{
Element elt = invoker.invoke(new Param("method", "rtm.tasks.setName"), new Param("timeline", timelineId), new Param("list_id", listId),
invoker.invoke(new Param("method", "rtm.tasks.setName"), new Param("timeline", timelineId), new Param("list_id", listId),
new Param("taskseries_id", taskSeriesId), new Param("task_id", taskId), new Param("name", newName), new Param("auth_token", currentAuthToken),
new Param("api_key", applicationInfo.getApiKey()));
RtmTaskList rtmTaskList = new RtmTaskList(elt);
return findTask(taskSeriesId, taskId, rtmTaskList);
}
private RtmTaskSeries findTask(String taskSeriesId, String taskId, RtmTaskList rtmTaskList)
@ -479,14 +474,12 @@ public class ServiceImpl
return null;
}
public RtmTaskSeries tasks_setPriority(String timelineId, String listId, String taskSeriesId, String taskId, Priority priority)
public void tasks_setPriority(String timelineId, String listId, String taskSeriesId, String taskId, Priority priority)
throws ServiceException
{
Element elt = invoker.invoke(new Param("method", "rtm.tasks.setPriority"), new Param("timeline", timelineId), new Param("list_id", listId),
invoker.invoke(new Param("method", "rtm.tasks.setPriority"), new Param("timeline", timelineId), new Param("list_id", listId),
new Param("taskseries_id", taskSeriesId), new Param("task_id", taskId), new Param("priority", RtmTask.convertPriority(priority)),
new Param("auth_token", currentAuthToken), new Param("api_key", applicationInfo.getApiKey()));
RtmTaskList rtmTaskList = new RtmTaskList(elt);
return findTask(taskSeriesId, taskId, rtmTaskList);
}
public void tasks_setRecurrence()
@ -494,9 +487,20 @@ public class ServiceImpl
throw new UnsupportedOperationException("Not supported yet.");
}
public void tasks_setTags()
public void tasks_setTags(String timelineId, String listId,
String taskSeriesId, String taskId, String[] tags) throws ServiceException
{
throw new UnsupportedOperationException("Not supported yet.");
StringBuilder tagString = new StringBuilder();
if(tags != null) {
for(int i = 0; i < tags.length; i++) {
tagString.append(tags[i]);
if(i < tags.length - 1)
tagString.append(",");
}
}
invoker.invoke(new Param("method", "rtm.tasks.setTags"), new Param("timeline", timelineId), new Param("list_id", listId),
new Param("taskseries_id", taskSeriesId), new Param("task_id", taskId), new Param("tags", tagString.toString()), new Param("auth_token", currentAuthToken),
new Param("api_key", applicationInfo.getApiKey()));
}
public void tasks_setURL()
@ -504,14 +508,12 @@ public class ServiceImpl
throw new UnsupportedOperationException("Not supported yet.");
}
public RtmTaskSeries tasks_uncomplete(String timelineId, String listId, String taskSeriesId, String taskId)
public void tasks_uncomplete(String timelineId, String listId, String taskSeriesId, String taskId)
throws ServiceException
{
Element response = invoker.invoke(new Param("method", "rtm.tasks.uncomplete"), new Param("timeline", timelineId), new Param("list_id", listId),
invoker.invoke(new Param("method", "rtm.tasks.uncomplete"), new Param("timeline", timelineId), new Param("list_id", listId),
new Param("taskseries_id", taskSeriesId), new Param("task_id", taskId), new Param("auth_token", currentAuthToken),
new Param("api_key", applicationInfo.getApiKey()));
RtmTaskList rtmTaskList = new RtmTaskList(response);
return findTask(taskSeriesId, taskId, rtmTaskList);
}
public RtmTaskNote tasks_notes_add(String timelineId, String listId, String taskSeriesId, String taskId, String title, String text)

@ -20,11 +20,14 @@
package com.mdt.rtm.data;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Logger;
import org.w3c.dom.Element;
/**
*
*
* @author Will Ross Jun 22, 2007
*/
public class RtmTaskSeries extends RtmData {
@ -43,6 +46,8 @@ public class RtmTaskSeries extends RtmData {
private final RtmTask task;
private final LinkedList<String> tags;
private final RtmTaskNotes notes;
private final String locationId;
@ -59,6 +64,7 @@ public class RtmTaskSeries extends RtmData {
this.locationId = null;
notes = null;
url = null;
tags = null;
}
public RtmTaskSeries(Element elt) {
@ -75,6 +81,19 @@ public class RtmTaskSeries extends RtmData {
notes = new RtmTaskNotes(child(elt, "notes"));
locationId = elt.getAttribute("location_id");
url = elt.getAttribute("url");
Element elementTags = child(elt, "tags");
if(elementTags.getChildNodes().getLength() > 0) {
List<Element> elementTagList = children(elementTags, "tag");
tags = new LinkedList<String>();
for (Element elementTag : elementTagList) {
String tag = text(elementTag);
if(tag != null)
tags.add(tag);
}
} else {
tags = null;
}
}
public String getId() {
@ -101,6 +120,10 @@ public class RtmTaskSeries extends RtmData {
return task;
}
public LinkedList<String> getTags() {
return tags;
}
public RtmTaskNotes getNotes()
{
return notes;

@ -154,6 +154,8 @@ public class TaskList extends Activity {
Synchronizer.setTaskController(taskController);
setupUIComponents();
loadTaskListSort();
fillData();
// auto sync
Integer autoSyncHours = Preferences.autoSyncFrequency(this);
@ -164,8 +166,7 @@ public class TaskList extends Activity {
1000L*3600*autoSyncHours < System.currentTimeMillis()) {
Synchronizer.synchronize(this, true, null);
}
} else
fillData();
}
}
public void setupUIComponents() {
@ -555,6 +556,7 @@ public class TaskList extends Activity {
if(numServicesSynced == 0)
DialogUtilities.okDialog(TaskList.this, getResources().getString(
R.string.sync_no_synchronizers), null);
fillData();
}
});
} else if(requestCode == ACTIVITY_TAGS && resultCode == RESULT_CANCELED)
@ -614,6 +616,24 @@ public class TaskList extends Activity {
}
}
/** Save the sorting mode to the preferences */
private void saveTaskListSort() {
int sortId = sortMode.ordinal() + 1;
if(sortReverse)
sortId *= -1;
Preferences.setTaskListSort(this, sortId);
}
/** Save the sorting mode to the preferences */
private void loadTaskListSort() {
int sortId = Preferences.getTaskListSort(this);
if(sortId == 0)
return;
sortReverse = sortId < 0;
sortMode = SortMode.values()[Math.abs(sortId)];
}
@Override
public boolean onMenuItemSelected(int featureId, MenuItem item) {
Intent intent;
@ -684,6 +704,7 @@ public class TaskList extends Activity {
return true;
sortReverse = false;
sortMode = SortMode.AUTO;
saveTaskListSort();
fillData();
return true;
case CONTEXT_SORT_ALPHA:
@ -691,6 +712,7 @@ public class TaskList extends Activity {
return true;
sortReverse = false;
sortMode = SortMode.ALPHA;
saveTaskListSort();
fillData();
return true;
case CONTEXT_SORT_DUEDATE:
@ -698,10 +720,12 @@ public class TaskList extends Activity {
return true;
sortReverse = false;
sortMode = SortMode.DUEDATE;
saveTaskListSort();
fillData();
return true;
case CONTEXT_SORT_REVERSE:
sortReverse = !sortReverse;
saveTaskListSort();
fillData();
return true;
}
@ -715,5 +739,7 @@ public class TaskList extends Activity {
taskController.close();
if(tagController != null)
tagController.close();
Synchronizer.setTagController(null);
Synchronizer.setTaskController(null);
}
}

@ -4,9 +4,9 @@ import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.Map.Entry;
import android.app.Activity;
import android.app.Dialog;
@ -31,6 +31,7 @@ import com.mdt.rtm.data.RtmTask.Priority;
import com.timsu.astrid.R;
import com.timsu.astrid.data.enums.Importance;
import com.timsu.astrid.data.sync.SyncMapping;
import com.timsu.astrid.data.task.TaskModelForSync;
import com.timsu.astrid.utilities.DialogUtilities;
import com.timsu.astrid.utilities.Preferences;
@ -45,6 +46,8 @@ public class RTMSyncService extends SynchronizationService {
super(id);
}
// --- abstract methods
@Override
String getName() {
return "RTM";
@ -52,7 +55,7 @@ public class RTMSyncService extends SynchronizationService {
@Override
protected void synchronize(final Activity activity) {
if(Preferences.shouldSyncRTM(activity) &&
if(Preferences.shouldSyncRTM(activity) && rtmService == null &&
Preferences.getSyncRTMToken(activity) == null) {
DialogUtilities.okCancelDialog(activity,
activity.getResources().getString(R.string.sync_rtm_notes),
@ -73,6 +76,8 @@ public class RTMSyncService extends SynchronizationService {
Synchronizer.getSyncController(activity).deleteAllMappings(getId());
}
// --- authentication
/** Perform authentication with RTM. Will open the SyncBrowser if necessary */
private void authenticate(final Activity activity) {
try {
@ -129,6 +134,8 @@ public class RTMSyncService extends SynchronizationService {
}
}
// --- synchronization!
private void performSync(final Activity activity) {
new Thread(new Runnable() {
@Override
@ -140,14 +147,8 @@ public class RTMSyncService extends SynchronizationService {
private void performSyncInNewThread(final Activity activity) {
try {
syncHandler.post(new Runnable() {
@Override
public void run() {
progressDialog.show();
progressDialog.setMessage("Reading Remote Information");
progressDialog.setProgress(0);
}
});
syncHandler.post(new ProgressLabelUpdater("Reading remote data"));
syncHandler.post(new ProgressUpdater(0, 1));
// get RTM timeline
final String timeline = rtmService.timelines_create();
@ -164,77 +165,52 @@ public class RTMSyncService extends SynchronizationService {
}
// read all tasks
List<TaskProxy> remoteChanges = new LinkedList<TaskProxy>();
LinkedList<TaskProxy> remoteChanges = new LinkedList<TaskProxy>();
Date lastSyncDate = Preferences.getSyncRTMLastSync(activity);
String filter = "";
if(lastSyncDate == null) // 1st time sync, just uncompleted tasks
filter = "status:incomplete";
int progress = 0;
for(final String listId : listIdToNameMap.keySet()) {
RtmTasks tasks;
try {
tasks = rtmService.tasks_getList(listId, filter, lastSyncDate);
} catch (Exception e) {
syncHandler.post(new Runnable() {
@Override
public void run() {
DialogUtilities.okDialog(activity,
"List " + listIdToNameMap.get(listId) +
" import failed (too big?)", null);
}
});
continue;
}
boolean shouldSyncIndividualLists = false;
String filter = null;
if(lastSyncDate == null)
filter = "status:incomplete"; // 1st time sync: get unfinished tasks
// try the quick synchronization
try {
Thread.sleep(1500); // throttle
RtmTasks tasks = rtmService.tasks_getList(null, filter, lastSyncDate);
syncHandler.post(new ProgressUpdater(1, 1));
addTasksToList(tasks, remoteChanges);
} catch (Exception e) {
remoteChanges.clear();
shouldSyncIndividualLists = true;
}
for(RtmTaskList taskList : tasks.getLists()) {
for(RtmTaskSeries taskSeries : taskList.getSeries()) {
TaskProxy remoteTask = parseRemoteTask(taskList.getId(), taskSeries);
remoteChanges.add(remoteTask);
if(shouldSyncIndividualLists) {
int progress = 0;
for(final Entry<String, String> entry : listIdToNameMap.entrySet()) {
syncHandler.post(new ProgressLabelUpdater("Reading " +
" list: " + entry.getValue()));
syncHandler.post(new ProgressUpdater(progress++,
listIdToNameMap.size()));
try {
Thread.sleep(1500);
RtmTasks tasks = rtmService.tasks_getList(entry.getKey(),
filter, lastSyncDate);
addTasksToList(tasks, remoteChanges);
} catch (Exception e) {
syncHandler.post(new Runnable() {
@Override
public void run() {
DialogUtilities.okDialog(activity,
"List '" + entry.getValue() +
"' import failed (too big?)", null);
}
});
continue;
}
}
syncHandler.post(new ProgressUpdater(++progress, listIdToNameMap.size()));
syncHandler.post(new ProgressUpdater(1, 1));
}
synchronizeTasks(activity, remoteChanges, new SynchronizeHelper() {
@Override
public String createTask(String listName) throws IOException {
if(listName == null)
listName = INBOX_LIST_NAME;
if(!listNameToIdMap.containsKey(listName.toLowerCase())) {
try {
String listId =
rtmService.lists_add(timeline, listName).getId();
listNameToIdMap.put(listName.toLowerCase(), listId);
} catch (Exception e) {
listName = INBOX_LIST_NAME;
}
}
String listId = listNameToIdMap.get(listName.toLowerCase());
RtmTaskSeries s = rtmService.tasks_add(timeline,
listId, "tmp");
return new RtmId(listId, s).toString();
}
@Override
public void deleteTask(SyncMapping mapping) throws IOException {
RtmId id = new RtmId(mapping.getRemoteId());
rtmService.tasks_delete(timeline, id.listId, id.taskSeriesId,
id.taskId);
}
@Override
public void pushTask(TaskProxy task, SyncMapping mapping) throws IOException {
pushLocalTask(timeline, task, mapping);
}
@Override
public TaskProxy refetchTask(TaskProxy task) throws IOException {
RtmId id = new RtmId(task.getRemoteId());
RtmTaskSeries rtmTask = rtmService.tasks_getTask(id.taskSeriesId,
task.name);
if(rtmTask != null)
return task; // can't fetch
return parseRemoteTask(id.listId, rtmTask);
}
});
synchronizeTasks(activity, remoteChanges, new RtmSyncHelper(timeline));
// add a bit of fudge time so we don't load tasks we just edited
Date syncTime = new Date(System.currentTimeMillis() + 1000);
@ -245,48 +221,52 @@ public class RTMSyncService extends SynchronizationService {
}
}
/** Helper class for processing RTM id's into one field */
private static class RtmId {
String taskId;
String taskSeriesId;
String listId;
// --- helper methods
public RtmId(String listId, RtmTaskSeries taskSeries) {
this.taskId = taskSeries.getTask().getId();
this.taskSeriesId = taskSeries.getId();
this.listId = listId;
}
public RtmId(String id) {
StringTokenizer strtok = new StringTokenizer(id, "|");
taskId = strtok.nextToken();
taskSeriesId = strtok.nextToken();
listId = strtok.nextToken();
}
@Override
public String toString() {
return taskId + "|" + taskSeriesId + "|" + listId;
/** Add the tasks read from RTM to the given list */
private void addTasksToList(RtmTasks tasks, LinkedList<TaskProxy> list) {
for(RtmTaskList taskList : tasks.getLists()) {
for(RtmTaskSeries taskSeries : taskList.getSeries()) {
TaskProxy remoteTask =
parseRemoteTask(taskList.getId(), taskSeries);
list.add(remoteTask);
}
}
}
/** Send changes for the given TaskProxy across the wire */
private void pushLocalTask(String timeline, TaskProxy task, SyncMapping mapping)
throws ServiceException {
private void pushLocalTask(String timeline, TaskProxy task, TaskProxy remoteTask,
SyncMapping mapping) throws ServiceException {
RtmId id = new RtmId(mapping.getRemoteId());
if(task.name != null)
// fetch remote task for comparison (won't work if you renamed it)
if(remoteTask == null) {
RtmTaskSeries rtmTask = rtmService.tasks_getTask(id.taskSeriesId, task.name);
if(rtmTask != null)
remoteTask = parseRemoteTask(id.listId, rtmTask);
}
if(remoteTask == null)
remoteTask = new TaskProxy(0, "", false);
if(task.name != null && !task.name.equals(remoteTask.name))
rtmService.tasks_setName(timeline, id.listId, id.taskSeriesId,
id.taskId, task.name);
if(task.importance != null)
if(task.importance != null && !task.importance.equals(remoteTask.importance))
rtmService.tasks_setPriority(timeline, id.listId, id.taskSeriesId,
id.taskId, Priority.values()[task.importance.ordinal()]);
// due date
Date dueDate = task.definiteDueDate;
if(dueDate == null)
dueDate = task.preferredDueDate;
rtmService.tasks_setDueDate(timeline, id.listId, id.taskSeriesId,
if(dueDate != remoteTask.definiteDueDate &&
(dueDate == null || !dueDate.equals(remoteTask.definiteDueDate)))
rtmService.tasks_setDueDate(timeline, id.listId, id.taskSeriesId,
id.taskId, dueDate, dueDate != null);
if(task.progressPercentage != null) {
// progress
if(task.progressPercentage != null && !task.progressPercentage.equals(
remoteTask.progressPercentage)) {
if(task.progressPercentage == 100)
rtmService.tasks_complete(timeline, id.listId, id.taskSeriesId,
id.taskId);
@ -294,9 +274,38 @@ public class RTMSyncService extends SynchronizationService {
rtmService.tasks_uncomplete(timeline, id.listId, id.taskSeriesId,
id.taskId);
}
if(task.notes != null && !task.notes.endsWith("\n")) {
// notes
if(task.notes != null && task.notes.length() > 0 && !task.equals(remoteTask.notes))
rtmService.tasks_notes_add(timeline, id.listId, id.taskSeriesId,
id.taskId, "From Astrid", task.notes);
// tags
if(task.tags != null && !task.tags.equals(remoteTask.tags)) {
String listName = listIdToNameMap.get(id.listId);
if(task.tags.size() > 0 && listName.equals(task.tags.getFirst()))
task.tags.remove(0);
rtmService.tasks_setTags(timeline, id.listId, id.taskSeriesId,
id.taskId, task.tags.toArray(new String[task.tags.size()]));
}
// estimated time
if(task.estimatedSeconds != null && !task.estimatedSeconds.equals(remoteTask.estimatedSeconds)) {
String estimation;
int estimatedSeconds = task.estimatedSeconds;
if(estimatedSeconds == 0)
estimation = "";
else if(estimatedSeconds < 3600)
estimation = estimatedSeconds/60 + " minutes";
else if(estimatedSeconds < 24*3600) {
int hours = (estimatedSeconds/3600);
estimation = hours+ " hours ";
if(hours*3600 != estimatedSeconds)
estimation += estimatedSeconds - hours*3600 + " minutes";
} else
estimation = estimatedSeconds/3600/24 + " days";
rtmService.tasks_setEstimate(timeline, id.listId, id.taskSeriesId,
id.taskId, estimation);
}
}
@ -307,26 +316,145 @@ public class RTMSyncService extends SynchronizationService {
rtmTaskSeries.getTask().getDeleted() != null);
task.name = rtmTaskSeries.getName();
// notes
StringBuilder sb = new StringBuilder();
for(RtmTaskNote note: rtmTaskSeries.getNotes().getNotes()) {
sb.append(note.getText() + "\n");
}
if(sb.length() > 0)
task.notes = sb.toString();
// list / tags
LinkedList<String> tagsList = rtmTaskSeries.getTags();
String listName = listIdToNameMap.get(listId);
if(listName != null && !listName.equals(INBOX_LIST_NAME))
task.tags = new String[] { listName };
if(listName != null && !listName.equals(INBOX_LIST_NAME)) {
if(tagsList == null)
tagsList = new LinkedList<String>();
tagsList.addFirst(listName);
}
if(tagsList != null)
task.tags = tagsList;
RtmTask rtmTask = rtmTaskSeries.getTask();
String estimate = rtmTask.getEstimate();
if(estimate != null && estimate.length() > 0) {
task.estimatedSeconds = parseEstimate(estimate);
}
task.creationDate = rtmTaskSeries.getCreated();
task.completionDate = rtmTask.getCompleted();
if(rtmTask.getDue() != null)
task.definiteDueDate = rtmTask.getDue();
task.progressPercentage = (rtmTask.getCompleted() == null) ? null : 100;
task.progressPercentage = (rtmTask.getCompleted() == null) ? 0 : 100;
task.importance = Importance.values()[rtmTask.getPriority().ordinal()];
return task;
}
/** Parse an estimated time of the format ## {s,m,h,d,w,mo} and return
* the duration in seconds. Returns null on failure. */
private Integer parseEstimate(String estimate) {
try {
float total = 0;
int position = 0;
while(position != -1) {
for(; position < estimate.length(); position++) {
char c = estimate.charAt(position);
if(c != '.' && (c < '0' || c > '9'))
break;
}
float numberPortion = Float.parseFloat(estimate.substring(0, position));
String stringPortion = estimate.substring(position).trim();
position = stringPortion.indexOf(" ");
if(position != -1)
estimate = stringPortion.substring(position+1).trim();
if(stringPortion.startsWith("mo"))
total += numberPortion * 31 * 24 * 3600;
else if(stringPortion.startsWith("w"))
total += numberPortion * 7 * 24 * 3600;
else if(stringPortion.startsWith("d"))
total += numberPortion * 24 * 3600;
else if(stringPortion.startsWith("h"))
total += numberPortion * 3600;
else if(stringPortion.startsWith("m"))
total += numberPortion * 60;
else if(stringPortion.startsWith("s"))
total += numberPortion;
}
return (int)total;
} catch (Exception e) { /* */ }
return null;
}
// --- helper classes
/** SynchronizeHelper for remember the milk */
private class RtmSyncHelper implements SynchronizeHelper {
private String timeline;
private String lastCreatedTask = null;
public RtmSyncHelper(String timeline) {
this.timeline = timeline;
}
@Override
public String createTask(TaskModelForSync task) throws IOException {
String listId = listNameToIdMap.get(INBOX_LIST_NAME.toLowerCase());
RtmTaskSeries s = rtmService.tasks_add(timeline, listId,
task.getName());
lastCreatedTask = new RtmId(listId, s).toString();
return lastCreatedTask;
}
@Override
public void deleteTask(SyncMapping mapping) throws IOException {
RtmId id = new RtmId(mapping.getRemoteId());
rtmService.tasks_delete(timeline, id.listId, id.taskSeriesId,
id.taskId);
}
@Override
public void pushTask(TaskProxy task, TaskProxy remoteTask,
SyncMapping mapping) throws IOException {
// don't save the stuff that we already saved by creating the task
if(task.getRemoteId().equals(lastCreatedTask))
task.name = null;
pushLocalTask(timeline, task, remoteTask, mapping);
}
@Override
public TaskProxy refetchTask(TaskProxy task) throws IOException {
RtmId id = new RtmId(task.getRemoteId());
RtmTaskSeries rtmTask = rtmService.tasks_getTask(id.taskSeriesId,
task.name);
if(rtmTask == null)
return task; // can't fetch
return parseRemoteTask(id.listId, rtmTask);
}
}
/** Helper class for processing RTM id's into one field */
private static class RtmId {
String taskId;
String taskSeriesId;
String listId;
public RtmId(String listId, RtmTaskSeries taskSeries) {
this.taskId = taskSeries.getTask().getId();
this.taskSeriesId = taskSeries.getId();
this.listId = listId;
}
public RtmId(String id) {
StringTokenizer strtok = new StringTokenizer(id, "|");
taskId = strtok.nextToken();
taskSeriesId = strtok.nextToken();
listId = strtok.nextToken();
}
@Override
public String toString() {
return taskId + "|" + taskSeriesId + "|" + listId;
}
}
}

@ -4,7 +4,6 @@ import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import android.app.Activity;
import android.app.Dialog;
@ -99,16 +98,19 @@ public abstract class SynchronizationService {
/** Push the given task to the remote server.
*
* @param task task proxy to push
* @param remoteTask remote task that we merged with, or null
* @param mapping local/remote mapping.
*/
void pushTask(TaskProxy task, SyncMapping mapping) throws IOException;
void pushTask(TaskProxy task, TaskProxy remoteTask,
SyncMapping mapping) throws IOException;
/** Create a task on the remote server
/** Create a task on the remote server. This is followed by a call of
* pushTask on the id in question.
*
* @return primaryTag primary tag of this task. null if no tags exist.
* @return task to create
* @return remote id
*/
String createTask(String primaryTag) throws IOException;
String createTask(TaskModelForSync task) throws IOException;
/** Fetch remote task. Used to re-read merged tasks
*
@ -135,153 +137,105 @@ public abstract class SynchronizationService {
* @param remoteTasks remote tasks that have been updated
* @return local tasks that need to be pushed across
*/
protected void synchronizeTasks(final Activity activity, List<TaskProxy> remoteTasks,
SynchronizeHelper helper) throws IOException {
protected void synchronizeTasks(final Activity activity, LinkedList<TaskProxy>
remoteTasks, SynchronizeHelper helper) throws IOException {
final SyncStats stats = new SyncStats();
final StringBuilder log = new StringBuilder();
syncHandler.post(new Runnable() {
@Override
public void run() {
if(!progressDialog.isShowing())
progressDialog.show();
}
});
SyncDataController syncController = Synchronizer.getSyncController(activity);
TaskController taskController = Synchronizer.getTaskController(activity);
TagController tagController = Synchronizer.getTagController(activity);
AlertController alertController = Synchronizer.getAlertController(activity);
SyncData data = new SyncData(activity, remoteTasks);
// 1. get data out of the database
HashSet<SyncMapping> mappings = syncController.getSyncMapping(getId());
HashSet<TaskIdentifier> activeTasks = taskController.
getActiveTaskIdentifiers();
HashSet<TaskIdentifier> allTasks = taskController.
getAllTaskIdentifiers();
HashMap<TagIdentifier, TagModelForView> tags =
tagController.getAllTagsAsMap(activity);
// 2. build helper data structures
HashMap<String, SyncMapping> remoteIdToSyncMapping =
new HashMap<String, SyncMapping>();
HashMap<TaskIdentifier, SyncMapping> localIdToSyncMapping =
new HashMap<TaskIdentifier, SyncMapping>();
HashSet<SyncMapping> localChanges = new HashSet<SyncMapping>();
HashSet<TaskIdentifier> mappedTasks = new HashSet<TaskIdentifier>();
for(SyncMapping mapping : mappings) {
if(mapping.isUpdated())
localChanges.add(mapping);
remoteIdToSyncMapping.put(mapping.getRemoteId(), mapping);
localIdToSyncMapping.put(mapping.getTask(), mapping);
mappedTasks.add(mapping.getTask());
}
// 3. build map of remote tasks
HashMap<TaskIdentifier, TaskProxy> remoteChangeMap =
new HashMap<TaskIdentifier, TaskProxy>();
HashMap<String, TaskProxy> newRemoteTasks = new HashMap<String, TaskProxy>();
for(TaskProxy remoteTask : remoteTasks) {
if(remoteIdToSyncMapping.containsKey(remoteTask.getRemoteId())) {
SyncMapping mapping = remoteIdToSyncMapping.get(remoteTask.getRemoteId());
remoteChangeMap.put(mapping.getTask(), remoteTask);
} else if(remoteTask.name != null){
newRemoteTasks.put(remoteTask.name, remoteTask);
}
}
// 4. CREATE: grab tasks without a sync mapping and create them remotely
// 1. CREATE: grab tasks without a sync mapping and create them remotely
log.append(">> on remote server:\n");
syncHandler.post(new ProgressLabelUpdater("Sending locally created tasks"));
HashSet<TaskIdentifier> newlyCreatedTasks = new HashSet<TaskIdentifier>(activeTasks);
newlyCreatedTasks.removeAll(mappedTasks);
for(TaskIdentifier taskId : newlyCreatedTasks) {
for(TaskIdentifier taskId : data.newlyCreatedTasks) {
TaskModelForSync task = taskController.fetchTaskForSync(taskId);
syncHandler.post(new ProgressLabelUpdater("Sending local task: " +
task.getName()));
syncHandler.post(new ProgressUpdater(stats.remoteCreatedTasks,
data.newlyCreatedTasks.size()));
/* 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.
* Instead, we create a mapping and do an update. */
if(newRemoteTasks.containsKey(task.getName())) {
TaskProxy remoteTask = newRemoteTasks.get(task.getName());
if(data.newRemoteTasks.containsKey(task.getName())) {
TaskProxy remoteTask = data.newRemoteTasks.get(task.getName());
SyncMapping mapping = new SyncMapping(taskId, getId(),
remoteTask.getRemoteId());
syncController.saveSyncMapping(mapping);
localChanges.add(mapping);
remoteChangeMap.put(taskId, remoteTask);
localIdToSyncMapping.put(taskId, mapping);
data.localChanges.add(mapping);
data.remoteChangeMap.put(taskId, remoteTask);
data.localIdToSyncMapping.put(taskId, mapping);
continue;
}
// grab the primary tag for this task
LinkedList<TagIdentifier> taskTags =
tagController.getTaskTags(activity, taskId);
String listName = null;
if(taskTags.size() > 0) {
listName = tags.get(taskTags.get(0)).getName();
if(listName.startsWith(TagModelForView.HIDDEN_FROM_MAIN_LIST_PREFIX))
listName = listName.substring(1);
}
String remoteId = helper.createTask(listName);
String remoteId = helper.createTask(task);
SyncMapping mapping = new SyncMapping(taskId, getId(), remoteId);
syncController.saveSyncMapping(mapping);
TaskProxy localTask = new TaskProxy(getId(), remoteId, false);
localTask.readFromTaskModel(task);
helper.pushTask(localTask, mapping);
localTask.readTagsFromController(activity, taskId, tagController, data.tags);
helper.pushTask(localTask, null, mapping);
// update stats
log.append("added " + task.getName() + "\n");
log.append("added '" + task.getName() + "'\n");
stats.remoteCreatedTasks++;
syncHandler.post(new ProgressUpdater(stats.remoteCreatedTasks,
newlyCreatedTasks.size()));
}
// 5. DELETE: find deleted tasks and remove them from the list
// 2. DELETE: find deleted tasks and remove them from the list
syncHandler.post(new ProgressLabelUpdater("Sending locally deleted tasks"));
HashSet<TaskIdentifier> deletedTasks = new HashSet<TaskIdentifier>(
mappedTasks);
deletedTasks.removeAll(allTasks);
for(TaskIdentifier taskId : deletedTasks) {
SyncMapping mapping = localIdToSyncMapping.get(taskId);
for(TaskIdentifier taskId : data.deletedTasks) {
SyncMapping mapping = data.localIdToSyncMapping.get(taskId);
syncController.deleteSyncMapping(mapping);
helper.deleteTask(mapping);
// remove it from data structures
localChanges.remove(mapping);
remoteIdToSyncMapping.remove(mapping);
remoteChangeMap.remove(taskId);
data.localChanges.remove(mapping);
data.remoteIdToSyncMapping.remove(mapping);
data.remoteChangeMap.remove(taskId);
// update stats
log.append("deleted id #" + taskId.getId() + "\n");
stats.remoteDeletedTasks++;
syncHandler.post(new ProgressUpdater(stats.remoteDeletedTasks,
deletedTasks.size()));
data.deletedTasks.size()));
}
// 6. UPDATE: for each updated local task
syncHandler.post(new ProgressLabelUpdater("Sending locally edited tasks"));
for(SyncMapping mapping : localChanges) {
// 3. UPDATE: for each updated local task
for(SyncMapping mapping : data.localChanges) {
TaskProxy localTask = new TaskProxy(getId(), mapping.getRemoteId(),
false);
TaskModelForSync task = taskController.fetchTaskForSync(
mapping.getTask());
localTask.readFromTaskModel(task);
localTask.readTagsFromController(activity, task.getTaskIdentifier(),
tagController, data.tags);
syncHandler.post(new ProgressLabelUpdater("Sending local task: " +
task.getName()));
syncHandler.post(new ProgressUpdater(stats.remoteUpdatedTasks,
data.localChanges.size()));
// if there is a conflict, merge
TaskProxy remoteConflict = null;
if(remoteChangeMap.containsKey(mapping.getTask())) {
remoteConflict = remoteChangeMap.get(mapping.getTask());
if(data.remoteChangeMap.containsKey(mapping.getTask())) {
remoteConflict = data.remoteChangeMap.get(mapping.getTask());
localTask.mergeWithOther(remoteConflict);
stats.mergedTasks++;
log.append("merged " + task.getName() + "\n");
} else {
log.append("updated " + task.getName() + "\n");
}
try {
helper.pushTask(localTask, mapping);
helper.pushTask(localTask, remoteConflict, mapping);
if(remoteConflict != null)
log.append("merged '" + task.getName() + "'\n");
else
log.append("updated '" + task.getName() + "'\n");
} catch (Exception e) {
Log.e("astrid", "Exception pushing task", e);
log.append("error sending '" + task.getName() + "'\n");
continue;
}
@ -292,19 +246,22 @@ public abstract class SynchronizationService {
remoteTasks.add(newTask);
} else
stats.remoteUpdatedTasks++;
syncHandler.post(new ProgressUpdater(stats.remoteUpdatedTasks,
localChanges.size()));
}
// 7. REMOTE SYNC load remote information
// 4. REMOTE SYNC load remote information
log.append(">> on astrid:\n");
syncHandler.post(new ProgressLabelUpdater("Updating local tasks"));
syncHandler.post(new ProgressUpdater(0, 1));
for(TaskProxy remoteTask : remoteTasks) {
if(remoteTask.name != null)
syncHandler.post(new ProgressLabelUpdater("Updating local " +
"tasks: " + remoteTask.name));
else
syncHandler.post(new ProgressLabelUpdater("Updating local tasks"));
SyncMapping mapping = null;
TaskModelForSync task = null;
// if it's new, create a new task model
if(!remoteIdToSyncMapping.containsKey(remoteTask.getRemoteId())) {
if(!data.remoteIdToSyncMapping.containsKey(remoteTask.getRemoteId())) {
// if it's new & deleted, forget about it
if(remoteTask.isDeleted()) {
continue;
@ -316,11 +273,11 @@ public abstract class SynchronizationService {
setupTaskDefaults(activity, task);
log.append("added " + remoteTask.name + "\n");
} else {
mapping = localIdToSyncMapping.get(task.getTaskIdentifier());
mapping = data.localIdToSyncMapping.get(task.getTaskIdentifier());
log.append("merged " + remoteTask.name + "\n");
}
} else {
mapping = remoteIdToSyncMapping.get(remoteTask.getRemoteId());
mapping = data.remoteIdToSyncMapping.get(remoteTask.getRemoteId());
if(remoteTask.isDeleted()) {
taskController.deleteTask(mapping.getTask());
syncController.deleteSyncMapping(mapping);
@ -329,7 +286,7 @@ public abstract class SynchronizationService {
continue;
}
log.append("updated " + remoteTask.name + "\n");
log.append("updated '" + remoteTask.name + "'\n");
task = taskController.fetchTaskForSync(
mapping.getTask());
}
@ -338,39 +295,40 @@ public abstract class SynchronizationService {
remoteTask.writeToTaskModel(task);
taskController.saveTask(task);
// save tag
if(remoteTask.tags != null && remoteTask.tags.length > 0) {
String tag = remoteTask.tags[0];
TagIdentifier tagIdentifier = null;
for(TagModelForView tagModel : tags.values()) {
String tagName = tagModel.getName();
if(tagName.startsWith(TagModelForView.HIDDEN_FROM_MAIN_LIST_PREFIX))
tagName = tagName.substring(1);
if(tagName.equalsIgnoreCase(tag)) {
tagIdentifier = tagModel.getTagIdentifier();
break;
}
}
try {
if(tagIdentifier == null)
tagIdentifier = tagController.createTag(tag);
tagController.addTag(task.getTaskIdentifier(),
tagIdentifier);
} catch (Exception e) {
// tag already exists or something
// save tags
if(remoteTask.tags != null) {
LinkedList<TagIdentifier> taskTags = tagController.getTaskTags(activity, task.getTaskIdentifier());
HashSet<TagIdentifier> tagsToAdd = new HashSet<TagIdentifier>();
for(String tag : remoteTask.tags) {
String tagLower = tag.toLowerCase();
if(!data.tagsByLCName.containsKey(tagLower)) {
TagIdentifier tagId = tagController.createTag(tag);
data.tagsByLCName.put(tagLower, tagId);
tagsToAdd.add(tagId);
} else
tagsToAdd.add(data.tagsByLCName.get(tagLower));
}
HashSet<TagIdentifier> tagsToDelete = new HashSet<TagIdentifier>(taskTags);
tagsToDelete.removeAll(tagsToAdd);
tagsToAdd.removeAll(taskTags);
for(TagIdentifier tagId : tagsToDelete)
tagController.removeTag(task.getTaskIdentifier(), tagId);
for(TagIdentifier tagId : tagsToAdd)
tagController.addTag(task.getTaskIdentifier(), tagId);
}
stats.localUpdatedTasks++;
// try looking for this task if it doesn't already have a mapping
if(mapping == null) {
// try looking for this task
mapping = localIdToSyncMapping.get(task.getTaskIdentifier());
mapping = data.localIdToSyncMapping.get(task.getTaskIdentifier());
if(mapping == null) {
try {
mapping = new SyncMapping(task.getTaskIdentifier(), remoteTask);
syncController.saveSyncMapping(mapping);
} catch (Exception e) {
// ignore - it'll get merged later
// unique violation: ignore - it'll get merged later
}
}
stats.localCreatedTasks++;
@ -401,6 +359,71 @@ public abstract class SynchronizationService {
// --- helper classes
/** data structure builder */
private class SyncData {
HashSet<SyncMapping> mappings;
HashSet<TaskIdentifier> activeTasks;
HashSet<TaskIdentifier> allTasks;
HashMap<String, SyncMapping> remoteIdToSyncMapping;
HashMap<TaskIdentifier, SyncMapping> localIdToSyncMapping;
HashSet<SyncMapping> localChanges;
HashSet<TaskIdentifier> mappedTasks;
HashMap<TaskIdentifier, TaskProxy> remoteChangeMap;
HashMap<String, TaskProxy> newRemoteTasks;
HashMap<TagIdentifier, TagModelForView> tags;
HashMap<String, TagIdentifier> tagsByLCName;
HashSet<TaskIdentifier> newlyCreatedTasks;
HashSet<TaskIdentifier> deletedTasks;
public SyncData(Activity activity, LinkedList<TaskProxy> remoteTasks) {
// 1. get data out of the database
mappings = Synchronizer.getSyncController(activity).getSyncMapping(getId());
activeTasks = Synchronizer.getTaskController(activity).getActiveTaskIdentifiers();
allTasks = Synchronizer.getTaskController(activity).getAllTaskIdentifiers();
tags = Synchronizer.getTagController(activity).getAllTagsAsMap(activity);
// 2. build helper data structures
remoteIdToSyncMapping = new HashMap<String, SyncMapping>();
localIdToSyncMapping = new HashMap<TaskIdentifier, SyncMapping>();
localChanges = new HashSet<SyncMapping>();
mappedTasks = new HashSet<TaskIdentifier>();
for(SyncMapping mapping : mappings) {
if(mapping.isUpdated())
localChanges.add(mapping);
remoteIdToSyncMapping.put(mapping.getRemoteId(), mapping);
localIdToSyncMapping.put(mapping.getTask(), mapping);
mappedTasks.add(mapping.getTask());
}
tagsByLCName = new HashMap<String, TagIdentifier>();
for(TagModelForView tag : tags.values())
tagsByLCName.put(tag.getName().toLowerCase(), tag.getTagIdentifier());
// 3. build map of remote tasks
remoteChangeMap = new HashMap<TaskIdentifier, TaskProxy>();
newRemoteTasks = new HashMap<String, TaskProxy>();
for(TaskProxy remoteTask : remoteTasks) {
if(remoteIdToSyncMapping.containsKey(remoteTask.getRemoteId())) {
SyncMapping mapping = remoteIdToSyncMapping.get(remoteTask.getRemoteId());
remoteChangeMap.put(mapping.getTask(), remoteTask);
} else if(remoteTask.name != null){
newRemoteTasks.put(remoteTask.name, remoteTask);
}
}
// 4. build data structures of things to do
newlyCreatedTasks = new HashSet<TaskIdentifier>(activeTasks);
newlyCreatedTasks.removeAll(mappedTasks);
deletedTasks = new HashSet<TaskIdentifier>(mappedTasks);
deletedTasks.removeAll(allTasks);
}
}
/** statistics tracking and displaying */
protected class SyncStats {
int localCreatedTasks = 0;
int localUpdatedTasks = 0;
@ -433,10 +456,10 @@ public abstract class SynchronizationService {
}
StringBuilder sb = new StringBuilder();
sb.append(getName()).append(" Sync Results:"); // TODO i18n
sb.append(getName()).append("Results:"); // TODO i18n
sb.append("\n\n");
sb.append(log);
sb.append("\n--- Summary: Astrid Tasks ---");
sb.append("\nSummary - Astrid Tasks:");
if(localCreatedTasks > 0)
sb.append("\nCreated: " + localCreatedTasks);
if(localUpdatedTasks > 0)
@ -447,7 +470,7 @@ public abstract class SynchronizationService {
if(mergedTasks > 0)
sb.append("\n\nMerged: " + localCreatedTasks);
sb.append("\n\n--- Summary: Remote Server ---");
sb.append("\n\nSummary - Remote Server:");
if(remoteCreatedTasks > 0)
sb.append("\nCreated: " + remoteCreatedTasks);
if(remoteUpdatedTasks > 0)
@ -478,8 +501,9 @@ public abstract class SynchronizationService {
this.label = label;
}
public void run() {
if(!progressDialog.isShowing())
progressDialog.show();
progressDialog.setMessage(label);
progressDialog.setProgress(0);
}
}
}

@ -167,6 +167,8 @@ public class Synchronizer {
}
public void set(TYPE newController) {
close();
override = newController != null;
controller = newController;
}

@ -1,9 +1,17 @@
package com.timsu.astrid.sync;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import android.app.Activity;
import com.timsu.astrid.data.enums.Importance;
import com.timsu.astrid.data.enums.RepeatInterval;
import com.timsu.astrid.data.tag.TagController;
import com.timsu.astrid.data.tag.TagIdentifier;
import com.timsu.astrid.data.tag.TagModelForView;
import com.timsu.astrid.data.task.TaskIdentifier;
import com.timsu.astrid.data.task.TaskModelForSync;
import com.timsu.astrid.data.task.AbstractTaskModel.RepeatInfo;
@ -24,35 +32,35 @@ public class TaskProxy {
// --- fill these out
String name = null;
String notes = null;
String name = null;
String notes = null;
Importance importance = null;
Integer progressPercentage = null;
Importance importance = null;
Integer progressPercentage = null;
Date creationDate = null;
Date completionDate = null;
Date creationDate = null;
Date completionDate = null;
Date definiteDueDate = null;
Date preferredDueDate = null;
Date hiddenUntil = null;
Date definiteDueDate = null;
Date preferredDueDate = null;
Date hiddenUntil = null;
String[] tags = null;
LinkedList<String> tags = null;
Integer estimatedSeconds = null;
Integer elapsedSeconds = null;
Integer repeatEveryNSeconds = null;
Integer estimatedSeconds = null;
Integer elapsedSeconds = null;
Integer repeatEveryNSeconds = null;
// --- internal state
/** id of the synchronization service */
private int syncServiceId;
private int syncServiceId;
/** id of this particular remote task */
private String syncTaskId;
private String syncTaskId;
/** was the task deleted on the remote server */
private boolean isDeleted = false;
private boolean isDeleted = false;
public int getSyncServiceId() {
return syncServiceId;
@ -120,6 +128,18 @@ public class TaskProxy {
}
}
/** Read tags from the given tag controller */
public void readTagsFromController(Activity activity, TaskIdentifier taskId,
TagController tagController, HashMap<TagIdentifier, TagModelForView>
tagList) {
LinkedList<TagIdentifier> tagIds = tagController.getTaskTags(activity,
taskId);
tags = new LinkedList<String>();
for(TagIdentifier tagId : tagIds) {
tags.add(tagList.get(tagId).getName());
}
}
/** Write to the given task model */
public void writeToTaskModel(TaskModelForSync task) {
if(name != null)
@ -141,8 +161,6 @@ public class TaskProxy {
if(hiddenUntil != null)
task.setHiddenUntil(hiddenUntil);
// TODO tags
if(estimatedSeconds != null)
task.setEstimatedSeconds(estimatedSeconds);
if(elapsedSeconds != null)

@ -113,7 +113,7 @@ public class DateUtilities {
unitsDisplayed++;
}
if(unitsDisplayed < unitsToShow && seconds > 0) {
result.append(hours).append(" s ");
result.append(seconds).append(" s ");
}
return result.toString();

@ -16,6 +16,7 @@ public class Preferences {
// pref keys
private static final String P_CURRENT_VERSION = "cv";
private static final String P_SHOW_REPEAT_HELP = "repeathelp";
private static final String P_TASK_LIST_SORT = "tlsort";
private static final String P_SYNC_RTM_TOKEN = "rtmtoken";
private static final String P_SYNC_RTM_LAST_SYNC = "rtmlastsync";
private static final String P_SYNC_LAST_SYNC = "lastsync";
@ -122,6 +123,18 @@ public class Preferences {
return getIntegerValue(context, R.string.p_notif_defaultRemind);
}
/** TaskListSort: the sorting method for the task list */
public static int getTaskListSort(Context context) {
return getPrefs(context).getInt(P_TASK_LIST_SORT, 0);
}
/** TaskListSort: the sorting method for the task list */
public static void setTaskListSort(Context context, int value) {
Editor editor = getPrefs(context).edit();
editor.putInt(P_TASK_LIST_SORT, value);
editor.commit();
}
// --- synchronization preferences
/** RTM authentication token, or null if doesn't exist */

Loading…
Cancel
Save