Made significant progress on producteev sync provider. does not compile yet, but really close.

pull/14/head
Tim Su 14 years ago
parent 3c8141bd6c
commit bd37bcd567

@ -73,7 +73,7 @@ abstract public class SyncProviderPreferences extends TodorooPreferences {
private static final String PREF_ONGOING = "_ongoing"; //$NON-NLS-1$
/** Get preferences object from the context */
private static SharedPreferences getPrefs() {
protected static SharedPreferences getPrefs() {
return PreferenceManager.getDefaultSharedPreferences(ContextManager.getContext());
}

@ -1,5 +1,7 @@
package com.todoroo.astrid.producteev;
import android.content.SharedPreferences.Editor;
import com.timsu.astrid.R;
import com.todoroo.astrid.common.SyncProviderPreferences;
import com.todoroo.astrid.producteev.sync.ProducteevSyncProvider;
@ -38,5 +40,21 @@ public class ProducteevPreferences extends SyncProviderPreferences {
new ProducteevSyncProvider().signOut();
}
// --- producteev-specific preferences
private static final String PREF_SERVER_LAST_SYNC = "_last_server"; //$NON-NLS-1$
/** @return last sync date, or null if no last */
public String getLastServerSync() {
return getPrefs().getString(getIdentifier() + PREF_SERVER_LAST_SYNC, null);
}
/** Deletes Last Successful Sync Date */
public void setLastServerSync(String value) {
Editor editor = getPrefs().edit();
editor.putString(getIdentifier() + PREF_SERVER_LAST_SYNC, value);
editor.commit();
}
}

@ -2,8 +2,6 @@ package com.todoroo.astrid.producteev.api;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.SimpleTimeZone;
import com.todoroo.andlib.utility.DateUtilities;
@ -18,11 +16,8 @@ public final class ApiUtilities {
private static final SimpleDateFormat timeParser = new SimpleDateFormat(
"EEE, dd MMM yyyy HH:mm:ss Z"); //$NON-NLS-1$
static {
// read and write dates in UTC
Calendar cal = Calendar.getInstance(new SimpleTimeZone(0, "UTC")); //$NON-NLS-1$
timeParser.setCalendar(cal);
}
private static final SimpleDateFormat dateParser = new SimpleDateFormat(
"EEE, dd MMM yyyy"); //$NON-NLS-1$
/**
* Utility method to convert PDV time to unix time
@ -52,4 +47,15 @@ public final class ApiUtilities {
}
}
/**
* Utility method to convert unix date to PDV date
* @param time
* @return
*/
public static String unixDateToProducteev(long date) {
synchronized(dateParser) {
return dateParser.format(DateUtilities.unixtimeToDate(date));
}
}
}

@ -9,6 +9,7 @@ import java.security.NoSuchAlgorithmException;
import java.util.Map;
import java.util.TreeMap;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
@ -72,9 +73,9 @@ public class ProducteevInvoker {
*
* @return array tasks/view
*/
public JSONObject tasksCreate(String title, Integer idResponsible, Integer idDashboard,
public JSONArray tasksCreate(String title, Long idResponsible, Long idDashboard,
String deadline, Integer reminder, Integer status, Integer star) throws ApiServiceException, IOException {
return invokeGet("tasks/create.json",
return getResponse(invokeGet("tasks/create.json",
"token", token,
"title", title,
"id_responsible", idResponsible,
@ -82,7 +83,7 @@ public class ProducteevInvoker {
"deadline", deadline,
"reminder", reminder,
"status", status,
"star", star);
"star", star), "tasks");
}
/**
@ -93,11 +94,24 @@ public class ProducteevInvoker {
*
* @return array tasks/view
*/
public JSONObject tasksShowList(Integer idDashboard, String since) throws ApiServiceException, IOException {
return invokeGet("tasks/show_list.json",
public JSONArray tasksShowList(Long idDashboard, String since) throws ApiServiceException, IOException {
return getResponse(invokeGet("tasks/show_list.json",
"token", token,
"id_dashboard", idDashboard,
"since", since);
"since", since), "tasks");
}
/**
* get a task
*
* @param idTask
*
* @return array tasks/view
*/
public JSONArray tasksView(Long idTask) throws ApiServiceException, IOException {
return getResponse(invokeGet("tasks/view.json",
"token", token,
"id_task", idTask), "tasks");
}
/**
@ -108,11 +122,26 @@ public class ProducteevInvoker {
*
* @return array tasks/view
*/
public JSONObject tasksSetTitle(int idTask, String title) throws ApiServiceException, IOException {
return invokeGet("tasks/set_title.json",
public JSONArray tasksSetTitle(long idTask, String title) throws ApiServiceException, IOException {
return getResponse(invokeGet("tasks/set_title.json",
"token", token,
"id_task", idTask,
"title", title);
"title", title), "tasks");
}
/**
* set status of a task
*
* @param idTask
* @param status (1 = UNDONE, 2 = DONE)
*
* @return array tasks/view
*/
public JSONArray tasksSetStatus(long idTask, int status) throws ApiServiceException, IOException {
return getResponse(invokeGet("tasks/set_star.json",
"token", token,
"id_task", idTask,
"status", status), "tasks");
}
/**
@ -123,11 +152,11 @@ public class ProducteevInvoker {
*
* @return array tasks/view
*/
public JSONObject tasksSetStar(int idTask, boolean star) throws ApiServiceException, IOException {
return invokeGet("tasks/set_star.json",
public JSONArray tasksSetStar(long idTask, int star) throws ApiServiceException, IOException {
return getResponse(invokeGet("tasks/set_star.json",
"token", token,
"id_task", idTask,
"star", star);
"star", star), "tasks");
}
/**
@ -138,11 +167,11 @@ public class ProducteevInvoker {
*
* @return array tasks/view
*/
public JSONObject tasksSetDeadline(int idTask, String deadline) throws ApiServiceException, IOException {
return invokeGet("tasks/set_deadline.json",
public JSONArray tasksSetDeadline(long idTask, String deadline) throws ApiServiceException, IOException {
return getResponse(invokeGet("tasks/set_deadline.json",
"token", token,
"id_task", idTask,
"deadline", deadline);
"deadline", deadline), "tasks");
}
/**
@ -152,7 +181,7 @@ public class ProducteevInvoker {
*
* @return array with the result = (Array("stats" => Array("result" => "TRUE|FALSE"))
*/
public JSONObject tasksDelete(int idTask) throws ApiServiceException, IOException {
public JSONObject tasksDelete(long idTask) throws ApiServiceException, IOException {
return invokeGet("tasks/delete.json",
"token", token,
"id_task", idTask);
@ -165,10 +194,10 @@ public class ProducteevInvoker {
*
* @return array: list of labels/view
*/
public JSONObject tasksLabels(int idTask) throws ApiServiceException, IOException {
return invokeGet("tasks/labels.json",
public JSONArray tasksLabels(long idTask) throws ApiServiceException, IOException {
return getResponse(invokeGet("tasks/labels.json",
"token", token,
"id_task", idTask);
"id_task", idTask), "labels");
}
/**
@ -179,11 +208,11 @@ public class ProducteevInvoker {
*
* @return array: tasks/view
*/
public JSONObject tasksSetLabel(int idTask, int idLabel) throws ApiServiceException, IOException {
return invokeGet("tasks/set_label.json",
public JSONArray tasksSetLabel(long idTask, long idLabel) throws ApiServiceException, IOException {
return getResponse(invokeGet("tasks/set_label.json",
"token", token,
"id_task", idTask,
"id_label", idLabel);
"id_label", idLabel), "tasks");
}
/**
@ -194,11 +223,11 @@ public class ProducteevInvoker {
*
* @return array: tasks/view
*/
public JSONObject tasksUnsetLabel(int idTask, int idLabel) throws ApiServiceException, IOException {
return invokeGet("tasks/unset_label.json",
public JSONArray tasksUnsetLabel(long idTask, long idLabel) throws ApiServiceException, IOException {
return getResponse(invokeGet("tasks/unset_label.json",
"token", token,
"id_task", idTask,
"id_label", idLabel);
"id_label", idLabel), "tasks");
}
// --- labels
@ -211,11 +240,11 @@ public class ProducteevInvoker {
*
* @return array: labels/view
*/
public JSONObject labelsShowList(int idDashboard, String since) throws ApiServiceException, IOException {
return invokeGet("labels/show_list.json",
public JSONArray labelsShowList(long idDashboard, String since) throws ApiServiceException, IOException {
return getResponse(invokeGet("labels/show_list.json",
"token", token,
"id_dashboard", idDashboard,
"since", since);
"since", since), "labels");
}
/**
@ -226,11 +255,11 @@ public class ProducteevInvoker {
*
* @return array: labels/view
*/
public JSONObject labelsCreate(int idDashboard, String title) throws ApiServiceException, IOException {
return invokeGet("labels/create.json",
public JSONArray labelsCreate(long idDashboard, String title) throws ApiServiceException, IOException {
return getResponse(invokeGet("labels/create.json",
"token", token,
"id_dashboard", idDashboard,
"title", title);
"title", title), "labels");
}
// --- users
@ -242,7 +271,7 @@ public class ProducteevInvoker {
*
* @return array information about the user
*/
public JSONObject usersView(Integer idColleague) throws ApiServiceException, IOException {
public JSONObject usersView(Long idColleague) throws ApiServiceException, IOException {
return invokeGet("users/view.json",
"token", token,
"id_colleague", idColleague);
@ -309,4 +338,19 @@ public class ProducteevInvoker {
return requestBuilder.toString();
}
/**
* Helper method to get a field out or throw an api exception
* @param response
* @param field
* @return
* @throws ApiResponseParseException
*/
private JSONArray getResponse(JSONObject response, String field) throws ApiResponseParseException {
try {
return response.getJSONArray(field);
} catch (JSONException e) {
throw new ApiResponseParseException(e);
}
}
}

@ -0,0 +1,191 @@
/**
* See the file "LICENSE" for the full license governing this code.
*/
package com.todoroo.astrid.producteev.sync;
import java.util.ArrayList;
import java.util.Random;
import android.content.Context;
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.sql.Criterion;
import com.todoroo.andlib.sql.Join;
import com.todoroo.andlib.sql.Query;
import com.todoroo.astrid.dao.MetadataDao;
import com.todoroo.astrid.dao.MetadataDao.MetadataCriteria;
import com.todoroo.astrid.dao.TaskDao;
import com.todoroo.astrid.dao.TaskDao.TaskCriteria;
import com.todoroo.astrid.model.Metadata;
import com.todoroo.astrid.model.Task;
import com.todoroo.astrid.rmilk.sync.RTMTaskContainer;
import com.todoroo.astrid.tags.TagService;
public final class ProducteevDataService {
// --- constants
/** Utility for joining tasks with metadata */
public static final Join METADATA_JOIN = Join.left(Metadata.TABLE, Task.ID.eq(Metadata.TASK));
// --- singleton
private static ProducteevDataService instance = null;
public static synchronized ProducteevDataService getInstance() {
if(instance == null)
instance = new ProducteevDataService(ContextManager.getContext());
return instance;
}
// --- instance variables
protected final Context context;
@Autowired
private TaskDao taskDao;
@Autowired
private MetadataDao metadataDao;
static final Random random = new Random();
private ProducteevDataService(Context context) {
this.context = context;
DependencyInjectionService.getInstance().inject(this);
}
// --- task and metadata methods
/**
* Clears RTM metadata information. Used when user logs out of RTM
*/
public void clearMetadata() {
metadataDao.deleteWhere(Metadata.KEY.eq(ProducteevTask.METADATA_KEY));
}
/**
* Gets tasks that were created since last sync
* @param properties
* @return
*/
public TodorooCursor<Task> getLocallyCreated(Property<?>[] properties) {
return
taskDao.query(Query.select(properties).join(ProducteevDataService.METADATA_JOIN).where(Criterion.and(
Criterion.not(Task.ID.in(Query.select(Metadata.TASK).from(Metadata.TABLE).
where(Criterion.and(MetadataCriteria.withKey(ProducteevTask.METADATA_KEY), ProducteevTask.TASK_SERIES_ID.gt(0))))),
TaskCriteria.isActive())));
}
/**
* Gets tasks that were modified since last sync
* @param properties
* @return null if never sync'd
*/
public TodorooCursor<Task> getLocallyUpdated(Property<?>[] properties) {
long lastSyncDate = ProducteevUtilities.getLastSyncDate();
if(lastSyncDate == 0)
return taskDao.query(Query.select(Task.ID).where(Criterion.none));
return
taskDao.query(Query.select(properties).join(ProducteevDataService.METADATA_JOIN).
where(Criterion.and(MetadataCriteria.withKey(ProducteevTask.METADATA_KEY),
Task.MODIFICATION_DATE.gt(lastSyncDate))));
}
/**
* Searches for a local task with same remote id, updates this task's id
* @param remoteTask
*/
public void findLocalMatch(RTMTaskContainer remoteTask) {
if(remoteTask.task.getId() != Task.NO_ID)
return;
TodorooCursor<Task> cursor = taskDao.query(Query.select(Task.ID).
join(ProducteevDataService.METADATA_JOIN).where(Criterion.and(MetadataCriteria.withKey(ProducteevTask.METADATA_KEY),
ProducteevTask.TASK_SERIES_ID.eq(remoteTask.taskSeriesId),
ProducteevTask.TASK_ID.eq(remoteTask.taskId))));
try {
if(cursor.getCount() == 0)
return;
cursor.moveToFirst();
remoteTask.task.setId(cursor.get(Task.ID));
} finally {
cursor.close();
}
}
/**
* Saves a task and its metadata
* @param task
*/
public void saveTaskAndMetadata(ProducteevTaskContainer task) {
taskDao.save(task.task, true);
metadataDao.deleteWhere(Criterion.and(MetadataCriteria.byTask(task.task.getId()),
Criterion.or(MetadataCriteria.withKey(ProducteevTask.METADATA_KEY),
MetadataCriteria.withKey(ProducteevNote.METADATA_KEY),
MetadataCriteria.withKey(TagService.KEY))));
task.metadata.add(ProducteevTask.create(task));
for(Metadata metadata : task.metadata) {
metadata.setValue(Metadata.TASK, task.task.getId());
metadataDao.createNew(metadata);
}
}
/**
* Reads a task and its metadata
* @param task
* @return
*/
public ProducteevTaskContainer readTaskAndMetadata(TodorooCursor<Task> taskCursor) {
Task task = new Task(taskCursor);
// read tags, notes, etc
ArrayList<Metadata> metadata = new ArrayList<Metadata>();
TodorooCursor<Metadata> metadataCursor = metadataDao.query(Query.select(Metadata.PROPERTIES).
where(Criterion.and(MetadataCriteria.byTask(task.getId()),
Criterion.or(MetadataCriteria.withKey(TagService.KEY),
MetadataCriteria.withKey(ProducteevTask.METADATA_KEY),
MetadataCriteria.withKey(ProducteevNote.METADATA_KEY)))));
try {
for(metadataCursor.moveToFirst(); !metadataCursor.isAfterLast(); metadataCursor.moveToNext()) {
metadata.add(new Metadata(metadataCursor));
}
} finally {
metadataCursor.close();
}
return new RTMTaskContainer(task, metadata);
}
/**
* Reads metadata out of a task
* @return null if no metadata found
*/
public Metadata getTaskMetadata(long taskId) {
TodorooCursor<Metadata> cursor = metadataDao.query(Query.select(
ProducteevTask.LIST_ID, ProducteevTask.TASK_SERIES_ID, ProducteevTask.TASK_ID, ProducteevTask.REPEATING).where(
MetadataCriteria.byTaskAndwithKey(taskId, ProducteevTask.METADATA_KEY)));
try {
if(cursor.getCount() == 0)
return null;
cursor.moveToFirst();
return new Metadata(cursor);
} finally {
cursor.close();
}
}
/**
* Reads task notes out of a task
*/
public TodorooCursor<Metadata> getTaskNotesCursor(long taskId) {
TodorooCursor<Metadata> cursor = metadataDao.query(Query.select(Metadata.PROPERTIES).
where(MetadataCriteria.byTaskAndwithKey(taskId, ProducteevNote.METADATA_KEY)));
return cursor;
}
}

@ -0,0 +1,45 @@
package com.todoroo.astrid.producteev.sync;
import org.json.JSONObject;
import com.todoroo.andlib.data.Property.LongProperty;
import com.todoroo.andlib.data.Property.StringProperty;
import com.todoroo.astrid.model.Metadata;
import com.todoroo.astrid.producteev.api.ApiUtilities;
/**
* Metadata entries for a Producteev note. The first Producteev note becomes
* Astrid's note field, subsequent notes are stored in metadata in this
* format.
*
* @author Tim Su <tim@todoroo.com>
*
*/
public class ProducteevNote {
/** metadata key */
public static final String METADATA_KEY = "producteev-note"; //$NON-NLS-1$
/** note id */
public static final LongProperty ID = new LongProperty(Metadata.TABLE,
Metadata.VALUE1.name);
/** note message */
public static final StringProperty MESSAGE = Metadata.VALUE2;
/** note creation date */
public static final LongProperty CREATED = new LongProperty(Metadata.TABLE,
Metadata.VALUE3.name);
@SuppressWarnings("nls")
public static Metadata create(JSONObject note) {
Metadata metadata = new Metadata();
metadata.setValue(Metadata.KEY, METADATA_KEY);
metadata.setValue(ID, note.optLong("id_note"));
metadata.setValue(MESSAGE, note.optString("message"));
metadata.setValue(CREATED, ApiUtilities.producteevToUnixTime(
note.optString("time_create"), 0));
return metadata;
}
}

@ -5,14 +5,19 @@ package com.todoroo.astrid.producteev.sync;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.app.Notification;
import android.app.PendingIntent;
import android.content.Context;
import android.content.res.Resources;
import android.content.Intent;
import android.text.TextUtils;
import android.widget.Toast;
import com.flurry.android.FlurryAgent;
import com.timsu.astrid.R;
@ -22,24 +27,37 @@ 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.utility.AndroidUtilities;
import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.andlib.utility.DialogUtilities;
import com.todoroo.astrid.api.SynchronizationProvider;
import com.todoroo.astrid.api.TaskContainer;
import com.todoroo.astrid.model.Metadata;
import com.todoroo.astrid.model.Task;
import com.todoroo.astrid.producteev.ProducteevPreferences;
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.rmilk.MilkUtilities;
import com.todoroo.astrid.rmilk.api.ServiceInternalException;
import com.todoroo.astrid.rmilk.data.MilkDataService;
import com.todoroo.astrid.rmilk.api.data.RtmTaskNote;
import com.todoroo.astrid.rmilk.data.MilkNote;
import com.todoroo.astrid.service.AstridDependencyInjector;
import com.todoroo.astrid.tags.TagService;
import com.todoroo.astrid.utility.Preferences;
@SuppressWarnings("nls")
public class ProducteevSyncProvider extends SynchronizationProvider<ProducteevTaskContainer> {
private MilkDataService dataService = null;
private ProducteevDataService dataService = null;
private ProducteevInvoker invoker = null;
private int defaultDashboard;
private long defaultDashboard;
private final ProducteevPreferences preferences = new ProducteevPreferences();
/** map of producteev labels to id's */
private final HashMap<String, Long> labelMap = new HashMap<String, Long>();
static {
AstridDependencyInjector.initialize();
}
@ -60,13 +78,13 @@ public class ProducteevSyncProvider extends SynchronizationProvider<ProducteevTa
// ----------------------------------------------------------------------
/**
* Sign out of RTM, deleting all synchronization metadata
* Sign out of service, deleting all synchronization metadata
*/
public void signOut() {
preferences.setToken(null);
preferences.clearLastSyncDate();
dataService.clearMetadata(); // TODO clear metadata
dataService.clearMetadata();
}
// ----------------------------------------------------------------------
@ -117,20 +135,17 @@ public class ProducteevSyncProvider extends SynchronizationProvider<ProducteevTa
@Override
protected void initiate(Context context) {
dataService = MilkDataService.getInstance();
dataService = ProducteevDataService.getInstance();
// authenticate the user. this will automatically call the next step
// authenticate(context);
Toast.makeText(context, "hi! this is empty", Toast.LENGTH_LONG);
authenticate();
}
/**
* Perform authentication with RTM. Will open the SyncBrowser if necessary
*/
@SuppressWarnings("nls")
private void authenticate(final Context context) {
final Resources r = context.getResources();
private void authenticate() {
FlurryAgent.onEvent("producteev-started");
preferences.recordSyncStart();
@ -150,6 +165,8 @@ public class ProducteevSyncProvider extends SynchronizationProvider<ProducteevTa
if(authToken == null) {
String email = Preferences.getStringValue(R.string.producteev_PPr_email);
String password = Preferences.getStringValue(R.string.producteev_PPr_password);
email = "astrid@todoroo.com";
password = "astrid";
invoker.authenticate(email, password);
}
@ -173,11 +190,11 @@ public class ProducteevSyncProvider extends SynchronizationProvider<ProducteevTa
try {
// load user information
JSONObject user = invoker.usersView(null);
defaultDashboard = user.getJSONObject("user").getInt("default_dashboard");
defaultDashboard = user.getJSONObject("user").getLong("default_dashboard");
// read all tasks
JSONObject tasks = invoker.tasksShowList(defaultDashboard,
null); // TODO
JSONArray tasks = invoker.tasksShowList(defaultDashboard,
preferences.getLastServerSync());
SyncData<ProducteevTaskContainer> syncData = populateSyncData(tasks);
try {
@ -210,77 +227,300 @@ public class ProducteevSyncProvider extends SynchronizationProvider<ProducteevTa
Task.CREATION_DATE,
Task.COMPLETION_DATE,
Task.DELETION_DATE,
Task.REMINDER_FLAGS,
Task.NOTES,
};
/**
* Populate SyncData data structure
* @throws JSONException
*/
private SyncData<ProducteevTaskContainer> populateSyncData(JSONObject tasks) {
@SuppressWarnings("nls")
private SyncData<ProducteevTaskContainer> populateSyncData(JSONArray tasks) 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>(tasks, localCreated, localUpdated);
return null;
// read json response
ArrayList<ProducteevTaskContainer> remoteTasks = new ArrayList<ProducteevTaskContainer>(tasks.length());
for(int i = 0; i < tasks.length(); i++)
remoteTasks.add(parseRemoteTask(tasks.getJSONObject(i)));
return new SyncData<ProducteevTaskContainer>(remoteTasks, localCreated, localUpdated);
}
// ----------------------------------------------------------------------
// ------------------------------------------------- create / push / pull
// ----------------------------------------------------------------------
@Override
protected void create(ProducteevTaskContainer task) throws IOException {
Task local = task.task;
Long dashboard = null;
if(task.pdvTask.containsNonNullValue(ProducteevTask.DASHBOARD_ID))
dashboard = task.pdvTask.getValue(ProducteevTask.DASHBOARD_ID);
JSONArray response = invoker.tasksCreate(local.getValue(Task.TITLE),
null, dashboard, createDeadline(local), createReminder(local),
local.isCompleted() ? 2 : 1, createStars(local));
if(response.length() != 1)
throw new ApiServiceException("Unexpected # of tasks created: " + response.length());
ProducteevTaskContainer newRemoteTask;
try {
newRemoteTask = parseRemoteTask(response.getJSONObject(0));
} catch (JSONException e) {
throw new ApiResponseParseException(e);
}
transferIdentifiers(newRemoteTask, task);
}
/** Create a task container for the given RtmTaskSeries
* @throws JSONException */
private ProducteevTaskContainer parseRemoteTask(JSONObject remoteTask) throws JSONException {
Task task = new Task();
ArrayList<Metadata> metadata = new ArrayList<Metadata>();
task.setValue(Task.TITLE, 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);
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, label.getString("title"));
metadata.add(tagData);
}
// ----------------------------------------------------------------------
// ------------------------------------------------------- helper classes
// ----------------------------------------------------------------------
JSONArray notes = remoteTask.getJSONArray("notes");
for(int i = notes.length() - 1; i >= 0; i--) {
JSONObject note = notes.getJSONObject(i).getJSONObject("note");
if(i == notes.length() - 1)
task.setValue(Task.NOTES, note.getString("message"));
else
metadata.add(ProducteevNote.create(note));
}
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)_)));
ProducteevTaskContainer container = new ProducteevTaskContainer(task, metadata, remoteTask);
return container;
}
@Override
protected void updateNotification(Context context, Notification n) {
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$
JSONArray tasks = invoker.tasksView(task.pdvTask.getValue(ProducteevTask.ID));
if(tasks.length() == 0)
return null;
try {
return parseRemoteTask(tasks.getJSONObject(0));
} 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 void create(ProducteevTaskContainer task) throws IOException {
protected void push(ProducteevTaskContainer local, ProducteevTaskContainer remote) throws IOException {
boolean remerge = false;
// fetch remote task for comparison
if(remote == null)
remote = pull(local);
long idTask = local.pdvTask.getValue(ProducteevTask.ID);
// TODO handle task workspace switching
// 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
create(local);
}
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))
invoker.tasksSetDeadline(idTask, createDeadline(local.task));
if(shouldTransmit(local, Task.COMPLETION_DATE, remote))
invoker.tasksSetStatus(idTask, local.task.isCompleted() ? 2 : 1);
// tags
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)) {
String[] tags = localTags.toArray(new String[localTags.size()]);
rtmService.tasks_setTags(timeline, listId, taskSeriesId,
taskId, tags);
}
// notes
if(shouldTransmit(local, Task.NOTES, remote)) {
String[] titleAndText = MilkNote.fromNoteField(local.task.getValue(Task.NOTES));
List<RtmTaskNote> notes = null;
if(remote != null && remote.pdvTask.getNotes() != null)
notes = remote.pdvTask.getNotes().getNotes();
if(notes != null && notes.size() > 0) {
String remoteNoteId = notes.get(0).getId();
rtmService.tasks_notes_edit(timeline, remoteNoteId, titleAndText[0],
titleAndText[1]);
} else {
rtmService.tasks_notes_add(timeline, listId, taskSeriesId,
taskId, titleAndText[0], titleAndText[1]);
}
}
if(remerge) {
remote = pull(local);
remote.task.setId(local.task.getId());
write(remote);
}
}
// ----------------------------------------------------------------------
// --------------------------------------------------------- read / write
// ----------------------------------------------------------------------
@Override
protected void push(ProducteevTaskContainer task,
ProducteevTaskContainer remote) throws IOException {
protected ProducteevTaskContainer read(TodorooCursor<Task> cursor) throws IOException {
return dataService.readTaskAndMetadata(cursor);
}
@Override
protected ProducteevTaskContainer pull(ProducteevTaskContainer task)
throws IOException {
return null;
protected void write(ProducteevTaskContainer task) throws IOException {
dataService.saveTaskAndMetadata(task);
}
// ----------------------------------------------------------------------
// --------------------------------------------------------- misc helpers
// ----------------------------------------------------------------------
@Override
protected ProducteevTaskContainer read(TodorooCursor<Task> task)
throws IOException {
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(AndroidUtilities.equals(task.pdvTask, target.pdvTask))
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;
}
@Override
protected void write(ProducteevTaskContainer task) throws IOException {
/**
* get deadline in producteev format
* @param task
* @return
*/
private String createDeadline(Task task) {
if(!task.hasDueDate())
return null;
if(!task.hasDueTime())
return ApiUtilities.unixDateToProducteev(task.getValue(Task.DUE_DATE));
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(TaskContainer task, Property<?> property, TaskContainer 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 matchTask(ArrayList<ProducteevTaskContainer> tasks,
ProducteevTaskContainer target) {
return 0;
protected void 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 ;
}
@Override
protected void transferIdentifiers(ProducteevTaskContainer source,
ProducteevTaskContainer destination) {
destination.pdvTask = source.pdvTask;
}
// ----------------------------------------------------------------------
// ------------------------------------------------------- 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)_)));
}
}

@ -1,8 +1,7 @@
package com.todoroo.astrid.producteev;
package com.todoroo.astrid.producteev.sync;
import com.todoroo.andlib.data.Property.LongProperty;
import com.todoroo.astrid.model.Metadata;
import com.todoroo.astrid.producteev.sync.ProducteevTaskContainer;
/**
* Metadata entries for a Remember The Milk Task
@ -22,17 +21,4 @@ public class ProducteevTask {
public static final LongProperty DASHBOARD_ID = new LongProperty(Metadata.TABLE,
Metadata.VALUE2.name);
/**
* Creates a piece of metadata from a remote task
* @param rtmTaskSeries
* @return
*/
public static Metadata create(ProducteevTaskContainer container) {
Metadata metadata = new Metadata();
metadata.setValue(Metadata.KEY, METADATA_KEY);
metadata.setValue(ProducteevTask.ID, container.id);
return metadata;
}
}

@ -3,10 +3,11 @@ package com.todoroo.astrid.producteev.sync;
import java.util.ArrayList;
import java.util.Iterator;
import org.json.JSONObject;
import com.todoroo.astrid.api.TaskContainer;
import com.todoroo.astrid.model.Metadata;
import com.todoroo.astrid.model.Task;
import com.todoroo.astrid.producteev.ProducteevTask;
/**
* RTM Task Container
@ -15,34 +16,38 @@ import com.todoroo.astrid.producteev.ProducteevTask;
*
*/
public class ProducteevTaskContainer extends TaskContainer {
public long id;
public long dashboard;
public ProducteevTaskContainer(Task task, ArrayList<Metadata> metadata, long id, long dashboard) {
public Metadata pdvTask;
public ProducteevTaskContainer(Task task, ArrayList<Metadata> metadata, Metadata remote) {
this.task = task;
this.metadata = metadata;
this.id = id;
this.dashboard = dashboard;
this.pdvTask = remote;
if(this.pdvTask == null)
this.pdvTask = new Metadata();
}
// public ProducteevTaskContainer(Task task, ArrayList<Metadata> metadata,
// RtmTaskSeries rtmTaskSeries) {
// this(task, metadata, );
// }
@SuppressWarnings("nls")
public ProducteevTaskContainer(Task task, ArrayList<Metadata> metadata, JSONObject remoteTask) {
this(task, metadata, new Metadata());
pdvTask.setValue(ProducteevTask.ID, remoteTask.optLong("id_task"));
pdvTask.setValue(ProducteevTask.DASHBOARD_ID, remoteTask.optLong("id_dashboard"));
}
public ProducteevTaskContainer(Task task, ArrayList<Metadata> metadata) {
this(task, metadata, 0, 0);
this.task = task;
this.metadata = metadata;
for(Iterator<Metadata> iterator = metadata.iterator(); iterator.hasNext(); ) {
Metadata item = iterator.next();
if(ProducteevTask.METADATA_KEY.equals(item.getValue(Metadata.KEY))) {
if(item.containsNonNullValue(ProducteevTask.ID))
id = item.getValue(ProducteevTask.ID);
if(item.containsNonNullValue(ProducteevTask.DASHBOARD_ID))
dashboard = item.getValue(ProducteevTask.DASHBOARD_ID);
pdvTask = item;
iterator.remove();
break;
}
}
if(this.pdvTask == null)
this.pdvTask = new Metadata();
}

@ -6,6 +6,12 @@
<!-- Preferences Title: Producteev -->
<string name="producteev_PPr_header">Producteev</string>
<!-- ================================================ Synchronization == -->
<!-- title for notification tray when synchronizing -->
<string name="producteev_notification_title">Astrid: Producteev</string>
</resources>

Loading…
Cancel
Save