mirror of https://github.com/tasks/tasks
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
317 lines
12 KiB
Java
317 lines
12 KiB
Java
/**
|
|
* 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.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.data.Metadata;
|
|
import com.todoroo.astrid.data.Task;
|
|
import com.todoroo.astrid.notes.NoteMetadata;
|
|
import com.todoroo.astrid.service.AstridDependencyInjector;
|
|
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;
|
|
|
|
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, true);
|
|
} 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();
|
|
|
|
try {
|
|
int serverTime = Preferences.getInt(ActFmPreferenceService.PREF_SERVER_TIME, 0);
|
|
ArrayList<ActFmTaskContainer> remoteTasks = new ArrayList<ActFmTaskContainer>();
|
|
|
|
serverTime = (int)(fetchRemoteTasks(serverTime, remoteTasks) - DateUtilities.now()/1000L);
|
|
|
|
fetchRemoteTagData(serverTime);
|
|
|
|
SyncData<ActFmTaskContainer> syncData = populateSyncData(remoteTasks);
|
|
|
|
try {
|
|
synchronizeTasks(syncData);
|
|
} finally {
|
|
syncData.localCreated.close();
|
|
syncData.localUpdated.close();
|
|
}
|
|
|
|
serverTime += DateUtilities.now()/1000L;
|
|
Preferences.setInt(ActFmPreferenceService.PREF_SERVER_TIME, serverTime);
|
|
actFmPreferenceService.recordSuccessfulSync();
|
|
|
|
StatisticsService.reportEvent("actfm-sync-finished"); //$NON-NLS-1$
|
|
|
|
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, true); //$NON-NLS-1$
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Read remote tag data and merge with local
|
|
* @param serverTime last sync time
|
|
*/
|
|
private void fetchRemoteTagData(int serverTime) throws ActFmServiceException, IOException, JSONException {
|
|
actFmSyncService.fetchTags();
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
// --------------------------------------------------------- read / write
|
|
// ----------------------------------------------------------------------
|
|
|
|
@Override
|
|
protected ActFmTaskContainer read(TodorooCursor<Task> cursor) throws IOException {
|
|
return actFmDataService.readTaskAndMetadata(cursor);
|
|
}
|
|
|
|
@Override
|
|
protected void write(ActFmTaskContainer task) throws IOException {
|
|
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));
|
|
}
|
|
|
|
}
|