From 35f5e65adccc10fc5a3fba8437105fafc4da628f Mon Sep 17 00:00:00 2001 From: Tim Su Date: Tue, 21 Sep 2010 01:11:37 +0800 Subject: [PATCH] Gtasks boilerplate. That's a lot of boilerplate, but it does get better with each iteration. In the future we should extract more of this type of functionality --- .../astrid/gtasks/GtasksDetailExposer.java | 83 ++++++++++++++ .../astrid/gtasks/GtasksFilterExposer.java | 96 +++++++++++++++++ .../com/todoroo/astrid/gtasks/GtasksList.java | 31 ++++++ .../astrid/gtasks/GtasksListService.java | 95 ++++++++++++++++ .../todoroo/astrid/gtasks/GtasksMetadata.java | 47 ++++++++ .../astrid/gtasks/GtasksMetadataService.java | 102 ++++++++++++++++++ .../astrid/gtasks/GtasksUtilities.java | 36 +++++++ astrid/res/values/keys.xml | 4 + astrid/res/values/strings-gtasks.xml | 24 +++++ .../service/AstridDependencyInjector.java | 6 ++ 10 files changed, 524 insertions(+) create mode 100644 astrid/plugin-src/com/todoroo/astrid/gtasks/GtasksDetailExposer.java create mode 100644 astrid/plugin-src/com/todoroo/astrid/gtasks/GtasksFilterExposer.java create mode 100644 astrid/plugin-src/com/todoroo/astrid/gtasks/GtasksList.java create mode 100644 astrid/plugin-src/com/todoroo/astrid/gtasks/GtasksListService.java create mode 100644 astrid/plugin-src/com/todoroo/astrid/gtasks/GtasksMetadata.java create mode 100644 astrid/plugin-src/com/todoroo/astrid/gtasks/GtasksMetadataService.java create mode 100644 astrid/plugin-src/com/todoroo/astrid/gtasks/GtasksUtilities.java create mode 100644 astrid/res/values/strings-gtasks.xml diff --git a/astrid/plugin-src/com/todoroo/astrid/gtasks/GtasksDetailExposer.java b/astrid/plugin-src/com/todoroo/astrid/gtasks/GtasksDetailExposer.java new file mode 100644 index 000000000..14a26e222 --- /dev/null +++ b/astrid/plugin-src/com/todoroo/astrid/gtasks/GtasksDetailExposer.java @@ -0,0 +1,83 @@ +/** + * See the file "LICENSE" for the full license governing this code. + */ +package com.todoroo.astrid.gtasks; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + +import com.todoroo.andlib.service.Autowired; +import com.todoroo.andlib.service.ContextManager; +import com.todoroo.andlib.service.DependencyInjectionService; +import com.todoroo.astrid.api.AstridApiConstants; +import com.todoroo.astrid.data.Metadata; + +/** + * Exposes Task Details for Remember the Milk: + * - RTM list + * - RTM repeat information + * - RTM notes + * + * @author Tim Su + * + */ +public class GtasksDetailExposer extends BroadcastReceiver { + + public static final String DETAIL_SEPARATOR = " | "; //$NON-NLS-1$ + + @Autowired + private GtasksMetadataService gtasksMetadataService; + + @Autowired + private GtasksListService gtasksListService; + + @Override + public void onReceive(Context context, Intent intent) { + ContextManager.setContext(context); + + // if we aren't logged in, don't expose features + if(!GtasksUtilities.INSTANCE.isLoggedIn()) + return; + + long taskId = intent.getLongExtra(AstridApiConstants.EXTRAS_TASK_ID, -1); + if(taskId == -1) + return; + + boolean extended = intent.getBooleanExtra(AstridApiConstants.EXTRAS_EXTENDED, false); + String taskDetail = getTaskDetails(taskId, extended); + if(taskDetail == null) + return; + + Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_SEND_DETAILS); + broadcastIntent.putExtra(AstridApiConstants.EXTRAS_ADDON, GtasksUtilities.IDENTIFIER); + broadcastIntent.putExtra(AstridApiConstants.EXTRAS_TASK_ID, taskId); + broadcastIntent.putExtra(AstridApiConstants.EXTRAS_EXTENDED, extended); + broadcastIntent.putExtra(AstridApiConstants.EXTRAS_RESPONSE, taskDetail); + context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ); + } + + public String getTaskDetails(long id, boolean extended) { + if(extended) + return null; + + DependencyInjectionService.getInstance().inject(this); + + Metadata metadata = gtasksMetadataService.getTaskMetadata(id); + if(metadata == null) + return null; + + StringBuilder builder = new StringBuilder(); + + long listId = metadata.getValue(GtasksMetadata.LIST_ID); + String listName = gtasksListService.getListName(listId); + // RTM list is out of date. don't display RTM stuff + if(listName == GtasksListService.LIST_NOT_FOUND) + return null; + + builder.append(" ").append(listName); //$NON-NLS-1$ + + return builder.toString(); + } + +} diff --git a/astrid/plugin-src/com/todoroo/astrid/gtasks/GtasksFilterExposer.java b/astrid/plugin-src/com/todoroo/astrid/gtasks/GtasksFilterExposer.java new file mode 100644 index 000000000..e28670b3e --- /dev/null +++ b/astrid/plugin-src/com/todoroo/astrid/gtasks/GtasksFilterExposer.java @@ -0,0 +1,96 @@ +/** + * See the file "LICENSE" for the full license governing this code. + */ +package com.todoroo.astrid.gtasks; + +import org.weloveastrid.rmilk.MilkUtilities; + +import android.content.BroadcastReceiver; +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; + +import com.timsu.astrid.R; +import com.todoroo.andlib.service.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.QueryTemplate; +import com.todoroo.astrid.api.AstridApiConstants; +import com.todoroo.astrid.api.Filter; +import com.todoroo.astrid.api.FilterCategory; +import com.todoroo.astrid.api.FilterListHeader; +import com.todoroo.astrid.api.FilterListItem; +import com.todoroo.astrid.data.Metadata; +import com.todoroo.astrid.data.StoreObject; +import com.todoroo.astrid.data.Task; +import com.todoroo.astrid.data.MetadataApiDao.MetadataCriteria; +import com.todoroo.astrid.data.TaskApiDao.TaskCriteria; + +/** + * Exposes filters based on RTM lists + * + * @author Tim Su + * + */ +public class GtasksFilterExposer extends BroadcastReceiver { + + @Autowired + private GtasksListService gtasksListService; + private StoreObject[] lists; + + private Filter filterFromList(StoreObject list) { + String listName = list.getValue(GtasksList.NAME); + ContentValues values = new ContentValues(); + values.putAll(GtasksMetadata.createEmptyMetadata().getMergedValues()); + values.remove(Metadata.TASK.name); + values.put(GtasksMetadata.LIST_ID.name, list.getValue(GtasksList.REMOTE_ID)); + Filter filter = new Filter(listName, listName, new QueryTemplate().join( + Join.left(Metadata.TABLE, Task.ID.eq(Metadata.TASK))).where(Criterion.and( + MetadataCriteria.withKey(GtasksMetadata.METADATA_KEY), + TaskCriteria.activeAndVisible(), + GtasksMetadata.LIST_ID.eq(list.getValue(GtasksList.REMOTE_ID)))), + values); + + return filter; + } + + @Override + public void onReceive(Context context, Intent intent) { + ContextManager.setContext(context); + DependencyInjectionService.getInstance().inject(this); + + // if we aren't logged in, don't expose features + if(!GtasksUtilities.INSTANCE.isLoggedIn()) + return; + + lists = gtasksListService.getLists(); + + // If user does not have any lists, don't show this section at all + if(noListsToShow()) + return; + + Filter[] listFilters = new Filter[lists.length]; + for(int i = 0; i < lists.length; i++) + listFilters[i] = filterFromList(lists[i]); + + FilterListHeader header = new FilterListHeader(context.getString(R.string.gtasks_FEx_header)); + FilterCategory listsCategory = new FilterCategory(context.getString(R.string.gtasks_FEx_list), + listFilters); + + // transmit filter list + FilterListItem[] list = new FilterListItem[2]; + list[0] = header; + list[1] = listsCategory; + Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_SEND_FILTERS); + broadcastIntent.putExtra(AstridApiConstants.EXTRAS_ADDON, MilkUtilities.IDENTIFIER); + broadcastIntent.putExtra(AstridApiConstants.EXTRAS_RESPONSE, list); + context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ); + } + + private boolean noListsToShow() { + return lists.length == 0; + } + +} diff --git a/astrid/plugin-src/com/todoroo/astrid/gtasks/GtasksList.java b/astrid/plugin-src/com/todoroo/astrid/gtasks/GtasksList.java new file mode 100644 index 000000000..55e302252 --- /dev/null +++ b/astrid/plugin-src/com/todoroo/astrid/gtasks/GtasksList.java @@ -0,0 +1,31 @@ +package com.todoroo.astrid.gtasks; + +import com.todoroo.andlib.data.Property.IntegerProperty; +import com.todoroo.andlib.data.Property.LongProperty; +import com.todoroo.andlib.data.Property.StringProperty; +import com.todoroo.astrid.data.StoreObject; + +/** + * {@link StoreObject} entries for a GTasks List + * + * @author Tim Su + * + */ +public class GtasksList { + + /** type*/ + public static final String TYPE = "gtasks-list"; //$NON-NLS-1$ + + /** list id in g-tasks */ + public static final LongProperty REMOTE_ID = new LongProperty(StoreObject.TABLE, + StoreObject.ITEM.name); + + /** list name */ + public static final StringProperty NAME = new StringProperty(StoreObject.TABLE, + StoreObject.VALUE1.name); + + /** list order */ + public static final IntegerProperty ORDER = new IntegerProperty(StoreObject.TABLE, + StoreObject.VALUE2.name); + +} diff --git a/astrid/plugin-src/com/todoroo/astrid/gtasks/GtasksListService.java b/astrid/plugin-src/com/todoroo/astrid/gtasks/GtasksListService.java new file mode 100644 index 000000000..1a9748886 --- /dev/null +++ b/astrid/plugin-src/com/todoroo/astrid/gtasks/GtasksListService.java @@ -0,0 +1,95 @@ +package com.todoroo.astrid.gtasks; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import com.todoroo.andlib.data.TodorooCursor; +import com.todoroo.andlib.service.Autowired; +import com.todoroo.andlib.service.DependencyInjectionService; +import com.todoroo.andlib.sql.Query; +import com.todoroo.astrid.dao.StoreObjectDao; +import com.todoroo.astrid.dao.StoreObjectDao.StoreObjectCriteria; +import com.todoroo.astrid.data.StoreObject; + +public class GtasksListService { + + public static final String LIST_NOT_FOUND = null; + + @Autowired + private StoreObjectDao storeObjectDao; + + private StoreObject[] lists = null; + + public GtasksListService() { + DependencyInjectionService.getInstance().inject(this); + } + + private void readLists() { + if(lists != null) + return; + + TodorooCursor cursor = storeObjectDao.query(Query.select(StoreObject.PROPERTIES). + where(StoreObjectCriteria.byType(GtasksList.TYPE))); + try { + lists = new StoreObject[cursor.getCount()]; + for(int i = 0; i < lists.length; i++) { + cursor.moveToNext(); + StoreObject dashboard = new StoreObject(cursor); + lists[i] = dashboard; + } + } finally { + cursor.close(); + } + } + + public StoreObject[] getLists() { + readLists(); + return lists; + } + + /** + * Get list name + * @param listId + * @return NOT_FOUND if no list by this id exists, otherwise list name + */ + public String getListName(long listId) { + readLists(); + for(StoreObject list : lists) + if(list.getValue(GtasksList.REMOTE_ID).equals(listId)) + return list.getValue(GtasksList.NAME); + return LIST_NOT_FOUND; + } + + @SuppressWarnings("nls") + public void updateLists(JSONArray newLists) throws JSONException { + for(int i = 0; i < newLists.length(); i++) { + JSONObject remote = newLists.getJSONObject(i); + + long id = remote.optLong("id"); + StoreObject local = null; + for(StoreObject list : lists) { + if(list.getValue(GtasksList.REMOTE_ID).equals(id)) { + local = list; + break; + } + } + + if(local == null) + local = new StoreObject(); + + local.setValue(StoreObject.TYPE, GtasksList.TYPE); + local.setValue(GtasksList.REMOTE_ID, id); + local.setValue(GtasksList.NAME, remote.getString("title")); + local.setValue(GtasksList.ORDER, i); + storeObjectDao.persist(local); + } + + clearListCache(); + } + + private void clearListCache() { + lists = null; + } + +} diff --git a/astrid/plugin-src/com/todoroo/astrid/gtasks/GtasksMetadata.java b/astrid/plugin-src/com/todoroo/astrid/gtasks/GtasksMetadata.java new file mode 100644 index 000000000..b7eb443a3 --- /dev/null +++ b/astrid/plugin-src/com/todoroo/astrid/gtasks/GtasksMetadata.java @@ -0,0 +1,47 @@ +package com.todoroo.astrid.gtasks; + +import com.todoroo.andlib.data.Property.IntegerProperty; +import com.todoroo.andlib.data.Property.LongProperty; +import com.todoroo.astrid.data.Metadata; +import com.todoroo.astrid.utility.Preferences; + +/** + * Metadata entries for a GTasks Task + * @author Tim Su + * + */ +public class GtasksMetadata { + + private static final int ORDERING_UNSET = -1; + + /** metadata key */ + public static final String METADATA_KEY = "gtasks"; //$NON-NLS-1$ + + /** task id in google */ + public static final LongProperty ID = new LongProperty(Metadata.TABLE, + Metadata.VALUE1.name); + + public static final LongProperty LIST_ID = new LongProperty(Metadata.TABLE, + Metadata.VALUE2.name); + + public static final LongProperty OWNER_ID = new LongProperty(Metadata.TABLE, + Metadata.VALUE3.name); + + public static final IntegerProperty INDENTATION = new IntegerProperty(Metadata.TABLE, + Metadata.VALUE4.name); + + public static final IntegerProperty ORDERING = new IntegerProperty(Metadata.TABLE, + Metadata.VALUE5.name); + + public static Metadata createEmptyMetadata() { + Metadata metadata = new Metadata(); + metadata.setValue(Metadata.KEY, GtasksMetadata.METADATA_KEY); + metadata.setValue(ID, 0L); + metadata.setValue(LIST_ID, Preferences.getLong(GtasksUtilities.PREF_DEFAULT_LIST, 0)); + metadata.setValue(OWNER_ID, 0L); + metadata.setValue(INDENTATION, 0); + metadata.setValue(ORDERING, ORDERING_UNSET); + return metadata; + } + +} diff --git a/astrid/plugin-src/com/todoroo/astrid/gtasks/GtasksMetadataService.java b/astrid/plugin-src/com/todoroo/astrid/gtasks/GtasksMetadataService.java new file mode 100644 index 000000000..17d62018f --- /dev/null +++ b/astrid/plugin-src/com/todoroo/astrid/gtasks/GtasksMetadataService.java @@ -0,0 +1,102 @@ +/** + * See the file "LICENSE" for the full license governing this code. + */ +package com.todoroo.astrid.gtasks; + +import com.todoroo.andlib.data.Property; +import com.todoroo.andlib.data.TodorooCursor; +import com.todoroo.andlib.service.Autowired; +import com.todoroo.andlib.service.DependencyInjectionService; +import com.todoroo.andlib.sql.Criterion; +import com.todoroo.andlib.sql.Join; +import com.todoroo.andlib.sql.Query; +import com.todoroo.astrid.core.PluginServices; +import com.todoroo.astrid.dao.MetadataDao.MetadataCriteria; +import com.todoroo.astrid.dao.TaskDao.TaskCriteria; +import com.todoroo.astrid.data.Metadata; +import com.todoroo.astrid.data.Task; +import com.todoroo.astrid.service.MetadataService; +import com.todoroo.astrid.service.TaskService; + +/** + * Service for working with GTasks metadata + * + * @author Tim Su + * + */ +public final class GtasksMetadataService { + + // --- constants + + /** Utility for joining tasks with metadata */ + public static final Join METADATA_JOIN = Join.left(Metadata.TABLE, Task.ID.eq(Metadata.TASK)); + + // --- instance variables + + @Autowired + private TaskService taskService; + + @Autowired + private MetadataService metadataService; + + public GtasksMetadataService() { + DependencyInjectionService.getInstance().inject(this); + } + + // --- task and metadata methods + + /** + * Clears metadata information. Used when user logs out of service + */ + public void clearMetadata() { + metadataService.deleteWhere(Metadata.KEY.eq(GtasksMetadata.METADATA_KEY)); + PluginServices.getTaskService().clearDetails(Task.ID.in(Query.select(Metadata.TASK).from(Metadata.TABLE). + where(MetadataCriteria.withKey(GtasksMetadata.METADATA_KEY)))); + } + + /** + * Gets tasks that were created since last sync + * @param properties + * @return + */ + public TodorooCursor getLocallyCreated(Property[] properties) { + return + taskService.query(Query.select(properties).join(GtasksMetadataService.METADATA_JOIN).where(Criterion.and( + Criterion.not(Task.ID.in(Query.select(Metadata.TASK).from(Metadata.TABLE). + where(Criterion.and(MetadataCriteria.withKey(GtasksMetadata.METADATA_KEY), GtasksMetadata.ID.gt(0))))), + TaskCriteria.isActive())).groupBy(Task.ID)); + } + + /** + * Gets tasks that were modified since last sync + * @param properties + * @return null if never sync'd + */ + public TodorooCursor getLocallyUpdated(Property[] properties) { + long lastSyncDate = GtasksUtilities.INSTANCE.getLastSyncDate(); + if(lastSyncDate == 0) + return taskService.query(Query.select(Task.ID).where(Criterion.none)); + return + taskService.query(Query.select(properties).join(GtasksMetadataService.METADATA_JOIN). + where(Criterion.and(MetadataCriteria.withKey(GtasksMetadata.METADATA_KEY), + Task.MODIFICATION_DATE.gt(lastSyncDate))).groupBy(Task.ID)); + } + + /** + * Reads metadata out of a task + * @return null if no metadata found + */ + public Metadata getTaskMetadata(long taskId) { + TodorooCursor cursor = metadataService.query(Query.select( + Metadata.PROPERTIES).where( + MetadataCriteria.byTaskAndwithKey(taskId, GtasksMetadata.METADATA_KEY))); + try { + if(cursor.getCount() == 0) + return null; + cursor.moveToFirst(); + return new Metadata(cursor); + } finally { + cursor.close(); + } + } +} diff --git a/astrid/plugin-src/com/todoroo/astrid/gtasks/GtasksUtilities.java b/astrid/plugin-src/com/todoroo/astrid/gtasks/GtasksUtilities.java new file mode 100644 index 000000000..36f2f2ba9 --- /dev/null +++ b/astrid/plugin-src/com/todoroo/astrid/gtasks/GtasksUtilities.java @@ -0,0 +1,36 @@ +package com.todoroo.astrid.gtasks; + +import com.timsu.astrid.R; +import com.todoroo.astrid.sync.SyncProviderUtilities; + +/** + * Methods for working with GTasks preferences + * + * @author timsu + * + */ +public class GtasksUtilities extends SyncProviderUtilities { + + /** add-on identifier */ + public static final String IDENTIFIER = "gtasks"; //$NON-NLS-1$ + + public static final GtasksUtilities INSTANCE = new GtasksUtilities(); + + @Override + public String getIdentifier() { + return IDENTIFIER; + } + + @Override + public int getSyncIntervalKey() { + return R.string.gtasks_GPr_interval_key; + } + + /** GTasks user's default list id */ + public static final String PREF_DEFAULT_LIST = IDENTIFIER + "_defaultlist"; //$NON-NLS-1$ + + private GtasksUtilities() { + // prevent instantiation + } + +} \ No newline at end of file diff --git a/astrid/res/values/keys.xml b/astrid/res/values/keys.xml index 7055dccba..f0f7ce395 100644 --- a/astrid/res/values/keys.xml +++ b/astrid/res/values/keys.xml @@ -228,6 +228,10 @@ producteev_email producteev_password producteev_defaultdash + + + + gtasks_sync_freq diff --git a/astrid/res/values/strings-gtasks.xml b/astrid/res/values/strings-gtasks.xml new file mode 100644 index 000000000..3d7c7d010 --- /dev/null +++ b/astrid/res/values/strings-gtasks.xml @@ -0,0 +1,24 @@ + + + + + + + + Google Tasks + + + By List + + + + + Google Tasks + + + + + Astrid: Google Tasks + + + diff --git a/astrid/src/com/todoroo/astrid/service/AstridDependencyInjector.java b/astrid/src/com/todoroo/astrid/service/AstridDependencyInjector.java index 9edf87964..fd4c2d5fb 100644 --- a/astrid/src/com/todoroo/astrid/service/AstridDependencyInjector.java +++ b/astrid/src/com/todoroo/astrid/service/AstridDependencyInjector.java @@ -11,6 +11,8 @@ import com.todoroo.astrid.dao.Database; import com.todoroo.astrid.dao.MetadataDao; import com.todoroo.astrid.dao.StoreObjectDao; import com.todoroo.astrid.dao.TaskDao; +import com.todoroo.astrid.gtasks.GtasksListService; +import com.todoroo.astrid.gtasks.GtasksMetadataService; import com.todoroo.astrid.utility.Constants; /** @@ -61,6 +63,10 @@ public class AstridDependencyInjector extends AbstractDependencyInjector { injectables.put("alertsTable", "alerts"); injectables.put("syncTable", "sync"); + // com.todoroo.astrid.gtasks + injectables.put("gtasksListService", GtasksListService.class); + injectables.put("gtasksMetadataService", GtasksMetadataService.class); + // these make reference to fields defined above injectables.put("errorReporters", new ErrorReporter[] { new AndroidLogReporter(),