Added Producteev code, but it doesn't work yet

pull/14/head
Tim Su 14 years ago
parent 375bc26423
commit 62d55bf50e

@ -0,0 +1,193 @@
package com.todoroo.astrid.producteev;
import java.util.Date;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.DialogInterface.OnClickListener;
import android.content.res.Resources;
import android.graphics.Color;
import android.os.Bundle;
import android.preference.Preference;
import android.preference.Preference.OnPreferenceClickListener;
import android.view.View;
import android.view.ViewGroup.OnHierarchyChangeListener;
import com.timsu.astrid.R;
import com.todoroo.andlib.service.Autowired;
import com.todoroo.andlib.utility.AndroidUtilities;
import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.andlib.utility.DialogUtilities;
import com.todoroo.andlib.widget.TodorooPreferences;
import com.todoroo.astrid.rmilk.sync.RTMSyncProvider;
/**
* Displays synchronization preferences and an action panel so users can
* initiate actions from the menu.
*
* @author timsu
*
*/
public class ProducteevPreferences extends TodorooPreferences {
@Autowired
private DialogUtilities dialogUtilities;
private int statusColor = Color.BLACK;
@Override
public int getPreferenceResource() {
return R.xml.preferences_rmilk;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getListView().setOnHierarchyChangeListener(new OnHierarchyChangeListener() {
@Override
public void onChildViewRemoved(View parent, View child) {
//
}
@Override
public void onChildViewAdded(View parent, View child) {
View view = findViewById(R.id.status);
if(view != null)
view.setBackgroundColor(statusColor);
}
});
}
@Override
protected void onPause() {
super.onPause();
MilkBackgroundService.scheduleService();
}
/**
*
* @param resource
* if null, updates all resources
*/
@Override
public void updatePreferences(Preference preference, Object value) {
final Resources r = getResources();
// interval
if (r.getString(R.string.rmilk_MPr_interval_key).equals(
preference.getKey())) {
int index = AndroidUtilities.indexOf(
r.getStringArray(R.array.rmilk_MPr_interval_values),
(String) value);
if (index <= 0)
preference.setSummary(R.string.rmilk_MPr_interval_desc_disabled);
else
preference.setSummary(r.getString(
R.string.rmilk_MPr_interval_desc,
r.getStringArray(R.array.rmilk_MPr_interval_entries)[index]));
}
// status
else if (r.getString(R.string.rmilk_MPr_status_key).equals(preference.getKey())) {
boolean loggedIn = ProducteevUtilities.isLoggedIn();
String status;
String subtitle = ""; //$NON-NLS-1$
// ! logged in - display message, click -> sync
if(!loggedIn) {
status = r.getString(R.string.rmilk_status_loggedout);
statusColor = Color.RED;
preference.setOnPreferenceClickListener(new OnPreferenceClickListener() {
public boolean onPreferenceClick(Preference p) {
startService(new Intent(ProducteevPreferences.this, MilkBackgroundService.class));
finish();
return true;
}
});
}
// sync is occurring
else if(ProducteevUtilities.isOngoing()) {
status = r.getString(R.string.rmilk_status_ongoing);
statusColor = Color.rgb(0, 0, 100);
}
// last sync was error
else if(ProducteevUtilities.getLastAttemptedSyncDate() != 0) {
status = r.getString(R.string.rmilk_status_failed,
DateUtilities.getDateWithTimeFormat(ProducteevPreferences.this).
format(new Date(ProducteevUtilities.getLastAttemptedSyncDate())));
if(ProducteevUtilities.getLastSyncDate() > 0) {
subtitle = r.getString(R.string.rmilk_status_failed_subtitle,
DateUtilities.getDateWithTimeFormat(ProducteevPreferences.this).
format(new Date(ProducteevUtilities.getLastSyncDate())));
}
statusColor = Color.rgb(100, 0, 0);
preference.setOnPreferenceClickListener(new OnPreferenceClickListener() {
public boolean onPreferenceClick(Preference p) {
String error = ProducteevUtilities.getLastError();
if(error != null)
dialogUtilities.okDialog(ProducteevPreferences.this, error, null);
return true;
}
});
} else if(ProducteevUtilities.getLastSyncDate() > 0) {
status = r.getString(R.string.rmilk_status_success,
DateUtilities.getDateWithTimeFormat(ProducteevPreferences.this).
format(new Date(ProducteevUtilities.getLastSyncDate())));
statusColor = Color.rgb(0, 100, 0);
} else {
status = r.getString(R.string.rmilk_status_never);
statusColor = Color.rgb(0, 0, 100);
preference.setOnPreferenceClickListener(new OnPreferenceClickListener() {
public boolean onPreferenceClick(Preference p) {
startService(new Intent(ProducteevPreferences.this, MilkBackgroundService.class));
finish();
return true;
}
});
}
preference.setTitle(status);
preference.setSummary(subtitle);
View view = findViewById(R.id.status);
if(view != null)
view.setBackgroundColor(statusColor);
}
// sync button
else if (r.getString(R.string.rmilk_MPr_sync_key).equals(preference.getKey())) {
boolean loggedIn = ProducteevUtilities.isLoggedIn();
preference.setOnPreferenceClickListener(new OnPreferenceClickListener() {
public boolean onPreferenceClick(Preference p) {
new RTMSyncProvider().synchronize(ProducteevPreferences.this);
finish();
return true;
}
});
if(!loggedIn)
preference.setTitle(R.string.rmilk_MPr_sync_log_in);
}
// log out button
else if (r.getString(R.string.rmilk_MPr_forget_key).equals(preference.getKey())) {
boolean loggedIn = ProducteevUtilities.isLoggedIn();
preference.setOnPreferenceClickListener(new OnPreferenceClickListener() {
public boolean onPreferenceClick(Preference p) {
dialogUtilities.okCancelDialog(ProducteevPreferences.this,
r.getString(R.string.rmilk_forget_confirm), new OnClickListener() {
@Override
public void onClick(DialogInterface dialog,
int which) {
new RTMSyncProvider().signOut();
initializePreference(getPreferenceScreen());
}
}, null);
return true;
}
});
if(!loggedIn)
preference.setEnabled(false);
}
}
}

@ -0,0 +1,39 @@
package com.todoroo.astrid.producteev;
import com.todoroo.andlib.data.Property.LongProperty;
import com.todoroo.astrid.model.Metadata;
import com.todoroo.astrid.producteev.sync.ProducteevTaskContainer;
import com.todoroo.astrid.rmilk.data.MilkList;
/**
* Metadata entries for a Remember The Milk Task
* @author Tim Su <tim@todoroo.com>
*
*/
public class ProducteevTask {
/** metadata key */
public static final String METADATA_KEY = "producteev"; //$NON-NLS-1$
/** {@link MilkList} id */
public static final LongProperty ID = new LongProperty(Metadata.TABLE,
Metadata.VALUE1.name);
/** RTM Task Series Id */
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;
}
}

@ -0,0 +1,150 @@
/**
* See the file "LICENSE" for the full license governing this code.
*/
package com.todoroo.astrid.producteev;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.preference.PreferenceManager;
import com.timsu.astrid.R;
import com.todoroo.andlib.service.ContextManager;
import com.todoroo.andlib.utility.DateUtilities;
/**
* Constants and preferences for rtm plugin
*
* @author Tim Su <tim@todoroo.com>
*
*/
@SuppressWarnings("nls")
public class ProducteevUtilities {
// --- constants
/** add-on identifier */
public static final String IDENTIFIER = "producteev";
// --- Preference Keys
private static final String PREF_TOKEN = "pdv_token";
private static final String PREF_LAST_SYNC = "pdv_last_sync";
private static final String PREF_LAST_SYNC_SERVER = "pdv_last_sync_server";
private static final String PREF_LAST_ATTEMPTED_SYNC = "pdv_last_attempted";
private static final String PREF_LAST_ERROR = "pdv_last_error";
private static final String PREF_ONGOING = "pdv_ongoing";
// --- Preference Utility Methods
/** Get preferences object from the context */
private static SharedPreferences getPrefs() {
return PreferenceManager.getDefaultSharedPreferences(ContextManager.getContext());
}
/**
* @return true if we have a token for this user, false otherwise
*/
public static boolean isLoggedIn() {
return getPrefs().getString(PREF_TOKEN, null) != null;
}
/** authentication token, or null if doesn't exist */
public static String getToken() {
return getPrefs().getString(PREF_TOKEN, null);
}
/** Sets the authentication token. Set to null to clear. */
public static void setToken(String setting) {
Editor editor = getPrefs().edit();
editor.putString(PREF_TOKEN, setting);
editor.commit();
}
/** @return Last Successful Sync Date, or 0 */
public static long getLastSyncDate() {
return getPrefs().getLong(PREF_LAST_SYNC, 0);
}
/** @return Last Attempted Sync Date, or 0 if it was successful */
public static long getLastAttemptedSyncDate() {
return getPrefs().getLong(PREF_LAST_ATTEMPTED_SYNC, 0);
}
/** @return Last Error, or null if no last error */
public static String getLastError() {
return getPrefs().getString(PREF_LAST_ERROR, null);
}
/** @return Last sync time according to producteev, or null */
public static String getLastServerSyncTime() {
return getPrefs().getString(PREF_LAST_SYNC_SERVER, null);
}
/** @return Last Error, or null if no last error */
public static boolean isOngoing() {
return getPrefs().getBoolean(PREF_ONGOING, false);
}
/** Deletes Last Successful Sync Date */
public static void clearLastSyncDate() {
Editor editor = getPrefs().edit();
editor.remove(PREF_LAST_SYNC);
editor.commit();
}
/** Set Last Successful Sync Date */
public static void setLastError(String error) {
Editor editor = getPrefs().edit();
editor.putString(PREF_LAST_ERROR, error);
editor.commit();
}
/** Set Ongoing */
public static void stopOngoing() {
Editor editor = getPrefs().edit();
editor.putBoolean(PREF_ONGOING, false);
editor.commit();
}
/** Set Last Successful Sync Date */
public static void recordSuccessfulSync(String producteevTime) {
Editor editor = getPrefs().edit();
editor.putLong(PREF_LAST_SYNC, DateUtilities.now());
editor.putLong(PREF_LAST_ATTEMPTED_SYNC, 0);
editor.putString(PREF_LAST_SYNC_SERVER, producteevTime);
editor.commit();
}
/** Set Last Attempted Sync Date */
public static void recordSyncStart() {
Editor editor = getPrefs().edit();
editor.putLong(PREF_LAST_ATTEMPTED_SYNC, DateUtilities.now());
editor.putString(PREF_LAST_ERROR, null);
editor.putBoolean(PREF_ONGOING, true);
editor.commit();
}
/**
* Reads the frequency, in seconds, auto-sync should occur.
*
* @return seconds duration, or 0 if not desired
*/
public static int getSyncAutoSyncFrequency() {
String value = getPrefs().getString(
ContextManager.getContext().getString(
R.string.rmilk_MPr_interval_key), null);
if (value == null)
return 0;
try {
return Integer.parseInt(value);
} catch (Exception e) {
return 0;
}
}
}

