mirror of https://github.com/tasks/tasks
temporarily restoring rmilk into mainline astrid product for a bug fix release
parent
14cd6d26e5
commit
4776e0e866
@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/rmilk_MLA_label"/>
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="5dip"
|
||||
android:baselineAligned="false">
|
||||
|
||||
<Button android:id="@+id/done"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/DLG_done"
|
||||
/>
|
||||
|
||||
<Button android:id="@+id/cancel"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@android:string/cancel"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<WebView android:id="@+id/browser"
|
||||
android:layout_weight="1"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
</LinearLayout>
|
||||
@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
This file contains preference keys and preference list values.
|
||||
These should not be translated
|
||||
-->
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
|
||||
<string-array name="sync_SPr_interval_values">
|
||||
<!-- sync_SPr_interval_values: interval in seconds for sync entries (do not edit) -->
|
||||
<item>0</item>
|
||||
<item>900</item>
|
||||
<item>1800</item>
|
||||
<item>3600</item>
|
||||
<item>10800</item>
|
||||
<item>21600</item>
|
||||
<item>43200</item>
|
||||
<item>86400</item>
|
||||
<item>259200</item>
|
||||
<item>604800</item>
|
||||
</string-array>
|
||||
|
||||
<!-- Preference Key (do not translate) -->
|
||||
<string name="sync_SPr_status_key">sync_status</string>
|
||||
<!-- Preference Key (do not translate) -->
|
||||
<string name="sync_SPr_bgwifi_key">sync_bgwifi</string>
|
||||
<!-- Preference Key (do not translate) -->
|
||||
<string name="sync_SPr_sync_key">sync_sync</string>
|
||||
<!-- Preference Key (do not translate) -->
|
||||
<string name="sync_SPr_forget_key">sync_forget</string>
|
||||
|
||||
<!-- ============================================================ MILK == -->
|
||||
|
||||
<!-- Preference Key (do not translate) -->
|
||||
<string name="rmilk_MPr_interval_key">sync_freq</string>
|
||||
|
||||
</resources>
|
||||
@ -0,0 +1,69 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- See the file "LICENSE" for the full license governing this code. -->
|
||||
<resources xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<!-- application name -->
|
||||
<string name="app_name">Astrid Remember the Milk Plugin</string>
|
||||
|
||||
<!-- ====================== Plugin Boilerplate ========================= -->
|
||||
|
||||
<!-- label for RMilk button in Task Edit Activity -->
|
||||
<string name="rmilk_EOE_button">Remember the Milk Settings</string>
|
||||
|
||||
<!-- task detail showing RTM repeat information -->
|
||||
<string name="rmilk_TLA_repeat">RTM Repeating Task</string>
|
||||
|
||||
<!-- task detail showing item needs to be synchronized -->
|
||||
<string name="rmilk_TLA_sync">Needs synchronization with RTM</string>
|
||||
|
||||
<!-- filters header: RTM -->
|
||||
<string name="rmilk_FEx_header">Remember the Milk</string>
|
||||
|
||||
<!-- filter category for RTM lists -->
|
||||
<string name="rmilk_FEx_list">Lists</string>
|
||||
|
||||
<!-- RTM list filter title (%s => list) -->
|
||||
<string name="rmilk_FEx_list_title">RTM List \'%s\'</string>
|
||||
|
||||
<!-- ======================= MilkEditActivity ========================== -->
|
||||
|
||||
<!-- RTM edit activity Title -->
|
||||
<string name="rmilk_MEA_title">Remember the Milk</string>
|
||||
|
||||
<!-- RTM edit List Edit Label -->
|
||||
<string name="rmilk_MEA_list_label">RTM List:</string>
|
||||
|
||||
<!-- RTM edit Repeat Label -->
|
||||
<string name="rmilk_MEA_repeat_label">RTM Repeat Status:</string>
|
||||
|
||||
<!-- RTM edit Repeat Hint -->
|
||||
<string name="rmilk_MEA_repeat_hint">i.e. every week, after 14 days</string>
|
||||
|
||||
<!-- ======================== MilkPreferences ========================== -->
|
||||
|
||||
<!-- Milk Preferences Title -->
|
||||
<string name="rmilk_MPr_header">Remember the Milk</string>
|
||||
|
||||
<!-- ======================= MilkLoginActivity ========================= -->
|
||||
|
||||
<!-- RTM Login Instructions -->
|
||||
<string name="rmilk_MLA_label">Please Log In and Authorize Astrid:</string>
|
||||
|
||||
<!-- Login Error Dialog (%s => message) -->
|
||||
<string name="rmilk_MLA_error">
|
||||
Sorry, there was an error verifying your login. Please try again.
|
||||
\n\n
|
||||
Error Message: %s
|
||||
</string>
|
||||
|
||||
<!-- ======================== Synchronization ========================== -->
|
||||
|
||||
<!-- title for notification tray when synchronizing -->
|
||||
<string name="rmilk_notification_title">Astrid: Remember the Milk</string>
|
||||
|
||||
<!-- Error msg when io exception with rmilk -->
|
||||
<string name="rmilk_ioerror">Connection Error! Check your Internet connection,
|
||||
or maybe RTM servers (status.rememberthemilk.com), for possible solutions.</string>
|
||||
|
||||
</resources>
|
||||
|
||||
@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<PreferenceScreen
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<PreferenceCategory
|
||||
android:title="@string/sync_SPr_group_status">
|
||||
|
||||
<Preference
|
||||
android:layout="@layout/status_preference"
|
||||
android:key="@string/sync_SPr_status_key"
|
||||
android:textSize="24sp"
|
||||
android:gravity="center"/>
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory
|
||||
android:title="@string/sync_SPr_group_options">
|
||||
|
||||
<ListPreference
|
||||
android:key="@string/rmilk_MPr_interval_key"
|
||||
android:entries="@array/sync_SPr_interval_entries"
|
||||
android:entryValues="@array/sync_SPr_interval_values"
|
||||
android:title="@string/sync_SPr_interval_title" />
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory
|
||||
android:title="@string/sync_SPr_group_actions">
|
||||
|
||||
<Preference
|
||||
android:key="@string/sync_SPr_sync_key"
|
||||
android:title="@string/sync_SPr_sync" />
|
||||
|
||||
<Preference
|
||||
android:key="@string/sync_SPr_forget_key"
|
||||
android:title="@string/sync_SPr_forget"
|
||||
android:summary="@string/sync_SPr_forget_description" />
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
</PreferenceScreen>
|
||||
@ -0,0 +1,130 @@
|
||||
package org.weloveastrid.rmilk;
|
||||
|
||||
import org.weloveastrid.rmilk.sync.MilkSyncProvider;
|
||||
|
||||
import android.app.AlarmManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.IBinder;
|
||||
import android.util.Log;
|
||||
|
||||
import com.todoroo.andlib.service.ContextManager;
|
||||
import com.todoroo.andlib.utility.DateUtilities;
|
||||
|
||||
/**
|
||||
* SynchronizationService is the service that performs Astrid's background
|
||||
* synchronization with online task managers. Starting this service
|
||||
* schedules a repeating alarm which handles the synchronization
|
||||
*
|
||||
* @author Tim Su
|
||||
*
|
||||
*/
|
||||
public class MilkBackgroundService extends Service {
|
||||
|
||||
/** Minimum time before an auto-sync */
|
||||
private static final long AUTO_SYNC_MIN_OFFSET = 5*60*1000L;
|
||||
|
||||
/** alarm identifier */
|
||||
public static final String SYNC_ACTION = "sync"; //$NON-NLS-1$
|
||||
|
||||
// --- BroadcastReceiver abstract methods
|
||||
|
||||
/** start the synchronization service. sits in the background */
|
||||
@Override
|
||||
public void onStart(Intent intent, int startId) {
|
||||
try {
|
||||
if(intent != null && SYNC_ACTION.equals(intent.getAction()))
|
||||
startSynchronization(this);
|
||||
} catch (Exception e) {
|
||||
MilkUtilities.INSTANCE.setLastError(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/** Start the actual synchronization */
|
||||
private void startSynchronization(Context context) {
|
||||
if(context == null || context.getResources() == null)
|
||||
return;
|
||||
|
||||
ContextManager.setContext(context);
|
||||
|
||||
if(MilkUtilities.INSTANCE.isOngoing())
|
||||
return;
|
||||
|
||||
new MilkSyncProvider().synchronize(context);
|
||||
}
|
||||
|
||||
// --- alarm management
|
||||
|
||||
/**
|
||||
* Schedules repeating alarm for auto-synchronization
|
||||
*/
|
||||
public static void scheduleService() {
|
||||
Context context = ContextManager.getContext();
|
||||
int syncFrequencySeconds = MilkUtilities.INSTANCE.getSyncAutoSyncFrequency();
|
||||
if(syncFrequencySeconds <= 0) {
|
||||
unscheduleService(context);
|
||||
return;
|
||||
}
|
||||
|
||||
// figure out synchronization frequency
|
||||
long interval = 1000L * syncFrequencySeconds;
|
||||
long offset = computeNextSyncOffset(interval);
|
||||
|
||||
// give a little padding
|
||||
offset = Math.max(offset, AUTO_SYNC_MIN_OFFSET);
|
||||
|
||||
AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
|
||||
PendingIntent pendingIntent = PendingIntent.getService(context, 0,
|
||||
createAlarmIntent(context), PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
|
||||
Log.i("Astrid", "Autosync set for " + offset / 1000 //$NON-NLS-1$ //$NON-NLS-2$
|
||||
+ " seconds repeating every " + syncFrequencySeconds); //$NON-NLS-1$
|
||||
|
||||
// cancel all existing
|
||||
am.cancel(pendingIntent);
|
||||
|
||||
// schedule new
|
||||
am.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + offset,
|
||||
interval, pendingIntent);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes repeating alarm for auto-synchronization
|
||||
*/
|
||||
private static void unscheduleService(Context context) {
|
||||
AlarmManager am = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
|
||||
PendingIntent pendingIntent = PendingIntent.getService(context, 0,
|
||||
createAlarmIntent(context), PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
am.cancel(pendingIntent);
|
||||
}
|
||||
|
||||
/** Create the alarm intent */
|
||||
private static Intent createAlarmIntent(Context context) {
|
||||
Intent intent = new Intent(context, MilkBackgroundService.class);
|
||||
intent.setAction(SYNC_ACTION);
|
||||
return intent;
|
||||
}
|
||||
|
||||
// --- utility methods
|
||||
|
||||
|
||||
private static long computeNextSyncOffset(long interval) {
|
||||
// figure out last synchronize time
|
||||
long lastSyncDate = MilkUtilities.INSTANCE.getLastSyncDate();
|
||||
|
||||
// if user never synchronized, give them a full offset period before bg sync
|
||||
if(lastSyncDate != 0)
|
||||
return Math.max(0, lastSyncDate + interval - DateUtilities.now());
|
||||
else
|
||||
return interval;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,98 @@
|
||||
/**
|
||||
* See the file "LICENSE" for the full license governing this code.
|
||||
*/
|
||||
package org.weloveastrid.rmilk;
|
||||
|
||||
import org.weloveastrid.rmilk.data.MilkDataService;
|
||||
import org.weloveastrid.rmilk.data.MilkNoteFields;
|
||||
import org.weloveastrid.rmilk.data.MilkTaskFields;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import com.timsu.astrid.R;
|
||||
import com.todoroo.andlib.data.TodorooCursor;
|
||||
import com.todoroo.andlib.service.ContextManager;
|
||||
import com.todoroo.astrid.api.AstridApiConstants;
|
||||
import com.todoroo.astrid.data.Metadata;
|
||||
|
||||
/**
|
||||
* Exposes Task Details for Remember the Milk:
|
||||
* - RTM list
|
||||
* - RTM repeat information
|
||||
* - RTM notes
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
public class MilkDetailExposer extends BroadcastReceiver {
|
||||
|
||||
public static final String DETAIL_SEPARATOR = " | "; //$NON-NLS-1$
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
ContextManager.setContext(context);
|
||||
|
||||
// if we aren't logged in, don't expose features
|
||||
if(!MilkUtilities.INSTANCE.isLoggedIn())
|
||||
return;
|
||||
|
||||
long taskId = intent.getLongExtra(AstridApiConstants.EXTRAS_TASK_ID, -1);
|
||||
if(taskId == -1)
|
||||
return;
|
||||
|
||||
boolean extended = intent.getBooleanExtra(AstridApiConstants.EXTRAS_EXTENDED, false);
|
||||
String taskDetail = getTaskDetails(context, taskId, extended);
|
||||
if(taskDetail == null)
|
||||
return;
|
||||
|
||||
Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_SEND_DETAILS);
|
||||
broadcastIntent.putExtra(AstridApiConstants.EXTRAS_ADDON, MilkUtilities.IDENTIFIER);
|
||||
broadcastIntent.putExtra(AstridApiConstants.EXTRAS_TASK_ID, taskId);
|
||||
broadcastIntent.putExtra(AstridApiConstants.EXTRAS_EXTENDED, extended);
|
||||
broadcastIntent.putExtra(AstridApiConstants.EXTRAS_RESPONSE, taskDetail);
|
||||
context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ);
|
||||
}
|
||||
|
||||
public String getTaskDetails(Context context, long id, boolean extended) {
|
||||
Metadata metadata = MilkDataService.getInstance(context).getTaskMetadata(id);
|
||||
if(metadata == null)
|
||||
return null;
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
if(!extended) {
|
||||
long listId = metadata.getValue(MilkTaskFields.LIST_ID);
|
||||
String listName = MilkDataService.getInstance(context).getListName(listId);
|
||||
// RTM list is out of date. don't display RTM stuff
|
||||
if(listName == null)
|
||||
return null;
|
||||
|
||||
if(listId > 0 && !"Inbox".equals(listName)) { //$NON-NLS-1$
|
||||
builder.append("<img src='silk_folder'/> ").append(listName).append(DETAIL_SEPARATOR); //$NON-NLS-1$
|
||||
}
|
||||
|
||||
int repeat = metadata.getValue(MilkTaskFields.REPEATING);
|
||||
if(repeat != 0) {
|
||||
builder.append(context.getString(R.string.rmilk_TLA_repeat)).append(DETAIL_SEPARATOR);
|
||||
}
|
||||
} else {
|
||||
TodorooCursor<Metadata> notesCursor = MilkDataService.getInstance(context).getTaskNotesCursor(id);
|
||||
try {
|
||||
for(notesCursor.moveToFirst(); !notesCursor.isAfterLast(); notesCursor.moveToNext()) {
|
||||
metadata.readFromCursor(notesCursor);
|
||||
builder.append(MilkNoteFields.toTaskDetail(metadata)).append(DETAIL_SEPARATOR);
|
||||
}
|
||||
} finally {
|
||||
notesCursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
if(builder.length() == 0)
|
||||
return null;
|
||||
String result = builder.toString();
|
||||
return result.substring(0, result.length() - DETAIL_SEPARATOR.length());
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,91 @@
|
||||
/**
|
||||
* See the file "LICENSE" for the full license governing this code.
|
||||
*/
|
||||
package org.weloveastrid.rmilk;
|
||||
|
||||
import org.weloveastrid.rmilk.data.MilkDataService;
|
||||
import org.weloveastrid.rmilk.data.MilkListFields;
|
||||
import org.weloveastrid.rmilk.data.MilkTaskFields;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import com.timsu.astrid.R;
|
||||
import com.todoroo.andlib.service.ContextManager;
|
||||
import com.todoroo.andlib.sql.Criterion;
|
||||
import com.todoroo.andlib.sql.Join;
|
||||
import com.todoroo.andlib.sql.QueryTemplate;
|
||||
import com.todoroo.astrid.api.AstridApiConstants;
|
||||
import com.todoroo.astrid.api.Filter;
|
||||
import com.todoroo.astrid.api.FilterCategory;
|
||||
import com.todoroo.astrid.api.FilterListHeader;
|
||||
import com.todoroo.astrid.api.FilterListItem;
|
||||
import com.todoroo.astrid.data.Metadata;
|
||||
import com.todoroo.astrid.data.StoreObject;
|
||||
import com.todoroo.astrid.data.Task;
|
||||
import com.todoroo.astrid.data.MetadataApiDao.MetadataCriteria;
|
||||
import com.todoroo.astrid.data.TaskApiDao.TaskCriteria;
|
||||
|
||||
/**
|
||||
* Exposes filters based on RTM lists
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
public class MilkFilterExposer extends BroadcastReceiver {
|
||||
|
||||
private Filter filterFromList(Context context, StoreObject list) {
|
||||
String listName = list.getValue(MilkListFields.NAME);
|
||||
String title = context.getString(R.string.rmilk_FEx_list_title,
|
||||
listName);
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(Metadata.KEY.name, MilkTaskFields.METADATA_KEY);
|
||||
values.put(MilkTaskFields.LIST_ID.name, list.getValue(MilkListFields.REMOTE_ID));
|
||||
values.put(MilkTaskFields.TASK_SERIES_ID.name, 0);
|
||||
values.put(MilkTaskFields.TASK_ID.name, 0);
|
||||
values.put(MilkTaskFields.REPEATING.name, 0);
|
||||
Filter filter = new Filter(listName, title, new QueryTemplate().join(
|
||||
Join.left(Metadata.TABLE, Task.ID.eq(Metadata.TASK))).where(Criterion.and(
|
||||
MetadataCriteria.withKey(MilkTaskFields.METADATA_KEY),
|
||||
TaskCriteria.activeAndVisible(),
|
||||
MilkTaskFields.LIST_ID.eq(list.getValue(MilkListFields.REMOTE_ID)))),
|
||||
values);
|
||||
|
||||
return filter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
ContextManager.setContext(context);
|
||||
|
||||
// if we aren't logged in, don't expose features
|
||||
if(!MilkUtilities.INSTANCE.isLoggedIn())
|
||||
return;
|
||||
|
||||
StoreObject[] lists = MilkDataService.getInstance(context).getLists();
|
||||
|
||||
// If user does not have any tags, don't show this section at all
|
||||
if(lists.length == 0)
|
||||
return;
|
||||
|
||||
Filter[] listFilters = new Filter[lists.length];
|
||||
for(int i = 0; i < lists.length; i++)
|
||||
listFilters[i] = filterFromList(context, lists[i]);
|
||||
|
||||
FilterListHeader rtmHeader = new FilterListHeader(context.getString(R.string.rmilk_FEx_header));
|
||||
FilterCategory rtmLists = new FilterCategory(context.getString(R.string.rmilk_FEx_list),
|
||||
listFilters);
|
||||
|
||||
// transmit filter list
|
||||
FilterListItem[] list = new FilterListItem[2];
|
||||
list[0] = rtmHeader;
|
||||
list[1] = rtmLists;
|
||||
Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_SEND_FILTERS);
|
||||
broadcastIntent.putExtra(AstridApiConstants.EXTRAS_ADDON, MilkUtilities.IDENTIFIER);
|
||||
broadcastIntent.putExtra(AstridApiConstants.EXTRAS_RESPONSE, list);
|
||||
context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,133 @@
|
||||
/*
|
||||
* ASTRID: Android's Simple Task Recording Dashboard
|
||||
*
|
||||
* Copyright (c) 2009 Tim Su
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
package org.weloveastrid.rmilk;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebViewClient;
|
||||
import android.widget.Button;
|
||||
|
||||
import com.timsu.astrid.R;
|
||||
import com.todoroo.andlib.utility.DialogUtilities;
|
||||
|
||||
/**
|
||||
* This activity displays a <code>WebView</code> that allows users to log in to the
|
||||
* synchronization provider requested. A callback method determines whether
|
||||
* their login was successful and therefore whether to dismiss the dialog.
|
||||
*
|
||||
* @author timsu
|
||||
*
|
||||
*/
|
||||
public class MilkLoginActivity extends Activity {
|
||||
|
||||
// --- bundle arguments
|
||||
|
||||
/**
|
||||
* URL to display
|
||||
*/
|
||||
public static final String URL_TOKEN = "u"; //$NON-NLS-1$
|
||||
|
||||
// --- callback
|
||||
|
||||
/** Callback interface */
|
||||
public interface SyncLoginCallback {
|
||||
/**
|
||||
* Verifies whether the user's login attempt was successful. Will be
|
||||
* called off of the UI thread, use the handler to post messages.
|
||||
*
|
||||
* @return error string, or null if sync was successful
|
||||
*/
|
||||
public String verifyLogin(Handler handler);
|
||||
}
|
||||
|
||||
protected static SyncLoginCallback callback = null;
|
||||
|
||||
/** Sets callback method */
|
||||
public static void setCallback(SyncLoginCallback newCallback) {
|
||||
callback = newCallback;
|
||||
}
|
||||
|
||||
// --- ui initialization
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.rmilk_login_activity);
|
||||
setTitle(R.string.rmilk_MPr_header);
|
||||
|
||||
final String urlParam = getIntent().getStringExtra(URL_TOKEN);
|
||||
|
||||
final WebView webView = (WebView)findViewById(R.id.browser);
|
||||
Button done = (Button)findViewById(R.id.done);
|
||||
Button cancel = (Button)findViewById(R.id.cancel);
|
||||
|
||||
webView.setWebViewClient(new WebViewClient() {
|
||||
@Override
|
||||
public boolean shouldOverrideUrlLoading(WebView view, String url) {
|
||||
view.loadUrl(url);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
webView.getSettings().setJavaScriptEnabled(true);
|
||||
webView.getSettings().setSavePassword(false);
|
||||
webView.getSettings().setSupportZoom(true);
|
||||
webView.loadUrl(urlParam);
|
||||
|
||||
done.setOnClickListener(new OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
final Handler handler = new Handler();
|
||||
|
||||
if(callback == null) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
new Thread(new Runnable() {
|
||||
public void run() {
|
||||
final String result = callback.verifyLogin(handler);
|
||||
if(result == null) {
|
||||
finish();
|
||||
} else {
|
||||
// display the error, re-load url
|
||||
handler.post(new Runnable() {
|
||||
public void run() {
|
||||
DialogUtilities.okDialog(MilkLoginActivity.this,
|
||||
result, null);
|
||||
webView.loadUrl(urlParam);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
});
|
||||
|
||||
cancel.setOnClickListener(new OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
setResult(RESULT_CANCELED);
|
||||
finish();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
package org.weloveastrid.rmilk;
|
||||
|
||||
import org.weloveastrid.rmilk.sync.MilkSyncProvider;
|
||||
|
||||
import com.timsu.astrid.R;
|
||||
import com.todoroo.astrid.sync.SyncProviderPreferences;
|
||||
import com.todoroo.astrid.sync.SyncProviderUtilities;
|
||||
|
||||
/**
|
||||
* Displays synchronization preferences and an action panel so users can
|
||||
* initiate actions from the menu.
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
public class MilkPreferences extends SyncProviderPreferences {
|
||||
|
||||
@Override
|
||||
public int getPreferenceResource() {
|
||||
return R.xml.preferences_rmilk;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startSync() {
|
||||
new MilkSyncProvider().synchronize(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logOut() {
|
||||
new MilkSyncProvider().signOut(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SyncProviderUtilities getUtilities() {
|
||||
return MilkUtilities.INSTANCE;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
/**
|
||||
* See the file "LICENSE" for the full license governing this code.
|
||||
*/
|
||||
package org.weloveastrid.rmilk;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import com.todoroo.andlib.service.ContextManager;
|
||||
|
||||
public class MilkStartupReceiver extends BroadcastReceiver {
|
||||
|
||||
@Override
|
||||
/** Called when device is restarted */
|
||||
public void onReceive(final Context context, Intent intent) {
|
||||
ContextManager.setContext(context);
|
||||
|
||||
MilkBackgroundService.scheduleService();
|
||||
MilkUtilities.INSTANCE.stopOngoing();
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,42 @@
|
||||
/**
|
||||
* See the file "LICENSE" for the full license governing this code.
|
||||
*/
|
||||
package org.weloveastrid.rmilk;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import com.timsu.astrid.R;
|
||||
import com.todoroo.andlib.service.ContextManager;
|
||||
import com.todoroo.astrid.api.AstridApiConstants;
|
||||
import com.todoroo.astrid.api.SyncAction;
|
||||
|
||||
/**
|
||||
* Exposes sync action
|
||||
*
|
||||
*/
|
||||
public class MilkSyncActionExposer extends BroadcastReceiver {
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
ContextManager.setContext(context);
|
||||
|
||||
// if we aren't logged in, don't expose sync action
|
||||
if(!MilkUtilities.INSTANCE.isLoggedIn())
|
||||
return;
|
||||
|
||||
Intent syncIntent = new Intent(MilkBackgroundService.SYNC_ACTION, null,
|
||||
context, MilkBackgroundService.class);
|
||||
PendingIntent pendingIntent = PendingIntent.getService(context, 0, syncIntent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
SyncAction syncAction = new SyncAction(context.getString(R.string.rmilk_MPr_header),
|
||||
pendingIntent);
|
||||
|
||||
Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_SEND_SYNC_ACTIONS);
|
||||
broadcastIntent.putExtra(AstridApiConstants.EXTRAS_ADDON, MilkUtilities.IDENTIFIER);
|
||||
broadcastIntent.putExtra(AstridApiConstants.EXTRAS_RESPONSE, syncAction);
|
||||
context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,63 @@
|
||||
/**
|
||||
* See the file "LICENSE" for the full license governing this code.
|
||||
*/
|
||||
package org.weloveastrid.rmilk;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.SharedPreferences.Editor;
|
||||
|
||||
import com.timsu.astrid.R;
|
||||
import com.todoroo.andlib.service.ContextManager;
|
||||
import com.todoroo.astrid.sync.SyncProviderUtilities;
|
||||
|
||||
/**
|
||||
* Constants and preferences for RTM plugin
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
@SuppressWarnings("nls")
|
||||
public class MilkUtilities extends SyncProviderUtilities {
|
||||
|
||||
// --- constants
|
||||
|
||||
/** add-on identifier */
|
||||
public static final String IDENTIFIER = "rmilk";
|
||||
|
||||
public static final MilkUtilities INSTANCE = new MilkUtilities();
|
||||
|
||||
// --- utilities boilerplate
|
||||
|
||||
private MilkUtilities() {
|
||||
// if no token is set, see if astrid has exported one
|
||||
if(getToken() == null) {
|
||||
try {
|
||||
Context astridContext = ContextManager.getContext().createPackageContext("com.timsu.astrid", 0);
|
||||
SharedPreferences sharedPreferences = astridContext.getSharedPreferences("rtm", 0);
|
||||
if(sharedPreferences != null) {
|
||||
String token = sharedPreferences.getString("rmilk_token", null);
|
||||
long lastSyncDate = sharedPreferences.getLong("rmilk_last_sync", 0);
|
||||
|
||||
Editor editor = getPrefs().edit();
|
||||
editor.putString(getIdentifier() + PREF_TOKEN, token);
|
||||
editor.putLong(getIdentifier() + PREF_LAST_SYNC, lastSyncDate);
|
||||
editor.commit();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// too bad
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
; @Override
|
||||
public String getIdentifier() {
|
||||
return IDENTIFIER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSyncIntervalKey() {
|
||||
return R.string.rmilk_MPr_interval_key;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 2007, MetaDimensional Technologies Inc.
|
||||
*
|
||||
*
|
||||
* This file is part of the RememberTheMilk Java API.
|
||||
*
|
||||
* The RememberTheMilk Java API is free software; you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Lesser General Public License
|
||||
* as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The RememberTheMilk Java API is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.weloveastrid.rmilk.api;
|
||||
|
||||
/**
|
||||
* Encapsulates information about an application that is a client of RememberTheMilk. Includes information required by RTM to connect: the API key and
|
||||
* the shared secret.
|
||||
*
|
||||
* @author Will Ross Jun 22, 2007
|
||||
*/
|
||||
public class ApplicationInfo
|
||||
{
|
||||
|
||||
private final String apiKey;
|
||||
|
||||
private final String sharedSecret;
|
||||
|
||||
private final String name;
|
||||
|
||||
private final String authToken;
|
||||
|
||||
public ApplicationInfo(String apiKey, String sharedSecret, String name)
|
||||
{
|
||||
this(apiKey, sharedSecret, name, null);
|
||||
}
|
||||
|
||||
public ApplicationInfo(String apiKey, String sharedSecret, String name,
|
||||
String authToken)
|
||||
{
|
||||
super();
|
||||
this.apiKey = apiKey;
|
||||
this.sharedSecret = sharedSecret;
|
||||
this.name = name;
|
||||
this.authToken = authToken;
|
||||
}
|
||||
|
||||
public String getApiKey()
|
||||
{
|
||||
return apiKey;
|
||||
}
|
||||
|
||||
public String getSharedSecret()
|
||||
{
|
||||
return sharedSecret;
|
||||
}
|
||||
|
||||
public String getName()
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getAuthToken()
|
||||
{
|
||||
return authToken;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,299 @@
|
||||
/*
|
||||
* Copyright 2007, MetaDimensional Technologies Inc.
|
||||
*
|
||||
*
|
||||
* This file is part of the RememberTheMilk Java API.
|
||||
*
|
||||
* The RememberTheMilk Java API is free software; you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Lesser General Public License
|
||||
* as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The RememberTheMilk Java API is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.weloveastrid.rmilk.api;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLEncoder;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.HttpStatus;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.impl.client.DefaultHttpClient;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.Node;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* Handles the details of invoking a method on the RTM REST API.
|
||||
*
|
||||
* @author Will Ross Jun 21, 2007
|
||||
*/
|
||||
@SuppressWarnings("nls")
|
||||
public class Invoker {
|
||||
|
||||
private static final String SERVICE_UNAVAILABLE_CODE = "105";
|
||||
|
||||
private static final String TAG = "rtm-invoker"; //$NON-NLS-1$
|
||||
|
||||
private static final DocumentBuilder builder;
|
||||
static
|
||||
{
|
||||
// Done this way because the builder is marked "final"
|
||||
DocumentBuilder aBuilder;
|
||||
try
|
||||
{
|
||||
final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
||||
factory.setNamespaceAware(false);
|
||||
factory.setValidating(false);
|
||||
aBuilder = factory.newDocumentBuilder();
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.e(TAG, "Unable to construct a document builder", exception);
|
||||
aBuilder = null;
|
||||
}
|
||||
builder = aBuilder;
|
||||
}
|
||||
|
||||
private static final String ENCODING = "UTF-8"; //$NON-NLS-1$
|
||||
|
||||
private static final String API_SIG_PARAM = "api_sig"; //$NON-NLS-1$
|
||||
|
||||
private static final long INVOCATION_INTERVAL = 400;
|
||||
|
||||
private long lastInvocation;
|
||||
|
||||
private final ApplicationInfo applicationInfo;
|
||||
|
||||
private final MessageDigest digest;
|
||||
|
||||
private final String serviceRelativeUri;
|
||||
|
||||
private final HttpClient httpClient;
|
||||
|
||||
public Invoker(@SuppressWarnings("unused") String serverHostName,
|
||||
@SuppressWarnings("unused") int serverPortNumber,
|
||||
String serviceRelativeUri, ApplicationInfo applicationInfo)
|
||||
throws ServiceInternalException {
|
||||
|
||||
this.serviceRelativeUri = serviceRelativeUri;
|
||||
|
||||
httpClient = new DefaultHttpClient();
|
||||
|
||||
lastInvocation = System.currentTimeMillis();
|
||||
this.applicationInfo = applicationInfo;
|
||||
|
||||
try {
|
||||
digest = MessageDigest.getInstance("md5"); //$NON-NLS-1$
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new ServiceInternalException(
|
||||
"Could not create properly the MD5 digest", e);
|
||||
}
|
||||
}
|
||||
|
||||
private StringBuffer computeRequestUri(Param... params)
|
||||
throws ServiceInternalException {
|
||||
final StringBuffer requestUri = new StringBuffer(serviceRelativeUri);
|
||||
if (params.length > 0) {
|
||||
requestUri.append("?");
|
||||
}
|
||||
for (Param param : params) {
|
||||
try {
|
||||
requestUri.append(param.getName()).append("=").append(
|
||||
URLEncoder.encode(param.getValue(), ENCODING)).append(
|
||||
"&");
|
||||
} catch (Exception exception) {
|
||||
final StringBuffer message = new StringBuffer(
|
||||
"Cannot encode properly the HTTP GET request URI: cannot execute query");
|
||||
Log.e(TAG, message.toString(), exception);
|
||||
throw new ServiceInternalException(message.toString());
|
||||
}
|
||||
}
|
||||
requestUri.append(API_SIG_PARAM).append("=").append(calcApiSig(params));
|
||||
return requestUri;
|
||||
}
|
||||
|
||||
/** Call invoke with a false repeat */
|
||||
public Element invoke(Param... params) throws ServiceException {
|
||||
return invoke(false, params);
|
||||
}
|
||||
|
||||
public Element invoke(boolean repeat, Param... params)
|
||||
throws ServiceException {
|
||||
long timeSinceLastInvocation = System.currentTimeMillis() -
|
||||
lastInvocation;
|
||||
if (timeSinceLastInvocation < INVOCATION_INTERVAL) {
|
||||
// In order not to invoke the RTM service too often
|
||||
try {
|
||||
Thread.sleep(INVOCATION_INTERVAL - timeSinceLastInvocation);
|
||||
} catch (InterruptedException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// We compute the URI
|
||||
final StringBuffer requestUri = computeRequestUri(params);
|
||||
HttpResponse response = null;
|
||||
|
||||
final HttpGet request = new HttpGet("http://" //$NON-NLS-1$
|
||||
+ ServiceImpl.SERVER_HOST_NAME + requestUri.toString());
|
||||
final String methodUri = request.getRequestLine().getUri();
|
||||
|
||||
Element result;
|
||||
try {
|
||||
Log.i(TAG, "Executing the method:" + methodUri); //$NON-NLS-1$
|
||||
response = httpClient.execute(request);
|
||||
lastInvocation = System.currentTimeMillis();
|
||||
|
||||
final int statusCode = response.getStatusLine().getStatusCode();
|
||||
if (statusCode != HttpStatus.SC_OK) {
|
||||
Log.e(TAG, "Method failed: " + response.getStatusLine()); //$NON-NLS-1$
|
||||
|
||||
// Tim: HTTP error. Let's wait a little bit
|
||||
if (!repeat) {
|
||||
try {
|
||||
Thread.sleep(1500);
|
||||
} catch (InterruptedException e) {
|
||||
// ignore
|
||||
}
|
||||
return invoke(true, params);
|
||||
}
|
||||
|
||||
throw new ServiceInternalException("method failed: "
|
||||
+ response.getStatusLine());
|
||||
}
|
||||
|
||||
final Document responseDoc = builder.parse(response.getEntity()
|
||||
.getContent());
|
||||
final Element wrapperElt = responseDoc.getDocumentElement();
|
||||
if (!wrapperElt.getNodeName().equals("rsp")) {
|
||||
throw new ServiceInternalException(
|
||||
"unexpected response returned by RTM service: "
|
||||
+ wrapperElt.getNodeName());
|
||||
} else {
|
||||
String stat = wrapperElt.getAttribute("stat");
|
||||
if (stat.equals("fail")) {
|
||||
Node errElt = wrapperElt.getFirstChild();
|
||||
while (errElt != null
|
||||
&& (errElt.getNodeType() != Node.ELEMENT_NODE || !errElt
|
||||
.getNodeName().equals("err"))) {
|
||||
errElt = errElt.getNextSibling();
|
||||
}
|
||||
if (errElt == null) {
|
||||
throw new ServiceInternalException(
|
||||
"unexpected response returned by RTM service: "
|
||||
+ wrapperElt.getNodeValue());
|
||||
} else {
|
||||
if (SERVICE_UNAVAILABLE_CODE.equals(((Element) errElt)
|
||||
.getAttribute("code")) && !repeat) {
|
||||
try {
|
||||
Thread.sleep(1500);
|
||||
} catch (InterruptedException e) {
|
||||
// ignore
|
||||
}
|
||||
return invoke(true, params);
|
||||
}
|
||||
|
||||
throw new ServiceException(Integer
|
||||
.parseInt(((Element) errElt)
|
||||
.getAttribute("code")),
|
||||
((Element) errElt).getAttribute("msg"));
|
||||
}
|
||||
} else {
|
||||
Node dataElt = wrapperElt.getFirstChild();
|
||||
while (dataElt != null
|
||||
&& (dataElt.getNodeType() != Node.ELEMENT_NODE || dataElt
|
||||
.getNodeName().equals("transaction") == true)) {
|
||||
try {
|
||||
Node nextSibling = dataElt.getNextSibling();
|
||||
if (nextSibling == null) {
|
||||
break;
|
||||
} else {
|
||||
dataElt = nextSibling;
|
||||
}
|
||||
} catch (IndexOutOfBoundsException exception) {
|
||||
// Some implementation may throw this exception,
|
||||
// instead of returning a null sibling
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (dataElt == null) {
|
||||
throw new ServiceInternalException(
|
||||
"unexpected response returned by RTM service: "
|
||||
+ wrapperElt.getNodeValue());
|
||||
} else {
|
||||
result = (Element) dataElt;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new ServiceInternalException("Error making connection: " +
|
||||
e.getMessage(), e);
|
||||
} catch (SAXException e) {
|
||||
// repeat call if possible.
|
||||
if(!repeat)
|
||||
return invoke(true, params);
|
||||
else
|
||||
throw new ServiceInternalException("Error parsing response. " +
|
||||
"Please try sync again!", e);
|
||||
} finally {
|
||||
httpClient.getConnectionManager().closeExpiredConnections();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
final String calcApiSig(Param... params) throws ServiceInternalException {
|
||||
try {
|
||||
digest.reset();
|
||||
digest.update(applicationInfo.getSharedSecret().getBytes(ENCODING));
|
||||
List<Param> sorted = Arrays.asList(params);
|
||||
Collections.sort(sorted);
|
||||
for (Param param : sorted) {
|
||||
digest.update(param.getName().getBytes(ENCODING));
|
||||
digest.update(param.getValue().getBytes(ENCODING));
|
||||
}
|
||||
return convertToHex(digest.digest());
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new ServiceInternalException(
|
||||
"cannot hahdle properly the encoding", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static String convertToHex(byte[] data) {
|
||||
StringBuffer buf = new StringBuffer();
|
||||
for (int i = 0; i < data.length; i++) {
|
||||
int halfbyte = (data[i] >>> 4) & 0x0F;
|
||||
int two_halfs = 0;
|
||||
do {
|
||||
if ((0 <= halfbyte) && (halfbyte <= 9))
|
||||
buf.append((char) ('0' + halfbyte));
|
||||
else
|
||||
buf.append((char) ('a' + (halfbyte - 10)));
|
||||
halfbyte = data[i] & 0x0F;
|
||||
} while (two_halfs++ < 1);
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright 2007, MetaDimensional Technologies Inc.
|
||||
*
|
||||
*
|
||||
* This file is part of the RememberTheMilk Java API.
|
||||
*
|
||||
* The RememberTheMilk Java API is free software; you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Lesser General Public License
|
||||
* as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The RememberTheMilk Java API is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.weloveastrid.rmilk.api;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import org.weloveastrid.rmilk.api.data.RtmData;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Will Ross Jun 21, 2007
|
||||
*/
|
||||
public class Param implements Comparable<Param> {
|
||||
|
||||
private final String name;
|
||||
|
||||
private final String value;
|
||||
|
||||
public Param(String name, String value) {
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public Param(String name, Date value) {
|
||||
this.name = name;
|
||||
this.value = RtmData.formatDate(value);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public int compareTo(Param p) {
|
||||
return name.compareTo(p.getName());
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2007, MetaDimensional Technologies Inc.
|
||||
*
|
||||
*
|
||||
* This file is part of the RememberTheMilk Java API.
|
||||
*
|
||||
* The RememberTheMilk Java API is free software; you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Lesser General Public License
|
||||
* as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The RememberTheMilk Java API is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.weloveastrid.rmilk.api;
|
||||
|
||||
import java.util.prefs.Preferences;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Will Ross Jun 21, 2007
|
||||
*/
|
||||
public class Prefs {
|
||||
|
||||
Preferences preferences;
|
||||
|
||||
public enum PrefKey {
|
||||
AuthToken
|
||||
}
|
||||
|
||||
public Prefs() {
|
||||
preferences = Preferences.userNodeForPackage(Prefs.class);
|
||||
}
|
||||
|
||||
public String getAuthToken() {
|
||||
return preferences.get(PrefKey.AuthToken.toString(), null);
|
||||
}
|
||||
|
||||
public void setAuthToken(String authToken) {
|
||||
preferences.put(PrefKey.AuthToken.toString(), authToken);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2007, MetaDimensional Technologies Inc.
|
||||
*
|
||||
*
|
||||
* This file is part of the RememberTheMilk Java API.
|
||||
*
|
||||
* The RememberTheMilk Java API is free software; you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Lesser General Public License
|
||||
* as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The RememberTheMilk Java API is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.weloveastrid.rmilk.api;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Will Ross Jun 21, 2007
|
||||
*/
|
||||
@SuppressWarnings("nls")
|
||||
public class ServiceException extends IOException {
|
||||
|
||||
private static final long serialVersionUID = -6711156026040643361L;
|
||||
|
||||
int responseCode;
|
||||
|
||||
String responseMessage;
|
||||
|
||||
public ServiceException(int responseCode, String responseMessage) {
|
||||
super("Service invocation failed. Code: " + responseCode + "; message: " + responseMessage);
|
||||
this.responseCode = responseCode;
|
||||
this.responseMessage = responseMessage;
|
||||
}
|
||||
|
||||
int getResponseCode() {
|
||||
return responseCode;
|
||||
}
|
||||
|
||||
String getResponseMessage() {
|
||||
return responseMessage;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,617 @@
|
||||
/*
|
||||
* Copyright 2007, MetaDimensional Technologies Inc.
|
||||
*
|
||||
*
|
||||
* This file is part of the RememberTheMilk Java API.
|
||||
*
|
||||
* The RememberTheMilk Java API is free software; you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Lesser General Public License
|
||||
* as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The RememberTheMilk Java API is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.weloveastrid.rmilk.api;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
|
||||
import org.w3c.dom.Element;
|
||||
import org.weloveastrid.rmilk.api.data.RtmAuth;
|
||||
import org.weloveastrid.rmilk.api.data.RtmData;
|
||||
import org.weloveastrid.rmilk.api.data.RtmFrob;
|
||||
import org.weloveastrid.rmilk.api.data.RtmList;
|
||||
import org.weloveastrid.rmilk.api.data.RtmLists;
|
||||
import org.weloveastrid.rmilk.api.data.RtmLocation;
|
||||
import org.weloveastrid.rmilk.api.data.RtmTask;
|
||||
import org.weloveastrid.rmilk.api.data.RtmTask.Priority;
|
||||
import org.weloveastrid.rmilk.api.data.RtmTaskList;
|
||||
import org.weloveastrid.rmilk.api.data.RtmTaskNote;
|
||||
import org.weloveastrid.rmilk.api.data.RtmTaskSeries;
|
||||
import org.weloveastrid.rmilk.api.data.RtmTasks;
|
||||
import org.weloveastrid.rmilk.api.data.RtmTimeline;
|
||||
|
||||
|
||||
/**
|
||||
* A major part of the RTM API implementation is here.
|
||||
*
|
||||
* @author Will Ross Jun 21, 2007
|
||||
* @author Edouard Mercier, since 2008.04.15
|
||||
* @author timsu January 2009
|
||||
*/
|
||||
@SuppressWarnings("nls")
|
||||
public class ServiceImpl
|
||||
{
|
||||
|
||||
public final static String SERVER_HOST_NAME = "api.rememberthemilk.com"; //$NON-NLS-1$
|
||||
|
||||
public final static int SERVER_PORT_NUMBER = 80;
|
||||
|
||||
public final static String REST_SERVICE_URL_POSTFIX = "/services/rest/"; //$NON-NLS-1$
|
||||
|
||||
private final ApplicationInfo applicationInfo;
|
||||
|
||||
private final Invoker invoker;
|
||||
|
||||
private final Prefs prefs;
|
||||
|
||||
private String currentAuthToken;
|
||||
|
||||
RtmFrob tempFrob;
|
||||
|
||||
public ServiceImpl(ApplicationInfo applicationInfo)
|
||||
throws ServiceInternalException
|
||||
{
|
||||
invoker = new Invoker(SERVER_HOST_NAME, SERVER_PORT_NUMBER, REST_SERVICE_URL_POSTFIX, applicationInfo);
|
||||
this.applicationInfo = applicationInfo;
|
||||
prefs = new Prefs();
|
||||
if (applicationInfo.getAuthToken() != null)
|
||||
{
|
||||
currentAuthToken = applicationInfo.getAuthToken();
|
||||
}
|
||||
else
|
||||
{
|
||||
currentAuthToken = prefs.getAuthToken();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isServiceAuthorized()
|
||||
throws ServiceException
|
||||
{
|
||||
if (currentAuthToken == null)
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
/* RtmAuth auth = */auth_checkToken(currentAuthToken);
|
||||
return true;
|
||||
}
|
||||
catch (ServiceException e)
|
||||
{
|
||||
if (e.getResponseCode() != 98)
|
||||
{
|
||||
throw e;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Bad token.
|
||||
currentAuthToken = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String beginAuthorization(RtmAuth.Perms permissions)
|
||||
throws ServiceException
|
||||
{
|
||||
// Instructions from the "User authentication for desktop applications"
|
||||
// section at http://www.rememberthemilk.com/services/api/authentication.rtm
|
||||
tempFrob = auth_getFrob();
|
||||
return beginAuthorization(tempFrob, permissions);
|
||||
}
|
||||
|
||||
public String beginAuthorization(RtmFrob frob, RtmAuth.Perms permissions)
|
||||
throws ServiceException
|
||||
{
|
||||
String authBaseUrl = "http://" + SERVER_HOST_NAME + "/services/auth/";
|
||||
Param[] params = new Param[] { new Param("api_key", applicationInfo.getApiKey()), new Param("perms", permissions.toString()),
|
||||
new Param("frob", frob.getValue()) };
|
||||
Param sig = new Param("api_sig", invoker.calcApiSig(params));
|
||||
StringBuilder authUrl = new StringBuilder(authBaseUrl);
|
||||
authUrl.append("?");
|
||||
for (Param param : params)
|
||||
{
|
||||
authUrl.append(param.getName()).append("=").append(param.getValue()).append("&");
|
||||
}
|
||||
authUrl.append(sig.getName()).append("=").append(sig.getValue());
|
||||
return authUrl.toString();
|
||||
}
|
||||
|
||||
public String completeAuthorization()
|
||||
throws ServiceException
|
||||
{
|
||||
return completeAuthorization(tempFrob);
|
||||
}
|
||||
|
||||
public String completeAuthorization(RtmFrob frob)
|
||||
throws ServiceException
|
||||
{
|
||||
currentAuthToken = auth_getToken(frob.getValue());
|
||||
prefs.setAuthToken(currentAuthToken);
|
||||
return currentAuthToken;
|
||||
}
|
||||
|
||||
public RtmAuth auth_checkToken(String authToken)
|
||||
throws ServiceException
|
||||
{
|
||||
Element response = invoker.invoke(new Param("method", "rtm.auth.checkToken"), new Param("auth_token", authToken),
|
||||
new Param("api_key", applicationInfo.getApiKey()));
|
||||
return new RtmAuth(response);
|
||||
}
|
||||
|
||||
public RtmFrob auth_getFrob()
|
||||
throws ServiceException
|
||||
{
|
||||
return new RtmFrob(invoker.invoke(new Param("method", "rtm.auth.getFrob"), new Param("api_key", applicationInfo.getApiKey())));
|
||||
}
|
||||
|
||||
public String auth_getToken(String frob)
|
||||
throws ServiceException
|
||||
{
|
||||
Element response = invoker.invoke(new Param("method", "rtm.auth.getToken"), new Param("frob", frob), new Param("api_key", applicationInfo.getApiKey()));
|
||||
return new RtmAuth(response).getToken();
|
||||
}
|
||||
|
||||
public void contacts_add()
|
||||
{
|
||||
throw new UnsupportedOperationException("Not supported yet.");
|
||||
}
|
||||
|
||||
public void contacts_delete()
|
||||
{
|
||||
throw new UnsupportedOperationException("Not supported yet.");
|
||||
}
|
||||
|
||||
public void contacts_getList()
|
||||
{
|
||||
throw new UnsupportedOperationException("Not supported yet.");
|
||||
}
|
||||
|
||||
public void groups_add()
|
||||
{
|
||||
throw new UnsupportedOperationException("Not supported yet.");
|
||||
}
|
||||
|
||||
public void groups_addContact()
|
||||
{
|
||||
throw new UnsupportedOperationException("Not supported yet.");
|
||||
}
|
||||
|
||||
public void groups_delete()
|
||||
{
|
||||
throw new UnsupportedOperationException("Not supported yet.");
|
||||
}
|
||||
|
||||
public void groups_getList()
|
||||
{
|
||||
throw new UnsupportedOperationException("Not supported yet.");
|
||||
}
|
||||
|
||||
public void groups_removeContact()
|
||||
{
|
||||
throw new UnsupportedOperationException("Not supported yet.");
|
||||
}
|
||||
|
||||
public RtmList lists_add(String timelineId, String listName)
|
||||
throws ServiceException
|
||||
{
|
||||
Element response = invoker.invoke(new Param("method", "rtm.lists.add"), new Param("auth_token", currentAuthToken),
|
||||
new Param("api_key", applicationInfo.getApiKey()), new Param("name", listName), new Param("timeline", timelineId));
|
||||
return new RtmList(response);
|
||||
}
|
||||
|
||||
public void lists_archive()
|
||||
{
|
||||
throw new UnsupportedOperationException("Not supported yet.");
|
||||
}
|
||||
|
||||
public void lists_delete(String timelineId, String listId)
|
||||
throws ServiceException
|
||||
{
|
||||
invoker.invoke(new Param("method", "rtm.lists.delete"), new Param("auth_token", currentAuthToken), new Param("api_key", applicationInfo.getApiKey()),
|
||||
new Param("timeline", timelineId), new Param("list_id", listId));
|
||||
}
|
||||
|
||||
public RtmLists lists_getList()
|
||||
throws ServiceException
|
||||
{
|
||||
Element response = invoker.invoke(new Param("method", "rtm.lists.getList"), new Param("auth_token", currentAuthToken),
|
||||
new Param("api_key", applicationInfo.getApiKey()));
|
||||
return new RtmLists(response);
|
||||
}
|
||||
|
||||
public RtmList lists_getList(String listName)
|
||||
throws ServiceException
|
||||
{
|
||||
RtmLists fullList = lists_getList();
|
||||
for (Entry<String, RtmList> entry : fullList.getLists().entrySet())
|
||||
{
|
||||
if (entry.getValue().getName().equals(listName))
|
||||
{
|
||||
return entry.getValue();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void lists_setDefaultList()
|
||||
{
|
||||
throw new UnsupportedOperationException("Not supported yet.");
|
||||
}
|
||||
|
||||
public RtmList lists_setName(String timelineId, String listId, String newName)
|
||||
throws ServiceException
|
||||
{
|
||||
Element response = invoker.invoke(new Param("method", "rtm.lists.setName"), new Param("timeline", timelineId), new Param("list_id", listId),
|
||||
new Param("name", newName), new Param("auth_token", currentAuthToken), new Param("api_key", applicationInfo.getApiKey()));
|
||||
return new RtmList(response);
|
||||
}
|
||||
|
||||
public void lists_unarchive()
|
||||
{
|
||||
throw new UnsupportedOperationException("Not supported yet.");
|
||||
}
|
||||
|
||||
public void reflection_getMethodInfo()
|
||||
{
|
||||
throw new UnsupportedOperationException("Not supported yet.");
|
||||
}
|
||||
|
||||
public void reflection_getMethods()
|
||||
{
|
||||
throw new UnsupportedOperationException("Not supported yet.");
|
||||
}
|
||||
|
||||
public void settings_getList()
|
||||
{
|
||||
throw new UnsupportedOperationException("Not supported yet.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a task, name, to the list specified by list_id.
|
||||
* @param timelineId
|
||||
* @param listId can be null to omit this parameter (assumes Inbox)
|
||||
* @param name
|
||||
* @return
|
||||
* @throws ServiceException
|
||||
*/
|
||||
public RtmTaskSeries tasks_add(String timelineId, String listId, String name)
|
||||
throws ServiceException
|
||||
{
|
||||
Element response;
|
||||
if(listId != null)
|
||||
response = invoker.invoke(new Param("method", "rtm.tasks.add"), new Param("timeline", timelineId), new Param("list_id", listId),
|
||||
new Param("name", name), new Param("auth_token", currentAuthToken), new Param("api_key", applicationInfo.getApiKey()));
|
||||
else
|
||||
response = invoker.invoke(new Param("method", "rtm.tasks.add"), new Param("timeline", timelineId),
|
||||
new Param("name", name), new Param("auth_token", currentAuthToken), new Param("api_key", applicationInfo.getApiKey()));
|
||||
|
||||
RtmTaskList rtmTaskList = new RtmTaskList(response);
|
||||
if (rtmTaskList.getSeries().size() == 1)
|
||||
{
|
||||
return rtmTaskList.getSeries().get(0);
|
||||
}
|
||||
else if (rtmTaskList.getSeries().size() > 1)
|
||||
{
|
||||
throw new ServiceInternalException("Internal error: more that one task (" + rtmTaskList.getSeries().size() + ") has been created");
|
||||
}
|
||||
throw new ServiceInternalException("Internal error: no task has been created");
|
||||
}
|
||||
|
||||
public void tasks_addTags()
|
||||
{
|
||||
throw new UnsupportedOperationException("Not supported yet.");
|
||||
}
|
||||
|
||||
public void tasks_complete(String timelineId, String listId, String taskSeriesId, String taskId)
|
||||
throws ServiceException
|
||||
{
|
||||
invoker.invoke(new Param("method", "rtm.tasks.complete"), new Param("timeline", timelineId), new Param("list_id", listId),
|
||||
new Param("taskseries_id", taskSeriesId), new Param("task_id", taskId), new Param("auth_token", currentAuthToken),
|
||||
new Param("api_key", applicationInfo.getApiKey()));
|
||||
}
|
||||
|
||||
public void tasks_delete(String timelineId, String listId, String taskSeriesId, String taskId)
|
||||
throws ServiceException
|
||||
{
|
||||
invoker.invoke(new Param("method", "rtm.tasks.delete"), new Param("timeline", timelineId), new Param("list_id", listId),
|
||||
new Param("taskseries_id", taskSeriesId), new Param("task_id", taskId), new Param("auth_token", currentAuthToken),
|
||||
new Param("api_key", applicationInfo.getApiKey()));
|
||||
}
|
||||
|
||||
public RtmTasks tasks_getList(String listId, String filter, Date lastSync)
|
||||
throws ServiceException
|
||||
{
|
||||
Set<Param> params = new HashSet<Param>();
|
||||
params.add(new Param("method", "rtm.tasks.getList"));
|
||||
if (listId != null)
|
||||
{
|
||||
params.add(new Param("list_id", listId));
|
||||
}
|
||||
if (filter != null)
|
||||
{
|
||||
params.add(new Param("filter", filter));
|
||||
}
|
||||
if (lastSync != null)
|
||||
{
|
||||
params.add(new Param("last_sync", lastSync));
|
||||
}
|
||||
params.add(new Param("auth_token", currentAuthToken));
|
||||
params.add(new Param("api_key", applicationInfo.getApiKey()));
|
||||
return new RtmTasks(invoker.invoke(params.toArray(new Param[params.size()])));
|
||||
}
|
||||
|
||||
public RtmTaskSeries tasks_getTask(String taskName)
|
||||
throws ServiceException
|
||||
{
|
||||
return tasks_getTask(null, taskName);
|
||||
}
|
||||
|
||||
public RtmTaskSeries tasks_getTask(String taskSeriesId, String taskName)
|
||||
throws ServiceException
|
||||
{
|
||||
Set<Param> params = new HashSet<Param>();
|
||||
params.add(new Param("method", "rtm.tasks.getList"));
|
||||
params.add(new Param("auth_token", currentAuthToken));
|
||||
params.add(new Param("api_key", applicationInfo.getApiKey()));
|
||||
params.add(new Param("filter", "name:\"" + taskName+"\""));
|
||||
RtmTasks rtmTasks = new RtmTasks(invoker.invoke(params.toArray(new Param[params.size()])));
|
||||
return findTask(taskSeriesId, rtmTasks);
|
||||
}
|
||||
|
||||
private RtmTaskSeries findTask(String taskId, RtmTasks rtmTasks)
|
||||
{
|
||||
for (RtmTaskList list : rtmTasks.getLists())
|
||||
{
|
||||
for (RtmTaskSeries series : list.getSeries())
|
||||
{
|
||||
if (taskId != null)
|
||||
{
|
||||
if (series.getId().equals(taskId))
|
||||
{
|
||||
return series;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return series;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void tasks_movePriority()
|
||||
{
|
||||
throw new UnsupportedOperationException("Not supported yet.");
|
||||
}
|
||||
|
||||
public RtmTaskSeries tasks_moveTo(String timelineId, String fromListId, String toListId, String taskSeriesId, String taskId)
|
||||
throws ServiceException
|
||||
{
|
||||
if(fromListId.equals(toListId))
|
||||
return null;
|
||||
Element elt = invoker.invoke(new Param("method", "rtm.tasks.moveTo"), new Param("timeline", timelineId), new Param("from_list_id", fromListId),
|
||||
new Param("to_list_id", toListId), new Param("taskseries_id", taskSeriesId), new Param("task_id", taskId), new Param("auth_token", currentAuthToken),
|
||||
new Param("api_key", applicationInfo.getApiKey()));
|
||||
RtmTaskList rtmTaskList = new RtmTaskList(elt);
|
||||
return findTask(taskSeriesId, taskId, rtmTaskList);
|
||||
}
|
||||
|
||||
public void tasks_postpone()
|
||||
{
|
||||
throw new UnsupportedOperationException("Not supported yet.");
|
||||
}
|
||||
|
||||
public void tasks_removeTags()
|
||||
{
|
||||
throw new UnsupportedOperationException("Not supported yet.");
|
||||
}
|
||||
|
||||
public void tasks_setDueDate(String timelineId, String listId, String taskSeriesId, String taskId, Date due, boolean hasDueTime)
|
||||
throws ServiceException
|
||||
{
|
||||
final boolean setDueDate = (due != null);
|
||||
if (setDueDate == true)
|
||||
{
|
||||
invoker.invoke(new Param("method", "rtm.tasks.setDueDate"), new Param("timeline", timelineId), new Param("list_id", listId),
|
||||
new Param("taskseries_id", taskSeriesId), new Param("task_id", taskId), new Param("due", due), new Param("has_due_time", hasDueTime ? "1" : "0"),
|
||||
new Param("auth_token", currentAuthToken), new Param("api_key", applicationInfo.getApiKey()));
|
||||
}
|
||||
else
|
||||
{
|
||||
invoker.invoke(new Param("method", "rtm.tasks.setDueDate"), new Param("timeline", timelineId), new Param("list_id", listId),
|
||||
new Param("taskseries_id", taskSeriesId), new Param("task_id", taskId), new Param("auth_token", currentAuthToken),
|
||||
new Param("api_key", applicationInfo.getApiKey()));
|
||||
}
|
||||
}
|
||||
|
||||
public void tasks_setEstimate(String timelineId, String listId, String taskSeriesId, String taskId, String newEstimate)
|
||||
throws ServiceException
|
||||
{
|
||||
invoker.invoke(new Param("method", "rtm.tasks.setEstimate"), new Param("timeline", timelineId), new Param("list_id", listId),
|
||||
new Param("taskseries_id", taskSeriesId), new Param("task_id", taskId), new Param("estimate", newEstimate), new Param("auth_token", currentAuthToken),
|
||||
new Param("api_key", applicationInfo.getApiKey()));
|
||||
}
|
||||
|
||||
public void tasks_setName(String timelineId, String listId, String taskSeriesId, String taskId, String newName)
|
||||
throws ServiceException
|
||||
{
|
||||
invoker.invoke(new Param("method", "rtm.tasks.setName"), new Param("timeline", timelineId), new Param("list_id", listId),
|
||||
new Param("taskseries_id", taskSeriesId), new Param("task_id", taskId), new Param("name", newName), new Param("auth_token", currentAuthToken),
|
||||
new Param("api_key", applicationInfo.getApiKey()));
|
||||
}
|
||||
|
||||
private RtmTaskSeries findTask(String taskSeriesId, String taskId, RtmTaskList rtmTaskList)
|
||||
{
|
||||
for (RtmTaskSeries series : rtmTaskList.getSeries())
|
||||
{
|
||||
if (series.getId().equals(taskSeriesId) && series.getTask().getId().equals(taskId))
|
||||
{
|
||||
return series;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void tasks_setPriority(String timelineId, String listId, String taskSeriesId, String taskId, Priority priority)
|
||||
throws ServiceException
|
||||
{
|
||||
invoker.invoke(new Param("method", "rtm.tasks.setPriority"), new Param("timeline", timelineId), new Param("list_id", listId),
|
||||
new Param("taskseries_id", taskSeriesId), new Param("task_id", taskId), new Param("priority", RtmTask.convertPriority(priority)),
|
||||
new Param("auth_token", currentAuthToken), new Param("api_key", applicationInfo.getApiKey()));
|
||||
}
|
||||
|
||||
public void tasks_setRecurrence(String timelineId, String listId, String taskSeriesId, String taskId, String repeat)
|
||||
throws ServiceException
|
||||
{
|
||||
invoker.invoke(new Param("method", "rtm.tasks.setRecurrence"), new Param("timeline", timelineId), new Param("list_id", listId),
|
||||
new Param("taskseries_id", taskSeriesId), new Param("task_id", taskId), new Param("repeat", repeat),
|
||||
new Param("auth_token", currentAuthToken), new Param("api_key", applicationInfo.getApiKey()));
|
||||
}
|
||||
|
||||
public void tasks_setTags(String timelineId, String listId,
|
||||
String taskSeriesId, String taskId, String[] tags) throws ServiceException
|
||||
{
|
||||
StringBuilder tagString = new StringBuilder();
|
||||
if(tags != null) {
|
||||
for(int i = 0; i < tags.length; i++) {
|
||||
tagString.append(tags[i].replace(" ", "_"));
|
||||
if(i < tags.length - 1)
|
||||
tagString.append(",");
|
||||
}
|
||||
}
|
||||
invoker.invoke(new Param("method", "rtm.tasks.setTags"), new Param("timeline", timelineId), new Param("list_id", listId),
|
||||
new Param("taskseries_id", taskSeriesId), new Param("task_id", taskId), new Param("tags", tagString.toString()), new Param("auth_token", currentAuthToken),
|
||||
new Param("api_key", applicationInfo.getApiKey()));
|
||||
}
|
||||
|
||||
public void tasks_setURL()
|
||||
{
|
||||
throw new UnsupportedOperationException("Not supported yet.");
|
||||
}
|
||||
|
||||
public void tasks_uncomplete(String timelineId, String listId, String taskSeriesId, String taskId)
|
||||
throws ServiceException
|
||||
{
|
||||
invoker.invoke(new Param("method", "rtm.tasks.uncomplete"), new Param("timeline", timelineId), new Param("list_id", listId),
|
||||
new Param("taskseries_id", taskSeriesId), new Param("task_id", taskId), new Param("auth_token", currentAuthToken),
|
||||
new Param("api_key", applicationInfo.getApiKey()));
|
||||
}
|
||||
|
||||
public RtmTaskNote tasks_notes_add(String timelineId, String listId, String taskSeriesId, String taskId, String title, String text)
|
||||
throws ServiceException
|
||||
{
|
||||
Element elt = invoker.invoke(new Param("method", "rtm.tasks.notes.add"), new Param("timeline", timelineId), new Param("list_id", listId),
|
||||
new Param("taskseries_id", taskSeriesId), new Param("task_id", taskId), new Param("note_title", title), new Param("note_text", text),
|
||||
new Param("auth_token", currentAuthToken), new Param("api_key", applicationInfo.getApiKey()));
|
||||
return new RtmTaskNote(elt);
|
||||
}
|
||||
|
||||
public void tasks_notes_delete(String timelineId, String noteId)
|
||||
throws ServiceException
|
||||
{
|
||||
invoker.invoke(new Param("method", "rtm.tasks.notes.delete"), new Param("timeline", timelineId), new Param("note_id", noteId),
|
||||
new Param("auth_token", currentAuthToken), new Param("api_key", applicationInfo.getApiKey()));
|
||||
}
|
||||
|
||||
public RtmTaskNote tasks_notes_edit(String timelineId, String noteId, String title, String text)
|
||||
throws ServiceException
|
||||
{
|
||||
Element elt = invoker.invoke(new Param("method", "rtm.tasks.notes.edit"), new Param("timeline", timelineId), new Param("note_id", noteId),
|
||||
new Param("note_title", title), new Param("note_text", text), new Param("auth_token", currentAuthToken),
|
||||
new Param("api_key", applicationInfo.getApiKey()));
|
||||
return new RtmTaskNote(elt);
|
||||
}
|
||||
|
||||
public RtmTaskSeries tasks_setLocation(String timelineId, String listId, String taskSeriesId, String taskId, String locationId)
|
||||
throws ServiceException
|
||||
{
|
||||
Element response = invoker.invoke(new Param("method", "rtm.tasks.setLocation"), new Param("timeline", timelineId), new Param("list_id", listId),
|
||||
new Param("taskseries_id", taskSeriesId), new Param("task_id", taskId), new Param("location_id", locationId),
|
||||
new Param("auth_token", currentAuthToken), new Param("api_key", applicationInfo.getApiKey()));
|
||||
RtmTaskList rtmTaskList = new RtmTaskList(response);
|
||||
return findTask(taskSeriesId, taskId, rtmTaskList);
|
||||
}
|
||||
|
||||
public RtmTaskSeries tasks_setURL(String timelineId, String listId, String taskSeriesId, String taskId, String url)
|
||||
throws ServiceException
|
||||
{
|
||||
Element response = invoker.invoke(new Param("method", "rtm.tasks.setURL"), new Param("timeline", timelineId), new Param("list_id", listId),
|
||||
new Param("taskseries_id", taskSeriesId), new Param("task_id", taskId), new Param("url", url), new Param("auth_token", currentAuthToken),
|
||||
new Param("api_key", applicationInfo.getApiKey()));
|
||||
RtmTaskList rtmTaskList = new RtmTaskList(response);
|
||||
return findTask(taskSeriesId, taskId, rtmTaskList);
|
||||
}
|
||||
|
||||
public void test_echo()
|
||||
{
|
||||
throw new UnsupportedOperationException("Not supported yet.");
|
||||
}
|
||||
|
||||
public void test_login()
|
||||
{
|
||||
throw new UnsupportedOperationException("Not supported yet.");
|
||||
}
|
||||
|
||||
public void time_convert()
|
||||
{
|
||||
throw new UnsupportedOperationException("Not supported yet.");
|
||||
}
|
||||
|
||||
public void time_parse()
|
||||
{
|
||||
throw new UnsupportedOperationException("Not supported yet.");
|
||||
}
|
||||
|
||||
public String timelines_create()
|
||||
throws ServiceException
|
||||
{
|
||||
return new RtmTimeline(invoker.invoke(new Param("method", "rtm.timelines.create"), new Param("auth_token", currentAuthToken),
|
||||
new Param("api_key", applicationInfo.getApiKey()))).getId();
|
||||
}
|
||||
|
||||
public void timezones_getList()
|
||||
{
|
||||
throw new UnsupportedOperationException("Not supported yet.");
|
||||
}
|
||||
|
||||
public void transactions_undo()
|
||||
{
|
||||
throw new UnsupportedOperationException("Not supported yet.");
|
||||
}
|
||||
|
||||
public List<RtmLocation> locations_getList()
|
||||
throws ServiceException
|
||||
{
|
||||
Element result = invoker.invoke(new Param("method", "rtm.locations.getList"), new Param("auth_token", currentAuthToken),
|
||||
new Param("api_key", applicationInfo.getApiKey()));
|
||||
List<RtmLocation> locations = new ArrayList<RtmLocation>();
|
||||
for (Element child : RtmData.children(result, "location"))
|
||||
{
|
||||
locations.add(new RtmLocation(child));
|
||||
}
|
||||
return locations;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright 2007, MetaDimensional Technologies Inc.
|
||||
*
|
||||
*
|
||||
* This file is part of the RememberTheMilk Java API.
|
||||
*
|
||||
* The RememberTheMilk Java API is free software; you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Lesser General Public License
|
||||
* as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The RememberTheMilk Java API is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.weloveastrid.rmilk.api;
|
||||
|
||||
|
||||
/**
|
||||
* Introduced in order to get rid of the {@link RuntimeException}, and have only one time of regular exception to cope with, from the API end-user
|
||||
* point of view.
|
||||
*
|
||||
* @author Edouard Mercier
|
||||
* @since 2008.04.23
|
||||
*/
|
||||
public class ServiceInternalException
|
||||
extends ServiceException
|
||||
{
|
||||
private static final long serialVersionUID = -423838945284984432L;
|
||||
|
||||
private final Exception enclosedException;
|
||||
|
||||
public ServiceInternalException(String message)
|
||||
{
|
||||
this(message, null);
|
||||
}
|
||||
|
||||
public ServiceInternalException(String message, Exception exception)
|
||||
{
|
||||
super(-1, message);
|
||||
this.enclosedException = exception;
|
||||
}
|
||||
|
||||
public Exception getEnclosedException()
|
||||
{
|
||||
return enclosedException;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright 2007, MetaDimensional Technologies Inc.
|
||||
*
|
||||
*
|
||||
* This file is part of the RememberTheMilk Java API.
|
||||
*
|
||||
* The RememberTheMilk Java API is free software; you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Lesser General Public License
|
||||
* as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The RememberTheMilk Java API is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.weloveastrid.rmilk.api.data;
|
||||
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Will Ross Jun 21, 2007
|
||||
*/
|
||||
@SuppressWarnings("nls")
|
||||
public class RtmAuth extends RtmData {
|
||||
|
||||
public enum Perms {
|
||||
read, write, delete
|
||||
}
|
||||
|
||||
private final String token;
|
||||
|
||||
private final Perms perms;
|
||||
|
||||
private final RtmUser user;
|
||||
|
||||
public RtmAuth(String token, Perms perms, RtmUser user) {
|
||||
this.token = token;
|
||||
this.perms = perms;
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
public RtmAuth(Element elt) {
|
||||
if (!elt.getNodeName().equals("auth")) { throw new IllegalArgumentException("Element " + elt.getNodeName() + " does not represent an Auth object."); }
|
||||
|
||||
this.token = text(child(elt, "token"));
|
||||
this.perms = Enum.valueOf(Perms.class, text(child(elt, "perms")));
|
||||
this.user = new RtmUser(child(elt, "user"));
|
||||
}
|
||||
|
||||
public String getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
public Perms getPerms() {
|
||||
return perms;
|
||||
}
|
||||
|
||||
public RtmUser getUser() {
|
||||
return user;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,120 @@
|
||||
/*
|
||||
* Copyright 2007, MetaDimensional Technologies Inc.
|
||||
*
|
||||
*
|
||||
* This file is part of the RememberTheMilk Java API.
|
||||
*
|
||||
* The RememberTheMilk Java API is free software; you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Lesser General Public License
|
||||
* as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The RememberTheMilk Java API is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.weloveastrid.rmilk.api.data;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Will Ross Jun 21, 2007
|
||||
*/
|
||||
@SuppressWarnings("nls")
|
||||
public abstract class RtmData
|
||||
{
|
||||
|
||||
private static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
|
||||
|
||||
public RtmData() {
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* The method is not optimized at most, but circumvents a bug in Android runtime.
|
||||
*/
|
||||
public static Element child(Element elt, String nodeName)
|
||||
{
|
||||
NodeList childNodes = elt.getChildNodes();
|
||||
for (int index = 0; index < childNodes.getLength(); index++)
|
||||
{
|
||||
Node child = childNodes.item(index);
|
||||
if (child.getNodeType() == Node.ELEMENT_NODE && child.getNodeName().equals(nodeName))
|
||||
{
|
||||
return (Element) child;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The method is not optimized at most, but circumvents a bug in Android runtime.
|
||||
*/
|
||||
public static List<Element> children(Element elt, String nodeName)
|
||||
{
|
||||
List<Element> result = new ArrayList<Element>();
|
||||
NodeList childNodes = elt.getChildNodes();
|
||||
for (int index = 0; index < childNodes.getLength(); index++)
|
||||
{
|
||||
Node child = childNodes.item(index);
|
||||
if (child.getNodeType() == Node.ELEMENT_NODE && child.getNodeName().equals(nodeName))
|
||||
{
|
||||
result.add((Element) child);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
protected String text(Element elt)
|
||||
{
|
||||
StringBuilder result = new StringBuilder();
|
||||
Node child = elt.getFirstChild();
|
||||
while (child != null)
|
||||
{
|
||||
switch (child.getNodeType())
|
||||
{
|
||||
case Node.TEXT_NODE:
|
||||
case Node.CDATA_SECTION_NODE:
|
||||
result.append(child.getNodeValue());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
child = child.getNextSibling();
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
public synchronized static Date parseDate(String s)
|
||||
{
|
||||
try
|
||||
{
|
||||
Date d = DATE_FORMAT.parse(s);
|
||||
return new Date(d.getTime() + TimeZone.getDefault().getOffset(d.getTime()));
|
||||
}
|
||||
catch (ParseException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized static String formatDate(Date d)
|
||||
{
|
||||
return DATE_FORMAT.format(new Date(d.getTime() - TimeZone.getDefault().getOffset(d.getTime()))) + "Z";
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2007, MetaDimensional Technologies Inc.
|
||||
*
|
||||
*
|
||||
* This file is part of the RememberTheMilk Java API.
|
||||
*
|
||||
* The RememberTheMilk Java API is free software; you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Lesser General Public License
|
||||
* as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The RememberTheMilk Java API is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.weloveastrid.rmilk.api.data;
|
||||
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Will Ross Jun 21, 2007
|
||||
*/
|
||||
public class RtmFrob extends RtmData {
|
||||
|
||||
private final String value;
|
||||
|
||||
public RtmFrob(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public RtmFrob(Element elt) {
|
||||
this.value = text(elt);
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright 2007, MetaDimensional Technologies Inc.
|
||||
*
|
||||
*
|
||||
* This file is part of the RememberTheMilk Java API.
|
||||
*
|
||||
* The RememberTheMilk Java API is free software; you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Lesser General Public License
|
||||
* as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The RememberTheMilk Java API is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.weloveastrid.rmilk.api.data;
|
||||
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
@SuppressWarnings("nls")
|
||||
public class RtmList extends RtmData {
|
||||
|
||||
private final String id;
|
||||
private final boolean smart;
|
||||
private final boolean archived;
|
||||
private final int position;
|
||||
private final String name;
|
||||
|
||||
public RtmList(String id, String name, boolean smart, boolean archived, int position) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.smart = smart;
|
||||
this.archived = archived;
|
||||
this.position = position;
|
||||
}
|
||||
|
||||
public RtmList(Element elt) {
|
||||
id = elt.getAttribute("id");
|
||||
name = elt.getAttribute("name");
|
||||
smart = elt.getAttribute("smart").equals("1");
|
||||
archived = elt.getAttribute("archived").equals("1");
|
||||
position = Integer.parseInt(elt.getAttribute("position"));
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public boolean isSmart() {
|
||||
return smart;
|
||||
}
|
||||
|
||||
public boolean isArchived() {
|
||||
return archived;
|
||||
}
|
||||
|
||||
public int getPosition() {
|
||||
return position;
|
||||
}
|
||||
|
||||
public boolean isInbox() {
|
||||
return position == -1;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 2007, MetaDimensional Technologies Inc.
|
||||
*
|
||||
*
|
||||
* This file is part of the RememberTheMilk Java API.
|
||||
*
|
||||
* The RememberTheMilk Java API is free software; you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Lesser General Public License
|
||||
* as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The RememberTheMilk Java API is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.weloveastrid.rmilk.api.data;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
@SuppressWarnings("nls")
|
||||
public class RtmLists extends RtmData {
|
||||
|
||||
private final Map<String, RtmList> lists;
|
||||
|
||||
public RtmLists() {
|
||||
this.lists = new HashMap<String, RtmList>();
|
||||
}
|
||||
|
||||
public RtmLists(Element elt) {
|
||||
this.lists = new HashMap<String, RtmList>();
|
||||
for (Element listElt : children(elt, "list")) {
|
||||
RtmList list = new RtmList(listElt);
|
||||
lists.put(list.getId(), list);
|
||||
}
|
||||
}
|
||||
|
||||
public RtmList getList(String id) {
|
||||
return lists.get(id);
|
||||
}
|
||||
|
||||
public Map<String, RtmList> getLists() {
|
||||
return Collections.unmodifiableMap(lists);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2007, MetaDimensional Technologies Inc.
|
||||
*
|
||||
*
|
||||
* This file is part of the RememberTheMilk Java API.
|
||||
*
|
||||
* The RememberTheMilk Java API is free software; you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Lesser General Public License
|
||||
* as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The RememberTheMilk Java API is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.weloveastrid.rmilk.api.data;
|
||||
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
/**
|
||||
* Represents a location.
|
||||
*
|
||||
* @author Edouard Mercier
|
||||
* @since 2008.05.22
|
||||
*/
|
||||
@SuppressWarnings("nls")
|
||||
public class RtmLocation
|
||||
extends RtmData
|
||||
{
|
||||
|
||||
public final String id;
|
||||
|
||||
public final String name;
|
||||
|
||||
public final float longitude;
|
||||
|
||||
public final float latitude;
|
||||
|
||||
public final String address;
|
||||
|
||||
public final boolean viewable;
|
||||
|
||||
public int zoom;
|
||||
|
||||
public RtmLocation(Element element)
|
||||
{
|
||||
id = element.getAttribute("id");
|
||||
name = element.getAttribute("name");
|
||||
longitude = Float.parseFloat(element.getAttribute("longitude"));
|
||||
latitude = Float.parseFloat(element.getAttribute("latitude"));
|
||||
address = element.getAttribute("address");
|
||||
zoom = Integer.parseInt(element.getAttribute("zoom"));
|
||||
viewable = element.getAttribute("viewable").equals("1") ? true : false;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,199 @@
|
||||
/*
|
||||
* Copyright 2007, MetaDimensional Technologies Inc.
|
||||
*
|
||||
*
|
||||
* This file is part of the RememberTheMilk Java API.
|
||||
*
|
||||
* The RememberTheMilk Java API is free software; you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Lesser General Public License
|
||||
* as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The RememberTheMilk Java API is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.weloveastrid.rmilk.api.data;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Will Ross Jun 21, 2007
|
||||
*/
|
||||
@SuppressWarnings("nls")
|
||||
public class RtmTask
|
||||
extends RtmData
|
||||
{
|
||||
|
||||
private static final String TAG = "rtm-task";
|
||||
|
||||
private final String id;
|
||||
|
||||
private final Date due;
|
||||
|
||||
private final boolean hasDueTime;
|
||||
|
||||
private final Date added;
|
||||
|
||||
private final Date completed;
|
||||
|
||||
private final Date deleted;
|
||||
|
||||
private final Priority priority;
|
||||
|
||||
private final int postponed;
|
||||
|
||||
private final String estimate;
|
||||
|
||||
public enum Priority
|
||||
{
|
||||
High, Medium, Low, None;
|
||||
|
||||
public static Priority values(Integer value) {
|
||||
value = Math.max(values().length - 1, value);
|
||||
return values()[value];
|
||||
}
|
||||
}
|
||||
|
||||
public static String convertPriority(Priority priority)
|
||||
{
|
||||
switch (priority)
|
||||
{
|
||||
case None:
|
||||
return new String(new char[] { 'n' });
|
||||
case Low:
|
||||
return new String(new char[] { '3' });
|
||||
case Medium:
|
||||
return new String(new char[] { '2' });
|
||||
case High:
|
||||
return new String(new char[] { '1' });
|
||||
default:
|
||||
Log.e(TAG, "Unrecognized RTM task priority: '" + priority + "'");
|
||||
return new String(new char[] { 'n' });
|
||||
}
|
||||
}
|
||||
|
||||
public RtmTask(String id, Date due, boolean hasDueTime, Date added, Date completed, Date deleted, Priority priority, int postponed, String estimate)
|
||||
{
|
||||
this.id = id;
|
||||
this.due = due;
|
||||
this.hasDueTime = hasDueTime;
|
||||
this.added = added;
|
||||
this.completed = completed;
|
||||
this.deleted = deleted;
|
||||
this.priority = priority;
|
||||
this.postponed = postponed;
|
||||
this.estimate = estimate;
|
||||
}
|
||||
|
||||
public RtmTask(Element elt)
|
||||
{
|
||||
id = elt.getAttribute("id");
|
||||
String dueStr = elt.getAttribute("due");
|
||||
due = (dueStr == null || dueStr.length() == 0) ? null : parseDate(dueStr);
|
||||
hasDueTime = Integer.parseInt(elt.getAttribute("has_due_time")) != 0;
|
||||
String addedStr = elt.getAttribute("added");
|
||||
added = (addedStr == null || addedStr.length() == 0) ? null : parseDate(addedStr);
|
||||
String completedStr = elt.getAttribute("completed");
|
||||
completed = (completedStr == null || completedStr.length() == 0) ? null : parseDate(completedStr);
|
||||
String deletedStr = elt.getAttribute("deleted");
|
||||
deleted = (deletedStr == null || deletedStr.length() == 0) ? null : parseDate(deletedStr);
|
||||
String priorityStr = elt.getAttribute("priority");
|
||||
if (priorityStr.length() > 0)
|
||||
{
|
||||
switch (priorityStr.charAt(0))
|
||||
{
|
||||
case 'N':
|
||||
case 'n':
|
||||
priority = Priority.None;
|
||||
break;
|
||||
case '3':
|
||||
priority = Priority.Low;
|
||||
break;
|
||||
case '2':
|
||||
priority = Priority.Medium;
|
||||
break;
|
||||
case '1':
|
||||
priority = Priority.High;
|
||||
break;
|
||||
default:
|
||||
System.err.println("Unrecognized RTM task priority: '" + priorityStr + "'");
|
||||
priority = Priority.Medium;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
priority = Priority.None;
|
||||
}
|
||||
if (elt.hasAttribute("postponed") == true && elt.getAttribute("postponed").length() > 0)
|
||||
{
|
||||
postponed = Integer.parseInt(elt.getAttribute("postponed"));
|
||||
}
|
||||
else
|
||||
{
|
||||
postponed = 0;
|
||||
}
|
||||
estimate = elt.getAttribute("estimate");
|
||||
}
|
||||
|
||||
public String getId()
|
||||
{
|
||||
return id;
|
||||
}
|
||||
|
||||
public Date getDue()
|
||||
{
|
||||
return due;
|
||||
}
|
||||
|
||||
public boolean getHasDueTime()
|
||||
{
|
||||
return hasDueTime;
|
||||
}
|
||||
|
||||
public Date getAdded()
|
||||
{
|
||||
return added;
|
||||
}
|
||||
|
||||
public Date getCompleted()
|
||||
{
|
||||
return completed;
|
||||
}
|
||||
|
||||
public Date getDeleted()
|
||||
{
|
||||
return deleted;
|
||||
}
|
||||
|
||||
public Priority getPriority()
|
||||
{
|
||||
return priority;
|
||||
}
|
||||
|
||||
public int getPostponed()
|
||||
{
|
||||
return postponed;
|
||||
}
|
||||
|
||||
public String getEstimate()
|
||||
{
|
||||
return estimate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "Task<" + id + ">";
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2007, MetaDimensional Technologies Inc.
|
||||
*
|
||||
*
|
||||
* This file is part of the RememberTheMilk Java API.
|
||||
*
|
||||
* The RememberTheMilk Java API is free software; you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Lesser General Public License
|
||||
* as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The RememberTheMilk Java API is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.weloveastrid.rmilk.api.data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Will Ross Jun 22, 2007
|
||||
*/
|
||||
@SuppressWarnings("nls")
|
||||
public class RtmTaskList extends RtmData {
|
||||
|
||||
private final String id;
|
||||
|
||||
private final List<RtmTaskSeries> series;
|
||||
|
||||
public RtmTaskList(String id) {
|
||||
this.id = id;
|
||||
this.series = new ArrayList<RtmTaskSeries>();
|
||||
}
|
||||
|
||||
public RtmTaskList(Element elt) {
|
||||
id = elt.getAttribute("id");
|
||||
series = new ArrayList<RtmTaskSeries>();
|
||||
for (Element seriesElt : children(elt, "taskseries")) {
|
||||
series.add(new RtmTaskSeries(this, seriesElt));
|
||||
}
|
||||
|
||||
if (id == null || id.length() == 0) { throw new RuntimeException("No id found in task list."); }
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public List<RtmTaskSeries> getSeries() {
|
||||
return Collections.unmodifiableList(series);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright 2007, MetaDimensional Technologies Inc.
|
||||
*
|
||||
*
|
||||
* This file is part of the RememberTheMilk Java API.
|
||||
*
|
||||
* The RememberTheMilk Java API is free software; you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Lesser General Public License
|
||||
* as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The RememberTheMilk Java API is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.weloveastrid.rmilk.api.data;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.EntityReference;
|
||||
import org.w3c.dom.Text;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* Represents a single task note.
|
||||
*
|
||||
* @author Edouard Mercier
|
||||
* @since 2008.04.22
|
||||
*/
|
||||
@SuppressWarnings("nls")
|
||||
public class RtmTaskNote
|
||||
extends RtmData
|
||||
{
|
||||
|
||||
private final String id;
|
||||
|
||||
private final Date created;
|
||||
|
||||
private final Date modified;
|
||||
|
||||
private final String title;
|
||||
|
||||
private String text;
|
||||
|
||||
public RtmTaskNote(Element element)
|
||||
{
|
||||
id = element.getAttribute("id");
|
||||
created = parseDate(element.getAttribute("created"));
|
||||
modified = parseDate(element.getAttribute("modified"));
|
||||
title = element.getAttribute("title");
|
||||
|
||||
// The note text itself might be split across multiple children of the
|
||||
// note element, so get all of the children.
|
||||
for (int i=0; i < element.getChildNodes().getLength(); i++) {
|
||||
Object innerNote = element.getChildNodes().item(i);
|
||||
|
||||
if(innerNote instanceof EntityReference) // this node is empty
|
||||
continue;
|
||||
|
||||
if(!(innerNote instanceof Text)) {
|
||||
Log.w("rtm-note", "Expected text type, got " + innerNote.getClass());
|
||||
continue;
|
||||
}
|
||||
|
||||
Text innerText = (Text) innerNote;
|
||||
|
||||
if (text == null)
|
||||
text = innerText.getData();
|
||||
else
|
||||
text = text.concat(innerText.getData());
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public String getId()
|
||||
{
|
||||
return id;
|
||||
}
|
||||
|
||||
public Date getCreated()
|
||||
{
|
||||
return created;
|
||||
}
|
||||
|
||||
public Date getModified()
|
||||
{
|
||||
return modified;
|
||||
}
|
||||
|
||||
public String getTitle()
|
||||
{
|
||||
return title;
|
||||
}
|
||||
|
||||
public String getText()
|
||||
{
|
||||
return text;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2007, MetaDimensional Technologies Inc.
|
||||
*
|
||||
*
|
||||
* This file is part of the RememberTheMilk Java API.
|
||||
*
|
||||
* The RememberTheMilk Java API is free software; you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Lesser General Public License
|
||||
* as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The RememberTheMilk Java API is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.weloveastrid.rmilk.api.data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
/**
|
||||
* Represents the notes of a task.
|
||||
*
|
||||
* @author Edouard Mercier
|
||||
* @since 2008.04.22
|
||||
*/
|
||||
@SuppressWarnings("nls")
|
||||
public class RtmTaskNotes
|
||||
extends RtmData
|
||||
{
|
||||
|
||||
private List<RtmTaskNote> notes;
|
||||
|
||||
public RtmTaskNotes(Element element)
|
||||
{
|
||||
notes = new ArrayList<RtmTaskNote>();
|
||||
for (Element child : children(element, "note"))
|
||||
{
|
||||
notes.add(new RtmTaskNote(child));
|
||||
}
|
||||
}
|
||||
|
||||
public List<RtmTaskNote> getNotes()
|
||||
{
|
||||
return notes;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,170 @@
|
||||
/*
|
||||
* Copyright 2007, MetaDimensional Technologies Inc.
|
||||
*
|
||||
*
|
||||
* This file is part of the RememberTheMilk Java API.
|
||||
*
|
||||
* The RememberTheMilk Java API is free software; you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Lesser General Public License
|
||||
* as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The RememberTheMilk Java API is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.weloveastrid.rmilk.api.data;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Will Ross Jun 22, 2007
|
||||
*/
|
||||
@SuppressWarnings("nls")
|
||||
public class RtmTaskSeries extends RtmData {
|
||||
|
||||
private final RtmTaskList list;
|
||||
|
||||
private final String id;
|
||||
|
||||
private final Date created;
|
||||
|
||||
private final Date modified;
|
||||
|
||||
private final String name;
|
||||
|
||||
private final String source;
|
||||
|
||||
private final RtmTask task;
|
||||
|
||||
private final LinkedList<String> tags;
|
||||
|
||||
private final RtmTaskNotes notes;
|
||||
|
||||
private final String locationId;
|
||||
|
||||
private final String url;
|
||||
|
||||
private final boolean hasRecurrence;
|
||||
|
||||
public RtmTaskSeries(RtmTaskList list, String id, Date created, Date modified, String name,
|
||||
String source, RtmTask task) {
|
||||
this.list = list;
|
||||
this.id = id;
|
||||
this.created = created;
|
||||
this.modified = modified;
|
||||
this.name = name;
|
||||
this.source = source;
|
||||
this.task = task;
|
||||
this.locationId = null;
|
||||
notes = null;
|
||||
url = null;
|
||||
tags = null;
|
||||
hasRecurrence = false;
|
||||
}
|
||||
|
||||
public RtmTaskSeries(RtmTaskList list, Element elt) {
|
||||
this.list = list;
|
||||
id = elt.getAttribute("id");
|
||||
created = parseDate(elt.getAttribute("created"));
|
||||
modified = parseDate(elt.getAttribute("modified"));
|
||||
name = elt.getAttribute("name");
|
||||
source = elt.getAttribute("source");
|
||||
List<Element> children = children(elt, "task");
|
||||
if (children.size() > 1) {
|
||||
// assume it's a repeating task - pick the child with nearest
|
||||
// but not expired due date
|
||||
RtmTask selectedTask = new RtmTask(children.get(0));
|
||||
for(Element element : children) {
|
||||
RtmTask childTask = new RtmTask(element);
|
||||
if(childTask.getCompleted() == null) {
|
||||
selectedTask = childTask;
|
||||
break;
|
||||
}
|
||||
}
|
||||
task = selectedTask;
|
||||
} else {
|
||||
task = new RtmTask(child(elt, "task"));
|
||||
}
|
||||
notes = new RtmTaskNotes(child(elt, "notes"));
|
||||
locationId = elt.getAttribute("location_id");
|
||||
url = elt.getAttribute("url");
|
||||
hasRecurrence = children(elt, "rrule").size() > 0;
|
||||
|
||||
Element elementTags = child(elt, "tags");
|
||||
if (elementTags.getChildNodes().getLength() > 0) {
|
||||
List<Element> elementTagList = children(elementTags, "tag");
|
||||
tags = new LinkedList<String>();
|
||||
for (Element elementTag : elementTagList) {
|
||||
String tag = text(elementTag);
|
||||
if (tag != null)
|
||||
tags.add(tag);
|
||||
}
|
||||
} else {
|
||||
tags = null;
|
||||
}
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public Date getCreated() {
|
||||
return created;
|
||||
}
|
||||
|
||||
public Date getModified() {
|
||||
return modified;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getSource() {
|
||||
return source;
|
||||
}
|
||||
|
||||
public RtmTask getTask() {
|
||||
return task;
|
||||
}
|
||||
|
||||
public LinkedList<String> getTags() {
|
||||
return tags;
|
||||
}
|
||||
|
||||
public RtmTaskNotes getNotes() {
|
||||
return notes;
|
||||
}
|
||||
|
||||
public String getLocationId() {
|
||||
return locationId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TaskSeries<" + id + "," + name + ">";
|
||||
}
|
||||
|
||||
public String getURL() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public boolean hasRecurrence() {
|
||||
return hasRecurrence;
|
||||
}
|
||||
|
||||
public RtmTaskList getList() {
|
||||
return list;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright 2007, MetaDimensional Technologies Inc.
|
||||
*
|
||||
*
|
||||
* This file is part of the RememberTheMilk Java API.
|
||||
*
|
||||
* The RememberTheMilk Java API is free software; you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Lesser General Public License
|
||||
* as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The RememberTheMilk Java API is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.weloveastrid.rmilk.api.data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Will Ross Jun 21, 2007
|
||||
*/
|
||||
@SuppressWarnings("nls")
|
||||
public class RtmTasks extends RtmData {
|
||||
|
||||
private final List<RtmTaskList> lists;
|
||||
|
||||
public RtmTasks() {
|
||||
this.lists = new ArrayList<RtmTaskList>();
|
||||
}
|
||||
|
||||
public RtmTasks(Element elt) {
|
||||
this.lists = new ArrayList<RtmTaskList>();
|
||||
for (Element listElt : children(elt, "list")) {
|
||||
lists.add(new RtmTaskList(listElt));
|
||||
}
|
||||
}
|
||||
|
||||
public List<RtmTaskList> getLists() {
|
||||
return Collections.unmodifiableList(lists);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2007, MetaDimensional Technologies Inc.
|
||||
*
|
||||
*
|
||||
* This file is part of the RememberTheMilk Java API.
|
||||
*
|
||||
* The RememberTheMilk Java API is free software; you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Lesser General Public License
|
||||
* as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The RememberTheMilk Java API is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.weloveastrid.rmilk.api.data;
|
||||
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
public class RtmTimeline extends RtmData {
|
||||
|
||||
private final String id;
|
||||
|
||||
public RtmTimeline(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public RtmTimeline(Element elt) {
|
||||
id = text(elt);
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 2007, MetaDimensional Technologies Inc.
|
||||
*
|
||||
*
|
||||
* This file is part of the RememberTheMilk Java API.
|
||||
*
|
||||
* The RememberTheMilk Java API is free software; you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Lesser General Public License
|
||||
* as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The RememberTheMilk Java API is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.weloveastrid.rmilk.api.data;
|
||||
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Will Ross Jun 21, 2007
|
||||
*/
|
||||
@SuppressWarnings("nls")
|
||||
public class RtmUser extends RtmData {
|
||||
|
||||
private final String id;
|
||||
|
||||
private final String username;
|
||||
|
||||
private final String fullname;
|
||||
|
||||
public RtmUser(String id, String username, String fullname) {
|
||||
this.id = id;
|
||||
this.username = username;
|
||||
this.fullname = fullname;
|
||||
}
|
||||
|
||||
public RtmUser(Element elt) {
|
||||
if (!elt.getNodeName().equals("user")) { throw new IllegalArgumentException("Element " + elt.getNodeName() + " does not represent a User object."); }
|
||||
|
||||
this.id = elt.getAttribute("id");
|
||||
this.username = elt.getAttribute("username");
|
||||
this.fullname = elt.getAttribute("fullname");
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public String getFullname() {
|
||||
return fullname;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,324 @@
|
||||
/**
|
||||
* See the file "LICENSE" for the full license governing this code.
|
||||
*/
|
||||
package org.weloveastrid.rmilk.data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
|
||||
import org.weloveastrid.rmilk.MilkUtilities;
|
||||
import org.weloveastrid.rmilk.api.data.RtmList;
|
||||
import org.weloveastrid.rmilk.api.data.RtmLists;
|
||||
import org.weloveastrid.rmilk.sync.MilkTaskContainer;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.CursorJoiner;
|
||||
|
||||
import com.todoroo.andlib.data.Property;
|
||||
import com.todoroo.andlib.data.TodorooCursor;
|
||||
import com.todoroo.andlib.sql.Criterion;
|
||||
import com.todoroo.andlib.sql.Order;
|
||||
import com.todoroo.andlib.sql.Query;
|
||||
import com.todoroo.astrid.data.Metadata;
|
||||
import com.todoroo.astrid.data.MetadataApiDao;
|
||||
import com.todoroo.astrid.data.MetadataApiDao.MetadataCriteria;
|
||||
import com.todoroo.astrid.data.StoreObject;
|
||||
import com.todoroo.astrid.data.StoreObjectApiDao;
|
||||
import com.todoroo.astrid.data.StoreObjectApiDao.StoreObjectCriteria;
|
||||
import com.todoroo.astrid.data.Task;
|
||||
import com.todoroo.astrid.data.TaskApiDao;
|
||||
import com.todoroo.astrid.data.TaskApiDao.TaskCriteria;
|
||||
|
||||
public final class MilkDataService {
|
||||
|
||||
/** metadata key of tag addon */
|
||||
public static final String TAG_KEY = "tags-tag"; //$NON-NLS-1$
|
||||
|
||||
// --- singleton
|
||||
|
||||
private static MilkDataService instance = null;
|
||||
|
||||
public static synchronized MilkDataService getInstance(Context context) {
|
||||
if(instance == null)
|
||||
instance = new MilkDataService(context);
|
||||
return instance;
|
||||
}
|
||||
|
||||
// --- instance variables
|
||||
|
||||
private final TaskApiDao taskDao;
|
||||
private final MetadataApiDao metadataDao;
|
||||
private final StoreObjectApiDao storeObjectDao;
|
||||
|
||||
static final Random random = new Random();
|
||||
|
||||
private MilkDataService(Context context) {
|
||||
// prevent instantiation
|
||||
taskDao = new TaskApiDao(context);
|
||||
storeObjectDao = new StoreObjectApiDao(context);
|
||||
metadataDao = new MetadataApiDao(context);
|
||||
}
|
||||
|
||||
// --- task and metadata methods
|
||||
|
||||
/**
|
||||
* Clears RTM metadata information. Used when user logs out of RTM
|
||||
*/
|
||||
public void clearMetadata() {
|
||||
metadataDao.deleteWhere(Metadata.KEY.eq(MilkTaskFields.METADATA_KEY));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets tasks that were modified since last sync. Used internally to
|
||||
* support the other methods.
|
||||
*
|
||||
* @param properties
|
||||
* @return cursor
|
||||
*/
|
||||
private TodorooCursor<Task> getLocallyModified(Criterion criterion, Property<?>... properties) {
|
||||
long lastSyncDate = MilkUtilities.INSTANCE.getLastSyncDate();
|
||||
if(lastSyncDate == 0)
|
||||
return taskDao.query(Query.select(Task.ID).where(criterion).orderBy(Order.asc(Task.ID)));
|
||||
return
|
||||
taskDao.query(Query.select(properties).where(Criterion.and(criterion,
|
||||
Task.MODIFICATION_DATE.gt(lastSyncDate))).orderBy(Order.asc(Task.ID)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets milk task metadata for joining
|
||||
*
|
||||
* @return cursor
|
||||
*/
|
||||
private TodorooCursor<Metadata> getMilkTaskMetadata() {
|
||||
return metadataDao.query(Query.select(Metadata.TASK).where(
|
||||
MetadataCriteria.withKey(MilkTaskFields.METADATA_KEY)).orderBy(Order.asc(Metadata.TASK)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets tasks that were created since last sync
|
||||
* @param properties
|
||||
* @return
|
||||
*/
|
||||
public TodorooCursor<Task> getLocallyCreated(Property<?>[] properties) {
|
||||
TodorooCursor<Task> tasks = getLocallyModified(TaskCriteria.isActive(), Task.ID);
|
||||
TodorooCursor<Metadata> metadata = getMilkTaskMetadata();
|
||||
|
||||
ArrayList<Long> matchingRows = new ArrayList<Long>();
|
||||
|
||||
CursorJoiner joiner = new CursorJoiner(tasks, new String[] { Task.ID.name },
|
||||
metadata, new String[] { Metadata.TASK.name });
|
||||
for (CursorJoiner.Result joinerResult : joiner) {
|
||||
// only pick up tasks without metadata
|
||||
if(joinerResult == CursorJoiner.Result.LEFT) {
|
||||
matchingRows.add(tasks.getLong(0));
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
taskDao.query(Query.select(properties).where(Task.ID.in(matchingRows.toArray(new Long[matchingRows.size()]))));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets tasks that were modified since last sync
|
||||
* @param properties
|
||||
* @return null if never sync'd
|
||||
*/
|
||||
public TodorooCursor<Task> getLocallyUpdated(Property<?>[] properties) {
|
||||
TodorooCursor<Task> tasks = getLocallyModified(TaskCriteria.isActive(), Task.ID);
|
||||
TodorooCursor<Metadata> metadata = getMilkTaskMetadata();
|
||||
|
||||
ArrayList<Long> matchingRows = new ArrayList<Long>();
|
||||
|
||||
CursorJoiner joiner = new CursorJoiner(tasks, new String[] { Task.ID.name },
|
||||
metadata, new String[] { Metadata.TASK.name });
|
||||
for (CursorJoiner.Result joinerResult : joiner) {
|
||||
// only pick up tasks with metadata
|
||||
if(joinerResult == CursorJoiner.Result.BOTH) {
|
||||
matchingRows.add(tasks.getLong(0));
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
taskDao.query(Query.select(properties).where(Task.ID.in(matchingRows.toArray(new Long[matchingRows.size()]))));
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for a local task with same remote id, updates this task's id
|
||||
* @param remoteTask
|
||||
*/
|
||||
public void findLocalMatch(MilkTaskContainer remoteTask) {
|
||||
if(remoteTask.task.getId() != Task.NO_ID)
|
||||
return;
|
||||
TodorooCursor<Metadata> cursor = metadataDao.query(Query.select(Metadata.TASK).
|
||||
where(Criterion.and(MetadataCriteria.withKey(MilkTaskFields.METADATA_KEY),
|
||||
MilkTaskFields.TASK_SERIES_ID.eq(remoteTask.taskSeriesId),
|
||||
MilkTaskFields.TASK_ID.eq(remoteTask.taskId))));
|
||||
try {
|
||||
if(cursor.getCount() == 0)
|
||||
return;
|
||||
cursor.moveToFirst();
|
||||
remoteTask.task.setId(cursor.get(Metadata.TASK));
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves a task and its metadata
|
||||
* @param task
|
||||
*/
|
||||
public void saveTaskAndMetadata(MilkTaskContainer task) {
|
||||
taskDao.save(task.task);
|
||||
|
||||
task.metadata.add(MilkTaskFields.create(task));
|
||||
metadataDao.synchronizeMetadata(task.task.getId(), task.metadata,
|
||||
Criterion.or(MetadataCriteria.withKey(TAG_KEY),
|
||||
MetadataCriteria.withKey(MilkTaskFields.METADATA_KEY),
|
||||
MetadataCriteria.withKey(MilkNoteFields.METADATA_KEY)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a task and its metadata
|
||||
* @param task
|
||||
* @return
|
||||
*/
|
||||
public MilkTaskContainer 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(TAG_KEY),
|
||||
MetadataCriteria.withKey(MilkTaskFields.METADATA_KEY),
|
||||
MetadataCriteria.withKey(MilkNoteFields.METADATA_KEY)))));
|
||||
try {
|
||||
for(metadataCursor.moveToFirst(); !metadataCursor.isAfterLast(); metadataCursor.moveToNext()) {
|
||||
metadata.add(new Metadata(metadataCursor));
|
||||
}
|
||||
} finally {
|
||||
metadataCursor.close();
|
||||
}
|
||||
|
||||
return new MilkTaskContainer(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(
|
||||
MilkTaskFields.LIST_ID, MilkTaskFields.TASK_SERIES_ID, MilkTaskFields.TASK_ID, MilkTaskFields.REPEATING).where(
|
||||
MetadataCriteria.byTaskAndwithKey(taskId, MilkTaskFields.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, MilkNoteFields.METADATA_KEY)));
|
||||
return cursor;
|
||||
}
|
||||
|
||||
// --- list methods
|
||||
|
||||
private StoreObject[] lists = null;
|
||||
|
||||
/**
|
||||
* Reads dashboards
|
||||
*/
|
||||
private void readLists() {
|
||||
if(lists != null)
|
||||
return;
|
||||
|
||||
TodorooCursor<StoreObject> cursor = storeObjectDao.query(Query.select(StoreObject.PROPERTIES).
|
||||
where(StoreObjectCriteria.byType(MilkListFields.TYPE)).orderBy(Order.asc(MilkListFields.POSITION)));
|
||||
try {
|
||||
lists = new StoreObject[cursor.getCount()];
|
||||
for(int i = 0; i < lists.length; i++) {
|
||||
cursor.moveToNext();
|
||||
StoreObject list = new StoreObject(cursor);
|
||||
lists[i] = list;
|
||||
}
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a list of lists
|
||||
*/
|
||||
public StoreObject[] getLists() {
|
||||
readLists();
|
||||
return lists;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears current cache of RTM lists and loads data from RTM into
|
||||
* database. Returns the inbox list.
|
||||
*
|
||||
* @param remoteLists
|
||||
* @return list with the name "inbox"
|
||||
*/
|
||||
public StoreObject setLists(RtmLists remoteLists) {
|
||||
readLists();
|
||||
|
||||
StoreObject inbox = null;
|
||||
for(Map.Entry<String, RtmList> remote : remoteLists.getLists().entrySet()) {
|
||||
if(remote.getValue().isSmart() || "All Tasks".equals(remote.getValue().getName())) //$NON-NLS-1$
|
||||
continue;
|
||||
|
||||
long id = Long.parseLong(remote.getValue().getId());
|
||||
StoreObject local = null;
|
||||
for(StoreObject list : lists) {
|
||||
if(list.getValue(MilkListFields.REMOTE_ID).equals(id)) {
|
||||
local = list;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(local == null)
|
||||
local = new StoreObject();
|
||||
|
||||
local.setValue(StoreObject.TYPE, MilkListFields.TYPE);
|
||||
local.setValue(MilkListFields.REMOTE_ID, id);
|
||||
local.setValue(MilkListFields.NAME, remote.getValue().getName());
|
||||
local.setValue(MilkListFields.POSITION, remote.getValue().getPosition());
|
||||
local.setValue(MilkListFields.ARCHIVED, remote.getValue().isArchived() ? 1 : 0);
|
||||
storeObjectDao.save(local);
|
||||
|
||||
if(remote.getValue().isInbox()) {
|
||||
inbox = local;
|
||||
}
|
||||
}
|
||||
|
||||
// clear dashboard cache
|
||||
lists = null;
|
||||
return inbox;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list name by list id
|
||||
* @param listId
|
||||
* @return null if no list by this id exists, otherwise list name
|
||||
*/
|
||||
public String getListName(long listId) {
|
||||
readLists();
|
||||
for(StoreObject list : lists)
|
||||
if(list.getValue(MilkListFields.REMOTE_ID).equals(listId))
|
||||
return list.getValue(MilkListFields.NAME);
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,40 @@
|
||||
/**
|
||||
* See the file "LICENSE" for the full license governing this code.
|
||||
*/
|
||||
package org.weloveastrid.rmilk.data;
|
||||
|
||||
|
||||
import com.todoroo.andlib.data.Property.IntegerProperty;
|
||||
import com.todoroo.andlib.data.Property.LongProperty;
|
||||
import com.todoroo.andlib.data.Property.StringProperty;
|
||||
import com.todoroo.astrid.data.StoreObject;
|
||||
|
||||
/**
|
||||
* Data Model which represents a list in RTM
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
public class MilkListFields {
|
||||
|
||||
/** type*/
|
||||
public static final String TYPE = "rmilk-list"; //$NON-NLS-1$
|
||||
|
||||
// --- properties
|
||||
|
||||
/** Remote ID */
|
||||
public static final LongProperty REMOTE_ID = new LongProperty(
|
||||
StoreObject.TABLE, StoreObject.ITEM.name);
|
||||
|
||||
/** Name */
|
||||
public static final StringProperty NAME = StoreObject.VALUE1;
|
||||
|
||||
/** Position */
|
||||
public static final IntegerProperty POSITION = new IntegerProperty(
|
||||
StoreObject.TABLE, StoreObject.VALUE2.name);
|
||||
|
||||
/** Archived (0 or 1) */
|
||||
public static final IntegerProperty ARCHIVED = new IntegerProperty(
|
||||
StoreObject.TABLE, StoreObject.VALUE3.name);
|
||||
|
||||
}
|
||||
@ -0,0 +1,109 @@
|
||||
package org.weloveastrid.rmilk.data;
|
||||
|
||||
import org.weloveastrid.rmilk.api.data.RtmTaskNote;
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.todoroo.andlib.data.Property.LongProperty;
|
||||
import com.todoroo.andlib.data.Property.StringProperty;
|
||||
import com.todoroo.astrid.data.Metadata;
|
||||
|
||||
/**
|
||||
* Metadata entries for a Remember the Milk note. The first RMilk note becomes
|
||||
* Astrid's note field, subsequent notes are stored in metadata in this
|
||||
* format.
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
public class MilkNoteFields {
|
||||
|
||||
/** metadata key */
|
||||
public static final String METADATA_KEY = "rmilk-note"; //$NON-NLS-1$
|
||||
|
||||
/** note id */
|
||||
public static final StringProperty ID = Metadata.VALUE1;
|
||||
|
||||
/** note title */
|
||||
public static final StringProperty TITLE = Metadata.VALUE2;
|
||||
|
||||
/** note text */
|
||||
public static final StringProperty TEXT = Metadata.VALUE3;
|
||||
|
||||
/** note creation date */
|
||||
public static final LongProperty CREATED = new LongProperty(Metadata.TABLE,
|
||||
Metadata.VALUE4.name);
|
||||
|
||||
public static Metadata create(RtmTaskNote note) {
|
||||
Metadata metadata = new Metadata();
|
||||
metadata.setValue(Metadata.KEY, METADATA_KEY);
|
||||
metadata.setValue(ID, note.getId());
|
||||
metadata.setValue(TITLE, note.getTitle());
|
||||
metadata.setValue(TEXT, note.getText());
|
||||
metadata.setValue(CREATED, note.getCreated().getTime());
|
||||
return metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn a note's title and text into a string
|
||||
* @param title
|
||||
* @param text
|
||||
* @return
|
||||
*/
|
||||
@SuppressWarnings("nls")
|
||||
public static String toNoteField(RtmTaskNote note) {
|
||||
String title = note.getTitle();
|
||||
String text = note.getText();
|
||||
if(TextUtils.isEmpty(text) && TextUtils.isEmpty(title))
|
||||
return "";
|
||||
StringBuilder result = new StringBuilder();
|
||||
if(!TextUtils.isEmpty(title)) {
|
||||
result.append(title);
|
||||
if(!TextUtils.isEmpty(text))
|
||||
result.append("\n");
|
||||
}
|
||||
if(!TextUtils.isEmpty(text)) {
|
||||
result.append(text);
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn a string into a note's title and text
|
||||
* @param value
|
||||
* @return
|
||||
*/
|
||||
@SuppressWarnings("nls")
|
||||
public static String[] fromNoteField(String value) {
|
||||
String[] result = new String[2];
|
||||
int firstLineBreak = value.indexOf('\n');
|
||||
if(firstLineBreak > -1 && firstLineBreak + 1 < value.length()) {
|
||||
result[0] = value.substring(0, firstLineBreak);
|
||||
result[1] = value.substring(firstLineBreak + 1, value.length());
|
||||
} else {
|
||||
result[0] = "";
|
||||
result[1] = value;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn a note's title and text into an HTML string for notes
|
||||
* @param metadata
|
||||
* @return
|
||||
*/
|
||||
@SuppressWarnings("nls")
|
||||
public static String toTaskDetail(Metadata metadata) {
|
||||
String title = metadata.getValue(TITLE);
|
||||
String text = metadata.getValue(TEXT);
|
||||
|
||||
String result;
|
||||
if(!TextUtils.isEmpty(title))
|
||||
result = "<b>" + title + "</b> " + text;
|
||||
else
|
||||
result = text;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,51 @@
|
||||
package org.weloveastrid.rmilk.data;
|
||||
|
||||
import org.weloveastrid.rmilk.sync.MilkTaskContainer;
|
||||
|
||||
import com.todoroo.andlib.data.Property.IntegerProperty;
|
||||
import com.todoroo.andlib.data.Property.LongProperty;
|
||||
import com.todoroo.astrid.data.Metadata;
|
||||
|
||||
/**
|
||||
* Metadata entries for a Remember The Milk Task
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
public class MilkTaskFields {
|
||||
|
||||
/** metadata key */
|
||||
public static final String METADATA_KEY = "rmilk"; //$NON-NLS-1$
|
||||
|
||||
/** {@link MilkListFields} id */
|
||||
public static final LongProperty LIST_ID = new LongProperty(Metadata.TABLE,
|
||||
Metadata.VALUE1.name);
|
||||
|
||||
/** RTM Task Series Id */
|
||||
public static final LongProperty TASK_SERIES_ID = new LongProperty(Metadata.TABLE,
|
||||
Metadata.VALUE2.name);
|
||||
|
||||
/** RTM Task Id */
|
||||
public static final LongProperty TASK_ID = new LongProperty(Metadata.TABLE,
|
||||
Metadata.VALUE3.name);
|
||||
|
||||
/** Whether task repeats in RTM (1 or 0) */
|
||||
public static final IntegerProperty REPEATING = new IntegerProperty(Metadata.TABLE,
|
||||
Metadata.VALUE4.name);
|
||||
|
||||
/**
|
||||
* Creates a piece of metadata from a remote task
|
||||
* @param rtmTaskSeries
|
||||
* @return
|
||||
*/
|
||||
public static Metadata create(MilkTaskContainer container) {
|
||||
Metadata metadata = new Metadata();
|
||||
metadata.setValue(Metadata.KEY, METADATA_KEY);
|
||||
metadata.setValue(MilkTaskFields.LIST_ID, container.listId);
|
||||
metadata.setValue(MilkTaskFields.TASK_SERIES_ID, container.taskSeriesId);
|
||||
metadata.setValue(MilkTaskFields.TASK_ID, container.taskId);
|
||||
metadata.setValue(MilkTaskFields.REPEATING, container.repeating ? 1 : 0);
|
||||
|
||||
return metadata;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,610 @@
|
||||
/**
|
||||
* See the file "LICENSE" for the full license governing this code.
|
||||
*/
|
||||
package org.weloveastrid.rmilk.sync;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
import org.weloveastrid.rmilk.MilkBackgroundService;
|
||||
import org.weloveastrid.rmilk.MilkLoginActivity;
|
||||
import org.weloveastrid.rmilk.MilkPreferences;
|
||||
import org.weloveastrid.rmilk.MilkUtilities;
|
||||
import org.weloveastrid.rmilk.MilkLoginActivity.SyncLoginCallback;
|
||||
import org.weloveastrid.rmilk.api.ApplicationInfo;
|
||||
import org.weloveastrid.rmilk.api.ServiceImpl;
|
||||
import org.weloveastrid.rmilk.api.ServiceInternalException;
|
||||
import org.weloveastrid.rmilk.api.data.RtmList;
|
||||
import org.weloveastrid.rmilk.api.data.RtmLists;
|
||||
import org.weloveastrid.rmilk.api.data.RtmTask;
|
||||
import org.weloveastrid.rmilk.api.data.RtmTaskList;
|
||||
import org.weloveastrid.rmilk.api.data.RtmTaskNote;
|
||||
import org.weloveastrid.rmilk.api.data.RtmTaskSeries;
|
||||
import org.weloveastrid.rmilk.api.data.RtmTasks;
|
||||
import org.weloveastrid.rmilk.api.data.RtmAuth.Perms;
|
||||
import org.weloveastrid.rmilk.api.data.RtmTask.Priority;
|
||||
import org.weloveastrid.rmilk.data.MilkDataService;
|
||||
import org.weloveastrid.rmilk.data.MilkNoteFields;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Notification;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Handler;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import com.timsu.astrid.R;
|
||||
import com.todoroo.andlib.data.Property;
|
||||
import com.todoroo.andlib.data.TodorooCursor;
|
||||
import com.todoroo.andlib.service.ContextManager;
|
||||
import com.todoroo.andlib.utility.AndroidUtilities;
|
||||
import com.todoroo.andlib.utility.DateUtilities;
|
||||
import com.todoroo.andlib.utility.DialogUtilities;
|
||||
import com.todoroo.astrid.api.AstridApiConstants;
|
||||
import com.todoroo.astrid.data.Metadata;
|
||||
import com.todoroo.astrid.data.Task;
|
||||
import com.todoroo.astrid.sync.SyncProvider;
|
||||
|
||||
public class MilkSyncProvider extends SyncProvider<MilkTaskContainer> {
|
||||
|
||||
private ServiceImpl rtmService = null;
|
||||
private String timeline = null;
|
||||
private MilkDataService dataService = null;
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// ------------------------------------------------------- public methods
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Sign out of RTM, deleting all synchronization metadata
|
||||
*/
|
||||
public void signOut(Context context) {
|
||||
MilkUtilities.INSTANCE.setToken(null);
|
||||
MilkUtilities.INSTANCE.clearLastSyncDate();
|
||||
|
||||
dataService = MilkDataService.getInstance(context);
|
||||
dataService.clearMetadata();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
final Context context = ContextManager.getContext();
|
||||
MilkUtilities.INSTANCE.setLastError(e.toString());
|
||||
|
||||
String message = null;
|
||||
|
||||
// occurs when application was closed
|
||||
if(e instanceof IllegalStateException) {
|
||||
Log.e(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();
|
||||
message = context.getString(R.string.rmilk_ioerror);
|
||||
Log.e(tag, "ioexception", enclosedException); //$NON-NLS-1$
|
||||
} else {
|
||||
if(e instanceof ServiceInternalException)
|
||||
e = ((ServiceInternalException)e).getEnclosedException();
|
||||
if(e != null)
|
||||
message = e.toString();
|
||||
Log.e(tag, "unhandled", e); //$NON-NLS-1$
|
||||
}
|
||||
|
||||
if(showError && context instanceof Activity && message != null) {
|
||||
DialogUtilities.okDialog((Activity)context, message, null);
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// ------------------------------------------------------ initiating sync
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* set up service
|
||||
*/
|
||||
@SuppressWarnings("nls")
|
||||
private void initializeService(String authToken) throws ServiceInternalException {
|
||||
String appName = null;
|
||||
String z = stripslashes(0,"q9883o3384n21snq17501qn38oo1r689", "b");
|
||||
String v = stripslashes(16,"19o2n020345219os","a");
|
||||
|
||||
if(authToken == null)
|
||||
rtmService = new ServiceImpl(new ApplicationInfo(
|
||||
z, v, appName));
|
||||
else
|
||||
rtmService = new ServiceImpl(new ApplicationInfo(
|
||||
z, v, appName, authToken));
|
||||
}
|
||||
|
||||
/**
|
||||
* initiate sync in background
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("nls")
|
||||
protected void initiateBackground(Service service) {
|
||||
dataService = MilkDataService.getInstance(service);
|
||||
|
||||
try {
|
||||
String authToken = MilkUtilities.INSTANCE.getToken();
|
||||
|
||||
// check if we have a token & it works
|
||||
if(authToken != null) {
|
||||
initializeService(authToken);
|
||||
if(!rtmService.isServiceAuthorized()) // re-do login
|
||||
authToken = null;
|
||||
}
|
||||
|
||||
if(authToken == null) {
|
||||
// try completing the authorization if it was partial
|
||||
if(rtmService != null) {
|
||||
try {
|
||||
String token = rtmService.completeAuthorization();
|
||||
MilkUtilities.INSTANCE.setToken(token);
|
||||
performSync();
|
||||
|
||||
return;
|
||||
} catch (Exception e) {
|
||||
// didn't work. do the process again.
|
||||
}
|
||||
}
|
||||
|
||||
// can't do anything, user not logged in
|
||||
|
||||
} else {
|
||||
performSync();
|
||||
}
|
||||
} catch (IllegalStateException e) {
|
||||
// occurs when application was closed
|
||||
} catch (Exception e) {
|
||||
handleException("rtm-authenticate", e, true);
|
||||
} finally {
|
||||
MilkUtilities.INSTANCE.stopOngoing();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If user isn't already signed in, show sign in dialog. Else perform sync.
|
||||
*/
|
||||
@SuppressWarnings("nls")
|
||||
@Override
|
||||
protected void initiateManual(final Activity activity) {
|
||||
final Resources r = activity.getResources();
|
||||
String authToken = MilkUtilities.INSTANCE.getToken();
|
||||
MilkUtilities.INSTANCE.stopOngoing();
|
||||
|
||||
// check if we have a token & it works
|
||||
if(authToken == null) {
|
||||
// open up a dialog and have the user go to browser
|
||||
final String url;
|
||||
try {
|
||||
initializeService(null);
|
||||
url = rtmService.beginAuthorization(Perms.delete);
|
||||
} catch (Exception e) {
|
||||
handleException("rmilk-auth", e, true);
|
||||
return;
|
||||
}
|
||||
|
||||
Intent intent = new Intent(activity, MilkLoginActivity.class);
|
||||
MilkLoginActivity.setCallback(new SyncLoginCallback() {
|
||||
public String verifyLogin(final Handler syncLoginHandler) {
|
||||
if(rtmService == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
String token = rtmService.completeAuthorization();
|
||||
MilkUtilities.INSTANCE.setToken(token);
|
||||
synchronize(activity);
|
||||
return null;
|
||||
} catch (Exception e) {
|
||||
// didn't work
|
||||
handleException("rtm-verify-login", e, false);
|
||||
rtmService = null;
|
||||
if(e instanceof ServiceInternalException)
|
||||
e = ((ServiceInternalException)e).getEnclosedException();
|
||||
return r.getString(R.string.rmilk_MLA_error, e.getMessage());
|
||||
}
|
||||
}
|
||||
});
|
||||
intent.putExtra(MilkLoginActivity.URL_TOKEN, url);
|
||||
activity.startActivityForResult(intent, 0);
|
||||
} else {
|
||||
activity.startService(new Intent(MilkBackgroundService.SYNC_ACTION, null,
|
||||
activity, MilkBackgroundService.class));
|
||||
activity.finish();
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// ----------------------------------------------------- synchronization!
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
protected void performSync() {
|
||||
try {
|
||||
// get RTM timeline
|
||||
timeline = rtmService.timelines_create();
|
||||
|
||||
// load RTM lists
|
||||
RtmLists lists = rtmService.lists_getList();
|
||||
dataService.setLists(lists);
|
||||
|
||||
// read all tasks
|
||||
ArrayList<MilkTaskContainer> remoteChanges = new ArrayList<MilkTaskContainer>();
|
||||
Date lastSyncDate = new Date(MilkUtilities.INSTANCE.getLastSyncDate());
|
||||
boolean shouldSyncIndividualLists = false;
|
||||
String filter = null;
|
||||
if(lastSyncDate.getTime() == 0)
|
||||
filter = "status:incomplete"; //$NON-NLS-1$ // 1st time sync: get unfinished tasks
|
||||
|
||||
// try the quick synchronization
|
||||
try {
|
||||
Thread.sleep(2000); // throttle
|
||||
RtmTasks tasks = rtmService.tasks_getList(null, filter, lastSyncDate);
|
||||
addTasksToList(tasks, remoteChanges);
|
||||
} catch (Exception e) {
|
||||
handleException("rtm-quick-sync", e, false); //$NON-NLS-1$
|
||||
remoteChanges.clear();
|
||||
shouldSyncIndividualLists = true;
|
||||
}
|
||||
|
||||
if(shouldSyncIndividualLists) {
|
||||
for(RtmList list : lists.getLists().values()) {
|
||||
if(list.isSmart())
|
||||
continue;
|
||||
try {
|
||||
Thread.sleep(1500);
|
||||
RtmTasks tasks = rtmService.tasks_getList(list.getId(),
|
||||
filter, lastSyncDate);
|
||||
addTasksToList(tasks, remoteChanges);
|
||||
} catch (Exception e) {
|
||||
handleException("rtm-indiv-sync", e, true); //$NON-NLS-1$
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SyncData<MilkTaskContainer> syncData = populateSyncData(remoteChanges);
|
||||
try {
|
||||
synchronizeTasks(syncData);
|
||||
} finally {
|
||||
syncData.localCreated.close();
|
||||
syncData.localUpdated.close();
|
||||
}
|
||||
|
||||
MilkUtilities.INSTANCE.recordSuccessfulSync();
|
||||
Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_EVENT_REFRESH);
|
||||
ContextManager.getContext().sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ);
|
||||
|
||||
} catch (IllegalStateException e) {
|
||||
// occurs when application was closed
|
||||
} catch (Exception e) {
|
||||
handleException("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<MilkTaskContainer> populateSyncData(ArrayList<MilkTaskContainer> remoteTasks) {
|
||||
// fetch locally created tasks
|
||||
TodorooCursor<Task> localCreated = dataService.getLocallyCreated(PROPERTIES);
|
||||
|
||||
// fetch locally updated tasks
|
||||
TodorooCursor<Task> localUpdated = dataService.getLocallyUpdated(PROPERTIES);
|
||||
|
||||
return new SyncData<MilkTaskContainer>(remoteTasks, localCreated, localUpdated);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the tasks read from RTM to the given list
|
||||
*/
|
||||
private void addTasksToList(RtmTasks tasks, ArrayList<MilkTaskContainer> list) {
|
||||
for (RtmTaskList taskList : tasks.getLists()) {
|
||||
for (RtmTaskSeries taskSeries : taskList.getSeries()) {
|
||||
MilkTaskContainer remote = parseRemoteTask(taskSeries);
|
||||
|
||||
// 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);
|
||||
list.add(remote);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// ------------------------------------------------- create / push / pull
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
protected MilkTaskContainer create(MilkTaskContainer task) throws IOException {
|
||||
String listId = null;
|
||||
if(task.listId > 0)
|
||||
listId = Long.toString(task.listId);
|
||||
RtmTaskSeries rtmTask = rtmService.tasks_add(timeline, listId,
|
||||
task.task.getValue(Task.TITLE));
|
||||
MilkTaskContainer newRemoteTask = parseRemoteTask(rtmTask);
|
||||
transferIdentifiers(newRemoteTask, task);
|
||||
push(task, newRemoteTask);
|
||||
return 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(MilkTaskContainer task, Property<?> property, MilkTaskContainer 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(MilkTaskContainer local, MilkTaskContainer 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(MilkDataService.TAG_KEY.equals(item.getValue(Metadata.KEY)))
|
||||
localTags.add(item.getValue(Metadata.VALUE1));
|
||||
if(remote != null && remote.metadata != null) {
|
||||
for(Metadata item : remote.metadata)
|
||||
if(MilkDataService.TAG_KEY.equals(item.getValue(Metadata.KEY)))
|
||||
remoteTags.add(item.getValue(Metadata.VALUE1));
|
||||
}
|
||||
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 = MilkNoteFields.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 MilkTaskContainer 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, MilkDataService.TAG_KEY);
|
||||
tagData.setValue(Metadata.VALUE1, 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, MilkNoteFields.toNoteField(note));
|
||||
} else
|
||||
metadata.add(MilkNoteFields.create(note));
|
||||
}
|
||||
}
|
||||
|
||||
MilkTaskContainer container = new MilkTaskContainer(task, metadata, rtmTaskSeries);
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected MilkTaskContainer pull(MilkTaskContainer 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 MilkTaskContainer read(TodorooCursor<Task> cursor) throws IOException {
|
||||
return dataService.readTaskAndMetadata(cursor);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void write(MilkTaskContainer task) throws IOException {
|
||||
dataService.saveTaskAndMetadata(task);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// --------------------------------------------------------- misc helpers
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
protected int matchTask(ArrayList<MilkTaskContainer> tasks, MilkTaskContainer target) {
|
||||
int length = tasks.size();
|
||||
for(int i = 0; i < length; i++) {
|
||||
MilkTaskContainer 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 int 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 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void transferIdentifiers(MilkTaskContainer source,
|
||||
MilkTaskContainer 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);_=_==115?_-1:_;_=((_>=97)&&(_<=123)?
|
||||
((_-83)%27+97):_);return TextUtils.htmlEncode(____==31?___:
|
||||
stripslashes(____+1,__.substring(1),___+((char)_)));
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,63 @@
|
||||
package org.weloveastrid.rmilk.sync;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
|
||||
import org.weloveastrid.rmilk.api.data.RtmTaskSeries;
|
||||
import org.weloveastrid.rmilk.data.MilkTaskFields;
|
||||
|
||||
import com.todoroo.astrid.data.Metadata;
|
||||
import com.todoroo.astrid.data.Task;
|
||||
import com.todoroo.astrid.sync.SyncContainer;
|
||||
|
||||
/**
|
||||
* RTM Task Container
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
public class MilkTaskContainer extends SyncContainer {
|
||||
public long listId, taskSeriesId, taskId;
|
||||
public boolean repeating;
|
||||
public RtmTaskSeries remote;
|
||||
|
||||
public MilkTaskContainer(Task task, ArrayList<Metadata> metadata,
|
||||
long listId, long taskSeriesId, long taskId, boolean repeating,
|
||||
RtmTaskSeries remote) {
|
||||
this.task = task;
|
||||
this.metadata = metadata;
|
||||
this.listId = listId;
|
||||
this.taskSeriesId = taskSeriesId;
|
||||
this.taskId = taskId;
|
||||
this.repeating = repeating;
|
||||
this.remote = remote;
|
||||
}
|
||||
|
||||
public MilkTaskContainer(Task task, ArrayList<Metadata> metadata,
|
||||
RtmTaskSeries rtmTaskSeries) {
|
||||
this(task, metadata, Long.parseLong(rtmTaskSeries.getList().getId()),
|
||||
Long.parseLong(rtmTaskSeries.getId()), Long.parseLong(rtmTaskSeries.getTask().getId()),
|
||||
rtmTaskSeries.hasRecurrence(), rtmTaskSeries);
|
||||
}
|
||||
|
||||
public MilkTaskContainer(Task task, ArrayList<Metadata> metadata) {
|
||||
this(task, metadata, 0, 0, 0, false, null);
|
||||
for(Iterator<Metadata> iterator = metadata.iterator(); iterator.hasNext(); ) {
|
||||
Metadata item = iterator.next();
|
||||
if(MilkTaskFields.METADATA_KEY.equals(item.getValue(Metadata.KEY))) {
|
||||
if(item.containsNonNullValue(MilkTaskFields.LIST_ID))
|
||||
listId = item.getValue(MilkTaskFields.LIST_ID);
|
||||
if(item.containsNonNullValue(MilkTaskFields.TASK_SERIES_ID))
|
||||
taskSeriesId = item.getValue(MilkTaskFields.TASK_SERIES_ID);
|
||||
if(item.containsNonNullValue(MilkTaskFields.TASK_ID))
|
||||
taskId = item.getValue(MilkTaskFields.TASK_ID);
|
||||
if(item.containsNonNullValue(MilkTaskFields.REPEATING))
|
||||
repeating = item.getValue(MilkTaskFields.REPEATING) == 1;
|
||||
iterator.remove();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue