mirror of https://github.com/tasks/tasks
Refactored Task.REMOTE_ID to be null when unset rather than 0. Also catch SQLiteConsistency exceptions and recover when inserting a task that already has that remote id.
parent
339b3f8062
commit
46f2ee2c2e
@ -1,363 +0,0 @@
|
|||||||
/**
|
|
||||||
* See the file "LICENSE" for the full license governing this code.
|
|
||||||
*/
|
|
||||||
package com.todoroo.astrid.actfm.sync;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
import org.json.JSONArray;
|
|
||||||
import org.json.JSONException;
|
|
||||||
import org.json.JSONObject;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.app.Notification;
|
|
||||||
import android.app.PendingIntent;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
|
|
||||||
import com.timsu.astrid.C2DMReceiver;
|
|
||||||
import com.timsu.astrid.R;
|
|
||||||
import com.todoroo.andlib.data.TodorooCursor;
|
|
||||||
import com.todoroo.andlib.service.Autowired;
|
|
||||||
import com.todoroo.andlib.service.ContextManager;
|
|
||||||
import com.todoroo.andlib.sql.Criterion;
|
|
||||||
import com.todoroo.andlib.sql.Query;
|
|
||||||
import com.todoroo.andlib.utility.DateUtilities;
|
|
||||||
import com.todoroo.andlib.utility.Preferences;
|
|
||||||
import com.todoroo.astrid.actfm.ActFmBackgroundService;
|
|
||||||
import com.todoroo.astrid.actfm.ActFmLoginActivity;
|
|
||||||
import com.todoroo.astrid.actfm.ActFmPreferences;
|
|
||||||
import com.todoroo.astrid.actfm.sync.ActFmSyncService.JsonHelper;
|
|
||||||
import com.todoroo.astrid.api.AstridApiConstants;
|
|
||||||
import com.todoroo.astrid.core.PluginServices;
|
|
||||||
import com.todoroo.astrid.dao.TagDataDao;
|
|
||||||
import com.todoroo.astrid.dao.TaskDao;
|
|
||||||
import com.todoroo.astrid.data.Metadata;
|
|
||||||
import com.todoroo.astrid.data.TagData;
|
|
||||||
import com.todoroo.astrid.data.Task;
|
|
||||||
import com.todoroo.astrid.notes.NoteMetadata;
|
|
||||||
import com.todoroo.astrid.service.AstridDependencyInjector;
|
|
||||||
import com.todoroo.astrid.service.StatisticsConstants;
|
|
||||||
import com.todoroo.astrid.service.StatisticsService;
|
|
||||||
import com.todoroo.astrid.sync.SyncProvider;
|
|
||||||
import com.todoroo.astrid.sync.SyncProviderUtilities;
|
|
||||||
import com.todoroo.astrid.utility.Constants;
|
|
||||||
|
|
||||||
@SuppressWarnings("nls")
|
|
||||||
public class ActFmSyncProvider extends SyncProvider<ActFmTaskContainer> {
|
|
||||||
|
|
||||||
private ActFmInvoker invoker = null;
|
|
||||||
|
|
||||||
@Autowired ActFmDataService actFmDataService;
|
|
||||||
@Autowired ActFmSyncService actFmSyncService;
|
|
||||||
@Autowired ActFmPreferenceService actFmPreferenceService;
|
|
||||||
@Autowired TagDataDao tagDataDao;
|
|
||||||
|
|
||||||
static {
|
|
||||||
AstridDependencyInjector.initialize();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------
|
|
||||||
// ------------------------------------------------------ utility methods
|
|
||||||
// ----------------------------------------------------------------------
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected SyncProviderUtilities getUtilities() {
|
|
||||||
return actFmPreferenceService;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sign out of service, deleting all synchronization metadata
|
|
||||||
*/
|
|
||||||
public void signOut() {
|
|
||||||
actFmPreferenceService.setToken(null);
|
|
||||||
actFmPreferenceService.clearLastSyncDate();
|
|
||||||
C2DMReceiver.unregister();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------
|
|
||||||
// ------------------------------------------------------ initiating sync
|
|
||||||
// ----------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* initiate sync in background
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
protected void initiateBackground() {
|
|
||||||
try {
|
|
||||||
C2DMReceiver.register();
|
|
||||||
String authToken = actFmPreferenceService.getToken();
|
|
||||||
invoker = new ActFmInvoker(authToken);
|
|
||||||
|
|
||||||
// check if we have a token & it works
|
|
||||||
if(authToken != null) {
|
|
||||||
performSync();
|
|
||||||
}
|
|
||||||
} catch (IllegalStateException e) {
|
|
||||||
// occurs when application was closed
|
|
||||||
} catch (Exception e) {
|
|
||||||
handleException("actfm-authenticate", e, false);
|
|
||||||
} finally {
|
|
||||||
actFmPreferenceService.stopOngoing();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If user isn't already signed in, show sign in dialog. Else perform sync.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
protected void initiateManual(Activity activity) {
|
|
||||||
String authToken = actFmPreferenceService.getToken();
|
|
||||||
actFmPreferenceService.stopOngoing();
|
|
||||||
|
|
||||||
// check if we have a token & it works
|
|
||||||
if(authToken == null) {
|
|
||||||
// display login-activity
|
|
||||||
Intent intent = new Intent(activity, ActFmLoginActivity.class);
|
|
||||||
activity.startActivityForResult(intent, 0);
|
|
||||||
} else {
|
|
||||||
activity.startService(new Intent(null, null,
|
|
||||||
activity, ActFmBackgroundService.class));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------
|
|
||||||
// ----------------------------------------------------- synchronization!
|
|
||||||
// ----------------------------------------------------------------------
|
|
||||||
|
|
||||||
protected void performSync() {
|
|
||||||
actFmPreferenceService.recordSyncStart();
|
|
||||||
String syncSuccess = "failed";
|
|
||||||
|
|
||||||
try {
|
|
||||||
int serverTime = Preferences.getInt(ActFmPreferenceService.PREF_SERVER_TIME, 0);
|
|
||||||
|
|
||||||
ArrayList<ActFmTaskContainer> remoteTasks = new ArrayList<ActFmTaskContainer>();
|
|
||||||
|
|
||||||
// int newServerTime = fetchRemoteTasks(serverTime, remoteTasks);
|
|
||||||
if (serverTime == 0) { // If we've never synced, we may lose some empty tags
|
|
||||||
pushUnsavedTagData();
|
|
||||||
}
|
|
||||||
// fetchRemoteTagData(serverTime);
|
|
||||||
|
|
||||||
/* SyncData<ActFmTaskContainer> syncData = populateSyncData(remoteTasks);
|
|
||||||
|
|
||||||
try {
|
|
||||||
synchronizeTasks(syncData);
|
|
||||||
} finally {
|
|
||||||
syncData.localCreated.close();
|
|
||||||
syncData.localUpdated.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
Preferences.setInt(ActFmPreferenceService.PREF_SERVER_TIME, newServerTime); */
|
|
||||||
|
|
||||||
actFmPreferenceService.recordSuccessfulSync();
|
|
||||||
|
|
||||||
syncSuccess = getFinalSyncStatus();
|
|
||||||
Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_EVENT_REFRESH);
|
|
||||||
ContextManager.getContext().sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ);
|
|
||||||
|
|
||||||
} catch (IllegalStateException e) {
|
|
||||||
// occurs when application was closed
|
|
||||||
} catch (Exception e) {
|
|
||||||
handleException("actfm-sync", e, false); //$NON-NLS-1$
|
|
||||||
} finally {
|
|
||||||
StatisticsService.reportEvent(StatisticsConstants.ACTFM_SYNC_FINISHED,
|
|
||||||
"success", syncSuccess); //$NON-NLS-1$
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Pushes unsaved, empty tag data, which will otherwise be deleted by the fetch tags call
|
|
||||||
private void pushUnsavedTagData() {
|
|
||||||
TodorooCursor<TagData> unsavedTagData = tagDataDao.query(Query.select(TagData.ID, TagData.TASK_COUNT).where(Criterion.and(TagData.TASK_COUNT.eq(0), TagData.REMOTE_ID.eq(0))));
|
|
||||||
TagData data = new TagData();
|
|
||||||
for (unsavedTagData.moveToFirst(); !unsavedTagData.isAfterLast(); unsavedTagData.moveToNext()) {
|
|
||||||
data.readFromCursor(unsavedTagData);
|
|
||||||
actFmSyncService.pushTag(data.getId());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read remote tag data and merge with local
|
|
||||||
* @param serverTime last sync time
|
|
||||||
*/
|
|
||||||
private void fetchRemoteTagData(int serverTime) throws ActFmServiceException, IOException, JSONException {
|
|
||||||
actFmSyncService.fetchTags(serverTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read remote task data into remote task array
|
|
||||||
* @param serverTime last sync time
|
|
||||||
*/
|
|
||||||
private int fetchRemoteTasks(int serverTime,
|
|
||||||
ArrayList<ActFmTaskContainer> remoteTasks) throws IOException,
|
|
||||||
ActFmServiceException, JSONException {
|
|
||||||
JSONObject result;
|
|
||||||
if(serverTime == 0)
|
|
||||||
result = invoker.invoke("task_list", "active", 1);
|
|
||||||
else
|
|
||||||
result = invoker.invoke("task_list", "modified_after", serverTime);
|
|
||||||
|
|
||||||
JSONArray taskList = result.getJSONArray("list");
|
|
||||||
for(int i = 0; i < taskList.length(); i++) {
|
|
||||||
ActFmTaskContainer remote = parseRemoteTask(taskList.getJSONObject(i));
|
|
||||||
|
|
||||||
// update reminder flags for incoming remote tasks to prevent annoying
|
|
||||||
if(remote.task.hasDueDate() && remote.task.getValue(Task.DUE_DATE) < DateUtilities.now())
|
|
||||||
remote.task.setFlag(Task.REMINDER_FLAGS, Task.NOTIFY_AFTER_DEADLINE, false);
|
|
||||||
|
|
||||||
actFmDataService.findLocalMatch(remote);
|
|
||||||
|
|
||||||
remoteTasks.add(remote);
|
|
||||||
}
|
|
||||||
return result.optInt("time", 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------
|
|
||||||
// ------------------------------------------------------------ sync data
|
|
||||||
// ----------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Populate SyncData data structure
|
|
||||||
* @throws JSONException
|
|
||||||
*/
|
|
||||||
private SyncData<ActFmTaskContainer> populateSyncData(ArrayList<ActFmTaskContainer> remoteTasks) throws JSONException {
|
|
||||||
// fetch locally created tasks
|
|
||||||
TodorooCursor<Task> localCreated = actFmDataService.getLocallyCreated(Task.PROPERTIES);
|
|
||||||
|
|
||||||
// fetch locally updated tasks
|
|
||||||
TodorooCursor<Task> localUpdated = actFmDataService.getLocallyUpdated(Task.PROPERTIES);
|
|
||||||
|
|
||||||
return new SyncData<ActFmTaskContainer>(remoteTasks, localCreated, localUpdated);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------
|
|
||||||
// ------------------------------------------------- create / push / pull
|
|
||||||
// ----------------------------------------------------------------------
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected ActFmTaskContainer create(ActFmTaskContainer local) throws IOException {
|
|
||||||
return push(local, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Create a task container for the given remote task
|
|
||||||
* @throws JSONException */
|
|
||||||
private ActFmTaskContainer parseRemoteTask(JSONObject remoteTask) throws JSONException {
|
|
||||||
Task task = new Task();
|
|
||||||
|
|
||||||
ArrayList<Metadata> metadata = new ArrayList<Metadata>();
|
|
||||||
|
|
||||||
JsonHelper.taskFromJson(remoteTask, task, metadata);
|
|
||||||
ActFmTaskContainer container = new ActFmTaskContainer(task, metadata, remoteTask);
|
|
||||||
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected ActFmTaskContainer pull(ActFmTaskContainer task) throws IOException {
|
|
||||||
if(task.task.getValue(Task.REMOTE_ID) == 0)
|
|
||||||
throw new ActFmServiceException("Tried to read an invalid task"); //$NON-NLS-1$
|
|
||||||
|
|
||||||
JSONObject remote = invoker.invoke("task_show", "id", task.task.getValue(Task.REMOTE_ID));
|
|
||||||
try {
|
|
||||||
return parseRemoteTask(remote);
|
|
||||||
} catch (JSONException e) {
|
|
||||||
throw new ActFmServiceException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send changes for the given Task across the wire.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
protected ActFmTaskContainer push(ActFmTaskContainer local, ActFmTaskContainer remote) throws IOException {
|
|
||||||
long id = local.task.getValue(Task.REMOTE_ID);
|
|
||||||
|
|
||||||
actFmSyncService.pushTaskOnSave(local.task, local.task.getDatabaseValues());
|
|
||||||
|
|
||||||
// push unsaved comments
|
|
||||||
for(Metadata item : local.metadata) {
|
|
||||||
if(NoteMetadata.METADATA_KEY.equals(item.getValue(Metadata.KEY)))
|
|
||||||
if(TextUtils.isEmpty(item.getValue(NoteMetadata.EXT_ID))) {
|
|
||||||
JSONObject comment = invoker.invoke("comment_add",
|
|
||||||
"task_id", id,
|
|
||||||
"message", item.getValue(NoteMetadata.BODY));
|
|
||||||
item.setValue(NoteMetadata.EXT_ID, comment.optString("id"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return local;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void readRemotelyUpdated(SyncData<ActFmTaskContainer> data) throws IOException {
|
|
||||||
int serverTime = Preferences.getInt(ActFmPreferenceService.PREF_SERVER_TIME, 0);
|
|
||||||
ArrayList<ActFmTaskContainer> remoteTasks = new ArrayList<ActFmTaskContainer>();
|
|
||||||
|
|
||||||
try {
|
|
||||||
fetchRemoteTasks(serverTime, remoteTasks);
|
|
||||||
data.remoteUpdated = remoteTasks;
|
|
||||||
} catch (JSONException e) {
|
|
||||||
// Ingnored
|
|
||||||
}
|
|
||||||
super.readRemotelyUpdated(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------
|
|
||||||
// --------------------------------------------------------- read / write
|
|
||||||
// ----------------------------------------------------------------------
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected ActFmTaskContainer read(TodorooCursor<Task> cursor) throws IOException {
|
|
||||||
return actFmDataService.readTaskAndMetadata(cursor);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void write(ActFmTaskContainer task) throws IOException {
|
|
||||||
if(task.task.isSaved()) {
|
|
||||||
Task local = PluginServices.getTaskService().fetchById(task.task.getId(), Task.COMPLETION_DATE);
|
|
||||||
if(task.task.isCompleted() && !local.isCompleted())
|
|
||||||
StatisticsService.reportEvent(StatisticsConstants.ACTFM_TASK_COMPLETED);
|
|
||||||
} else { // Set default reminders for remotely created tasks
|
|
||||||
TaskDao.setDefaultReminders(task.task);
|
|
||||||
}
|
|
||||||
task.task.setValue(Task.LAST_SYNC, DateUtilities.now() + 1000);
|
|
||||||
actFmDataService.saveTaskAndMetadata(task);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------
|
|
||||||
// --------------------------------------------------------- misc helpers
|
|
||||||
// ----------------------------------------------------------------------
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected int matchTask(ArrayList<ActFmTaskContainer> tasks, ActFmTaskContainer target) {
|
|
||||||
int length = tasks.size();
|
|
||||||
for(int i = 0; i < length; i++) {
|
|
||||||
ActFmTaskContainer task = tasks.get(i);
|
|
||||||
if (task.task.getValue(Task.REMOTE_ID) == target.task.getValue(Task.REMOTE_ID))
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected int updateNotification(Context context, Notification notification) {
|
|
||||||
String notificationTitle = context.getString(R.string.actfm_notification_title);
|
|
||||||
Intent intent = new Intent(context, ActFmPreferences.class);
|
|
||||||
PendingIntent notificationIntent = PendingIntent.getActivity(context, 0,
|
|
||||||
intent, 0);
|
|
||||||
notification.setLatestEventInfo(context,
|
|
||||||
notificationTitle, context.getString(R.string.SyP_progress),
|
|
||||||
notificationIntent);
|
|
||||||
return Constants.NOTIFICATION_SYNC;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void transferIdentifiers(ActFmTaskContainer source,
|
|
||||||
ActFmTaskContainer destination) {
|
|
||||||
destination.task.setValue(Task.REMOTE_ID, source.task.getValue(Task.REMOTE_ID));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
Loading…
Reference in New Issue