mirror of https://github.com/tasks/tasks
Deleted old sync stuff
parent
34ee32f5dd
commit
24d90aa020
@ -1,606 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.Date;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.StringTokenizer;
|
|
||||||
import java.util.Map.Entry;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.res.Resources;
|
|
||||||
import android.os.Handler;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import com.flurry.android.FlurryAgent;
|
|
||||||
import com.mdt.rtm.ApplicationInfo;
|
|
||||||
import com.mdt.rtm.ServiceException;
|
|
||||||
import com.mdt.rtm.ServiceImpl;
|
|
||||||
import com.mdt.rtm.ServiceInternalException;
|
|
||||||
import com.mdt.rtm.data.RtmList;
|
|
||||||
import com.mdt.rtm.data.RtmLists;
|
|
||||||
import com.mdt.rtm.data.RtmTask;
|
|
||||||
import com.mdt.rtm.data.RtmTaskList;
|
|
||||||
import com.mdt.rtm.data.RtmTaskNote;
|
|
||||||
import com.mdt.rtm.data.RtmTaskSeries;
|
|
||||||
import com.mdt.rtm.data.RtmTasks;
|
|
||||||
import com.mdt.rtm.data.RtmAuth.Perms;
|
|
||||||
import com.mdt.rtm.data.RtmTask.Priority;
|
|
||||||
import com.timsu.astrid.R;
|
|
||||||
import com.timsu.astrid.activities.SyncLoginActivity;
|
|
||||||
import com.timsu.astrid.activities.SyncLoginActivity.SyncLoginCallback;
|
|
||||||
import com.timsu.astrid.data.enums.Importance;
|
|
||||||
import com.timsu.astrid.data.sync.SyncMapping;
|
|
||||||
import com.timsu.astrid.data.tag.TagController;
|
|
||||||
import com.timsu.astrid.data.tag.TagModelForView;
|
|
||||||
import com.timsu.astrid.data.task.AbstractTaskModel;
|
|
||||||
import com.timsu.astrid.data.task.TaskModelForSync;
|
|
||||||
import com.timsu.astrid.utilities.AstridUtilities;
|
|
||||||
import com.timsu.astrid.utilities.Preferences;
|
|
||||||
|
|
||||||
public class RTMSyncProvider extends SynchronizationProvider {
|
|
||||||
|
|
||||||
private ServiceImpl rtmService = null;
|
|
||||||
private String INBOX_LIST_NAME = "Inbox";
|
|
||||||
Map<String, String> listNameToIdMap = new HashMap<String, String>();
|
|
||||||
Map<String, String> listIdToNameMap = new HashMap<String, String>();
|
|
||||||
|
|
||||||
public RTMSyncProvider(int id) {
|
|
||||||
super(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- abstract methods
|
|
||||||
|
|
||||||
@Override
|
|
||||||
String getName() {
|
|
||||||
return "RTM";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void synchronize(final Context activity) {
|
|
||||||
// authenticate the user. this will automatically call the next step
|
|
||||||
authenticate(activity);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void clearPersonalData(Context context) {
|
|
||||||
Preferences.setSyncRTMToken(context, null);
|
|
||||||
Preferences.setSyncRTMLastSync(context, null);
|
|
||||||
synchronizer.getSyncController(context).deleteAllMappings(getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- authentication
|
|
||||||
|
|
||||||
/** Helper method that handles RTM methods and may show an error dialog */
|
|
||||||
private void handleRtmException(Context context, String tag, Exception e,
|
|
||||||
boolean showErrorIfNeeded) {
|
|
||||||
// occurs when application was closed
|
|
||||||
if(e instanceof IllegalStateException) {
|
|
||||||
AstridUtilities.reportFlurryError(tag + "-caught", e);
|
|
||||||
Log.e(tag, "Illegal State during Sync", e);
|
|
||||||
|
|
||||||
// occurs when network error
|
|
||||||
} else if(e instanceof ServiceInternalException &&
|
|
||||||
((ServiceInternalException)e).getEnclosedException() instanceof
|
|
||||||
IOException) {
|
|
||||||
Exception enclosedException = ((ServiceInternalException)e).getEnclosedException();
|
|
||||||
AstridUtilities.reportFlurryError(tag + "-ioexception", enclosedException);
|
|
||||||
if(showErrorIfNeeded)
|
|
||||||
showError(context, enclosedException, "Connection Error! Check your " +
|
|
||||||
"Internet connection & try again...");
|
|
||||||
} else {
|
|
||||||
if(e instanceof ServiceInternalException)
|
|
||||||
e = ((ServiceInternalException)e).getEnclosedException();
|
|
||||||
AstridUtilities.reportFlurryError(tag + "-unhandled", e);
|
|
||||||
if(showErrorIfNeeded)
|
|
||||||
showError(context, e, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Perform authentication with RTM. Will open the SyncBrowser if necessary */
|
|
||||||
private void authenticate(final Context context) {
|
|
||||||
final Resources r = context.getResources();
|
|
||||||
FlurryAgent.onEvent("rtm-started");
|
|
||||||
|
|
||||||
try {
|
|
||||||
String apiKey = "bd9883b3384a21ead17501da38bb1e68";
|
|
||||||
String sharedSecret = "a19b2a020345219b";
|
|
||||||
String appName = null;
|
|
||||||
String authToken = Preferences.getSyncRTMToken(context);
|
|
||||||
|
|
||||||
// check if we have a token & it works
|
|
||||||
if(authToken != null) {
|
|
||||||
rtmService = new ServiceImpl(new ApplicationInfo(
|
|
||||||
apiKey, sharedSecret, appName, authToken));
|
|
||||||
if(!rtmService.isServiceAuthorized()) // re-do login
|
|
||||||
authToken = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// don't do anything if you're a background service
|
|
||||||
if(authToken == null && isBackgroundService())
|
|
||||||
return;
|
|
||||||
|
|
||||||
if(authToken == null) {
|
|
||||||
// try completing the authorization if it was partial
|
|
||||||
if(rtmService != null) {
|
|
||||||
try {
|
|
||||||
String token = rtmService.completeAuthorization();
|
|
||||||
Log.w("astrid", "got RTM token: " + token);
|
|
||||||
Preferences.setSyncRTMToken(context, token);
|
|
||||||
performSync(context);
|
|
||||||
|
|
||||||
return;
|
|
||||||
} catch (Exception e) {
|
|
||||||
// didn't work. do the process again.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// open up a dialog and have the user go to browser
|
|
||||||
FlurryAgent.onEvent("rtm-login-dialog");
|
|
||||||
|
|
||||||
rtmService = new ServiceImpl(new ApplicationInfo(
|
|
||||||
apiKey, sharedSecret, appName));
|
|
||||||
final String url = rtmService.beginAuthorization(Perms.delete);
|
|
||||||
if(progressDialog != null)
|
|
||||||
progressDialog.dismiss();
|
|
||||||
|
|
||||||
Intent intent = new Intent(context, SyncLoginActivity.class);
|
|
||||||
SyncLoginActivity.setCallback(new SyncLoginCallback() {
|
|
||||||
@Override
|
|
||||||
public String verifyLogin(final Handler syncLoginHandler) {
|
|
||||||
if(rtmService == null) {
|
|
||||||
Log.e("rtmsync", "Error: sync login activity displayed with no service!");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
String token = rtmService.completeAuthorization();
|
|
||||||
Log.w("astrid", "got RTM token: " + token);
|
|
||||||
Preferences.setSyncRTMToken(context, token);
|
|
||||||
return null;
|
|
||||||
} catch (Exception e) {
|
|
||||||
// didn't work
|
|
||||||
AstridUtilities.reportFlurryError("rtm-verify-login", e);
|
|
||||||
rtmService = null;
|
|
||||||
if(e instanceof ServiceInternalException)
|
|
||||||
e = ((ServiceInternalException)e).getEnclosedException();
|
|
||||||
|
|
||||||
return r.getString(R.string.rtm_login_error) +
|
|
||||||
" " + e.getMessage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
intent.putExtra(SyncLoginActivity.URL_TOKEN, url);
|
|
||||||
intent.putExtra(SyncLoginActivity.LABEL_TOKEN, R.string.rtm_login_label);
|
|
||||||
context.startActivity(intent);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
performSync(context);
|
|
||||||
}
|
|
||||||
} catch (IllegalStateException e) {
|
|
||||||
// occurs when application was closed
|
|
||||||
Log.e("rtmsync", "Illegal State during Sync", e);
|
|
||||||
} catch (Exception e) {
|
|
||||||
handleRtmException(context, "rtm-authenticate", e, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- synchronization!
|
|
||||||
|
|
||||||
private void performSync(final Context context) {
|
|
||||||
new Thread(new Runnable() {
|
|
||||||
public void run() {
|
|
||||||
performSyncInNewThread(context);
|
|
||||||
}
|
|
||||||
}).start();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void performSyncInNewThread(final Context context) {
|
|
||||||
try {
|
|
||||||
postUpdate(new ProgressLabelUpdater(context, R.string.sync_progress_remote));
|
|
||||||
postUpdate(new ProgressUpdater(0, 5));
|
|
||||||
|
|
||||||
// get RTM timeline
|
|
||||||
final String timeline = rtmService.timelines_create();
|
|
||||||
postUpdate(new ProgressUpdater(1, 5));
|
|
||||||
|
|
||||||
// push task if single task sync is requested
|
|
||||||
if(getSingleTaskForSync() != null) {
|
|
||||||
SyncMapping mapping = synchronizer.getSyncController(context).
|
|
||||||
getSyncMapping(getId(), getSingleTaskForSync());
|
|
||||||
if(mapping == null) {
|
|
||||||
Log.w("astrid-rtm", "Couldn't find sync mapping for updated task");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
TaskProxy localTask = new TaskProxy(getId(), mapping.getRemoteId());
|
|
||||||
TaskModelForSync task = synchronizer.getTaskController(context).
|
|
||||||
fetchTaskForSync(getSingleTaskForSync());
|
|
||||||
localTask.readFromTaskModel(task);
|
|
||||||
postUpdate(new ProgressLabelUpdater(context, R.string.sync_progress_repeating));
|
|
||||||
pushLocalTask(timeline, localTask, null, mapping);
|
|
||||||
}
|
|
||||||
|
|
||||||
// load RTM lists
|
|
||||||
RtmLists lists = rtmService.lists_getList();
|
|
||||||
for(RtmList list : lists.getLists().values()) {
|
|
||||||
if(list.isSmart() || list.isArchived())
|
|
||||||
continue;
|
|
||||||
listNameToIdMap.put(list.getName().toLowerCase(), 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();
|
|
||||||
}
|
|
||||||
postUpdate(new ProgressUpdater(2, 5));
|
|
||||||
|
|
||||||
// read all tasks
|
|
||||||
LinkedList<TaskProxy> remoteChanges = new LinkedList<TaskProxy>();
|
|
||||||
Date lastSyncDate = Preferences.getSyncRTMLastSync(context);
|
|
||||||
boolean shouldSyncIndividualLists = false;
|
|
||||||
String filter = null;
|
|
||||||
if(lastSyncDate == null)
|
|
||||||
filter = "status:incomplete"; // 1st time sync: get unfinished tasks
|
|
||||||
|
|
||||||
// try the quick synchronization
|
|
||||||
try {
|
|
||||||
Thread.sleep(2000); // throttle
|
|
||||||
postUpdate(new ProgressUpdater(3, 5));
|
|
||||||
RtmTasks tasks = rtmService.tasks_getList(null, filter, lastSyncDate);
|
|
||||||
postUpdate(new ProgressUpdater(5, 5));
|
|
||||||
addTasksToList(context, tasks, remoteChanges);
|
|
||||||
} catch (Exception e) {
|
|
||||||
handleRtmException(context, "rtm-quick-sync", e, false);
|
|
||||||
remoteChanges.clear();
|
|
||||||
shouldSyncIndividualLists = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(shouldSyncIndividualLists) {
|
|
||||||
int progress = 0;
|
|
||||||
for(final Entry<String, String> entry : listIdToNameMap.entrySet()) {
|
|
||||||
postUpdate(new ProgressLabelUpdater(context,
|
|
||||||
R.string.sync_progress_rxlist, entry.getValue()));
|
|
||||||
postUpdate(new ProgressUpdater(progress++,
|
|
||||||
listIdToNameMap.size()));
|
|
||||||
try {
|
|
||||||
Thread.sleep(1500);
|
|
||||||
RtmTasks tasks = rtmService.tasks_getList(entry.getKey(),
|
|
||||||
filter, lastSyncDate);
|
|
||||||
addTasksToList(context, tasks, remoteChanges);
|
|
||||||
} catch (Exception e) {
|
|
||||||
handleRtmException(context, "rtm-indiv-sync", e, true);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
postUpdate(new ProgressUpdater(1, 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
synchronizeTasks(context, remoteChanges, new RtmSyncHelper(context, timeline));
|
|
||||||
|
|
||||||
// set sync time in the future so we don't retrieve already
|
|
||||||
// synchronized tasks and try to merge them
|
|
||||||
Date syncTime = new Date(System.currentTimeMillis() + 1000L);
|
|
||||||
Preferences.setSyncRTMLastSync(context, syncTime);
|
|
||||||
|
|
||||||
FlurryAgent.onEvent("rtm-sync-finished");
|
|
||||||
} catch (IllegalStateException e) {
|
|
||||||
// occurs when application was closed
|
|
||||||
Log.w("rtmsync", "Illegal State during Sync", e);
|
|
||||||
} catch (Exception e) {
|
|
||||||
handleRtmException(context, "rtm-sync", e, true);
|
|
||||||
|
|
||||||
} finally {
|
|
||||||
// on with the synchronization
|
|
||||||
synchronizer.continueSynchronization(context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- helper methods
|
|
||||||
|
|
||||||
/** Add the tasks read from RTM to the given list */
|
|
||||||
private void addTasksToList(Context context, RtmTasks tasks, LinkedList<TaskProxy> list) {
|
|
||||||
for(RtmTaskList taskList : tasks.getLists()) {
|
|
||||||
for(RtmTaskSeries taskSeries : taskList.getSeries()) {
|
|
||||||
TaskProxy remoteTask =
|
|
||||||
parseRemoteTask(taskList.getId(), taskSeries,
|
|
||||||
synchronizer.getTagController(context));
|
|
||||||
list.add(remoteTask);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Get a task proxy with default RTM values */
|
|
||||||
private TaskProxy getDefaultTaskProxy() {
|
|
||||||
TaskProxy taskProxy = new TaskProxy(0, "");
|
|
||||||
taskProxy.progressPercentage = 0;
|
|
||||||
taskProxy.tags = new LinkedList<String>();
|
|
||||||
taskProxy.notes = "";
|
|
||||||
return taskProxy;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Send changes for the given TaskProxy across the wire */
|
|
||||||
private void pushLocalTask(String timeline, TaskProxy task, TaskProxy remoteTask,
|
|
||||||
SyncMapping mapping) throws ServiceException {
|
|
||||||
RtmId id = new RtmId(mapping.getRemoteId());
|
|
||||||
|
|
||||||
// fetch remote task for comparison (won't work if you renamed it)
|
|
||||||
if(remoteTask == null) {
|
|
||||||
RtmTaskSeries rtmTask = rtmService.tasks_getTask(id.taskSeriesId, task.name);
|
|
||||||
if(rtmTask != null)
|
|
||||||
remoteTask = parseRemoteTask(id.listId, rtmTask, null);
|
|
||||||
}
|
|
||||||
if(remoteTask == null)
|
|
||||||
remoteTask = getDefaultTaskProxy();
|
|
||||||
|
|
||||||
if(task.name != null && !task.name.equals(remoteTask.name))
|
|
||||||
rtmService.tasks_setName(timeline, id.listId, id.taskSeriesId,
|
|
||||||
id.taskId, task.name);
|
|
||||||
if(task.importance != null && !task.importance.equals(remoteTask.importance))
|
|
||||||
rtmService.tasks_setPriority(timeline, id.listId, id.taskSeriesId,
|
|
||||||
id.taskId, Priority.values()[task.importance.ordinal()]);
|
|
||||||
|
|
||||||
// due date
|
|
||||||
Date dueDate = task.dueDate;
|
|
||||||
if(dueDate == null)
|
|
||||||
dueDate = task.definiteDueDate;
|
|
||||||
if(dueDate == null)
|
|
||||||
dueDate = task.preferredDueDate;
|
|
||||||
if(dueDate != remoteTask.dueDate && (dueDate == null ||
|
|
||||||
!dueDate.equals(remoteTask.dueDate))) {
|
|
||||||
// note tha dueDate could be null
|
|
||||||
rtmService.tasks_setDueDate(timeline, id.listId, id.taskSeriesId,
|
|
||||||
id.taskId, dueDate, dueDate != null);
|
|
||||||
}
|
|
||||||
|
|
||||||
// progress
|
|
||||||
if(task.progressPercentage != null && !task.progressPercentage.equals(
|
|
||||||
remoteTask.progressPercentage)) {
|
|
||||||
if(task.progressPercentage == 100)
|
|
||||||
rtmService.tasks_complete(timeline, id.listId, id.taskSeriesId,
|
|
||||||
id.taskId);
|
|
||||||
else
|
|
||||||
rtmService.tasks_uncomplete(timeline, id.listId, id.taskSeriesId,
|
|
||||||
id.taskId);
|
|
||||||
}
|
|
||||||
|
|
||||||
// notes
|
|
||||||
if(task.notes != null && task.notes.length() > 0 &&
|
|
||||||
!task.notes.equals(remoteTask.notes))
|
|
||||||
rtmService.tasks_notes_add(timeline, id.listId, id.taskSeriesId,
|
|
||||||
id.taskId, "From Astrid", task.notes);
|
|
||||||
|
|
||||||
// tags
|
|
||||||
if(task.tags != null && !task.tags.equals(remoteTask.tags)) {
|
|
||||||
String listName = listIdToNameMap.get(id.listId);
|
|
||||||
|
|
||||||
// if the first tag is the list, or _list, remove it
|
|
||||||
if(task.tags.size() > 0) {
|
|
||||||
String firstTag = task.tags.getFirst();
|
|
||||||
if(firstTag.startsWith(TagModelForView.HIDDEN_FROM_MAIN_LIST_PREFIX))
|
|
||||||
firstTag = firstTag.substring(TagModelForView.HIDDEN_FROM_MAIN_LIST_PREFIX.length());
|
|
||||||
if(firstTag.equals(listName))
|
|
||||||
task.tags.remove(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
rtmService.tasks_setTags(timeline, id.listId, id.taskSeriesId,
|
|
||||||
id.taskId, task.tags.toArray(new String[task.tags.size()]));
|
|
||||||
}
|
|
||||||
|
|
||||||
// estimated time
|
|
||||||
if(task.estimatedSeconds == 0 && remoteTask.estimatedSeconds != null ||
|
|
||||||
task.estimatedSeconds > 0 && remoteTask.estimatedSeconds == null) {
|
|
||||||
String estimation;
|
|
||||||
int estimatedSeconds = task.estimatedSeconds;
|
|
||||||
if(estimatedSeconds == 0)
|
|
||||||
estimation = "";
|
|
||||||
else if(estimatedSeconds < 3600)
|
|
||||||
estimation = estimatedSeconds/60 + " minutes";
|
|
||||||
else if(estimatedSeconds < 24*3600) {
|
|
||||||
int hours = (estimatedSeconds/3600);
|
|
||||||
estimation = hours+ " hours ";
|
|
||||||
if(hours*3600 != estimatedSeconds)
|
|
||||||
estimation += estimatedSeconds - hours*3600 + " minutes";
|
|
||||||
} else
|
|
||||||
estimation = estimatedSeconds/3600/24 + " days";
|
|
||||||
rtmService.tasks_setEstimate(timeline, id.listId, id.taskSeriesId,
|
|
||||||
id.taskId, estimation);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Create a task proxy for the given RtmTaskSeries */
|
|
||||||
private TaskProxy parseRemoteTask(String listId, RtmTaskSeries
|
|
||||||
rtmTaskSeries, TagController tagController) {
|
|
||||||
TaskProxy task = new TaskProxy(getId(),
|
|
||||||
new RtmId(listId, rtmTaskSeries).toString());
|
|
||||||
|
|
||||||
task.name = rtmTaskSeries.getName();
|
|
||||||
|
|
||||||
// notes
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
for(RtmTaskNote note: rtmTaskSeries.getNotes().getNotes()) {
|
|
||||||
sb.append(note.getTitle() + "\n");
|
|
||||||
sb.append(note.getText() + "\n");
|
|
||||||
}
|
|
||||||
if(sb.length() > 0)
|
|
||||||
task.notes = sb.toString().trim();
|
|
||||||
|
|
||||||
// repeat
|
|
||||||
if(rtmTaskSeries.hasRecurrence())
|
|
||||||
task.syncOnComplete = true;
|
|
||||||
|
|
||||||
// list / tags
|
|
||||||
LinkedList<String> tagsList = rtmTaskSeries.getTags();
|
|
||||||
String listName = listIdToNameMap.get(listId);
|
|
||||||
if(listName != null && !listName.equals(INBOX_LIST_NAME)) {
|
|
||||||
if(tagsList == null)
|
|
||||||
tagsList = new LinkedList<String>();
|
|
||||||
|
|
||||||
// if user has created a hidden version of this tag, use it
|
|
||||||
String hiddenName = TagModelForView.HIDDEN_FROM_MAIN_LIST_PREFIX + listName;
|
|
||||||
if(tagController != null && tagController.fetchTagFromName(hiddenName) != null)
|
|
||||||
tagsList.addFirst(hiddenName);
|
|
||||||
else
|
|
||||||
tagsList.addFirst(listName);
|
|
||||||
}
|
|
||||||
if(tagsList != null)
|
|
||||||
task.tags = tagsList;
|
|
||||||
|
|
||||||
RtmTask rtmTask = rtmTaskSeries.getTask();
|
|
||||||
if(rtmTask != null) {
|
|
||||||
String estimate = rtmTask.getEstimate();
|
|
||||||
if(estimate != null && estimate.length() > 0) {
|
|
||||||
task.estimatedSeconds = parseEstimate(estimate);
|
|
||||||
}
|
|
||||||
task.creationDate = rtmTaskSeries.getCreated();
|
|
||||||
task.completionDate = rtmTask.getCompleted();
|
|
||||||
task.isDeleted = rtmTask.getDeleted() != null;
|
|
||||||
if(rtmTask.getDue() != null) {
|
|
||||||
Date due = rtmTask.getDue();
|
|
||||||
|
|
||||||
// if no time is set, set it to midnight
|
|
||||||
if(due.getHours() == 0 && due.getMinutes() == 0 && due.getSeconds() == 0) {
|
|
||||||
due.setHours(23);
|
|
||||||
due.setMinutes(59);
|
|
||||||
}
|
|
||||||
task.dueDate = due;
|
|
||||||
}
|
|
||||||
task.progressPercentage = (rtmTask.getCompleted() == null) ? 0 :
|
|
||||||
AbstractTaskModel.COMPLETE_PERCENTAGE;
|
|
||||||
task.importance = Importance.values()[rtmTask.getPriority().ordinal()];
|
|
||||||
} else {
|
|
||||||
// error in upstream code, try to handle gracefully
|
|
||||||
Log.e("rtmsync", "Got null task parsing remote task series", new Throwable());
|
|
||||||
}
|
|
||||||
|
|
||||||
return task;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Parse an estimated time of the format ## {s,m,h,d,w,mo} and return
|
|
||||||
* the duration in seconds. Returns null on failure. */
|
|
||||||
private Integer parseEstimate(String estimate) {
|
|
||||||
try {
|
|
||||||
float total = 0;
|
|
||||||
int position = 0;
|
|
||||||
while(position != -1) {
|
|
||||||
for(; position < estimate.length(); position++) {
|
|
||||||
char c = estimate.charAt(position);
|
|
||||||
if(c != '.' && (c < '0' || c > '9'))
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
float numberPortion = Float.parseFloat(estimate.substring(0, position));
|
|
||||||
String stringPortion = estimate.substring(position).trim();
|
|
||||||
position = stringPortion.indexOf(" ");
|
|
||||||
if(position != -1)
|
|
||||||
estimate = stringPortion.substring(position+1).trim();
|
|
||||||
|
|
||||||
if(stringPortion.startsWith("mo"))
|
|
||||||
total += numberPortion * 31 * 24 * 3600;
|
|
||||||
else if(stringPortion.startsWith("w"))
|
|
||||||
total += numberPortion * 7 * 24 * 3600;
|
|
||||||
else if(stringPortion.startsWith("d"))
|
|
||||||
total += numberPortion * 24 * 3600;
|
|
||||||
else if(stringPortion.startsWith("h"))
|
|
||||||
total += numberPortion * 3600;
|
|
||||||
else if(stringPortion.startsWith("m"))
|
|
||||||
total += numberPortion * 60;
|
|
||||||
else if(stringPortion.startsWith("s"))
|
|
||||||
total += numberPortion;
|
|
||||||
}
|
|
||||||
return (int)total;
|
|
||||||
} catch (Exception e) { /* */ }
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- helper classes
|
|
||||||
|
|
||||||
/** SynchronizeHelper for remember the milk */
|
|
||||||
class RtmSyncHelper implements SynchronizeHelper {
|
|
||||||
private final String timeline;
|
|
||||||
private String lastCreatedTask = null;
|
|
||||||
private Context context;
|
|
||||||
|
|
||||||
public RtmSyncHelper(Context context, String timeline) {
|
|
||||||
this.timeline = timeline;
|
|
||||||
}
|
|
||||||
public String createTask(TaskModelForSync task) throws IOException {
|
|
||||||
String listId = listNameToIdMap.get(INBOX_LIST_NAME.toLowerCase());
|
|
||||||
RtmTaskSeries s = rtmService.tasks_add(timeline, listId,
|
|
||||||
task.getName());
|
|
||||||
lastCreatedTask = new RtmId(listId, s).toString();
|
|
||||||
return lastCreatedTask;
|
|
||||||
}
|
|
||||||
public void deleteTask(SyncMapping mapping) throws IOException {
|
|
||||||
RtmId id = new RtmId(mapping.getRemoteId());
|
|
||||||
rtmService.tasks_delete(timeline, id.listId, id.taskSeriesId,
|
|
||||||
id.taskId);
|
|
||||||
}
|
|
||||||
public void pushTask(TaskProxy task, TaskProxy remoteTask,
|
|
||||||
SyncMapping mapping) throws IOException {
|
|
||||||
|
|
||||||
// don't save the stuff that we already saved by creating the task
|
|
||||||
if(task.getRemoteId().equals(lastCreatedTask))
|
|
||||||
task.name = null;
|
|
||||||
|
|
||||||
pushLocalTask(timeline, task, remoteTask, mapping);
|
|
||||||
}
|
|
||||||
public TaskProxy refetchTask(TaskProxy task) throws IOException {
|
|
||||||
RtmId id = new RtmId(task.getRemoteId());
|
|
||||||
RtmTaskSeries rtmTask = rtmService.tasks_getTask(id.taskSeriesId,
|
|
||||||
task.name);
|
|
||||||
if(rtmTask == null)
|
|
||||||
return task; // can't fetch
|
|
||||||
return parseRemoteTask(id.listId, rtmTask, synchronizer.getTagController(context));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Helper class for processing RTM id's into one field */
|
|
||||||
private static class RtmId {
|
|
||||||
String taskId;
|
|
||||||
String taskSeriesId;
|
|
||||||
String listId;
|
|
||||||
|
|
||||||
public RtmId(String listId, RtmTaskSeries taskSeries) {
|
|
||||||
if(taskSeries.getTask() == null) {
|
|
||||||
Log.w("rtm", "Error - found task with no task id");
|
|
||||||
this.taskId = "";
|
|
||||||
} else
|
|
||||||
this.taskId = taskSeries.getTask().getId();
|
|
||||||
this.taskSeriesId = taskSeries.getId();
|
|
||||||
this.listId = listId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public RtmId(String id) {
|
|
||||||
StringTokenizer strtok = new StringTokenizer(id, "|");
|
|
||||||
taskId = strtok.nextToken();
|
|
||||||
taskSeriesId = strtok.nextToken();
|
|
||||||
listId = strtok.nextToken();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return taskId + "|" + taskSeriesId + "|" + listId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,684 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.Collections;
|
|
||||||
import java.util.Comparator;
|
|
||||||
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.res.Resources;
|
|
||||||
import android.os.Handler;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import com.flurry.android.FlurryAgent;
|
|
||||||
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.AbstractTaskModel;
|
|
||||||
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.AstridUtilities;
|
|
||||||
import com.timsu.astrid.utilities.DialogUtilities;
|
|
||||||
import com.timsu.astrid.utilities.Preferences;
|
|
||||||
|
|
||||||
/** A service that synchronizes with Astrid
|
|
||||||
*
|
|
||||||
* @author timsu
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public abstract class SynchronizationProvider {
|
|
||||||
|
|
||||||
private final int id;
|
|
||||||
static ProgressDialog progressDialog;
|
|
||||||
private Handler syncHandler;
|
|
||||||
protected Synchronizer synchronizer;
|
|
||||||
|
|
||||||
public SynchronizationProvider(int id) {
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Does some setup and then invokes implemented synchronize method. Call me
|
|
||||||
* on the UI thread!
|
|
||||||
*
|
|
||||||
* @param activity
|
|
||||||
* @param caller
|
|
||||||
*/
|
|
||||||
void synchronizeService(final Context context, Synchronizer caller) {
|
|
||||||
this.synchronizer = caller;
|
|
||||||
|
|
||||||
if(!isBackgroundService()) {
|
|
||||||
try {
|
|
||||||
this.syncHandler = new Handler();
|
|
||||||
syncHandler.post(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
SynchronizationProvider.progressDialog = new ProgressDialog(context);
|
|
||||||
progressDialog.setIcon(android.R.drawable.ic_dialog_info);
|
|
||||||
progressDialog.setTitle("Synchronization");
|
|
||||||
progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
|
|
||||||
progressDialog.setMax(100);
|
|
||||||
progressDialog.setMessage("Checking Authorization...");
|
|
||||||
progressDialog.setProgress(0);
|
|
||||||
progressDialog.setCancelable(false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (IllegalStateException e) {
|
|
||||||
Log.w("sync", "Wasn't in UI thread when creating handler.");
|
|
||||||
syncHandler = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
synchronize(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 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 synchronizer.isService();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Check whether the synchronization request wants to only transmit
|
|
||||||
* one specific task. Returns null if this is not the case */
|
|
||||||
protected TaskIdentifier getSingleTaskForSync() {
|
|
||||||
return synchronizer.getSingleTaskForSync();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Utility method for showing synchronization errors. If message is null,
|
|
||||||
* the contents of the throwable is displayed. It is assumed that the
|
|
||||||
* error was logged separately.
|
|
||||||
*/
|
|
||||||
void showError(final Context context, Throwable e, String message) {
|
|
||||||
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() {
|
|
||||||
try {
|
|
||||||
if(progressDialog != null)
|
|
||||||
progressDialog.dismiss();
|
|
||||||
} catch (Exception e) {
|
|
||||||
// suppress it
|
|
||||||
}
|
|
||||||
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() || syncHandler == null) {
|
|
||||||
// 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 synchronized void synchronizeTasks(final Context context, LinkedList<TaskProxy>
|
|
||||||
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(context, R.string.sync_progress_localtx,
|
|
||||||
task.getName().replaceAll("%", "%%")));
|
|
||||||
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);
|
|
||||||
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(context, R.string.sync_progress_localdel));
|
|
||||||
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.getRemoteId());
|
|
||||||
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 = null;
|
|
||||||
TaskModelForSync task = null;
|
|
||||||
try {
|
|
||||||
localTask = new TaskProxy(getId(), mapping.getRemoteId());
|
|
||||||
task = taskController.fetchTaskForSync(
|
|
||||||
mapping.getTask());
|
|
||||||
if(task == null) {
|
|
||||||
// sucks... task was deleted i guess.
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
localTask.readFromTaskModel(task);
|
|
||||||
localTask.readTagsFromController(task.getTaskIdentifier(),
|
|
||||||
tagController, data.tags);
|
|
||||||
postUpdate(new ProgressLabelUpdater(context, R.string.sync_progress_localtx,
|
|
||||||
task.getName().replaceAll("%", "%%")));
|
|
||||||
} catch (Exception e) {
|
|
||||||
AstridUtilities.reportFlurryError("sync-read-local-task", e);
|
|
||||||
Log.e("astrid", "Exception receiving task", e);
|
|
||||||
if(task != null)
|
|
||||||
log.append("error reading '" + task.getName() + "'\n");
|
|
||||||
else
|
|
||||||
log.append("error reading local task\n");
|
|
||||||
|
|
||||||
continue;
|
|
||||||
} finally {
|
|
||||||
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());
|
|
||||||
// merging disabled - seems not to do the right thing
|
|
||||||
// localTask.mergeWithOther(remoteConflict);
|
|
||||||
stats.mergedTasks++;
|
|
||||||
} else {
|
|
||||||
stats.remoteUpdatedTasks++;
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
|
||||||
AstridUtilities.reportFlurryError("sync-push-task", e);
|
|
||||||
|
|
||||||
Log.e("astrid", "Exception pushing task", e);
|
|
||||||
log.append("error sending '" + task.getName() + "'\n");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// re-fetch remote task
|
|
||||||
if(remoteConflict != null || getSingleTaskForSync() != null) {
|
|
||||||
TaskProxy newTask = helper.refetchTask(remoteConflict);
|
|
||||||
remoteTasks.remove(remoteConflict);
|
|
||||||
remoteTasks.add(newTask);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. REMOTE SYNC load remote information
|
|
||||||
log.append("\n>> on astrid:\n");
|
|
||||||
postUpdate(new ProgressUpdater(0, 1));
|
|
||||||
|
|
||||||
// Rearrange remoteTasks so completed tasks get synchronized first.
|
|
||||||
// This prevents bugs where a repeated task has two copies come down
|
|
||||||
// the wire, the new version and the completed old version. The new
|
|
||||||
// version would get merged, then completed, if done in the wrong order.
|
|
||||||
|
|
||||||
Collections.sort(remoteTasks, new Comparator<TaskProxy>() {
|
|
||||||
@Override
|
|
||||||
public int compare(TaskProxy object1, TaskProxy object2) {
|
|
||||||
if(object1.isDeleted && object2.isDeleted)
|
|
||||||
return 0;
|
|
||||||
else if(object1.isDeleted)
|
|
||||||
return -1;
|
|
||||||
else if(object2.isDeleted())
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
if(object1.completionDate != null && object2.completionDate != null)
|
|
||||||
return object1.completionDate.compareTo(object2.completionDate);
|
|
||||||
else if(object1.completionDate != null)
|
|
||||||
return -1;
|
|
||||||
else if(object2.completionDate != null)
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
for(TaskProxy remoteTask : remoteTasks) {
|
|
||||||
if(remoteTask.name != null)
|
|
||||||
postUpdate(new ProgressLabelUpdater(context, R.string.sync_progress_remotetx,
|
|
||||||
remoteTask.name));
|
|
||||||
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() || remoteTask.progressPercentage ==
|
|
||||||
AbstractTaskModel.COMPLETE_PERCENTAGE) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
task = taskController.searchForTaskForSync(remoteTask.name);
|
|
||||||
|
|
||||||
if(task == null) {
|
|
||||||
task = new TaskModelForSync();
|
|
||||||
setupTaskDefaults(context, task);
|
|
||||||
log.append("added " + remoteTask.name + "\n");
|
|
||||||
} else {
|
|
||||||
log.append("merged " + remoteTask.name + "\n");
|
|
||||||
|
|
||||||
// delete old mapping
|
|
||||||
mapping = data.localIdToSyncMapping.get(task.getTaskIdentifier());
|
|
||||||
if(mapping != null) {
|
|
||||||
syncController.deleteSyncMapping(mapping);
|
|
||||||
data.localIdToSyncMapping.remove(task.getTaskIdentifier());
|
|
||||||
mapping = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} 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, true);
|
|
||||||
|
|
||||||
// save tags
|
|
||||||
LinkedList<TagIdentifier> taskTags = tagController.getTaskTags(task.getTaskIdentifier());
|
|
||||||
if(remoteTask.tags != null) {
|
|
||||||
HashSet<TagIdentifier> tagsToAdd = new HashSet<TagIdentifier>();
|
|
||||||
for(String tag : remoteTask.tags) {
|
|
||||||
String tagLower = tag.toLowerCase();
|
|
||||||
if(!data.tagsByLCName.containsKey(tagLower)) {
|
|
||||||
TagIdentifier tagId = tagController.createTag(tag);
|
|
||||||
data.tagsByLCName.put(tagLower, tagId);
|
|
||||||
tagsToAdd.add(tagId);
|
|
||||||
} else
|
|
||||||
tagsToAdd.add(data.tagsByLCName.get(tagLower));
|
|
||||||
}
|
|
||||||
|
|
||||||
HashSet<TagIdentifier> tagsToDelete = new HashSet<TagIdentifier>(taskTags);
|
|
||||||
tagsToDelete.removeAll(tagsToAdd);
|
|
||||||
tagsToAdd.removeAll(taskTags);
|
|
||||||
|
|
||||||
for(TagIdentifier tagId : tagsToDelete)
|
|
||||||
tagController.removeTag(task.getTaskIdentifier(), tagId);
|
|
||||||
for(TagIdentifier tagId : tagsToAdd)
|
|
||||||
tagController.addTag(task.getTaskIdentifier(), tagId);
|
|
||||||
} else {
|
|
||||||
// remove existing tags
|
|
||||||
for(TagIdentifier tagId : taskTags) {
|
|
||||||
tagController.removeTag(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++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReminderService.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<SyncMapping> mappings;
|
|
||||||
HashSet<TaskIdentifier> activeTasks;
|
|
||||||
HashSet<TaskIdentifier> allTasks;
|
|
||||||
|
|
||||||
HashMap<String, SyncMapping> remoteIdToSyncMapping;
|
|
||||||
HashMap<TaskIdentifier, SyncMapping> localIdToSyncMapping;
|
|
||||||
|
|
||||||
HashSet<SyncMapping> localChanges;
|
|
||||||
HashSet<TaskIdentifier> mappedTasks;
|
|
||||||
HashMap<TaskIdentifier, TaskProxy> remoteChangeMap;
|
|
||||||
HashMap<String, TaskProxy> newRemoteTasks;
|
|
||||||
|
|
||||||
HashMap<TagIdentifier, TagModelForView> tags;
|
|
||||||
HashMap<String, TagIdentifier> tagsByLCName;
|
|
||||||
|
|
||||||
HashSet<TaskIdentifier> newlyCreatedTasks;
|
|
||||||
HashSet<TaskIdentifier> deletedTasks;
|
|
||||||
|
|
||||||
public SyncData(Context context, LinkedList<TaskProxy> remoteTasks) {
|
|
||||||
// 1. get data out of the database
|
|
||||||
mappings = synchronizer.getSyncController(context).getSyncMappings(getId());
|
|
||||||
activeTasks = synchronizer.getTaskController(context).getActiveTaskIdentifiers();
|
|
||||||
allTasks = synchronizer.getTaskController(context).getAllTaskIdentifiers();
|
|
||||||
tags = synchronizer.getTagController(context).getAllTagsAsMap();
|
|
||||||
|
|
||||||
// 2. build helper data structures
|
|
||||||
remoteIdToSyncMapping = new HashMap<String, SyncMapping>();
|
|
||||||
localIdToSyncMapping = new HashMap<TaskIdentifier, SyncMapping>();
|
|
||||||
mappedTasks = new HashSet<TaskIdentifier>();
|
|
||||||
for(SyncMapping mapping : mappings) {
|
|
||||||
remoteIdToSyncMapping.put(mapping.getRemoteId(), mapping);
|
|
||||||
localIdToSyncMapping.put(mapping.getTask(), mapping);
|
|
||||||
mappedTasks.add(mapping.getTask());
|
|
||||||
}
|
|
||||||
tagsByLCName = new HashMap<String, TagIdentifier>();
|
|
||||||
for(TagModelForView tag : tags.values())
|
|
||||||
tagsByLCName.put(tag.getName().toLowerCase(), tag.getTagIdentifier());
|
|
||||||
|
|
||||||
// 3. build map of remote tasks
|
|
||||||
remoteChangeMap = new HashMap<TaskIdentifier, TaskProxy>();
|
|
||||||
newRemoteTasks = new HashMap<String, TaskProxy>();
|
|
||||||
for(TaskProxy remoteTask : remoteTasks) {
|
|
||||||
if(remoteIdToSyncMapping.containsKey(remoteTask.getRemoteId())) {
|
|
||||||
SyncMapping mapping = remoteIdToSyncMapping.get(remoteTask.getRemoteId());
|
|
||||||
remoteChangeMap.put(mapping.getTask(), remoteTask);
|
|
||||||
} else if(remoteTask.name != null){
|
|
||||||
newRemoteTasks.put(remoteTask.name, remoteTask);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. build data structures of things to do
|
|
||||||
if(getSingleTaskForSync() != null) {
|
|
||||||
newlyCreatedTasks = new HashSet<TaskIdentifier>();
|
|
||||||
deletedTasks = new HashSet<TaskIdentifier>();
|
|
||||||
localChanges = new HashSet<SyncMapping>();
|
|
||||||
} else {
|
|
||||||
newlyCreatedTasks = new HashSet<TaskIdentifier>(activeTasks);
|
|
||||||
newlyCreatedTasks.removeAll(mappedTasks);
|
|
||||||
deletedTasks = new HashSet<TaskIdentifier>(mappedTasks);
|
|
||||||
deletedTasks.removeAll(allTasks);
|
|
||||||
localChanges = new HashSet<SyncMapping>();
|
|
||||||
for(SyncMapping mapping : localIdToSyncMapping.values()) {
|
|
||||||
if(mapping.isUpdated())
|
|
||||||
localChanges.add(mapping);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/** 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();
|
|
||||||
|
|
||||||
HashMap<String, String> args = new HashMap<String, String>();
|
|
||||||
args.put("localCreatedTasks", Integer.toString(localCreatedTasks));
|
|
||||||
args.put("localUpdatedTasks", Integer.toString(localUpdatedTasks));
|
|
||||||
args.put("localDeletedTasks", Integer.toString(localDeletedTasks));
|
|
||||||
args.put("mergedTasks", Integer.toString(mergedTasks));
|
|
||||||
args.put("remoteCreatedTasks", Integer.toString(remoteCreatedTasks));
|
|
||||||
args.put("remoteUpdatedTasks", Integer.toString(remoteUpdatedTasks));
|
|
||||||
args.put("remoteDeletedTasks", Integer.toString(remoteDeletedTasks));
|
|
||||||
FlurryAgent.onEvent("sync-finished", args);
|
|
||||||
|
|
||||||
if(Preferences.shouldSuppressSyncDialogs(context) ||
|
|
||||||
getSingleTaskForSync() != null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Resources r = context.getResources();
|
|
||||||
Dialog.OnClickListener finishListener = null;
|
|
||||||
|
|
||||||
// nothing updated
|
|
||||||
if(localCreatedTasks + localUpdatedTasks + localDeletedTasks +
|
|
||||||
mergedTasks + remoteCreatedTasks + remoteDeletedTasks +
|
|
||||||
remoteUpdatedTasks == 0) {
|
|
||||||
if(!isBackgroundService())
|
|
||||||
DialogUtilities.okDialog(context, context.getResources().
|
|
||||||
getString(R.string.sync_uptodate), 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(Context context, int id, Object... args) {
|
|
||||||
try {
|
|
||||||
this.label = context.getResources().getString(id, args);
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.e("sync-progress", "Error formatting progress label", e);
|
|
||||||
this.label = context.getResources().getString(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public void run() {
|
|
||||||
if(isBackgroundService()) {
|
|
||||||
Log.i("astrid-sync", label);
|
|
||||||
} else {
|
|
||||||
if(!progressDialog.isShowing())
|
|
||||||
progressDialog.show();
|
|
||||||
progressDialog.setMessage(label);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,149 +0,0 @@
|
|||||||
package com.timsu.astrid.sync;
|
|
||||||
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
import android.app.AlarmManager;
|
|
||||||
import android.app.PendingIntent;
|
|
||||||
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.Constants;
|
|
||||||
import com.timsu.astrid.utilities.Preferences;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SynchronizationService is the service that performs Astrid's background
|
|
||||||
* synchronization with online task managers. Starting this service
|
|
||||||
* schedules a repeating alarm which handles the synchronization
|
|
||||||
*
|
|
||||||
* @author Tim Su
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class SynchronizationService extends Service {
|
|
||||||
|
|
||||||
/** miniumum time before an auto-sync */
|
|
||||||
private static final long AUTO_SYNC_MIN_OFFSET = 5*60*1000L;
|
|
||||||
|
|
||||||
/** alarm identifier */
|
|
||||||
private static final String SYNC_ACTION = "sync";
|
|
||||||
|
|
||||||
// --- BroadcastReceiver abstract methods
|
|
||||||
|
|
||||||
/** Receive the alarm - start the synchronize service! */
|
|
||||||
@Override
|
|
||||||
public void onStart(Intent intent, int startId) {
|
|
||||||
Log.i("astrid", "wassup");
|
|
||||||
if(intent.getAction().equals(SYNC_ACTION))
|
|
||||||
startSynchronization(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Start the actual synchronization */
|
|
||||||
private void startSynchronization(Context context) {
|
|
||||||
if(context == null || context.getResources() == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// figure out synchronization frequency
|
|
||||||
Integer syncFrequencySeconds = Preferences.getSyncAutoSyncFrequency(context);
|
|
||||||
if(syncFrequencySeconds == null)
|
|
||||||
return;
|
|
||||||
long interval = 1000L * syncFrequencySeconds;
|
|
||||||
long offset = computeNextSyncOffset(context, interval);
|
|
||||||
|
|
||||||
// if premature request for sync (i.e. user invoked manual sync,
|
|
||||||
// reschedule our service
|
|
||||||
if(offset != 0) {
|
|
||||||
Log.i("astrid", "Automatic Synchronize Postponed.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.i("astrid", "Automatic Synchronize Initiated.");
|
|
||||||
Preferences.setSyncLastSyncAttempt(context, new Date());
|
|
||||||
|
|
||||||
Synchronizer sync = new Synchronizer(true);
|
|
||||||
sync.synchronize(context, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- alarm management
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Schedules repeating alarm for auto-synchronization
|
|
||||||
*/
|
|
||||||
public static void scheduleService(Context context) {
|
|
||||||
Integer syncFrequencySeconds = Preferences.getSyncAutoSyncFrequency(context);
|
|
||||||
|
|
||||||
if(syncFrequencySeconds == null) {
|
|
||||||
Log.w("sync-service", "disabled synchronization service");
|
|
||||||
unscheduleService(context);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// figure out synchronization frequency
|
|
||||||
long interval = 1000L * syncFrequencySeconds;
|
|
||||||
long offset = computeNextSyncOffset(context, interval);
|
|
||||||
|
|
||||||
// give a little padding
|
|
||||||
offset = Math.max(offset, AUTO_SYNC_MIN_OFFSET);
|
|
||||||
|
|
||||||
AlarmManager am = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
|
|
||||||
PendingIntent pendingIntent = PendingIntent.getService(context, 0,
|
|
||||||
createAlarmIntent(context), PendingIntent.FLAG_UPDATE_CURRENT);
|
|
||||||
|
|
||||||
if (Constants.DEBUG)
|
|
||||||
Log.e("Astrid", "Autosync set for " + offset / 1000
|
|
||||||
+ " seconds repeating every " + syncFrequencySeconds);
|
|
||||||
|
|
||||||
// cancel all existing
|
|
||||||
am.cancel(pendingIntent);
|
|
||||||
|
|
||||||
// schedule new
|
|
||||||
am.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + offset,
|
|
||||||
interval, pendingIntent);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes repeating alarm for auto-synchronization
|
|
||||||
*/
|
|
||||||
public static void unscheduleService(Context context) {
|
|
||||||
AlarmManager am = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
|
|
||||||
PendingIntent pendingIntent = PendingIntent.getService(context, 0,
|
|
||||||
createAlarmIntent(context), PendingIntent.FLAG_UPDATE_CURRENT);
|
|
||||||
am.cancel(pendingIntent);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Create the alarm intent */
|
|
||||||
private static Intent createAlarmIntent(Context context) {
|
|
||||||
Intent intent = new Intent(context, SynchronizationService.class);
|
|
||||||
intent.setAction(SYNC_ACTION);
|
|
||||||
return intent;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- utility methods
|
|
||||||
|
|
||||||
|
|
||||||
private static long computeNextSyncOffset(Context context, long interval) {
|
|
||||||
// figure out last synchronize time
|
|
||||||
Date lastSyncDate = Preferences.getSyncLastSync(context);
|
|
||||||
Date lastAutoSyncDate = Preferences.getSyncLastSyncAttempt(context);
|
|
||||||
|
|
||||||
// if user never synchronized, give them a full offset period before bg sync
|
|
||||||
long latestSyncMillis = System.currentTimeMillis();
|
|
||||||
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());
|
|
||||||
|
|
||||||
return offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public IBinder onBind(Intent intent) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,292 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.lang.reflect.InvocationTargetException;
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import com.timsu.astrid.data.AbstractController;
|
|
||||||
import com.timsu.astrid.data.alerts.AlertController;
|
|
||||||
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.data.task.TaskIdentifier;
|
|
||||||
import com.timsu.astrid.utilities.AstridUtilities;
|
|
||||||
import com.timsu.astrid.utilities.Preferences;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Synchronizer is a class that manages a synchronization lifecycle. You would
|
|
||||||
* use it as follows.
|
|
||||||
* <p>
|
|
||||||
* <pre>Synchronizer synchronizer = new Synchronizer(...);
|
|
||||||
* synchronizer.synchronize();</pre>
|
|
||||||
*
|
|
||||||
* @author Tim Su
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class Synchronizer {
|
|
||||||
|
|
||||||
/** identifier for the RTM sync provider */
|
|
||||||
private static final int SYNC_ID_RTM = 1;
|
|
||||||
|
|
||||||
// --- public interface
|
|
||||||
|
|
||||||
/** Synchronize all tasks */
|
|
||||||
public Synchronizer(boolean isService) {
|
|
||||||
this.isService = isService;
|
|
||||||
singleTaskForSync = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Synchronize a specific task only */
|
|
||||||
public Synchronizer(TaskIdentifier task) {
|
|
||||||
isService = false;
|
|
||||||
singleTaskForSync = task;
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface SynchronizerListener {
|
|
||||||
void onSynchronizerFinished(int numServicesSynced);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Synchronize all activated sync services. */
|
|
||||||
public synchronized void synchronize(Context context,
|
|
||||||
SynchronizerListener listener) {
|
|
||||||
currentStep = ServiceWrapper._FIRST_SERVICE.ordinal();
|
|
||||||
servicesSynced = 0;
|
|
||||||
callback = listener;
|
|
||||||
|
|
||||||
continueSynchronization(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/** Clears tokens of activated services */
|
|
||||||
public static void clearUserData(Activity activity) {
|
|
||||||
Synchronizer synchronizer = new Synchronizer(false);
|
|
||||||
for(ServiceWrapper serviceWrapper : ServiceWrapper.values()) {
|
|
||||||
if(serviceWrapper.isActivated(activity)) {
|
|
||||||
serviceWrapper.service.synchronizer = synchronizer;
|
|
||||||
serviceWrapper.service.clearPersonalData(activity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
synchronizer.closeControllers();
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- internal synchronization logic
|
|
||||||
|
|
||||||
/** Synchronization Services enumeration
|
|
||||||
* note that id must be kept constant!
|
|
||||||
* @author timsu
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
private enum ServiceWrapper {
|
|
||||||
_FIRST_SERVICE(null) { // must be first entry
|
|
||||||
@Override
|
|
||||||
boolean isActivated(Context arg0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
RTM(new RTMSyncProvider(SYNC_ID_RTM)) {
|
|
||||||
@Override
|
|
||||||
boolean isActivated(Context context) {
|
|
||||||
return Preferences.shouldSyncRTM(context);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
_LAST_SERVICE(null) { // must be last entry
|
|
||||||
@Override
|
|
||||||
boolean isActivated(Context arg0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private SynchronizationProvider service;
|
|
||||||
|
|
||||||
private ServiceWrapper(SynchronizationProvider service) {
|
|
||||||
this.service = service;
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract boolean isActivated(Context context);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Internal state for the synchronization process
|
|
||||||
|
|
||||||
/** Current step in the sync process */
|
|
||||||
private int currentStep = 0;
|
|
||||||
|
|
||||||
/** # of services synchronized */
|
|
||||||
private int servicesSynced = 0;
|
|
||||||
|
|
||||||
/** On finished callback */
|
|
||||||
private SynchronizerListener callback = null;
|
|
||||||
|
|
||||||
/** Whether this sync is initiated by a background service */
|
|
||||||
private final boolean isService;
|
|
||||||
|
|
||||||
/** The single task to synchronize, if applicable */
|
|
||||||
private final TaskIdentifier singleTaskForSync;
|
|
||||||
|
|
||||||
boolean isService() {
|
|
||||||
return isService;
|
|
||||||
}
|
|
||||||
|
|
||||||
TaskIdentifier getSingleTaskForSync() {
|
|
||||||
return singleTaskForSync;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Called to do the next step of synchronization. */
|
|
||||||
void continueSynchronization(Context context) {
|
|
||||||
try {
|
|
||||||
if(currentStep >= ServiceWrapper.values().length)
|
|
||||||
currentStep = ServiceWrapper.values().length - 1;
|
|
||||||
|
|
||||||
ServiceWrapper serviceWrapper =
|
|
||||||
ServiceWrapper.values()[currentStep];
|
|
||||||
currentStep++;
|
|
||||||
switch(serviceWrapper) {
|
|
||||||
case _FIRST_SERVICE:
|
|
||||||
continueSynchronization(context);
|
|
||||||
break;
|
|
||||||
case RTM:
|
|
||||||
if(serviceWrapper.isActivated(context)) {
|
|
||||||
servicesSynced++;
|
|
||||||
serviceWrapper.service.synchronizeService(context, this);
|
|
||||||
} else {
|
|
||||||
continueSynchronization(context);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case _LAST_SERVICE:
|
|
||||||
finishSynchronization(context);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.e("sync", "Error continuing synchronization", e);
|
|
||||||
AstridUtilities.reportFlurryError("sync-continue", e);
|
|
||||||
finishSynchronization(context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Called at the end of sync. */
|
|
||||||
private void finishSynchronization(final Context context) {
|
|
||||||
closeControllers();
|
|
||||||
if(callback != null)
|
|
||||||
callback.onSynchronizerFinished(servicesSynced);
|
|
||||||
|
|
||||||
if(getSingleTaskForSync() == null)
|
|
||||||
Preferences.setSyncLastSync(context, new Date());
|
|
||||||
if(!isService) {
|
|
||||||
// TaskListSubActivity.shouldRefreshTaskList = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.i("sync", "Synchronization Service Finished");
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- controller stuff
|
|
||||||
|
|
||||||
private final ControllerWrapper<SyncDataController> syncController =
|
|
||||||
new ControllerWrapper<SyncDataController>(SyncDataController.class);
|
|
||||||
private final ControllerWrapper<TaskController> taskController =
|
|
||||||
new ControllerWrapper<TaskController>(TaskController.class);
|
|
||||||
private final ControllerWrapper<TagController> tagController =
|
|
||||||
new ControllerWrapper<TagController>(TagController.class);
|
|
||||||
private final ControllerWrapper<AlertController> alertController =
|
|
||||||
new ControllerWrapper<AlertController>(AlertController.class);
|
|
||||||
|
|
||||||
private static class ControllerWrapper<TYPE extends AbstractController> {
|
|
||||||
TYPE controller;
|
|
||||||
Class<TYPE> typeClass;
|
|
||||||
boolean override;
|
|
||||||
|
|
||||||
public ControllerWrapper(Class<TYPE> cls) {
|
|
||||||
override = false;
|
|
||||||
controller = null;
|
|
||||||
typeClass = cls;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public TYPE get(Context context) {
|
|
||||||
if(controller == null) {
|
|
||||||
try {
|
|
||||||
controller = (TYPE) typeClass.getConstructors()[0].newInstance(context);
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
Log.e(getClass().getSimpleName(), e.toString());
|
|
||||||
} catch (SecurityException e) {
|
|
||||||
Log.e(getClass().getSimpleName(), e.toString());
|
|
||||||
} catch (InstantiationException e) {
|
|
||||||
Log.e(getClass().getSimpleName(), e.toString());
|
|
||||||
} catch (IllegalAccessException e) {
|
|
||||||
Log.e(getClass().getSimpleName(), e.toString());
|
|
||||||
} catch (InvocationTargetException e) {
|
|
||||||
Log.e(getClass().getSimpleName(), e.toString());
|
|
||||||
}
|
|
||||||
controller.open();
|
|
||||||
}
|
|
||||||
return controller;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void set(TYPE newController) {
|
|
||||||
if(controller != null && !override)
|
|
||||||
close();
|
|
||||||
|
|
||||||
override = newController != null;
|
|
||||||
controller = newController;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void close() {
|
|
||||||
if(controller != null && !override) {
|
|
||||||
controller.close();
|
|
||||||
controller = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SyncDataController getSyncController(Context context) {
|
|
||||||
return syncController.get(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
TaskController getTaskController(Context context) {
|
|
||||||
return taskController.get(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
TagController getTagController(Context context) {
|
|
||||||
return tagController.get(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
AlertController getAlertController(Context context) {
|
|
||||||
return alertController.get(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTaskController(TaskController taskController) {
|
|
||||||
this.taskController.set(taskController);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTagController(TagController tagController) {
|
|
||||||
this.tagController.set(tagController);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void closeControllers() {
|
|
||||||
syncController.close();
|
|
||||||
taskController.close();
|
|
||||||
tagController.close();
|
|
||||||
alertController.close();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,219 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.util.Date;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
|
|
||||||
import com.timsu.astrid.data.enums.Importance;
|
|
||||||
import com.timsu.astrid.data.enums.RepeatInterval;
|
|
||||||
import com.timsu.astrid.data.tag.TagController;
|
|
||||||
import com.timsu.astrid.data.tag.TagIdentifier;
|
|
||||||
import com.timsu.astrid.data.tag.TagModelForView;
|
|
||||||
import com.timsu.astrid.data.task.AbstractTaskModel.RepeatInfo;
|
|
||||||
import com.timsu.astrid.data.task.TaskIdentifier;
|
|
||||||
import com.timsu.astrid.data.task.TaskModelForSync;
|
|
||||||
|
|
||||||
/** Representation of a task on a remote server. Your synchronization
|
|
||||||
* service should instantiate these, filling out every field (use null
|
|
||||||
* where the field does not exist).
|
|
||||||
*
|
|
||||||
* @author timsu
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class TaskProxy {
|
|
||||||
|
|
||||||
public static final Date NO_DATE_SET = new Date(0);
|
|
||||||
public static final RepeatInfo NO_REPEAT_SET = new RepeatInfo(RepeatInterval.DAYS, 0);
|
|
||||||
|
|
||||||
TaskProxy(int syncServiceId, String syncTaskId) {
|
|
||||||
this.syncServiceId = syncServiceId;
|
|
||||||
this.syncTaskId = syncTaskId;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- fill these out
|
|
||||||
|
|
||||||
String name = null;
|
|
||||||
String notes = null;
|
|
||||||
|
|
||||||
Importance importance = null;
|
|
||||||
Integer progressPercentage = null;
|
|
||||||
|
|
||||||
Date creationDate = null;
|
|
||||||
Date completionDate = null;
|
|
||||||
|
|
||||||
Date dueDate = null;
|
|
||||||
Date definiteDueDate = null;
|
|
||||||
Date preferredDueDate = null;
|
|
||||||
Date hiddenUntil = null;
|
|
||||||
|
|
||||||
LinkedList<String> tags = null;
|
|
||||||
|
|
||||||
Integer estimatedSeconds = null;
|
|
||||||
Integer elapsedSeconds = null;
|
|
||||||
RepeatInfo repeatInfo = null;
|
|
||||||
|
|
||||||
Boolean syncOnComplete = null;
|
|
||||||
|
|
||||||
/** was the task deleted on the remote server */
|
|
||||||
boolean isDeleted = false;
|
|
||||||
|
|
||||||
// --- internal state
|
|
||||||
|
|
||||||
/** id of the synchronization service */
|
|
||||||
private int syncServiceId;
|
|
||||||
|
|
||||||
/** id of this particular remote task */
|
|
||||||
private String syncTaskId;
|
|
||||||
|
|
||||||
|
|
||||||
public int getSyncServiceId() {
|
|
||||||
return syncServiceId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getRemoteId() {
|
|
||||||
return syncTaskId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isDeleted() {
|
|
||||||
return isDeleted;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- helper methods
|
|
||||||
|
|
||||||
/** Merge with another TaskProxy. Fields in this taskProxy will be overwritten! */
|
|
||||||
public void mergeWithOther(TaskProxy other) {
|
|
||||||
if(other == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if(other.name != null)
|
|
||||||
name = other.name;
|
|
||||||
if(other.notes != null)
|
|
||||||
notes = other.notes;
|
|
||||||
if(other.importance != null)
|
|
||||||
importance = other.importance;
|
|
||||||
if(other.progressPercentage != null)
|
|
||||||
progressPercentage = other.progressPercentage;
|
|
||||||
if(other.creationDate != null)
|
|
||||||
creationDate = other.creationDate;
|
|
||||||
if(other.completionDate != null)
|
|
||||||
completionDate = other.completionDate;
|
|
||||||
if(other.dueDate != null)
|
|
||||||
dueDate = other.dueDate;
|
|
||||||
if(other.definiteDueDate != null)
|
|
||||||
definiteDueDate = other.definiteDueDate;
|
|
||||||
if(other.preferredDueDate != null)
|
|
||||||
preferredDueDate = other.preferredDueDate;
|
|
||||||
if(other.hiddenUntil != null)
|
|
||||||
hiddenUntil = other.hiddenUntil;
|
|
||||||
if(other.tags != null)
|
|
||||||
tags = other.tags;
|
|
||||||
if(other.estimatedSeconds != null)
|
|
||||||
estimatedSeconds = other.estimatedSeconds;
|
|
||||||
if(other.elapsedSeconds != null)
|
|
||||||
elapsedSeconds = other.elapsedSeconds;
|
|
||||||
if(other.repeatInfo != null)
|
|
||||||
repeatInfo = other.repeatInfo;
|
|
||||||
if(other.syncOnComplete != null)
|
|
||||||
syncOnComplete = other.syncOnComplete;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Read from the given task model */
|
|
||||||
public void readFromTaskModel(TaskModelForSync task) {
|
|
||||||
name = task.getName();
|
|
||||||
notes = task.getNotes();
|
|
||||||
importance = task.getImportance();
|
|
||||||
progressPercentage = task.getProgressPercentage();
|
|
||||||
creationDate = task.getCreationDate();
|
|
||||||
completionDate = task.getCompletionDate();
|
|
||||||
definiteDueDate = task.getDefiniteDueDate();
|
|
||||||
preferredDueDate = task.getPreferredDueDate();
|
|
||||||
dueDate = definiteDueDate != null ? definiteDueDate : preferredDueDate;
|
|
||||||
hiddenUntil = task.getHiddenUntil();
|
|
||||||
estimatedSeconds = task.getEstimatedSeconds();
|
|
||||||
elapsedSeconds = task.getElapsedSeconds();
|
|
||||||
syncOnComplete = (task.getFlags() & TaskModelForSync.FLAG_SYNC_ON_COMPLETE) > 0;
|
|
||||||
repeatInfo = task.getRepeat();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Read tags from the given tag controller */
|
|
||||||
public void readTagsFromController(TaskIdentifier taskId,
|
|
||||||
TagController tagController, HashMap<TagIdentifier, TagModelForView>
|
|
||||||
tagList) {
|
|
||||||
LinkedList<TagIdentifier> tagIds = tagController.getTaskTags(taskId);
|
|
||||||
tags = new LinkedList<String>();
|
|
||||||
for(TagIdentifier tagId : tagIds) {
|
|
||||||
tags.add(tagList.get(tagId).getName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Write to the given task model */
|
|
||||||
public void writeToTaskModel(TaskModelForSync task) {
|
|
||||||
if(name != null)
|
|
||||||
task.setName(name);
|
|
||||||
if(notes != null)
|
|
||||||
task.setNotes(notes);
|
|
||||||
else
|
|
||||||
task.setNotes("");
|
|
||||||
if(importance != null)
|
|
||||||
task.setImportance(importance);
|
|
||||||
if(progressPercentage != null)
|
|
||||||
task.setProgressPercentage(progressPercentage);
|
|
||||||
if(creationDate != null)
|
|
||||||
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);
|
|
||||||
} else {
|
|
||||||
task.setDefiniteDueDate(definiteDueDate);
|
|
||||||
task.setPreferredDueDate(preferredDueDate);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(hiddenUntil != null)
|
|
||||||
task.setHiddenUntil(hiddenUntil);
|
|
||||||
task.setEstimatedSeconds(estimatedSeconds);
|
|
||||||
if(elapsedSeconds != null)
|
|
||||||
task.setElapsedSeconds(elapsedSeconds);
|
|
||||||
if(syncOnComplete != null)
|
|
||||||
task.setFlags(task.getFlags() | TaskModelForSync.FLAG_SYNC_ON_COMPLETE);
|
|
||||||
else
|
|
||||||
task.setFlags(task.getFlags() & ~TaskModelForSync.FLAG_SYNC_ON_COMPLETE);
|
|
||||||
|
|
||||||
// this is inaccurate. =/
|
|
||||||
if(repeatInfo != null) {
|
|
||||||
if(repeatInfo == NO_REPEAT_SET)
|
|
||||||
task.setRepeat(null);
|
|
||||||
else
|
|
||||||
task.setRepeat(repeatInfo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue