Merge remote-tracking branch 'sbosley/110715_gtasks_sync_on_save_bugfixes'

pull/14/head
Tim Su 14 years ago
commit 0292894194

@ -64,7 +64,7 @@ public class GtasksFilterExposer extends BroadcastReceiver {
MetadataCriteria.withKey(GtasksMetadata.METADATA_KEY), MetadataCriteria.withKey(GtasksMetadata.METADATA_KEY),
TaskCriteria.activeAndVisible(), TaskCriteria.activeAndVisible(),
GtasksMetadata.LIST_ID.eq(list.getValue(GtasksList.REMOTE_ID)))).orderBy( 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); values);
filter.listingIcon = ((BitmapDrawable)context.getResources().getDrawable(R.drawable.gtasks_icon)).getBitmap(); filter.listingIcon = ((BitmapDrawable)context.getResources().getDrawable(R.drawable.gtasks_icon)).getBitmap();
filter.customTaskList = new ComponentName(ContextManager.getContext(), GtasksListActivity.class); filter.customTaskList = new ComponentName(ContextManager.getContext(), GtasksListActivity.class);

@ -11,11 +11,16 @@ import com.todoroo.andlib.service.Autowired;
import com.todoroo.andlib.utility.DialogUtilities; import com.todoroo.andlib.utility.DialogUtilities;
import com.todoroo.andlib.utility.Preferences; import com.todoroo.andlib.utility.Preferences;
import com.todoroo.astrid.activity.DraggableTaskListActivity; import com.todoroo.astrid.activity.DraggableTaskListActivity;
import com.todoroo.astrid.gtasks.sync.GtasksSyncOnSaveService;
public class GtasksListActivity extends DraggableTaskListActivity { public class GtasksListActivity extends DraggableTaskListActivity {
@Autowired private GtasksTaskListUpdater gtasksTaskListUpdater; @Autowired private GtasksTaskListUpdater gtasksTaskListUpdater;
@Autowired private GtasksSyncOnSaveService gtasksSyncOnSaveService;
@Autowired private GtasksMetadataService gtasksMetadataService;
@Override @Override
protected IntegerProperty getIndentProperty() { protected IntegerProperty getIndentProperty() {
return GtasksMetadata.INDENT; return GtasksMetadata.INDENT;
@ -42,7 +47,12 @@ public class GtasksListActivity extends DraggableTaskListActivity {
public void drop(int from, int to) { public void drop(int from, int to) {
long targetTaskId = taskAdapter.getItemId(from); long targetTaskId = taskAdapter.getItemId(from);
long destinationTaskId = taskAdapter.getItemId(to); long destinationTaskId = taskAdapter.getItemId(to);
if(to == getListView().getCount() - 1)
gtasksTaskListUpdater.moveTo(targetTaskId, -1);
else
gtasksTaskListUpdater.moveTo(targetTaskId, destinationTaskId); gtasksTaskListUpdater.moveTo(targetTaskId, destinationTaskId);
gtasksSyncOnSaveService.triggerMoveForMetadata(gtasksMetadataService.getTaskMetadata(targetTaskId));
loadTaskListContent(true); loadTaskListContent(true);
} }
}; };
@ -52,6 +62,7 @@ public class GtasksListActivity extends DraggableTaskListActivity {
public void swipeRight(int which) { public void swipeRight(int which) {
long targetTaskId = taskAdapter.getItemId(which); long targetTaskId = taskAdapter.getItemId(which);
gtasksTaskListUpdater.indent(targetTaskId, 1); gtasksTaskListUpdater.indent(targetTaskId, 1);
gtasksSyncOnSaveService.triggerMoveForMetadata(gtasksMetadataService.getTaskMetadata(targetTaskId));
loadTaskListContent(true); loadTaskListContent(true);
} }
@ -59,6 +70,7 @@ public class GtasksListActivity extends DraggableTaskListActivity {
public void swipeLeft(int which) { public void swipeLeft(int which) {
long targetTaskId = taskAdapter.getItemId(which); long targetTaskId = taskAdapter.getItemId(which);
gtasksTaskListUpdater.indent(targetTaskId, -1); gtasksTaskListUpdater.indent(targetTaskId, -1);
gtasksSyncOnSaveService.triggerMoveForMetadata(gtasksMetadataService.getTaskMetadata(targetTaskId));
loadTaskListContent(true); loadTaskListContent(true);
} }
}; };

@ -70,7 +70,6 @@ public class GtasksTaskListUpdater {
long newParent = computeNewParent(listRef.get(), taskId, indent + delta - 1); long newParent = computeNewParent(listRef.get(), taskId, indent + delta - 1);
if (newParent == taskId) { if (newParent == taskId) {
System.err.println("Tried to set parent to self");
metadata.setValue(GtasksMetadata.PARENT_TASK, Task.NO_ID); metadata.setValue(GtasksMetadata.PARENT_TASK, Task.NO_ID);
} else { } else {
metadata.setValue(GtasksMetadata.PARENT_TASK, newParent); metadata.setValue(GtasksMetadata.PARENT_TASK, newParent);
@ -131,7 +130,7 @@ public class GtasksTaskListUpdater {
private static class Node { private static class Node {
public final long taskId; public final long taskId;
public final Node parent; public Node parent;
public final ArrayList<Node> children = new ArrayList<Node>(); public final ArrayList<Node> children = new ArrayList<Node>();
public Node(long taskId, Node parent) { public Node(long taskId, Node parent) {
@ -161,12 +160,14 @@ public class GtasksTaskListUpdater {
if(moveBeforeTaskId == -1) { if(moveBeforeTaskId == -1) {
target.parent.children.remove(target); target.parent.children.remove(target);
root.children.add(target); root.children.add(target);
target.parent = root;
} else { } else {
Node sibling = findNode(root, moveBeforeTaskId); Node sibling = findNode(root, moveBeforeTaskId);
if(sibling != null) { if(sibling != null) {
int index = sibling.parent.children.indexOf(sibling); int index = sibling.parent.children.indexOf(sibling);
target.parent.children.remove(target); target.parent.children.remove(target);
sibling.parent.children.add(index, target); sibling.parent.children.add(index, target);
target.parent = sibling.parent;
} }
} }
} }
@ -224,6 +225,8 @@ public class GtasksTaskListUpdater {
Node node = currentNode.get().parent; Node node = currentNode.get().parent;
for(int i = indent; i < previousIndentValue; i++) for(int i = indent; i < previousIndentValue; i++)
node = node.parent; node = node.parent;
if(node == null)
node = root;
currentNode.set(new Node(taskId, node)); currentNode.set(new Node(taskId, node));
node.children.add(currentNode.get()); node.children.add(currentNode.get());
} }
@ -251,6 +254,15 @@ public class GtasksTaskListUpdater {
}); });
} }
public void debugPrint(Node root, int depth) {
for(int i = 0; i < depth; i++) System.err.print(" + ");
System.err.format("%03d", root.taskId);
System.err.print("\n");
for(int i = 0; i < root.children.size(); i++)
debugPrint(root.children.get(i), depth + 1);
}
private final Task taskContainer = new Task(); private final Task taskContainer = new Task();
private void saveAndUpdateModifiedDate(Metadata metadata, long taskId) { private void saveAndUpdateModifiedDate(Metadata metadata, long taskId) {

@ -4,6 +4,7 @@ import java.io.IOException;
import com.google.api.client.extensions.android2.AndroidHttp; import com.google.api.client.extensions.android2.AndroidHttp;
import com.google.api.client.googleapis.auth.oauth2.draft10.GoogleAccessProtectedResource; 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.client.json.jackson.JacksonFactory;
import com.google.api.services.tasks.v1.Tasks; import com.google.api.services.tasks.v1.Tasks;
import com.google.api.services.tasks.v1.Tasks.TasksOperations.Insert; import com.google.api.services.tasks.v1.Tasks.TasksOperations.Insert;
@ -12,6 +13,7 @@ import com.google.api.services.tasks.v1.Tasks.TasksOperations.Move;
import com.google.api.services.tasks.v1.model.Task; 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.TaskList;
import com.google.api.services.tasks.v1.model.TaskLists; 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 * Wrapper around the official Google Tasks API to simplify common operations. In the case
@ -22,28 +24,39 @@ import com.google.api.services.tasks.v1.model.TaskLists;
@SuppressWarnings("nls") @SuppressWarnings("nls")
public class GtasksService { public class GtasksService {
private Tasks service; private Tasks service;
private GoogleAccessProtectedResource accessProtectedResource;
private String token;
private static final String API_KEY = "AIzaSyCIYZTBo6haRHxmiplZsfYdagFEpaiFnAk"; // non-production API key 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 static final String AUTH_TOKEN_TYPE = "oauth2:https://www.googleapis.com/auth/tasks";
public GtasksService(String authToken) { public GtasksService(String authToken) {
try {
authenticate(authToken); authenticate(authToken);
} catch (Exception e) {
e.printStackTrace();
}
} }
public void authenticate(String authToken) throws IOException { public void authenticate(String authToken) {
this.token = authToken;
GoogleAccessProtectedResource accessProtectedResource = new GoogleAccessProtectedResource(authToken); accessProtectedResource = new GoogleAccessProtectedResource(authToken);
service = new Tasks(AndroidHttp.newCompatibleTransport(), accessProtectedResource, new JacksonFactory()); service = new Tasks(AndroidHttp.newCompatibleTransport(), accessProtectedResource, new JacksonFactory());
service.accessKey = API_KEY; service.accessKey = API_KEY;
service.setApplicationName("Astrid"); 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. * 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-- * Useful for checking if token needs revalidating or if there are network problems--
@ -59,6 +72,7 @@ public class GtasksService {
try { try {
toReturn = service.tasklists.list().execute(); toReturn = service.tasklists.list().execute();
} catch (IOException e) { } catch (IOException e) {
handleException(e);
toReturn = service.tasklists.list().execute(); toReturn = service.tasklists.list().execute();
} }
return toReturn; return toReturn;
@ -69,6 +83,7 @@ public class GtasksService {
try { try {
toReturn = service.tasklists.get(id).execute(); toReturn = service.tasklists.get(id).execute();
} catch (IOException e) { } catch (IOException e) {
handleException(e);
toReturn = service.tasklists.get(id).execute(); toReturn = service.tasklists.get(id).execute();
} }
return toReturn; return toReturn;
@ -81,6 +96,7 @@ public class GtasksService {
try { try {
toReturn = service.tasklists.insert(newList).execute(); toReturn = service.tasklists.insert(newList).execute();
} catch (IOException e) { } catch (IOException e) {
handleException(e);
toReturn = service.tasklists.insert(newList).execute(); toReturn = service.tasklists.insert(newList).execute();
} }
return toReturn; return toReturn;
@ -91,6 +107,7 @@ public class GtasksService {
try { try {
toReturn = service.tasklists.update(list.id, list).execute(); toReturn = service.tasklists.update(list.id, list).execute();
} catch (IOException e) { } catch (IOException e) {
handleException(e);
toReturn = service.tasklists.update(list.id, list).execute(); toReturn = service.tasklists.update(list.id, list).execute();
} }
return toReturn; return toReturn;
@ -100,7 +117,8 @@ public class GtasksService {
try { try {
service.tasklists.delete(listId).execute(); service.tasklists.delete(listId).execute();
} catch (IOException e) { } catch (IOException e) {
service.tasks.clear(listId).execute(); handleException(e);
service.tasklists.delete(listId).execute();
} }
} }
@ -109,6 +127,7 @@ public class GtasksService {
try { try {
toReturn = getAllGtasksFromListId(list.id, includeDeleted); toReturn = getAllGtasksFromListId(list.id, includeDeleted);
} catch (IOException e) { } catch (IOException e) {
handleException(e);
toReturn = getAllGtasksFromListId(list.id, includeDeleted); toReturn = getAllGtasksFromListId(list.id, includeDeleted);
} }
return toReturn; return toReturn;
@ -121,6 +140,7 @@ public class GtasksService {
try { try {
toReturn = request.execute(); toReturn = request.execute();
} catch (IOException e) { } catch (IOException e) {
handleException(e);
toReturn = request.execute(); toReturn = request.execute();
} }
return toReturn; return toReturn;
@ -131,6 +151,7 @@ public class GtasksService {
try { try {
toReturn = service.tasks.get(listId, taskId).execute(); toReturn = service.tasks.get(listId, taskId).execute();
} catch (IOException e) { } catch (IOException e) {
handleException(e);
toReturn = service.tasks.get(listId, taskId).execute(); toReturn = service.tasks.get(listId, taskId).execute();
} }
return toReturn; return toReturn;
@ -158,6 +179,7 @@ public class GtasksService {
try { try {
toReturn = insertOp.execute(); toReturn = insertOp.execute();
} catch (IOException e) { } catch (IOException e) {
handleException(e);
toReturn = insertOp.execute(); toReturn = insertOp.execute();
} }
return toReturn; return toReturn;
@ -168,6 +190,7 @@ public class GtasksService {
try { try {
toReturn = service.tasks.update(listId, task.id, task).execute(); toReturn = service.tasks.update(listId, task.id, task).execute();
} catch (IOException e) { } catch (IOException e) {
handleException(e);
toReturn = service.tasks.update(listId, task.id, task).execute(); toReturn = service.tasks.update(listId, task.id, task).execute();
} }
return toReturn; return toReturn;
@ -182,6 +205,7 @@ public class GtasksService {
try { try {
toReturn = move.execute(); toReturn = move.execute();
} catch (IOException e) { } catch (IOException e) {
handleException(e);
toReturn = move.execute(); toReturn = move.execute();
} }
return toReturn; return toReturn;
@ -191,6 +215,7 @@ public class GtasksService {
try { try {
service.tasks.delete(listId, taskId).execute(); service.tasks.delete(listId, taskId).execute();
} catch (IOException e) { } catch (IOException e) {
handleException(e);
service.tasks.delete(listId, taskId).execute(); service.tasks.delete(listId, taskId).execute();
} }
} }
@ -199,6 +224,7 @@ public class GtasksService {
try { try {
service.tasks.clear(listId).execute(); service.tasks.clear(listId).execute();
} catch (IOException e) { } catch (IOException e) {
handleException(e);
service.tasks.clear(listId).execute(); service.tasks.clear(listId).execute();
} }
} }

@ -1,7 +1,7 @@
package com.todoroo.astrid.gtasks.sync; package com.todoroo.astrid.gtasks.sync;
import java.io.IOException; import java.io.IOException;
import java.util.concurrent.Semaphore; import java.util.concurrent.LinkedBlockingQueue;
import android.content.ContentValues; import android.content.ContentValues;
import android.text.TextUtils; import android.text.TextUtils;
@ -42,93 +42,70 @@ public final class GtasksSyncOnSaveService {
DependencyInjectionService.getInstance().inject(this); DependencyInjectionService.getInstance().inject(this);
} }
private final Semaphore syncOnSaveSema = new Semaphore(1); private final LinkedBlockingQueue<SyncOnSaveOperation> operationQueue = new LinkedBlockingQueue<SyncOnSaveOperation>();
public void initialize() { private abstract class SyncOnSaveOperation {}
taskDao.addListener(new ModelUpdateListener<Task>() {
public void onModelUpdated(final Task model) { private class TaskPushOp extends SyncOnSaveOperation {
if (!syncOnSaveEnabled()) protected Task model;
return;
if (gtasksPreferenceService.isOngoing()) //Don't try and sync changes that occur during a normal sync public TaskPushOp(Task model) {
return; this.model = model;
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;
class MoveOp extends SyncOnSaveOperation {
protected Metadata metadata;
public MoveOp(Metadata metadata) {
this.metadata = metadata;
}
}
public void initialize() {
new Thread(new Runnable() { new Thread(new Runnable() {
@Override
public void run() { public void run() {
// sleep so metadata associated with task is saved while (true) {
AndroidUtilities.sleepDeep(1000L); SyncOnSaveOperation op;
outer:
try { try {
op = operationQueue.take();
} catch (InterruptedException e) {
continue;
}
try { try {
syncOnSaveSema.acquire(); if (syncOnSaveEnabled() && !gtasksPreferenceService.isOngoing()) {
} catch (InterruptedException ingored) { if (op instanceof TaskPushOp) {
break outer; TaskPushOp taskPush = (TaskPushOp)op;
pushTaskOnSave(taskPush.model, taskPush.model.getSetValues());
} else if (op instanceof MoveOp) {
MoveOp move = (MoveOp)op;
pushMetadataOnSave(move.metadata);
} }
pushTaskOnSave(model, setValues); }
} catch (IOException e) { } catch (IOException e){
e.printStackTrace();
System.err.println("Sync on save failed"); //$NON-NLS-1$ System.err.println("Sync on save failed"); //$NON-NLS-1$
return;
} finally {
syncOnSaveSema.release();
} }
} }
}).start();
} }
});//*/ }).start();
metadataDao.addListener(new ModelUpdateListener<Metadata>() { taskDao.addListener(new ModelUpdateListener<Task>() {
public void onModelUpdated(final Metadata model) { public void onModelUpdated(final Task model) {
if (!syncOnSaveEnabled()) if (!syncOnSaveEnabled())
return; 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 if (gtasksPreferenceService.isOngoing()) //Don't try and sync changes that occur during a normal sync
return; return;
final ContentValues setValues = model.getSetValues(); final ContentValues setValues = model.getSetValues();
if (setValues == null || !checkForToken()) if(setValues == null || !checkForToken())
return;
if (checkValuesForProperties(setValues, METADATA_IGNORE_PROPERTIES)) // don't sync the move cases we don't handle
return; return;
if (!checkValuesForProperties(setValues, METADATA_PROPERTIES)) if (!checkValuesForProperties(setValues, TASK_PROPERTIES)) //None of the properties we sync were updated
return; return;
if(Flags.checkAndClear(Flags.GTASKS_SUPPRESS_SYNC))
if (Flags.checkAndClear(Flags.GTASKS_SUPPRESS_SYNC))
return; return;
new Thread(new Runnable() { operationQueue.offer(new TaskPushOp((Task)model.clone()));
@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();
}
}); });
} }
@ -139,14 +116,6 @@ public final class GtasksSyncOnSaveService {
Task.COMPLETION_DATE, Task.COMPLETION_DATE,
Task.DELETION_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 * Checks to see if any of the values changed are among the properties we sync
* @param values * @param values
@ -162,10 +131,27 @@ public final class GtasksSyncOnSaveService {
} }
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 * Synchronize with server when data changes
*/ */
private void pushTaskOnSave(Task task, ContentValues values) throws IOException { private void pushTaskOnSave(Task task, ContentValues values) throws IOException {
AndroidUtilities.sleepDeep(1000L); //Wait for metadata to be saved
Metadata gtasksMetadata = gtasksMetadataService.getTaskMetadata(task.getId()); Metadata gtasksMetadata = gtasksMetadataService.getTaskMetadata(task.getId());
com.google.api.services.tasks.v1.model.Task remoteModel = null; com.google.api.services.tasks.v1.model.Task remoteModel = null;
boolean newlyCreated = false; boolean newlyCreated = false;
@ -276,6 +262,7 @@ public final class GtasksSyncOnSaveService {
} }
private void pushMetadataOnSave(Metadata model) throws IOException { private void pushMetadataOnSave(Metadata model) throws IOException {
AndroidUtilities.sleepDeep(1000L);
//Initialize the gtasks api service //Initialize the gtasks api service
String token = gtasksPreferenceService.getToken(); String token = gtasksPreferenceService.getToken();
token = GtasksTokenValidator.validateAuthToken(token); token = GtasksTokenValidator.validateAuthToken(token);

Loading…
Cancel
Save