RTM can sync! Now to make it better...

pull/14/head
Tim Su 14 years ago
parent 9031eb079b
commit 115bda98f6

@ -4,7 +4,7 @@
<classpathentry kind="src" path="src-legacy"/>
<classpathentry kind="src" path="api-src"/>
<classpathentry kind="src" path="common-src"/>
<classpathentry excluding="com/todoroo/astrid/rmilk/DetailExposer.java|com/todoroo/astrid/rmilk/EditOperationExposer.java|com/todoroo/astrid/rmilk/MilkEditActivity.java|com/todoroo/astrid/rmilk/StartupReceiver.java" kind="src" path="plugin-src"/>
<classpathentry excluding="com/todoroo/astrid/rmilk/EditOperationExposer.java|com/todoroo/astrid/rmilk/MilkEditActivity.java|com/todoroo/astrid/rmilk/StartupReceiver.java" kind="src" path="plugin-src"/>
<classpathentry kind="src" path="gen"/>
<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
<classpathentry exported="true" kind="lib" path="lib/commons-codec-1.3.jar"/>

@ -22,11 +22,6 @@ import com.todoroo.andlib.sql.QueryTemplate;
*/
public final class Filter extends FilterListItem {
/**
* Plug-in Identifier
*/
public final String plugin;
/**
* Expanded title of this filter. This is displayed at the top
* of the screen when user is viewing this filter.
@ -62,9 +57,6 @@ public final class Filter extends FilterListItem {
/**
* Utility constructor for creating a TaskList object
*
* @param plugin
* {@link Addon} identifier that encompasses object
* @param listingTitle
* Title of this item as displayed on the lists page, e.g. Inbox
* @param title
@ -75,9 +67,8 @@ public final class Filter extends FilterListItem {
* @param valuesForNewTasks
* see {@link sqlForNewTasks}
*/
public Filter(String plugin, String listingTitle,
String title, QueryTemplate sqlQuery, ContentValues valuesForNewTasks) {
this.plugin = plugin;
public Filter(String listingTitle, String title,
QueryTemplate sqlQuery, ContentValues valuesForNewTasks) {
this.listingTitle = listingTitle;
this.title = title;
this.sqlQuery = sqlQuery.toString();
@ -90,8 +81,8 @@ public final class Filter extends FilterListItem {
* @param plugin
* {@link Addon} identifier that encompasses object
*/
protected Filter(String plugin) {
this.plugin = plugin;
protected Filter() {
// do nothing
}
// --- parcelable
@ -108,7 +99,6 @@ public final class Filter extends FilterListItem {
*/
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(plugin);
super.writeToParcel(dest, flags);
dest.writeString(title);
dest.writeString(sqlQuery);
@ -124,7 +114,7 @@ public final class Filter extends FilterListItem {
* {@inheritDoc}
*/
public Filter createFromParcel(Parcel source) {
Filter item = new Filter(source.readString());
Filter item = new Filter();
item.readFromParcel(source);
item.title = source.readString();
item.sqlQuery = source.readString();

@ -25,14 +25,13 @@ 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;
import com.todoroo.astrid.utility.Constants;
/**
* 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,
* Use {@link initiate} as the entry point for your synchronization service,
* which should handle authentication and then call {@link synchronizeTasks} to
* initiate synchronization.
*
@ -41,15 +40,13 @@ import com.todoroo.astrid.service.TaskService;
*/
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
* Perform authenticate and other pre-synchronization steps, then
* synchronize.
*/
abstract public void synchronize();
abstract protected void initiate();
/**
* Push variables from given task to the remote server.
@ -77,6 +74,14 @@ public abstract class SynchronizationProvider {
*/
abstract protected Task read(Task task) throws IOException;
/**
* Save task. Used to save local tasks that have been updated and remote
* tasks that need to be created locally
*
* @param task
*/
abstract protected void save(Task task) throws IOException;
/**
* Finds a task in the list with the same remote identifier(s) as
* the task passed in
@ -92,9 +97,6 @@ public abstract class SynchronizationProvider {
// --- implementation
@Autowired
private TaskService taskService;
@Autowired
private ExceptionService exceptionService;
@ -110,6 +112,27 @@ public abstract class SynchronizationProvider {
notification = new Notification(icon, null, when);
}
public void synchronize() {
Context context = ContextManager.getContext();
// display notification
notificationIntent = PendingIntent.getActivity(context, 0, new Intent(), 0);
postUpdate(context, context.getString(R.string.SyP_progress_starting));
final NotificationManager nm = new NotificationManager.AndroidNotificationManager(context);
nm.notify(Constants.NOTIFICATION_SYNC, notification);
// start next step in background thread
new Thread(new Runnable() {
public void run() {
try {
initiate();
} finally {
nm.cancel(Constants.NOTIFICATION_SYNC);
}
}
}).start();
}
// --- utilities
/**
@ -148,12 +171,6 @@ public abstract class SynchronizationProvider {
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();
@ -171,7 +188,7 @@ public abstract class SynchronizationProvider {
task.readFromCursor(data.localCreated);
String taskTitle = task.getValue(Task.TITLE);
postUpdate(context, r.getString(R.string.rmilk_progress_localtx,
postUpdate(context, r.getString(R.string.SyP_progress_localtx,
taskTitle));
/* If there exists an incoming remote task with the same name and no
@ -185,10 +202,14 @@ public abstract class SynchronizationProvider {
transferIdentifiers(remote, task);
push(task, remote);
read(remote);
// re-read remote task after merge
Task newRemote = read(remote);
remote.mergeWith(newRemote.getMergedValues());
} else {
create(task);
}
save(task);
}
// 2. UPDATE: for each updated local task
@ -196,14 +217,17 @@ public abstract class SynchronizationProvider {
for(int i = 0; i < length; i++) {
data.localUpdated.moveToNext();
task.readFromCursor(data.localUpdated);
postUpdate(context, r.getString(R.string.rmilk_progress_localtx,
postUpdate(context, r.getString(R.string.SyP_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);
// re-read remote task after merge
Task newRemote = read(remote);
remote.mergeWith(newRemote.getMergedValues());
} else {
push(task, null);
}
@ -243,11 +267,10 @@ public abstract class SynchronizationProvider {
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,
postUpdate(context, r.getString(R.string.SyP_progress_remotetx,
task.getValue(Task.TITLE)));
// save the data (TODO: save metadata)
taskService.save(task, true);
save(task);
}
}

@ -14,11 +14,6 @@ import android.os.Parcelable;
*/
public final class TaskDetail implements Parcelable {
/**
* Plug-in Identifier
*/
public final String plugin;
/**
* Text of detail
*/
@ -31,28 +26,23 @@ public final class TaskDetail implements Parcelable {
/**
* Creates a TaskDetail object
*
* @param plugin
* {@link Addon} identifier that encompasses object
* @param text
* text to display
* @param color
* color to use for text. Use <code>0</code> for default color
*/
public TaskDetail(String plugin, String text, int color) {
this.plugin = plugin;
public TaskDetail(String text, int color) {
this.text = text;
this.color = color;
}
/**
* Convenience constructor to make a TaskDetail with default color
*
* @param text
* text to display
*/
public TaskDetail(String plugin, String text) {
this(plugin, text, 0);
public TaskDetail(String text) {
this(text, 0);
}
// --- parcelable helpers
@ -68,7 +58,6 @@ public final class TaskDetail implements Parcelable {
* {@inheritDoc}
*/
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(plugin);
dest.writeString(text);
dest.writeInt(color);
}
@ -81,7 +70,7 @@ public final class TaskDetail implements Parcelable {
* {@inheritDoc}
*/
public TaskDetail createFromParcel(Parcel source) {
return new TaskDetail(source.readString(), source.readString(), source.readInt());
return new TaskDetail(source.readString(), source.readInt());
}
/**

@ -10,9 +10,11 @@ import java.lang.reflect.InvocationTargetException;
import android.content.ContentValues;
import android.database.Cursor;
import android.util.Log;
import com.todoroo.andlib.sql.Criterion;
import com.todoroo.andlib.sql.Query;
import com.todoroo.astrid.utility.Constants;
@ -62,6 +64,8 @@ public class GenericDao<TYPE extends AbstractModel> {
*/
public TodorooCursor<TYPE> query(Query query) {
query.from(table);
if(Constants.DEBUG)
Log.d("SQL-" + modelClass.getSimpleName(), query.toString()); //$NON-NLS-1$
Cursor cursor = database.rawQuery(query.toString(), null);
return new TodorooCursor<TYPE>(cursor, query.getFields());
}

@ -39,10 +39,6 @@ public class DependencyInjectionService {
@SuppressWarnings("nls")
public void inject(Object caller) {
if(Constants.DEBUG) {
Log.d("INJECTOR", "Invoked Injector on " + caller, new Throwable());
}
// Traverse through class and all parent classes, looking for
// fields declared with the @Autowired annotation and using
// dependency injection to set them as appropriate
@ -103,7 +99,7 @@ public class DependencyInjectionService {
Object injection = injector.getInjection(caller, field);
if (injection != null) {
if(Constants.DEBUG)
Log.e("INJECTOR", injector + ":" + caller + "." + field.getName() + " => " + injection);
Log.d("INJECTOR", injector + ":" + caller + "." + field.getName() + " => " + injection);
field.set(caller, injection);
return;
}

@ -22,6 +22,13 @@ public abstract class Criterion {
}
};
public static Criterion none = new Criterion(Operator.exists) {
@Override
protected void populate(StringBuilder sb) {
sb.append(0);
}
};
public static Criterion and(final Criterion criterion, final Criterion... criterions) {
return new Criterion(Operator.and) {

@ -1,5 +1,6 @@
package com.todoroo.andlib.sql;
@SuppressWarnings("nls")
public final class Functions {
@ -13,4 +14,8 @@ public final class Functions {
return value.toString();
}
public static Field upper(Field title) {
return new Field("UPPER(" + title.toString() + ")");
}
}

@ -47,8 +47,7 @@ public final class CoreFilterExposer extends BroadcastReceiver {
FilterCategory extended = new FilterCategory(CorePlugin.IDENTIFIER,
r.getString(R.string.BFE_Extended), new Filter[5]);
Filter alphabetical = new Filter(CorePlugin.IDENTIFIER,
r.getString(R.string.BFE_Alphabetical),
Filter alphabetical = new Filter(r.getString(R.string.BFE_Alphabetical),
r.getString(R.string.BFE_Alphabetical),
new QueryTemplate().where(Criterion.and(TaskCriteria.isActive(),
TaskCriteria.isVisible(DateUtilities.now()))).
@ -56,8 +55,7 @@ public final class CoreFilterExposer extends BroadcastReceiver {
null);
alphabetical.listingIcon = ((BitmapDrawable)r.getDrawable(R.drawable.tango_alpha)).getBitmap();
Filter recent = new Filter(CorePlugin.IDENTIFIER,
r.getString(R.string.BFE_Recent),
Filter recent = new Filter(r.getString(R.string.BFE_Recent),
r.getString(R.string.BFE_Recent),
new QueryTemplate().orderBy(Order.desc(Task.MODIFICATION_DATE)).limit(15),
null);
@ -65,26 +63,23 @@ public final class CoreFilterExposer extends BroadcastReceiver {
ContentValues hiddenValues = new ContentValues();
hiddenValues.put(Task.HIDE_UNTIL.name, DateUtilities.now() + DateUtilities.ONE_DAY);
Filter hidden = new Filter(CorePlugin.IDENTIFIER,
r.getString(R.string.BFE_Hidden),
Filter hidden = new Filter(r.getString(R.string.BFE_Hidden),
r.getString(R.string.BFE_Hidden),
new QueryTemplate().where(Criterion.and(TaskCriteria.isActive(),
Criterion.not(TaskCriteria.isVisible(DateUtilities.now())))).
orderBy(Order.asc(Task.HIDE_UNTIL)),
hiddenValues);
hiddenValues);
hidden.listingIcon = ((BitmapDrawable)r.getDrawable(R.drawable.tango_clouds)).getBitmap();
ContentValues completedValues = new ContentValues();
hiddenValues.put(Task.COMPLETION_DATE.name, DateUtilities.now());
Filter completed = new Filter(CorePlugin.IDENTIFIER, r.getString(R.string.BFE_Completed),
r.getString(R.string.BFE_Completed),
Filter completed = new Filter(r.getString(R.string.BFE_Completed), r.getString(R.string.BFE_Completed),
new QueryTemplate().where(Criterion.and(TaskCriteria.completedBefore(DateUtilities.now()),
Criterion.not(TaskCriteria.isDeleted()))). orderBy(Order.desc(Task.COMPLETION_DATE)),
completedValues);
completed.listingIcon = ((BitmapDrawable)r.getDrawable(R.drawable.tango_check)).getBitmap();
Filter deleted = new Filter(CorePlugin.IDENTIFIER,
r.getString(R.string.BFE_Deleted),
Filter deleted = new Filter(r.getString(R.string.BFE_Deleted),
r.getString(R.string.BFE_Deleted),
new QueryTemplate().where(TaskCriteria.isDeleted()).
orderBy(Order.desc(Task.DELETION_DATE)),
@ -112,8 +107,7 @@ public final class CoreFilterExposer extends BroadcastReceiver {
* @return
*/
public static Filter buildInboxFilter(Resources r) {
Filter inbox = new Filter(CorePlugin.IDENTIFIER, r.getString(R.string.BFE_Active),
r.getString(R.string.BFE_Active_title),
Filter inbox = new Filter(r.getString(R.string.BFE_Active), r.getString(R.string.BFE_Active_title),
new QueryTemplate().where(Criterion.and(TaskCriteria.isActive(),
TaskCriteria.isVisible(DateUtilities.now()))),
null);

@ -48,7 +48,7 @@ public class NoteDetailExposer extends BroadcastReceiver {
if(notes.length() == 0)
return;
TaskDetail taskDetail = new TaskDetail(NotesPlugin.IDENTIFIER, notes);
TaskDetail taskDetail = new TaskDetail(notes);
// transmit
Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_SEND_DETAILS);

@ -65,8 +65,7 @@ public class NotificationActivity extends Activity {
return;
Intent taskListIntent = new Intent(this, TaskListActivity.class);
Filter itemFilter = new Filter(ReminderPlugin.IDENTIFIER,
getString(R.string.rmd_NoA_filter),
Filter itemFilter = new Filter(getString(R.string.rmd_NoA_filter),
getString(R.string.rmd_NoA_filter),
new QueryTemplate().where(TaskCriteria.byId(id)),
null);

@ -104,7 +104,7 @@ public class RepeatDetailExposer extends BroadcastReceiver {
detail = context.getString(R.string.repeat_detail_duedate, interval);
TaskDetail taskDetail = new TaskDetail(RepeatsPlugin.IDENTIFIER, detail);
TaskDetail taskDetail = new TaskDetail(detail);
// transmit
Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_SEND_DETAILS);

@ -7,10 +7,10 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import com.todoroo.astrid.R;
import com.timsu.astrid.R;
import com.todoroo.astrid.api.AstridApiConstants;
import com.todoroo.astrid.api.TaskDetail;
import com.todoroo.astrid.model.Task;
import com.todoroo.astrid.model.Metadata;
import com.todoroo.astrid.rmilk.data.MilkDataService;
/**
@ -34,22 +34,20 @@ public class DetailExposer extends BroadcastReceiver {
if(taskId == -1)
return;
MilkDataService service = new MilkDataService(context);
Task task = service.readTask(taskId);
if(task == null)
Metadata metadata = MilkDataService.getInstance().getTaskMetadata(taskId);
if(metadata == null)
return;
TaskDetail[] details = new TaskDetail[2];
String listId = task.getValue(MilkDataService.LIST_ID);
if(listId != null && listId.length() > 0)
long listId = metadata.getValue(MilkDataService.LIST_ID);
if(listId > 0)
details[0] = new TaskDetail(context.getString(R.string.rmilk_TLA_list,
service.getList(listId)));
MilkDataService.getInstance().getListName(listId)));
else
details[0] = null;
int repeat = task.getValue(MilkDataService.REPEAT);
int repeat = metadata.getValue(MilkDataService.REPEATING);
if(repeat != 0)
details[1] = new TaskDetail(context.getString(R.string.rmilk_TLA_repeat));
else
@ -57,7 +55,8 @@ public class DetailExposer extends BroadcastReceiver {
// transmit
Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_SEND_DETAILS);
broadcastIntent.putExtra(AstridApiConstants.EXTRAS_ITEMS, details);
broadcastIntent.putExtra(AstridApiConstants.EXTRAS_ADDON, Utilities.IDENTIFIER);
broadcastIntent.putExtra(AstridApiConstants.EXTRAS_RESPONSE, details);
broadcastIntent.putExtra(AstridApiConstants.EXTRAS_TASK_ID, taskId);
context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ);
}

@ -15,9 +15,9 @@ 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.model.Metadata;
import com.todoroo.astrid.rmilk.Utilities.ListContainer;
import com.todoroo.astrid.rmilk.data.MilkDataService;
import com.todoroo.astrid.rmilk.data.MilkTask;
/**
* Exposes filters based on RTM lists
@ -27,16 +27,16 @@ import com.todoroo.astrid.rmilk.data.MilkTask;
*/
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);
ContentValues values = new ContentValues(); // TODO
Filter filter = new Filter(Utilities.IDENTIFIER, listTitle, title,
new QueryTemplate().join(MilkDataService.MILK_JOIN).where(MilkTask.LIST_ID.eq(list.id)),
values);
ContentValues values = new ContentValues();
values.put(Metadata.KEY.name, MilkDataService.METADATA_KEY);
values.put(MilkDataService.LIST_ID.name, list.id);
Filter filter = new Filter(listTitle, title, new QueryTemplate().join(MilkDataService.METADATA_JOIN).where(MilkDataService.LIST_ID.eq(list.id)),
values);
return filter;
}
@ -47,8 +47,7 @@ public class FilterExposer extends BroadcastReceiver {
if(!Utilities.isLoggedIn())
return;
MilkDataService service = new MilkDataService(context);
ListContainer[] lists = service.getListsWithCounts();
ListContainer[] lists = MilkDataService.getInstance().getListsWithCounts();
// If user does not have any tags, don't show this section at all
if(lists.length == 0)
@ -68,6 +67,7 @@ public class FilterExposer extends BroadcastReceiver {
list[0] = rtmHeader;
list[1] = rtmLists;
Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_SEND_FILTERS);
broadcastIntent.putExtra(AstridApiConstants.EXTRAS_ADDON, Utilities.IDENTIFIER);
broadcastIntent.putExtra(AstridApiConstants.EXTRAS_RESPONSE, list);
context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ);
}

@ -3,8 +3,6 @@
*/
package com.todoroo.astrid.rmilk;
import java.util.Date;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.preference.PreferenceManager;
@ -42,7 +40,7 @@ public class Utilities {
public ListContainer(long id, String name) {
this.id = id;
this.name = name;
this.count = -1;
this.count = 0;
}
@Override
@ -87,22 +85,15 @@ public class Utilities {
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);
/** RTM Last Successful Sync Date, or 0 */
public static long getLastSyncDate() {
return getPrefs().getLong(PREF_LAST_SYNC, 0);
}
/** Set RTM Last Successful Sync Date */
public static void setLastSyncDate(Date date) {
public static void setLastSyncDate(long time) {
Editor editor = getPrefs().edit();
if (date == null) {
editor.remove(PREF_LAST_SYNC);
} else {
editor.putLong(PREF_LAST_SYNC, date.getTime());
}
editor.putLong(PREF_LAST_SYNC, time);
editor.commit();
}

@ -50,7 +50,7 @@ import com.todoroo.astrid.rmilk.api.data.RtmTask.Priority;
* @author timsu January 2009
*/
@SuppressWarnings("nls")
public class ServiceImpl
public class ServiceImpl
{
public final static String SERVER_HOST_NAME = "api.rememberthemilk.com"; //$NON-NLS-1$
@ -288,7 +288,7 @@ public class ServiceImpl
}
/**
* Adds a task, name, to the list specified by list_id.
* 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
@ -303,9 +303,9 @@ public class ServiceImpl
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),
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)
{
@ -409,6 +409,8 @@ public class ServiceImpl
public RtmTaskSeries tasks_moveTo(String timelineId, String fromListId, String toListId, String taskSeriesId, String taskId)
throws ServiceException
{
if(fromListId.equals(toListId))
return null;
Element elt = invoker.invoke(new Param("method", "rtm.tasks.moveTo"), new Param("timeline", timelineId), new Param("from_list_id", fromListId),
new Param("to_list_id", toListId), new Param("taskseries_id", taskSeriesId), new Param("task_id", taskId), new Param("auth_token", currentAuthToken),
new Param("api_key", applicationInfo.getApiKey()));

@ -27,13 +27,15 @@ public class RtmList extends RtmData {
private final String id;
private final boolean smart;
private final boolean archived;
private final int position;
private final String name;
public RtmList(String id, String name, boolean smart) {
public RtmList(String id, String name, boolean smart, boolean archived, int position) {
this.id = id;
this.name = name;
this.smart = smart;
this.archived = false;
this.archived = archived;
this.position = position;
}
public RtmList(Element elt) {
@ -41,6 +43,7 @@ public class RtmList extends RtmData {
name = elt.getAttribute("name");
smart = elt.getAttribute("smart") == "1";
archived = elt.getAttribute("archived") == "1";
position = Integer.parseInt(elt.getAttribute("position"));
}
public String getId() {
@ -57,5 +60,13 @@ public class RtmList extends RtmData {
public boolean isArchived() {
return archived;
}
}
public int getPosition() {
return position;
}
public boolean isInbox() {
return position == -1;
}
}

@ -3,6 +3,7 @@
*/
package com.todoroo.astrid.rmilk.data;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
@ -12,25 +13,62 @@ import com.todoroo.andlib.data.GenericDao;
import com.todoroo.andlib.data.Property;
import com.todoroo.andlib.data.TodorooCursor;
import com.todoroo.andlib.data.Property.CountProperty;
import com.todoroo.andlib.data.Property.IntegerProperty;
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.sql.Criterion;
import com.todoroo.andlib.sql.Join;
import com.todoroo.andlib.sql.Order;
import com.todoroo.andlib.sql.Query;
import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.astrid.dao.MetadataDao;
import com.todoroo.astrid.dao.TaskDao;
import com.todoroo.astrid.dao.MetadataDao.MetadataCriteria;
import com.todoroo.astrid.dao.TaskDao.TaskCriteria;
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;
public class MilkDataService {
public final class MilkDataService {
// --- constants
// --- public constants
/** for joining milk task table with task table */
public static final Join MILK_JOIN = Join.left(MilkTask.TABLE,
Task.ID.eq(MilkTask.TASK));
/** metadata key */
public static final String METADATA_KEY = "rmilk"; //$NON-NLS-1$
/** {@link MilkList} id */
public static final LongProperty LIST_ID = new LongProperty(Metadata.TABLE,
Metadata.VALUE1.name);
/** RTM Task Series Id */
public static final LongProperty TASK_SERIES_ID = new LongProperty(Metadata.TABLE,
Metadata.VALUE2.name);
/** RTM Task Id */
public static final LongProperty TASK_ID = new LongProperty(Metadata.TABLE,
Metadata.VALUE3.name);
/** Whether task repeats in RTM (1 or 0) */
public static final IntegerProperty REPEATING = new IntegerProperty(Metadata.TABLE,
Metadata.VALUE4.name);
/** Utility for joining tasks with metadata */
public static final Join METADATA_JOIN = Join.left(Metadata.TABLE, Task.ID.eq(Metadata.TASK));
// --- singleton
private static MilkDataService instance = null;
public static synchronized MilkDataService getInstance() {
if(instance == null)
instance = new MilkDataService(ContextManager.getContext());
return instance;
}
// --- instance variables
@ -39,18 +77,19 @@ public class MilkDataService {
private final MilkDatabase milkDatabase = new MilkDatabase();
private final GenericDao<MilkList> milkListDao;
private final GenericDao<MilkTask> milkTaskDao;
@Autowired
private TaskDao taskDao;
@Autowired
private MetadataDao metadataDao;
static final Random random = new Random();
public MilkDataService(Context context) {
private MilkDataService(Context context) {
this.context = context;
DependencyInjectionService.getInstance().inject(this);
milkListDao = new GenericDao<MilkList>(MilkList.class, milkDatabase);
milkTaskDao = new GenericDao<MilkTask>(MilkTask.class, milkDatabase);
milkDatabase.openForReading();
}
@ -60,7 +99,7 @@ public class MilkDataService {
* Clears RTM metadata information. Used when user logs out of RTM
*/
public void clearMetadata() {
milkTaskDao.deleteWhere(Criterion.all);
metadataDao.deleteWhere(Metadata.KEY.eq(METADATA_KEY));
}
/**
@ -70,20 +109,85 @@ public class MilkDataService {
*/
public TodorooCursor<Task> getLocallyCreated(Property<?>[] properties) {
return
taskDao.query(Query.select(properties).join(MILK_JOIN).where(
Criterion.or(MilkTask.UPDATED.eq(0), MilkTask.TASK.isNull())));
taskDao.query(Query.select(properties).join(METADATA_JOIN).where(
Criterion.or(TASK_ID.eq(0), Metadata.TASK.isNull())));
}
/**
* Gets tasks that were modified since last sync
* @param properties
* @return
* @return null if never sync'd
*/
public TodorooCursor<Task> getLocallyUpdated(Property<?>[] properties) {
long lastSyncDate = Utilities.getLastSyncDate();
if(lastSyncDate == 0)
return taskDao.query(Query.select(Task.ID).where(Criterion.none));
return
taskDao.query(Query.select(properties).join(MILK_JOIN).
where(Criterion.and(MilkTask.UPDATED.neq(0),
MilkTask.UPDATED.lt(Task.MODIFICATION_DATE))));
taskDao.query(Query.select(properties).join(METADATA_JOIN).
where(Task.MODIFICATION_DATE.gt(lastSyncDate)));
}
/**
* Searches for a local task with same remote id, updates this task's id
* @param task
*/
public void updateFromLocalCopy(Task task) {
if(task.getId() != Task.NO_ID)
return;
TodorooCursor<Task> cursor = taskDao.query(Query.select(Task.ID).
join(METADATA_JOIN).where(Criterion.and(LIST_ID.eq(task.getValue(LIST_ID)),
TASK_SERIES_ID.eq(task.getValue(TASK_SERIES_ID)),
TASK_ID.eq(task.getValue(TASK_ID)))));
try {
if(cursor.getCount() == 0)
return;
cursor.moveToFirst();
task.setId(cursor.get(Task.ID));
} finally {
cursor.close();
}
}
/**
* Saves a task and its metadata
* @param task
*/
public void saveTaskAndMetadata(Task task) {
Metadata metadata = new Metadata();
metadata.setValue(Metadata.KEY, METADATA_KEY);
metadata.setValue(LIST_ID, task.getValue(LIST_ID));
metadata.setValue(TASK_SERIES_ID, task.getValue(TASK_SERIES_ID));
metadata.setValue(TASK_ID, task.getValue(TASK_ID));
metadata.setValue(REPEATING, task.getValue(REPEATING));
task.clearValue(LIST_ID);
task.clearValue(TASK_SERIES_ID);
task.clearValue(TASK_ID);
task.clearValue(REPEATING);
taskDao.save(task, true);
metadata.setValue(Metadata.TASK, task.getId());
metadataDao.deleteWhere(MetadataCriteria.byTaskAndwithKey(task.getId(),
METADATA_KEY));
metadataDao.persist(metadata);
}
/**
* Reads metadata out of a task
* @return null if no metadata found
*/
public Metadata getTaskMetadata(long taskId) {
TodorooCursor<Metadata> cursor = metadataDao.query(Query.select(Metadata.PROPERTIES).where(
MetadataCriteria.byTaskAndwithKey(taskId, METADATA_KEY)));
try {
if(cursor.getCount() == 0)
return null;
cursor.moveToFirst();
return new Metadata(cursor);
} finally {
cursor.close();
}
}
// --- list methods
@ -93,7 +197,7 @@ public class MilkDataService {
* @param listId
* @return null if no list by this id exists, otherwise list name
*/
public String getList(String listId) {
public String getListName(long listId) {
TodorooCursor<MilkList> cursor = milkListDao.query(Query.select(
MilkList.NAME).where(MilkList.ID.eq(listId)));
try {
@ -113,22 +217,40 @@ public class MilkDataService {
public ListContainer[] getListsWithCounts() {
CountProperty COUNT = new CountProperty();
// read list names
TodorooCursor<MilkList> listCursor = milkListDao.query(Query.select(MilkList.ID,
MilkList.NAME).where(MilkList.ARCHIVED.eq(0)).orderBy(Order.asc(MilkList.POSITION)));
ListContainer[] lists = new ListContainer[listCursor.getCount()];
HashMap<Long, ListContainer> listIdToContainerMap;
try {
int length = listCursor.getCount();
if(length == 0)
return lists;
listIdToContainerMap = new HashMap<Long, ListContainer>(length);
MilkList list = new MilkList();
for(int i = 0; i < length; i++) {
listCursor.moveToNext();
list.readFromCursor(listCursor);
lists[i] = new ListContainer(list);
listIdToContainerMap.put(list.getId(), lists[i]);
}
} finally {
listCursor.close();
}
// read all list counts
TodorooCursor<MilkTask> cursor = milkTaskDao.query(Query.select(MilkList.ID, MilkList.NAME, COUNT).
join(Join.inner(MilkList.TABLE, MilkTask.LIST_ID.eq(MilkList.ID))).
orderBy(Order.asc(MilkList.POSITION), Order.asc(MilkList.ID)).
groupBy(MilkTask.LIST_ID));
ListContainer[] containers = new ListContainer[cursor.getCount()];
TodorooCursor<Metadata> cursor = metadataDao.query(Query.select(LIST_ID, COUNT).
join(Join.inner(Task.TABLE, Metadata.TASK.eq(Task.ID))).
where(Criterion.and(TaskCriteria.isVisible(DateUtilities.now()), TaskCriteria.isActive())).
groupBy(LIST_ID));
try {
for(int i = 0; i < containers.length; i++) {
cursor.moveToNext();
long id = cursor.get(MilkList.ID);
String name = cursor.get(MilkList.NAME);
int count = cursor.get(COUNT);
containers[i] = new ListContainer(id, name);
containers[i].count = count;
for(cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
ListContainer container = listIdToContainerMap.get(cursor.get(LIST_ID));
if(container != null) {
container.count = cursor.get(COUNT);
}
}
return containers;
return lists;
} finally {
cursor.close();
}
@ -160,18 +282,31 @@ public class MilkDataService {
}*/
/**
* Clears current cache of RTM lists and re-populates
* Clears current cache of RTM lists and re-populates. Returns the inbox
* list.
*
* @param lists
* @return list with the name "inbox"
*/
public void setLists(RtmLists lists) {
public MilkList setLists(RtmLists lists) {
milkListDao.deleteWhere(Criterion.all);
MilkList model = new MilkList();
MilkList inbox = null;
for(Map.Entry<String, RtmList> list : lists.getLists().entrySet()) {
if(list.getValue().isSmart())
continue;
model.setValue(MilkList.ID, Long.parseLong(list.getValue().getId()));
model.setValue(MilkList.NAME, list.getValue().getName());
model.setValue(MilkList.POSITION, list.getValue().getPosition());
model.setValue(MilkList.ARCHIVED, list.getValue().isArchived()? 1 : 0);
milkListDao.createNew(model);
if(list.getValue().isInbox()) {
inbox = model;
model = new MilkList();
}
}
return inbox;
}
}

@ -37,7 +37,6 @@ public class MilkDatabase extends AbstractDatabase {
*/
public static final Table[] TABLES = new Table[] {
MilkList.TABLE,
MilkTask.TABLE
};
// --- implementation

@ -1,104 +0,0 @@
/**
* 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.astrid.model.Task;
/**
* Data Model which represents a list in RTM
*
* @author Tim Su <tim@todoroo.com>
*
*/
@SuppressWarnings("nls")
public class MilkTask extends AbstractModel {
// --- table
public static final Table TABLE = new Table("tasks", MilkTask.class);
// --- properties
/** Task Id */
public static final LongProperty TASK = new LongProperty(
TABLE, "task");
/** {@link MilkList} id */
public static final LongProperty LIST_ID = new LongProperty(
TABLE, "listId");
/** RTM Task Id */
public static final LongProperty TASK_SERIES_ID = new LongProperty(
TABLE, "taskSeriesId");
/** RTM Task Series Id */
public static final LongProperty TASK_ID = new LongProperty(
TABLE, "taskId");
/** Whether task repeats in RTM (1 or 0) */
public static final IntegerProperty REPEATING = new IntegerProperty(
TABLE, "repeating");
/** Unixtime task was last updated in RTM */
public static final LongProperty UPDATED = new LongProperty(
TABLE, "updated");
/** List of all properties for this model */
public static final Property<?>[] PROPERTIES = generateProperties(MilkTask.class);
// --- defaults
/** Default values container */
private static final ContentValues defaultValues = new ContentValues();
static {
defaultValues.put(REPEATING.name, 0);
defaultValues.put(UPDATED.name, 0);
}
@Override
public ContentValues getDefaultValues() {
return defaultValues;
}
// --- data access boilerplate
public MilkTask() {
super();
}
public MilkTask(TodorooCursor<MilkTask> cursor) {
this();
readPropertiesFromCursor(cursor);
}
public void readFromCursor(TodorooCursor<MilkTask> cursor) {
super.readPropertiesFromCursor(cursor);
}
@Override
public long getId() {
return getIdHelper(TASK);
};
// --- parcelable helpers
private static final Creator<Task> CREATOR = new ModelCreator<Task>(Task.class);
@Override
protected Creator<? extends AbstractModel> getCreator() {
return CREATOR;
}
}

@ -7,14 +7,11 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import android.app.Activity;
import android.app.ProgressDialog;
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;
@ -44,7 +41,6 @@ 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.rmilk.data.MilkTask;
import com.todoroo.astrid.service.AstridDependencyInjector;
public class RTMSyncProvider extends SynchronizationProvider {
@ -52,7 +48,6 @@ public class RTMSyncProvider extends SynchronizationProvider {
private ServiceImpl rtmService = null;
private String timeline = null;
private MilkDataService dataService = null;
private ProgressDialog progressDialog = null;
static {
AstridDependencyInjector.initialize();
@ -73,27 +68,12 @@ public class RTMSyncProvider extends SynchronizationProvider {
// ------------------------------------------------------- public methods
// ----------------------------------------------------------------------
@Override
public void synchronize() {
Context context = ContextManager.getContext();
dataService = new MilkDataService(context);
if(context instanceof Activity) {
progressDialog = dialogUtilities.progressDialog(context,
context.getString(R.string.DLG_communicating_text));
progressDialog.show();
}
// 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);
Utilities.setLastSyncDate(0);
dataService.clearMetadata();
}
@ -138,19 +118,22 @@ public class RTMSyncProvider extends SynchronizationProvider {
}
}
private void authenticate(final Context context) {
new Thread(new Runnable() {
public void run() {
authenticateInNewThread(context);
}
}).start();
@Override
protected void initiate() {
Context context = ContextManager.getContext();
dataService = MilkDataService.getInstance();
// authenticate the user. this will automatically call the next step
authenticate(context);
}
/**
* Perform authentication with RTM. Will open the SyncBrowser if necessary
*/
@SuppressWarnings("nls")
private void authenticateInNewThread(final Context context) {
private void authenticate(final Context context) {
final Resources r = context.getResources();
FlurryAgent.onEvent("rtm-started");
@ -237,10 +220,10 @@ public class RTMSyncProvider extends SynchronizationProvider {
// read all tasks
ArrayList<Task> remoteChanges = new ArrayList<Task>();
Date lastSyncDate = Utilities.getLastSyncDate();
Date lastSyncDate = new Date(Utilities.getLastSyncDate());
boolean shouldSyncIndividualLists = false;
String filter = null;
if(lastSyncDate == null)
if(lastSyncDate.getTime() == 0)
filter = "status:incomplete"; //$NON-NLS-1$ // 1st time sync: get unfinished tasks
// try the quick synchronization
@ -273,8 +256,7 @@ public class RTMSyncProvider extends SynchronizationProvider {
SyncData syncData = populateSyncData(remoteChanges);
synchronizeTasks(syncData);
Date syncTime = new Date(System.currentTimeMillis());
Utilities.setLastSyncDate(syncTime);
Utilities.setLastSyncDate(DateUtilities.now());
FlurryAgent.onEvent("rtm-sync-finished"); //$NON-NLS-1$
} catch (IllegalStateException e) {
@ -301,10 +283,10 @@ public class RTMSyncProvider extends SynchronizationProvider {
Task.CREATION_DATE,
Task.COMPLETION_DATE,
Task.DELETION_DATE,
MilkTask.LIST_ID,
MilkTask.TASK_SERIES_ID,
MilkTask.TASK_ID,
MilkTask.REPEATING,
MilkDataService.LIST_ID,
MilkDataService.TASK_SERIES_ID,
MilkDataService.TASK_ID,
MilkDataService.REPEATING,
// TODO tags
};
@ -350,23 +332,29 @@ public class RTMSyncProvider extends SynchronizationProvider {
@Override
protected void create(Task task) throws IOException {
String listId = null;
if(task.containsValue(MilkTask.LIST_ID))
listId = Long.toString(task.getValue(MilkTask.LIST_ID));
if(task.containsValue(MilkDataService.LIST_ID) && task.getValue(MilkDataService.LIST_ID) != null)
listId = Long.toString(task.getValue(MilkDataService.LIST_ID));
if("0".equals(listId)) //$NON-NLS-1$
listId = null;
RtmTaskSeries rtmTask = rtmService.tasks_add(timeline, listId,
task.getValue(Task.TITLE));
push(task, parseRemoteTask(rtmTask));
Task newRemoteTask = parseRemoteTask(rtmTask);
task.mergeWith(newRemoteTask.getMergedValues());
push(task, newRemoteTask);
}
/** Send changes for the given TaskProxy across the wire */
/**
* Send changes for the given Task across the wire. If a remoteTask is
* supplied, we attempt to intelligently only transmit the values that
* have changed.
*/
@Override
protected void push(Task task, Task remoteTask) throws IOException {
RtmId id = new RtmId(task);
// fetch remote task for comparison
if(remoteTask == null) {
if(remoteTask == null)
remoteTask = read(task);
}
RtmId id = new RtmId(task);
if(shouldTransmit(task, Task.TITLE, remoteTask))
rtmService.tasks_setName(timeline, id.listId, id.taskSeriesId,
@ -386,42 +374,52 @@ public class RTMSyncProvider extends SynchronizationProvider {
rtmService.tasks_complete(timeline, id.listId, id.taskSeriesId,
id.taskId);
}
if(shouldTransmit(task, Task.DELETION_DATE, remoteTask))
if(shouldTransmit(task, Task.DELETION_DATE, remoteTask) &&
task.getValue(Task.DELETION_DATE) > 0)
rtmService.tasks_delete(timeline, id.listId, id.taskSeriesId,
id.taskId);
if(shouldTransmit(task, MilkTask.LIST_ID, remoteTask) && remoteTask != null)
rtmService.tasks_moveTo(timeline, Long.toString(remoteTask.getValue(MilkTask.LIST_ID)),
// TODO tags, notes, url, ...
if(remoteTask != null && shouldTransmit(task, MilkDataService.LIST_ID, remoteTask) &&
task.getValue(MilkDataService.LIST_ID) != 0)
rtmService.tasks_moveTo(timeline, Long.toString(remoteTask.getValue(MilkDataService.LIST_ID)),
id.listId, id.taskSeriesId, id.taskId);
}
@Override
protected void save(Task task) throws IOException {
// if task has no id, try to find a corresponding task
if(task.getId() == Task.NO_ID) {
dataService.updateFromLocalCopy(task);
}
dataService.saveTaskAndMetadata(task);
}
/** Create a task proxy for the given RtmTaskSeries */
private Task parseRemoteTask(RtmTaskSeries rtmTaskSeries) {
Task task = new Task();
task.setValue(MilkTask.LIST_ID, Long.parseLong(rtmTaskSeries.getList().getId()));
task.setValue(MilkTask.TASK_SERIES_ID, Long.parseLong(rtmTaskSeries.getId()));
task.setValue(MilkDataService.LIST_ID, Long.parseLong(rtmTaskSeries.getList().getId()));
task.setValue(MilkDataService.TASK_SERIES_ID, Long.parseLong(rtmTaskSeries.getId()));
task.setValue(Task.TITLE, rtmTaskSeries.getName());
task.setValue(MilkTask.REPEATING, rtmTaskSeries.hasRecurrence() ? 1 : 0);
task.setValue(MilkDataService.REPEATING, 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,
task.createDueDate(rtmTask.getHasDueTime() ? Task.URGENCY_SPECIFIC_DAY_TIME :
Task.URGENCY_SPECIFIC_DAY, DateUtilities.dateToUnixtime(rtmTask.getDue())));
} else {
task.setValue(Task.DUE_DATE, 0L);
}
task.setValue(Task.IMPORTANCE, rtmTask.getPriority().ordinal());
task.setValue(MilkDataService.TASK_ID, Long.parseLong(rtmTask.getId()));
task.setValue(Task.CREATION_DATE, DateUtilities.dateToUnixtime(rtmTask.getAdded()));
task.setValue(Task.COMPLETION_DATE, DateUtilities.dateToUnixtime(rtmTask.getCompleted()));
task.setValue(Task.DELETION_DATE, DateUtilities.dateToUnixtime(rtmTask.getDeleted()));
if(rtmTask.getDue() != null) {
task.setValue(Task.DUE_DATE,
task.createDueDate(rtmTask.getHasDueTime() ? Task.URGENCY_SPECIFIC_DAY_TIME :
Task.URGENCY_SPECIFIC_DAY, DateUtilities.dateToUnixtime(rtmTask.getDue())));
} else {
// error in upstream code, try to handle gracefully
Log.e("rtmsync", "Got null task parsing remote task series", //$NON-NLS-1$//$NON-NLS-2$
new Throwable());
task.setValue(Task.DUE_DATE, 0L);
}
task.setValue(Task.IMPORTANCE, rtmTask.getPriority().ordinal());
return task;
}
@ -431,9 +429,9 @@ public class RTMSyncProvider extends SynchronizationProvider {
int length = tasks.size();
for(int i = 0; i < length; i++) {
Task task = tasks.get(i);
if(task.getValue(MilkTask.LIST_ID).equals(target.getValue(MilkTask.LIST_ID)) &&
task.getValue(MilkTask.TASK_SERIES_ID).equals(target.getValue(MilkTask.TASK_SERIES_ID)) &&
task.getValue(MilkTask.TASK_ID).equals(target.getValue(MilkTask.TASK_ID)))
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;
@ -441,7 +439,7 @@ public class RTMSyncProvider extends SynchronizationProvider {
@Override
protected Task read(Task task) throws IOException {
RtmTaskSeries rtmTask = rtmService.tasks_getTask(Long.toString(task.getValue(MilkTask.TASK_SERIES_ID)),
RtmTaskSeries rtmTask = rtmService.tasks_getTask(Long.toString(task.getValue(MilkDataService.TASK_SERIES_ID)),
task.getValue(Task.TITLE));
if(rtmTask != null)
return parseRemoteTask(rtmTask);
@ -450,9 +448,9 @@ public class RTMSyncProvider extends SynchronizationProvider {
@Override
protected void transferIdentifiers(Task source, Task destination) {
destination.setValue(MilkTask.LIST_ID, source.getValue(MilkTask.LIST_ID));
destination.setValue(MilkTask.TASK_SERIES_ID, source.getValue(MilkTask.TASK_SERIES_ID));
destination.setValue(MilkTask.TASK_ID, source.getValue(MilkTask.TASK_ID));
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));
}
// ----------------------------------------------------------------------
@ -466,9 +464,9 @@ public class RTMSyncProvider extends SynchronizationProvider {
public String listId;
public RtmId(Task task) {
taskId = Long.toString(task.getValue(MilkTask.TASK_ID));
taskSeriesId = Long.toString(task.getValue(MilkTask.TASK_SERIES_ID));
listId = Long.toString(task.getValue(MilkTask.LIST_ID));
taskId = Long.toString(task.getValue(MilkDataService.TASK_ID));
taskSeriesId = Long.toString(task.getValue(MilkDataService.TASK_SERIES_ID));
listId = Long.toString(task.getValue(MilkDataService.LIST_ID));
}
}

@ -35,8 +35,7 @@ public class TagDetailExposer extends BroadcastReceiver {
if(tagList.length() == 0)
return;
TaskDetail taskDetail = new TaskDetail(TagsPlugin.IDENTIFIER,
context.getString(R.string.tag_TLA_detail, tagList));
TaskDetail taskDetail = new TaskDetail(context.getString(R.string.tag_TLA_detail, tagList));
// transmit
Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_SEND_DETAILS);

@ -40,9 +40,8 @@ public class TagFilterExposer extends BroadcastReceiver {
contentValues.put(Metadata.KEY.name, TagService.KEY);
contentValues.put(TagService.TAG.name, tag.tag);
Filter filter = new Filter(TagsPlugin.IDENTIFIER,
listTitle, title,
tagTemplate,
Filter filter = new Filter(listTitle,
title, tagTemplate,
contentValues);
// filters[0].contextMenuLabels = new String[] {
@ -79,8 +78,7 @@ public class TagFilterExposer extends BroadcastReceiver {
FilterListHeader tagsHeader = new FilterListHeader(TagsPlugin.IDENTIFIER,
context.getString(R.string.tag_FEx_header));
Filter untagged = new Filter(TagsPlugin.IDENTIFIER,
r.getString(R.string.tag_FEx_untagged),
Filter untagged = new Filter(r.getString(R.string.tag_FEx_untagged),
r.getString(R.string.tag_FEx_untagged),
tagService.untaggedTemplate(),
null);

@ -164,6 +164,7 @@ public class TagService {
metadata.setValue(Metadata.KEY, KEY);
metadata.setValue(Metadata.TASK, taskId);
for(String tag : tags) {
metadata.clearValue(Metadata.ID);
metadata.setValue(TAG, tag.trim());
metadataDao.createNew(metadata);
}

@ -402,5 +402,15 @@ If you don\'t want to see the new task right after you complete the old one, you
<!-- Add Ons: author for internal authors -->
<string name="AOA_internal_author">Astrid Team</string>
<!-- ========================================== SynchronizationProvider == -->
<!-- Sync Notification: message when starting up -->
<string name="SyP_progress_starting">Logging In...</string>
<!-- Sync Notification: transmitting local task (%s -> task name) -->
<string name="SyP_progress_localtx">Transmitting: %s</string>
<!-- Sync Notification: receiving remote task (%s -> task name) -->
<string name="SyP_progress_remotetx">Receiving: %s</string>
</resources>

@ -322,6 +322,7 @@ occur (it is a minor drain on battery).
<string name="sync_result_merged">Merged: %d</string>
<string name="sync_progress_remote">Reading Remote Data</string>
<string name="sync_progress_starting">Reading Remote Data</string>
<string name="sync_progress_rxlist">Reading List: %s</string>
<string name="sync_progress_repeating">Synchronizing Repeating Task</string>
<string name="sync_progress_localtx">Transmitting: %s</string>

@ -115,17 +115,10 @@ Error Message: %s
</string>
<!-- ======================== Synchronization ========================== -->
<string name="rmilk_notification_title">Astrid: Remember the Milk</string>
<!-- generic sync constants -->
<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>
@ -133,12 +126,16 @@ Error Message: %s
<string name="rmilk_result_updated">Updated: %d</string>
<string name="rmilk_result_deleted">Deleted: %d</string>
<string name="rmilk_result_merged">Merged: %d</string>
<!-- rmilk-specific constants -->
<string name="rmilk_notification_title">Astrid: Remember the Milk</string>
<string name="rmilk_forget_confirm">Log out / clear synchronization data?</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_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 name="rmilk_ioerror">Connection Error! Check your Internet connection,
or maybe RTM servers (status.rememberthemilk.com), for possible solutions.</string>
<string-array name="rmilk_MPr_interval_entries">
<!-- rmilk_MPr_interval_entries: Synchronization Intervals -->

@ -24,8 +24,8 @@ import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Map.Entry;
import java.util.StringTokenizer;
import java.util.Map.Entry;
import android.content.Context;
import android.content.Intent;
@ -38,15 +38,15 @@ import com.mdt.rtm.ApplicationInfo;
import com.mdt.rtm.ServiceException;
import com.mdt.rtm.ServiceImpl;
import com.mdt.rtm.ServiceInternalException;
import com.mdt.rtm.data.RtmAuth.Perms;
import com.mdt.rtm.data.RtmList;
import com.mdt.rtm.data.RtmLists;
import com.mdt.rtm.data.RtmTask;
import com.mdt.rtm.data.RtmTask.Priority;
import com.mdt.rtm.data.RtmTaskList;
import com.mdt.rtm.data.RtmTaskNote;
import com.mdt.rtm.data.RtmTaskSeries;
import com.mdt.rtm.data.RtmTasks;
import com.mdt.rtm.data.RtmAuth.Perms;
import com.mdt.rtm.data.RtmTask.Priority;
import com.timsu.astrid.R;
import com.timsu.astrid.activities.SyncLoginActivity;
import com.timsu.astrid.activities.SyncLoginActivity.SyncLoginCallback;
@ -537,7 +537,7 @@ public class RTMSyncProvider extends SynchronizationProvider {
/** SynchronizeHelper for remember the milk */
class RtmSyncHelper implements SynchronizeHelper {
private String timeline;
private final String timeline;
private String lastCreatedTask = null;
private Context context;

@ -22,7 +22,6 @@ import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.ViewGroup.OnHierarchyChangeListener;
import android.widget.EditText;
import android.widget.ExpandableListView;
import android.widget.FrameLayout;
@ -34,6 +33,7 @@ import com.timsu.astrid.R;
import com.todoroo.andlib.service.Autowired;
import com.todoroo.andlib.service.DependencyInjectionService;
import com.todoroo.andlib.service.ExceptionService;
import com.todoroo.andlib.sql.Functions;
import com.todoroo.andlib.sql.QueryTemplate;
import com.todoroo.andlib.utility.DialogUtilities;
import com.todoroo.astrid.adapter.FilterAdapter;
@ -111,9 +111,9 @@ public class FilterListActivity extends ExpandableListActivity {
final String intentAction = intent.getAction();
if (Intent.ACTION_SEARCH.equals(intentAction)) {
String query = intent.getStringExtra(SearchManager.QUERY).trim();
Filter filter = new Filter(null, null,
getString(R.string.FLA_search_filter, query),
new QueryTemplate().where(Task.TITLE.like("%" + query + "%")), //$NON-NLS-1$ //$NON-NLS-2$
Filter filter = new Filter(null, getString(R.string.FLA_search_filter, query),
new QueryTemplate().where(Functions.upper(Task.TITLE).like("%" + //$NON-NLS-1$
query.toUpperCase() + "%")),
null);
intent = new Intent(FilterListActivity.this, TaskListActivity.class);
intent.putExtra(TaskListActivity.TOKEN_FILTER, filter);
@ -208,7 +208,7 @@ public class FilterListActivity extends ExpandableListActivity {
registerForContextMenu(getExpandableListView());
getExpandableListView().setGroupIndicator(
getResources().getDrawable(R.drawable.expander_group));
getExpandableListView().setOnHierarchyChangeListener(new OnHierarchyChangeListener() {
/*getExpandableListView().setOnHierarchyChangeListener(new OnHierarchyChangeListener() {
@Override
public void onChildViewAdded(View parent, View child) {
// auto-expand adapter
@ -233,7 +233,7 @@ public class FilterListActivity extends ExpandableListActivity {
public void onChildViewRemoved(View parent, View child) {
// do nothing
}
});
});*/
}
/**

@ -174,7 +174,7 @@ public class TaskListActivity extends ListActivity implements OnScrollListener {
if(extras.containsKey(TOKEN_FILTER_VALUES))
values = AndroidUtilities.contentValuesFromString(extras.getString(TOKEN_FILTER_VALUES));
filter = new Filter("", "", title, new QueryTemplate(), values); //$NON-NLS-1$ //$NON-NLS-2$
filter = new Filter("", title, new QueryTemplate(), values); //$NON-NLS-1$ //$NON-NLS-2$
filter.sqlQuery = sql;
} else {
filter = CoreFilterExposer.buildInboxFilter(getResources());

@ -52,6 +52,11 @@ public class MetadataDao extends GenericDao<Metadata> {
return Metadata.KEY.eq(key);
}
/** Returns all metadata associated with a given key */
public static Criterion byTaskAndwithKey(long taskId, String key) {
return Criterion.and(withKey(key), byTask(taskId));
}
}

@ -8,13 +8,12 @@ import android.app.AlertDialog;
import android.app.PendingIntent;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.content.DialogInterface.OnClickListener;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import com.timsu.astrid.R;
import com.timsu.astrid.sync.SynchronizationService;
import com.timsu.astrid.utilities.BackupService;
import com.todoroo.andlib.service.Autowired;
import com.todoroo.andlib.service.ContextManager;
@ -105,10 +104,6 @@ public class StartupService {
am.setInexactRepeating(AlarmManager.RTC, 0,
Constants.WIDGET_UPDATE_INTERVAL, pendingIntent);
// start synchronization service
if(Constants.SYNCHRONIZE)
SynchronizationService.scheduleService(context);
// start backup service
BackupService.scheduleService(context);

@ -2,6 +2,8 @@ package com.todoroo.astrid.utility;
public final class Constants {
// --- general application constants
/**
* Flurry API Key
*/
@ -17,11 +19,6 @@ public final class Constants {
*/
public static final boolean OEM = false;
/**
* Whether to display synchronization options
*/
public static final boolean SYNCHRONIZE = !OEM;
/**
* Interval to update the widget (in order to detect hidden tasks
* becoming visible)
@ -31,5 +28,11 @@ public final class Constants {
/**
* Whether to turn on debugging logging and UI
*/
public static final boolean DEBUG = false;
public static final boolean DEBUG = true;
// --- notification id's
/** Notification Manager id for RMilk notifications */
public static final int NOTIFICATION_SYNC = -1;
}

Loading…
Cancel
Save