diff --git a/res/layout/sync_footer.xml b/res/layout/sync_footer.xml
index ea94ebb44..fbff6c8ea 100644
--- a/res/layout/sync_footer.xml
+++ b/res/layout/sync_footer.xml
@@ -28,8 +28,20 @@
android:layout_height="wrap_content">
+
+
+ 2
+
+
+ - disable
+ - twice an hour
+ - hourly
+ - twice a day
+ - daily
+ - twice a week
+ - weekly
+
+
+
+
+ - 0
+ - 1800
+ - 3600
+ - 43200
+ - 86400
+ - 302400
+ - 604800
+
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 8e3da932b..2e2fb0e28 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -248,11 +248,12 @@ If you don\'t want to see the new task right after you complete the old one, you
Actions
Options
sync_rtm
- Remember The Milk
+ Remember The Milk
http://www.rememberthemilk.com
- sync_every
- Synchronize Frequency
- If set, perform sync every # hours
+ sync_every
+ sync_freq
+ Auto-Synchronize
+ If set, synchronization occurs automatically given interval
sync_button
Main Menu Shortcut
Show \"Synchronize\" in Astrid\'s menu
@@ -281,6 +282,16 @@ Wish me luck!\n
Clear Personal Data
Clear data for selected services?
No Synchronizers Enabled!
+ Last Sync Date: %s
+ Last AutoSync Attempt: %s
+ never
+ %s Results
+ Summary - Astrid Tasks:
+ Summary - Remote Server:
+ Created: %d
+ Updated: %d
+ Deleted: %d
+ Merged: %d
diff --git a/res/xml/sync_preferences.xml b/res/xml/sync_preferences.xml
index deb15db82..4417a3d4d 100644
--- a/res/xml/sync_preferences.xml
+++ b/res/xml/sync_preferences.xml
@@ -15,10 +15,12 @@
-
+
();
for(TagModelForView tag : tagArray) {
LinkedList tasks = getTagController().getTaggedTasks(
- getParent(), tag.getTagIdentifier());
+ tag.getTagIdentifier());
int count = 0;
for(TaskIdentifier task : tasks)
if(activeTasks.contains(task))
@@ -156,7 +156,7 @@ public class TagListSubActivity extends SubActivity {
/** Fill in the Tag List with our tags */
private synchronized void fillData() {
try {
- tagArray = getTagController().getAllTags(getParent());
+ tagArray = getTagController().getAllTags();
sortTagArray();
} catch (StaleDataException e) {
// happens when you rotate the screen while the thread is
diff --git a/src/com/timsu/astrid/activities/TaskEdit.java b/src/com/timsu/astrid/activities/TaskEdit.java
index c078636d7..1eb3e0131 100644
--- a/src/com/timsu/astrid/activities/TaskEdit.java
+++ b/src/com/timsu/astrid/activities/TaskEdit.java
@@ -216,9 +216,9 @@ public class TaskEdit extends TaskModificationTabbedActivity {
addToCalendar.setText(r.getString(R.string.showCalendar_label));
// tags
- tags = tagController.getAllTags(this);
+ tags = tagController.getAllTags();
if(model.getTaskIdentifier() != null) {
- taskTags = tagController.getTaskTags(this, model.getTaskIdentifier());
+ taskTags = tagController.getTaskTags(model.getTaskIdentifier());
if(taskTags.size() > 0) {
Map tagsMap =
new HashMap();
@@ -286,7 +286,7 @@ public class TaskEdit extends TaskModificationTabbedActivity {
saveTags();
saveAlerts();
Notifications.updateAlarm(this, controller, alertController, model);
-
+
Date dueDate = model.getPreferredDueDate();
if (dueDate == null) {
dueDate = model.getDefiniteDueDate();
@@ -296,33 +296,33 @@ public class TaskEdit extends TaskModificationTabbedActivity {
} else {
showSaveToast();
}
-
+
} catch (Exception e) {
Log.e("astrid", "Error saving", e);
}
}
/**
- * Displays a Toast reporting that the selected task has been saved and is
+ * Displays a Toast reporting that the selected task has been saved and is
* due in 'x' amount of time, to 2 time-units of precision (e.g. Days + Hours).
- * @param dueDate the Date when the task is due
+ * @param dueDate the Date when the task is due
*/
private void showSaveToast(Date dueDate) {
int timeInSeconds = (int)((dueDate.getTime() - System.currentTimeMillis())/1000L);
String formattedDate = DateUtilities.getDurationString(getResources(), timeInSeconds, 2);
- Toast.makeText(this,
+ Toast.makeText(this,
getResources().getString(R.string.taskEdit_onTaskSave_Due, formattedDate),
Toast.LENGTH_SHORT).show();
}
-
+
/**
* Displays a Toast reporting that the selected task has been saved.
* Use this version when no due Date has been set.
*/
private void showSaveToast() {
Toast.makeText(this, R.string.taskEdit_onTaskSave_notDue, Toast.LENGTH_SHORT).show();
- }
-
+ }
+
/** Save task tags. Must be called after task already has an ID */
private void saveTags() {
Set tagsToDelete;
diff --git a/src/com/timsu/astrid/activities/TaskList.java b/src/com/timsu/astrid/activities/TaskList.java
index b06aeeab8..6bd4c72dc 100644
--- a/src/com/timsu/astrid/activities/TaskList.java
+++ b/src/com/timsu/astrid/activities/TaskList.java
@@ -19,8 +19,6 @@
*/
package com.timsu.astrid.activities;
-import java.util.Date;
-
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
@@ -41,7 +39,6 @@ import com.timsu.astrid.data.tag.TagController;
import com.timsu.astrid.data.task.TaskController;
import com.timsu.astrid.sync.Synchronizer;
import com.timsu.astrid.utilities.Constants;
-import com.timsu.astrid.utilities.Preferences;
import com.timsu.astrid.utilities.StartupReceiver;
/**
@@ -128,20 +125,11 @@ public class TaskList extends Activity {
getCurrentSubActivity().onDisplay(variables);
}
- // auto sync if requested
- Float autoSyncHours = Preferences.autoSyncFrequency(this);
+ // sync now if requested
if(synchronizeNow) {
- synchronizeNow = false;
Synchronizer.synchronize(this, true, null);
- } else if(autoSyncHours != null) {
- final Date lastSync = Preferences.getSyncLastSync(this);
-
- if(lastSync == null || lastSync.getTime() +
- 1000L*3600*autoSyncHours < System.currentTimeMillis()) {
- Synchronizer.synchronize(this, true, null);
- }
}
-
+
// if we have no filter tag, we're not on the last task
if(getCurrentSubActivity() == taskListWTag &&
((TaskListSubActivity)taskListWTag).getFilterTag() == null) {
diff --git a/src/com/timsu/astrid/activities/TaskListSubActivity.java b/src/com/timsu/astrid/activities/TaskListSubActivity.java
index 4621ad463..d5e9d1dc5 100644
--- a/src/com/timsu/astrid/activities/TaskListSubActivity.java
+++ b/src/com/timsu/astrid/activities/TaskListSubActivity.java
@@ -59,6 +59,7 @@ 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.SynchronizationService;
import com.timsu.astrid.sync.Synchronizer;
import com.timsu.astrid.sync.Synchronizer.SynchronizerListener;
import com.timsu.astrid.utilities.Constants;
@@ -132,6 +133,9 @@ public class TaskListSubActivity extends SubActivity {
// in another activity)
static boolean shouldRefreshTaskList = false;
+ // indicator flag set if synchronization window has been opened & closed
+ static boolean syncPreferencesOpened = false;
+
// other instance variables
class TaskListContext {
Map tagMap;
@@ -205,7 +209,7 @@ public class TaskListSubActivity extends SubActivity {
// process tag to filter, if any
if(variables != null && variables.containsKey(TAG_TOKEN)) {
TagIdentifier identifier = new TagIdentifier(variables.getLong(TAG_TOKEN));
- context.tagMap = getTagController().getAllTagsAsMap(getParent());
+ context.tagMap = getTagController().getAllTagsAsMap();
if(context.tagMap.containsKey(identifier))
context.filterTag = context.tagMap.get(identifier);
else
@@ -480,7 +484,7 @@ public class TaskListSubActivity extends SubActivity {
// get a cursor to the task list
Cursor tasksCursor;
if(context.filterTag != null) {
- LinkedList tasks = getTagController().getTaggedTasks(getParent(),
+ LinkedList tasks = getTagController().getTaggedTasks(
context.filterTag.getTagIdentifier());
tasksCursor = getTaskController().getTaskListCursorById(tasks);
} else {
@@ -493,7 +497,7 @@ public class TaskListSubActivity extends SubActivity {
context.taskArray = getTaskController().createTaskListFromCursor(tasksCursor);
// read tags and apply filters
- context.tagMap = getTagController().getAllTagsAsMap(getParent());
+ context.tagMap = getTagController().getAllTagsAsMap();
context.taskTags = new HashMap();
StringBuilder tagBuilder = new StringBuilder();
context.tasksById = new HashMap();
@@ -515,7 +519,7 @@ public class TaskListSubActivity extends SubActivity {
}
// get list of tags
- LinkedList tagIds = getTagController().getTaskTags(getParent(),
+ LinkedList tagIds = getTagController().getTaskTags(
task.getTaskIdentifier());
tagBuilder.delete(0, tagBuilder.length());
for(Iterator j = tagIds.iterator(); j.hasNext(); ) {
@@ -798,7 +802,14 @@ public class TaskListSubActivity extends SubActivity {
if (hasFocus) {
if (shouldRefreshTaskList)
reloadList();
- else if (context.taskArray != null &&
+ else if(syncPreferencesOpened) {
+ syncPreferencesOpened = false;
+
+ // stop & start synchronization service
+ Intent service = new Intent(getParent(), SynchronizationService.class);
+ getParent().stopService(service);
+ getParent().startService(service);
+ } else if (context.taskArray != null &&
context.taskArray.size() > 0 &&
context.taskArray.size() < AUTO_REFRESH_MAX_LIST_SIZE) {
@@ -1013,7 +1024,7 @@ public class TaskListSubActivity extends SubActivity {
showTagsView();
return true;
case SYNC_ID:
- onActivityResult(ACTIVITY_SYNCHRONIZE, Constants.RESULT_SYNCHRONIZE, null);
+ onActivityResult(ACTIVITY_SYNCHRONIZE, Constants.RESULT_SYNCHRONIZE, null);
return true;
case MORE_ID:
layout.showContextMenu();
@@ -1021,6 +1032,7 @@ public class TaskListSubActivity extends SubActivity {
// --- more options menu items
case OPTIONS_SYNC_ID:
+ syncPreferencesOpened = true;
launchActivity(new Intent(getParent(), SyncPreferences.class),
ACTIVITY_SYNCHRONIZE);
return true;
diff --git a/src/com/timsu/astrid/data/tag/TagController.java b/src/com/timsu/astrid/data/tag/TagController.java
index be3697615..46afe5d60 100644
--- a/src/com/timsu/astrid/data/tag/TagController.java
+++ b/src/com/timsu/astrid/data/tag/TagController.java
@@ -41,19 +41,22 @@ public class TagController extends AbstractController {
// --- tag batch operations
/** Get a list of all tags */
- public LinkedList getAllTags(Activity activity)
+ public LinkedList getAllTags()
throws SQLException {
LinkedList list = new LinkedList();
Cursor cursor = tagDatabase.query(TAG_TABLE_NAME,
TagModelForView.FIELD_LIST, null, null, null, null, null, null);
- activity.startManagingCursor(cursor);
- if(cursor.getCount() == 0)
- return list;
- do {
- cursor.moveToNext();
- list.add(new TagModelForView(cursor));
- } while(!cursor.isLast());
+ try {
+ if(cursor.getCount() == 0)
+ return list;
+ do {
+ cursor.moveToNext();
+ list.add(new TagModelForView(cursor));
+ } while(!cursor.isLast());
+ } finally {
+ cursor.close();
+ }
return list;
}
@@ -61,47 +64,53 @@ public class TagController extends AbstractController {
// --- tag to task map batch operations
/** Get a list of all tags as an id => tag map */
- public HashMap getAllTagsAsMap(Activity activity) throws SQLException {
+ public HashMap getAllTagsAsMap() throws SQLException {
HashMap map = new HashMap();
- for(TagModelForView tag : getAllTags(activity))
+ for(TagModelForView tag : getAllTags())
map.put(tag.getTagIdentifier(), tag);
return map;
}
/** Get a list of tag identifiers for the given task */
- public LinkedList getTaskTags(Activity activity, TaskIdentifier
+ public LinkedList getTaskTags(TaskIdentifier
taskId) throws SQLException {
LinkedList list = new LinkedList();
Cursor cursor = tagToTaskMapDatabase.query(TAG_TASK_MAP_NAME,
TagToTaskMapping.FIELD_LIST, TagToTaskMapping.TASK + " = ?",
new String[] { taskId.idAsString() }, null, null, null);
- activity.startManagingCursor(cursor);
- if(cursor.getCount() == 0)
- return list;
- do {
- cursor.moveToNext();
- list.add(new TagToTaskMapping(cursor).getTag());
- } while(!cursor.isLast());
+ try {
+ if(cursor.getCount() == 0)
+ return list;
+ do {
+ cursor.moveToNext();
+ list.add(new TagToTaskMapping(cursor).getTag());
+ } while(!cursor.isLast());
+ } finally {
+ cursor.close();
+ }
return list;
}
/** Get a list of task identifiers for the given tag */
- public LinkedList getTaggedTasks(Activity activity, TagIdentifier
+ public LinkedList getTaggedTasks(TagIdentifier
tagId) throws SQLException {
LinkedList list = new LinkedList();
Cursor cursor = tagToTaskMapDatabase.query(TAG_TASK_MAP_NAME,
TagToTaskMapping.FIELD_LIST, TagToTaskMapping.TAG + " = ?",
new String[] { tagId.idAsString() }, null, null, null);
- activity.startManagingCursor(cursor);
-
- if(cursor.getCount() == 0)
- return list;
- do {
- cursor.moveToNext();
- list.add(new TagToTaskMapping(cursor).getTask());
- } while(!cursor.isLast());
+
+ try {
+ if(cursor.getCount() == 0)
+ return list;
+ do {
+ cursor.moveToNext();
+ list.add(new TagToTaskMapping(cursor).getTask());
+ } while(!cursor.isLast());
+ } finally {
+ cursor.close();
+ }
return list;
}
diff --git a/src/com/timsu/astrid/sync/RTMSyncService.java b/src/com/timsu/astrid/sync/RTMSyncProvider.java
similarity index 84%
rename from src/com/timsu/astrid/sync/RTMSyncService.java
rename to src/com/timsu/astrid/sync/RTMSyncProvider.java
index c82444f73..be9e49fb3 100644
--- a/src/com/timsu/astrid/sync/RTMSyncService.java
+++ b/src/com/timsu/astrid/sync/RTMSyncProvider.java
@@ -27,8 +27,7 @@ import java.util.Map;
import java.util.StringTokenizer;
import java.util.Map.Entry;
-import android.app.Activity;
-import android.app.Dialog;
+import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.Resources;
@@ -56,14 +55,14 @@ import com.timsu.astrid.data.task.TaskModelForSync;
import com.timsu.astrid.utilities.DialogUtilities;
import com.timsu.astrid.utilities.Preferences;
-public class RTMSyncService extends SynchronizationService {
+public class RTMSyncProvider extends SynchronizationProvider {
private ServiceImpl rtmService = null;
private String INBOX_LIST_NAME = "Inbox";
Map listNameToIdMap = new HashMap();
Map listIdToNameMap = new HashMap();
- public RTMSyncService(int id) {
+ public RTMSyncProvider(int id) {
super(id);
}
@@ -75,41 +74,27 @@ public class RTMSyncService extends SynchronizationService {
}
@Override
- protected void synchronize(final Activity activity) {
- if(Preferences.shouldSyncRTM(activity) && rtmService == null &&
- Preferences.getSyncRTMToken(activity) == null) {
- DialogUtilities.okCancelDialog(activity,
- activity.getResources().getString(R.string.sync_rtm_notes),
- new Dialog.OnClickListener() {
- public void onClick(DialogInterface dialog, int which) {
- authenticate(activity);
- }
- }, new Dialog.OnClickListener() {
- public void onClick(DialogInterface dialog, int which) {
- if(progressDialog != null)
- progressDialog.dismiss();
- }
- });
- } else
- authenticate(activity);
+ protected void synchronize(final Context activity) {
+ // authenticate the user. this will automatically call the next step
+ authenticate(activity);
}
@Override
- public void clearPersonalData(Activity activity) {
- Preferences.setSyncRTMToken(activity, null);
- Preferences.setSyncRTMLastSync(activity, null);
- Synchronizer.getSyncController(activity).deleteAllMappings(getId());
+ public void clearPersonalData(Context context) {
+ Preferences.setSyncRTMToken(context, null);
+ Preferences.setSyncRTMLastSync(context, null);
+ Synchronizer.getSyncController(context).deleteAllMappings(getId());
}
// --- authentication
/** Perform authentication with RTM. Will open the SyncBrowser if necessary */
- private void authenticate(final Activity activity) {
+ private void authenticate(final Context context) {
try {
String apiKey = "bd9883b3384a21ead17501da38bb1e68";
String sharedSecret = "a19b2a020345219b";
String appName = null;
- String authToken = Preferences.getSyncRTMToken(activity);
+ String authToken = Preferences.getSyncRTMToken(context);
// check if we have a token & it works
if(authToken != null) {
@@ -125,8 +110,8 @@ public class RTMSyncService extends SynchronizationService {
try {
String token = rtmService.completeAuthorization();
Log.w("astrid", "got RTM token: " + token);
- Preferences.setSyncRTMToken(activity, token);
- performSync(activity);
+ Preferences.setSyncRTMToken(context, token);
+ performSync(context);
return;
} catch (Exception e) {
@@ -134,24 +119,28 @@ public class RTMSyncService extends SynchronizationService {
}
}
+ // open up a dialog and have the user go to browser
+ if(isBackgroundService())
+ return;
+
rtmService = new ServiceImpl(new ApplicationInfo(
apiKey, sharedSecret, appName));
final String url = rtmService.beginAuthorization(Perms.delete);
progressDialog.dismiss();
- Resources r = activity.getResources();
- DialogUtilities.okCancelDialog(activity,
+ Resources r = context.getResources();
+ DialogUtilities.okCancelDialog(context,
r.getString(R.string.sync_auth_request, "RTM"),
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
TaskList.synchronizeNow = true;
Intent intent = new Intent(Intent.ACTION_VIEW,
Uri.parse(url));
- activity.startActivity(intent);
+ context.startActivity(intent);
}
}, null);
} else {
- performSync(activity);
+ performSync(context);
}
} catch (Exception e) {
@@ -159,31 +148,31 @@ public class RTMSyncService extends SynchronizationService {
if(e instanceof ServiceInternalException &&
((ServiceInternalException)e).getEnclosedException() instanceof
IOException) {
- showError(activity, e, "Sync Connection Error! Check your " +
+ showError(context, e, "Sync Connection Error! Check your " +
"Internet connection & try again...");
} else
- showError(activity, e, null);
+ showError(context, e, null);
}
}
// --- synchronization!
- private void performSync(final Activity activity) {
+ private void performSync(final Context context) {
new Thread(new Runnable() {
public void run() {
- performSyncInNewThread(activity);
+ performSyncInNewThread(context);
}
}).start();
}
- private void performSyncInNewThread(final Activity activity) {
+ private void performSyncInNewThread(final Context context) {
try {
- syncHandler.post(new ProgressLabelUpdater("Reading remote data"));
- syncHandler.post(new ProgressUpdater(0, 5));
+ postUpdate(new ProgressLabelUpdater("Reading remote data"));
+ postUpdate(new ProgressUpdater(0, 5));
// get RTM timeline
final String timeline = rtmService.timelines_create();
- syncHandler.post(new ProgressUpdater(1, 5));
+ postUpdate(new ProgressUpdater(1, 5));
// load RTM lists
RtmLists lists = rtmService.lists_getList();
@@ -195,11 +184,11 @@ public class RTMSyncService extends SynchronizationService {
if(INBOX_LIST_NAME.equalsIgnoreCase(list.getName()))
INBOX_LIST_NAME = list.getName();
}
- syncHandler.post(new ProgressUpdater(2, 5));
+ postUpdate(new ProgressUpdater(2, 5));
// read all tasks
LinkedList remoteChanges = new LinkedList();
- Date lastSyncDate = Preferences.getSyncRTMLastSync(activity);
+ Date lastSyncDate = Preferences.getSyncRTMLastSync(context);
boolean shouldSyncIndividualLists = false;
String filter = null;
if(lastSyncDate == null)
@@ -208,9 +197,9 @@ public class RTMSyncService extends SynchronizationService {
// try the quick synchronization
try {
Thread.sleep(2000); // throttle
- syncHandler.post(new ProgressUpdater(3, 5));
+ postUpdate(new ProgressUpdater(3, 5));
RtmTasks tasks = rtmService.tasks_getList(null, filter, lastSyncDate);
- syncHandler.post(new ProgressUpdater(5, 5));
+ postUpdate(new ProgressUpdater(5, 5));
addTasksToList(tasks, remoteChanges);
} catch (Exception e) {
remoteChanges.clear();
@@ -220,9 +209,9 @@ public class RTMSyncService extends SynchronizationService {
if(shouldSyncIndividualLists) {
int progress = 0;
for(final Entry entry : listIdToNameMap.entrySet()) {
- syncHandler.post(new ProgressLabelUpdater("Reading " +
+ postUpdate(new ProgressLabelUpdater("Reading " +
" list: " + entry.getValue()));
- syncHandler.post(new ProgressUpdater(progress++,
+ postUpdate(new ProgressUpdater(progress++,
listIdToNameMap.size()));
try {
Thread.sleep(1500);
@@ -230,9 +219,9 @@ public class RTMSyncService extends SynchronizationService {
filter, lastSyncDate);
addTasksToList(tasks, remoteChanges);
} catch (Exception e) {
- syncHandler.post(new Runnable() {
+ postUpdate(new Runnable() {
public void run() {
- DialogUtilities.okDialog(activity,
+ DialogUtilities.okDialog(context,
"List '" + entry.getValue() +
"' import failed (too big?)", null);
}
@@ -240,17 +229,17 @@ public class RTMSyncService extends SynchronizationService {
continue;
}
}
- syncHandler.post(new ProgressUpdater(1, 1));
+ postUpdate(new ProgressUpdater(1, 1));
}
- synchronizeTasks(activity, remoteChanges, new RtmSyncHelper(timeline));
+ synchronizeTasks(context, 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);
- Preferences.setSyncRTMLastSync(activity, syncTime);
+ Preferences.setSyncRTMLastSync(context, syncTime);
} catch (Exception e) {
- showError(activity, e, null);
+ showError(context, e, null);
}
}
@@ -335,8 +324,8 @@ public class RTMSyncService extends SynchronizationService {
}
// estimated time
- if(task.estimatedSeconds != remoteTask.estimatedSeconds &&
- !task.estimatedSeconds.equals(remoteTask.estimatedSeconds)) {
+ if(task.estimatedSeconds == 0 && remoteTask.estimatedSeconds != null ||
+ task.estimatedSeconds > 0 && remoteTask.estimatedSeconds == null) {
String estimation;
int estimatedSeconds = task.estimatedSeconds;
if(estimatedSeconds == 0)
diff --git a/src/com/timsu/astrid/sync/SynchronizationProvider.java b/src/com/timsu/astrid/sync/SynchronizationProvider.java
new file mode 100644
index 000000000..4895bef20
--- /dev/null
+++ b/src/com/timsu/astrid/sync/SynchronizationProvider.java
@@ -0,0 +1,578 @@
+/*
+ * 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
+ */
+package com.timsu.astrid.sync;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+
+import android.app.Dialog;
+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.alerts.AlertController;
+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;
+import com.timsu.astrid.utilities.Notifications;
+import com.timsu.astrid.utilities.Preferences;
+
+/** A service that synchronizes with Astrid
+ *
+ * @author timsu
+ *
+ */
+public abstract class SynchronizationProvider {
+
+ private int id;
+ static ProgressDialog progressDialog;
+ private Handler syncHandler;
+ private boolean backgroundSync;
+
+ public SynchronizationProvider(int id) {
+ this.id = id;
+ }
+
+ // called off the UI thread. does some setup
+ void synchronizeService(final Context activity, boolean isBackgroundSync) {
+ this.backgroundSync = isBackgroundSync;
+
+ if(!isBackgroundService()) {
+ syncHandler = new Handler();
+ SynchronizationProvider.progressDialog = new ProgressDialog(activity);
+ progressDialog.setIcon(android.R.drawable.ic_dialog_alert);
+ progressDialog.setTitle("Synchronization");
+ progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
+ progressDialog.setMax(100);
+ progressDialog.setMessage("Checking Authorization...");
+ progressDialog.setProgress(0);
+ progressDialog.setCancelable(false);
+ progressDialog.show();
+ }
+
+ synchronize(activity);
+ }
+
+ /** Synchronize with the service */
+ protected abstract void synchronize(Context activity);
+
+ /** Called when user requests a data clear */
+ abstract void clearPersonalData(Context activity);
+
+ /** Get this service's id */
+ public int getId() {
+ return id;
+ }
+
+ /** Gets this service's name */
+ abstract String getName();
+
+ // --- utilities
+
+ /** Check whether this synchronization request is running in the background
+ * @return true if it's running as a background service
+ */
+ protected boolean isBackgroundService() {
+ return backgroundSync;
+ }
+
+ /** Utility method for showing synchronization errors. If message is null,
+ * the contents of the throwable is displayed.
+ */
+ void showError(final Context context, Throwable e, String message) {
+ Log.e("astrid", "Synchronization Error", e);
+
+ if(isBackgroundService())
+ return;
+
+ Resources r = context.getResources();
+ final String messageToDisplay;
+ if(message == null) {
+ messageToDisplay = r.getString(R.string.sync_error) + " " +
+ e.toString() + " - " + e.getStackTrace()[1];
+ } else {
+ messageToDisplay = message;
+ }
+ syncHandler.post(new Runnable() {
+ public void run() {
+ if(progressDialog != null)
+ progressDialog.dismiss();
+ DialogUtilities.okDialog(context, messageToDisplay, null);
+ }
+ });
+ }
+
+ /** Utility method to update the UI if we're an active sync, or output
+ * to console if we're a background sync.
+ */
+ protected void postUpdate(Runnable updater) {
+ if(isBackgroundService()) {
+ // only run jobs if they can actually be processed
+ if(updater instanceof ProgressLabelUpdater)
+ updater.run();
+ } else {
+ syncHandler.post(updater);
+ }
+ }
+
+ // --- synchronization logic
+
+ /** interface to assist with synchronization */
+ protected interface SynchronizeHelper {
+ /** 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, TaskProxy remoteTask,
+ SyncMapping mapping) throws IOException;
+
+ /** Create a task on the remote server. This is followed by a call of
+ * pushTask on the id in question.
+ *
+ * @return task to create
+ * @return remote id
+ */
+ String createTask(TaskModelForSync task) throws IOException;
+
+ /** Fetch remote task. Used to re-read merged tasks
+ *
+ * @param task TaskProxy of the original task
+ * @return new TaskProxy
+ */
+ TaskProxy refetchTask(TaskProxy task) throws IOException;
+
+ /** Delete the task from the remote server
+ *
+ * @param mapping mapping to delete
+ */
+ void deleteTask(SyncMapping mapping) throws IOException;
+ }
+
+ /** Helper to synchronize remote tasks with our local database.
+ *
+ * This initiates the following process:
+ * 1. local changes are read
+ * 2. remote changes are read
+ * 3. local tasks are merged with remote changes and pushed across
+ * 4. remote changes are then read in
+ *
+ * @param remoteTasks remote tasks that have been updated
+ * @return local tasks that need to be pushed across
+ */
+ protected void synchronizeTasks(final Context context, LinkedList
+ remoteTasks, SynchronizeHelper helper) throws IOException {
+ final SyncStats stats = new SyncStats();
+ final StringBuilder log = new StringBuilder();
+
+ SyncDataController syncController = Synchronizer.getSyncController(context);
+ TaskController taskController = Synchronizer.getTaskController(context);
+ TagController tagController = Synchronizer.getTagController(context);
+ AlertController alertController = Synchronizer.getAlertController(context);
+ SyncData data = new SyncData(context, remoteTasks);
+
+ // 1. CREATE: grab tasks without a sync mapping and create them remotely
+ log.append(">> on remote server:\n");
+ for(TaskIdentifier taskId : data.newlyCreatedTasks) {
+ TaskModelForSync task = taskController.fetchTaskForSync(taskId);
+ postUpdate(new ProgressLabelUpdater("Sending local task: " +
+ task.getName()));
+ postUpdate(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(data.newRemoteTasks.containsKey(task.getName())) {
+ TaskProxy remoteTask = data.newRemoteTasks.get(task.getName());
+ SyncMapping mapping = new SyncMapping(taskId, getId(),
+ remoteTask.getRemoteId());
+ syncController.saveSyncMapping(mapping);
+ data.localChanges.add(mapping);
+ data.remoteChangeMap.put(taskId, remoteTask);
+ data.localIdToSyncMapping.put(taskId, mapping);
+ continue;
+ }
+
+ String remoteId = helper.createTask(task);
+ SyncMapping mapping = new SyncMapping(taskId, getId(), remoteId);
+ syncController.saveSyncMapping(mapping);
+ data.localIdToSyncMapping.put(taskId, mapping);
+
+ TaskProxy localTask = new TaskProxy(getId(), remoteId, false);
+ localTask.readFromTaskModel(task);
+ localTask.readTagsFromController(taskId, tagController, data.tags);
+ helper.pushTask(localTask, null, mapping);
+
+ // update stats
+ log.append("added '" + task.getName() + "'\n");
+ stats.remoteCreatedTasks++;
+ }
+
+ // 2. DELETE: find deleted tasks and remove them from the list
+ postUpdate(new ProgressLabelUpdater("Sending locally deleted tasks"));
+ for(TaskIdentifier taskId : data.deletedTasks) {
+ SyncMapping mapping = data.localIdToSyncMapping.get(taskId);
+ syncController.deleteSyncMapping(mapping);
+ helper.deleteTask(mapping);
+
+ // remove it from data structures
+ data.localChanges.remove(mapping);
+ data.localIdToSyncMapping.remove(taskId);
+ data.remoteIdToSyncMapping.remove(mapping);
+ data.remoteChangeMap.remove(taskId);
+
+ // update stats
+ log.append("deleted id #" + taskId.getId() + "\n");
+ stats.remoteDeletedTasks++;
+ postUpdate(new ProgressUpdater(stats.remoteDeletedTasks,
+ data.deletedTasks.size()));
+ }
+
+ // 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(task.getTaskIdentifier(),
+ tagController, data.tags);
+
+ postUpdate(new ProgressLabelUpdater("Sending local task: " +
+ task.getName()));
+ postUpdate(new ProgressUpdater(stats.remoteUpdatedTasks,
+ data.localChanges.size()));
+
+ // if there is a conflict, merge
+ TaskProxy remoteConflict = null;
+ if(data.remoteChangeMap.containsKey(mapping.getTask())) {
+ remoteConflict = data.remoteChangeMap.get(mapping.getTask());
+ localTask.mergeWithOther(remoteConflict);
+ stats.mergedTasks++;
+ }
+
+ try {
+ 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;
+ }
+
+ // re-fetch remote task
+ if(remoteConflict != null) {
+ TaskProxy newTask = helper.refetchTask(remoteConflict);
+ remoteTasks.remove(remoteConflict);
+ remoteTasks.add(newTask);
+ } else
+ stats.remoteUpdatedTasks++;
+ }
+
+ // 4. REMOTE SYNC load remote information
+ log.append("\n>> on astrid:\n");
+ postUpdate(new ProgressUpdater(0, 1));
+ for(TaskProxy remoteTask : remoteTasks) {
+ if(remoteTask.name != null)
+ postUpdate(new ProgressLabelUpdater("Updating local " +
+ "tasks: " + remoteTask.name));
+ else
+ postUpdate(new ProgressLabelUpdater("Updating local tasks"));
+ SyncMapping mapping = null;
+ TaskModelForSync task = null;
+
+ // if it's new, create a new task model
+ if(!data.remoteIdToSyncMapping.containsKey(remoteTask.getRemoteId())) {
+ // if it's new & deleted, forget about it
+ if(remoteTask.isDeleted()) {
+ continue;
+ }
+
+ task = taskController.searchForTaskForSync(remoteTask.name);
+ if(task == null) {
+ task = new TaskModelForSync();
+ setupTaskDefaults(context, task);
+ log.append("added " + remoteTask.name + "\n");
+ } else {
+ mapping = data.localIdToSyncMapping.get(task.getTaskIdentifier());
+ log.append("merged " + remoteTask.name + "\n");
+ }
+ } else {
+ mapping = data.remoteIdToSyncMapping.get(remoteTask.getRemoteId());
+ if(remoteTask.isDeleted()) {
+ taskController.deleteTask(mapping.getTask());
+ syncController.deleteSyncMapping(mapping);
+ log.append("deleted " + remoteTask.name + "\n");
+ stats.localDeletedTasks++;
+ continue;
+ }
+
+ log.append("updated '" + remoteTask.name + "'\n");
+ task = taskController.fetchTaskForSync(
+ mapping.getTask());
+ }
+
+ // save the data
+ remoteTask.writeToTaskModel(task);
+ taskController.saveTask(task);
+
+ // save tags
+ if(remoteTask.tags != null) {
+ LinkedList taskTags = tagController.getTaskTags(task.getTaskIdentifier());
+ HashSet tagsToAdd = new HashSet();
+ 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 tagsToDelete = new HashSet(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) {
+ mapping = data.localIdToSyncMapping.get(task.getTaskIdentifier());
+ if(mapping == null) {
+ try {
+ mapping = new SyncMapping(task.getTaskIdentifier(), remoteTask);
+ syncController.saveSyncMapping(mapping);
+ data.localIdToSyncMapping.put(task.getTaskIdentifier(),
+ mapping);
+ } catch (Exception e) {
+ // unique violation: ignore - it'll get merged later
+ Log.e("astrid-sync", "Exception creating mapping", e);
+ }
+ }
+ stats.localCreatedTasks++;
+ }
+
+ Notifications.updateAlarm(context, taskController, alertController,
+ task);
+ postUpdate(new ProgressUpdater(stats.localUpdatedTasks,
+ remoteTasks.size()));
+ }
+ stats.localUpdatedTasks -= stats.localCreatedTasks;
+
+ syncController.clearUpdatedTaskList(getId());
+ postUpdate(new Runnable() {
+ public void run() {
+ stats.showDialog(context, log.toString());
+ }
+ });
+ }
+
+ /** Set up defaults from preferences for this task */
+ private void setupTaskDefaults(Context context, TaskModelForSync task) {
+ Integer reminder = Preferences.getDefaultReminder(context);
+ if(reminder != null)
+ task.setNotificationIntervalSeconds(24*3600*reminder);
+ }
+
+ // --- helper classes
+
+ /** data structure builder */
+ class SyncData {
+ HashSet mappings;
+ HashSet activeTasks;
+ HashSet allTasks;
+
+ HashMap remoteIdToSyncMapping;
+ HashMap localIdToSyncMapping;
+
+ HashSet localChanges;
+ HashSet mappedTasks;
+ HashMap remoteChangeMap;
+ HashMap newRemoteTasks;
+
+ HashMap tags;
+ HashMap tagsByLCName;
+
+ HashSet newlyCreatedTasks;
+ HashSet deletedTasks;
+
+ public SyncData(Context context, LinkedList remoteTasks) {
+ // 1. get data out of the database
+ mappings = Synchronizer.getSyncController(context).getSyncMapping(getId());
+ activeTasks = Synchronizer.getTaskController(context).getActiveTaskIdentifiers();
+ allTasks = Synchronizer.getTaskController(context).getAllTaskIdentifiers();
+ tags = Synchronizer.getTagController(context).getAllTagsAsMap();
+
+ // 2. build helper data structures
+ remoteIdToSyncMapping = new HashMap();
+ localIdToSyncMapping = new HashMap();
+ localChanges = new HashSet();
+ mappedTasks = new HashSet();
+ 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();
+ for(TagModelForView tag : tags.values())
+ tagsByLCName.put(tag.getName().toLowerCase(), tag.getTagIdentifier());
+
+ // 3. build map of remote tasks
+ remoteChangeMap = new HashMap();
+ newRemoteTasks = new HashMap();
+ 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(activeTasks);
+ newlyCreatedTasks.removeAll(mappedTasks);
+ deletedTasks = new HashSet(mappedTasks);
+ deletedTasks.removeAll(allTasks);
+ }
+ }
+
+
+ /** statistics tracking and displaying */
+ protected class SyncStats {
+ int localCreatedTasks = 0;
+ int localUpdatedTasks = 0;
+ int localDeletedTasks = 0;
+
+ int mergedTasks = 0;
+
+ int remoteCreatedTasks = 0;
+ int remoteUpdatedTasks = 0;
+ int remoteDeletedTasks = 0;
+
+ /** Display a dialog with statistics */
+ public void showDialog(final Context context, String log) {
+ progressDialog.hide();
+ Resources r = context.getResources();
+
+ if(Preferences.shouldSuppressSyncDialogs(context))
+ return;
+
+ Dialog.OnClickListener finishListener = new Dialog.OnClickListener() {
+ public void onClick(DialogInterface dialog,
+ int which) {
+ Synchronizer.continueSynchronization(context);
+ }
+ };
+
+ // nothing updated
+ if(localCreatedTasks + localUpdatedTasks + localDeletedTasks +
+ mergedTasks + remoteCreatedTasks + remoteDeletedTasks +
+ remoteUpdatedTasks == 0) {
+ if(!isBackgroundService())
+ DialogUtilities.okDialog(context, "Sync: Up to date!", finishListener);
+ return;
+ }
+
+ StringBuilder sb = new StringBuilder();
+ sb.append(r.getString(R.string.sync_result_title, getName()));
+ sb.append("\n\n");
+ sb.append(log).append("\n");
+ if(localCreatedTasks + localUpdatedTasks + localDeletedTasks > 0)
+ sb.append(r.getString(R.string.sync_result_local)).append("\n");
+ if(localCreatedTasks > 0)
+ sb.append(r.getString(R.string.sync_result_created, localCreatedTasks)).append("\n");
+ if(localUpdatedTasks > 0)
+ sb.append(r.getString(R.string.sync_result_updated, localUpdatedTasks)).append("\n");
+ if(localDeletedTasks > 0)
+ sb.append(r.getString(R.string.sync_result_deleted, localDeletedTasks)).append("\n");
+
+ if(mergedTasks > 0)
+ sb.append("\n").append(r.getString(R.string.sync_result_merged, mergedTasks)).append("\n");
+ sb.append("\n");
+
+ if(remoteCreatedTasks + remoteDeletedTasks + remoteUpdatedTasks > 0)
+ sb.append(r.getString(R.string.sync_result_remote)).append("\n");
+ if(remoteCreatedTasks > 0)
+ sb.append(r.getString(R.string.sync_result_created, remoteCreatedTasks)).append("\n");
+ if(remoteUpdatedTasks > 0)
+ sb.append(r.getString(R.string.sync_result_updated, remoteUpdatedTasks)).append("\n");
+ if(remoteDeletedTasks > 0)
+ sb.append(r.getString(R.string.sync_result_deleted, remoteDeletedTasks)).append("\n");
+
+ sb.append("\n");
+
+ DialogUtilities.okDialog(context, sb.toString(), finishListener);
+ }
+ }
+
+ protected class ProgressUpdater implements Runnable {
+ int step, outOf;
+ public ProgressUpdater(int step, int outOf) {
+ this.step = step;
+ this.outOf = outOf;
+ }
+ public void run() {
+ if(!isBackgroundService())
+ progressDialog.setProgress(100*step/outOf);
+ }
+ }
+
+ protected class ProgressLabelUpdater implements Runnable {
+ String label;
+ public ProgressLabelUpdater(String label) {
+ this.label = label;
+ }
+ public void run() {
+ if(isBackgroundService()) {
+ Log.i("astrid-sync", label);
+ } else {
+ if(!progressDialog.isShowing())
+ progressDialog.show();
+ progressDialog.setMessage(label);
+ }
+ }
+ }
+}
diff --git a/src/com/timsu/astrid/sync/SynchronizationService.java b/src/com/timsu/astrid/sync/SynchronizationService.java
index 962d405fd..18081b2c3 100644
--- a/src/com/timsu/astrid/sync/SynchronizationService.java
+++ b/src/com/timsu/astrid/sync/SynchronizationService.java
@@ -1,542 +1,99 @@
-/*
- * 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
- */
-package com.timsu.astrid.sync;
-
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedList;
-
-import android.app.Activity;
-import android.app.Dialog;
-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.alerts.AlertController;
-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;
-import com.timsu.astrid.utilities.Notifications;
-import com.timsu.astrid.utilities.Preferences;
-
-/** A service that synchronizes with Astrid
- *
- * @author timsu
- *
- */
-public abstract class SynchronizationService {
-
- private int id;
- static ProgressDialog progressDialog;
- protected Handler syncHandler;
- public SynchronizationService(int id) {
- this.id = id;
- }
-
- // called off the UI thread. does some setup
- void synchronizeService(final Activity activity) {
- syncHandler = new Handler();
- SynchronizationService.progressDialog = new ProgressDialog(activity);
- progressDialog.setIcon(android.R.drawable.ic_dialog_alert);
- progressDialog.setTitle("Synchronization");
- progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
- progressDialog.setMax(100);
- progressDialog.setMessage("Checking Authorization...");
- progressDialog.setProgress(0);
- progressDialog.setCancelable(false);
- progressDialog.show();
-
- synchronize(activity);
- }
-
- /** Synchronize with the service */
- protected abstract void synchronize(Activity activity);
-
- /** Called when user requests a data clear */
- abstract void clearPersonalData(Activity activity);
-
- /** Get this service's id */
- public int getId() {
- return id;
- }
-
- /** Gets this service's name */
- abstract String getName();
-
- // --- utilities
-
- /** Utility method for showing synchronization errors. If message is null,
- * the contents of the throwable is displayed.
- */
- void showError(final Context context, Throwable e, String message) {
- Log.e("astrid", "Synchronization Error", e);
- Resources r = context.getResources();
- final String messageToDisplay;
- if(message == null) {
- messageToDisplay = r.getString(R.string.sync_error) + " " +
- e.toString() + " - " + e.getStackTrace()[1];
- } else {
- messageToDisplay = message;
- }
- syncHandler.post(new Runnable() {
- public void run() {
- if(progressDialog != null)
- progressDialog.dismiss();
- DialogUtilities.okDialog(context, messageToDisplay, null);
- }
- });
- }
-
- // --- synchronization logic
-
- /** interface to assist with synchronization */
- protected interface SynchronizeHelper {
- /** 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, TaskProxy remoteTask,
- SyncMapping mapping) throws IOException;
-
- /** Create a task on the remote server. This is followed by a call of
- * pushTask on the id in question.
- *
- * @return task to create
- * @return remote id
- */
- String createTask(TaskModelForSync task) throws IOException;
-
- /** Fetch remote task. Used to re-read merged tasks
- *
- * @param task TaskProxy of the original task
- * @return new TaskProxy
- */
- TaskProxy refetchTask(TaskProxy task) throws IOException;
-
- /** Delete the task from the remote server
- *
- * @param mapping mapping to delete
- */
- void deleteTask(SyncMapping mapping) throws IOException;
- }
-
- /** Helper to synchronize remote tasks with our local database.
- *
- * This initiates the following process:
- * 1. local changes are read
- * 2. remote changes are read
- * 3. local tasks are merged with remote changes and pushed across
- * 4. remote changes are then read in
- *
- * @param remoteTasks remote tasks that have been updated
- * @return local tasks that need to be pushed across
- */
- protected void synchronizeTasks(final Activity activity, LinkedList
- remoteTasks, SynchronizeHelper helper) throws IOException {
- final SyncStats stats = new SyncStats();
- final StringBuilder log = new StringBuilder();
-
- 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. CREATE: grab tasks without a sync mapping and create them remotely
- log.append(">> on remote server:\n");
- 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(data.newRemoteTasks.containsKey(task.getName())) {
- TaskProxy remoteTask = data.newRemoteTasks.get(task.getName());
- SyncMapping mapping = new SyncMapping(taskId, getId(),
- remoteTask.getRemoteId());
- syncController.saveSyncMapping(mapping);
- data.localChanges.add(mapping);
- data.remoteChangeMap.put(taskId, remoteTask);
- data.localIdToSyncMapping.put(taskId, mapping);
- continue;
- }
-
- String remoteId = helper.createTask(task);
- SyncMapping mapping = new SyncMapping(taskId, getId(), remoteId);
- syncController.saveSyncMapping(mapping);
- data.localIdToSyncMapping.put(taskId, mapping);
-
- TaskProxy localTask = new TaskProxy(getId(), remoteId, false);
- localTask.readFromTaskModel(task);
- localTask.readTagsFromController(activity, taskId, tagController, data.tags);
- helper.pushTask(localTask, null, mapping);
-
- // update stats
- log.append("added '" + task.getName() + "'\n");
- stats.remoteCreatedTasks++;
- }
-
- // 2. DELETE: find deleted tasks and remove them from the list
- syncHandler.post(new ProgressLabelUpdater("Sending locally deleted tasks"));
- for(TaskIdentifier taskId : data.deletedTasks) {
- SyncMapping mapping = data.localIdToSyncMapping.get(taskId);
- syncController.deleteSyncMapping(mapping);
- helper.deleteTask(mapping);
-
- // remove it from data structures
- data.localChanges.remove(mapping);
- data.localIdToSyncMapping.remove(taskId);
- 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,
- data.deletedTasks.size()));
- }
-
- // 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(data.remoteChangeMap.containsKey(mapping.getTask())) {
- remoteConflict = data.remoteChangeMap.get(mapping.getTask());
- localTask.mergeWithOther(remoteConflict);
- stats.mergedTasks++;
- }
-
- try {
- 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;
- }
-
- // re-fetch remote task
- if(remoteConflict != null) {
- TaskProxy newTask = helper.refetchTask(remoteConflict);
- remoteTasks.remove(remoteConflict);
- remoteTasks.add(newTask);
- } else
- stats.remoteUpdatedTasks++;
- }
-
- // 4. REMOTE SYNC load remote information
- log.append("\n>> on astrid:\n");
- 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(!data.remoteIdToSyncMapping.containsKey(remoteTask.getRemoteId())) {
- // if it's new & deleted, forget about it
- if(remoteTask.isDeleted()) {
- continue;
- }
-
- task = taskController.searchForTaskForSync(remoteTask.name);
- if(task == null) {
- task = new TaskModelForSync();
- setupTaskDefaults(activity, task);
- log.append("added " + remoteTask.name + "\n");
- } else {
- mapping = data.localIdToSyncMapping.get(task.getTaskIdentifier());
- log.append("merged " + remoteTask.name + "\n");
- }
- } else {
- mapping = data.remoteIdToSyncMapping.get(remoteTask.getRemoteId());
- if(remoteTask.isDeleted()) {
- taskController.deleteTask(mapping.getTask());
- syncController.deleteSyncMapping(mapping);
- log.append("deleted " + remoteTask.name + "\n");
- stats.localDeletedTasks++;
- continue;
- }
-
- log.append("updated '" + remoteTask.name + "'\n");
- task = taskController.fetchTaskForSync(
- mapping.getTask());
- }
-
- // save the data
- remoteTask.writeToTaskModel(task);
- taskController.saveTask(task);
-
- // save tags
- if(remoteTask.tags != null) {
- LinkedList taskTags = tagController.getTaskTags(activity, task.getTaskIdentifier());
- HashSet tagsToAdd = new HashSet();
- 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 tagsToDelete = new HashSet(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) {
- mapping = data.localIdToSyncMapping.get(task.getTaskIdentifier());
- if(mapping == null) {
- try {
- mapping = new SyncMapping(task.getTaskIdentifier(), remoteTask);
- syncController.saveSyncMapping(mapping);
- data.localIdToSyncMapping.put(task.getTaskIdentifier(),
- mapping);
- } catch (Exception e) {
- // unique violation: ignore - it'll get merged later
- Log.e("astrid-sync", "Exception creating mapping", e);
- }
- }
- stats.localCreatedTasks++;
- }
-
- Notifications.updateAlarm(activity, taskController, alertController,
- task);
- syncHandler.post(new ProgressUpdater(stats.localUpdatedTasks,
- remoteTasks.size()));
- }
- stats.localUpdatedTasks -= stats.localCreatedTasks;
-
- syncController.clearUpdatedTaskList(getId());
- syncHandler.post(new Runnable() {
- public void run() {
- stats.showDialog(activity, log.toString());
- }
- });
- }
-
- /** Set up defaults from preferences for this task */
- private void setupTaskDefaults(Activity activity, TaskModelForSync task) {
- Integer reminder = Preferences.getDefaultReminder(activity);
- if(reminder != null)
- task.setNotificationIntervalSeconds(24*3600*reminder);
- }
-
- // --- helper classes
-
- /** data structure builder */
- class SyncData {
- HashSet mappings;
- HashSet activeTasks;
- HashSet allTasks;
-
- HashMap remoteIdToSyncMapping;
- HashMap localIdToSyncMapping;
-
- HashSet localChanges;
- HashSet mappedTasks;
- HashMap remoteChangeMap;
- HashMap newRemoteTasks;
-
- HashMap tags;
- HashMap tagsByLCName;
-
- HashSet newlyCreatedTasks;
- HashSet deletedTasks;
-
- public SyncData(Activity activity, LinkedList 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();
- localIdToSyncMapping = new HashMap();
- localChanges = new HashSet();
- mappedTasks = new HashSet();
- 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();
- for(TagModelForView tag : tags.values())
- tagsByLCName.put(tag.getName().toLowerCase(), tag.getTagIdentifier());
-
- // 3. build map of remote tasks
- remoteChangeMap = new HashMap();
- newRemoteTasks = new HashMap();
- 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(activeTasks);
- newlyCreatedTasks.removeAll(mappedTasks);
- deletedTasks = new HashSet(mappedTasks);
- deletedTasks.removeAll(allTasks);
- }
- }
-
-
- /** statistics tracking and displaying */
- protected class SyncStats {
- int localCreatedTasks = 0;
- int localUpdatedTasks = 0;
- int localDeletedTasks = 0;
-
- int mergedTasks = 0;
-
- int remoteCreatedTasks = 0;
- int remoteUpdatedTasks = 0;
- int remoteDeletedTasks = 0;
-
- /** Display a dialog with statistics */
- public void showDialog(final Activity activity, String log) {
- progressDialog.hide();
-
- if(Preferences.shouldSuppressSyncDialogs(activity))
- return;
-
- Dialog.OnClickListener finishListener = new Dialog.OnClickListener() {
- public void onClick(DialogInterface dialog,
- int which) {
- Synchronizer.continueSynchronization(activity);
- }
- };
-
- // nothing updated
- if(localCreatedTasks + localUpdatedTasks + localDeletedTasks +
- mergedTasks + remoteCreatedTasks + remoteDeletedTasks +
- remoteUpdatedTasks == 0) {
- if(!Synchronizer.isAutoSync())
- DialogUtilities.okDialog(activity, "Sync: Up to date!", finishListener);
- return;
- }
-
- StringBuilder sb = new StringBuilder();
- sb.append(getName()).append(" Results:"); // TODO i18n
- sb.append("\n\n");
- sb.append(log);
- if(localCreatedTasks + localUpdatedTasks + localDeletedTasks > 0)
- sb.append("\nSummary - Astrid Tasks:");
- if(localCreatedTasks > 0)
- sb.append("\nCreated: " + localCreatedTasks);
- if(localUpdatedTasks > 0)
- sb.append("\nUpdated: " + localUpdatedTasks);
- if(localDeletedTasks > 0)
- sb.append("\nDeleted: " + localDeletedTasks);
-
- if(mergedTasks > 0)
- sb.append("\n\nMerged: " + mergedTasks);
-
- if(remoteCreatedTasks + remoteDeletedTasks + remoteUpdatedTasks > 0)
- sb.append("\n\nSummary - Remote Server:");
- if(remoteCreatedTasks > 0)
- sb.append("\nCreated: " + remoteCreatedTasks);
- if(remoteUpdatedTasks > 0)
- sb.append("\nUpdated: " + remoteUpdatedTasks);
- if(remoteDeletedTasks > 0)
- sb.append("\nDeleted: " + remoteDeletedTasks);
-
- sb.append("\n");
-
- DialogUtilities.okDialog(activity, sb.toString(), finishListener);
- }
- }
-
- protected static class ProgressUpdater implements Runnable {
- int step, outOf;
- public ProgressUpdater(int step, int outOf) {
- this.step = step;
- this.outOf = outOf;
- }
- public void run() {
- progressDialog.setProgress(100*step/outOf);
- }
- }
-
- protected static class ProgressLabelUpdater implements Runnable {
- String label;
- public ProgressLabelUpdater(String label) {
- this.label = label;
- }
- public void run() {
- if(!progressDialog.isShowing())
- progressDialog.show();
- progressDialog.setMessage(label);
- }
- }
-}
+package com.timsu.astrid.sync;
+
+import java.util.Date;
+import java.util.Timer;
+import java.util.TimerTask;
+
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.os.IBinder;
+import android.util.Log;
+
+import com.timsu.astrid.utilities.Preferences;
+
+public class SynchronizationService extends Service {
+
+ /** Service timer */
+ private Timer timer = new Timer();
+
+ /** Service activity */
+ private static Context context;
+
+ /** Set the activity for this service */
+ public static void setContext(Context context) {
+ SynchronizationService.context = context;
+ }
+
+ @Override
+ public IBinder onBind(Intent arg0) {
+ return null; // unused
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+
+ // init the service here
+ startService();
+
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+
+ shutdownService();
+ }
+
+ /** Start the timer that runs the service */
+ private void startService() {
+ // figure out synchronization frequency
+ Integer syncFrequencySeconds = Preferences.getSyncAutoSyncFrequency(context);
+ if(syncFrequencySeconds == null) {
+ shutdownService();
+ return;
+ }
+
+ long interval = 1000L * syncFrequencySeconds;
+
+ // figure out last synchronize time
+ Date lastSyncDate = Preferences.getSyncLastSync(context);
+ Date lastAutoSyncDate = Preferences.getSyncLastSyncAttempt(context);
+ long latestSyncMillis = 0;
+ if(lastSyncDate != null)
+ latestSyncMillis = lastSyncDate.getTime();
+ if(lastAutoSyncDate != null && lastAutoSyncDate.getTime() > latestSyncMillis)
+ latestSyncMillis = lastAutoSyncDate.getTime();
+ long offset = 0;
+ if(latestSyncMillis != 0)
+ offset = Math.max(0, latestSyncMillis + interval - System.currentTimeMillis());
+
+ timer.scheduleAtFixedRate(new TimerTask() {
+ @Override
+ public void run() {
+ performSynchronization();
+ }
+ }, offset, interval);
+ Log.i("astrid", "Synchronization Service Started, Offset: " + offset/1000 +
+ "s Interval: " + interval/1000);
+ }
+
+ /** Stop the timer that runs the service */
+ private void shutdownService() {
+ if (timer != null)
+ timer.cancel();
+ Log.i("astrid", "Synchronization Service Stopped");
+ }
+
+ /** Perform the actual synchronization */
+ private void performSynchronization() {
+ if(context == null || context.getResources() == null)
+ return;
+
+ Log.i("astrid", "Automatic Synchronize Initiated.");
+ Preferences.setSyncLastSyncAttempt(context, new Date());
+
+ Synchronizer.synchronize(context, true, null);
+ }
+}
diff --git a/src/com/timsu/astrid/sync/Synchronizer.java b/src/com/timsu/astrid/sync/Synchronizer.java
index dc4195525..c9a37f46e 100644
--- a/src/com/timsu/astrid/sync/Synchronizer.java
+++ b/src/com/timsu/astrid/sync/Synchronizer.java
@@ -44,13 +44,13 @@ public class Synchronizer {
}
/** Synchronize all activated sync services */
- public static void synchronize(Activity activity, boolean isAutoSync,
+ public synchronized static void synchronize(Context context, boolean isAutoSync,
SynchronizerListener listener) {
currentStep = ServiceWrapper._FIRST_SERVICE.ordinal();
servicesSynced = 0;
autoSync = isAutoSync;
callback = listener;
- continueSynchronization(activity);
+ continueSynchronization(context);
}
@@ -78,7 +78,7 @@ public class Synchronizer {
}
},
- RTM(new RTMSyncService(SYNC_ID_RTM)) {
+ RTM(new RTMSyncProvider(SYNC_ID_RTM)) {
@Override
boolean isActivated(Context context) {
return Preferences.shouldSyncRTM(context);
@@ -92,9 +92,9 @@ public class Synchronizer {
}
};
- private SynchronizationService service;
+ private SynchronizationProvider service;
- private ServiceWrapper(SynchronizationService service) {
+ private ServiceWrapper(SynchronizationProvider service) {
this.service = service;
}
@@ -112,45 +112,38 @@ public class Synchronizer {
/** On finished callback */
private static SynchronizerListener callback;
- /** If this synchronization was automatically initiated */
private static boolean autoSync;
-
/** Called to do the next step of synchronization. Run me on the UI thread! */
- static void continueSynchronization(Activity activity) {
+ static void continueSynchronization(Context context) {
ServiceWrapper serviceWrapper =
ServiceWrapper.values()[currentStep];
currentStep++;
switch(serviceWrapper) {
case _FIRST_SERVICE:
- continueSynchronization(activity);
+ continueSynchronization(context);
break;
case RTM:
- if(Preferences.shouldSyncRTM(activity)) {
+ if(Preferences.shouldSyncRTM(context)) {
servicesSynced++;
- serviceWrapper.service.synchronizeService(activity);
+ serviceWrapper.service.synchronizeService(context, autoSync);
} else {
- continueSynchronization(activity);
+ continueSynchronization(context);
}
break;
case _LAST_SERVICE:
- finishSynchronization(activity);
+ finishSynchronization(context);
}
}
/** Called at the end of sync. */
- private static void finishSynchronization(final Activity activity) {
+ private static void finishSynchronization(final Context context) {
closeControllers();
- Preferences.setSyncLastSync(activity, new Date());
+ Preferences.setSyncLastSync(context, new Date());
if(callback != null)
callback.onSynchronizerFinished(servicesSynced);
}
- /** Was this sync automatically initiated? */
- static boolean isAutoSync() {
- return autoSync;
- }
-
// --- controller stuff
private static class ControllerWrapper {
@@ -165,11 +158,11 @@ public class Synchronizer {
}
@SuppressWarnings("unchecked")
- public TYPE get(Activity activity) {
+ public TYPE get(Context context) {
if(controller == null) {
try {
controller = (TYPE)typeClass.getConstructors()[0].newInstance(
- activity);
+ context);
} catch (IllegalArgumentException e) {
Log.e(getClass().getSimpleName(), e.toString());
} catch (SecurityException e) {
@@ -210,20 +203,20 @@ public class Synchronizer {
private static ControllerWrapper alertController =
new ControllerWrapper(AlertController.class);
- static SyncDataController getSyncController(Activity activity) {
- return syncController.get(activity);
+ static SyncDataController getSyncController(Context context) {
+ return syncController.get(context);
}
- static TaskController getTaskController(Activity activity) {
- return taskController.get(activity);
+ static TaskController getTaskController(Context context) {
+ return taskController.get(context);
}
- static TagController getTagController(Activity activity) {
- return tagController.get(activity);
+ static TagController getTagController(Context context) {
+ return tagController.get(context);
}
- static AlertController getAlertController(Activity activity) {
- return alertController.get(activity);
+ static AlertController getAlertController(Context context) {
+ return alertController.get(context);
}
public static void setTaskController(TaskController taskController) {
diff --git a/src/com/timsu/astrid/sync/TaskProxy.java b/src/com/timsu/astrid/sync/TaskProxy.java
index 3c31c6432..f109e3cb9 100644
--- a/src/com/timsu/astrid/sync/TaskProxy.java
+++ b/src/com/timsu/astrid/sync/TaskProxy.java
@@ -23,8 +23,6 @@ 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;
@@ -141,7 +139,7 @@ public class TaskProxy {
completionDate = task.getCompletionDate();
definiteDueDate = task.getDefiniteDueDate();
preferredDueDate = task.getPreferredDueDate();
- dueDate = definiteDueDate != null ? definiteDueDate : preferredDueDate;
+ dueDate = definiteDueDate != null ? definiteDueDate : preferredDueDate;
hiddenUntil = task.getHiddenUntil();
estimatedSeconds = task.getEstimatedSeconds();
elapsedSeconds = task.getElapsedSeconds();
@@ -152,11 +150,10 @@ public class TaskProxy {
}
/** Read tags from the given tag controller */
- public void readTagsFromController(Activity activity, TaskIdentifier taskId,
+ public void readTagsFromController(TaskIdentifier taskId,
TagController tagController, HashMap
tagList) {
- LinkedList tagIds = tagController.getTaskTags(activity,
- taskId);
+ LinkedList tagIds = tagController.getTaskTags(taskId);
tags = new LinkedList();
for(TagIdentifier tagId : tagIds) {
tags.add(tagList.get(tagId).getName());
@@ -177,25 +174,25 @@ public class TaskProxy {
task.setCreationDate(creationDate);
if(completionDate != null)
task.setCompletionDate(completionDate);
-
+
// date handling: if sync service only supports one type of due date,
// we have to figure out which field to write to based on what
// already has data
-
+
if(dueDate != null) {
if(task.getDefiniteDueDate() != null)
task.setDefiniteDueDate(dueDate);
else if(task.getPreferredDueDate() != null)
task.setPreferredDueDate(dueDate);
else
- task.setDefiniteDueDate(dueDate);
+ task.setDefiniteDueDate(dueDate);
} else {
if(definiteDueDate != null)
task.setDefiniteDueDate(definiteDueDate);
if(preferredDueDate != null)
task.setPreferredDueDate(preferredDueDate);
}
-
+
if(hiddenUntil != null)
task.setHiddenUntil(hiddenUntil);
if(estimatedSeconds != null)
diff --git a/src/com/timsu/astrid/utilities/Preferences.java b/src/com/timsu/astrid/utilities/Preferences.java
index edeaad167..d9e9e1ee6 100644
--- a/src/com/timsu/astrid/utilities/Preferences.java
+++ b/src/com/timsu/astrid/utilities/Preferences.java
@@ -20,6 +20,7 @@ public class Preferences {
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";
+ private static final String P_SYNC_LAST_SYNC_ATTEMPT = "lastsyncattempt";
// pref values
public static final int ICON_SET_PINK = 0;
@@ -93,7 +94,7 @@ public class Preferences {
}
}
- // --- sysetm preferences
+ // --- system preferences
/** CurrentVersion: the currently installed version of Astrid */
public static int getCurrentVersion(Context context) {
@@ -257,9 +258,26 @@ public class Preferences {
R.string.p_sync_quiet), false);
}
- /** returns the font size user wants on the front page */
- public static Float autoSyncFrequency(Context context) {
- return getFloatValue(context, R.string.p_sync_every);
+ /** Reads the frequency, in seconds, auto-sync should occur.
+ * @return seconds duration, or null if not desired */
+ public static Integer getSyncAutoSyncFrequency(Context context) {
+ Integer time = getIntegerValue(context, R.string.p_sync_interval);
+ if(time != null && time == 0)
+ time = null;
+ return time;
+ }
+
+ /** Reads the old auto */
+ public static Float getSyncOldAutoSyncFrequency(Context context) {
+ return getFloatValue(context, R.string.p_sync_every_old);
+ }
+
+ /** Sets the auto-sync frequency to the desired value */
+ public static void setSyncAutoSyncFrequency(Context context, int value) {
+ Editor editor = getPrefs(context).edit();
+ editor.putString(context.getResources().getString(R.string.p_sync_interval),
+ Integer.toString(value));
+ editor.commit();
}
/** Last Auto-Sync Date, or null */
@@ -270,7 +288,15 @@ public class Preferences {
return new Date(value);
}
- /** Set Last Auto-Sync Date */
+ /** Last Successful Auto-Sync Date, or null */
+ public static Date getSyncLastSyncAttempt(Context context) {
+ Long value = getPrefs(context).getLong(P_SYNC_LAST_SYNC_ATTEMPT, 0);
+ if(value == 0)
+ return null;
+ return new Date(value);
+ }
+
+ /** Set Last Sync Date */
public static void setSyncLastSync(Context context, Date date) {
if(date == null) {
clearPref(context, P_SYNC_LAST_SYNC);
@@ -282,6 +308,13 @@ public class Preferences {
editor.commit();
}
+ /** Set Last Auto-Sync Attempt Date */
+ public static void setSyncLastSyncAttempt(Context context, Date date) {
+ Editor editor = getPrefs(context).edit();
+ editor.putLong(P_SYNC_LAST_SYNC_ATTEMPT, date.getTime());
+ editor.commit();
+ }
+
// --- helper methods
/** Clear the given preference */
diff --git a/src/com/timsu/astrid/utilities/StartupReceiver.java b/src/com/timsu/astrid/utilities/StartupReceiver.java
index 015436a7f..b8f9f402a 100644
--- a/src/com/timsu/astrid/utilities/StartupReceiver.java
+++ b/src/com/timsu/astrid/utilities/StartupReceiver.java
@@ -1,5 +1,7 @@
package com.timsu.astrid.utilities;
+import com.timsu.astrid.sync.SynchronizationService;
+
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -36,6 +38,15 @@ public class StartupReceiver extends BroadcastReceiver {
boolean justUpgraded = latestSetVersion != version;
final int finalVersion = version;
if(justUpgraded) {
+ // perform version-specific processing
+ if(latestSetVersion <= 99) {
+ if(Preferences.getSyncOldAutoSyncFrequency(context) != null) {
+ float value = Preferences.getSyncOldAutoSyncFrequency(context);
+ Preferences.setSyncAutoSyncFrequency(context,
+ Math.round(value * 3600));
+ }
+ }
+
new Thread(new Runnable() {
public void run() {
Notifications.scheduleAllAlarms(context);
@@ -49,6 +60,11 @@ public class StartupReceiver extends BroadcastReceiver {
Preferences.setPreferenceDefaults(context);
+ // start synchronization service
+ SynchronizationService.setContext(context);
+ Intent service = new Intent(context, SynchronizationService.class);
+ context.startService(service);
+
hasStartedUp = true;
}
}