Added ability to sync individual gtasks immediately on save. Properties synced--all relevant task data e.g. title, due date, parent indentation. Properties ignored--ordering in a list. Also fixed several major bugs related to normal syncing.

pull/14/head
Sam Bosley 13 years ago
parent 06330e4f45
commit 6254348933

@ -71,12 +71,14 @@ abstract public class TodorooPreferenceActivity extends PreferenceActivity {
updatePreferences(preference, value);
preference.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
public boolean onPreferenceChange(Preference myPreference, Object newValue) {
updatePreferences(myPreference, newValue);
return true;
}
});
if (preference.getOnPreferenceChangeListener() == null) {
preference.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
public boolean onPreferenceChange(Preference myPreference, Object newValue) {
updatePreferences(myPreference, newValue);
return true;
}
});
}
}
}

@ -97,8 +97,9 @@ abstract public class SyncMetadataService<TYPE extends SyncContainer> {
if(lastSyncDate == 0)
tasks = taskDao.query(Query.select(Task.ID).where(Criterion.none));
else
tasks = taskDao.query(Query.select(Task.ID).where(Task.MODIFICATION_DATE.
gt(lastSyncDate)).orderBy(Order.asc(Task.ID)));
tasks = taskDao.query(Query.select(Task.ID).where(Criterion.and(
Task.MODIFICATION_DATE.gt(lastSyncDate),
Task.LAST_SYNC.lte(lastSyncDate))).orderBy(Order.asc(Task.ID)));
return joinWithMetadata(tasks, true, properties);
}