@ -0,0 +1,19 @@
package com.todoroo.astrid.producteev.api;
/**
* Exception that wraps an exception encountered during API invocation or
* processing.
*
* @author timsu
*
*/
public class ApiResponseParseException extends ApiServiceException {
private static final long serialVersionUID = 5421855785088364483L;
public ApiResponseParseException(Throwable throwable) {
super("Exception reading API response: " + throwable.getMessage()); //$NON-NLS-1$
initCause(throwable);
}
}

@ -0,0 +1,35 @@
package com.todoroo.astrid.producteev.api;
import java.io.IOException;
/**
* Exception that wraps an exception encountered during API invocation or
* processing.
*
* @author timsu
*
*/
public class ApiServiceException extends IOException {
private static final long serialVersionUID = 8805573304840404684L;
public ApiServiceException(String detailMessage) {
super(detailMessage);
}
public ApiServiceException(Throwable throwable) {
super(throwable.getMessage());
initCause(throwable);
}
public ApiServiceException() {
super();
}
@Override
public String toString() {
return getClass().getSimpleName() + ": " + getMessage(); //$NON-NLS-1$
}
}

@ -0,0 +1,55 @@
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;
/**
* Utilities for working with API responses and JSON objects
*
* @author timsu
*
*/
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);
}
/**
* Utility method to convert PDV time to unix time
*
* @param date
* @param defaultValue
* @return
*/
public static long producteevToUnixTime(String value, long defaultValue) {
synchronized(timeParser) {
try {
return DateUtilities.dateToUnixtime(timeParser.parse(value));
} catch (ParseException e) {
return defaultValue;
}
}
}
/**
* Utility method to convert unix time to PDV time
* @param time
* @return
*/
public static String unixTimeToProducteev(long time) {
synchronized(timeParser) {
return timeParser.format(DateUtilities.unixtimeToDate(time));
}
}
}

