mirror of https://github.com/tasks/tasks
Remember the milk initial commit. stuff don't compile yet...
parent
a7536dcf4d
commit
c38890a885
@ -0,0 +1,277 @@
|
||||
/**
|
||||
* See the file "LICENSE" for the full license governing this code.
|
||||
*/
|
||||
package com.todoroo.astrid.api;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Resources;
|
||||
|
||||
import com.timsu.astrid.R;
|
||||
import com.todoroo.andlib.data.Property;
|
||||
import com.todoroo.andlib.data.TodorooCursor;
|
||||
import com.todoroo.andlib.data.Property.LongProperty;
|
||||
import com.todoroo.andlib.service.Autowired;
|
||||
import com.todoroo.andlib.service.ContextManager;
|
||||
import com.todoroo.andlib.service.DependencyInjectionService;
|
||||
import com.todoroo.andlib.service.ExceptionService;
|
||||
import com.todoroo.andlib.service.NotificationManager;
|
||||
import com.todoroo.astrid.model.Task;
|
||||
import com.todoroo.astrid.rmilk.MilkPreferences;
|
||||
import com.todoroo.astrid.service.TaskService;
|
||||
|
||||
/**
|
||||
* A helper class for writing synchronization services for Astrid. This class
|
||||
* contains logic for merging incoming changes and writing outgoing changes.
|
||||
* <p>
|
||||
* Use {@link synchronize} as the entry point for your synchronization service,
|
||||
* which should handle authentication and then call {@link synchronizeTasks} to
|
||||
* initiate synchronization.
|
||||
*
|
||||
* @author timsu
|
||||
*
|
||||
*/
|
||||
public abstract class SynchronizationProvider {
|
||||
|
||||
/** Notification Manager id for RMilk notifications */
|
||||
private static final int RMILK_NOTIFICATION_ID = -1;
|
||||
|
||||
// --- abstract methods - your services should implement these
|
||||
|
||||
/**
|
||||
* Synchronize with the service
|
||||
*/
|
||||
abstract public void synchronize();
|
||||
|
||||
/**
|
||||
* Push variables from given task to the remote server.
|
||||
*
|
||||
* @param task
|
||||
* task proxy to push
|
||||
* @param remoteTask
|
||||
* remote task that we merged with. may be null
|
||||
*/
|
||||
abstract protected void push(Task task, Task remote) throws IOException;
|
||||
|
||||
/**
|
||||
* Create a task on the remote server.
|
||||
*
|
||||
* @return task to create
|
||||
*/
|
||||
abstract protected void create(Task task) throws IOException;
|
||||
|
||||
/**
|
||||
* Fetch remote task. Used to re-read merged tasks
|
||||
*
|
||||
* @param task
|
||||
* task with id's to re-read
|
||||
* @return new Task
|
||||
*/
|
||||
abstract protected Task read(Task task) throws IOException;
|
||||
|
||||
/**
|
||||
* Finds a task in the list with the same remote identifier(s) as
|
||||
* the task passed in
|
||||
*
|
||||
* @return task from list if matches, null otherwise
|
||||
*/
|
||||
abstract protected Task matchTask(ArrayList<Task> tasks, Task target);
|
||||
|
||||
/**
|
||||
* Transfer remote identifier(s) from one task to another
|
||||
*/
|
||||
abstract protected void transferIdentifiers(Task source, Task destination);
|
||||
|
||||
// --- implementation
|
||||
|
||||
@Autowired
|
||||
private TaskService taskService;
|
||||
|
||||
@Autowired
|
||||
private ExceptionService exceptionService;
|
||||
|
||||
private final Notification notification;
|
||||
private PendingIntent notificationIntent;
|
||||
|
||||
public SynchronizationProvider() {
|
||||
DependencyInjectionService.getInstance().inject(this);
|
||||
|
||||
// initialize notification
|
||||
int icon = android.R.drawable.stat_notify_sync;
|
||||
long when = System.currentTimeMillis();
|
||||
notification = new Notification(icon, null, when);
|
||||
}
|
||||
|
||||
// --- utilities
|
||||
|
||||
/**
|
||||
* Utility method for showing synchronization errors. If message is null,
|
||||
* the contents of the throwable is displayed. It is assumed that the error
|
||||
* was logged separately.
|
||||
*/
|
||||
protected void showError(final Context context, Throwable e, String message) {
|
||||
exceptionService.displayAndReportError(context, message, e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method to update the UI if we're an active sync, or output to
|
||||
* console if we're a background sync.
|
||||
*/
|
||||
protected void postUpdate(Context context, String string) {
|
||||
notification.setLatestEventInfo(context,
|
||||
context.getString(R.string.rmilk_notification_title), string, notificationIntent);
|
||||
}
|
||||
|
||||
// --- synchronization logic
|
||||
|
||||
/**
|
||||
* Helper to synchronize remote tasks with our local database.
|
||||
*
|
||||
* This initiates the following process: 1. local changes are read 2. remote
|
||||
* changes are read 3. local tasks are merged with remote changes and pushed
|
||||
* across 4. remote changes are then read in
|
||||
*
|
||||
* @param data synchronization data structure
|
||||
*/
|
||||
protected void synchronizeTasks(SyncData data) throws IOException {
|
||||
|
||||
int length;
|
||||
Task task = new Task();
|
||||
Context context = ContextManager.getContext();
|
||||
Resources r = context.getResources();
|
||||
|
||||
// create notification
|
||||
notificationIntent = PendingIntent.getActivity(context, 0, new Intent(context, MilkPreferences.class), 0);
|
||||
postUpdate(context, r.getString(R.string.rmilk_progress_starting));
|
||||
NotificationManager nm = new NotificationManager.AndroidNotificationManager(context);
|
||||
nm.notify(RMILK_NOTIFICATION_ID, notification);
|
||||
|
||||
// create internal data structures
|
||||
HashMap<String, Task> remoteNewTaskNameMap = new HashMap<String, Task>();
|
||||
length = data.remoteUpdated.size();
|
||||
for(int i = 0; i < length; i++) {
|
||||
Task remote = data.remoteUpdated.get(i);
|
||||
if(remote.getId() != Task.NO_ID)
|
||||
continue;
|
||||
remoteNewTaskNameMap.put(remote.getValue(Task.TITLE), remote);
|
||||
}
|
||||
|
||||
// 1. CREATE: grab newly created tasks and create them remotely
|
||||
length = data.localCreated.getCount();
|
||||
for(int i = 0; i < length; i++) {
|
||||
data.localCreated.moveToNext();
|
||||
task.readFromCursor(data.localCreated);
|
||||
|
||||
String taskTitle = task.getValue(Task.TITLE);
|
||||
postUpdate(context, r.getString(R.string.rmilk_progress_localtx,
|
||||
taskTitle));
|
||||
|
||||
/* If there exists an incoming remote task with the same name and no
|
||||
* mapping, we don't want to create this on the remote server,
|
||||
* because user could have synchronized like this before. Instead,
|
||||
* we create a mapping and do an update.
|
||||
*/
|
||||
if (remoteNewTaskNameMap.containsKey(taskTitle)) {
|
||||
Task remote = remoteNewTaskNameMap.remove(taskTitle);
|
||||
remote.setId(task.getId());
|
||||
|
||||
transferIdentifiers(remote, task);
|
||||
push(task, remote);
|
||||
read(remote);
|
||||
} else {
|
||||
create(task);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. UPDATE: for each updated local task
|
||||
length = data.localUpdated.getCount();
|
||||
for(int i = 0; i < length; i++) {
|
||||
data.localUpdated.moveToNext();
|
||||
task.readFromCursor(data.localUpdated);
|
||||
postUpdate(context, r.getString(R.string.rmilk_progress_localtx,
|
||||
task.getValue(Task.TITLE)));
|
||||
|
||||
// if there is a conflict, merge
|
||||
Task remote = matchTask(data.remoteUpdated, task);
|
||||
if(remote != null) {
|
||||
push(task, remote);
|
||||
read(remote);
|
||||
} else {
|
||||
push(task, null);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. REMOTE: load remote information
|
||||
|
||||
// Rearrange remoteTasks so completed tasks get synchronized first.
|
||||
// This prevents bugs where a repeated task has two copies come down
|
||||
// the wire, the new version and the completed old version. The new
|
||||
// version would get merged, then completed, if done in the wrong order.
|
||||
|
||||
Collections.sort(data.remoteUpdated, new Comparator<Task>() {
|
||||
private static final int SENTINEL = -2;
|
||||
private final int check(Task o1, Task o2, LongProperty property) {
|
||||
long o1Property = o1.getValue(property);
|
||||
long o2Property = o2.getValue(property);
|
||||
if(o1Property != 0 && o2Property != 0)
|
||||
return 0;
|
||||
else if(o1Property != 0)
|
||||
return -1;
|
||||
else if(o2Property != 0)
|
||||
return 1;
|
||||
return SENTINEL;
|
||||
}
|
||||
public int compare(Task o1, Task o2) {
|
||||
int comparison = check(o1, o2, Task.DELETION_DATE);
|
||||
if(comparison != SENTINEL)
|
||||
return comparison;
|
||||
comparison = check(o1, o2, Task.COMPLETION_DATE);
|
||||
if(comparison != SENTINEL)
|
||||
return comparison;
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
|
||||
length = data.remoteUpdated.size();
|
||||
for(int i = 0; i < length; i++) {
|
||||
task = data.remoteUpdated.get(i);
|
||||
postUpdate(context, r.getString(R.string.rmilk_progress_remotetx,
|
||||
task.getValue(Task.TITLE)));
|
||||
|
||||
// save the data (TODO: save metadata)
|
||||
taskService.save(task, true);
|
||||
}
|
||||
}
|
||||
|
||||
// --- helper classes
|
||||
|
||||
/** data structure builder */
|
||||
protected static class SyncData {
|
||||
|
||||
public final Property<?>[] properties;
|
||||
public final ArrayList<Task> remoteUpdated;
|
||||
|
||||
public final TodorooCursor<Task> localCreated;
|
||||
public final TodorooCursor<Task> localUpdated;
|
||||
|
||||
public SyncData(Property<?>[] properties,
|
||||
ArrayList<Task> remoteUpdated,
|
||||
TodorooCursor<Task> localCreated,
|
||||
TodorooCursor<Task> localUpdated) {
|
||||
super();
|
||||
this.properties = properties;
|
||||
this.remoteUpdated = remoteUpdated;
|
||||
this.localCreated = localCreated;
|
||||
this.localUpdated = localUpdated;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
/**
|
||||
* See the file "LICENSE" for the full license governing this code.
|
||||
*/
|
||||
package com.todoroo.astrid.rmilk;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import com.todoroo.astrid.R;
|
||||
import com.todoroo.astrid.api.AstridApiConstants;
|
||||
import com.todoroo.astrid.api.TaskDetail;
|
||||
import com.todoroo.astrid.model.Task;
|
||||
import com.todoroo.astrid.rmilk.data.MilkDataService;
|
||||
|
||||
/**
|
||||
* Exposes {@link TaskDetail}s for Remember the Milk:
|
||||
* - RTM list
|
||||
* - RTM repeat information
|
||||
* - whether task has been changed
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
public class DetailExposer extends BroadcastReceiver {
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
// if we aren't logged in, don't expose features
|
||||
if(!Utilities.isLoggedIn())
|
||||
return;
|
||||
|
||||
long taskId = intent.getLongExtra(AstridApiConstants.EXTRAS_TASK_ID, -1);
|
||||
if(taskId == -1)
|
||||
return;
|
||||
|
||||
MilkDataService service = new MilkDataService(context);
|
||||
Task task = service.readTask(taskId);
|
||||
|
||||
if(task == null)
|
||||
return;
|
||||
|
||||
TaskDetail[] details = new TaskDetail[2];
|
||||
|
||||
String listId = task.getValue(MilkDataService.LIST_ID);
|
||||
if(listId != null && listId.length() > 0)
|
||||
details[0] = new TaskDetail(context.getString(R.string.rmilk_TLA_list,
|
||||
service.getList(listId)));
|
||||
else
|
||||
details[0] = null;
|
||||
|
||||
int repeat = task.getValue(MilkDataService.REPEAT);
|
||||
if(repeat != 0)
|
||||
details[1] = new TaskDetail(context.getString(R.string.rmilk_TLA_repeat));
|
||||
else
|
||||
details[1] = null;
|
||||
|
||||
// transmit
|
||||
Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_SEND_DETAILS);
|
||||
broadcastIntent.putExtra(AstridApiConstants.EXTRAS_ITEMS, details);
|
||||
broadcastIntent.putExtra(AstridApiConstants.EXTRAS_TASK_ID, taskId);
|
||||
context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
/**
|
||||
* See the file "LICENSE" for the full license governing this code.
|
||||
*/
|
||||
package com.todoroo.astrid.rmilk;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import com.todoroo.astrid.R;
|
||||
import com.todoroo.astrid.api.AstridApiConstants;
|
||||
import com.todoroo.astrid.api.EditOperation;
|
||||
|
||||
/**
|
||||
* Exposes {@link EditOperation} for Remember the Milk
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
public class EditOperationExposer extends BroadcastReceiver {
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
// if we aren't logged in, don't expose features
|
||||
if(!Utilities.isLoggedIn())
|
||||
return;
|
||||
|
||||
long taskId = intent
|
||||
.getLongExtra(AstridApiConstants.EXTRAS_TASK_ID, -1);
|
||||
if (taskId == -1)
|
||||
return;
|
||||
|
||||
EditOperation taskEditOperation;
|
||||
Intent editIntent = new Intent(context, MilkEditActivity.class);
|
||||
taskEditOperation = new EditOperation(context.getString(
|
||||
R.string.rmilk_EOE_button), editIntent);
|
||||
|
||||
// transmit
|
||||
EditOperation[] operations = new EditOperation[1];
|
||||
operations[0] = taskEditOperation;
|
||||
Intent broadcastIntent = new Intent(
|
||||
AstridApiConstants.BROADCAST_SEND_EDIT_OPERATIONS);
|
||||
broadcastIntent.putExtra(AstridApiConstants.EXTRAS_ITEMS, operations);
|
||||
context.sendBroadcast(broadcastIntent,
|
||||
AstridApiConstants.PERMISSION_READ);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
/**
|
||||
* See the file "LICENSE" for the full license governing this code.
|
||||
*/
|
||||
package com.todoroo.astrid.rmilk;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import com.todoroo.astrid.R;
|
||||
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.rmilk.Utilities.ListContainer;
|
||||
import com.todoroo.astrid.rmilk.data.MilkDataService;
|
||||
|
||||
/**
|
||||
* Exposes filters based on RTM lists
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
public class FilterExposer extends BroadcastReceiver {
|
||||
|
||||
|
||||
@SuppressWarnings("nls")
|
||||
private Filter filterFromList(Context context, ListContainer list) {
|
||||
String listTitle = context.getString(R.string.rmilk_FEx_list_item).
|
||||
replace("$N", list.name).replace("$C", Integer.toString(list.count));
|
||||
String title = context.getString(R.string.rmilk_FEx_list_title, list.name);
|
||||
Filter filter = new Filter(listTitle, title,
|
||||
"TODO",
|
||||
"TODO");
|
||||
|
||||
return filter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
// if we aren't logged in, don't expose features
|
||||
if(!Utilities.isLoggedIn())
|
||||
return;
|
||||
|
||||
MilkDataService service = new MilkDataService(context);
|
||||
ListContainer[] lists = service.getListsWithCounts();
|
||||
|
||||
// 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_ITEMS, list);
|
||||
context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,86 @@
|
||||
/**
|
||||
* See the file "LICENSE" for the full license governing this code.
|
||||
*/
|
||||
package com.todoroo.astrid.rmilk;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.Spinner;
|
||||
|
||||
import com.todoroo.astrid.R;
|
||||
import com.todoroo.astrid.api.AstridApiConstants;
|
||||
import com.todoroo.astrid.model.Task;
|
||||
import com.todoroo.astrid.rmilk.Utilities.ListContainer;
|
||||
import com.todoroo.astrid.rmilk.data.MilkDataService;
|
||||
|
||||
/**
|
||||
* Displays a dialog box for users to edit their RTM stuff
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
public class MilkEditActivity extends Activity {
|
||||
|
||||
long taskId;
|
||||
MilkDataService service;
|
||||
Task model;
|
||||
|
||||
Spinner list;
|
||||
EditText repeat;
|
||||
|
||||
/** Called when loading up the activity */
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
taskId = getIntent().getLongExtra(AstridApiConstants.EXTRAS_TASK_ID, -1);
|
||||
if(taskId == -1)
|
||||
return;
|
||||
|
||||
setContentView(R.layout.rmilk_edit_activity);
|
||||
setTitle(R.string.rmilk_MEA_title);
|
||||
|
||||
((Button)findViewById(R.id.ok)).setOnClickListener(new OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
saveAndQuit();
|
||||
}
|
||||
});
|
||||
((Button)findViewById(R.id.cancel)).setOnClickListener(new OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
setResult(RESULT_CANCELED);
|
||||
finish();
|
||||
}
|
||||
});
|
||||
|
||||
// load all lists
|
||||
service = new MilkDataService(this);
|
||||
ListContainer[] lists = service.getLists();
|
||||
|
||||
list = (Spinner) findViewById(R.id.rmilk_list);
|
||||
ArrayAdapter<ListContainer> listAdapter = new ArrayAdapter<ListContainer>(
|
||||
this, android.R.layout.simple_spinner_item,
|
||||
lists);
|
||||
listAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
list.setAdapter(listAdapter);
|
||||
|
||||
// load model
|
||||
model = service.readTask(taskId);
|
||||
repeat.setText(model.getValue(MilkDataService.REPEAT));
|
||||
list.setSelection(0); // TODO
|
||||
}
|
||||
|
||||
/**
|
||||
* Save tags to task and then quit
|
||||
*/
|
||||
protected void saveAndQuit() {
|
||||
// model.setValue(DataService.LIST_ID, list.getSelectedItem()); TODO
|
||||
|
||||
setResult(RESULT_OK);
|
||||
finish();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,141 @@
|
||||
/*
|
||||
* 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 com.todoroo.astrid.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.service.Autowired;
|
||||
import com.todoroo.andlib.service.DependencyInjectionService;
|
||||
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 {
|
||||
|
||||
@Autowired
|
||||
DialogUtilities dialogUtilities;
|
||||
|
||||
// --- 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
|
||||
|
||||
public MilkLoginActivity() {
|
||||
super();
|
||||
DependencyInjectionService.getInstance().inject(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.rmilk_login_activity);
|
||||
|
||||
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
|
||||
handler.post(new Runnable() {
|
||||
public void run() {
|
||||
dialogUtilities.okDialog(MilkLoginActivity.this,
|
||||
result, null);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
});
|
||||
|
||||
cancel.setOnClickListener(new OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
setResult(RESULT_CANCELED);
|
||||
finish();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,127 @@
|
||||
package com.todoroo.astrid.rmilk;
|
||||
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Color;
|
||||
import android.os.Bundle;
|
||||
import android.preference.CheckBoxPreference;
|
||||
import android.preference.EditTextPreference;
|
||||
import android.preference.ListPreference;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceActivity;
|
||||
import android.preference.PreferenceGroup;
|
||||
import android.preference.PreferenceScreen;
|
||||
import android.preference.Preference.OnPreferenceChangeListener;
|
||||
import android.preference.Preference.OnPreferenceClickListener;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup.OnHierarchyChangeListener;
|
||||
import android.widget.ListView;
|
||||
|
||||
import com.timsu.astrid.R;
|
||||
import com.todoroo.andlib.utility.AndroidUtilities;
|
||||
import com.todoroo.astrid.rmilk.sync.RTMSyncProvider;
|
||||
|
||||
/**
|
||||
* Displays synchronization preferences and an action panel so users can
|
||||
* initiate actions from the menu.
|
||||
*
|
||||
* @author timsu
|
||||
*
|
||||
*/
|
||||
public class MilkPreferences extends PreferenceActivity {
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
addPreferencesFromResource(R.xml.preferences_rmilk);
|
||||
|
||||
PreferenceScreen screen = getPreferenceScreen();
|
||||
initializePreference(screen);
|
||||
|
||||
// status
|
||||
final String status = "Please Log In To RTM!"; //$NON-NLS-1$
|
||||
final int statusColor = Color.RED;
|
||||
|
||||
Resources r = getResources();
|
||||
Preference preference = screen.findPreference(r.getString(R.string.rmilk_MPr_status_key));
|
||||
preference.setTitle(status);
|
||||
|
||||
getListView().setOnHierarchyChangeListener(new OnHierarchyChangeListener() {
|
||||
public void onChildViewRemoved(View arg0, View arg1) {
|
||||
//
|
||||
}
|
||||
public void onChildViewAdded(View parent, View child) {
|
||||
if(((ListView)parent).getChildCount() == 2) {
|
||||
child.setBackgroundColor(statusColor);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// action buttons
|
||||
Preference syncAction = screen.getPreferenceManager().findPreference(
|
||||
getString(R.string.rmilk_MPr_sync_key));
|
||||
syncAction.setOnPreferenceClickListener(new OnPreferenceClickListener() {
|
||||
public boolean onPreferenceClick(Preference p) {
|
||||
new RTMSyncProvider().synchronize(MilkPreferences.this);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
/*Preference clearDataPreference = screen.getPreferenceManager().findPreference(
|
||||
getString(R.string.rmilk_MPr_forget_key));*/
|
||||
}
|
||||
|
||||
private void initializePreference(Preference preference) {
|
||||
if(preference instanceof PreferenceGroup) {
|
||||
PreferenceGroup group = (PreferenceGroup)preference;
|
||||
for(int i = 0; i < group.getPreferenceCount(); i++) {
|
||||
initializePreference(group.getPreference(i));
|
||||
}
|
||||
} else {
|
||||
Object value = null;
|
||||
if(preference instanceof ListPreference)
|
||||
value = ((ListPreference)preference).getValue();
|
||||
else if(preference instanceof CheckBoxPreference)
|
||||
value = ((CheckBoxPreference)preference).isChecked();
|
||||
else if(preference instanceof EditTextPreference)
|
||||
value = ((EditTextPreference)preference).getText();
|
||||
else
|
||||
return;
|
||||
|
||||
updatePreferences(preference, value);
|
||||
|
||||
preference.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
|
||||
public boolean onPreferenceChange(Preference myPreference, Object newValue) {
|
||||
return updatePreferences(myPreference, newValue);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param resource if null, updates all resources
|
||||
*/
|
||||
protected boolean updatePreferences(Preference preference, Object value) {
|
||||
Resources r = getResources();
|
||||
|
||||
// interval
|
||||
if(r.getString(R.string.rmilk_MPr_interval_key).equals(preference.getKey())) {
|
||||
int index = AndroidUtilities.indexOf(r.getStringArray(R.array.rmilk_MPr_interval_values), (String)value);
|
||||
if(index <= 0)
|
||||
preference.setSummary(R.string.rmilk_MPr_interval_desc_disabled);
|
||||
else
|
||||
preference.setSummary(r.getString(R.string.rmilk_MPr_interval_desc,
|
||||
r.getStringArray(R.array.rmilk_MPr_interval_entries)[index]));
|
||||
}
|
||||
|
||||
// shortcut
|
||||
else if(r.getString(R.string.rmilk_MPr_shortcut_key).equals(preference.getKey())) {
|
||||
if((Boolean)value) {
|
||||
preference.setSummary(R.string.rmilk_MPr_shortcut_desc_enabled);
|
||||
} else {
|
||||
preference.setSummary(R.string.rmilk_MPr_shortcut_desc_disabled);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
/**
|
||||
* See the file "LICENSE" for the full license governing this code.
|
||||
*/
|
||||
package com.todoroo.astrid.rmilk;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.SharedPreferences.Editor;
|
||||
import android.content.res.Resources;
|
||||
import android.preference.PreferenceManager;
|
||||
|
||||
import com.todoroo.astrid.R;
|
||||
|
||||
public class StartupReceiver extends BroadcastReceiver {
|
||||
|
||||
public static void setPreferenceDefaults(Context context) {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
Editor editor = prefs.edit();
|
||||
Resources r = context.getResources();
|
||||
|
||||
if(!prefs.contains(r.getString(R.string.rmilk_MPr_interval_key))) {
|
||||
editor.putString(r.getString(R.string.rmilk_MPr_interval_key),
|
||||
Integer.toString(0));
|
||||
}
|
||||
if(!prefs.contains(r.getString(R.string.rmilk_MPr_shortcut_key))) {
|
||||
editor.putBoolean(r.getString(R.string.rmilk_MPr_shortcut_key), true);
|
||||
}
|
||||
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
@Override
|
||||
/** Called when this plug-in run for the first time (installed, upgrade, or device was rebooted */
|
||||
public void onReceive(final Context context, Intent intent) {
|
||||
setPreferenceDefaults(context);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,135 @@
|
||||
/**
|
||||
* See the file "LICENSE" for the full license governing this code.
|
||||
*/
|
||||
package com.todoroo.astrid.rmilk;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.SharedPreferences.Editor;
|
||||
import android.preference.PreferenceManager;
|
||||
|
||||
import com.timsu.astrid.R;
|
||||
import com.todoroo.andlib.service.ContextManager;
|
||||
import com.todoroo.astrid.rmilk.data.MilkList;
|
||||
|
||||
/**
|
||||
* Utility constants
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
@SuppressWarnings("nls")
|
||||
public class Utilities {
|
||||
|
||||
// --- helper classes
|
||||
|
||||
/**
|
||||
* Helper class for describing RTM lists
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*/
|
||||
public static class ListContainer {
|
||||
public ListContainer(MilkList list) {
|
||||
this(list.getValue(MilkList.ID), list.getValue(MilkList.NAME));
|
||||
}
|
||||
|
||||
public ListContainer(long id, String name) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.count = -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public long id;
|
||||
public String name;
|
||||
public int count;
|
||||
}
|
||||
|
||||
// --- Metadata keys
|
||||
// NOTE: no sql escaping is provided for keys
|
||||
|
||||
public static final String KEY_LIST_ID = "rmilk_listId";
|
||||
|
||||
public static final String KEY_TASK_SERIES_ID = "rmilk_taskSeriesId";
|
||||
|
||||
public static final String KEY_TASK_ID = "rmilk_taskId";
|
||||
|
||||
public static final String KEY_REPEAT = "rmilk_repeat";
|
||||
|
||||
public static final String KEY_UPDATED = "rmilk_updated";
|
||||
|
||||
// --- Preference Keys
|
||||
|
||||
private static final String PREF_TOKEN = "rmilk_token";
|
||||
|
||||
private static final String PREF_LAST_SYNC = "rmilk_last_sync";
|
||||
|
||||
// --- Preference Utility Methods
|
||||
|
||||
/** Get preferences object from the context */
|
||||
private static SharedPreferences getPrefs() {
|
||||
return PreferenceManager.getDefaultSharedPreferences(ContextManager.getContext());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if we have a token for this user, false otherwise
|
||||
*/
|
||||
public static boolean isLoggedIn() {
|
||||
return getPrefs().getString(PREF_TOKEN, null) != null;
|
||||
}
|
||||
|
||||
/** RTM authentication token, or null if doesn't exist */
|
||||
public static String getToken() {
|
||||
return getPrefs().getString(PREF_TOKEN, null);
|
||||
}
|
||||
|
||||
/** Sets the RTM authentication token. Set to null to clear. */
|
||||
public static void setToken(String setting) {
|
||||
Editor editor = getPrefs().edit();
|
||||
editor.putString(PREF_TOKEN, setting);
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
/** RTM Last Successful Sync Date, or null */
|
||||
public static Date getLastSyncDate() {
|
||||
Long value = getPrefs().getLong(PREF_LAST_SYNC, 0);
|
||||
if (value == 0)
|
||||
return null;
|
||||
return new Date(value);
|
||||
}
|
||||
|
||||
/** Set RTM Last Successful Sync Date */
|
||||
public static void setLastSyncDate(Date date) {
|
||||
Editor editor = getPrefs().edit();
|
||||
if (date == null) {
|
||||
editor.remove(PREF_LAST_SYNC);
|
||||
} else {
|
||||
editor.putLong(PREF_LAST_SYNC, date.getTime());
|
||||
}
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the frequency, in seconds, auto-sync should occur.
|
||||
*
|
||||
* @return seconds duration, or 0 if not desired
|
||||
*/
|
||||
public static int getSyncAutoSyncFrequency() {
|
||||
String value = getPrefs().getString(
|
||||
ContextManager.getContext().getString(
|
||||
R.string.rmilk_MPr_interval_key), null);
|
||||
if (value == null)
|
||||
return 0;
|
||||
try {
|
||||
return Integer.parseInt(value);
|
||||
} catch (Exception e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,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 com.todoroo.astrid.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,287 @@
|
||||
/*
|
||||
* 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 com.todoroo.astrid.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 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 String serviceRelativeUri;
|
||||
|
||||
private 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 {
|
||||
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,64 @@
|
||||
/*
|
||||
* 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 com.todoroo.astrid.rmilk.api;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import com.todoroo.astrid.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 com.todoroo.astrid.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 com.todoroo.astrid.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,615 @@
|
||||
/*
|
||||
* 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 com.todoroo.astrid.rmilk.api;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import com.todoroo.astrid.rmilk.api.data.RtmAuth;
|
||||
import com.todoroo.astrid.rmilk.api.data.RtmData;
|
||||
import com.todoroo.astrid.rmilk.api.data.RtmFrob;
|
||||
import com.todoroo.astrid.rmilk.api.data.RtmList;
|
||||
import com.todoroo.astrid.rmilk.api.data.RtmLists;
|
||||
import com.todoroo.astrid.rmilk.api.data.RtmLocation;
|
||||
import com.todoroo.astrid.rmilk.api.data.RtmTask;
|
||||
import com.todoroo.astrid.rmilk.api.data.RtmTaskList;
|
||||
import com.todoroo.astrid.rmilk.api.data.RtmTaskNote;
|
||||
import com.todoroo.astrid.rmilk.api.data.RtmTaskSeries;
|
||||
import com.todoroo.astrid.rmilk.api.data.RtmTasks;
|
||||
import com.todoroo.astrid.rmilk.api.data.RtmTimeline;
|
||||
import com.todoroo.astrid.rmilk.api.data.RtmTask.Priority;
|
||||
|
||||
/**
|
||||
* 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
|
||||
{
|
||||
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 com.todoroo.astrid.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 com.todoroo.astrid.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 com.todoroo.astrid.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 com.todoroo.astrid.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,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 com.todoroo.astrid.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 String name;
|
||||
|
||||
public RtmList(String id, String name, boolean smart) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.smart = smart;
|
||||
}
|
||||
|
||||
public RtmList(Element elt) {
|
||||
id = elt.getAttribute("id");
|
||||
name = elt.getAttribute("name");
|
||||
smart = elt.getAttribute("smart") == "1";
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public boolean isSmart() {
|
||||
return smart;
|
||||
}
|
||||
}
|
@ -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 com.todoroo.astrid.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 com.todoroo.astrid.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,194 @@
|
||||
/*
|
||||
* 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 com.todoroo.astrid.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 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 com.todoroo.astrid.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,101 @@
|
||||
/*
|
||||
* 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 com.todoroo.astrid.rmilk.api.data;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import org.w3c.dom.Element;
|
||||
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 String id;
|
||||
|
||||
private Date created;
|
||||
|
||||
private Date modified;
|
||||
|
||||
private 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 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 com.todoroo.astrid.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 com.todoroo.astrid.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,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 com.todoroo.astrid.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 com.todoroo.astrid.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 com.todoroo.astrid.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,282 @@
|
||||
/**
|
||||
* See the file "LICENSE" for the full license governing this code.
|
||||
*/
|
||||
package com.todoroo.astrid.rmilk.data;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.sqlite.SQLiteQueryBuilder;
|
||||
|
||||
import com.todoroo.andlib.data.GenericDao;
|
||||
import com.todoroo.andlib.data.Property;
|
||||
import com.todoroo.andlib.data.TodorooCursor;
|
||||
import com.todoroo.andlib.data.Property.IntegerProperty;
|
||||
import com.todoroo.andlib.service.Autowired;
|
||||
import com.todoroo.andlib.service.DependencyInjectionService;
|
||||
import com.todoroo.astrid.dao.Database;
|
||||
import com.todoroo.astrid.dao.TaskDao;
|
||||
import com.todoroo.astrid.model.Metadata;
|
||||
import com.todoroo.astrid.model.Task;
|
||||
import com.todoroo.astrid.rmilk.Utilities;
|
||||
import com.todoroo.astrid.rmilk.Utilities.ListContainer;
|
||||
import com.todoroo.astrid.rmilk.api.data.RtmList;
|
||||
import com.todoroo.astrid.rmilk.api.data.RtmLists;
|
||||
import com.todoroo.astrid.service.MetadataService;
|
||||
|
||||
@SuppressWarnings("nls")
|
||||
public class MilkDataService {
|
||||
|
||||
protected final Context context;
|
||||
|
||||
private final MilkDatabase milkDatabase = new MilkDatabase();
|
||||
|
||||
@Autowired
|
||||
private MetadataService metadataService;
|
||||
|
||||
private final GenericDao<MilkList> listDao;
|
||||
|
||||
@Autowired
|
||||
private TaskDao taskDao;
|
||||
|
||||
@Autowired
|
||||
private Database database;
|
||||
|
||||
static final Random random = new Random();
|
||||
|
||||
public MilkDataService(Context context) {
|
||||
this.context = context;
|
||||
DependencyInjectionService.getInstance().inject(this);
|
||||
listDao = new GenericDao<MilkList>(MilkList.class, milkDatabase);
|
||||
}
|
||||
|
||||
// --- RTM properties
|
||||
|
||||
/** RTM List id */
|
||||
public static final StringJoinProperty LIST_ID =
|
||||
new StringJoinProperty(Utilities.KEY_LIST_ID);
|
||||
|
||||
/** RTM Task Series id */
|
||||
public static final StringJoinProperty TASK_SERIES_ID =
|
||||
new StringJoinProperty(Utilities.KEY_TASK_SERIES_ID);
|
||||
|
||||
/** RTM Task id */
|
||||
public static final StringJoinProperty TASK_ID =
|
||||
new StringJoinProperty(Utilities.KEY_TASK_ID);
|
||||
|
||||
/** 1 if task repeats in RTM, 0 otherwise */
|
||||
public static final IntegerJoinProperty REPEAT =
|
||||
new IntegerJoinProperty(Utilities.KEY_REPEAT);
|
||||
|
||||
/** 1 if task was updated since last sync, 0 otherwise */
|
||||
public static final IntegerJoinProperty UPDATED =
|
||||
new IntegerJoinProperty(Utilities.KEY_UPDATED);
|
||||
|
||||
// --- non-RTM properties we synchronize
|
||||
|
||||
public static final StringJoinProperty TAGS =
|
||||
new StringJoinProperty(com.todoroo.astrid.tags.TagService.KEY);
|
||||
|
||||
// --- task and metadata methods
|
||||
|
||||
/** Properties to fetch when user wants to view / edit tasks */
|
||||
public static final Property<?>[] RTM_PROPERTIES = new Property<?>[] {
|
||||
Task.ID,
|
||||
LIST_ID,
|
||||
TASK_SERIES_ID,
|
||||
TASK_ID,
|
||||
REPEAT
|
||||
};
|
||||
|
||||
/**
|
||||
* Read a single task by task id
|
||||
* @param taskId
|
||||
* @return item, or null if it doesn't exist
|
||||
*/
|
||||
public Task readTask(long taskId) {
|
||||
SQLiteQueryBuilder builder = new SQLiteQueryBuilder();
|
||||
builder.setTables(createJoinClause(RTM_PROPERTIES));
|
||||
TodorooCursor<Task> cursor =
|
||||
taskDao.query(database, RTM_PROPERTIES, builder,
|
||||
Task.ID.qualifiedName()+ " = " + taskId,
|
||||
null, null);
|
||||
try {
|
||||
if (cursor.getCount() == 0)
|
||||
return null;
|
||||
cursor.moveToFirst();
|
||||
Task task = new Task(cursor, RTM_PROPERTIES);
|
||||
return task;
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
/** Helper method for building a join clause for task/metadata joins */
|
||||
public static String createJoinClause(Property<?>[] properties) {
|
||||
StringBuilder stringBuilder = new StringBuilder(Database.TASK_TABLE);
|
||||
int joinTableCount = 0;
|
||||
for(Property<?> property : properties) {
|
||||
if(property instanceof JoinProperty) {
|
||||
JoinProperty jp = (JoinProperty)property;
|
||||
stringBuilder.append(" LEFT JOIN (").append(jp.joinTable()).
|
||||
append(") m").append(++joinTableCount).
|
||||
append(" ON ").append(Task.ID_PROPERTY).append(" = m").
|
||||
append(joinTableCount).append('.').append(Metadata.TASK.name).
|
||||
append(' ');
|
||||
}
|
||||
}
|
||||
return stringBuilder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears RTM metadata information. Used when user logs out of RTM
|
||||
*/
|
||||
public void clearMetadata() {
|
||||
metadataService.deleteWhere(String.format("%s = '%s' OR %s = '%s' " +
|
||||
"OR %s = '%s OR %s = '%s'",
|
||||
Metadata.KEY, LIST_ID,
|
||||
Metadata.KEY, TASK_SERIES_ID,
|
||||
Metadata.KEY, TASK_ID,
|
||||
Metadata.KEY, REPEAT));
|
||||
}
|
||||
|
||||
public TodorooCursor<Task> getLocallyCreated(Property<?>[] properties) {
|
||||
SQLiteQueryBuilder builder = new SQLiteQueryBuilder();
|
||||
builder.setTables(createJoinClause(properties));
|
||||
TodorooCursor<Task> cursor =
|
||||
taskDao.query(database, properties, builder,
|
||||
TASK_ID + " ISNULL",
|
||||
null, null);
|
||||
return cursor;
|
||||
}
|
||||
|
||||
public TodorooCursor<Task> getLocallyUpdated(Property<?>[] properties) {
|
||||
SQLiteQueryBuilder builder = new SQLiteQueryBuilder();
|
||||
builder.setTables(createJoinClause(properties));
|
||||
TodorooCursor<Task> cursor =
|
||||
taskDao.query(database, properties, builder,
|
||||
"NOT " + TASK_ID + " ISNULL", // TODO wrong!
|
||||
null, null);
|
||||
return cursor;
|
||||
}
|
||||
|
||||
// --- list methods
|
||||
|
||||
/**
|
||||
* Get list name by list id
|
||||
* @param listId
|
||||
* @return null if no list by this id exists, otherwise list name
|
||||
*/
|
||||
public String getList(String listId) {
|
||||
pluginDatabase.open(context);
|
||||
TodorooCursor<List> cursor = listDao.fetch(pluginDatabase,
|
||||
List.PROPERTIES, ListSql.withId(listId), null, "1"); //$NON-NLS-1$
|
||||
try {
|
||||
if(cursor.getCount() == 0)
|
||||
return null;
|
||||
cursor.moveToFirst();
|
||||
return cursor.get(List.NAME);
|
||||
} finally {
|
||||
cursor.close();
|
||||
pluginDatabase.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get RTM lists as container objects
|
||||
* @return
|
||||
*/
|
||||
public ListContainer[] getListsWithCounts() {
|
||||
// read all list titles
|
||||
pluginDatabase.open(context);
|
||||
TodorooCursor<List> listCursor = listDao.fetch(pluginDatabase,
|
||||
List.PROPERTIES, null);
|
||||
HashMap<String, ListContainer> listIdToContainerMap;
|
||||
try {
|
||||
int count = listCursor.getCount();
|
||||
if(count == 0)
|
||||
return new ListContainer[0];
|
||||
|
||||
listIdToContainerMap =
|
||||
new HashMap<String, ListContainer>(count);
|
||||
List list = new List();
|
||||
for(int i = 0; i < count; i++) {
|
||||
listCursor.moveToNext();
|
||||
list.readFromCursor(listCursor, List.PROPERTIES);
|
||||
ListContainer container = new ListContainer(list);
|
||||
listIdToContainerMap.put(container.id, container);
|
||||
}
|
||||
} finally {
|
||||
listCursor.close();
|
||||
}
|
||||
|
||||
// read all list counts
|
||||
IntegerProperty countProperty = Property.countProperty();
|
||||
TodorooCursor<Metadata> metadataCursor = metadataService.fetchWithCount(
|
||||
MetadataSql.withKey(Utilities.KEY_LIST_ID), Metadata.VALUE + " ASC", false); //$NON-NLS-1$
|
||||
ListContainer[] containers = new ListContainer[metadataCursor.getCount()];
|
||||
try {
|
||||
for(int i = 0; i < containers.length; i++) {
|
||||
metadataCursor.moveToNext();
|
||||
String id = metadataCursor.get(Metadata.VALUE);
|
||||
ListContainer container = listIdToContainerMap.get(id);
|
||||
if(container == null) {
|
||||
container = new ListContainer(id, "[unknown]"); //$NON-NLS-1$
|
||||
}
|
||||
container.count = metadataCursor.get(countProperty);
|
||||
containers[i] = container;
|
||||
}
|
||||
return containers;
|
||||
} finally {
|
||||
metadataCursor.close();
|
||||
pluginDatabase.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get RTM lists as strings
|
||||
* @return
|
||||
*/
|
||||
public ListContainer[] getLists() {
|
||||
// read all list titles
|
||||
pluginDatabase.open(context);
|
||||
TodorooCursor<List> cursor = listDao.fetch(pluginDatabase,
|
||||
List.PROPERTIES, null, List.ID + " ASC"); //$NON-NLS-1$
|
||||
ListContainer[] containers = new ListContainer[cursor.getCount()];
|
||||
try {
|
||||
List list = new List();
|
||||
for(int i = 0; i < containers.length; i++) {
|
||||
cursor.moveToNext();
|
||||
list.readFromCursor(cursor, List.PROPERTIES);
|
||||
ListContainer container = new ListContainer(list);
|
||||
containers[i] = container;
|
||||
}
|
||||
return containers;
|
||||
} finally {
|
||||
cursor.close();
|
||||
pluginDatabase.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears current cache of RTM lists and re-populates
|
||||
* @param lists
|
||||
*/
|
||||
public void setLists(RtmLists lists) {
|
||||
pluginDatabase.open(context);
|
||||
try {
|
||||
List model = new List();
|
||||
for(Map.Entry<String, RtmList> list : lists.getLists().entrySet()) {
|
||||
model.setValue(List.ID, list.getValue().getId());
|
||||
model.setValue(List.NAME, list.getValue().getName());
|
||||
listDao.save(pluginDatabase, model);
|
||||
}
|
||||
} finally {
|
||||
pluginDatabase.close();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright (c) 2009, Todoroo Inc
|
||||
* All Rights Reserved
|
||||
* http://www.todoroo.com
|
||||
*/
|
||||
package com.todoroo.astrid.rmilk.data;
|
||||
|
||||
import com.todoroo.andlib.data.AbstractDatabase;
|
||||
import com.todoroo.andlib.data.GenericDao;
|
||||
import com.todoroo.andlib.data.Table;
|
||||
|
||||
/**
|
||||
* Database wrapper
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
@SuppressWarnings("nls")
|
||||
public class MilkDatabase extends AbstractDatabase {
|
||||
|
||||
// --- constants
|
||||
|
||||
/**
|
||||
* Database version number. This variable must be updated when database
|
||||
* tables are updated, as it determines whether a database needs updating.
|
||||
*/
|
||||
public static final int VERSION = 1;
|
||||
|
||||
/**
|
||||
* Database name (must be unique)
|
||||
*/
|
||||
private static final String NAME = "milk";
|
||||
|
||||
/**
|
||||
* List of table/ If you're adding a new table, add it to this list and
|
||||
* also make sure that our SQLite helper does the right thing.
|
||||
*/
|
||||
public static final Table[] TABLES = new Table[] {
|
||||
MilkList.TABLE
|
||||
};
|
||||
|
||||
// --- implementation
|
||||
|
||||
private final GenericDao<MilkList> dao = new GenericDao<MilkList>(MilkList.class, this);
|
||||
|
||||
@Override
|
||||
protected String getName() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getVersion() {
|
||||
return VERSION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Table[] getTables() {
|
||||
return TABLES;
|
||||
}
|
||||
|
||||
public GenericDao<MilkList> getDao() {
|
||||
return dao;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreateTables() {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean onUpgrade(int oldVersion, int newVersion) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,103 @@
|
||||
/**
|
||||
* See the file "LICENSE" for the full license governing this code.
|
||||
*/
|
||||
package com.todoroo.astrid.rmilk.data;
|
||||
|
||||
|
||||
import android.content.ContentValues;
|
||||
|
||||
import com.todoroo.andlib.data.AbstractModel;
|
||||
import com.todoroo.andlib.data.Property;
|
||||
import com.todoroo.andlib.data.Table;
|
||||
import com.todoroo.andlib.data.TodorooCursor;
|
||||
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.model.Task;
|
||||
|
||||
/**
|
||||
* Data Model which represents a list in RTM
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
@SuppressWarnings("nls")
|
||||
public class MilkList extends AbstractModel {
|
||||
|
||||
// --- table
|
||||
|
||||
public static final Table TABLE = new Table("lists", MilkList.class);
|
||||
|
||||
// --- properties
|
||||
|
||||
/** ID (corresponds to RTM ID) */
|
||||
public static final LongProperty ID = new LongProperty(
|
||||
TABLE, ID_PROPERTY_NAME);
|
||||
|
||||
/** Name */
|
||||
public static final StringProperty NAME = new StringProperty(
|
||||
TABLE, "name");
|
||||
|
||||
/** Position */
|
||||
public static final IntegerProperty POSITION = new IntegerProperty(
|
||||
TABLE, "position");
|
||||
|
||||
/** Archived (0 or 1) */
|
||||
public static final IntegerProperty ARCHIVED = new IntegerProperty(
|
||||
TABLE, "archived");
|
||||
|
||||
/** List of all properties for this model */
|
||||
public static final Property<?>[] PROPERTIES = generateProperties(MilkList.class);
|
||||
|
||||
// --- defaults
|
||||
|
||||
/** Default values container */
|
||||
private static final ContentValues defaultValues = new ContentValues();
|
||||
|
||||
static {
|
||||
defaultValues.put(POSITION.name, 0);
|
||||
defaultValues.put(ARCHIVED.name, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ContentValues getDefaultValues() {
|
||||
return defaultValues;
|
||||
}
|
||||
|
||||
// --- data access boilerplate
|
||||
|
||||
public MilkList() {
|
||||
super();
|
||||
}
|
||||
|
||||
public MilkList(TodorooCursor<MilkList> cursor) {
|
||||
this();
|
||||
readPropertiesFromCursor(cursor);
|
||||
}
|
||||
|
||||
public void readFromCursor(TodorooCursor<MilkList> cursor) {
|
||||
super.readPropertiesFromCursor(cursor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getId() {
|
||||
return getIdHelper(ID);
|
||||
};
|
||||
|
||||
/**
|
||||
* @return whether this list is archived. requires {@link ARCHIVED}
|
||||
*/
|
||||
public boolean isArchived() {
|
||||
return getValue(ARCHIVED) > 0;
|
||||
}
|
||||
|
||||
// --- parcelable helpers
|
||||
|
||||
private static final Creator<Task> CREATOR = new ModelCreator<Task>(Task.class);
|
||||
|
||||
@Override
|
||||
protected Creator<? extends AbstractModel> getCreator() {
|
||||
return CREATOR;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,465 @@
|
||||
/**
|
||||
* See the file "LICENSE" for the full license governing this code.
|
||||
*/
|
||||
package com.todoroo.astrid.rmilk.sync;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
|
||||
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.flurry.android.FlurryAgent;
|
||||
import com.timsu.astrid.R;
|
||||
import com.todoroo.andlib.data.Property;
|
||||
import com.todoroo.andlib.data.TodorooCursor;
|
||||
import com.todoroo.andlib.service.Autowired;
|
||||
import com.todoroo.andlib.service.ContextManager;
|
||||
import com.todoroo.andlib.service.DependencyInjectionService;
|
||||
import com.todoroo.andlib.service.ExceptionService;
|
||||
import com.todoroo.andlib.utility.AndroidUtilities;
|
||||
import com.todoroo.andlib.utility.DateUtilities;
|
||||
import com.todoroo.astrid.api.SynchronizationProvider;
|
||||
import com.todoroo.astrid.model.Task;
|
||||
import com.todoroo.astrid.rmilk.MilkLoginActivity;
|
||||
import com.todoroo.astrid.rmilk.Utilities;
|
||||
import com.todoroo.astrid.rmilk.MilkLoginActivity.SyncLoginCallback;
|
||||
import com.todoroo.astrid.rmilk.api.ApplicationInfo;
|
||||
import com.todoroo.astrid.rmilk.api.ServiceImpl;
|
||||
import com.todoroo.astrid.rmilk.api.ServiceInternalException;
|
||||
import com.todoroo.astrid.rmilk.api.data.RtmList;
|
||||
import com.todoroo.astrid.rmilk.api.data.RtmLists;
|
||||
import com.todoroo.astrid.rmilk.api.data.RtmTask;
|
||||
import com.todoroo.astrid.rmilk.api.data.RtmTaskList;
|
||||
import com.todoroo.astrid.rmilk.api.data.RtmTaskSeries;
|
||||
import com.todoroo.astrid.rmilk.api.data.RtmTasks;
|
||||
import com.todoroo.astrid.rmilk.api.data.RtmAuth.Perms;
|
||||
import com.todoroo.astrid.rmilk.api.data.RtmTask.Priority;
|
||||
import com.todoroo.astrid.rmilk.data.MilkDataService;
|
||||
import com.todoroo.astrid.service.AstridDependencyInjector;
|
||||
|
||||
public class RTMSyncProvider extends SynchronizationProvider {
|
||||
|
||||
protected ServiceImpl rtmService = null;
|
||||
protected String timeline = null;
|
||||
protected MilkDataService dataService = null;
|
||||
|
||||
static {
|
||||
AstridDependencyInjector.initialize();
|
||||
}
|
||||
|
||||
@Autowired
|
||||
protected ExceptionService exceptionService;
|
||||
|
||||
public RTMSyncProvider() {
|
||||
super();
|
||||
DependencyInjectionService.getInstance().inject(this);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// ------------------------------------------------------- public methods
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public void synchronize() {
|
||||
Context context = ContextManager.getContext();
|
||||
dataService = new MilkDataService(context);
|
||||
|
||||
// authenticate the user. this will automatically call the next step
|
||||
authenticate(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign out of RTM, deleting all synchronization metadata
|
||||
*/
|
||||
public void signOut() {
|
||||
Utilities.setToken(null);
|
||||
Utilities.setLastSyncDate(null);
|
||||
dataService.clearMetadata();
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// ------------------------------------------------------- authentication
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Deal with a synchronization exception. If requested, will show an error
|
||||
* to the user (unless synchronization is happening in background)
|
||||
*
|
||||
* @param context
|
||||
* @param tag
|
||||
* error tag
|
||||
* @param e
|
||||
* exception
|
||||
* @param showErrorIfNeeded
|
||||
* whether to display a dialog
|
||||
*/
|
||||
private void handleRtmException(Context context, String tag, Exception e,
|
||||
boolean showErrorIfNeeded) {
|
||||
// occurs when application was closed
|
||||
if(e instanceof IllegalStateException) {
|
||||
exceptionService.reportError(tag + "-caught", e); //$NON-NLS-1$
|
||||
|
||||
// occurs when network error
|
||||
} else if(e instanceof ServiceInternalException &&
|
||||
((ServiceInternalException)e).getEnclosedException() instanceof
|
||||
IOException) {
|
||||
Exception enclosedException = ((ServiceInternalException)e).getEnclosedException();
|
||||
exceptionService.reportError(tag + "-ioexception", enclosedException); //$NON-NLS-1$
|
||||
if(showErrorIfNeeded) {
|
||||
showError(context, enclosedException,
|
||||
context.getString(R.string.rmilk_ioerror));
|
||||
}
|
||||
} else {
|
||||
if(e instanceof ServiceInternalException)
|
||||
e = ((ServiceInternalException)e).getEnclosedException();
|
||||
exceptionService.reportError(tag + "-unhandled", e); //$NON-NLS-1$
|
||||
if(showErrorIfNeeded)
|
||||
showError(context, e, null);
|
||||
}
|
||||
}
|
||||
|
||||
/** Perform authentication with RTM. Will open the SyncBrowser if necessary */
|
||||
@SuppressWarnings("nls")
|
||||
private void authenticate(final Context context) {
|
||||
final Resources r = context.getResources();
|
||||
FlurryAgent.onEvent("rtm-started");
|
||||
|
||||
try {
|
||||
String appName = null;
|
||||
String authToken = Utilities.getToken();
|
||||
String z = stripslashes(0,"q9883o3384n21snq17501qn38oo1r689", "b");
|
||||
String v = stripslashes(16,"19o2n020345219os","a");
|
||||
|
||||
// check if we have a token & it works
|
||||
if(authToken != null) {
|
||||
rtmService = new ServiceImpl(new ApplicationInfo(
|
||||
z, v, appName, 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();
|
||||
Utilities.setToken(token);
|
||||
performSync(context);
|
||||
|
||||
return;
|
||||
} catch (Exception e) {
|
||||
// didn't work. do the process again.
|
||||
}
|
||||
}
|
||||
|
||||
// open up a dialog and have the user go to browser
|
||||
rtmService = new ServiceImpl(new ApplicationInfo(
|
||||
z, v, appName));
|
||||
final String url = rtmService.beginAuthorization(Perms.delete);
|
||||
|
||||
Intent intent = new Intent(context, MilkLoginActivity.class);
|
||||
MilkLoginActivity.setCallback(new SyncLoginCallback() {
|
||||
public String verifyLogin(final Handler syncLoginHandler) {
|
||||
if(rtmService == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
String token = rtmService.completeAuthorization();
|
||||
Utilities.setToken(token);
|
||||
return null;
|
||||
} catch (Exception e) {
|
||||
// didn't work
|
||||
exceptionService.reportError("rtm-verify-login", e);
|
||||
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);
|
||||
context.startActivity(intent);
|
||||
|
||||
} else {
|
||||
performSync(context);
|
||||
}
|
||||
} catch (IllegalStateException e) {
|
||||
// occurs when application was closed
|
||||
} catch (Exception e) {
|
||||
handleRtmException(context, "rtm-authenticate", e, true);
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// ----------------------------------------------------- synchronization!
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
private void performSync(final Context context) {
|
||||
new Thread(new Runnable() {
|
||||
public void run() {
|
||||
performSyncInNewThread(context);
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
protected void performSyncInNewThread(final Context context) {
|
||||
try {
|
||||
// get RTM timeline
|
||||
timeline = rtmService.timelines_create();
|
||||
|
||||
// load RTM lists
|
||||
RtmLists lists = rtmService.lists_getList();
|
||||
dataService.setLists(lists);
|
||||
|
||||
// read all tasks
|
||||
ArrayList<Task> remoteChanges = new ArrayList<Task>();
|
||||
Date lastSyncDate = Utilities.getLastSyncDate();
|
||||
boolean shouldSyncIndividualLists = false;
|
||||
String filter = null;
|
||||
if(lastSyncDate == null)
|
||||
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) {
|
||||
handleRtmException(context, "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) {
|
||||
handleRtmException(context, "rtm-indiv-sync", e, true); //$NON-NLS-1$
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SyncData syncData = populateSyncData(remoteChanges);
|
||||
synchronizeTasks(syncData);
|
||||
|
||||
Date syncTime = new Date(System.currentTimeMillis());
|
||||
Utilities.setLastSyncDate(syncTime);
|
||||
|
||||
FlurryAgent.onEvent("rtm-sync-finished"); //$NON-NLS-1$
|
||||
} catch (IllegalStateException e) {
|
||||
// occurs when application was closed
|
||||
} catch (Exception e) {
|
||||
handleRtmException(context, "rtm-sync", e, true); //$NON-NLS-1$
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// ------------------------------------------------------- helper methods
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Populate SyncData data structure
|
||||
*/
|
||||
private SyncData populateSyncData(ArrayList<Task> remoteTasks) {
|
||||
// all synchronized properties
|
||||
Property<?>[] properties = new Property<?>[] {
|
||||
Task.ID,
|
||||
Task.TITLE,
|
||||
Task.IMPORTANCE,
|
||||
Task.DUE_DATE,
|
||||
Task.CREATION_DATE,
|
||||
Task.COMPLETION_DATE,
|
||||
Task.DELETION_DATE,
|
||||
MilkDataService.LIST_ID,
|
||||
MilkDataService.TASK_SERIES_ID,
|
||||
MilkDataService.TASK_ID,
|
||||
MilkDataService.REPEAT,
|
||||
MilkDataService.TAGS,
|
||||
};
|
||||
|
||||
// fetch locally created tasks
|
||||
TodorooCursor<Task> localCreated = dataService.getLocallyCreated(properties);
|
||||
|
||||
// fetch locally updated tasks
|
||||
TodorooCursor<Task> localUpdated = dataService.getLocallyUpdated(properties);
|
||||
|
||||
return new SyncData(properties, remoteTasks, localCreated, localUpdated);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the tasks read from RTM to the given list
|
||||
*/
|
||||
private void addTasksToList(RtmTasks tasks, ArrayList<Task> list) {
|
||||
for (RtmTaskList taskList : tasks.getLists()) {
|
||||
for (RtmTaskSeries taskSeries : taskList.getSeries()) {
|
||||
Task remoteTask = parseRemoteTask(taskSeries);
|
||||
list.add(remoteTask);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(Task task, Property<?> property, Task remoteTask) {
|
||||
if(!task.hasValue(property))
|
||||
return false;
|
||||
|
||||
if(remoteTask == null)
|
||||
return true;
|
||||
if(!remoteTask.hasValue(property))
|
||||
return true;
|
||||
return !AndroidUtilities.equals(task.getValue(property), remoteTask.getValue(property));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void create(Task task) throws IOException {
|
||||
String listId = null;
|
||||
if(task.hasValue(MilkDataService.LIST_ID))
|
||||
listId = task.getValue(MilkDataService.LIST_ID);
|
||||
|
||||
RtmTaskSeries rtmTask = rtmService.tasks_add(timeline, listId,
|
||||
task.getValue(Task.TITLE));
|
||||
push(task, parseRemoteTask(rtmTask));
|
||||
}
|
||||
|
||||
/** Send changes for the given TaskProxy across the wire */
|
||||
@Override
|
||||
protected void push(Task task, Task remoteTask) throws IOException {
|
||||
RtmId id = new RtmId(task);
|
||||
|
||||
// fetch remote task for comparison
|
||||
if(remoteTask == null) {
|
||||
remoteTask = read(task);
|
||||
}
|
||||
|
||||
if(shouldTransmit(task, Task.TITLE, remoteTask))
|
||||
rtmService.tasks_setName(timeline, id.listId, id.taskSeriesId,
|
||||
id.taskId, task.getValue(Task.TITLE));
|
||||
if(shouldTransmit(task, Task.IMPORTANCE, remoteTask))
|
||||
rtmService.tasks_setPriority(timeline, id.listId, id.taskSeriesId,
|
||||
id.taskId, Priority.values()[task.getValue(Task.IMPORTANCE)]);
|
||||
if(shouldTransmit(task, Task.DUE_DATE, remoteTask))
|
||||
rtmService.tasks_setDueDate(timeline, id.listId, id.taskSeriesId,
|
||||
id.taskId, DateUtilities.unixtimeToDate(task.getValue(Task.DUE_DATE)),
|
||||
task.getValue(Task.URGENCY) == Task.URGENCY_SPECIFIC_DAY_TIME);
|
||||
if(shouldTransmit(task, Task.COMPLETION_DATE, remoteTask)) {
|
||||
if(task.getValue(Task.COMPLETION_DATE) == 0)
|
||||
rtmService.tasks_uncomplete(timeline, id.listId, id.taskSeriesId,
|
||||
id.taskId);
|
||||
else
|
||||
rtmService.tasks_complete(timeline, id.listId, id.taskSeriesId,
|
||||
id.taskId);
|
||||
}
|
||||
if(shouldTransmit(task, Task.DELETION_DATE, remoteTask))
|
||||
rtmService.tasks_delete(timeline, id.listId, id.taskSeriesId,
|
||||
id.taskId);
|
||||
|
||||
if(shouldTransmit(task, MilkDataService.LIST_ID, remoteTask) && remoteTask != null)
|
||||
rtmService.tasks_moveTo(timeline, remoteTask.getValue(MilkDataService.LIST_ID),
|
||||
id.listId, id.taskSeriesId, id.taskId);
|
||||
}
|
||||
|
||||
/** Create a task proxy for the given RtmTaskSeries */
|
||||
private Task parseRemoteTask(RtmTaskSeries rtmTaskSeries) {
|
||||
Task task = new Task();
|
||||
|
||||
task.setValue(MilkDataService.LIST_ID, rtmTaskSeries.getList().getId());
|
||||
task.setValue(MilkDataService.TASK_SERIES_ID, rtmTaskSeries.getId());
|
||||
task.setValue(Task.TITLE, rtmTaskSeries.getName());
|
||||
task.setValue(MilkDataService.REPEAT, rtmTaskSeries.hasRecurrence() ? 1 : 0);
|
||||
|
||||
RtmTask rtmTask = rtmTaskSeries.getTask();
|
||||
if(rtmTask != null) {
|
||||
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, DateUtilities.dateToUnixtime(rtmTask.getDue()));
|
||||
if(rtmTask.getHasDueTime())
|
||||
task.setValue(Task.URGENCY, Task.URGENCY_SPECIFIC_DAY_TIME);
|
||||
else
|
||||
task.setValue(Task.URGENCY, Task.URGENCY_SPECIFIC_DAY);
|
||||
} else {
|
||||
task.setValue(Task.URGENCY, Task.URGENCY_NONE);
|
||||
}
|
||||
task.setValue(Task.IMPORTANCE, rtmTask.getPriority().ordinal());
|
||||
} else {
|
||||
// error in upstream code, try to handle gracefully
|
||||
Log.e("rtmsync", "Got null task parsing remote task series",
|
||||
new Throwable());
|
||||
}
|
||||
|
||||
return task;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Task matchTask(ArrayList<Task> tasks, Task target) {
|
||||
int length = tasks.size();
|
||||
for(int i = 0; i < length; i++) {
|
||||
Task task = tasks.get(i);
|
||||
if(task.getValue(MilkDataService.LIST_ID).equals(target.getValue(MilkDataService.LIST_ID)) &&
|
||||
task.getValue(MilkDataService.TASK_SERIES_ID).equals(target.getValue(MilkDataService.TASK_SERIES_ID)) &&
|
||||
task.getValue(MilkDataService.TASK_ID).equals(target.getValue(MilkDataService.TASK_ID)))
|
||||
return task;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Task read(Task task) throws IOException {
|
||||
RtmTaskSeries rtmTask = rtmService.tasks_getTask(task.getValue(MilkDataService.TASK_SERIES_ID),
|
||||
task.getValue(Task.TITLE));
|
||||
if(rtmTask != null)
|
||||
return parseRemoteTask(rtmTask);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void transferIdentifiers(Task source, Task destination) {
|
||||
destination.setValue(MilkDataService.LIST_ID, source.getValue(MilkDataService.LIST_ID));
|
||||
destination.setValue(MilkDataService.TASK_SERIES_ID, source.getValue(MilkDataService.TASK_SERIES_ID));
|
||||
destination.setValue(MilkDataService.TASK_ID, source.getValue(MilkDataService.TASK_ID));
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// ------------------------------------------------------- helper classes
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
/** Helper class for processing RTM id's into one field */
|
||||
private static class RtmId {
|
||||
public String taskId;
|
||||
public String taskSeriesId;
|
||||
public String listId;
|
||||
|
||||
public RtmId(Task task) {
|
||||
taskId = task.getValue(MilkDataService.TASK_ID);
|
||||
taskSeriesId = task.getValue(MilkDataService.TASK_SERIES_ID);
|
||||
listId = task.getValue(MilkDataService.LIST_ID);
|
||||
}
|
||||
}
|
||||
|
||||
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,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="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/rmilk_MEA_list_label"
|
||||
style="@style/TextAppearance.GEN_EditLabel"/>
|
||||
|
||||
<Spinner android:id="@+id/rmilk_list"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/rmilk_MEA_repeat_label"
|
||||
style="@style/TextAppearance.GEN_EditLabel"/>
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<Button android:id="@+id/ok"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@android:string/ok" />
|
||||
<Button android:id="@+id/cancel"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@android:string/cancel" />
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
@ -0,0 +1,41 @@
|
||||
<?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"
|
||||
style="@style/TextAppearance.GEN_EditLabel"/>
|
||||
|
||||
<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/rmilk_MLA_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,170 @@
|
||||
<?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">
|
||||
|
||||
<!-- ====================== Plugin Boilerplate ========================= -->
|
||||
|
||||
<!-- label for RMilk button in Task Edit Activity -->
|
||||
<string name="rmilk_EOE_button">Remember the Milk Settings</string>
|
||||
|
||||
<!-- task detail showing RTM list information -->
|
||||
<string name="rmilk_TLA_list">RTM List: %s</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 -->
|
||||
<string name="rmilk_FEx_header">Remember the Milk</string>
|
||||
|
||||
<!-- filter category for RTM lists -->
|
||||
<string name="rmilk_FEx_list">Lists</string>
|
||||
|
||||
<!-- RTM list filter name ($N => list, $C => count) -->
|
||||
<string name="rmilk_FEx_list_item">$N ($C)</string>
|
||||
|
||||
<!-- RTM list filter title (%s => list) -->
|
||||
<string name="rmilk_FEx_list_title">RTM List \'%s\'</string>
|
||||
|
||||
<!-- ======================= MilkEditActivity ========================== -->
|
||||
|
||||
<!-- Title -->
|
||||
<string name="rmilk_MEA_title">Remember the Milk</string>
|
||||
|
||||
<!-- List Edit Label -->
|
||||
<string name="rmilk_MEA_list_label">RTM List:</string>
|
||||
|
||||
<!-- Repeat Label -->
|
||||
<string name="rmilk_MEA_repeat_label">RTM Repeat Status:</string>
|
||||
|
||||
<!-- Repeat Hint -->
|
||||
<string name="rmilk_MEA_repeat_hint">i.e. every week, after 14 days</string>
|
||||
|
||||
<!-- ======================== MilkPreferences ========================== -->
|
||||
|
||||
<!-- Title -->
|
||||
<string name="rmilk_MPr_header">Remember the Milk</string>
|
||||
|
||||
<!-- Status Group Label -->
|
||||
<string name="rmilk_MPr_group_status">Status</string>
|
||||
<!-- Preference Key (do not translate) -->
|
||||
<string name="rmilk_MPr_status_key">rmilk_status</string>
|
||||
|
||||
<!-- Options Group Label -->
|
||||
<string name="rmilk_MPr_group_options">Options</string>
|
||||
|
||||
<!-- Synchronization Interval Title -->
|
||||
<string name="rmilk_MPr_interval_title">Synchronization Interval</string>
|
||||
<!-- Synchronization Interval Description (when disabled) -->
|
||||
<string name="rmilk_MPr_interval_desc_disabled">Background synchronization is disabled</string>
|
||||
<!-- Synchronization Interval Description (%s => setting) -->
|
||||
<string name="rmilk_MPr_interval_desc">Currently set to: %s</string>
|
||||
<!-- Preference Key (do not translate) -->
|
||||
<string name="rmilk_MPr_interval_key">rmilk_interval</string>
|
||||
|
||||
<!-- Shortcut Title -->
|
||||
<string name="rmilk_MPr_shortcut_title">Main Menu Shortcut</string>
|
||||
<!-- Shortcut Description (enabled) -->
|
||||
<string name="rmilk_MPr_shortcut_desc_enabled">Shortcut will be shown in main menu</string>
|
||||
<!-- Shortcut Description (disabled) -->
|
||||
<string name="rmilk_MPr_shortcut_desc_disabled">Shortcut will be hidden in main menu</string>
|
||||
<!-- Preference Key (do not translate) -->
|
||||
<string name="rmilk_MPr_shortcut_key">rmilk_shortcut</string>
|
||||
|
||||
<!-- Background Wifi Title -->
|
||||
<string name="rmilk_MPr_bgwifi_title">Wifi Only Setting</string>
|
||||
<!-- Background Wifi Description (enabled) -->
|
||||
<string name="rmilk_MPr_bgwifi_desc_enabled">Background synchronization only happens when on Wifi</string>
|
||||
<!-- Background Wifi Description (disabled) -->
|
||||
<string name="rmilk_MPr_bgwifi_desc_disabled">Background synchronization will always occur</string>
|
||||
<!-- Preference Key (do not translate) -->
|
||||
<string name="rmilk_MPr_bgwifi_key">rmilk_bgwifi</string>
|
||||
|
||||
<!-- Actions Group Label -->
|
||||
<string name="rmilk_MPr_group_actions">Actions</string>
|
||||
|
||||
<!-- Synchronize Now Button -->
|
||||
<string name="rmilk_MPr_sync">Synchronize Now!</string>
|
||||
<!-- Preference Key (do not translate) -->
|
||||
<string name="rmilk_MPr_sync_key">rmilk_sync</string>
|
||||
|
||||
<!-- Clear Data Title -->
|
||||
<string name="rmilk_MPr_forget">Clear Personal Data</string>
|
||||
<!-- Clear Data Description -->
|
||||
<string name="rmilk_MPr_forget_description">Log out of Remember the Milk</string>
|
||||
<!-- Preference Key (do not translate) -->
|
||||
<string name="rmilk_MPr_forget_key">rmilk_forget</string>
|
||||
|
||||
<!-- ======================= MilkLoginActivity ========================= -->
|
||||
|
||||
<!-- RTM Login Instructions -->
|
||||
<string name="rmilk_MLA_label">Please Log In and Authorize Astrid:</string>
|
||||
|
||||
<!-- RTM Login Done Button -->
|
||||
<string name="rmilk_MLA_done">Done</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 ========================== -->
|
||||
|
||||
<string name="rmilk_notification_title">Astrid: Remember the Milk</string>
|
||||
<string name="rmilk_error">Sync Error! Sorry for the inconvenience! Error:</string>
|
||||
<string name="rmilk_ioerror">Connection Error! Check your Internet connection,
|
||||
or maybe RTM servers (status.rememberthemilk.com), for possible solutions.</string>
|
||||
<string name="rmilk_uptodate">Sync: Up to date!</string>
|
||||
<string name="rmilk_forget_confirm">Clear data for selected services?</string>
|
||||
<string name="rmilk_no_synchronizers">No Synchronizers Enabled!</string>
|
||||
<string name="rmilk_last_sync">Last Sync Date: %s</string>
|
||||
<string name="rmilk_last_auto_sync">Last AutoSync Attempt: %s</string>
|
||||
<string name="rmilk_date_never">never</string>
|
||||
<string name="rmilk_result_title">%s Results</string>
|
||||
<string name="rmilk_result_local">Summary - Astrid Tasks:</string>
|
||||
<string name="rmilk_result_remote">Summary - Remote Server:</string>
|
||||
<string name="rmilk_result_created">Created: %d</string>
|
||||
<string name="rmilk_result_updated">Updated: %d</string>
|
||||
<string name="rmilk_result_deleted">Deleted: %d</string>
|
||||
<string name="rmilk_result_merged">Merged: %d</string>
|
||||
|
||||
<string name="rmilk_progress_starting">Starting Sync...</string>
|
||||
<string name="rmilk_progress_rxlist">Reading List: %s</string>
|
||||
<string name="rmilk_progress_localtx">Transmitting: %s</string>
|
||||
<string name="rmilk_progress_localdel">Deleting: %s</string>
|
||||
<string name="rmilk_progress_remotetx">Receiving: %s</string>
|
||||
|
||||
<string-array name="rmilk_MPr_interval_entries">
|
||||
<!-- rmilk_MPr_interval_entries: Synchronization Intervals -->
|
||||
<item>disable</item>
|
||||
<item>every fifteen minutes</item>
|
||||
<item>every thirty minutes</item>
|
||||
<item>every hour</item>
|
||||
<item>every three hours</item>
|
||||
<item>every six hours</item>
|
||||
<item>every twelve hours</item>
|
||||
<item>every day</item>
|
||||
<item>every three days</item>
|
||||
<item>every week</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="rmilk_MPr_interval_values">
|
||||
<!-- rmilk_MPr_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>
|
||||
|
||||
</resources>
|
||||
|
@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<PreferenceScreen
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<PreferenceCategory
|
||||
android:title="@string/rmilk_MPr_group_status">
|
||||
|
||||
<Preference
|
||||
android:key="@string/rmilk_MPr_status_key"
|
||||
android:textSize="24sp"
|
||||
android:gravity="center"/>
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory
|
||||
android:title="@string/rmilk_MPr_group_options">
|
||||
|
||||
<ListPreference
|
||||
android:key="@string/rmilk_MPr_interval_key"
|
||||
android:entries="@array/rmilk_MPr_interval_entries"
|
||||
android:entryValues="@array/rmilk_MPr_interval_values"
|
||||
android:title="@string/rmilk_MPr_interval_title" />
|
||||
|
||||
<CheckBoxPreference
|
||||
android:key="@string/rmilk_MPr_shortcut_key"
|
||||
android:title="@string/rmilk_MPr_shortcut_title" />
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory
|
||||
android:title="@string/rmilk_MPr_group_actions">
|
||||
|
||||
<Preference
|
||||
android:key="@string/rmilk_MPr_sync_key"
|
||||
android:title="@string/rmilk_MPr_sync" />
|
||||
|
||||
<Preference
|
||||
android:key="@string/rmilk_MPr_forget_key"
|
||||
android:title="@string/rmilk_MPr_forget"
|
||||
android:summary="@string/rmilk_MPr_forget_description" />
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
</PreferenceScreen>
|
Loading…
Reference in New Issue