@ -30,7 +30,7 @@ abstract public class SyncProviderUtilities {
protected static final String PREF_LAST_ATTEMPTED_SYNC = "_last_attempted"; //$NON-NLS-1$
protected static final String PREF_LAST_ERROR = "_last_error"; //$NON-NLS-1$
protected static final String PREF_LAST_ERROR = "_last_err"; //$NON-NLS-1$
protected static final String PREF_ONGOING = "_ongoing"; //$NON-NLS-1$
@ -70,7 +70,7 @@ abstract public class SyncProviderUtilities {
/** @return Last Error, or null if no last error */
public String getLastError() {
return getPrefs().getString(PREF_LAST_ERROR, null);
return getPrefs().getString(getIdentifier() + PREF_LAST_ERROR, null);
}
/** @return Last Error, or null if no last error */
@ -112,7 +112,7 @@ abstract public class SyncProviderUtilities {
Editor editor = getPrefs().edit();
editor.putLong(getIdentifier() + PREF_LAST_ATTEMPTED_SYNC,
DateUtilities.now());
editor.putString(getIdentifier() + PREF_LAST_ERROR, null);
editor.remove(getIdentifier() + PREF_LAST_ERROR);
editor.putBoolean(getIdentifier() + PREF_ONGOING, true);
editor.commit();
}

@ -91,7 +91,7 @@ public class C2DMReceiver extends BroadcastReceiver {
TagData tagData = new TagData();
if(cursor.getCount() == 0) {
tagData.setValue(Task.REMOTE_ID, Long.parseLong(intent.getStringExtra("tag_id")));
Flags.set(Flags.SUPPRESS_SYNC);
Flags.set(Flags.ACTFM_SUPPRESS_SYNC);
tagDataService.save(tagData);
} else {
cursor.moveToNext();
@ -110,7 +110,7 @@ public class C2DMReceiver extends BroadcastReceiver {
final Task task = new Task();
if(cursor.getCount() == 0) {
task.setValue(Task.REMOTE_ID, Long.parseLong(intent.getStringExtra("task_id")));
Flags.set(Flags.SUPPRESS_SYNC);
Flags.set(Flags.ACTFM_SUPPRESS_SYNC);
taskService.save(task);
} else {
cursor.moveToNext();
@ -204,7 +204,7 @@ public class C2DMReceiver extends BroadcastReceiver {
task.setValue(Task.TITLE, intent.getStringExtra("title"));
task.setValue(Task.REMOTE_ID, Long.parseLong(intent.getStringExtra("task_id")));
task.setValue(Task.USER_ID, -1L); // set it to invalid number because we don't know whose it is
Flags.set(Flags.SUPPRESS_SYNC);
Flags.set(Flags.ACTFM_SUPPRESS_SYNC);
taskService.save(task);
new Thread(new Runnable() {
@ -244,7 +244,7 @@ public class C2DMReceiver extends BroadcastReceiver {
if(cursor.getCount() == 0) {
tagData.setValue(TagData.NAME, intent.getStringExtra("title"));
tagData.setValue(TagData.REMOTE_ID, Long.parseLong(intent.getStringExtra("tag_id")));
Flags.set(Flags.SUPPRESS_SYNC);
Flags.set(Flags.ACTFM_SUPPRESS_SYNC);
tagDataService.save(tagData);
new Thread(new Runnable() {

@ -494,7 +494,7 @@ public class EditPeopleControlSet implements TaskEditControlSet {
readTagData(result.getJSONArray("tags"));
JsonHelper.readUser(result.getJSONObject("assignee"),
task, Task.USER_ID, Task.USER);
Flags.set(Flags.SUPPRESS_SYNC);
Flags.set(Flags.ACTFM_SUPPRESS_SYNC);
taskService.save(task);
int count = result.optInt("shared", 0);

@ -689,7 +689,7 @@ public class TagViewActivity extends TaskListActivity implements OnTabChangeList
try {
String url = actFmSyncService.setTagPicture(tagData.getValue(TagData.REMOTE_ID), bitmap);
tagData.setValue(TagData.PICTURE, url);
Flags.set(Flags.SUPPRESS_SYNC);
Flags.set(Flags.ACTFM_SUPPRESS_SYNC);
tagDataService.save(tagData);
} catch (IOException e) {
DialogUtilities.okDialog(TagViewActivity.this, e.toString(), null);
@ -709,7 +709,7 @@ public class TagViewActivity extends TaskListActivity implements OnTabChangeList
update.setValue(Update.USER_ID, 0L);
update.setValue(Update.TAGS, "," + tagData.getValue(TagData.REMOTE_ID) + ",");
update.setValue(Update.CREATION_DATE, DateUtilities.now());
Flags.checkAndClear(Flags.SUPPRESS_SYNC);
Flags.checkAndClear(Flags.ACTFM_SUPPRESS_SYNC);
updateDao.createNew(update);
addCommentField.setText(""); //$NON-NLS-1$

@ -88,7 +88,7 @@ public final class ActFmSyncService {
taskDao.addListener(new ModelUpdateListener<Task>() {
@Override
public void onModelUpdated(final Task model) {
if(Flags.checkAndClear(Flags.SUPPRESS_SYNC))
if(Flags.checkAndClear(Flags.ACTFM_SUPPRESS_SYNC))
return;
final ContentValues setValues = model.getSetValues();
if(setValues == null || !checkForToken() || setValues.containsKey(RemoteModel.REMOTE_ID_PROPERTY_NAME))
@ -108,7 +108,7 @@ public final class ActFmSyncService {
updateDao.addListener(new ModelUpdateListener<Update>() {
@Override
public void onModelUpdated(final Update model) {
if(Flags.checkAndClear(Flags.SUPPRESS_SYNC))
if(Flags.checkAndClear(Flags.ACTFM_SUPPRESS_SYNC))
return;
final ContentValues setValues = model.getSetValues();
if(setValues == null || !checkForToken() || model.getValue(Update.REMOTE_ID) > 0)
@ -126,7 +126,7 @@ public final class ActFmSyncService {
tagDataDao.addListener(new ModelUpdateListener<TagData>() {
@Override
public void onModelUpdated(final TagData model) {
if(Flags.checkAndClear(Flags.SUPPRESS_SYNC))
if(Flags.checkAndClear(Flags.ACTFM_SUPPRESS_SYNC))
return;
final ContentValues setValues = model.getSetValues();
if(setValues == null || !checkForToken() || setValues.containsKey(RemoteModel.REMOTE_ID_PROPERTY_NAME))
@ -265,7 +265,7 @@ public final class ActFmSyncService {
JsonHelper.taskFromJson(result, task, metadata);
task.setValue(Task.MODIFICATION_DATE, DateUtilities.now());
task.setValue(Task.LAST_SYNC, DateUtilities.now());
Flags.set(Flags.SUPPRESS_SYNC);
Flags.set(Flags.ACTFM_SUPPRESS_SYNC);
taskDao.saveExisting(task);
} catch (JSONException e) {
handleException("task-save-json", e);
@ -355,7 +355,7 @@ public final class ActFmSyncService {
JSONObject result = actFmInvoker.invoke("tag_save", params.toArray(new Object[params.size()]));
if(newlyCreated) {
tagData.setValue(TagData.REMOTE_ID, result.optLong("id"));
Flags.set(Flags.SUPPRESS_SYNC);
Flags.set(Flags.ACTFM_SUPPRESS_SYNC);
tagDataDao.saveExisting(tagData);
}
success = true;
@ -395,7 +395,7 @@ public final class ActFmSyncService {
JSONObject item = list.getJSONObject(i);
readIds(locals, item, remote);
JsonHelper.tagFromJson(item, remote);
Flags.set(Flags.SUPPRESS_SYNC);
Flags.set(Flags.ACTFM_SUPPRESS_SYNC);
tagDataService.save(remote);
}
}
@ -431,7 +431,7 @@ public final class ActFmSyncService {
"token", token);
JsonHelper.tagFromJson(result, tagData);
Flags.set(Flags.SUPPRESS_SYNC);
Flags.set(Flags.ACTFM_SUPPRESS_SYNC);
tagDataService.save(tagData);
}
@ -453,7 +453,7 @@ public final class ActFmSyncService {
ArrayList<Metadata> metadata = new ArrayList<Metadata>();
JsonHelper.taskFromJson(result, task, metadata);
Flags.set(Flags.SUPPRESS_SYNC);
Flags.set(Flags.ACTFM_SUPPRESS_SYNC);
taskService.save(task);
metadataService.synchronizeMetadata(task.getId(), metadata, Metadata.KEY.eq(TagService.KEY));
}
@ -505,7 +505,7 @@ public final class ActFmSyncService {
}
Flags.set(Flags.SUPPRESS_SYNC);
Flags.set(Flags.ACTFM_SUPPRESS_SYNC);
taskService.save(remote);
metadataService.synchronizeMetadata(remote.getId(), metadata, MetadataCriteria.withKey(TagService.KEY));
remote.clear();
@ -543,7 +543,7 @@ public final class ActFmSyncService {
readIds(locals, item, remote);
JsonHelper.updateFromJson(item, remote);
Flags.set(Flags.SUPPRESS_SYNC);
Flags.set(Flags.ACTFM_SUPPRESS_SYNC);
if(remote.getId() == AbstractModel.NO_ID)
updateDao.createNew(remote);
else
@ -578,7 +578,7 @@ public final class ActFmSyncService {
readIds(locals, item, remote);
JsonHelper.updateFromJson(item, remote);
Flags.set(Flags.SUPPRESS_SYNC);
Flags.set(Flags.ACTFM_SUPPRESS_SYNC);
if(remote.getId() == AbstractModel.NO_ID)
updateDao.createNew(remote);
else

@ -26,6 +26,7 @@ import com.todoroo.astrid.api.FilterCategory;
import com.todoroo.astrid.api.FilterListHeader;
import com.todoroo.astrid.api.FilterListItem;
import com.todoroo.astrid.api.FilterWithCustomIntent;
import com.todoroo.astrid.api.PermaSql;
import com.todoroo.astrid.dao.TaskDao.TaskCriteria;
import com.todoroo.astrid.data.Metadata;
import com.todoroo.astrid.data.MetadataApiDao.MetadataCriteria;
@ -56,13 +57,14 @@ public class GtasksFilterExposer extends BroadcastReceiver {
values.putAll(GtasksMetadata.createEmptyMetadata(AbstractModel.NO_ID).getMergedValues());
values.remove(Metadata.TASK.name);
values.put(GtasksMetadata.LIST_ID.name, list.getValue(GtasksList.REMOTE_ID));
values.put(GtasksMetadata.ORDER.name, PermaSql.VALUE_NOW);
FilterWithCustomIntent filter = new FilterWithCustomIntent(listName,
ContextManager.getString(R.string.gtasks_FEx_title, 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)))).orderBy(
Order.asc(Functions.cast(GtasksMetadata.ORDER, "INTEGER"))), //$NON-NLS-1$
Order.asc(Functions.cast(GtasksMetadata.ORDER, "LONG"))), //$NON-NLS-1$
values);
filter.listingIcon = ((BitmapDrawable)context.getResources().getDrawable(R.drawable.gtasks_icon)).getBitmap();
filter.customTaskList = new ComponentName(ContextManager.getContext(), GtasksListActivity.class);

@ -34,7 +34,7 @@ public class GtasksMetadata {
public static final IntegerProperty INDENT = new IntegerProperty(Metadata.TABLE,
Metadata.VALUE4.name);
public static final IntegerProperty ORDER = new IntegerProperty(Metadata.TABLE,
public static final LongProperty ORDER = new LongProperty(Metadata.TABLE,
Metadata.VALUE5.name);
/**
@ -52,9 +52,9 @@ public class GtasksMetadata {
defaultList = ""; //$NON-NLS-1$
metadata.setValue(LIST_ID, defaultList);
metadata.setValue(PARENT_TASK, (long)VALUE_UNSET);
metadata.setValue(PARENT_TASK, AbstractModel.NO_ID);
metadata.setValue(INDENT, 0);
metadata.setValue(ORDER, (int)(DateUtilities.now() / 1000L));
metadata.setValue(ORDER, DateUtilities.now());
if(taskId > AbstractModel.NO_ID)
metadata.setValue(Metadata.TASK, taskId);
return metadata;

@ -4,13 +4,25 @@
package com.todoroo.astrid.gtasks;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import android.text.TextUtils;
import com.todoroo.andlib.data.TodorooCursor;
import com.todoroo.andlib.service.Autowired;
import com.todoroo.andlib.service.ContextManager;
import com.todoroo.andlib.service.DependencyInjectionService;
import com.todoroo.andlib.sql.Criterion;
import com.todoroo.andlib.sql.Field;
import com.todoroo.andlib.sql.Functions;
import com.todoroo.andlib.sql.Order;
import com.todoroo.andlib.sql.Query;
import com.todoroo.astrid.core.PluginServices;
import com.todoroo.astrid.dao.MetadataDao.MetadataCriteria;
import com.todoroo.astrid.data.Metadata;
import com.todoroo.astrid.data.StoreObject;
import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.gtasks.sync.GtasksTaskContainer;
import com.todoroo.astrid.sync.SyncMetadataService;
@ -62,4 +74,94 @@ public final class GtasksMetadataService extends SyncMetadataService<GtasksTaskC
return GtasksMetadata.ID.neq(""); //$NON-NLS-1$
}
// --- list iterating helpers
public interface ListIterator {
public void processTask(long taskId, Metadata metadata);
}
public void iterateThroughList(StoreObject list, ListIterator iterator) {
String listId = list.getValue(GtasksList.REMOTE_ID);
iterateThroughList(listId, iterator, 0, false);
}
@SuppressWarnings("nls")
public void iterateThroughList(String listId, ListIterator iterator, long startAtOrder, boolean reverse) {
Field orderField = Functions.cast(GtasksMetadata.ORDER, "LONG");
Order order = reverse ? Order.desc(orderField) : Order.asc(orderField);
Criterion startAtCriterion = reverse ? Functions.cast(GtasksMetadata.ORDER, "LONG").lt(startAtOrder) :
Functions.cast(GtasksMetadata.ORDER, "LONG").gt(startAtOrder - 1);
Query query = Query.select(Metadata.PROPERTIES).where(Criterion.and(
MetadataCriteria.withKey(GtasksMetadata.METADATA_KEY),
GtasksMetadata.LIST_ID.eq(listId),
startAtCriterion)).
orderBy(order);
TodorooCursor<Metadata> cursor = PluginServices.getMetadataService().query(query);
try {
for(cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
long taskId = cursor.get(Metadata.TASK);
Metadata metadata = getTaskMetadata(taskId);
if(metadata == null)
continue;
iterator.processTask(taskId, metadata);
}
} finally {
cursor.close();
}
}
/**
* Gets the remote id string of the parent task
* @param gtasksMetadata
* @return
*/
public String getRemoteParentId(Metadata gtasksMetadata) {
String parent = null;
if (gtasksMetadata.containsNonNullValue(GtasksMetadata.PARENT_TASK)) {
long parentId = gtasksMetadata.getValue(GtasksMetadata.PARENT_TASK);
Metadata parentMetadata = getTaskMetadata(parentId);
if (parentMetadata != null && parentMetadata.containsNonNullValue(GtasksMetadata.ID)) {
parent = parentMetadata.getValue(GtasksMetadata.ID);
if (TextUtils.isEmpty(parent)) {
parent = null;
}
}
}
return parent;
}
/**
* Gets the remote id string of the previous sibling task
* @param listId
* @param gtasksMetadata
* @return
*/
public String getRemoteSiblingId(String listId, Metadata gtasksMetadata) {
final AtomicInteger indentToMatch = new AtomicInteger(gtasksMetadata.getValue(GtasksMetadata.INDENT).intValue());
final AtomicLong parentToMatch = new AtomicLong(gtasksMetadata.getValue(GtasksMetadata.PARENT_TASK).longValue());
final AtomicReference<String> sibling = new AtomicReference<String>();
ListIterator iterator = new ListIterator() {
@Override
public void processTask(long taskId, Metadata metadata) {
Task t = taskDao.fetch(taskId, Task.TITLE, Task.DELETION_DATE);
if (t.isDeleted()) return;
int currIndent = metadata.getValue(GtasksMetadata.INDENT).intValue();
long currParent = metadata.getValue(GtasksMetadata.PARENT_TASK);
if (currIndent == indentToMatch.get() && currParent == parentToMatch.get()) {
if (sibling.get() == null) {
sibling.set(metadata.getValue(GtasksMetadata.ID));
}
}
}
};
this.iterateThroughList(listId, iterator, gtasksMetadata.getValue(GtasksMetadata.ORDER), true);
return sibling.get();
}
}

@ -24,6 +24,10 @@ public class GtasksPreferenceService extends SyncProviderUtilities {
return R.string.gtasks_GPr_interval_key;
}
public int getSyncOnSaveKey() {
return R.string.gtasks_GPr_sync_on_save_key;
}
/** GTasks user's default list id */
public static final String PREF_DEFAULT_LIST = IDENTIFIER + "_defaultlist"; //$NON-NLS-1$

@ -1,6 +1,9 @@
package com.todoroo.astrid.gtasks;
import android.content.res.Resources;
import android.os.Bundle;
import android.preference.Preference;
import android.preference.Preference.OnPreferenceChangeListener;
import com.timsu.astrid.R;
import com.todoroo.andlib.service.Autowired;
@ -57,4 +60,24 @@ public class GtasksPreferences extends SyncProviderPreferences {
new GtasksBackgroundService().scheduleService();
}
@Override
public void updatePreferences(Preference preference, Object value) {
final Resources r = getResources();
if (r.getString(R.string.gtasks_GPr_sync_on_save_key).equals(preference.getKey())) {
preference.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
if (((Boolean) newValue).booleanValue()) {
startSync();
}
return true;
}
});
} else {
super.updatePreferences(preference, value);
}
}
}

@ -2,6 +2,7 @@ package com.todoroo.astrid.gtasks;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
@ -9,12 +10,9 @@ import java.util.concurrent.atomic.AtomicReference;
import android.text.TextUtils;
import android.util.Log;
import com.todoroo.andlib.data.TodorooCursor;
import com.todoroo.andlib.service.Autowired;
import com.todoroo.andlib.service.ContextManager;
import com.todoroo.andlib.service.DependencyInjectionService;
import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.astrid.api.Filter;
import com.todoroo.astrid.core.PluginServices;
import com.todoroo.astrid.data.Metadata;
import com.todoroo.astrid.data.StoreObject;
@ -57,8 +55,9 @@ public class GtasksTaskListUpdater {
final AtomicInteger targetTaskIndent = new AtomicInteger(-1);
final AtomicInteger previousIndent = new AtomicInteger(-1);
final AtomicLong previousTask = new AtomicLong(-1);
final AtomicReference<StoreObject> listRef = new AtomicReference<StoreObject>(list);
iterateThroughList(list, new ListIterator() {
gtasksMetadataService.iterateThroughList(list, new GtasksMetadataService.ListIterator() {
@Override
public void processTask(long taskId, Metadata metadata) {
int indent = metadata.getValue(GtasksMetadata.INDENT);
@ -68,13 +67,14 @@ public class GtasksTaskListUpdater {
if(indent + delta <= previousIndent.get() + 1 && indent + delta >= 0) {
targetTaskIndent.set(indent);
metadata.setValue(GtasksMetadata.INDENT, indent + delta);
if(delta > 0)
metadata.setValue(GtasksMetadata.PARENT_TASK, previousTask.get());
else if(parents.containsKey(taskId))
metadata.setValue(GtasksMetadata.PARENT_TASK,
parents.get(parents.get(taskId)));
else
long newParent = computeNewParent(listRef.get(), taskId, indent + delta - 1);
if (newParent == taskId) {
System.err.println("Tried to set parent to self");
metadata.setValue(GtasksMetadata.PARENT_TASK, Task.NO_ID);
} else {
metadata.setValue(GtasksMetadata.PARENT_TASK, newParent);
}
saveAndUpdateModifiedDate(metadata, taskId);
}
} else if(targetTaskIndent.get() > -1) {
@ -94,6 +94,39 @@ public class GtasksTaskListUpdater {
});
}
/**
* Helper function to iterate through a list and compute a new parent for the target task
* based on the target parent's indent
* @param list
* @param targetTaskId
* @param newIndent
* @return
*/
private long computeNewParent(StoreObject list, long targetTaskId, int targetParentIndent) {
final AtomicInteger desiredParentIndent = new AtomicInteger(targetParentIndent);
final AtomicLong targetTask = new AtomicLong(targetTaskId);
final AtomicLong lastPotentialParent = new AtomicLong(-1);
final AtomicBoolean computedParent = new AtomicBoolean(false);
GtasksMetadataService.ListIterator iterator = new GtasksMetadataService.ListIterator() {
@Override
public void processTask(long taskId, Metadata metadata) {
if (targetTask.get() == taskId) {
computedParent.set(true);
}
int indent = metadata.getValue(GtasksMetadata.INDENT);
if (!computedParent.get() && indent == desiredParentIndent.get()) {
lastPotentialParent.set(taskId);
}
}
};
gtasksMetadataService.iterateThroughList(list, iterator);
if (lastPotentialParent.get() == -1) return Task.NO_ID;
return lastPotentialParent.get();
}
// --- task moving
private static class Node {
@ -138,10 +171,10 @@ public class GtasksTaskListUpdater {
}
}
traverseTreeAndWriteValues(root, new AtomicInteger(0), -1);
traverseTreeAndWriteValues(root, new AtomicLong(0), -1);
}
private void traverseTreeAndWriteValues(Node node, AtomicInteger order, int indent) {
private void traverseTreeAndWriteValues(Node node, AtomicLong order, int indent) {
if(node.taskId != -1) {
Metadata metadata = gtasksMetadataService.getTaskMetadata(node.taskId);
if(metadata == null)
@ -173,7 +206,7 @@ public class GtasksTaskListUpdater {
final AtomicInteger previoustIndent = new AtomicInteger(-1);
final AtomicReference<Node> currentNode = new AtomicReference<Node>(root);
iterateThroughList(list, new ListIterator() {
gtasksMetadataService.iterateThroughList(list, new GtasksMetadataService.ListIterator() {
@Override
public void processTask(long taskId, Metadata metadata) {
int indent = metadata.getValue(GtasksMetadata.INDENT);
@ -208,7 +241,7 @@ public class GtasksTaskListUpdater {
if(list == GtasksListService.LIST_NOT_FOUND_OBJECT)
return;
iterateThroughList(list, new ListIterator() {
gtasksMetadataService.iterateThroughList(list, new GtasksMetadataService.ListIterator() {
public void processTask(long taskId, Metadata metadata) {
System.err.format("id %d: order %d, indent:%d, parent:%d\n", taskId, //$NON-NLS-1$
metadata.getValue(GtasksMetadata.ORDER),
@ -252,10 +285,10 @@ public class GtasksTaskListUpdater {
updateParentSiblingMapsFor(list);
final AtomicInteger order = new AtomicInteger(0);
final AtomicLong order = new AtomicLong(0);
final AtomicInteger previousIndent = new AtomicInteger(-1);
iterateThroughList(list, new ListIterator() {
gtasksMetadataService.iterateThroughList(list, new GtasksMetadataService.ListIterator() {
@Override
public void processTask(long taskId, Metadata metadata) {
metadata.setValue(GtasksMetadata.ORDER, order.getAndAdd(1));
@ -288,7 +321,7 @@ public class GtasksTaskListUpdater {
final AtomicLong previousTask = new AtomicLong(-1L);
final AtomicInteger previousIndent = new AtomicInteger(-1);
iterateThroughList(list, new ListIterator() {
gtasksMetadataService.iterateThroughList(list, new GtasksMetadataService.ListIterator() {
@Override
public void processTask(long taskId, Metadata metadata) {
int indent = metadata.getValue(GtasksMetadata.INDENT);
@ -349,28 +382,6 @@ public class GtasksTaskListUpdater {
localToRemoteIdMap.put(id, remoteId);
}
// --- private helpers
private interface ListIterator {
public void processTask(long taskId, Metadata metadata);
}
private void iterateThroughList(StoreObject list, ListIterator iterator) {
Filter filter = GtasksFilterExposer.filterFromList(ContextManager.getContext(), list);
TodorooCursor<Task> cursor = PluginServices.getTaskService().fetchFiltered(filter.sqlQuery, null, Task.ID);
try {
for(cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
long taskId = cursor.getLong(0);
Metadata metadata = gtasksMetadataService.getTaskMetadata(taskId);
if(metadata == null)
continue;
iterator.processTask(taskId, metadata);
}
} finally {
cursor.close();
}
}
}

@ -44,6 +44,16 @@ public class GtasksService {
service.setApplicationName("Astrid");
}
/**
* 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--
* no exception means all is well
* @throws IOException
*/
public void ping() throws IOException {
service.tasklists.get("@default").execute();
}
public TaskLists allGtaskLists() throws IOException {
TaskLists toReturn;
try {

@ -1,5 +1,7 @@
package com.todoroo.astrid.gtasks.auth;
import java.io.IOException;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AccountManagerFuture;
@ -17,25 +19,41 @@ public class GtasksTokenValidator {
/**
* Invalidates and then revalidates the auth token for the currently logged in user
* Shouldn't be called from the main thread--will block on network calls
* @param token
* @return
* @return valid token on success, null on failure
*/
public static String validateAuthToken(String token) {
Account a = accountManager.getAccountByName(Preferences.getStringValue(GtasksPreferenceService.PREF_USER_NAME));
if (a == null) return null;
accountManager.invalidateAuthToken(token);
AccountManagerFuture<Bundle> future = accountManager.manager.getAuthToken(a, GtasksService.AUTH_TOKEN_TYPE, true, null, null);
GtasksService testService = new GtasksService(token);
try {
if (future.getResult().containsKey(AccountManager.KEY_AUTHTOKEN)) {
Bundle result = future.getResult();
return result.getString(AccountManager.KEY_AUTHTOKEN);
testService.ping();
return token;
} catch (IOException i) { //If fail, token may have expired -- get a new one and return that
Account a = accountManager.getAccountByName(Preferences.getStringValue(GtasksPreferenceService.PREF_USER_NAME));
if (a == null) return null;
accountManager.invalidateAuthToken(token);
AccountManagerFuture<Bundle> future = accountManager.manager.getAuthToken(a, GtasksService.AUTH_TOKEN_TYPE, true, null, null);
try {
if (future.getResult().containsKey(AccountManager.KEY_AUTHTOKEN)) {
Bundle result = future.getResult();
token = result.getString(AccountManager.KEY_AUTHTOKEN);
testService = new GtasksService(token);
try { //Make sure the new token works--if not, we may have network problems
testService.ping();
return token;
} catch (IOException i2) {
return null;
}
}
} catch (Exception e) {
e.printStackTrace();
return null;
}
} catch (Exception e) {
e.printStackTrace();
return null;
}
return null;
}

@ -0,0 +1,326 @@
package com.todoroo.astrid.gtasks.sync;
import java.io.IOException;
import java.util.concurrent.Semaphore;
import android.content.ContentValues;
import android.text.TextUtils;
import com.todoroo.andlib.data.DatabaseDao.ModelUpdateListener;
import com.todoroo.andlib.data.Property;
import com.todoroo.andlib.service.Autowired;
import com.todoroo.andlib.service.DependencyInjectionService;
import com.todoroo.andlib.utility.AndroidUtilities;
import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.andlib.utility.Preferences;
import com.todoroo.astrid.dao.MetadataDao;
import com.todoroo.astrid.dao.TaskDao;
import com.todoroo.astrid.data.Metadata;
import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.gtasks.GtasksMetadata;
import com.todoroo.astrid.gtasks.GtasksMetadataService;
import com.todoroo.astrid.gtasks.GtasksPreferenceService;
import com.todoroo.astrid.gtasks.GtasksTaskListUpdater;
import com.todoroo.astrid.gtasks.api.GoogleTasksException;
import com.todoroo.astrid.gtasks.api.GtasksApiUtilities;
import com.todoroo.astrid.gtasks.api.GtasksService;
import com.todoroo.astrid.gtasks.api.MoveRequest;
import com.todoroo.astrid.gtasks.auth.GtasksTokenValidator;
import com.todoroo.astrid.service.MetadataService;
import com.todoroo.astrid.utility.Flags;
public final class GtasksSyncOnSaveService {
@Autowired MetadataService metadataService;
@Autowired MetadataDao metadataDao;
@Autowired GtasksMetadataService gtasksMetadataService;
@Autowired TaskDao taskDao;
@Autowired GtasksPreferenceService gtasksPreferenceService;
@Autowired GtasksTaskListUpdater gtasksTaskListUpdater;
public GtasksSyncOnSaveService() {
DependencyInjectionService.getInstance().inject(this);
}
private final Semaphore syncOnSaveSema = new Semaphore(1);
public void initialize() {
taskDao.addListener(new ModelUpdateListener<Task>() {
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<Metadata>() {
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))
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();
}
});
}
private static final Property<?>[] TASK_PROPERTIES =
{ Task.TITLE,
Task.NOTES,
Task.DUE_DATE,
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
* @param properties
* @return false if none of the properties we sync were changed, true otherwise
*/
private boolean checkValuesForProperties(ContentValues values, Property<?>[] properties) {
for (Property<?> property : properties) {
if (values.containsKey(property.name))
return true;
}
return false;
}
/**
* Synchronize with server when data changes
*/
private void pushTaskOnSave(Task task, ContentValues values) throws IOException {
Metadata gtasksMetadata = gtasksMetadataService.getTaskMetadata(task.getId());
com.google.api.services.tasks.v1.model.Task remoteModel = null;
boolean newlyCreated = false;
//Initialize the gtasks api service
String token = gtasksPreferenceService.getToken();
token = GtasksTokenValidator.validateAuthToken(token);
if (token == null) {
throw new GoogleTasksException("Failed to establish connection for sync on save"); //$NON-NLS-1$
}
gtasksPreferenceService.setToken(token);
GtasksService gtasksService = new GtasksService(token);
String remoteId = null;
String listId = Preferences.getStringValue(GtasksPreferenceService.PREF_DEFAULT_LIST);
if (listId == null) {
listId = gtasksService.getGtaskList("@default").id; //$NON-NLS-1$
Preferences.setString(GtasksPreferenceService.PREF_DEFAULT_LIST, listId);
}
if (gtasksMetadata == null || !gtasksMetadata.containsNonNullValue(GtasksMetadata.ID) ||
TextUtils.isEmpty(gtasksMetadata.getValue(GtasksMetadata.ID))) { //Create case
if (gtasksMetadata == null) {
gtasksMetadata = GtasksMetadata.createEmptyMetadata(task.getId());
}
if (gtasksMetadata.containsNonNullValue(GtasksMetadata.LIST_ID)) {
listId = gtasksMetadata.getValue(GtasksMetadata.LIST_ID);
}
remoteModel = new com.google.api.services.tasks.v1.model.Task();
newlyCreated = true;
} else { //update case
remoteId = gtasksMetadata.getValue(GtasksMetadata.ID);
listId = gtasksMetadata.getValue(GtasksMetadata.LIST_ID);
remoteModel = gtasksService.getGtask(listId, remoteId);
}
//If task was newly created but without a title, don't sync--we're in the middle of
//creating a task which may end up being cancelled
if (newlyCreated &&
(!values.containsKey(Task.TITLE.name) || TextUtils.isEmpty(task.getValue(Task.TITLE)))) {
return;
}
//Update the remote model's changed properties
if (values.containsKey(Task.DELETION_DATE.name) && task.isDeleted()) {
remoteModel.deleted = true;
}
if (values.containsKey(Task.TITLE.name)) {
remoteModel.title = task.getValue(Task.TITLE);
}
if (values.containsKey(Task.NOTES.name)) {
remoteModel.notes = task.getValue(Task.NOTES);
}
if (values.containsKey(Task.DUE_DATE.name)) {
remoteModel.due = GtasksApiUtilities.unixTimeToGtasksTime(task.getValue(Task.DUE_DATE));
}
if (values.containsKey(Task.COMPLETION_DATE.name)) {
if (task.isCompleted()) {
remoteModel.completed = GtasksApiUtilities.unixTimeToGtasksTime(task.getValue(Task.COMPLETION_DATE));
remoteModel.status = "completed"; //$NON-NLS-1$
} else {
remoteModel.completed = null;
remoteModel.status = "needsAction"; //$NON-NLS-1$
}
}
if (!newlyCreated) {
gtasksService.updateGtask(listId, remoteModel);
} else {
String parent = gtasksMetadataService.getRemoteParentId(gtasksMetadata);
String priorSibling = gtasksMetadataService.getRemoteSiblingId(listId, gtasksMetadata);
try { //Make sure the parent task exists on the target list
if (parent != null) {
com.google.api.services.tasks.v1.model.Task remoteParent = gtasksService.getGtask(listId, parent);
if (remoteParent == null || (remoteParent.deleted != null && remoteParent.deleted))
parent = null;
}
} catch (IOException e) {
parent = null;
}
try {
if (priorSibling != null) {
com.google.api.services.tasks.v1.model.Task remoteSibling = gtasksService.getGtask(listId, priorSibling);
if (remoteSibling == null || (remoteSibling.deleted != null && remoteSibling.deleted))
priorSibling = null;
}
} catch (IOException e) {
priorSibling = null;
}
com.google.api.services.tasks.v1.model.Task created = gtasksService.createGtask(listId, remoteModel, parent, priorSibling);
//Update the metadata for the newly created task
gtasksMetadata.setValue(GtasksMetadata.ID, created.id);
gtasksMetadata.setValue(GtasksMetadata.LIST_ID, listId);
metadataService.save(gtasksMetadata);
}
task.setValue(Task.MODIFICATION_DATE, DateUtilities.now());
task.setValue(Task.LAST_SYNC, DateUtilities.now());
Flags.set(Flags.GTASKS_SUPPRESS_SYNC);
taskDao.saveExisting(task);
}
private void pushMetadataOnSave(Metadata model) throws IOException {
//Initialize the gtasks api service
String token = gtasksPreferenceService.getToken();
token = GtasksTokenValidator.validateAuthToken(token);
if (token == null) {
throw new GoogleTasksException("Failed to establish connection for sync on save"); //$NON-NLS-1$
}
gtasksPreferenceService.setToken(token);
GtasksService gtasksService = new GtasksService(token);
String taskId = model.getValue(GtasksMetadata.ID);
String listId = model.getValue(GtasksMetadata.LIST_ID);
String parent = gtasksMetadataService.getRemoteParentId(model);
String priorSibling = gtasksMetadataService.getRemoteSiblingId(listId, model);
try { //Make sure the parent task exists on the target list
if (parent != null) {
com.google.api.services.tasks.v1.model.Task remoteParent = gtasksService.getGtask(listId, parent);
if (remoteParent == null || (remoteParent.deleted != null && remoteParent.deleted))
parent = null;
}
} catch (IOException e) {
parent = null;
}
try {
if (priorSibling != null) {
com.google.api.services.tasks.v1.model.Task remoteSibling = gtasksService.getGtask(listId, priorSibling);
if (remoteSibling == null || (remoteSibling.deleted != null && remoteSibling.deleted))
priorSibling = null;
}
} catch (IOException e) {
priorSibling = null;
}
MoveRequest move = new MoveRequest(gtasksService, taskId, listId, parent, priorSibling);
move.executePush();
}
private boolean syncOnSaveEnabled() {
return Preferences.getBoolean(gtasksPreferenceService.getSyncOnSaveKey(), false);
}
private boolean checkForToken() {
if (!gtasksPreferenceService.isLoggedIn())
return false;
return true;
}
}

@ -5,6 +5,8 @@ package com.todoroo.astrid.gtasks.sync;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
@ -80,7 +82,7 @@ public class GtasksSyncProvider extends SyncProvider<GtasksTaskContainer> {
/** tasks to read id for */
ArrayList<GtasksTaskContainer> createdWithoutId;
ArrayList<GtasksTaskContainer> createdWithoutParent;
ArrayList<GtasksTaskContainer> createdWithoutOrder;
Semaphore pushedTaskSemaphore = new Semaphore(0);
AtomicInteger pushedTaskCount = new AtomicInteger(0);
@ -161,7 +163,7 @@ public class GtasksSyncProvider extends SyncProvider<GtasksTaskContainer> {
if(Constants.DEBUG)
Log.e("gtasks-debug", "- -------- SYNC STARTED");
createdWithoutId = new ArrayList<GtasksTaskContainer>();
createdWithoutParent = new ArrayList<GtasksTaskContainer>();
createdWithoutOrder = new ArrayList<GtasksTaskContainer>();
try {
TaskLists allTaskLists = taskService.allGtaskLists();
@ -213,7 +215,7 @@ public class GtasksSyncProvider extends SyncProvider<GtasksTaskContainer> {
} else if (Preferences.getStringValue(GtasksPreferenceService.PREF_DEFAULT_LIST) != null) {
listId = Preferences.getStringValue(GtasksPreferenceService.PREF_DEFAULT_LIST);
} else {
listId = "@default";
listId = taskService.getGtaskList("@default").id;
}
Preferences.setString(GtasksPreferenceService.PREF_DEFAULT_LIST, listId);
@ -233,13 +235,11 @@ public class GtasksSyncProvider extends SyncProvider<GtasksTaskContainer> {
// match remote tasks to locally created tasks
HashMap<String, GtasksTaskContainer> locals = new HashMap<String, GtasksTaskContainer>();
HashMap<Long, String> localIdsToRemoteIds = new HashMap<Long, String>();
for(GtasksTaskContainer task : createdWithoutId) {
locals.put(task.gtaskMetadata.getValue(GtasksMetadata.ID), task);
localIdsToRemoteIds.put(task.task.getId(), task.gtaskMetadata.getValue(GtasksMetadata.ID));
}
verifyCreatedOrder(localIdsToRemoteIds);
verifyCreatedOrder();
// first, pull all tasks. then we can write them
// include deleted tasks so we can delete them in astrid
@ -259,16 +259,27 @@ public class GtasksSyncProvider extends SyncProvider<GtasksTaskContainer> {
super.readRemotelyUpdated(data);
}
private void verifyCreatedOrder(HashMap<Long, String> localIdsToRemoteIds) throws IOException {
for (GtasksTaskContainer t : createdWithoutParent) {
private void verifyCreatedOrder() throws IOException {
Collections.sort(createdWithoutOrder, new Comparator<GtasksTaskContainer>() {
@Override
public int compare(GtasksTaskContainer arg0, GtasksTaskContainer arg1) {
long order0 = arg0.gtaskMetadata.getValue(GtasksMetadata.ORDER);
long order1 = arg1.gtaskMetadata.getValue(GtasksMetadata.ORDER);
if (order0 == order1) return 0;
if (order0 < order1) return -1;
else return 1;
}
});
for (GtasksTaskContainer t : createdWithoutOrder) {
String toMove = t.gtaskMetadata.getValue(GtasksMetadata.ID);
String listId = t.gtaskMetadata.getValue(GtasksMetadata.LIST_ID);
long parentTask = t.gtaskMetadata.getValue(GtasksMetadata.PARENT_TASK);
if (parentTask > 0) {
String remoteParent = localIdsToRemoteIds.get(parentTask);
MoveRequest move = new MoveRequest(taskService, toMove, listId, remoteParent, null);
move.executePush();
}
String remoteParent = gtasksMetadataService.getRemoteParentId(t.gtaskMetadata);
String remoteSibling = gtasksMetadataService.getRemoteSiblingId(listId, t.gtaskMetadata);
MoveRequest move = new MoveRequest(taskService, toMove, listId, remoteParent, remoteSibling);
move.executePush();
}
}
@ -303,7 +314,7 @@ public class GtasksSyncProvider extends SyncProvider<GtasksTaskContainer> {
// ----------------------------------------------------------------------
private ArrayList<GtasksTaskContainer> readAllRemoteTasks(final boolean includeDeleted) {
final ArrayList<GtasksTaskContainer> remoteTasks = new ArrayList<GtasksTaskContainer>();
final ArrayList<GtasksTaskContainer> list = new ArrayList<GtasksTaskContainer>();
final Semaphore listsFinished = new Semaphore(0);
// launch threads
@ -316,8 +327,8 @@ public class GtasksSyncProvider extends SyncProvider<GtasksTaskContainer> {
String listId = dashboard.getValue(GtasksList.REMOTE_ID);
if(Constants.DEBUG)
Log.e("gtasks-debug", "ACTION: getTasks, " + listId);
List<com.google.api.services.tasks.v1.model.Task> list = taskService.getAllGtasksFromListId(listId, includeDeleted).items;
addRemoteTasksToList(list, remoteTasks);
List<com.google.api.services.tasks.v1.model.Task> remoteTasks = taskService.getAllGtasksFromListId(listId, includeDeleted).items;
addRemoteTasksToList(remoteTasks, list);
} catch (Exception e) {
handleException("read-remotes", e, false);
} finally {
@ -332,14 +343,14 @@ public class GtasksSyncProvider extends SyncProvider<GtasksTaskContainer> {
} catch (InterruptedException e) {
handleException("wait-for-remotes", e, false);
}
return remoteTasks;
return list;
}
private void addRemoteTasksToList(List<com.google.api.services.tasks.v1.model.Task> remoteTasks,
ArrayList<GtasksTaskContainer> list) {
if (remoteTasks != null) {
int order = 0;
long order = 0;
HashMap<String, com.google.api.services.tasks.v1.model.Task> idsToTasks = new HashMap<String, com.google.api.services.tasks.v1.model.Task>();
HashMap<String, Integer> indentation = new HashMap<String, Integer>();
HashMap<String, String> parentToPriorSiblingMap = new HashMap<String, String>();
@ -385,6 +396,7 @@ public class GtasksSyncProvider extends SyncProvider<GtasksTaskContainer> {
private int findIndentation(HashMap<String, com.google.api.services.tasks.v1.model.Task> idsToTasks,
HashMap<String, Integer> indentation, com.google.api.services.tasks.v1.model.Task task) {
if (task == null) return 0; //TODO: Not sure why this is happening...
if(indentation.containsKey(task.id))
return indentation.get(task.id);
@ -404,13 +416,17 @@ public class GtasksSyncProvider extends SyncProvider<GtasksTaskContainer> {
local.gtaskMetadata.setValue(GtasksMetadata.LIST_ID, listId);
createdWithoutId.add(local);
if (local.gtaskMetadata.containsNonNullValue(GtasksMetadata.PARENT_TASK)) {
createdWithoutParent.add(local);
}
createdWithoutOrder.add(local);
com.google.api.services.tasks.v1.model.Task createdTask = new com.google.api.services.tasks.v1.model.Task();
CreateRequest createRequest = new CreateRequest(taskService, listId, createdTask, local.parentId, local.priorSiblingId);
updateTaskHelper(local, null, createRequest);
//updateTaskHelper(local, null, createRequest);
localPropertiesToModel(local, null, createRequest.getToPush());
com.google.api.services.tasks.v1.model.Task createResult = createRequest.executePush();
String newIdTask = createResult.id;
local.gtaskMetadata.setValue(GtasksMetadata.ID, newIdTask);
return local;
}//*/
@ -456,12 +472,13 @@ public class GtasksSyncProvider extends SyncProvider<GtasksTaskContainer> {
public void run() {
String newIdTask = idTask;
try {
if (request instanceof CreateRequest) {
/*if (request instanceof CreateRequest) { //This is commented out until bugs with multithreaded task creation are resolved
com.google.api.services.tasks.v1.model.Task createResult = request.executePush();
newIdTask = createResult.id;
System.err.println("Created " + createResult.title + " successfully, remote id: " + createResult.id);
local.gtaskMetadata.setValue(GtasksMetadata.ID, newIdTask);
}
if(!TextUtils.isEmpty(newIdTask) && (remote == null || local.parentId != remote.parentId ||
}//*/
if(!TextUtils.isEmpty(newIdTask) && remote != null && (local.parentId != remote.parentId ||
local.priorSiblingId != remote.priorSiblingId)) {
if(Constants.DEBUG)
Log.e("gtasks-debug", "ACTION: move(1) - " + newIdTask + ", " + local.parentId + ", " + local.priorSiblingId);

@ -255,7 +255,7 @@ public class EditNoteActivity extends ListActivity {
update.setValue(Update.USER_ID, 0L);
update.setValue(Update.TASK, task.getValue(Task.REMOTE_ID));
update.setValue(Update.CREATION_DATE, DateUtilities.now());
Flags.checkAndClear(Flags.SUPPRESS_SYNC);
Flags.checkAndClear(Flags.ACTFM_SUPPRESS_SYNC);
updateDao.createNew(update);
commentField.setText(""); //$NON-NLS-1$

@ -273,6 +273,9 @@
<string name="gtasks_GPr_interval_key">gtasks_sync_freq</string>
<string name="gtasks_GPr_sync_on_save_key">gtasks_sync_on_save</string>
<!-- ========================================================== SHARING == -->
<string name="actfm_APr_interval_key">actfm_sync_freq</string>

@ -70,6 +70,10 @@
<!-- GTasks Preferences Title -->
<string name="gtasks_GPr_header">Google Tasks (Beta!)</string>
<string name="gtasks_GPr_sync_on_save">Sync on Save</string>
<string name="gtasks_GPr_sync_on_save_summary">Sync individual tasks as they are saved</string>
<!-- ================================================ Synchronization == -->
<!-- title for notification tray when synchronizing -->

@ -22,6 +22,11 @@
android:entryValues="@array/sync_SPr_interval_values"
android:title="@string/sync_SPr_interval_title" />
<CheckBoxPreference
android:key="@string/gtasks_GPr_sync_on_save_key"
android:title="@string/gtasks_GPr_sync_on_save"
android:summary="@string/gtasks_GPr_sync_on_save_summary" />
</PreferenceCategory>
<PreferenceCategory

@ -221,6 +221,8 @@ public class TaskListActivity extends ListActivity implements OnScrollListener,
setUpUiComponents();
onNewIntent(getIntent());
AndroidUtilities.copyDatabases(this, "/sdcard/astrid");
Eula.showEula(this);
if(getIntent().hasExtra(TOKEN_SOURCE)) {

@ -22,6 +22,7 @@ import com.todoroo.astrid.gtasks.GtasksListService;
import com.todoroo.astrid.gtasks.GtasksMetadataService;
import com.todoroo.astrid.gtasks.GtasksPreferenceService;
import com.todoroo.astrid.gtasks.GtasksTaskListUpdater;
import com.todoroo.astrid.gtasks.sync.GtasksSyncOnSaveService;
import com.todoroo.astrid.tags.TagService;
import com.todoroo.astrid.utility.Constants;
@ -88,6 +89,7 @@ public class AstridDependencyInjector extends AbstractDependencyInjector {
injectables.put("gtasksListService", GtasksListService.class);
injectables.put("gtasksMetadataService", GtasksMetadataService.class);
injectables.put("gtasksTaskListUpdater", GtasksTaskListUpdater.class);
injectables.put("gtasksSyncOnSaveService", GtasksSyncOnSaveService.class);
// com.todoroo.astrid.tags
injectables.put("tagService", TagService.class);

@ -38,6 +38,7 @@ import com.todoroo.astrid.backup.TasksXmlImporter;
import com.todoroo.astrid.dao.Database;
import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.gtasks.GtasksPreferenceService;
import com.todoroo.astrid.gtasks.sync.GtasksSyncOnSaveService;
import com.todoroo.astrid.opencrx.OpencrxCoreUtils;
import com.todoroo.astrid.producteev.ProducteevUtilities;
import com.todoroo.astrid.reminders.ReminderStartupReceiver;
@ -79,6 +80,8 @@ public class StartupService {
@Autowired ActFmPreferenceService actFmPreferenceService;
@Autowired GtasksSyncOnSaveService gtasksSyncOnSaveService;
/**
* bit to prevent multiple initializations
*/
@ -171,6 +174,8 @@ public class StartupService {
BackupService.scheduleService(context);
actFmSyncService.initialize();
gtasksSyncOnSaveService.initialize();
// get and display update messages
new UpdateMessageService().processUpdates(context);
}

@ -22,15 +22,21 @@ public class Flags {
public static final int TOAST_ON_SAVE = 1 << 2;
/**
* If set, indicates sync service sync this change
* If set, indicates to suppress the next act.fm sync attempt
*/
public static final int SUPPRESS_SYNC = 1 << 3;
public static final int ACTFM_SUPPRESS_SYNC = 1 << 3;
/**
* If set, indicates save hooks should be suppressed
*/
public static final int SUPPRESS_HOOKS = 1 << 4;
/**
* If set, indicates to suppress the next gtasks sync attempt
*/
public static final int GTASKS_SUPPRESS_SYNC = 1 << 5;
public static boolean checkAndClear(int flag) {
boolean set = (state & flag) > 0;
state &= ~flag;

@ -139,7 +139,7 @@ public class GtasksIndentActionTest extends DatabaseTestCase {
gtasksListService.updateLists(lists);
}
private Task taskWithMetadata(int order, int indentation) {
private Task taskWithMetadata(long order, int indentation) {
Task task = new Task();
PluginServices.getTaskService().save(task);
Metadata metadata = GtasksMetadata.createEmptyMetadata(task.getId());

@ -36,7 +36,7 @@ public class GtasksNewSyncTest extends DatabaseTestCase {
private static boolean initialized = false;
private static String DEFAULT_LIST = "@default";
private static final String TEST_ACCOUNT = "sync_tester@astrid.com";
private static final String TEST_ACCOUNT = "sync_tester2@astrid.com";
private static final long TIME_BETWEEN_SYNCS = 3000l;
@Autowired TaskService taskService;
@ -497,6 +497,7 @@ public class GtasksNewSyncTest extends DatabaseTestCase {
}
String authToken = authTokenBundle.getString(AccountManager.KEY_AUTHTOKEN);
authToken = GtasksTokenValidator.validateAuthToken(authToken);
gtasksPreferenceService.setToken(authToken);
gtasksService = new GtasksService(authToken);

@ -0,0 +1,208 @@
package com.todoroo.astrid.gtasks;
import java.io.IOException;
import java.util.Date;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AccountManagerFuture;
import android.content.Intent;
import android.os.Bundle;
import com.google.api.client.googleapis.extensions.android2.auth.GoogleAccountManager;
import com.google.api.services.tasks.v1.model.Tasks;
import com.timsu.astrid.R;
import com.todoroo.andlib.service.Autowired;
import com.todoroo.andlib.service.ContextManager;
import com.todoroo.andlib.utility.AndroidUtilities;
import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.andlib.utility.Preferences;
import com.todoroo.astrid.data.Metadata;
import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.gtasks.api.GtasksApiUtilities;
import com.todoroo.astrid.gtasks.api.GtasksService;
import com.todoroo.astrid.gtasks.auth.GtasksTokenValidator;
import com.todoroo.astrid.gtasks.sync.GtasksSyncOnSaveService;
import com.todoroo.astrid.service.TaskService;
import com.todoroo.astrid.test.DatabaseTestCase;
public class GtasksSyncOnSaveTest extends DatabaseTestCase {
@Autowired TaskService taskService;
@Autowired GtasksSyncOnSaveService gtasksSyncOnSaveService;
@Autowired GtasksMetadataService gtasksMetadataService;
@Autowired GtasksPreferenceService gtasksPreferenceService;
private GtasksService gtasksService;
private boolean initialized = false;
private static final String TEST_ACCOUNT = "sync_tester2@astrid.com";
private static String DEFAULT_LIST = "@default";
//Have to wait a long time because sync on save happens in another thread--currently no way to know when finished
private static final long TIME_TO_WAIT = 8000L;
public void testSyncOnCreate() throws IOException {
performBasicCreation("");
}
private Task performBasicCreation(String appendToTitle) throws IOException {
String title = "Created task" + appendToTitle;
Task localTask = setupLocalTaskModel(title);
taskService.save(localTask);
AndroidUtilities.sleepDeep(TIME_TO_WAIT);
com.google.api.services.tasks.v1.model.Task remoteTask = getRemoteTaskForLocalId(localTask.getId());
assertEquals(title, remoteTask.title);
return localTask;
}
private Task setupLocalTaskModel(String title) {
Task localTask = new Task();
localTask.setValue(Task.TITLE, title);
return localTask;
}
private com.google.api.services.tasks.v1.model.Task getRemoteTaskForLocalId(long localId) throws IOException {
Metadata gtasksMetadata = gtasksMetadataService.getTaskMetadata(localId);
assertNotNull(gtasksMetadata);
com.google.api.services.tasks.v1.model.Task remoteTask = gtasksService.getGtask(DEFAULT_LIST, gtasksMetadata.getValue(GtasksMetadata.ID));
assertNotNull(remoteTask);
return remoteTask;
}
public void testSyncOnTitleUpdate() throws IOException {
Task localTask = performBasicCreation("-title will change");
String newTitle = "Title has changed!";
localTask.setValue(Task.TITLE, newTitle);
taskService.save(localTask);
AndroidUtilities.sleepDeep(TIME_TO_WAIT);
com.google.api.services.tasks.v1.model.Task remoteTask = getRemoteTaskForLocalId(localTask.getId());
assertEquals(newTitle, remoteTask.title);
}
public void testSyncOnDueDateUpdate() throws IOException {
Task localTask = performBasicCreation("-due date will change");
long dueDate = new Date(115, 2, 14).getTime();
localTask.setValue(Task.DUE_DATE, dueDate);
taskService.save(localTask);
AndroidUtilities.sleepDeep(TIME_TO_WAIT);
com.google.api.services.tasks.v1.model.Task remoteTask = getRemoteTaskForLocalId(localTask.getId());
assertEquals(dueDate, GtasksApiUtilities.gtasksDueTimeToUnixTime(remoteTask.due, 0));
}
public void testSyncOnNotesUpdate() throws IOException {
Task localTask = performBasicCreation("-notes will change");
String notes = "Noted!";
localTask.setValue(Task.NOTES, notes);
taskService.save(localTask);
AndroidUtilities.sleepDeep(TIME_TO_WAIT);
com.google.api.services.tasks.v1.model.Task remoteTask = getRemoteTaskForLocalId(localTask.getId());
assertEquals(notes, remoteTask.notes);
}
public void testSyncOnCompleted() throws IOException {
Task localTask = performBasicCreation("-will be completed");
long completionDate = DateUtilities.now();
localTask.setValue(Task.COMPLETION_DATE, completionDate);
taskService.save(localTask);
AndroidUtilities.sleepDeep(TIME_TO_WAIT);
com.google.api.services.tasks.v1.model.Task remoteTask = getRemoteTaskForLocalId(localTask.getId());
assertEquals("completed", remoteTask.status);
assertEquals(completionDate, GtasksApiUtilities.gtasksCompletedTimeToUnixTime(remoteTask.completed, 0));
}
public void testSyncOnDeleted() throws IOException {
Task localTask = performBasicCreation("-will be deleted");
long deletionDate = DateUtilities.now();
localTask.setValue(Task.DELETION_DATE, deletionDate);
taskService.save(localTask);
AndroidUtilities.sleepDeep(TIME_TO_WAIT);
com.google.api.services.tasks.v1.model.Task remoteTask = getRemoteTaskForLocalId(localTask.getId());
assertTrue(remoteTask.deleted);
assertFalse(taskWithTitleExists(localTask.getValue(Task.TITLE)));
}
private boolean taskWithTitleExists(String title) throws IOException {
Tasks allTasks = gtasksService.getAllGtasksFromListId(DEFAULT_LIST, false);
if (allTasks.items != null) {
for (com.google.api.services.tasks.v1.model.Task t : allTasks.items) {
if (t.title.equals(title))
return true;
}
}
return false;
}
@Override
protected void setUp() throws Exception {
super.setUp();
if (!initialized) {
initializeTestService();
gtasksSyncOnSaveService.initialize();
initialized = true;
Preferences.setBoolean(R.string.gtasks_GPr_sync_on_save_key, true);
}
setupTestList();
}
private void initializeTestService() throws Exception {
GoogleAccountManager manager = new GoogleAccountManager(ContextManager.getContext());
Account[] accounts = manager.getAccounts();
Account toUse = null;
for (Account a : accounts) {
if (a.name.equals(TEST_ACCOUNT)) {
toUse = a;
break;
}
}
if (toUse == null) {
toUse = accounts[0];
}
Preferences.setString(GtasksPreferenceService.PREF_USER_NAME, toUse.name);
AccountManagerFuture<Bundle> accountManagerFuture = manager.manager.getAuthToken(toUse, "oauth2:https://www.googleapis.com/auth/tasks", true, null, null);
Bundle authTokenBundle = accountManagerFuture.getResult();
if (authTokenBundle.containsKey(AccountManager.KEY_INTENT)) {
Intent i = (Intent) authTokenBundle.get(AccountManager.KEY_INTENT);
ContextManager.getContext().startActivity(i);
return;
}
String authToken = authTokenBundle.getString(AccountManager.KEY_AUTHTOKEN);
authToken = GtasksTokenValidator.validateAuthToken(authToken);
gtasksPreferenceService.setToken(authToken);
gtasksService = new GtasksService(authToken);
initialized = true;
}
private void setupTestList() throws Exception {
Tasks defaultListTasks = gtasksService.getAllGtasksFromListId(DEFAULT_LIST, false);
if (defaultListTasks.items != null) {
for (com.google.api.services.tasks.v1.model.Task t : defaultListTasks.items) {
gtasksService.deleteGtask(DEFAULT_LIST, t.id);
}
}
}
}

@ -79,10 +79,10 @@ public class GtasksTaskListUpdaterTest extends DatabaseTestCase {
// --- helpers
private void thenExpectMetadataIndentAndOrder(Task task, int order, int indent) {
private void thenExpectMetadataIndentAndOrder(Task task, long order, int indent) {
Metadata metadata = gtasksMetadataService.getTaskMetadata(task.getId());
assertNotNull("metadata was found", metadata);
assertEquals("order", order, (int)metadata.getValue(GtasksMetadata.ORDER));
assertEquals("order", order, metadata.getValue(GtasksMetadata.ORDER).longValue());
assertEquals("indentation", indent, (int)metadata.getValue(GtasksMetadata.INDENT));
}
@ -156,7 +156,7 @@ public class GtasksTaskListUpdaterTest extends DatabaseTestCase {
}
private Task createTask(String title, int order, int indent) {
private Task createTask(String title, long order, int indent) {
Task task = new Task();
task.setValue(Task.TITLE, title);
PluginServices.getTaskService().save(task);

@ -223,10 +223,10 @@ public class GtasksTaskMovingTest extends DatabaseTestCase {
gtasksTaskListUpdater.debugPrint("1");
}
private void thenExpectMetadataOrderAndIndent(Task task, int order, int indent) {
private void thenExpectMetadataOrderAndIndent(Task task, long order, int indent) {
Metadata metadata = gtasksMetadataService.getTaskMetadata(task.getId());
assertNotNull("metadata was found", metadata);
assertEquals("order", order, (int)metadata.getValue(GtasksMetadata.ORDER));
assertEquals("order", order, metadata.getValue(GtasksMetadata.ORDER).longValue());
assertEquals("indentation", indent, (int)metadata.getValue(GtasksMetadata.INDENT));
}
@ -265,7 +265,7 @@ public class GtasksTaskMovingTest extends DatabaseTestCase {
return tasks;
}
private Task createTask(String title, int order, int indent) {
private Task createTask(String title, long order, int indent) {
Task task = new Task();
task.setValue(Task.TITLE, title);
PluginServices.getTaskService().save(task);

Loading…
Cancel
Save