@ -0,0 +1,312 @@
package com.todoroo.astrid.producteev.api;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Map;
import java.util.TreeMap;
import org.json.JSONException;
import org.json.JSONObject;
import com.todoroo.andlib.service.RestClient;
@SuppressWarnings("nls")
public class ProducteevInvoker {
private final String URL = "https://api.producteev.com/";
private final String apiKey;
private final String apiSecret;
private String token = null;
/**
* Create new producteev service
* @param apiKey
* @param apiSecret
*/
public ProducteevInvoker(String apiKey, String apiSecret) {
this.apiKey = apiKey;
this.apiSecret = apiSecret;
}
// --- authentication
/**
* Authenticate the given user
*/
public void authenticate(String email, String password) throws IOException, ApiServiceException {
JSONObject response = invokeGet("users/login.json",
"email", email, "password", password);
try {
token = response.getJSONObject("login").getString("token");
} catch (JSONException e) {
throw new ApiServiceException(e);
}
}
public boolean hasToken() {
return token != null;
}
public void setToken(String token) {
this.token = token;
}
// --- tasks
/**
* create a task
*
* @param title
* @param idResponsible (optional)
* @param idDashboard (optional);
* @param deadline (optional)
* @param reminder (optional) 0 = "None", 1 = "5 minutes before", 2 = "15 minutes before", 3 = "30 minutes before", 4 = "1 hour before", 5 = "2 hours before", 6 = "1 day before", 7 = "2 days before", 8 = "on date of event"
* @param status (optional) (1 = UNDONE, 2 = DONE)
* @param star (optional) (0 to 5 stars)
*
* @return array tasks/view
*/
public JSONObject tasksCreate(String title, Integer idResponsible, Integer idDashboard,
String deadline, Integer reminder, Integer status, Integer star) throws ApiServiceException, IOException {
return invokeGet("tasks/create.json",
"token", token,
"title", title,
"id_responsible", idResponsible,
"id_dashboard", idDashboard,
"deadline", deadline,
"reminder", reminder,
"status", status,
"star", star);
}
/**
* show list
*
* @param idResponsible (optional) if null return every task for current user
* @param since (optional) if not null, the function only returns tasks modified or created since this date
*
* @return array tasks/view
*/
public JSONObject tasksShowList(Integer idDashboard, String since) throws ApiServiceException, IOException {
return invokeGet("tasks/show_list.json",
"token", token,
"id_dashboard", idDashboard,
"since", since);
}
/**
* change title of a task
*
* @param idTask
* @param title
*
* @return array tasks/view
*/
public JSONObject tasksSetTitle(int idTask, String title) throws ApiServiceException, IOException {
return invokeGet("tasks/set_title.json",
"token", token,
"id_task", idTask,
"title", title);
}
/**
* set star status of a task
*
* @param idTask
* @param star (0 to 5 stars)
*
* @return array tasks/view
*/
public JSONObject tasksSetStar(int idTask, boolean star) throws ApiServiceException, IOException {
return invokeGet("tasks/set_star.json",
"token", token,
"id_task", idTask,
"star", star);
}
/**
* set a deadline
*
* @param idTask
* @param deadline
*
* @return array tasks/view
*/
public JSONObject tasksSetDeadline(int idTask, String deadline) throws ApiServiceException, IOException {
return invokeGet("tasks/set_deadline.json",
"token", token,
"id_task", idTask,
"deadline", deadline);
}
/**
* delete a task
*
* @param idTask
*
* @return array with the result = (Array("stats" => Array("result" => "TRUE|FALSE"))
*/
public JSONObject tasksDelete(int idTask) throws ApiServiceException, IOException {
return invokeGet("tasks/delete.json",
"token", token,
"id_task", idTask);
}
/**
* get labels assigned to a task
*
* @param idTask
*
* @return array: list of labels/view
*/
public JSONObject tasksLabels(int idTask) throws ApiServiceException, IOException {
return invokeGet("tasks/labels.json",
"token", token,
"id_task", idTask);
}
/**
* set a labels to a task
*
* @param idTask
* @param idLabel
*
* @return array: tasks/view
*/
public JSONObject tasksSetLabel(int idTask, int idLabel) throws ApiServiceException, IOException {
return invokeGet("tasks/set_label.json",
"token", token,
"id_task", idTask,
"id_label", idLabel);
}
/**
* set a labels to a task
*
* @param idTask
* @param idLabel
*
* @return array: tasks/view
*/
public JSONObject tasksUnsetLabel(int idTask, int idLabel) throws ApiServiceException, IOException {
return invokeGet("tasks/unset_label.json",
"token", token,
"id_task", idTask,
"id_label", idLabel);
}
// --- labels
/**
* get every label for a given dashboard
*
* @param idTask
* @param idLabel
*
* @return array: labels/view
*/
public JSONObject labelsShowList(int idDashboard, String since) throws ApiServiceException, IOException {
return invokeGet("labels/show_list.json",
"token", token,
"id_dashboard", idDashboard,
"since", since);
}
/**
* create a task
*
* @param idDashboard
* @param title
*
* @return array: labels/view
*/
public JSONObject labelsCreate(int idDashboard, String title) throws ApiServiceException, IOException {
return invokeGet("labels/create.json",
"token", token,
"id_dashboard", idDashboard,
"title", title);
}
// --- users
/**
* get a user
*
* @param idColleague
*
* @return array information about the user
*/
public JSONObject usersView(Integer idColleague) throws ApiServiceException, IOException {
return invokeGet("users/view.json",
"token", token,
"id_colleague", idColleague);
}
// --- invocation
private final RestClient restClient = new ProducteevRestClient();
/**
* Invokes API method using HTTP GET
*
* @param method
* API method to invoke
* @param getParameters
* Name/Value pairs. Values will be URL encoded.
* @return response object
*/
private JSONObject invokeGet(String method, Object... getParameters)
throws IOException, ApiServiceException {
try {
String request = createFetchUrl(method, getParameters);
String response = restClient.get(request);
return new JSONObject(response);
} catch (JSONException e) {
throw new ApiResponseParseException(e);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
/**
* Creates a URL for invoking an HTTP GET/POST on the given method
* @param method
* @param getParameters
* @return
* @throws UnsupportedEncodingException
* @throws NoSuchAlgorithmException
*/
public String createFetchUrl(String method, Object... getParameters) throws UnsupportedEncodingException, NoSuchAlgorithmException {
TreeMap<String, Object> treeMap = new TreeMap<String, Object>();
for(int i = 0; i < getParameters.length; i += 2)
treeMap.put(getParameters[i].toString(), getParameters[i+1]);
treeMap.put("api_key", apiKey);
StringBuilder requestBuilder = new StringBuilder(URL).append(method).append('?');
StringBuilder sigBuilder = new StringBuilder();
for(Map.Entry<String, Object> entry : treeMap.entrySet()) {
if(entry.getValue() == null)
continue;
String key = entry.getKey();
String value = entry.getValue().toString();
String encoded = URLEncoder.encode(value, "UTF-8");
requestBuilder.append(key).append('=').append(encoded).append('&');
sigBuilder.append(key).append(value);
}
sigBuilder.append(apiSecret);
byte[] digest = MessageDigest.getInstance("MD5").digest(sigBuilder.toString().getBytes("UTF-8"));
String signature = new BigInteger(1, digest).toString(16);
requestBuilder.append("api_sig").append('=').append(signature);
return requestBuilder.toString();
}
}

@ -0,0 +1,160 @@
package com.todoroo.astrid.producteev.api;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.ref.WeakReference;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.json.JSONObject;
import com.todoroo.andlib.service.RestClient;
/**
* RestClient allows Android to consume web requests.
* <p>
* Portions by Praeda:
* http://senior.ceng.metu.edu.tr/2009/praeda/2009/01/11/a-simple
* -restful-client-at-android/
*
* @author Tim Su <tim@todoroo.com>
*
*/
public class ProducteevRestClient implements RestClient {
private static final int HTTP_OK = 200;
private static final int TIMEOUT_MILLIS = 30000;
private static WeakReference<HttpClient> httpClient = null;
private static String convertStreamToString(InputStream is) {
/*
* To convert the InputStream to String we use the
* BufferedReader.readLine() method. We iterate until the BufferedReader
* return null which means there's no more data to read. Each line will
* appended to a StringBuilder and returned as String.
*/
BufferedReader reader = new BufferedReader(new InputStreamReader(is), 16384);
StringBuilder sb = new StringBuilder();
String line = null;
try {
while ((line = reader.readLine()) != null) {
sb.append(line + "\n"); //$NON-NLS-1$
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return sb.toString();
}
private synchronized static void initializeHttpClient() {
if (httpClient == null || httpClient.get() == null) {
HttpParams params = new BasicHttpParams();
HttpConnectionParams.setConnectionTimeout(params, TIMEOUT_MILLIS);
HttpConnectionParams.setSoTimeout(params, TIMEOUT_MILLIS);
httpClient = new WeakReference<HttpClient>(new DefaultHttpClient(params));
}
}
private String processHttpResponse(HttpResponse response) throws IOException, ApiServiceException {
HttpEntity entity = response.getEntity();
String body = null;
if (entity != null) {
InputStream contentStream = entity.getContent();
try {
body = convertStreamToString(contentStream);
} finally {
contentStream.close();
}
}
int statusCode = response.getStatusLine().getStatusCode();
if(statusCode != HTTP_OK) {
try {
JSONObject error = new JSONObject(body);
String errorMessage = error.getJSONObject("error").getString("message"); //$NON-NLS-1$ //$NON-NLS-2$
throw new ApiServiceException(errorMessage);
} catch (Exception e) {
throw new ApiServiceException(response.getStatusLine() +
"\n" + body); //$NON-NLS-1$
}
}
return body;
}
/**
* Issue an HTTP GET for the given URL, return the response
*
* @param url url with url-encoded params
* @return response, or null if there was no response
* @throws IOException
*/
public synchronized String get(String url) throws IOException {
initializeHttpClient();
System.err.println("GET: " + url); //$NON-NLS-1$ // (debug)
try {
HttpGet httpGet = new HttpGet(url);
HttpResponse response = httpClient.get().execute(httpGet);
return processHttpResponse(response);
} catch (IOException e) {
throw e;
} catch (Exception e) {
IOException ioException = new IOException(e.getMessage());
ioException.initCause(e);
throw ioException;
}
}
/**
* Issue an HTTP POST for the given URL, return the response
*
* @param url
* @param data
* url-encoded data
* @throws IOException
*/
public synchronized String post(String url, String data) throws IOException {
initializeHttpClient();
System.err.println("POST: " + url); //$NON-NLS-1$ // (debug)
try {
HttpPost httpPost = new HttpPost(url);
httpPost.setEntity(new StringEntity(data));
HttpResponse response = httpClient.get().execute(httpPost);
return processHttpResponse(response);
} catch (IOException e) {
throw e;
} catch (Exception e) {
IOException ioException = new IOException(e.getMessage());
ioException.initCause(e);
throw ioException;
}
}
}

@ -0,0 +1,512 @@
/**
* 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.Collections;
import java.util.HashSet;
import java.util.List;
import org.json.JSONObject;
import android.app.Notification;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.text.TextUtils;
import com.flurry.android.FlurryAgent;
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.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.ProducteevUtilities;
import com.todoroo.astrid.producteev.api.ProducteevInvoker;
import com.todoroo.astrid.rmilk.MilkPreferences;
import com.todoroo.astrid.rmilk.MilkUtilities;
import com.todoroo.astrid.rmilk.api.ServiceInternalException;
import com.todoroo.astrid.rmilk.api.data.RtmTask;
import com.todoroo.astrid.rmilk.api.data.RtmTaskList;
import com.todoroo.astrid.rmilk.api.data.RtmTaskNote;
import com.todoroo.astrid.rmilk.api.data.RtmTaskSeries;
import com.todoroo.astrid.rmilk.api.data.RtmTasks;
import com.todoroo.astrid.rmilk.api.data.RtmTask.Priority;
import com.todoroo.astrid.rmilk.data.MilkDataService;
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;
public class ProducteevSyncProvider extends SynchronizationProvider<ProducteevTaskContainer> {
private MilkDataService dataService = null;
private ProducteevInvoker invoker = null;
private int defaultDashboard;
static {
AstridDependencyInjector.initialize();
}
@Autowired
protected ExceptionService exceptionService;
@Autowired
protected DialogUtilities dialogUtilities;
public ProducteevSyncProvider() {
super();
DependencyInjectionService.getInstance().inject(this);
}
// ----------------------------------------------------------------------
// ------------------------------------------------------- public methods
// ----------------------------------------------------------------------
/**
* Sign out of RTM, deleting all synchronization metadata
*/
public void signOut() {
ProducteevUtilities.setToken(null);
ProducteevUtilities.clearLastSyncDate();
dataService.clearMetadata(); // TODO clear metadata
}
// ----------------------------------------------------------------------
// ------------------------------------------------------- authentication
// ----------------------------------------------------------------------
/**
* Deal with a synchronization exception. If requested, will show an error
* to the user (unless synchronization is happening in background)
*
* @param context
* @param tag
* error tag
* @param e
* exception
* @param showError
* whether to display a dialog
*/
@Override
protected void handleException(String tag, Exception e, boolean showError) {
ProducteevUtilities.setLastError(e.toString());
// occurs when application was closed
if(e instanceof IllegalStateException) {
exceptionService.reportError(tag + "-caught", e); //$NON-NLS-1$
// occurs when network error
} else if(e instanceof ServiceInternalException &&
((ServiceInternalException)e).getEnclosedException() instanceof
IOException) {
Exception enclosedException = ((ServiceInternalException)e).getEnclosedException();
exceptionService.reportError(tag + "-ioexception", enclosedException); //$NON-NLS-1$
if(showError) {
Context context = ContextManager.getContext();
showError(context, enclosedException,
context.getString(R.string.rmilk_ioerror));
}
} else {
if(e instanceof ServiceInternalException)
e = ((ServiceInternalException)e).getEnclosedException();
exceptionService.reportError(tag + "-unhandled", e); //$NON-NLS-1$
if(showError) {
Context context = ContextManager.getContext();
showError(context, e, null);
}
}
}
@Override
protected void initiate(Context context) {
dataService = MilkDataService.getInstance();
// authenticate the user. this will automatically call the next step
authenticate(context);
}
/**
* Perform authentication with RTM. Will open the SyncBrowser if necessary
*/
@SuppressWarnings("nls")
private void authenticate(final Context context) {
final Resources r = context.getResources();
FlurryAgent.onEvent("producteev-started");
ProducteevUtilities.recordSyncStart();
try {
String authToken = ProducteevUtilities.getToken();
String z = stripslashes(0, "71o3346pr40o5o4nt4n7t6n287t4op28","2");
String v = stripslashes(2, "9641n76n9s1736q1578q1o1337q19233","4ae");
invoker = new ProducteevInvoker(z, v);
// check if we have a token & it works
if(authToken != null) {
invoker.setToken(authToken);
}
if(authToken == null) {
String email = Preferences.getStringValue(R.string.producteev_PPr_email);
String password = Preferences.getStringValue(R.string.producteev_PPr_password);
invoker.authenticate(email, password);
}
performSync();
} catch (IllegalStateException e) {
// occurs when application was closed
} catch (Exception e) {
handleException("rtm-authenticate", e, true);
} finally {
ProducteevUtilities.stopOngoing();
}
}
// ----------------------------------------------------------------------
// ----------------------------------------------------- synchronization!
// ----------------------------------------------------------------------
@SuppressWarnings("nls")
protected void performSync() {
try {
// load user information
JSONObject user = invoker.usersView(null);
defaultDashboard = user.getJSONObject("user").getInt("default_dashboard");
// read all tasks
JSONObject tasks = invoker.tasksShowList(defaultDashboard,
ProducteevUtilities.getLastServerSyncTime());
SyncData<ProducteevTaskContainer> syncData = populateSyncData(tasks);
try {
synchronizeTasks(syncData);
} finally {
syncData.localCreated.close();
syncData.localUpdated.close();
}
MilkUtilities.recordSuccessfulSync();
FlurryAgent.onEvent("rtm-sync-finished"); //$NON-NLS-1$
} catch (IllegalStateException e) {
// occurs when application was closed
} catch (Exception e) {
handleException("rtm-sync", e, true); //$NON-NLS-1$
}
}
// ----------------------------------------------------------------------
// ------------------------------------------------------------ sync data
// ----------------------------------------------------------------------
// 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.NOTES,
};
/**
* Populate SyncData data structure
*/
private SyncData<ProducteevTaskContainer> populateSyncData(ArrayList<ProducteevTaskContainer> remoteTasks) {
// 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);
}
/**
* Add the tasks read from RTM to the given list
*/
private void addTasksToList(RtmTasks tasks, ArrayList<ProducteevTaskContainer> list) {
for (RtmTaskList taskList : tasks.getLists()) {
for (RtmTaskSeries taskSeries : taskList.getSeries()) {
ProducteevTaskContainer remoteTask = parseRemoteTask(taskSeries);
dataService.findLocalMatch(remoteTask);
list.add(remoteTask);
}
}
}
// ----------------------------------------------------------------------
// ------------------------------------------------- create / push / pull
// ----------------------------------------------------------------------
@Override
protected void create(ProducteevTaskContainer task) throws IOException {
String listId = null;
if(task.dashboard > 0)
listId = Long.toString(task.listId);
RtmTaskSeries rtmTask = rtmService.tasks_add(timeline, listId,
task.task.getValue(Task.TITLE));
ProducteevTaskContainer newRemoteTask = parseRemoteTask(rtmTask);
transferIdentifiers(newRemoteTask, task);
push(task, newRemoteTask);
}
/**
* 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));
}
/**
* 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 push(ProducteevTaskContainer local, ProducteevTaskContainer remote) throws IOException {
boolean remerge = false;
// fetch remote task for comparison
if(remote == null)
remote = pull(local);
String listId = Long.toString(local.listId);
String taskSeriesId = Long.toString(local.taskSeriesId);
String taskId = Long.toString(local.taskId);
if(remote != null && !AndroidUtilities.equals(local.listId, remote.listId))
rtmService.tasks_moveTo(timeline, Long.toString(remote.listId),
listId, taskSeriesId, taskId);
// either delete or re-create if necessary
if(shouldTransmit(local, Task.DELETION_DATE, remote)) {
if(local.task.getValue(Task.DELETION_DATE) > 0)
rtmService.tasks_delete(timeline, listId, taskSeriesId, taskId);
else if(remote == null) {
RtmTaskSeries rtmTask = rtmService.tasks_add(timeline, listId,
local.task.getValue(Task.TITLE));
remote = parseRemoteTask(rtmTask);
transferIdentifiers(remote, local);
}
}
if(shouldTransmit(local, Task.TITLE, remote))
rtmService.tasks_setName(timeline, listId, taskSeriesId,
taskId, local.task.getValue(Task.TITLE));
if(shouldTransmit(local, Task.IMPORTANCE, remote))
rtmService.tasks_setPriority(timeline, listId, taskSeriesId,
taskId, Priority.values()[local.task.getValue(Task.IMPORTANCE)]);
if(shouldTransmit(local, Task.DUE_DATE, remote))
rtmService.tasks_setDueDate(timeline, listId, taskSeriesId,
taskId, DateUtilities.unixtimeToDate(local.task.getValue(Task.DUE_DATE)),
local.task.hasDueTime());
if(shouldTransmit(local, Task.COMPLETION_DATE, remote)) {
if(local.task.getValue(Task.COMPLETION_DATE) == 0)
rtmService.tasks_uncomplete(timeline, listId, taskSeriesId,
taskId);
else {
rtmService.tasks_complete(timeline, listId, taskSeriesId,
taskId);
// if repeating, pull and merge
if(local.repeating)
remerge = true;
}
}
// 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.remote.getNotes() != null)
notes = remote.remote.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);
}
}
/** Create a task container for the given RtmTaskSeries */
private ProducteevTaskContainer parseRemoteTask(RtmTaskSeries rtmTaskSeries) {
Task task = new Task();
RtmTask rtmTask = rtmTaskSeries.getTask();
ArrayList<Metadata> metadata = new ArrayList<Metadata>();
task.setValue(Task.TITLE, rtmTaskSeries.getName());
task.setValue(Task.CREATION_DATE, DateUtilities.dateToUnixtime(rtmTask.getAdded()));
task.setValue(Task.COMPLETION_DATE, DateUtilities.dateToUnixtime(rtmTask.getCompleted()));
task.setValue(Task.DELETION_DATE, DateUtilities.dateToUnixtime(rtmTask.getDeleted()));
if(rtmTask.getDue() != null) {
task.setValue(Task.DUE_DATE,
task.createDueDate(rtmTask.getHasDueTime() ? Task.URGENCY_SPECIFIC_DAY_TIME :
Task.URGENCY_SPECIFIC_DAY, DateUtilities.dateToUnixtime(rtmTask.getDue())));
} else {
task.setValue(Task.DUE_DATE, 0L);
}
task.setValue(Task.IMPORTANCE, rtmTask.getPriority().ordinal());
if(rtmTaskSeries.getTags() != null) {
for(String tag : rtmTaskSeries.getTags()) {
Metadata tagData = new Metadata();
tagData.setValue(Metadata.KEY, TagService.KEY);
tagData.setValue(TagService.TAG, tag);
metadata.add(tagData);
}
}
task.setValue(Task.NOTES, ""); //$NON-NLS-1$
if(rtmTaskSeries.getNotes() != null && rtmTaskSeries.getNotes().getNotes().size() > 0) {
boolean firstNote = true;
Collections.reverse(rtmTaskSeries.getNotes().getNotes()); // reverse so oldest is first
for(RtmTaskNote note : rtmTaskSeries.getNotes().getNotes()) {
if(firstNote) {
firstNote = false;
task.setValue(Task.NOTES, MilkNote.toNoteField(note));
} else
metadata.add(MilkNote.create(note));
}
}
ProducteevTaskContainer container = new ProducteevTaskContainer(task, metadata, rtmTaskSeries);
return container;
}
@Override
protected ProducteevTaskContainer pull(ProducteevTaskContainer task) throws IOException {
if(task.taskSeriesId == 0)
throw new ServiceInternalException("Tried to read an invalid task"); //$NON-NLS-1$
RtmTaskSeries rtmTask = rtmService.tasks_getTask(Long.toString(task.taskSeriesId),
task.task.getValue(Task.TITLE));
if(rtmTask != null)
return parseRemoteTask(rtmTask);
return null;
}
// ----------------------------------------------------------------------
// --------------------------------------------------------- read / write
// ----------------------------------------------------------------------
@Override
protected ProducteevTaskContainer read(TodorooCursor<Task> cursor) throws IOException {
return dataService.readTaskAndMetadata(cursor);
}
@Override
protected void write(ProducteevTaskContainer task) throws IOException {
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(AndroidUtilities.equals(task.listId, target.listId) &&
AndroidUtilities.equals(task.taskSeriesId, target.taskSeriesId) &&
AndroidUtilities.equals(task.taskId, target.taskId))
return i;
}
return -1;
}
@Override
protected void updateNotification(Context context, Notification notification) {
String notificationTitle = context.getString(R.string.rmilk_notification_title);
Intent intent = new Intent(context, MilkPreferences.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.listId = source.listId;
destination.taskSeriesId = source.taskSeriesId;
destination.taskId = source.taskId;
}
// ----------------------------------------------------------------------
// ------------------------------------------------------- helper classes
// ----------------------------------------------------------------------
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)_)));
}
}

