diff --git a/astrid/plugin-src/com/todoroo/astrid/gtasks/GtasksFilterExposer.java b/astrid/plugin-src/com/todoroo/astrid/gtasks/GtasksFilterExposer.java index f993117015..c87829fdea 100644 --- a/astrid/plugin-src/com/todoroo/astrid/gtasks/GtasksFilterExposer.java +++ b/astrid/plugin-src/com/todoroo/astrid/gtasks/GtasksFilterExposer.java @@ -64,7 +64,7 @@ public static Filter filterFromList(Context context, StoreObject list) { MetadataCriteria.withKey(GtasksMetadata.METADATA_KEY), TaskCriteria.activeAndVisible(), GtasksMetadata.LIST_ID.eq(list.getValue(GtasksList.REMOTE_ID)))).orderBy( - Order.asc(Functions.cast(GtasksMetadata.ORDER, "LONG"))), //$NON-NLS-1$ + Order.asc(Functions.cast(GtasksMetadata.ORDER, "LONG"))).groupBy(Task.ID), //$NON-NLS-1$ values); filter.listingIcon = ((BitmapDrawable)context.getResources().getDrawable(R.drawable.gtasks_icon)).getBitmap(); filter.customTaskList = new ComponentName(ContextManager.getContext(), GtasksListActivity.class); diff --git a/astrid/plugin-src/com/todoroo/astrid/gtasks/GtasksListActivity.java b/astrid/plugin-src/com/todoroo/astrid/gtasks/GtasksListActivity.java index faa0abcc44..40060343f7 100644 --- a/astrid/plugin-src/com/todoroo/astrid/gtasks/GtasksListActivity.java +++ b/astrid/plugin-src/com/todoroo/astrid/gtasks/GtasksListActivity.java @@ -11,11 +11,16 @@ import com.todoroo.andlib.utility.DialogUtilities; import com.todoroo.andlib.utility.Preferences; import com.todoroo.astrid.activity.DraggableTaskListActivity; +import com.todoroo.astrid.gtasks.sync.GtasksSyncOnSaveService; public class GtasksListActivity extends DraggableTaskListActivity { @Autowired private GtasksTaskListUpdater gtasksTaskListUpdater; + @Autowired private GtasksSyncOnSaveService gtasksSyncOnSaveService; + + @Autowired private GtasksMetadataService gtasksMetadataService; + @Override protected IntegerProperty getIndentProperty() { return GtasksMetadata.INDENT; @@ -43,6 +48,7 @@ public void drop(int from, int to) { long targetTaskId = taskAdapter.getItemId(from); long destinationTaskId = taskAdapter.getItemId(to); gtasksTaskListUpdater.moveTo(targetTaskId, destinationTaskId); + gtasksSyncOnSaveService.triggerMoveForMetadata(gtasksMetadataService.getTaskMetadata(targetTaskId)); loadTaskListContent(true); } }; @@ -52,6 +58,7 @@ public void drop(int from, int to) { public void swipeRight(int which) { long targetTaskId = taskAdapter.getItemId(which); gtasksTaskListUpdater.indent(targetTaskId, 1); + gtasksSyncOnSaveService.triggerMoveForMetadata(gtasksMetadataService.getTaskMetadata(targetTaskId)); loadTaskListContent(true); } @@ -59,6 +66,7 @@ public void swipeRight(int which) { public void swipeLeft(int which) { long targetTaskId = taskAdapter.getItemId(which); gtasksTaskListUpdater.indent(targetTaskId, -1); + gtasksSyncOnSaveService.triggerMoveForMetadata(gtasksMetadataService.getTaskMetadata(targetTaskId)); loadTaskListContent(true); } }; diff --git a/astrid/plugin-src/com/todoroo/astrid/gtasks/api/GtasksService.java b/astrid/plugin-src/com/todoroo/astrid/gtasks/api/GtasksService.java index 6890ebe318..d75e364c0d 100644 --- a/astrid/plugin-src/com/todoroo/astrid/gtasks/api/GtasksService.java +++ b/astrid/plugin-src/com/todoroo/astrid/gtasks/api/GtasksService.java @@ -4,6 +4,7 @@ import com.google.api.client.extensions.android2.AndroidHttp; import com.google.api.client.googleapis.auth.oauth2.draft10.GoogleAccessProtectedResource; +import com.google.api.client.http.HttpResponseException; import com.google.api.client.json.jackson.JacksonFactory; import com.google.api.services.tasks.v1.Tasks; import com.google.api.services.tasks.v1.Tasks.TasksOperations.Insert; @@ -12,6 +13,7 @@ import com.google.api.services.tasks.v1.model.Task; import com.google.api.services.tasks.v1.model.TaskList; import com.google.api.services.tasks.v1.model.TaskLists; +import com.todoroo.astrid.gtasks.auth.GtasksTokenValidator; /** * Wrapper around the official Google Tasks API to simplify common operations. In the case @@ -22,28 +24,39 @@ @SuppressWarnings("nls") public class GtasksService { private Tasks service; + private GoogleAccessProtectedResource accessProtectedResource; + private String token; private static final String API_KEY = "AIzaSyCIYZTBo6haRHxmiplZsfYdagFEpaiFnAk"; // non-production API key public static final String AUTH_TOKEN_TYPE = "oauth2:https://www.googleapis.com/auth/tasks"; public GtasksService(String authToken) { - try { - authenticate(authToken); - } catch (Exception e) { - e.printStackTrace(); - } + authenticate(authToken); } - public void authenticate(String authToken) throws IOException { - - GoogleAccessProtectedResource accessProtectedResource = new GoogleAccessProtectedResource(authToken); + public void authenticate(String authToken) { + this.token = authToken; + accessProtectedResource = new GoogleAccessProtectedResource(authToken); service = new Tasks(AndroidHttp.newCompatibleTransport(), accessProtectedResource, new JacksonFactory()); service.accessKey = API_KEY; service.setApplicationName("Astrid"); } + //If we get a 401 or 403, try revalidating the auth token before bailing + private synchronized void handleException(IOException e) { + if (e instanceof HttpResponseException) { + HttpResponseException h = (HttpResponseException)e; + if (h.response.statusCode == 401 || h.response.statusCode == 403) { + token = GtasksTokenValidator.validateAuthToken(token); + if (token != null) { + accessProtectedResource.setAccessToken(token); + } + } + } + } + /** * A simple service query that will throw an exception if anything goes wrong. * Useful for checking if token needs revalidating or if there are network problems-- @@ -59,6 +72,7 @@ public TaskLists allGtaskLists() throws IOException { try { toReturn = service.tasklists.list().execute(); } catch (IOException e) { + handleException(e); toReturn = service.tasklists.list().execute(); } return toReturn; @@ -69,6 +83,7 @@ public TaskList getGtaskList(String id) throws IOException { try { toReturn = service.tasklists.get(id).execute(); } catch (IOException e) { + handleException(e); toReturn = service.tasklists.get(id).execute(); } return toReturn; @@ -81,6 +96,7 @@ public TaskList createGtaskList(String title) throws IOException { try { toReturn = service.tasklists.insert(newList).execute(); } catch (IOException e) { + handleException(e); toReturn = service.tasklists.insert(newList).execute(); } return toReturn; @@ -91,6 +107,7 @@ public TaskList updateGtaskList(TaskList list) throws IOException { try { toReturn = service.tasklists.update(list.id, list).execute(); } catch (IOException e) { + handleException(e); toReturn = service.tasklists.update(list.id, list).execute(); } return toReturn; @@ -100,7 +117,8 @@ public void deleteGtaskList(String listId) throws IOException { try { service.tasklists.delete(listId).execute(); } catch (IOException e) { - service.tasks.clear(listId).execute(); + handleException(e); + service.tasklists.delete(listId).execute(); } } @@ -109,6 +127,7 @@ public com.google.api.services.tasks.v1.model.Tasks getAllGtasksFromTaskList(Tas try { toReturn = getAllGtasksFromListId(list.id, includeDeleted); } catch (IOException e) { + handleException(e); toReturn = getAllGtasksFromListId(list.id, includeDeleted); } return toReturn; @@ -121,6 +140,7 @@ public com.google.api.services.tasks.v1.model.Tasks getAllGtasksFromListId(Strin try { toReturn = request.execute(); } catch (IOException e) { + handleException(e); toReturn = request.execute(); } return toReturn; @@ -131,6 +151,7 @@ public Task getGtask(String listId, String taskId) throws IOException { try { toReturn = service.tasks.get(listId, taskId).execute(); } catch (IOException e) { + handleException(e); toReturn = service.tasks.get(listId, taskId).execute(); } return toReturn; @@ -158,6 +179,7 @@ public Task createGtask(String listId, Task task, String parent, String priorSib try { toReturn = insertOp.execute(); } catch (IOException e) { + handleException(e); toReturn = insertOp.execute(); } return toReturn; @@ -168,6 +190,7 @@ public Task updateGtask(String listId, Task task) throws IOException { try { toReturn = service.tasks.update(listId, task.id, task).execute(); } catch (IOException e) { + handleException(e); toReturn = service.tasks.update(listId, task.id, task).execute(); } return toReturn; @@ -182,6 +205,7 @@ public Task moveGtask(String listId, String taskId, String parentId, String prev try { toReturn = move.execute(); } catch (IOException e) { + handleException(e); toReturn = move.execute(); } return toReturn; @@ -191,6 +215,7 @@ public void deleteGtask(String listId, String taskId) throws IOException { try { service.tasks.delete(listId, taskId).execute(); } catch (IOException e) { + handleException(e); service.tasks.delete(listId, taskId).execute(); } } @@ -199,6 +224,7 @@ public void clearCompletedTasks(String listId) throws IOException { try { service.tasks.clear(listId).execute(); } catch (IOException e) { + handleException(e); service.tasks.clear(listId).execute(); } } diff --git a/astrid/plugin-src/com/todoroo/astrid/gtasks/sync/GtasksSyncOnSaveService.java b/astrid/plugin-src/com/todoroo/astrid/gtasks/sync/GtasksSyncOnSaveService.java index 6f4f239b87..056283ccb6 100644 --- a/astrid/plugin-src/com/todoroo/astrid/gtasks/sync/GtasksSyncOnSaveService.java +++ b/astrid/plugin-src/com/todoroo/astrid/gtasks/sync/GtasksSyncOnSaveService.java @@ -1,7 +1,7 @@ package com.todoroo.astrid.gtasks.sync; import java.io.IOException; -import java.util.concurrent.Semaphore; +import java.util.concurrent.LinkedBlockingQueue; import android.content.ContentValues; import android.text.TextUtils; @@ -42,93 +42,70 @@ public GtasksSyncOnSaveService() { DependencyInjectionService.getInstance().inject(this); } - private final Semaphore syncOnSaveSema = new Semaphore(1); + private final LinkedBlockingQueue operationQueue = new LinkedBlockingQueue(); + + private abstract class SyncOnSaveOperation {} + + private class TaskPushOp extends SyncOnSaveOperation { + protected Task model; + + public TaskPushOp(Task model) { + this.model = model; + } + } + + class MoveOp extends SyncOnSaveOperation { + protected Metadata metadata; + + public MoveOp(Metadata metadata) { + this.metadata = metadata; + } + } + public void initialize() { + new Thread(new Runnable() { + public void run() { + while (true) { + SyncOnSaveOperation op; + try { + op = operationQueue.take(); + } catch (InterruptedException e) { + continue; + } + try { + if (syncOnSaveEnabled() && !gtasksPreferenceService.isOngoing()) { + if (op instanceof TaskPushOp) { + TaskPushOp taskPush = (TaskPushOp)op; + pushTaskOnSave(taskPush.model, taskPush.model.getSetValues()); + } else if (op instanceof MoveOp) { + MoveOp move = (MoveOp)op; + pushMetadataOnSave(move.metadata); + } + } + } catch (IOException e){ + System.err.println("Sync on save failed"); //$NON-NLS-1$ + } + } + } + }).start(); + taskDao.addListener(new ModelUpdateListener() { public void onModelUpdated(final Task model) { if (!syncOnSaveEnabled()) return; if (gtasksPreferenceService.isOngoing()) //Don't try and sync changes that occur during a normal sync return; - if(Flags.checkAndClear(Flags.GTASKS_SUPPRESS_SYNC)) - return; final ContentValues setValues = model.getSetValues(); if(setValues == null || !checkForToken()) return; if (!checkValuesForProperties(setValues, TASK_PROPERTIES)) //None of the properties we sync were updated return; - - new Thread(new Runnable() { - @Override - public void run() { - // sleep so metadata associated with task is saved - AndroidUtilities.sleepDeep(1000L); - outer: - try { - try { - syncOnSaveSema.acquire(); - } catch (InterruptedException ingored) { - break outer; - } - pushTaskOnSave(model, setValues); - } catch (IOException e) { - e.printStackTrace(); - System.err.println("Sync on save failed"); //$NON-NLS-1$ - return; - } finally { - syncOnSaveSema.release(); - } - } - }).start(); - - } - });//*/ - - metadataDao.addListener(new ModelUpdateListener() { - public void onModelUpdated(final Metadata model) { - if (!syncOnSaveEnabled()) - return; - if (!model.getValue(Metadata.KEY).equals(GtasksMetadata.METADATA_KEY)) //Don't care about non-gtasks metadata - return; - if (gtasksPreferenceService.isOngoing()) //Don't try and sync changes that occur during a normal sync - return; - final ContentValues setValues = model.getSetValues(); - if (setValues == null || !checkForToken()) - return; - - if (checkValuesForProperties(setValues, METADATA_IGNORE_PROPERTIES)) // don't sync the move cases we don't handle - return; - if (!checkValuesForProperties(setValues, METADATA_PROPERTIES)) - return; - - if (Flags.checkAndClear(Flags.GTASKS_SUPPRESS_SYNC)) + if(Flags.checkAndClear(Flags.GTASKS_SUPPRESS_SYNC)) return; - new Thread(new Runnable() { - @Override - public void run() { - // sleep so metadata associated with task is saved - AndroidUtilities.sleepDeep(1000L); - outer: - try { - try { - syncOnSaveSema.acquire(); - } catch (InterruptedException ignored) { - break outer; - } - pushMetadataOnSave(model); - } catch (IOException e) { - e.printStackTrace(); - System.err.println("Sync on save failed"); //$NON-NLS-1$ - return; - } finally { - syncOnSaveSema.release(); - } - } - }).start(); + operationQueue.offer(new TaskPushOp((Task)model.clone())); } - }); } @@ -139,14 +116,6 @@ public void run() { Task.COMPLETION_DATE, Task.DELETION_DATE }; - private static final Property[] METADATA_PROPERTIES = - { GtasksMetadata.INDENT, - GtasksMetadata.PARENT_TASK }; - - private static final Property[] METADATA_IGNORE_PROPERTIES = - { GtasksMetadata.ORDER, - GtasksMetadata.LIST_ID }; - /** * Checks to see if any of the values changed are among the properties we sync * @param values @@ -162,10 +131,27 @@ private boolean checkValuesForProperties(ContentValues values, Property[] pro } + public void triggerMoveForMetadata(final Metadata metadata) { + if (!syncOnSaveEnabled()) + return; + if (!metadata.getValue(Metadata.KEY).equals(GtasksMetadata.METADATA_KEY)) //Don't care about non-gtasks metadata + return; + if (gtasksPreferenceService.isOngoing()) //Don't try and sync changes that occur during a normal sync + return; + if (!checkForToken()) + return; + if (Flags.checkAndClear(Flags.GTASKS_SUPPRESS_SYNC)) + return; + + operationQueue.offer(new MoveOp(metadata)); + } + /** * Synchronize with server when data changes */ private void pushTaskOnSave(Task task, ContentValues values) throws IOException { + AndroidUtilities.sleepDeep(1000L); //Wait for metadata to be saved + Metadata gtasksMetadata = gtasksMetadataService.getTaskMetadata(task.getId()); com.google.api.services.tasks.v1.model.Task remoteModel = null; boolean newlyCreated = false; @@ -276,6 +262,7 @@ private void pushTaskOnSave(Task task, ContentValues values) throws IOException } private void pushMetadataOnSave(Metadata model) throws IOException { + AndroidUtilities.sleepDeep(1000L); //Initialize the gtasks api service String token = gtasksPreferenceService.getToken(); token = GtasksTokenValidator.validateAuthToken(token);