Merge pull request #142 from sbosley/120217_sb_duplicate_issues

Fixes for lots of sync issues, hopefully resolving duplicates
pull/14/head
Tim Su 14 years ago
commit 34fde08ba4

@ -10,6 +10,7 @@ import java.util.ArrayList;
import android.content.ContentValues; import android.content.ContentValues;
import android.content.Context; import android.content.Context;
import android.database.Cursor; import android.database.Cursor;
import android.database.sqlite.SQLiteConstraintException;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase.CursorFactory; import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteOpenHelper;
@ -230,7 +231,15 @@ abstract public class AbstractDatabase {
* @see android.database.sqlite.SQLiteDatabase#insert(String table, String nullColumnHack, ContentValues values) * @see android.database.sqlite.SQLiteDatabase#insert(String table, String nullColumnHack, ContentValues values)
*/ */
public synchronized long insert(String table, String nullColumnHack, ContentValues values) { public synchronized long insert(String table, String nullColumnHack, ContentValues values) {
long result = getDatabase().insert(table, nullColumnHack, values); long result = -1;
try {
getDatabase().insertOrThrow(table, nullColumnHack, values);
} catch (SQLiteConstraintException e) { // Throw these exceptions
throw e;
} catch (Exception e) { // Suppress others
Log.e("SQLiteDatabase", "Error inserting " + values, e);
result = -1;
}
onDatabaseUpdated(); onDatabaseUpdated();
return result; return result;
} }

@ -96,7 +96,10 @@ public class TodorooCursor<TYPE extends AbstractModel> extends CursorWrapper {
} }
public Object visitLong(Property<Long> property, TodorooCursor<?> cursor) { public Object visitLong(Property<Long> property, TodorooCursor<?> cursor) {
return cursor.getLong(cursor.getColumnIndexFromCache(property.name)); int column = columnIndex(property, cursor);
if(cursor.isNull(column))
return null;
return cursor.getLong(column);
} }
public Object visitString(Property<String> property, public Object visitString(Property<String> property,
@ -104,6 +107,9 @@ public class TodorooCursor<TYPE extends AbstractModel> extends CursorWrapper {
return cursor.getString(cursor.getColumnIndexFromCache(property.name)); return cursor.getString(cursor.getColumnIndexFromCache(property.name));
} }
private int columnIndex(Property<Long> property, TodorooCursor<?> cursor) {
return cursor.getColumnIndexFromCache(property.name);
}
} }
} }

