Synchronization works, I think. Needs testing of course.

pull/14/head
Tim Su 16 years ago
parent fd78e22cd8
commit c722008f89

@ -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="56"
android:versionName="1.12">
android:versionCode="57"
android:versionName="2.0-beta">
<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="56" />
android:value="57" />
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.INTERNET"/>

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
ASTRID: Android's Simple Task Recording Dashboard
Copyright (c) 2009 Tim Su
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-->
<LinearLayout android:id="@+id/footer"
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:padding="10dip"
android:layout_gravity="bottom"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:paddingBottom="10dip"
android:textAppearance="?android:attr/textAppearanceMedium"
android:gravity="center_horizontal"
android:text="@string/sync_pref_group_actions"/>
<Button android:id="@+id/forget"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/sync_forget"/>
<Button android:id="@+id/sync"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/sync_now"/>
</LinearLayout>

@ -200,14 +200,29 @@ If you don\'t want to see the new task right after you complete the old one, you
<skip />
<string name="p_sync_rtm">sync_rtm</string>
<string name="sync_pref_group">Synchronization Services</string>
<string name="sync_pref_group_actions">Actions</string>
<string name="sync_rtm_title">Remember The Milk</string>
<string name="sync_rtm_desc">http://www.rememberthemilk.com</string>
<string name="sync_error">Sync Error!</string>
<string name="sync_error">Sync Error! Sorry for the inconvenience! Error:</string>
<string name="sync_auth_request">
In order to synchronize, please log in to your %s account and authorize Astrid to read your data.
\n\n
When finished, restart Astrid.
</string>
When finished, restart Astrid and come back here.
</string>
<string name="sync_rtm_notes">
Astrid's RTM sync is not complete!
- RTM tags are not read. Instead, RTM's lists are mapped to Astrid\'s tags.\n
- Task notes are read from RTM, but changes are not written back.\n
- Notifications and repeats are not synchronized.\n
- Moving tasks in RTM to another list and then editing the task name causes duplication.\n
- Synchronization is SLOW! Be patient.\n
\n\n
Wish me luck!\n
</string>
<string name="sync_now">Synchronize!</string>
<string name="sync_forget">Clear Personal Data</string>
<string name="sync_forget_confirm">Clear data for selected services?</string>
<!-- Dialog Boxes -->
<skip />

@ -11,5 +11,5 @@
android:summary="@string/sync_rtm_desc" />
</PreferenceCategory>
</PreferenceScreen>

@ -96,7 +96,7 @@ public class Invoker {
public static String API_SIG_PARAM = "api_sig";
public static final long INVOCATION_INTERVAL = 2000;
public static final long INVOCATION_INTERVAL = 500;
private long lastInvocation;
@ -277,7 +277,7 @@ public class Invoker {
// THINK: this method is deprecated, but the only way to get the body as a string, without consuming
// the body input stream: the HttpMethodBase issues a warning but does not let you call the "setResponseStream()" method!
final String responseBodyAsString = "";//EntityUtils.toString(response.getEntity());
Log.i(TAG, " Invocation response:\r\n" + responseBodyAsString);
// Log.i(TAG, " Invocation response:\r\n" + responseBodyAsString);
final Document responseDoc = builder.parse(response.getEntity().getContent());
final Element wrapperElt = responseDoc.getDocumentElement();
if (!wrapperElt.getNodeName().equals("rsp"))

@ -1,9 +1,15 @@
package com.timsu.astrid.activities;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.preference.PreferenceActivity;
import android.view.View;
import android.widget.Button;
import com.timsu.astrid.R;
import com.timsu.astrid.sync.Synchronizer;
import com.timsu.astrid.utilities.DialogUtilities;
public class SyncPreferences extends PreferenceActivity {
@Override
@ -11,5 +17,31 @@ public class SyncPreferences extends PreferenceActivity {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.sync_preferences);
getListView().addFooterView(getLayoutInflater().inflate(
R.layout.sync_footer, getListView(), false));
((Button)findViewById(R.id.sync)).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Synchronizer.synchronize(SyncPreferences.this);
finish();
}
});
((Button)findViewById(R.id.forget)).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
DialogUtilities.okCancelDialog(SyncPreferences.this,
getResources().getString(R.string.sync_forget_confirm),
new Dialog.OnClickListener() {
@Override
public void onClick(DialogInterface dialog,
int which) {
Synchronizer.clearUserData(SyncPreferences.this);
}
}, null);
}
});
}
}

