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.
811 lines
34 KiB
Java
811 lines
34 KiB
Java
/**
|
|
* See the file "LICENSE" for the full license governing this code.
|
|
*/
|
|
package com.todoroo.astrid.producteev.sync;
|
|
|
|
import java.io.IOException;
|
|
import java.util.ArrayList;
|
|
import java.util.HashMap;
|
|
import java.util.HashSet;
|
|
|
|
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.R;
|
|
import com.todoroo.andlib.data.Property;
|
|
import com.todoroo.andlib.data.TodorooCursor;
|
|
import com.todoroo.andlib.service.Autowired;
|
|
import com.todoroo.andlib.service.ContextManager;
|
|
import com.todoroo.andlib.service.DependencyInjectionService;
|
|
import com.todoroo.andlib.service.ExceptionService;
|
|
import com.todoroo.andlib.service.NotificationManager;
|
|
import com.todoroo.andlib.sql.Criterion;
|
|
import com.todoroo.andlib.utility.AndroidUtilities;
|
|
import com.todoroo.andlib.utility.DateUtilities;
|
|
import com.todoroo.andlib.utility.Preferences;
|
|
import com.todoroo.astrid.activity.ShortcutActivity;
|
|
import com.todoroo.astrid.api.AstridApiConstants;
|
|
import com.todoroo.astrid.api.Filter;
|
|
import com.todoroo.astrid.core.PluginServices;
|
|
import com.todoroo.astrid.dao.TaskDao;
|
|
import com.todoroo.astrid.data.Metadata;
|
|
import com.todoroo.astrid.data.StoreObject;
|
|
import com.todoroo.astrid.data.Task;
|
|
import com.todoroo.astrid.notes.NoteMetadata;
|
|
import com.todoroo.astrid.producteev.ProducteevBackgroundService;
|
|
import com.todoroo.astrid.producteev.ProducteevFilterExposer;
|
|
import com.todoroo.astrid.producteev.ProducteevLoginActivity;
|
|
import com.todoroo.astrid.producteev.ProducteevPreferences;
|
|
import com.todoroo.astrid.producteev.ProducteevUtilities;
|
|
import com.todoroo.astrid.producteev.api.ApiResponseParseException;
|
|
import com.todoroo.astrid.producteev.api.ApiServiceException;
|
|
import com.todoroo.astrid.producteev.api.ApiUtilities;
|
|
import com.todoroo.astrid.producteev.api.ProducteevInvoker;
|
|
import com.todoroo.astrid.service.AstridDependencyInjector;
|
|
import com.todoroo.astrid.service.StatisticsConstants;
|
|
import com.todoroo.astrid.service.StatisticsService;
|
|
import com.todoroo.astrid.sync.SyncContainer;
|
|
import com.todoroo.astrid.sync.SyncProvider;
|
|
import com.todoroo.astrid.sync.SyncProviderUtilities;
|
|
import com.todoroo.astrid.tags.TagService;
|
|
import com.todoroo.astrid.utility.Constants;
|
|
|
|
@SuppressWarnings("nls")
|
|
public class ProducteevSyncProvider extends SyncProvider<ProducteevTaskContainer> {
|
|
|
|
private static final long TASK_ID_UNSYNCED = 1L;
|
|
private ProducteevDataService dataService = null;
|
|
private ProducteevInvoker invoker = null;
|
|
private final ProducteevUtilities preferences = ProducteevUtilities.INSTANCE;
|
|
|
|
/** producteev user id. set during sync */
|
|
private long userId;
|
|
|
|
/** map of producteev dashboard id + label name to id's */
|
|
private final HashMap<String, Long> labelMap = new HashMap<String, Long>();
|
|
|
|
static {
|
|
AstridDependencyInjector.initialize();
|
|
}
|
|
|
|
@Autowired
|
|
protected ExceptionService exceptionService;
|
|
|
|
public ProducteevSyncProvider() {
|
|
super();
|
|
DependencyInjectionService.getInstance().inject(this);
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
// ------------------------------------------------------ utility methods
|
|
// ----------------------------------------------------------------------
|
|
|
|
/**
|
|
* Sign out of service, deleting all synchronization metadata
|
|
*/
|
|
public void signOut() {
|
|
preferences.setToken(null);
|
|
Preferences.setString(R.string.producteev_PPr_email, null);
|
|
Preferences.setString(R.string.producteev_PPr_password, null);
|
|
Preferences.setString(ProducteevUtilities.PREF_SERVER_LAST_SYNC, null);
|
|
Preferences.setStringFromInteger(R.string.producteev_PPr_defaultdash_key,
|
|
ProducteevUtilities.DASHBOARD_DEFAULT);
|
|
preferences.clearLastSyncDate();
|
|
|
|
dataService = ProducteevDataService.getInstance();
|
|
dataService.clearMetadata();
|
|
}
|
|
|
|
@Override
|
|
protected SyncProviderUtilities getUtilities() {
|
|
return ProducteevUtilities.INSTANCE;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
// ------------------------------------------------------ initiating sync
|
|
// ----------------------------------------------------------------------
|
|
|
|
/**
|
|
* initiate sync in background
|
|
*/
|
|
@Override
|
|
protected void initiateBackground() {
|
|
dataService = ProducteevDataService.getInstance();
|
|
|
|
try {
|
|
String authToken = preferences.getToken();
|
|
invoker = getInvoker();
|
|
|
|
String email = Preferences.getStringValue(R.string.producteev_PPr_email);
|
|
String password = Preferences.getStringValue(R.string.producteev_PPr_password);
|
|
|
|
// check if we have a token & it works
|
|
if(authToken != null) {
|
|
invoker.setCredentials(authToken, email, password);
|
|
performSync();
|
|
} else {
|
|
if (email == null && password == null) {
|
|
// we can't do anything, user is not logged in
|
|
} else {
|
|
invoker.authenticate(email, password);
|
|
preferences.setToken(invoker.getToken());
|
|
performSync();
|
|
}
|
|
}
|
|
} catch (IllegalStateException e) {
|
|
// occurs when application was closed
|
|
} catch (Exception e) {
|
|
handleException("pdv-authenticate", e, true);
|
|
} finally {
|
|
preferences.stopOngoing();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* If user isn't already signed in, show sign in dialog. Else perform sync.
|
|
*/
|
|
@Override
|
|
protected void initiateManual(Activity activity) {
|
|
String authToken = preferences.getToken();
|
|
ProducteevUtilities.INSTANCE.stopOngoing();
|
|
|
|
// check if we have a token & it works
|
|
if(authToken == null) {
|
|
// display login-activity
|
|
Intent intent = new Intent(activity, ProducteevLoginActivity.class);
|
|
activity.startActivityForResult(intent, 0);
|
|
} else {
|
|
activity.startService(new Intent(null, null,
|
|
activity, ProducteevBackgroundService.class));
|
|
}
|
|
}
|
|
|
|
public static ProducteevInvoker getInvoker() {
|
|
String z = stripslashes(0, "71o3346pr40o5o4nt4n7t6n287t4op28","2");
|
|
String v = stripslashes(2, "9641n76n9s1736q1578q1o1337q19233","4ae");
|
|
return new ProducteevInvoker(z, v);
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
// ----------------------------------------------------- synchronization!
|
|
// ----------------------------------------------------------------------
|
|
|
|
protected void performSync() {
|
|
preferences.recordSyncStart();
|
|
String syncSuccess = "failed";
|
|
|
|
try {
|
|
// load user information
|
|
JSONObject user = invoker.usersView(null).getJSONObject("user");
|
|
saveUserData(user);
|
|
|
|
String lastServerSync = Preferences.getStringValue(ProducteevUtilities.PREF_SERVER_LAST_SYNC);
|
|
|
|
// read dashboards
|
|
JSONArray dashboards = invoker.dashboardsShowList(lastServerSync);
|
|
dataService.updateDashboards(dashboards);
|
|
|
|
// read labels and tasks for each dashboard
|
|
ArrayList<ProducteevTaskContainer> remoteTasks = new ArrayList<ProducteevTaskContainer>();
|
|
for(StoreObject dashboard : dataService.getDashboards()) {
|
|
long dashboardId = dashboard.getValue(ProducteevDashboard.REMOTE_ID);
|
|
JSONArray labels = invoker.labelsShowList(dashboardId, null);
|
|
readLabels(labels);
|
|
|
|
try {
|
|
// This invocation throws ApiServiceException for workspaces that need to be upgraded
|
|
JSONArray tasks = invoker.tasksShowList(dashboardId, lastServerSync);
|
|
for(int i = 0; i < tasks.length(); i++) {
|
|
ProducteevTaskContainer remote = parseRemoteTask(tasks.getJSONObject(i));
|
|
|
|
if(remote.pdvTask.getValue(ProducteevTask.CREATOR_ID) != userId &&
|
|
remote.pdvTask.getValue(ProducteevTask.RESPONSIBLE_ID) != userId)
|
|
remote.task.setFlag(Task.FLAGS, Task.FLAG_IS_READONLY, true);
|
|
else
|
|
remote.task.setFlag(Task.FLAGS, Task.FLAG_IS_READONLY, false);
|
|
|
|
// 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);
|
|
|
|
dataService.findLocalMatch(remote);
|
|
|
|
remoteTasks.add(remote);
|
|
}
|
|
} catch (ApiServiceException ase) {
|
|
// catch it here, so that other dashboards can still be synchronized
|
|
handleException("pdv-sync", ase, true); //$NON-NLS-1$
|
|
}
|
|
}
|
|
|
|
SyncData<ProducteevTaskContainer> syncData = populateSyncData(remoteTasks);
|
|
try {
|
|
synchronizeTasks(syncData);
|
|
AndroidUtilities.sleepDeep(3000L);
|
|
checkForCreatedDuringSync();
|
|
} finally {
|
|
syncData.localCreated.close();
|
|
syncData.localUpdated.close();
|
|
}
|
|
|
|
Preferences.setString(ProducteevUtilities.PREF_SERVER_LAST_SYNC, invoker.time());
|
|
preferences.recordSuccessfulSync();
|
|
|
|
Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_EVENT_REFRESH);
|
|
ContextManager.getContext().sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ);
|
|
|
|
// notification/activities stuff
|
|
processNotifications();
|
|
|
|
syncSuccess = getFinalSyncStatus();
|
|
} catch (IllegalStateException e) {
|
|
// occurs when application was closed
|
|
} catch (Exception e) {
|
|
handleException("pdv-sync", e, true); //$NON-NLS-1$
|
|
} finally {
|
|
StatisticsService.reportEvent(StatisticsConstants.PDV_SYNC_FINISHED,
|
|
"success", syncSuccess); //$NON-NLS-1$
|
|
}
|
|
}
|
|
|
|
private void checkForCreatedDuringSync() {
|
|
TodorooCursor<Task> localCreated = dataService.getLocallyCreated(PROPERTIES);
|
|
try {
|
|
SyncData<ProducteevTaskContainer> localCreatedData = new SyncData<ProducteevTaskContainer>(null, localCreated, null);
|
|
sendLocallyCreated(localCreatedData, new HashMap<String, Integer>());
|
|
} catch (IOException e) {
|
|
handleException("gtasks-sync", e, true);
|
|
} finally {
|
|
localCreated.close();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param activities
|
|
* @return
|
|
* @throws JSONException
|
|
*/
|
|
private String[] parseActivities(JSONArray activities) throws JSONException {
|
|
int count = (activities == null ? 0 : activities.length());
|
|
String[] activitiesList = new String[count];
|
|
if(activities == null)
|
|
return activitiesList;
|
|
for(int i = 0; i < activities.length(); i++) {
|
|
String message = activities.getJSONObject(i).getJSONObject("activity").getString("message");
|
|
activitiesList[i] = ApiUtilities.decode(message);
|
|
}
|
|
return activitiesList;
|
|
}
|
|
|
|
|
|
// ----------------------------------------------------------------------
|
|
// ------------------------------------------------------------ sync data
|
|
// ----------------------------------------------------------------------
|
|
|
|
private void saveUserData(JSONObject user) throws JSONException {
|
|
long defaultDashboard = user.getLong("default_dashboard");
|
|
userId = user.getLong("id_user");
|
|
Preferences.setLong(ProducteevUtilities.PREF_DEFAULT_DASHBOARD, defaultDashboard);
|
|
Preferences.setLong(ProducteevUtilities.PREF_USER_ID, userId);
|
|
|
|
// save the default dashboard preference if unset
|
|
int defaultDashSetting = Preferences.getIntegerFromString(R.string.producteev_PPr_defaultdash_key,
|
|
ProducteevUtilities.DASHBOARD_DEFAULT);
|
|
if(defaultDashSetting == ProducteevUtilities.DASHBOARD_DEFAULT)
|
|
Preferences.setStringFromInteger(R.string.producteev_PPr_defaultdash_key, (int) defaultDashboard);
|
|
}
|
|
|
|
// all synchronized properties
|
|
private static final Property<?>[] PROPERTIES = new Property<?>[] {
|
|
Task.ID,
|
|
Task.TITLE,
|
|
Task.IMPORTANCE,
|
|
Task.DUE_DATE,
|
|
Task.CREATION_DATE,
|
|
Task.COMPLETION_DATE,
|
|
Task.DELETION_DATE,
|
|
Task.REMINDER_FLAGS,
|
|
Task.NOTES,
|
|
Task.RECURRENCE
|
|
};
|
|
|
|
/**
|
|
* Populate SyncData data structure
|
|
* @throws JSONException
|
|
*/
|
|
private SyncData<ProducteevTaskContainer> populateSyncData(ArrayList<ProducteevTaskContainer> remoteTasks) throws JSONException {
|
|
// fetch locally created tasks
|
|
TodorooCursor<Task> localCreated = dataService.getLocallyCreated(PROPERTIES);
|
|
|
|
// fetch locally updated tasks
|
|
TodorooCursor<Task> localUpdated = dataService.getLocallyUpdated(PROPERTIES);
|
|
|
|
return new SyncData<ProducteevTaskContainer>(remoteTasks, localCreated, localUpdated);
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
// ------------------------------------------------- create / push / pull
|
|
// ----------------------------------------------------------------------
|
|
|
|
@Override
|
|
protected ProducteevTaskContainer create(ProducteevTaskContainer local) throws IOException {
|
|
Task localTask = local.task;
|
|
long dashboard = ProducteevUtilities.INSTANCE.getDefaultDashboard();
|
|
if(local.pdvTask.containsNonNullValue(ProducteevTask.DASHBOARD_ID))
|
|
dashboard = local.pdvTask.getValue(ProducteevTask.DASHBOARD_ID);
|
|
long responsibleId = local.pdvTask.getValue(ProducteevTask.RESPONSIBLE_ID);
|
|
|
|
if(dashboard == ProducteevUtilities.DASHBOARD_NO_SYNC) {
|
|
// set a bogus task id, then return without creating
|
|
local.pdvTask.setValue(ProducteevTask.ID, TASK_ID_UNSYNCED);
|
|
return local;
|
|
}
|
|
|
|
JSONObject response = invoker.tasksCreate(localTask.getValue(Task.TITLE),
|
|
responsibleId, dashboard, createDeadline(localTask), createReminder(localTask),
|
|
localTask.isCompleted() ? 2 : 1, createStars(localTask));
|
|
ProducteevTaskContainer newRemoteTask;
|
|
try {
|
|
newRemoteTask = parseRemoteTask(response);
|
|
} catch (JSONException e) {
|
|
throw new ApiResponseParseException(e);
|
|
}
|
|
transferIdentifiers(newRemoteTask, local);
|
|
push(local, newRemoteTask);
|
|
return newRemoteTask;
|
|
}
|
|
|
|
/** Create a task container for the given ProducteevTask
|
|
* @throws JSONException */
|
|
private ProducteevTaskContainer parseRemoteTask(JSONObject remoteTask) throws JSONException {
|
|
Task task = new Task();
|
|
|
|
ArrayList<Metadata> metadata = new ArrayList<Metadata>();
|
|
|
|
if(remoteTask.has("task"))
|
|
remoteTask = remoteTask.getJSONObject("task");
|
|
|
|
task.setValue(Task.TITLE, ApiUtilities.decode(remoteTask.getString("title")));
|
|
task.setValue(Task.CREATION_DATE, ApiUtilities.producteevToUnixTime(remoteTask.getString("time_created"), 0));
|
|
task.setValue(Task.COMPLETION_DATE, remoteTask.getInt("status") == 2 ? DateUtilities.now() : 0);
|
|
task.setValue(Task.DELETION_DATE, remoteTask.getInt("deleted") == 1 ? DateUtilities.now() : 0);
|
|
|
|
long dueDate = ApiUtilities.producteevToUnixTime(remoteTask.getString("deadline"), 0);
|
|
if(remoteTask.optInt("all_day", 0) == 1)
|
|
task.setValue(Task.DUE_DATE, Task.createDueDate(Task.URGENCY_SPECIFIC_DAY, dueDate));
|
|
else
|
|
task.setValue(Task.DUE_DATE, Task.createDueDate(Task.URGENCY_SPECIFIC_DAY_TIME, dueDate));
|
|
task.setValue(Task.IMPORTANCE, 5 - remoteTask.getInt("star"));
|
|
|
|
JSONArray labels = remoteTask.getJSONArray("labels");
|
|
for(int i = 0; i < labels.length(); i++) {
|
|
JSONObject label = labels.getJSONObject(i).getJSONObject("label");
|
|
if(label.getInt("deleted") != 0)
|
|
continue;
|
|
|
|
Metadata tagData = new Metadata();
|
|
tagData.setValue(Metadata.KEY, TagService.KEY);
|
|
tagData.setValue(TagService.TAG, ApiUtilities.decode(label.getString("title")));
|
|
metadata.add(tagData);
|
|
}
|
|
|
|
ProducteevTaskContainer container = new ProducteevTaskContainer(task, metadata, remoteTask);
|
|
|
|
JSONArray notes = remoteTask.getJSONArray("notes");
|
|
for(int i = notes.length() - 1; i >= 0; i--) {
|
|
JSONObject note = notes.getJSONObject(i).getJSONObject("note");
|
|
PluginServices.getMetadataService().deleteWhere(Criterion.and(Metadata.KEY.eq(NoteMetadata.METADATA_KEY),
|
|
NoteMetadata.EXT_ID.eq(note.getString("id_note"))));
|
|
if(note.getLong("deleted") != 0)
|
|
continue;
|
|
|
|
long creator = note.getLong("id_creator");
|
|
metadata.add(ApiUtilities.createNoteMetadata(note, creatorName(container, creator)));
|
|
}
|
|
|
|
return container;
|
|
}
|
|
|
|
private String creatorName(ProducteevTaskContainer container, long creator) {
|
|
StoreObject[] dashboards = dataService.getDashboards();
|
|
for(int i = 0; i < dashboards.length; i++) {
|
|
Long dashboard = container.pdvTask.getValue(ProducteevTask.DASHBOARD_ID);
|
|
if(dashboard.equals(dashboards[i].getValue(ProducteevDashboard.REMOTE_ID))) {
|
|
return ProducteevDashboard.getUserFromDashboard(dashboards[i], creator);
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
protected ProducteevTaskContainer pull(ProducteevTaskContainer task) throws IOException {
|
|
if(!task.pdvTask.containsNonNullValue(ProducteevTask.ID))
|
|
throw new ApiServiceException("Tried to read an invalid task"); //$NON-NLS-1$
|
|
|
|
JSONObject remote = invoker.tasksView(task.pdvTask.getValue(ProducteevTask.ID));
|
|
try {
|
|
return parseRemoteTask(remote);
|
|
} catch (JSONException e) {
|
|
throw new ApiResponseParseException(e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Send changes for the given Task across the wire. If a remoteTask is
|
|
* supplied, we attempt to intelligently only transmit the values that
|
|
* have changed.
|
|
*/
|
|
@Override
|
|
protected ProducteevTaskContainer push(ProducteevTaskContainer local, ProducteevTaskContainer remote) throws IOException {
|
|
boolean remerge = false;
|
|
|
|
long idTask = local.pdvTask.getValue(ProducteevTask.ID);
|
|
long idDashboard = local.pdvTask.getValue(ProducteevTask.DASHBOARD_ID);
|
|
long idResponsible = local.pdvTask.getValue(ProducteevTask.RESPONSIBLE_ID);
|
|
|
|
// if local is marked do not sync, handle accordingly
|
|
if(idDashboard == ProducteevUtilities.DASHBOARD_NO_SYNC) {
|
|
return local;
|
|
}
|
|
|
|
// fetch remote task for comparison
|
|
if(remote == null)
|
|
remote = pull(local);
|
|
|
|
// either delete or re-create if necessary
|
|
if(shouldTransmit(local, Task.DELETION_DATE, remote)) {
|
|
if(local.task.getValue(Task.DELETION_DATE) > 0)
|
|
invoker.tasksDelete(idTask);
|
|
else {
|
|
// if we create, we transfer identifiers to old remote
|
|
// in case it is used by caller for other purposes
|
|
ProducteevTaskContainer newRemote = create(local);
|
|
transferIdentifiers(newRemote, remote);
|
|
remote = newRemote;
|
|
}
|
|
}
|
|
|
|
// dashboard
|
|
if(remote != null && idDashboard != remote.pdvTask.getValue(ProducteevTask.DASHBOARD_ID)) {
|
|
invoker.tasksSetWorkspace(idTask, idDashboard);
|
|
remote = pull(local);
|
|
} else if(remote == null && idTask == TASK_ID_UNSYNCED) {
|
|
// was un-synced, create remote
|
|
remote = create(local);
|
|
}
|
|
|
|
// responsible
|
|
if(remote != null && idResponsible !=
|
|
remote.pdvTask.getValue(ProducteevTask.RESPONSIBLE_ID)) {
|
|
invoker.tasksSetResponsible(idTask, idResponsible);
|
|
}
|
|
|
|
// core properties
|
|
if(shouldTransmit(local, Task.TITLE, remote))
|
|
invoker.tasksSetTitle(idTask, local.task.getValue(Task.TITLE));
|
|
if(shouldTransmit(local, Task.IMPORTANCE, remote))
|
|
invoker.tasksSetStar(idTask, createStars(local.task));
|
|
if(shouldTransmit(local, Task.DUE_DATE, remote)) {
|
|
if(local.task.hasDueDate())
|
|
invoker.tasksSetDeadline(idTask, createDeadline(local.task), local.task.hasDueTime() ? 0 : 1);
|
|
else
|
|
invoker.tasksUnsetDeadline(idTask);
|
|
}
|
|
|
|
boolean isPDVRepeating = ((local.pdvTask.containsNonNullValue(ProducteevTask.REPEATING_SETTING) &&
|
|
local.pdvTask.getValue(ProducteevTask.REPEATING_SETTING).length()>0) ||
|
|
(remote != null && remote.pdvTask.containsNonNullValue(ProducteevTask.REPEATING_SETTING) &&
|
|
remote.pdvTask.getValue(ProducteevTask.REPEATING_SETTING).length()>0));
|
|
|
|
boolean isAstridRepeating = local.task.containsNonNullValue(Task.RECURRENCE) &&
|
|
local.task.getValue(Task.RECURRENCE).length() > 0;
|
|
|
|
if (isAstridRepeating && isPDVRepeating) {
|
|
// Astrid-repeat overrides PDV-repeat
|
|
invoker.tasksUnsetRepeating(idTask);
|
|
}
|
|
|
|
if(shouldTransmit(local, Task.COMPLETION_DATE, remote)) {
|
|
invoker.tasksSetStatus(idTask, local.task.isCompleted() ? 2 : 1);
|
|
if (local.task.isCompleted() && !isAstridRepeating &&
|
|
isPDVRepeating) {
|
|
local.task.setValue(Task.COMPLETION_DATE, 0L);
|
|
remerge = true;
|
|
}
|
|
}
|
|
|
|
|
|
try {
|
|
// tags
|
|
transmitTags(local, remote, idTask, idDashboard);
|
|
|
|
// notes
|
|
if(!TextUtils.isEmpty(local.task.getValue(Task.NOTES))) {
|
|
String note = local.task.getValue(Task.NOTES);
|
|
JSONObject result = invoker.tasksNoteCreate(idTask, note);
|
|
local.metadata.add(ApiUtilities.createNoteMetadata(result.getJSONObject("note"), null));
|
|
local.task.setValue(Task.NOTES, "");
|
|
}
|
|
|
|
remote = pull(local);
|
|
remote.task.setId(local.task.getId());
|
|
if(remerge) {
|
|
// transform local into remote
|
|
local.task = remote.task;
|
|
local.pdvTask.setValue(ProducteevTask.ID, remote.pdvTask.getValue(ProducteevTask.ID));
|
|
local.pdvTask.setValue(ProducteevTask.DASHBOARD_ID, remote.pdvTask.getValue(ProducteevTask.DASHBOARD_ID));
|
|
local.pdvTask.setValue(ProducteevTask.CREATOR_ID, remote.pdvTask.getValue(ProducteevTask.CREATOR_ID));
|
|
local.pdvTask.setValue(ProducteevTask.RESPONSIBLE_ID, remote.pdvTask.getValue(ProducteevTask.RESPONSIBLE_ID));
|
|
if(remote.pdvTask.containsNonNullValue(ProducteevTask.REPEATING_SETTING))
|
|
local.pdvTask.setValue(ProducteevTask.REPEATING_SETTING, remote.pdvTask.getValue(ProducteevTask.REPEATING_SETTING));
|
|
}
|
|
|
|
return remote;
|
|
} catch (JSONException e) {
|
|
throw new ApiResponseParseException(e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Transmit tags
|
|
*
|
|
* @param local
|
|
* @param remote
|
|
* @param idTask
|
|
* @param idDashboard
|
|
* @throws ApiServiceException
|
|
* @throws JSONException
|
|
* @throws IOException
|
|
*/
|
|
private void transmitTags(ProducteevTaskContainer local,
|
|
ProducteevTaskContainer remote, long idTask, long idDashboard) throws ApiServiceException, JSONException, IOException {
|
|
HashSet<String> localTags = new HashSet<String>();
|
|
HashSet<String> remoteTags = new HashSet<String>();
|
|
for(Metadata item : local.metadata)
|
|
if(TagService.KEY.equals(item.getValue(Metadata.KEY)))
|
|
localTags.add(item.getValue(TagService.TAG));
|
|
if(remote != null && remote.metadata != null) {
|
|
for(Metadata item : remote.metadata)
|
|
if(TagService.KEY.equals(item.getValue(Metadata.KEY)))
|
|
remoteTags.add(item.getValue(TagService.TAG));
|
|
}
|
|
|
|
if(!localTags.equals(remoteTags)) {
|
|
long[] labels = new long[localTags.size()];
|
|
int index = 0;
|
|
for(String label : localTags) {
|
|
String pdvLabel = idDashboard + label;
|
|
final long id;
|
|
if(!labelMap.containsKey(pdvLabel)) {
|
|
JSONObject result = invoker.labelsCreate(idDashboard, label).getJSONObject("label");
|
|
id = putLabelIntoCache(result);
|
|
} else
|
|
id = labelMap.get(pdvLabel);
|
|
labels[index++] = id;
|
|
}
|
|
invoker.tasksChangeLabel(idTask, labels);
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
// --------------------------------------------------------- read / write
|
|
// ----------------------------------------------------------------------
|
|
|
|
@Override
|
|
protected ProducteevTaskContainer read(TodorooCursor<Task> cursor) throws IOException {
|
|
return dataService.readTaskAndMetadata(cursor);
|
|
}
|
|
|
|
@Override
|
|
protected void write(ProducteevTaskContainer task) throws IOException {
|
|
if(task.task.isSaved()) {
|
|
Task local = PluginServices.getTaskService().fetchById(task.task.getId(), Task.COMPLETION_DATE, Task.FLAGS);
|
|
task.task.setFlag(Task.FLAGS, Task.FLAG_REPEAT_AFTER_COMPLETION, local.getFlag(Task.FLAGS, Task.FLAG_REPEAT_AFTER_COMPLETION));
|
|
if(task.task.isCompleted() && !local.isCompleted())
|
|
StatisticsService.reportEvent(StatisticsConstants.PDV_TASK_COMPLETED);
|
|
} else { // Set default reminders for remotely created tasks
|
|
TaskDao.setDefaultReminders(task.task);
|
|
}
|
|
dataService.saveTaskAndMetadata(task);
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
// --------------------------------------------------------- misc helpers
|
|
// ----------------------------------------------------------------------
|
|
|
|
@Override
|
|
protected int matchTask(ArrayList<ProducteevTaskContainer> tasks, ProducteevTaskContainer target) {
|
|
int length = tasks.size();
|
|
for(int i = 0; i < length; i++) {
|
|
ProducteevTaskContainer task = tasks.get(i);
|
|
if (target.pdvTask.containsNonNullValue(ProducteevTask.ID) &&
|
|
task.pdvTask.getValue(ProducteevTask.ID).equals(target.pdvTask.getValue(ProducteevTask.ID)))
|
|
return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* get stars in producteev format
|
|
* @param local
|
|
* @return
|
|
*/
|
|
private Integer createStars(Task local) {
|
|
return 5 - local.getValue(Task.IMPORTANCE);
|
|
}
|
|
|
|
/**
|
|
* get reminder in producteev format
|
|
* @param local
|
|
* @return
|
|
*/
|
|
private Integer createReminder(Task local) {
|
|
if(local.getFlag(Task.REMINDER_FLAGS, Task.NOTIFY_AT_DEADLINE))
|
|
return 8;
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* get deadline in producteev format
|
|
* @param task
|
|
* @return
|
|
*/
|
|
private String createDeadline(Task task) {
|
|
if(!task.hasDueDate())
|
|
return "";
|
|
return ApiUtilities.unixTimeToProducteev(task.getValue(Task.DUE_DATE));
|
|
}
|
|
|
|
/**
|
|
* Determine whether this task's property should be transmitted
|
|
* @param task task to consider
|
|
* @param property property to consider
|
|
* @param remoteTask remote task proxy
|
|
* @return
|
|
*/
|
|
private boolean shouldTransmit(SyncContainer task, Property<?> property, SyncContainer remoteTask) {
|
|
if(!task.task.containsValue(property))
|
|
return false;
|
|
|
|
if(remoteTask == null)
|
|
return true;
|
|
if(!remoteTask.task.containsValue(property))
|
|
return true;
|
|
|
|
// special cases - match if they're zero or nonzero
|
|
if(property == Task.COMPLETION_DATE ||
|
|
property == Task.DELETION_DATE)
|
|
return !AndroidUtilities.equals((Long)task.task.getValue(property) == 0,
|
|
(Long)remoteTask.task.getValue(property) == 0);
|
|
|
|
return !AndroidUtilities.equals(task.task.getValue(property),
|
|
remoteTask.task.getValue(property));
|
|
}
|
|
|
|
@Override
|
|
protected int updateNotification(Context context, Notification notification) {
|
|
String notificationTitle = context.getString(R.string.producteev_notification_title);
|
|
Intent intent = new Intent(context, ProducteevPreferences.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(ProducteevTaskContainer source,
|
|
ProducteevTaskContainer destination) {
|
|
destination.pdvTask = source.pdvTask;
|
|
}
|
|
|
|
/**
|
|
* Read labels into label map
|
|
* @param dashboardId
|
|
* @throws JSONException
|
|
* @throws ApiServiceException
|
|
* @throws IOException
|
|
*/
|
|
private void readLabels(JSONArray labels) throws JSONException, ApiServiceException, IOException {
|
|
for(int i = 0; i < labels.length(); i++) {
|
|
JSONObject label = labels.getJSONObject(i).getJSONObject("label");
|
|
putLabelIntoCache(label);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Puts a single label into the cache
|
|
* @param dashboardId
|
|
* @param label
|
|
* @throws JSONException
|
|
*/
|
|
private long putLabelIntoCache(JSONObject label)
|
|
throws JSONException {
|
|
String name = ApiUtilities.decode(label.getString("title"));
|
|
long dashboard = label.getLong("id_dashboard");
|
|
labelMap.put(dashboard + name, label.getLong("id_label"));
|
|
return label.getLong("id_label");
|
|
}
|
|
|
|
/**
|
|
* Show workspace notifications
|
|
*
|
|
* @throws ApiResponseParseException
|
|
* @throws ApiServiceException
|
|
* @throws IOException
|
|
* @throws JSONException
|
|
*/
|
|
private void processNotifications() throws ApiResponseParseException,
|
|
ApiServiceException, IOException, JSONException {
|
|
String lastNotificationId = Preferences.getStringValue(ProducteevUtilities.PREF_SERVER_LAST_NOTIFICATION);
|
|
String lastActivityId = Preferences.getStringValue(ProducteevUtilities.PREF_SERVER_LAST_ACTIVITY);
|
|
JSONArray notifications = invoker.activitiesShowNotifications(null, (lastNotificationId == null ? null : new Long(lastNotificationId)));
|
|
String[] notificationsList = parseActivities(notifications);
|
|
// update lastIds
|
|
if (notifications.length() > 0) {
|
|
lastNotificationId = ""+notifications.getJSONObject(0).getJSONObject("activity").getLong("id_activity");
|
|
}
|
|
|
|
// display notifications from producteev-log
|
|
Context context = ContextManager.getContext();
|
|
final NotificationManager nm = new NotificationManager.AndroidNotificationManager(context);
|
|
for (int i = 0; i< notificationsList.length; i++) {
|
|
long id_dashboard = notifications.getJSONObject(i).getJSONObject("activity").getLong("id_dashboard");
|
|
String dashboardName = null;
|
|
StoreObject[] dashboardsData = ProducteevDataService.getInstance().getDashboards();
|
|
ProducteevDashboard dashboard = null;
|
|
if (dashboardsData != null) {
|
|
for (int j=0; i<dashboardsData.length;i++) {
|
|
long id = dashboardsData[j].getValue(ProducteevDashboard.REMOTE_ID);
|
|
if (id == id_dashboard) {
|
|
dashboardName = dashboardsData[j].getValue(ProducteevDashboard.NAME);
|
|
dashboard = new ProducteevDashboard(id, dashboardName, null);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
// it seems dashboard is null if we get a notification about an unknown dashboard, just filter it.
|
|
if (dashboard != null) {
|
|
// initialize notification
|
|
int icon = R.drawable.ic_producteev_notification;
|
|
long when = System.currentTimeMillis();
|
|
Notification notification = new Notification(icon, null, when);
|
|
CharSequence contentTitle = context.getString(R.string.producteev_notification_title)+": "+dashboard.getName();
|
|
|
|
Filter filter = ProducteevFilterExposer.filterFromList(context, dashboard, userId);
|
|
Intent notificationIntent = ShortcutActivity.createIntent(filter);
|
|
|
|
// filter the tags from the message
|
|
String message = notificationsList[i].replaceAll("<[^>]+>", "");
|
|
PendingIntent contentIntent = PendingIntent.getActivity(context, 0, notificationIntent, 0);
|
|
notification.setLatestEventInfo(context, contentTitle, message, contentIntent);
|
|
|
|
nm.notify(Constants.NOTIFICATION_PRODUCTEEV_NOTIFICATIONS-i, notification);
|
|
}
|
|
}
|
|
|
|
// store lastIds in Preferences
|
|
Preferences.setString(ProducteevUtilities.PREF_SERVER_LAST_NOTIFICATION, lastNotificationId);
|
|
Preferences.setString(ProducteevUtilities.PREF_SERVER_LAST_ACTIVITY, lastActivityId);
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
// ------------------------------------------------------- helper methods
|
|
// ----------------------------------------------------------------------
|
|
|
|
private static final String stripslashes(int ____,String __,String ___) {
|
|
int _=__.charAt(____/92);_=_==116?_-1:_;_=((_>=97)&&(_<=123)?
|
|
((_-83)%27+97):_);return TextUtils.htmlEncode(____==31?___:
|
|
stripslashes(____+1,__.substring(1),___+((char)_)));
|
|
}
|
|
}
|