@ -252,7 +252,7 @@ public final class Task extends RemoteModel {
defaultValues.put(DETAILS_DATE.name, 0); defaultValues.put(DETAILS_DATE.name, 0);
defaultValues.put(LAST_SYNC.name, 0); defaultValues.put(LAST_SYNC.name, 0);
defaultValues.put(REMOTE_ID.name, 0); defaultValues.putNull(REMOTE_ID.name);
defaultValues.put(USER_ID.name, 0); defaultValues.put(USER_ID.name, 0);
defaultValues.put(CREATOR_ID.name, 0); defaultValues.put(CREATOR_ID.name, 0);
defaultValues.put(USER.name, "{}"); defaultValues.put(USER.name, "{}");

@ -3,7 +3,7 @@
<classpathentry excluding="javax/xml/validation/|javax/xml/transform/dom/|javax/xml/parsers/|org/jaxp/transform/dom/|org/jaxp/transform/sax/|org/jaxp/transform/stax/|org/jaxp/transform/stream/|org/jaxp/stream/events/|org/jaxp/stream/util/|org/jaxp/parsers/|org/jaxp/stream/|org/jaxp/validation/" kind="src" path="src"/> <classpathentry excluding="javax/xml/validation/|javax/xml/transform/dom/|javax/xml/parsers/|org/jaxp/transform/dom/|org/jaxp/transform/sax/|org/jaxp/transform/stax/|org/jaxp/transform/stream/|org/jaxp/stream/events/|org/jaxp/stream/util/|org/jaxp/parsers/|org/jaxp/stream/|org/jaxp/validation/" kind="src" path="src"/>
<classpathentry kind="src" path="src-legacy"/> <classpathentry kind="src" path="src-legacy"/>
<classpathentry kind="src" path="common-src"/> <classpathentry kind="src" path="common-src"/>
<classpathentry excluding="com/todoroo/astrid/rmilk/EditOperationExposer.java|com/todoroo/astrid/rmilk/MilkEditActivity.java|com/todoroo/astrid/actfm/TaskFields.java|com/todoroo/astrid/actfm/ShowProjectExposer.java|com/todoroo/astrid/actfm/ProjectDetailExposer.java|com/todoroo/astrid/actfm/ProjectListActivity.java" kind="src" path="plugin-src"/> <classpathentry excluding="com/todoroo/astrid/rmilk/EditOperationExposer.java|com/todoroo/astrid/rmilk/MilkEditActivity.java|com/todoroo/astrid/actfm/TaskFields.java|com/todoroo/astrid/actfm/ShowProjectExposer.java|com/todoroo/astrid/actfm/ProjectDetailExposer.java|com/todoroo/astrid/actfm/ProjectListActivity.java|com/todoroo/astrid/actfm/sync/ActFmSyncProvider.java" kind="src" path="plugin-src"/>
<classpathentry kind="src" path="gen"/> <classpathentry kind="src" path="gen"/>
<classpathentry kind="src" path="rmilk-src"/> <classpathentry kind="src" path="rmilk-src"/>
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/> <classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>

@ -132,7 +132,7 @@ public class C2DMReceiver extends BroadcastReceiver {
try { try {
TagData tagData = new TagData(); TagData tagData = new TagData();
if(cursor.getCount() == 0) { if(cursor.getCount() == 0) {
tagData.setValue(Task.REMOTE_ID, Long.parseLong(intent.getStringExtra("tag_id"))); tagData.setValue(TagData.REMOTE_ID, Long.parseLong(intent.getStringExtra("tag_id")));
Flags.set(Flags.ACTFM_SUPPRESS_SYNC); Flags.set(Flags.ACTFM_SUPPRESS_SYNC);
tagDataService.save(tagData); tagDataService.save(tagData);
} else { } else {

@ -695,9 +695,12 @@ public class EditPeopleControlSet extends PopupControlSet {
@SuppressWarnings("nls") @SuppressWarnings("nls")
protected Object[] buildSharingArgs(JSONArray emails) throws JSONException { protected Object[] buildSharingArgs(JSONArray emails) throws JSONException {
ArrayList<Object> values = new ArrayList<Object>(); ArrayList<Object> values = new ArrayList<Object>();
long currentTaskID = task.getValue(Task.REMOTE_ID);
values.add("id"); if(task.containsNonNullValue(Task.REMOTE_ID)) {
values.add(currentTaskID); long currentTaskID = task.getValue(Task.REMOTE_ID);
values.add("id");
values.add(currentTaskID);
}
if(emails != null) { if(emails != null) {
for(int i = 0; i < emails.length(); i++) { for(int i = 0; i < emails.length(); i++) {

@ -41,7 +41,6 @@ import com.todoroo.astrid.activity.TaskListActivity;
import com.todoroo.astrid.adapter.UpdateAdapter; import com.todoroo.astrid.adapter.UpdateAdapter;
import com.todoroo.astrid.dao.UpdateDao; import com.todoroo.astrid.dao.UpdateDao;
import com.todoroo.astrid.data.TagData; import com.todoroo.astrid.data.TagData;
import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.data.Update; import com.todoroo.astrid.data.Update;
import com.todoroo.astrid.helper.ImageDiskCache; import com.todoroo.astrid.helper.ImageDiskCache;
import com.todoroo.astrid.helper.ProgressBarSyncResultCallback; import com.todoroo.astrid.helper.ProgressBarSyncResultCallback;
@ -205,7 +204,7 @@ public class TagUpdatesFragment extends ListFragment {
} }
public void setLastViewed() { public void setLastViewed() {
if(tagData != null && tagData.getValue(Task.REMOTE_ID) > 0) { if(tagData != null && tagData.getValue(TagData.REMOTE_ID) > 0) {
Preferences.setLong(UPDATES_LAST_VIEWED + tagData.getValue(TagData.REMOTE_ID), DateUtilities.now()); Preferences.setLong(UPDATES_LAST_VIEWED + tagData.getValue(TagData.REMOTE_ID), DateUtilities.now());
Activity activity = getActivity(); Activity activity = getActivity();
if (activity instanceof TaskListActivity) if (activity instanceof TaskListActivity)

@ -64,7 +64,7 @@ public final class ActFmDataService {
*/ */
public void clearMetadata() { public void clearMetadata() {
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
values.put(Task.REMOTE_ID.name, 0); values.putNull(Task.REMOTE_ID.name);
taskDao.updateMultiple(values, Criterion.all); taskDao.updateMultiple(values, Criterion.all);
} }
@ -75,7 +75,7 @@ public final class ActFmDataService {
*/ */
public TodorooCursor<Task> getLocallyCreated(Property<?>[] properties) { public TodorooCursor<Task> getLocallyCreated(Property<?>[] properties) {
return taskDao.query(Query.select(properties).where(Criterion.and(TaskCriteria.isActive(), return taskDao.query(Query.select(properties).where(Criterion.and(TaskCriteria.isActive(),
Task.REMOTE_ID.eq(0)))); Task.REMOTE_ID.isNull())));
} }
/** /**
@ -89,7 +89,7 @@ public final class ActFmDataService {
return taskDao.query(Query.select(properties).where(Criterion.none)); return taskDao.query(Query.select(properties).where(Criterion.none));
return return
taskDao.query(Query.select(properties). taskDao.query(Query.select(properties).
where(Criterion.and(Task.REMOTE_ID.gt(0), where(Criterion.and(Task.REMOTE_ID.isNotNull(),
Task.MODIFICATION_DATE.gt(lastSyncDate), Task.MODIFICATION_DATE.gt(lastSyncDate),
Task.MODIFICATION_DATE.gt(Task.LAST_SYNC))).groupBy(Task.ID)); Task.MODIFICATION_DATE.gt(Task.LAST_SYNC))).groupBy(Task.ID));
} }

@ -1,363 +0,0 @@
/**
* See the file "LICENSE" for the full license governing this code.
*/
package com.todoroo.astrid.actfm.sync;
import java.io.IOException;
import java.util.ArrayList;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.app.Activity;
import android.app.Notification;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.text.TextUtils;
import com.timsu.astrid.C2DMReceiver;
import com.timsu.astrid.R;
import com.todoroo.andlib.data.TodorooCursor;
import com.todoroo.andlib.service.Autowired;
import com.todoroo.andlib.service.ContextManager;
import com.todoroo.andlib.sql.Criterion;
import com.todoroo.andlib.sql.Query;
import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.andlib.utility.Preferences;
import com.todoroo.astrid.actfm.ActFmBackgroundService;
import com.todoroo.astrid.actfm.ActFmLoginActivity;
import com.todoroo.astrid.actfm.ActFmPreferences;
import com.todoroo.astrid.actfm.sync.ActFmSyncService.JsonHelper;
import com.todoroo.astrid.api.AstridApiConstants;
import com.todoroo.astrid.core.PluginServices;
import com.todoroo.astrid.dao.TagDataDao;
import com.todoroo.astrid.dao.TaskDao;
import com.todoroo.astrid.data.Metadata;
import com.todoroo.astrid.data.TagData;
import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.notes.NoteMetadata;
import com.todoroo.astrid.service.AstridDependencyInjector;
import com.todoroo.astrid.service.StatisticsConstants;
import com.todoroo.astrid.service.StatisticsService;
import com.todoroo.astrid.sync.SyncProvider;
import com.todoroo.astrid.sync.SyncProviderUtilities;
import com.todoroo.astrid.utility.Constants;
@SuppressWarnings("nls")
public class ActFmSyncProvider extends SyncProvider<ActFmTaskContainer> {
private ActFmInvoker invoker = null;
@Autowired ActFmDataService actFmDataService;
@Autowired ActFmSyncService actFmSyncService;
@Autowired ActFmPreferenceService actFmPreferenceService;
@Autowired TagDataDao tagDataDao;
static {
AstridDependencyInjector.initialize();
}
// ----------------------------------------------------------------------
// ------------------------------------------------------ utility methods
// ----------------------------------------------------------------------
@Override
protected SyncProviderUtilities getUtilities() {
return actFmPreferenceService;
}
/**
* Sign out of service, deleting all synchronization metadata
*/
public void signOut() {
actFmPreferenceService.setToken(null);
actFmPreferenceService.clearLastSyncDate();
C2DMReceiver.unregister();
}
// ----------------------------------------------------------------------
// ------------------------------------------------------ initiating sync
// ----------------------------------------------------------------------
/**
* initiate sync in background
*/
@Override
protected void initiateBackground() {
try {
C2DMReceiver.register();
String authToken = actFmPreferenceService.getToken();
invoker = new ActFmInvoker(authToken);
// check if we have a token & it works
if(authToken != null) {
performSync();
}
} catch (IllegalStateException e) {
// occurs when application was closed
} catch (Exception e) {
handleException("actfm-authenticate", e, false);
} finally {
actFmPreferenceService.stopOngoing();
}
}
/**
* If user isn't already signed in, show sign in dialog. Else perform sync.
*/
@Override
protected void initiateManual(Activity activity) {
String authToken = actFmPreferenceService.getToken();
actFmPreferenceService.stopOngoing();
// check if we have a token & it works
if(authToken == null) {
// display login-activity
Intent intent = new Intent(activity, ActFmLoginActivity.class);
activity.startActivityForResult(intent, 0);
} else {
activity.startService(new Intent(null, null,
activity, ActFmBackgroundService.class));
}
}
// ----------------------------------------------------------------------
// ----------------------------------------------------- synchronization!
// ----------------------------------------------------------------------
protected void performSync() {
actFmPreferenceService.recordSyncStart();
String syncSuccess = "failed";
try {
int serverTime = Preferences.getInt(ActFmPreferenceService.PREF_SERVER_TIME, 0);
ArrayList<ActFmTaskContainer> remoteTasks = new ArrayList<ActFmTaskContainer>();
// int newServerTime = fetchRemoteTasks(serverTime, remoteTasks);
if (serverTime == 0) { // If we've never synced, we may lose some empty tags
pushUnsavedTagData();
}
// fetchRemoteTagData(serverTime);
/* SyncData<ActFmTaskContainer> syncData = populateSyncData(remoteTasks);
try {
synchronizeTasks(syncData);
} finally {
syncData.localCreated.close();
syncData.localUpdated.close();
}
Preferences.setInt(ActFmPreferenceService.PREF_SERVER_TIME, newServerTime); */
actFmPreferenceService.recordSuccessfulSync();
syncSuccess = getFinalSyncStatus();
Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_EVENT_REFRESH);
ContextManager.getContext().sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ);
} catch (IllegalStateException e) {
// occurs when application was closed
} catch (Exception e) {
handleException("actfm-sync", e, false); //$NON-NLS-1$
} finally {
StatisticsService.reportEvent(StatisticsConstants.ACTFM_SYNC_FINISHED,
"success", syncSuccess); //$NON-NLS-1$
}
}
//Pushes unsaved, empty tag data, which will otherwise be deleted by the fetch tags call
private void pushUnsavedTagData() {
TodorooCursor<TagData> unsavedTagData = tagDataDao.query(Query.select(TagData.ID, TagData.TASK_COUNT).where(Criterion.and(TagData.TASK_COUNT.eq(0), TagData.REMOTE_ID.eq(0))));
TagData data = new TagData();
for (unsavedTagData.moveToFirst(); !unsavedTagData.isAfterLast(); unsavedTagData.moveToNext()) {
data.readFromCursor(unsavedTagData);
actFmSyncService.pushTag(data.getId());
}
}
/**
* Read remote tag data and merge with local
* @param serverTime last sync time
*/
private void fetchRemoteTagData(int serverTime) throws ActFmServiceException, IOException, JSONException {
actFmSyncService.fetchTags(serverTime);
}
/**
* Read remote task data into remote task array
* @param serverTime last sync time
*/
private int fetchRemoteTasks(int serverTime,
ArrayList<ActFmTaskContainer> remoteTasks) throws IOException,
ActFmServiceException, JSONException {
JSONObject result;
if(serverTime == 0)
result = invoker.invoke("task_list", "active", 1);
else
result = invoker.invoke("task_list", "modified_after", serverTime);
JSONArray taskList = result.getJSONArray("list");
for(int i = 0; i < taskList.length(); i++) {
ActFmTaskContainer remote = parseRemoteTask(taskList.getJSONObject(i));
// update reminder flags for incoming remote tasks to prevent annoying
if(remote.task.hasDueDate() && remote.task.getValue(Task.DUE_DATE) < DateUtilities.now())
remote.task.setFlag(Task.REMINDER_FLAGS, Task.NOTIFY_AFTER_DEADLINE, false);
actFmDataService.findLocalMatch(remote);
remoteTasks.add(remote);
}
return result.optInt("time", 0);
}
// ----------------------------------------------------------------------
// ------------------------------------------------------------ sync data
// ----------------------------------------------------------------------
/**
* Populate SyncData data structure
* @throws JSONException
*/
private SyncData<ActFmTaskContainer> populateSyncData(ArrayList<ActFmTaskContainer> remoteTasks) throws JSONException {
// fetch locally created tasks
TodorooCursor<Task> localCreated = actFmDataService.getLocallyCreated(Task.PROPERTIES);
// fetch locally updated tasks
TodorooCursor<Task> localUpdated = actFmDataService.getLocallyUpdated(Task.PROPERTIES);
return new SyncData<ActFmTaskContainer>(remoteTasks, localCreated, localUpdated);
}
// ----------------------------------------------------------------------
// ------------------------------------------------- create / push / pull
// ----------------------------------------------------------------------
@Override
protected ActFmTaskContainer create(ActFmTaskContainer local) throws IOException {
return push(local, null);
}
/** Create a task container for the given remote task
* @throws JSONException */
private ActFmTaskContainer parseRemoteTask(JSONObject remoteTask) throws JSONException {
Task task = new Task();
ArrayList<Metadata> metadata = new ArrayList<Metadata>();
JsonHelper.taskFromJson(remoteTask, task, metadata);
ActFmTaskContainer container = new ActFmTaskContainer(task, metadata, remoteTask);
return container;
}
@Override
protected ActFmTaskContainer pull(ActFmTaskContainer task) throws IOException {
if(task.task.getValue(Task.REMOTE_ID) == 0)
throw new ActFmServiceException("Tried to read an invalid task"); //$NON-NLS-1$
JSONObject remote = invoker.invoke("task_show", "id", task.task.getValue(Task.REMOTE_ID));
try {
return parseRemoteTask(remote);
} catch (JSONException e) {
throw new ActFmServiceException(e);
}
}
/**
* Send changes for the given Task across the wire.
*/
@Override
protected ActFmTaskContainer push(ActFmTaskContainer local, ActFmTaskContainer remote) throws IOException {
long id = local.task.getValue(Task.REMOTE_ID);
actFmSyncService.pushTaskOnSave(local.task, local.task.getDatabaseValues());
// push unsaved comments
for(Metadata item : local.metadata) {
if(NoteMetadata.METADATA_KEY.equals(item.getValue(Metadata.KEY)))
if(TextUtils.isEmpty(item.getValue(NoteMetadata.EXT_ID))) {
JSONObject comment = invoker.invoke("comment_add",
"task_id", id,
"message", item.getValue(NoteMetadata.BODY));
item.setValue(NoteMetadata.EXT_ID, comment.optString("id"));
}
}
return local;
}
@Override
protected void readRemotelyUpdated(SyncData<ActFmTaskContainer> data) throws IOException {
int serverTime = Preferences.getInt(ActFmPreferenceService.PREF_SERVER_TIME, 0);
ArrayList<ActFmTaskContainer> remoteTasks = new ArrayList<ActFmTaskContainer>();
try {
fetchRemoteTasks(serverTime, remoteTasks);
data.remoteUpdated = remoteTasks;
} catch (JSONException e) {
// Ingnored
}
super.readRemotelyUpdated(data);
}
// ----------------------------------------------------------------------
// --------------------------------------------------------- read / write
// ----------------------------------------------------------------------
@Override
protected ActFmTaskContainer read(TodorooCursor<Task> cursor) throws IOException {
return actFmDataService.readTaskAndMetadata(cursor);
}
@Override
protected void write(ActFmTaskContainer task) throws IOException {
if(task.task.isSaved()) {
Task local = PluginServices.getTaskService().fetchById(task.task.getId(), Task.COMPLETION_DATE);
if(task.task.isCompleted() && !local.isCompleted())
StatisticsService.reportEvent(StatisticsConstants.ACTFM_TASK_COMPLETED);
} else { // Set default reminders for remotely created tasks
TaskDao.setDefaultReminders(task.task);
}
task.task.setValue(Task.LAST_SYNC, DateUtilities.now() + 1000);
actFmDataService.saveTaskAndMetadata(task);
}
// ----------------------------------------------------------------------
// --------------------------------------------------------- misc helpers
// ----------------------------------------------------------------------
@Override
protected int matchTask(ArrayList<ActFmTaskContainer> tasks, ActFmTaskContainer target) {
int length = tasks.size();
for(int i = 0; i < length; i++) {
ActFmTaskContainer task = tasks.get(i);
if (task.task.getValue(Task.REMOTE_ID) == target.task.getValue(Task.REMOTE_ID))
return i;
}
return -1;
}
@Override
protected int updateNotification(Context context, Notification notification) {
String notificationTitle = context.getString(R.string.actfm_notification_title);
Intent intent = new Intent(context, ActFmPreferences.class);
PendingIntent notificationIntent = PendingIntent.getActivity(context, 0,
intent, 0);
notification.setLatestEventInfo(context,
notificationTitle, context.getString(R.string.SyP_progress),
notificationIntent);
return Constants.NOTIFICATION_SYNC;
}
@Override
protected void transferIdentifiers(ActFmTaskContainer source,
ActFmTaskContainer destination) {
destination.task.setValue(Task.REMOTE_ID, source.task.getValue(Task.REMOTE_ID));
}
}

@ -298,13 +298,14 @@ public final class ActFmSyncService {
public void pushTaskOnSave(Task task, ContentValues values) { public void pushTaskOnSave(Task task, ContentValues values) {
Task taskForRemote = taskService.fetchById(task.getId(), Task.REMOTE_ID, Task.CREATION_DATE); Task taskForRemote = taskService.fetchById(task.getId(), Task.REMOTE_ID, Task.CREATION_DATE);
long remoteId; long remoteId = 0;
if(task.containsValue(Task.REMOTE_ID)) { if(task.containsNonNullValue(Task.REMOTE_ID)) {
remoteId = task.getValue(Task.REMOTE_ID); remoteId = task.getValue(Task.REMOTE_ID);
} else { } else {
if(taskForRemote == null) if(taskForRemote == null)
return; return;
remoteId = taskForRemote.getValue(Task.REMOTE_ID); if(taskForRemote.containsNonNullValue(Task.REMOTE_ID))
remoteId = taskForRemote.getValue(Task.REMOTE_ID);
} }
long creationDate; long creationDate;
@ -627,6 +628,12 @@ public final class ActFmSyncService {
Order.asc(TagData.REMOTE_ID))); Order.asc(TagData.REMOTE_ID)));
return cursorToMap(cursor, taskDao, TagData.REMOTE_ID, TagData.ID); return cursorToMap(cursor, taskDao, TagData.REMOTE_ID, TagData.ID);
} }
@Override
protected Class<TagData> typeClass() {
return TagData.class;
}
}, done, "goals"); }, done, "goals");
} }
@ -747,6 +754,11 @@ public final class ActFmSyncService {
Order.asc(Task.REMOTE_ID))); Order.asc(Task.REMOTE_ID)));
return cursorToMap(cursor, taskDao, Task.REMOTE_ID, Task.ID); return cursorToMap(cursor, taskDao, Task.REMOTE_ID, Task.ID);
} }
@Override
protected Class<Task> typeClass() {
return Task.class;
}
}, done, "active_tasks"); }, done, "active_tasks");
} }
@ -796,6 +808,11 @@ public final class ActFmSyncService {
Order.asc(Task.REMOTE_ID))); Order.asc(Task.REMOTE_ID)));
return cursorToMap(cursor, taskDao, Task.REMOTE_ID, Task.ID); return cursorToMap(cursor, taskDao, Task.REMOTE_ID, Task.ID);
} }
@Override
protected Class<Task> typeClass() {
return Task.class;
}
}, done, "tasks:" + tagData.getId(), "tag_id", tagData.getValue(TagData.REMOTE_ID)); }, done, "tasks:" + tagData.getId(), "tag_id", tagData.getValue(TagData.REMOTE_ID));
} }
@ -860,22 +877,15 @@ public final class ActFmSyncService {
TodorooCursor<Update> cursor = updateDao.query(Query.select(Update.ID, Update.PICTURE).where(criterion)); TodorooCursor<Update> cursor = updateDao.query(Query.select(Update.ID, Update.PICTURE).where(criterion));
pushQueuedUpdates(cursor); pushQueuedUpdates(cursor);
Log.d("ActFmSyncService", "Push queued updates for tag");
} }
private void pushQueuedUpdates(Task task) { private void pushQueuedUpdates(Task task) {
Criterion criterion = null; Criterion criterion = null;
if (task.getValue(Task.REMOTE_ID) < 1) { if (task.containsNonNullValue(Task.REMOTE_ID)) {
criterion = Criterion.and(Update.REMOTE_ID.eq(0),
Update.TASK_LOCAL.eq(task.getId()));
}
else {
criterion = Criterion.and(Update.REMOTE_ID.eq(0), criterion = Criterion.and(Update.REMOTE_ID.eq(0),
Criterion.or(Update.TASK.eq(task.getValue(Task.REMOTE_ID)), Update.TASK_LOCAL.eq(task.getId()))); Criterion.or(Update.TASK.eq(task.getValue(Task.REMOTE_ID)), Update.TASK_LOCAL.eq(task.getId())));
} } else
return;
Update template = new Update(); Update template = new Update();
template.setValue(Update.TASK, task.getValue(Task.REMOTE_ID)); //$NON-NLS-1$ template.setValue(Update.TASK, task.getValue(Task.REMOTE_ID)); //$NON-NLS-1$
@ -883,8 +893,6 @@ public final class ActFmSyncService {
TodorooCursor<Update> cursor = updateDao.query(Query.select(Update.ID, Update.PICTURE).where(criterion)); TodorooCursor<Update> cursor = updateDao.query(Query.select(Update.ID, Update.PICTURE).where(criterion));
pushQueuedUpdates(cursor); pushQueuedUpdates(cursor);
Log.d("ActFmSyncService", "Push queued updates for task");
} }
private void pushQueuedUpdates( TodorooCursor<Update> cursor) { private void pushQueuedUpdates( TodorooCursor<Update> cursor) {
@ -896,18 +904,15 @@ public final class ActFmSyncService {
final Update update = new Update(cursor); final Update update = new Update(cursor);
new Thread(new Runnable() { new Thread(new Runnable() {
public void run() { public void run() {
try { Bitmap picture = null;
Bitmap picture = null; if(imageCache != null && imageCache.contains(update.getValue(Update.PICTURE))) {
if(imageCache != null && imageCache.contains(update.getValue(update.PICTURE))) { try {
try { picture = imageCache.get(update.getValue(Update.PICTURE));
picture = imageCache.get(update.getValue(update.PICTURE)); } catch (IOException e) {
} catch (IOException e) { e.printStackTrace();
e.printStackTrace();
}
} }
pushUpdate(update.getId(), picture);
} finally {
} }
pushUpdate(update.getId(), picture);
} }
}).start(); }).start();
} }
@ -916,11 +921,6 @@ public final class ActFmSyncService {
} }
} }
private class UpdateListItemProcessor extends ListItemProcessor<Update> { private class UpdateListItemProcessor extends ListItemProcessor<Update> {
@Override @Override
protected void mergeAndSave(JSONArray list, HashMap<Long,Long> locals) throws JSONException { protected void mergeAndSave(JSONArray list, HashMap<Long,Long> locals) throws JSONException {
@ -946,6 +946,11 @@ public final class ActFmSyncService {
Order.asc(Update.REMOTE_ID))); Order.asc(Update.REMOTE_ID)));
return cursorToMap(cursor, updateDao, Update.REMOTE_ID, Update.ID); return cursorToMap(cursor, updateDao, Update.REMOTE_ID, Update.ID);
} }
@Override
protected Class<Update> typeClass() {
return Update.class;
}
} }
/** /**
@ -999,16 +1004,19 @@ public final class ActFmSyncService {
abstract protected HashMap<Long, Long> getLocalModels(); abstract protected HashMap<Long, Long> getLocalModels();
abstract protected Class<TYPE> typeClass();
abstract protected void mergeAndSave(JSONArray list, abstract protected void mergeAndSave(JSONArray list,
HashMap<Long,Long> locals) throws JSONException; HashMap<Long,Long> locals) throws JSONException;
public void process(JSONArray list) throws JSONException { public void process(JSONArray list) throws JSONException {
readRemoteIds(list); readRemoteIds(list);
HashMap<Long, Long> locals = getLocalModels(); synchronized (typeClass()) {
mergeAndSave(list, locals); HashMap<Long, Long> locals = getLocalModels();
mergeAndSave(list, locals);
}
} }
protected void readRemoteIds(JSONArray list) throws JSONException { protected void readRemoteIds(JSONArray list) throws JSONException {
remoteIds = new Long[list.length()]; remoteIds = new Long[list.length()];
for(int i = 0; i < list.length(); i++) for(int i = 0; i < list.length(); i++)

@ -213,8 +213,8 @@ public class ActFmSyncV2Provider extends SyncV2Provider {
TodorooCursor<Task> taskCursor = taskService.query(Query.select(Task.PROPERTIES). TodorooCursor<Task> taskCursor = taskService.query(Query.select(Task.PROPERTIES).
where(Criterion.or( where(Criterion.or(
Criterion.and(TaskCriteria.isActive(), Criterion.and(TaskCriteria.isActive(),
Task.REMOTE_ID.eq(0)), Task.REMOTE_ID.isNull()),
Criterion.and(Task.REMOTE_ID.gt(0), Criterion.and(Task.REMOTE_ID.isNotNull(),
Task.MODIFICATION_DATE.gt(Task.LAST_SYNC))))); Task.MODIFICATION_DATE.gt(Task.LAST_SYNC)))));
pushQueued(callback, finisher, taskCursor, false, taskPusher); pushQueued(callback, finisher, taskCursor, false, taskPusher);

@ -77,7 +77,7 @@ public class GtasksListService {
} }
@SuppressWarnings("nls") @SuppressWarnings("nls")
public void updateLists(TaskLists remoteLists) { public synchronized void updateLists(TaskLists remoteLists) {
readLists(); readLists();
for(StoreObject list : lists) for(StoreObject list : lists)

@ -121,7 +121,6 @@ public class EditNoteActivity extends LinearLayout implements TimerActionListene
} }
public void loadViewForTaskID(long t){ public void loadViewForTaskID(long t){
task = PluginServices.getTaskService().fetchById(t, Task.NOTES, Task.ID, Task.REMOTE_ID, Task.TITLE); task = PluginServices.getTaskService().fetchById(t, Task.NOTES, Task.ID, Task.REMOTE_ID, Task.TITLE);
if(task == null) { if(task == null) {
return; return;
@ -130,7 +129,7 @@ public class EditNoteActivity extends LinearLayout implements TimerActionListene
setUpListAdapter(); setUpListAdapter();
if(actFmPreferenceService.isLoggedIn()) { if(actFmPreferenceService.isLoggedIn()) {
if(task.getValue(Task.REMOTE_ID) == 0) if(!task.containsNonNullValue(Task.REMOTE_ID))
refreshData(true, null); refreshData(true, null);
else { else {
String fetchKey = LAST_FETCH_KEY + task.getId(); String fetchKey = LAST_FETCH_KEY + task.getId();
@ -257,7 +256,7 @@ public class EditNoteActivity extends LinearLayout implements TimerActionListene
TodorooCursor<Update> updates; TodorooCursor<Update> updates;
if (task.getValue(Task.REMOTE_ID) < 1) { if (!task.containsNonNullValue(Task.REMOTE_ID)) {
updates = updateDao.query(Query.select(Update.PROPERTIES).where(Update.TASK_LOCAL.eq(task.getId()))); updates = updateDao.query(Query.select(Update.PROPERTIES).where(Update.TASK_LOCAL.eq(task.getId())));
} }
else { else {
@ -406,7 +405,7 @@ public class EditNoteActivity extends LinearLayout implements TimerActionListene
} }
// push task if it hasn't been pushed // push task if it hasn't been pushed
if(task.getValue(Task.REMOTE_ID) == 0 && !TextUtils.isEmpty(task.getValue(Task.TITLE))) { if(!task.containsNonNullValue(Task.REMOTE_ID) && !TextUtils.isEmpty(task.getValue(Task.TITLE))) {
new Thread(new Runnable() { new Thread(new Runnable() {
@Override @Override
public void run() { public void run() {
@ -446,7 +445,8 @@ public class EditNoteActivity extends LinearLayout implements TimerActionListene
update.setValue(Update.MESSAGE, message); update.setValue(Update.MESSAGE, message);
update.setValue(Update.ACTION_CODE, actionCode); update.setValue(Update.ACTION_CODE, actionCode);
update.setValue(Update.USER_ID, 0L); update.setValue(Update.USER_ID, 0L);
update.setValue(Update.TASK, task.getValue(Task.REMOTE_ID)); if(task.containsNonNullValue(Task.REMOTE_ID))
update.setValue(Update.TASK, task.getValue(Task.REMOTE_ID));
update.setValue(Update.TASK_LOCAL, task.getId()); update.setValue(Update.TASK_LOCAL, task.getId());
update.setValue(Update.CREATION_DATE, DateUtilities.now()); update.setValue(Update.CREATION_DATE, DateUtilities.now());

@ -721,10 +721,9 @@ ViewPager.OnPageChangeListener, EditNoteActivity.UpdatesChangedListener {
if (idParam > -1L) { if (idParam > -1L) {
model = taskService.fetchById(idParam, Task.PROPERTIES); model = taskService.fetchById(idParam, Task.PROPERTIES);
if (model != null) { if (model != null && model.containsNonNullValue(Task.REMOTE_ID)) {
remoteId = model.getValue(Task.REMOTE_ID); remoteId = model.getValue(Task.REMOTE_ID);
model.clearValue(Task.REMOTE_ID); // Having this can screw up model.clearValue(Task.REMOTE_ID); // Having this can screw up autosync
// autosync
} }
} }

@ -37,7 +37,7 @@ public class Database extends AbstractDatabase {
* Database version number. This variable must be updated when database * Database version number. This variable must be updated when database
* tables are updated, as it determines whether a database needs updating. * tables are updated, as it determines whether a database needs updating.
*/ */
public static final int VERSION = 20; public static final int VERSION = 21;
/** /**
* Database name (must be unique) * Database name (must be unique)
@ -130,19 +130,26 @@ public class Database extends AbstractDatabase {
database.execSQL(sql.toString()); database.execSQL(sql.toString());
sql.setLength(0); sql.setLength(0);
sql.append("CREATE INDEX IF NOT EXISTS up_tid ON "). sql.append("CREATE INDEX IF NOT EXISTS up_tkid ON ").
append(Update.TABLE).append('('). append(Update.TABLE).append('(').
append(Update.TASK_LOCAL.name). append(Update.TASK_LOCAL.name).
append(')'); append(')');
database.execSQL(sql.toString()); database.execSQL(sql.toString());
sql.setLength(0); sql.setLength(0);
sql.append("CREATE INDEX IF NOT EXISTS up_pid ON "). sql.append("CREATE INDEX IF NOT EXISTS up_tgl ON ").
append(Update.TABLE).append('('). append(Update.TABLE).append('(').
append(Update.TAGS_LOCAL.name). append(Update.TAGS_LOCAL.name).
append(')'); append(')');
database.execSQL(sql.toString()); database.execSQL(sql.toString());
sql.setLength(0); sql.setLength(0);
sql.append("CREATE UNIQUE INDEX IF NOT EXISTS t_rid ON ").
append(Task.TABLE).append('(').
append(Task.REMOTE_ID.name).
append(')');
database.execSQL(sql.toString());
sql.setLength(0);
} }
@Override @Override
@ -263,6 +270,26 @@ public class Database extends AbstractDatabase {
database.execSQL("CREATE INDEX IF NOT EXISTS up_tid ON " + database.execSQL("CREATE INDEX IF NOT EXISTS up_tid ON " +
Update.TABLE + "(" + Update.TAGS_LOCAL.name + ")"); Update.TABLE + "(" + Update.TAGS_LOCAL.name + ")");
} catch (SQLiteException e) {
Log.e("astrid", "db-upgrade-" + oldVersion + "-" + newVersion, e);
}
case 20: try {
String tasks = Task.TABLE.name;
String id = Task.ID.name;
String remoteId = Task.REMOTE_ID.name;
// Delete any items that have duplicate remote ids
String deleteDuplicates = String.format("DELETE FROM %s WHERE %s IN (SELECT %s.%s FROM %s, %s AS t2 WHERE %s.%s < t2.%s AND %s.%s = t2.%s AND %s.%s > 0 GROUP BY %s.%s)",
tasks, id, tasks, id, tasks, tasks, tasks, id, id, tasks, remoteId, remoteId, tasks, remoteId, tasks, id);
// Change all items with remote id = 0 to be remote id = NULL
String changeZeroes = String.format("UPDATE %s SET %s = NULL WHERE %s = 0", tasks, remoteId, remoteId);
database.execSQL(deleteDuplicates);
database.execSQL(changeZeroes);
onCreateTables();
} catch (SQLiteException e) { } catch (SQLiteException e) {
Log.e("astrid", "db-upgrade-" + oldVersion + "-" + newVersion, e); Log.e("astrid", "db-upgrade-" + oldVersion + "-" + newVersion, e);
} }

@ -6,13 +6,16 @@
package com.todoroo.astrid.dao; package com.todoroo.astrid.dao;
import android.content.ContentValues; import android.content.ContentValues;
import android.database.sqlite.SQLiteConstraintException;
import com.timsu.astrid.R; import com.timsu.astrid.R;
import com.todoroo.andlib.data.DatabaseDao; import com.todoroo.andlib.data.DatabaseDao;
import com.todoroo.andlib.data.TodorooCursor;
import com.todoroo.andlib.service.Autowired; import com.todoroo.andlib.service.Autowired;
import com.todoroo.andlib.service.DependencyInjectionService; import com.todoroo.andlib.service.DependencyInjectionService;
import com.todoroo.andlib.sql.Criterion; import com.todoroo.andlib.sql.Criterion;
import com.todoroo.andlib.sql.Functions; import com.todoroo.andlib.sql.Functions;
import com.todoroo.andlib.sql.Query;
import com.todoroo.andlib.utility.DateUtilities; import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.andlib.utility.Preferences; import com.todoroo.andlib.utility.Preferences;
import com.todoroo.astrid.dao.MetadataDao.MetadataCriteria; import com.todoroo.astrid.dao.MetadataDao.MetadataCriteria;
@ -166,7 +169,17 @@ public class TaskDao extends DatabaseDao<Task> {
public boolean save(Task task) { public boolean save(Task task) {
boolean saveSuccessful; boolean saveSuccessful;
if (task.getId() == Task.NO_ID) { if (task.getId() == Task.NO_ID) {
saveSuccessful = createNew(task); try {
saveSuccessful = createNew(task);
} catch (SQLiteConstraintException e) { // Tried to create task with remote id that already exists
saveSuccessful = false;
TodorooCursor<Task> cursor = query(Query.select(Task.ID).where(Task.REMOTE_ID.eq(task.getValue(Task.REMOTE_ID))));
if (cursor.getCount() > 0) {
cursor.moveToFirst();
task.setId(cursor.get(Task.ID));
saveSuccessful = saveExisting(task);
}
}
} else { } else {
saveSuccessful = saveExisting(task); saveSuccessful = saveExisting(task);
} }

@ -13,7 +13,6 @@ import com.todoroo.astrid.dao.TagDataDao;
import com.todoroo.astrid.dao.TaskDao; import com.todoroo.astrid.dao.TaskDao;
import com.todoroo.astrid.dao.UpdateDao; import com.todoroo.astrid.dao.UpdateDao;
import com.todoroo.astrid.data.TagData; import com.todoroo.astrid.data.TagData;
import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.data.Update; import com.todoroo.astrid.data.Update;
/** /**
@ -141,10 +140,10 @@ public class TagDataService {
return updateDao.query(Query.select(Update.PROPERTIES).where( return updateDao.query(Query.select(Update.PROPERTIES).where(
criterion). criterion).
orderBy(Order.desc(Update.CREATION_DATE))); orderBy(Order.desc(Update.CREATION_DATE)));
if(tagData.getValue(Task.REMOTE_ID) < 1) if(tagData.getValue(TagData.REMOTE_ID) == 0)
return updateDao.query(Query.select(Update.PROPERTIES).where(Update.TAGS_LOCAL.like("%," + tagData.getId() + ",%"))); return updateDao.query(Query.select(Update.PROPERTIES).where(Update.TAGS_LOCAL.like("%," + tagData.getId() + ",%")));
return updateDao.query(Query.select(Update.PROPERTIES).where(Criterion.and(criterion, return updateDao.query(Query.select(Update.PROPERTIES).where(Criterion.and(criterion,
Criterion.or(Update.TAGS.like("%," + tagData.getValue(Task.REMOTE_ID) + ",%"), Criterion.or(Update.TAGS.like("%," + tagData.getValue(TagData.REMOTE_ID) + ",%"),
Update.TAGS_LOCAL.like("%," + tagData.getId() + ",%")))). Update.TAGS_LOCAL.like("%," + tagData.getId() + ",%")))).
orderBy(Order.desc(Update.CREATION_DATE))); orderBy(Order.desc(Update.CREATION_DATE)));
} }
@ -155,12 +154,12 @@ public class TagDataService {
* @return * @return
*/ */
public Update getLatestUpdate(TagData tagData) { public Update getLatestUpdate(TagData tagData) {
if(tagData.getValue(Task.REMOTE_ID) < 1) if(tagData.getValue(TagData.REMOTE_ID) == 0)
return null; return null;
@SuppressWarnings("nls") @SuppressWarnings("nls")
TodorooCursor<Update> updates = updateDao.query(Query.select(Update.PROPERTIES).where( TodorooCursor<Update> updates = updateDao.query(Query.select(Update.PROPERTIES).where(
Update.TAGS.like("%," + tagData.getValue(Task.REMOTE_ID) + ",%")). Update.TAGS.like("%," + tagData.getValue(TagData.REMOTE_ID) + ",%")).
orderBy(Order.desc(Update.CREATION_DATE)).limit(1)); orderBy(Order.desc(Update.CREATION_DATE)).limit(1));
try { try {
if(updates.getCount() == 0) if(updates.getCount() == 0)

Loading…
Cancel
Save