@ -0,0 +1,49 @@
package com.todoroo.astrid.producteev.sync;
import java.util.ArrayList;
import java.util.Iterator;
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
*
* @author Tim Su <tim@todoroo.com>
*
*/
public class ProducteevTaskContainer extends TaskContainer {
public long id;
public long dashboard;
public ProducteevTaskContainer(Task task, ArrayList<Metadata> metadata, long id, long dashboard) {
this.task = task;
this.metadata = metadata;
this.id = id;
this.dashboard = dashboard;
}
// public ProducteevTaskContainer(Task task, ArrayList<Metadata> metadata,
// RtmTaskSeries rtmTaskSeries) {
// this(task, metadata, );
// }
public ProducteevTaskContainer(Task task, ArrayList<Metadata> metadata) {
this(task, metadata, 0, 0);
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);
iterator.remove();
break;
}
}
}
}

@ -179,7 +179,6 @@
<!-- ============================================================ MILK == -->
<string-array name="rmilk_MPr_interval_values">
<!-- rmilk_MPr_interval_values: interval in seconds for sync entries (do not edit) -->
<item>0</item>
@ -204,6 +203,11 @@
<string name="rmilk_MPr_sync_key">rmilk_sync</string>
<!-- Preference Key (do not translate) -->
<string name="rmilk_MPr_forget_key">rmilk_forget</string>
<!-- ======================================================= PRODUCTEEV == -->
<string name="producteev_PPr_email">producteev_email</string>
<string name="producteev_PPr_password">producteev_password</string>
<!-- ============================================================ OTHER == -->

Loading…
Cancel
Save