@ -57,7 +57,6 @@ import com.timsu.astrid.data.tag.TagModelForView;
import com.timsu.astrid.data.task.TaskController;
import com.timsu.astrid.data.task.TaskIdentifier;
import com.timsu.astrid.data.task.TaskModelForList;
import com.timsu.astrid.sync.Synchronizer;
import com.timsu.astrid.utilities.Constants;
import com.timsu.astrid.utilities.StartupReceiver;
@ -166,7 +165,6 @@ public class TaskList extends Activity {
});
fillData();
Synchronizer.synchronize(this);
gestureDetector = new GestureDetector(new TaskListGestureDetector());
gestureTouchListener = new View.OnTouchListener() {
@ -525,12 +523,6 @@ public class TaskList extends Activity {
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
/** Handle synchronization callbacks */
if(requestCode == ACTIVITY_SYNCHRONIZE) {
Synchronizer.synchronizerStatusUpdated(this);
Synchronizer.synchronize(this);
}
if(requestCode == ACTIVITY_TAGS && resultCode == RESULT_CANCELED)
filterTag = null;
}

@ -135,7 +135,7 @@ public class TaskListAdapter extends ArrayAdapter<TaskModelForList> {
append(r.getString(R.string.taskList_completedPrefix)).
append(" ").
append(DateUtilities.getDurationString(r, Math.abs(secondsLeft), 1)).
append(r.getString(R.string.ago_suffix));
append(" " + r.getString(R.string.ago_suffix));
dueDateView.setText(label);
dueDateView.setTextColor(r.getColor(R.color.taskList_completedDate));
hasProperties = true;

@ -27,6 +27,7 @@ import android.content.Context;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import com.timsu.astrid.data.AbstractController;
import com.timsu.astrid.data.sync.SyncMapping.SyncMappingDatabaseHelper;
@ -94,6 +95,12 @@ public class SyncDataController extends AbstractController {
mapping.getId(), null) > 0;
}
/** Deletes the given mapping. Returns true on success */
public boolean deleteAllMappings(int syncServiceId) {
return syncDatabase.delete(SYNC_TABLE_NAME, SyncMapping.SYNC_SERVICE +
"=" + syncServiceId, null) > 0;
}
// --- boilerplate
/**
@ -114,8 +121,9 @@ public class SyncDataController extends AbstractController {
* @throws SQLException if the database could be neither opened or created
*/
public SyncDataController open() throws SQLException {
syncDatabase = new SyncMappingDatabaseHelper(context,
SYNC_TABLE_NAME, SYNC_TABLE_NAME).getWritableDatabase();
SQLiteOpenHelper helper = new SyncMappingDatabaseHelper(context,
SYNC_TABLE_NAME, SYNC_TABLE_NAME);
syncDatabase = helper.getWritableDatabase();
return this;
}

@ -125,6 +125,7 @@ public class SyncMapping extends AbstractModel {
getTask();
getSyncServiceId();
getRemoteId();
isUpdated();
}
// --- getters and setters

@ -34,6 +34,7 @@ import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import com.timsu.astrid.data.AbstractController;
import com.timsu.astrid.data.sync.SyncDataController;
import com.timsu.astrid.data.task.AbstractTaskModel.RepeatInfo;
import com.timsu.astrid.data.task.AbstractTaskModel.TaskModelDatabaseHelper;
import com.timsu.astrid.utilities.Notifications;
@ -119,10 +120,11 @@ public class TaskController extends AbstractController {
}
/** Get identifiers for all tasks */
public Set<TaskIdentifier> getAllTaskIdentifiers() {
public Set<TaskIdentifier> getActiveTaskIdentifiers() {
Set<TaskIdentifier> list = new HashSet<TaskIdentifier>();
Cursor cursor = database.query(TASK_TABLE_NAME, new String[] { KEY_ROWID },
null, null, null, null, null, null);
AbstractTaskModel.PROGRESS_PERCENTAGE + " < " +
AbstractTaskModel.COMPLETE_PERCENTAGE, null, null, null, null, null);
if(cursor.getCount() == 0)
return list;
@ -193,6 +195,13 @@ public class TaskController extends AbstractController {
saveSucessful = database.update(TASK_TABLE_NAME, values,
KEY_ROWID + "=" + id, null) > 0;
if(!(task instanceof TaskModelForSync)) {
SyncDataController syncController = new SyncDataController(context);
syncController.open();
syncController.addToUpdatedList(task.getTaskIdentifier());
syncController.close();
}
}
return saveSucessful;
@ -269,6 +278,18 @@ public class TaskController extends AbstractController {
return model;
}
/** Returns a TaskModelForView by name */
public TaskModelForSync searchForTaskForSync(String name) throws SQLException {
Cursor cursor = database.query(true, TASK_TABLE_NAME, TaskModelForSync.FIELD_LIST,
AbstractTaskModel.NAME + " = ?", new String[] { name }, null, null, null, null);
if (cursor == null || cursor.getCount() == 0)
return null;
cursor.moveToFirst();
TaskModelForSync model = new TaskModelForSync(cursor);
cursor.close();
return model;
}
/** Returns a TaskModelForView corresponding to the given TaskIdentifier */
public TaskModelForNotify fetchTaskForNotify(TaskIdentifier taskId) throws SQLException {
Cursor cursor = fetchTaskCursor(taskId, TaskModelForNotify.FIELD_LIST);

@ -23,6 +23,7 @@ import java.util.Date;
import android.database.Cursor;
import com.timsu.astrid.data.AbstractController;
import com.timsu.astrid.data.enums.Importance;
@ -31,6 +32,7 @@ import com.timsu.astrid.data.enums.Importance;
public class TaskModelForSync extends AbstractTaskModel {
static String[] FIELD_LIST = new String[] {
AbstractController.KEY_ROWID,
NAME,
IMPORTANCE,
ESTIMATED_SECONDS,

@ -1,11 +1,15 @@
package com.timsu.astrid.sync;
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 android.app.Activity;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.Resources;
@ -33,7 +37,9 @@ import com.timsu.astrid.utilities.Preferences;
public class RTMSyncService extends SynchronizationService {
private ServiceImpl rtmService = null;
private static final String ASTRID_LIST_NAME = "Astrid";
private String INBOX_LIST_NAME = "Inbox";
Map<String, String> listNameToIdMap = new HashMap<String, String>();
Map<String, String> listIdToNameMap = new HashMap<String, String>();
public RTMSyncService(int id) {
super(id);
@ -45,13 +51,31 @@ public class RTMSyncService extends SynchronizationService {
}
@Override
public void synchronize(Activity activity) {
void synchronizeService(final Activity activity) {
Date lastSyncDate = Preferences.getSyncRTMLastSync(activity);
if(lastSyncDate == null || true) {
DialogUtilities.okCancelDialog(activity,
activity.getResources().getString(R.string.sync_rtm_notes),
new Dialog.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
RTMSyncService.super.synchronizeService(activity);
}
}, null);
} else
super.synchronizeService(activity);
}
@Override
protected void synchronize(Activity activity) {
authenticate(activity);
}
@Override
public void clearPersonalData(Activity activity) {
Preferences.setSyncRTMToken(activity, null);
Preferences.setSyncRTMLastSync(activity, null);
Synchronizer.getSyncController(activity).deleteAllMappings(getId());
}
/** Perform authentication with RTM. Will open the SyncBrowser if necessary */
@ -88,18 +112,24 @@ public class RTMSyncService extends SynchronizationService {
rtmService = new ServiceImpl(new ApplicationInfo(
apiKey, sharedSecret, appName));
final String url = rtmService.beginAuthorization(Perms.delete);
Resources r = activity.getResources();
DialogUtilities.okCancelDialog(activity,
r.getString(R.string.sync_auth_request, "RTM"),
new DialogInterface.OnClickListener() {
syncHandler.post(new Runnable() {
@Override
public void onClick(DialogInterface dialog, int which) {
Intent intent = new Intent(Intent.ACTION_VIEW,
Uri.parse(url));
activity.startActivity(intent);
public void run() {
progressDialog.dismiss();
Resources r = activity.getResources();
DialogUtilities.okCancelDialog(activity,
r.getString(R.string.sync_auth_request, "RTM"),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Intent intent = new Intent(Intent.ACTION_VIEW,
Uri.parse(url));
activity.startActivity(intent);
}
}, null);
}
}, null);
});
} else {
performSync(activity);
}
@ -111,24 +141,35 @@ public class RTMSyncService extends SynchronizationService {
private void performSync(Activity activity) {
try {
syncHandler.post(new Runnable() {
@Override
public void run() {
progressDialog.show();
progressDialog.setMessage("Reading Remote Information");
progressDialog.setProgress(0);
}
});
// get RTM timeline
final String timeline = rtmService.timelines_create();
syncHandler.post(new ProgressUpdater(20, 100));
// get / create astrid list
RtmList astridList = null;
// load RTM lists
RtmLists lists = rtmService.lists_getList();
for(RtmList list : lists.getLists().values()) {
if(ASTRID_LIST_NAME.equals(list.getName())) {
astridList = list;
break;
}
listNameToIdMap.put(list.getName(), list.getId());
listIdToNameMap.put(list.getId(), list.getName());
// read the name of the inbox with the correct case
if(INBOX_LIST_NAME.equalsIgnoreCase(list.getName()))
INBOX_LIST_NAME = list.getName();
}
if(astridList == null)
astridList = rtmService.lists_add(timeline, ASTRID_LIST_NAME);
final RtmList newTaskCreationList = astridList;
syncHandler.post(new ProgressUpdater(40, 100));
// read all tasks
RtmTasks tasks = rtmService.tasks_getList(null, null,
Preferences.getSyncRTMLastSync(activity));
syncHandler.post(new ProgressUpdater(100, 100));
List<TaskProxy> remoteChanges = new LinkedList<TaskProxy>();
for(RtmTaskList taskList : tasks.getLists()) {
@ -140,10 +181,22 @@ public class RTMSyncService extends SynchronizationService {
synchronizeTasks(activity, remoteChanges, new SynchronizeHelper() {
@Override
public String createTask() throws IOException {
public String createTask(String listName) throws IOException {
if(listName == null)
listName = INBOX_LIST_NAME;
if(!listNameToIdMap.containsKey(listName)) {
try {
String listId =
rtmService.lists_add(timeline, listName).getId();
listNameToIdMap.put(listName, listId);
} catch (Exception e) {
listName = INBOX_LIST_NAME;
}
}
String listId = listNameToIdMap.get(listName);
RtmTaskSeries s = rtmService.tasks_add(timeline,
newTaskCreationList.getId(), "tmp");
return new RtmId(newTaskCreationList.getId(), s).toString();
listId, "tmp");
return new RtmId(listId, s).toString();
}
@Override
public void deleteTask(SyncMapping mapping) throws IOException {
@ -158,12 +211,16 @@ public class RTMSyncService extends SynchronizationService {
@Override
public TaskProxy refetchTask(TaskProxy task) throws IOException {
RtmId id = new RtmId(task.getRemoteId());
RtmTaskSeries rtmTask = rtmService.tasks_getTask(task.getRemoteId(),
RtmTaskSeries rtmTask = rtmService.tasks_getTask(id.taskSeriesId,
task.name);
if(rtmTask != null)
return task; // can't fetch
return parseRemoteTask(id.listId, rtmTask);
}
});
Preferences.setSyncRTMLastSync(activity, new Date());
} catch (Exception e) {
showError(activity, e);
}
@ -195,7 +252,7 @@ public class RTMSyncService extends SynchronizationService {
}
/** Send changes for the given TaskProxy across the wire */
public void pushLocalTask(String timeline, TaskProxy task, SyncMapping mapping)
private void pushLocalTask(String timeline, TaskProxy task, SyncMapping mapping)
throws ServiceException {
RtmId id = new RtmId(mapping.getRemoteId());
@ -205,8 +262,11 @@ public class RTMSyncService extends SynchronizationService {
if(task.importance != null)
rtmService.tasks_setPriority(timeline, id.listId, id.taskSeriesId,
id.taskId, Priority.values()[task.importance.ordinal()]);
Date dueDate = task.definiteDueDate;
if(dueDate == null)
dueDate = task.preferredDueDate;
rtmService.tasks_setDueDate(timeline, id.listId, id.taskSeriesId,
id.taskId, task.definiteDueDate, task.definiteDueDate != null);
id.taskId, dueDate, dueDate != null);
if(task.progressPercentage != null) {
if(task.progressPercentage == 100)
rtmService.tasks_complete(timeline, id.listId, id.taskSeriesId,
@ -228,12 +288,16 @@ public class RTMSyncService extends SynchronizationService {
for(RtmTaskNote note: rtmTaskSeries.getNotes().getNotes()) {
sb.append(note.getText() + "\n");
}
task.notes = sb.toString();
if(sb.length() > 0)
task.notes = sb.toString();
String listName = listIdToNameMap.get(listId);
if(listName != null && !listName.equals(INBOX_LIST_NAME))
task.tags = new String[] { listName };
RtmTask rtmTask = rtmTaskSeries.getTask();
task.creationDate = rtmTaskSeries.getCreated();
task.completionDate = rtmTask.getCompleted();
if(rtmTask.getHasDueTime() > 0)
if(rtmTask.getDue() != null)
task.definiteDueDate = rtmTask.getDue();
task.progressPercentage = (rtmTask.getCompleted() == null) ? null : 100;

@ -8,13 +8,19 @@ import java.util.Map;
import java.util.Set;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.res.Resources;
import android.os.Handler;
import android.util.Log;
import com.timsu.astrid.R;
import com.timsu.astrid.data.sync.SyncDataController;
import com.timsu.astrid.data.sync.SyncMapping;
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.TaskController;
import com.timsu.astrid.data.task.TaskIdentifier;
import com.timsu.astrid.data.task.TaskModelForSync;
import com.timsu.astrid.utilities.DialogUtilities;
@ -27,13 +33,27 @@ import com.timsu.astrid.utilities.DialogUtilities;
public abstract class SynchronizationService {
private int id;
protected ProgressDialog progressDialog;
protected Handler syncHandler = new Handler();
public SynchronizationService(int id) {
this.id = id;
}
void synchronizeService(final Activity activity) {
progressDialog = ProgressDialog.show(activity, "Synchronization",
"Checking Authorization...");
new Thread(new Runnable() {
@Override
public void run() {
synchronize(activity);
}
}).start();;
}
/** Synchronize with the service */
abstract void synchronize(Activity activity);
protected abstract void synchronize(Activity activity);
/** Called when user requests a data clear */
abstract void clearPersonalData(Activity activity);
@ -49,16 +69,18 @@ public abstract class SynchronizationService {
// --- utilities
/** Utility class for showing synchronization errors */
static void showError(Context context, Throwable e) {
void showError(final Context context, final Throwable e) {
Log.e("astrid", "Synchronization Error", e);
Resources r = context.getResources();
DialogUtilities.okDialog(context,
r.getString(R.string.sync_error) + " " +
e.getLocalizedMessage(), new DialogInterface.OnClickListener() {
syncHandler.post(new Runnable() {
@Override
public void onClick(DialogInterface dialog, int which) {
// do nothing?
public void run() {
if(progressDialog != null)
progressDialog.dismiss();
Resources r = context.getResources();
DialogUtilities.okDialog(context,
r.getString(R.string.sync_error) + " " +
e.toString() + " - " + e.getStackTrace()[0], null);
}
});
}
@ -76,9 +98,10 @@ public abstract class SynchronizationService {
/** Create a task on the remote server
*
* @return listName list name to create it on. null -> inbox
* @return remote id
*/
String createTask() throws IOException;
String createTask(String listName) throws IOException;
/** Fetch remote task. Used to re-read merged tasks
*
@ -105,13 +128,19 @@ public abstract class SynchronizationService {
* @param remoteTasks remote tasks that have been updated
* @return local tasks that need to be pushed across
*/
protected void synchronizeTasks(Activity activity, List<TaskProxy> remoteTasks,
protected void synchronizeTasks(final Activity activity, List<TaskProxy> remoteTasks,
SynchronizeHelper helper) throws IOException {
SyncStats stats = new SyncStats();
final SyncStats stats = new SyncStats();
// get data out of the database
Set<SyncMapping> mappings = Synchronizer.getSyncController().getSyncMapping(getId());
Set<TaskIdentifier> localTasks = Synchronizer.getTaskController().getAllTaskIdentifiers();
SyncDataController syncController = Synchronizer.getSyncController(activity);
TaskController taskController = Synchronizer.getTaskController(activity);
TagController tagController = Synchronizer.getTagController(activity);
// get data out of the database (note we get non-completed tasks only)
Set<SyncMapping> mappings = syncController.getSyncMapping(getId());
Set<TaskIdentifier> localTasks = taskController.getActiveTaskIdentifiers();
Map<TagIdentifier, TagModelForView> tags =
tagController.getAllTagsAsMap(activity);
// build local maps / lists
Map<String, SyncMapping> remoteIdToSyncMapping =
@ -139,37 +168,80 @@ public abstract class SynchronizationService {
}
// grab tasks without a sync mapping and create them remotely
Set<TaskIdentifier> newlyCreatedTasks = new HashSet<TaskIdentifier>(localTasks);
syncHandler.post(new Runnable() {
@Override
public void run() {
progressDialog.setMessage("Sending locally created tasks");
progressDialog.setProgress(0);
}
});
Set<TaskIdentifier> newlyCreatedTasks = new HashSet<TaskIdentifier>(
localTasks);
newlyCreatedTasks.removeAll(mappedTasks);
for(TaskIdentifier taskId : newlyCreatedTasks) {
String remoteId = helper.createTask();
stats.remoteCreatedTasks++;
List<TagIdentifier> taskTags =
tagController.getTaskTags(activity, taskId);
String listName = null;
if(taskTags.size() > 0) {
listName = tags.get(taskTags.get(0)).getName();
// strip the underline
if(listName.startsWith(TagModelForView.HIDDEN_FROM_MAIN_LIST_PREFIX))
listName = listName.substring(1);
}
String remoteId = helper.createTask(listName);
SyncMapping mapping = new SyncMapping(taskId, getId(), remoteId);
Synchronizer.getSyncController().saveSyncMapping(mapping);
syncController.saveSyncMapping(mapping);
TaskModelForSync task = taskController.fetchTaskForSync(
mapping.getTask());
TaskProxy localTask = new TaskProxy(getId(), remoteId, false);
localTask.readFromTaskModel(task);
helper.pushTask(localTask, mapping);
// add it to data structures
localChanges.add(mapping);
// update stats
stats.remoteCreatedTasks++;
syncHandler.post(new ProgressUpdater(stats.remoteCreatedTasks,
newlyCreatedTasks.size()));
}
// find deleted tasks and remove them from the list
syncHandler.post(new Runnable() {
@Override
public void run() {
progressDialog.setMessage("Sending locally deleted tasks");
progressDialog.setProgress(0);
}
});
Set<TaskIdentifier> deletedTasks = new HashSet<TaskIdentifier>(mappedTasks);
deletedTasks.removeAll(localTasks);
for(TaskIdentifier taskId : deletedTasks) {
stats.remoteDeletedTasks++;
SyncMapping mapping = localIdToSyncMapping.get(taskId);
Synchronizer.getSyncController().deleteSyncMapping(mapping);
syncController.deleteSyncMapping(mapping);
helper.deleteTask(mapping);
// remove it from data structures
localChanges.remove(mapping);
remoteIdToSyncMapping.remove(mapping);
remoteChangeMap.remove(taskId);
// update stats
stats.remoteDeletedTasks++;
syncHandler.post(new ProgressUpdater(stats.remoteDeletedTasks,
deletedTasks.size()));
}
// for each updated local task
syncHandler.post(new Runnable() {
@Override
public void run() {
progressDialog.setMessage("Sending locally edited tasks");
progressDialog.setProgress(0);
}
});
for(SyncMapping mapping : localChanges) {
TaskProxy localTask = new TaskProxy(getId(), mapping.getRemoteId(), false);
TaskModelForSync task = Synchronizer.getTaskController().fetchTaskForSync(
TaskProxy localTask = new TaskProxy(getId(), mapping.getRemoteId(),
false);
TaskModelForSync task = taskController.fetchTaskForSync(
mapping.getTask());
localTask.readFromTaskModel(task);
@ -181,7 +253,12 @@ public abstract class SynchronizationService {
stats.mergedTasks++;
}
helper.pushTask(localTask, mapping);
try {
helper.pushTask(localTask, mapping);
} catch (Exception e) {
Log.e("astrid", "Exception pushing task", e);
continue;
}
// re-fetch remote task
if(remoteConflict != null) {
@ -190,8 +267,9 @@ public abstract class SynchronizationService {
remoteTasks.add(newTask);
}
stats.remoteUpdatedTasks++;
syncHandler.post(new ProgressUpdater(stats.remoteUpdatedTasks,
localChanges.size()));
}
stats.remoteUpdatedTasks -= stats.remoteCreatedTasks;
// load remote information
for(TaskProxy remoteTask : remoteTasks) {
@ -205,35 +283,68 @@ public abstract class SynchronizationService {
continue;
}
task = new TaskModelForSync();
stats.localCreatedTasks++;
task = taskController.searchForTaskForSync(remoteTask.name);
if(task == null) {
task = new TaskModelForSync();
} else {
mapping = localIdToSyncMapping.get(task.getTaskIdentifier());
}
} else {
mapping = remoteIdToSyncMapping.get(remoteTask.getRemoteId());
if(remoteTask.isDeleted()) {
Synchronizer.getTaskController().deleteTask(mapping.getTask());
Synchronizer.getSyncController().deleteSyncMapping(mapping);
taskController.deleteTask(mapping.getTask());
syncController.deleteSyncMapping(mapping);
stats.localDeletedTasks++;
continue;
}
task = Synchronizer.getTaskController().fetchTaskForSync(
task = taskController.fetchTaskForSync(
mapping.getTask());
}
// save the data
remoteTask.writeToTaskModel(task);
Synchronizer.getTaskController().saveTask(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
}
}
stats.localUpdatedTasks++;
if(mapping == null) {
mapping = new SyncMapping(task.getTaskIdentifier(), remoteTask);
Synchronizer.getSyncController().saveSyncMapping(mapping);
syncController.saveSyncMapping(mapping);
stats.localCreatedTasks++;
}
}
stats.localUpdatedTasks -= stats.localCreatedTasks;
Synchronizer.getSyncController().clearUpdatedTaskList(getId());
stats.showDialog(activity);
syncController.clearUpdatedTaskList(getId());
syncHandler.post(new Runnable() {
@Override
public void run() {
stats.showDialog(activity);
}
});
}
// --- helper classes
@ -251,12 +362,16 @@ public abstract class SynchronizationService {
/** Display a dialog with statistics */
public void showDialog(Context context) {
if(equals(new SyncStats())) // i.e. no change
progressDialog.dismiss();
if(equals(new SyncStats())) { // i.e. no change
DialogUtilities.okDialog(context, "Nothing to do!", null);
return;
}
StringBuilder sb = new StringBuilder();
sb.append(getName()).append(" Sync Results:"); // TODO i18n
sb.append("\n\nLocal ---");
sb.append("\n\n--- Astrid Tasks ---");
if(localCreatedTasks > 0)
sb.append("\nCreated: " + localCreatedTasks);
if(localUpdatedTasks > 0)
@ -267,7 +382,7 @@ public abstract class SynchronizationService {
if(mergedTasks > 0)
sb.append("\n\nMerged: " + localCreatedTasks);
sb.append("\n\nRemote ---");
sb.append("\n\n--- Remote Tasks ---");
if(remoteCreatedTasks > 0)
sb.append("\nCreated: " + remoteCreatedTasks);
if(remoteUpdatedTasks > 0)
@ -278,4 +393,15 @@ public abstract class SynchronizationService {
DialogUtilities.okDialog(context, sb.toString(), null);
}
}
protected class ProgressUpdater implements Runnable {
int step, outOf;
public ProgressUpdater(int step, int outOf) {
this.step = step;
this.outOf = outOf;
}
public void run() {
progressDialog.setProgress(10000*step/outOf);
}
}
}

@ -6,6 +6,7 @@ import java.util.Map;
import android.app.Activity;
import com.timsu.astrid.data.sync.SyncDataController;
import com.timsu.astrid.data.tag.TagController;
import com.timsu.astrid.data.task.TaskController;
import com.timsu.astrid.utilities.Preferences;
@ -18,24 +19,19 @@ public class Synchronizer {
/** Synchronize all activated sync services */
public static void synchronize(final Activity activity) {
// kick off a new thread
new Thread(new Runnable() {
@Override
public void run() {
// RTM sync
if(Preferences.shouldSyncRTM(activity)) {
openControllers(activity);
services.get(SYNC_ID_RTM).synchronize(activity);
}
closeControllers();
}
}, "sync").start();
// RTM sync
if(Preferences.shouldSyncRTM(activity)) {
services.get(SYNC_ID_RTM).synchronizeService(activity);
}
closeControllers();
}
/** Clears tokens if services are disabled */
public static void synchronizerStatusUpdated(Activity activity) {
// do nothing
public static void clearUserData(Activity activity) {
if(Preferences.shouldSyncRTM(activity)) {
services.get(SYNC_ID_RTM).clearPersonalData(activity);
}
}
// --- package helpers
@ -47,30 +43,35 @@ public class Synchronizer {
services.put(SYNC_ID_RTM, new RTMSyncService(SYNC_ID_RTM));
}
static SyncDataController getSyncController() {
return syncController;
}
static TaskController getTaskController() {
return taskController;
}
// --- controller stuff
private static SyncDataController syncController = null;
private static TaskController taskController = null;
private static void openControllers(Activity activity) {
static SyncDataController getSyncController(Activity activity) {
if(syncController == null) {
syncController = new SyncDataController(activity);
syncController.open();
}
return syncController;
}
static TaskController getTaskController(Activity activity) {
if(taskController == null) {
taskController = new TaskController(activity);
taskController.open();
}
return taskController;
}
static TagController getTagController(Activity activity) {
if(tagController == null) {
tagController = new TagController(activity);
tagController.open();
}
return tagController;
}
// --- controller stuff
private static SyncDataController syncController = null;
private static TaskController taskController = null;
private static TagController tagController = null;
private static void closeControllers() {
if(syncController != null) {
syncController.close();
@ -81,5 +82,10 @@ public class Synchronizer {
taskController.close();
taskController = null;
}
if(tagController != null) {
tagController.close();
tagController = null;
}
}
}

@ -143,6 +143,13 @@ public class Preferences {
return new Date(value);
}
/** Set RTM Last Successful Sync Date */
public static void setSyncRTMLastSync(Context context, Date date) {
Editor editor = getPrefs(context).edit();
editor.putLong(P_SYNC_RTM_LAST_SYNC, date.getTime());
editor.commit();
}
/** Should sync with RTM? */
public static boolean shouldSyncRTM(Context context) {
Resources r = context.getResources();
@ -152,6 +159,7 @@ public class Preferences {
// --- helper methods
@SuppressWarnings("unused")
private static void clearPref(Context context, String key) {
Editor editor = getPrefs(context).edit();
editor.remove(key);

Loading…
Cancel
Save