package com.todoroo.astrid.actfm.sync; import java.util.HashSet; import java.util.Set; import android.text.TextUtils; import android.util.Log; import com.crittercism.app.Crittercism; import com.todoroo.andlib.data.DatabaseDao; import com.todoroo.andlib.data.Property; import com.todoroo.andlib.data.TodorooCursor; import com.todoroo.andlib.service.Autowired; import com.todoroo.andlib.service.DependencyInjectionService; import com.todoroo.andlib.sql.Criterion; import com.todoroo.andlib.sql.Functions; import com.todoroo.andlib.sql.Order; import com.todoroo.andlib.sql.Query; import com.todoroo.andlib.utility.DateUtilities; import com.todoroo.andlib.utility.Preferences; import com.todoroo.astrid.actfm.sync.messages.NameMaps; import com.todoroo.astrid.dao.MetadataDao.MetadataCriteria; import com.todoroo.astrid.dao.OutstandingEntryDao; import com.todoroo.astrid.dao.RemoteModelDao; import com.todoroo.astrid.dao.TagDataDao; import com.todoroo.astrid.dao.TagOutstandingDao; import com.todoroo.astrid.dao.TaskAttachmentDao; import com.todoroo.astrid.dao.TaskDao; import com.todoroo.astrid.dao.TaskListMetadataDao; import com.todoroo.astrid.dao.TaskOutstandingDao; import com.todoroo.astrid.dao.UpdateDao; import com.todoroo.astrid.dao.UserActivityDao; import com.todoroo.astrid.dao.UserDao; import com.todoroo.astrid.data.Metadata; import com.todoroo.astrid.data.OutstandingEntry; import com.todoroo.astrid.data.RemoteModel; import com.todoroo.astrid.data.SyncFlags; import com.todoroo.astrid.data.TagData; import com.todoroo.astrid.data.TagOutstanding; import com.todoroo.astrid.data.Task; import com.todoroo.astrid.data.TaskAttachment; import com.todoroo.astrid.data.TaskListMetadata; import com.todoroo.astrid.data.TaskOutstanding; import com.todoroo.astrid.data.Update; import com.todoroo.astrid.data.User; import com.todoroo.astrid.data.UserActivity; import com.todoroo.astrid.files.FileMetadata; import com.todoroo.astrid.helper.UUIDHelper; import com.todoroo.astrid.service.MetadataService; import com.todoroo.astrid.service.TagDataService; import com.todoroo.astrid.subtasks.SubtasksHelper; import com.todoroo.astrid.subtasks.SubtasksUpdater; import com.todoroo.astrid.tags.TaskToTagMetadata; @SuppressWarnings("nls") public class AstridNewSyncMigrator { @Autowired private MetadataService metadataService; @Autowired private TagDataService tagDataService; @Autowired private TagDataDao tagDataDao; @Autowired private TaskDao taskDao; @Autowired private UpdateDao updateDao; @Autowired private UserActivityDao userActivityDao; @Autowired private UserDao userDao; @Autowired private TaskAttachmentDao taskAttachmentDao; @Autowired private TaskListMetadataDao taskListMetadataDao; @Autowired private TaskOutstandingDao taskOutstandingDao; @Autowired private TagOutstandingDao tagOutstandingDao; private static final String LOG_TAG = "sync-migrate"; public static final String PREF_SYNC_MIGRATION = "p_sync_migration"; public AstridNewSyncMigrator() { DependencyInjectionService.getInstance().inject(this); } @SuppressWarnings("deprecation") public void performMigration() { if (Preferences.getBoolean(PREF_SYNC_MIGRATION, false)) return; // -------------- // First ensure that a TagData object exists for each tag metadata // -------------- Query noTagDataQuery = Query.select(Metadata.PROPERTIES).where(Criterion.and( MetadataCriteria.withKey(TaskToTagMetadata.KEY), Criterion.or(TaskToTagMetadata.TAG_UUID.isNull(), TaskToTagMetadata.TAG_UUID.eq(0)), Criterion.not(TaskToTagMetadata.TAG_NAME.in(Query.select(TagData.NAME).from(TagData.TABLE))))).groupBy(TaskToTagMetadata.TAG_NAME); TodorooCursor<Metadata> noTagData = null; try { noTagData = metadataService.query(noTagDataQuery); Metadata tag = new Metadata(); TagData newTagData = new TagData(); for (noTagData.moveToFirst(); !noTagData.isAfterLast(); noTagData.moveToNext()) { try { newTagData.clear(); tag.clear(); tag.readFromCursor(noTagData); if (ActFmInvoker.SYNC_DEBUG) Log.w(LOG_TAG, "CREATING TAG DATA " + tag.getValue(TaskToTagMetadata.TAG_NAME)); newTagData.setValue(TagData.NAME, tag.getValue(TaskToTagMetadata.TAG_NAME)); tagDataService.save(newTagData); } catch (Exception e) { Log.e(LOG_TAG, "Error creating tag data", e); Crittercism.logHandledException(e); } } } catch (Exception e) { Log.e(LOG_TAG, "Error creating tag data", e); Crittercism.logHandledException(e); } finally { if (noTagData != null) noTagData.close(); } // -------------- // Delete all emergent tag data, we don't need it // -------------- TodorooCursor<TagData> emergentTags = null; try { emergentTags = tagDataDao.query(Query.select(TagData.ID, TagData.NAME).where(Functions.bitwiseAnd(TagData.FLAGS, TagData.FLAG_EMERGENT).gt(0))); TagData td = new TagData(); for (emergentTags.moveToFirst(); !emergentTags.isAfterLast(); emergentTags.moveToNext()) { try { td.clear(); td.readFromCursor(emergentTags); String name = td.getValue(TagData.NAME); tagDataDao.delete(td.getId()); if (!TextUtils.isEmpty(name)) metadataService.deleteWhere(Criterion.and(MetadataCriteria.withKey(TaskToTagMetadata.KEY), TaskToTagMetadata.TAG_NAME.eq(name))); } catch (Exception e) { Log.e(LOG_TAG, "Error clearing emergent tags"); Crittercism.logHandledException(e); } } } catch (Exception e){ Crittercism.logHandledException(e); } finally { if (emergentTags != null) emergentTags.close(); } // -------------- // Then ensure that every remote model has a remote id, by generating one using the uuid generator for all those without one // -------------- final Set<Long> tasksThatNeedTagSync = new HashSet<Long>(); try { Query tagsQuery = Query.select(TagData.ID, TagData.UUID, TagData.MODIFICATION_DATE) .where(Criterion.or(TagData.UUID.eq(RemoteModel.NO_UUID), TagData.UUID.isNull(), TagData.UUID.eq(""))); assertUUIDsExist(tagsQuery, new TagData(), tagDataDao, tagOutstandingDao, new TagOutstanding(), NameMaps.syncableProperties(NameMaps.TABLE_ID_TAGS), new UUIDAssertionExtras<TagData>() { private static final String LAST_TAG_FETCH_TIME = "actfm_lastTag"; //$NON-NLS-1$ private final long lastFetchTime = Preferences.getInt(LAST_TAG_FETCH_TIME, 0) * 1000L; @Override public boolean shouldCreateOutstandingEntries(TagData instance) { boolean result = lastFetchTime == 0 || (instance.containsNonNullValue(TagData.MODIFICATION_DATE) && instance.getValue(TagData.MODIFICATION_DATE) > lastFetchTime); return result && RemoteModelDao.getOutstandingEntryFlag(RemoteModelDao.OUTSTANDING_ENTRY_FLAG_RECORD_OUTSTANDING); } @Override public void afterSave(TagData instance, boolean createdOutstanding) {/**/} }); Query tasksQuery = Query.select(Task.ID, Task.UUID, Task.RECURRENCE, Task.FLAGS, Task.MODIFICATION_DATE, Task.LAST_SYNC) .where(Criterion.or(Task.UUID.eq(RemoteModel.NO_UUID), Task.UUID.isNull(), Task.UUID.eq(""))); assertUUIDsExist(tasksQuery, new Task(), taskDao, taskOutstandingDao, new TaskOutstanding(), NameMaps.syncableProperties(NameMaps.TABLE_ID_TASKS), new UUIDAssertionExtras<Task>() { @Override public boolean shouldCreateOutstandingEntries(Task instance) { if (!instance.containsNonNullValue(Task.MODIFICATION_DATE) || instance.getValue(Task.LAST_SYNC) == 0) return RemoteModelDao.getOutstandingEntryFlag(RemoteModelDao.OUTSTANDING_ENTRY_FLAG_RECORD_OUTSTANDING); return (instance.getValue(Task.LAST_SYNC) < instance.getValue(Task.MODIFICATION_DATE)) && RemoteModelDao.getOutstandingEntryFlag(RemoteModelDao.OUTSTANDING_ENTRY_FLAG_RECORD_OUTSTANDING); } @Override public void afterSave(Task instance, boolean createdOutstanding) { if (createdOutstanding) tasksThatNeedTagSync.add(instance.getId()); } }); } catch (Exception e) { Log.e(LOG_TAG, "Error asserting UUIDs", e); Crittercism.logHandledException(e); } // -------------- // Update task flags // -------------- Task template = new Task(); try { template.setValue(Task.IS_READONLY, 1); taskDao.update(Functions.bitwiseAnd(Task.FLAGS, Task.FLAG_IS_READONLY).gt(0), template); template.clear(); template.setValue(Task.IS_PUBLIC, 1); taskDao.update(Functions.bitwiseAnd(Task.FLAGS, Task.FLAG_PUBLIC).gt(0), template); } catch (Exception e) { Log.e(LOG_TAG, "Error clearing task flags", e); Crittercism.logHandledException(e); } // -------------- // Update recurrence values // -------------- TodorooCursor<Task> tasksWithRecurrence = null; try { tasksWithRecurrence = taskDao.query(Query.select(Task.ID, Task.FLAGS, Task.RECURRENCE).where(Criterion.or(Task.RECURRENCE.isNotNull(), Task.RECURRENCE.neq("")))); for (tasksWithRecurrence.moveToFirst(); !tasksWithRecurrence.isAfterLast(); tasksWithRecurrence.moveToNext()) { try { template.clear(); template.readFromCursor(tasksWithRecurrence); String recurrence = template.getValue(Task.RECURRENCE); if (!TextUtils.isEmpty(recurrence)) { String fromCompletion = ";FROM=COMPLETION"; boolean repeatAfterCompletion = template.getFlag(Task.FLAGS, Task.FLAG_REPEAT_AFTER_COMPLETION); template.setFlag(Task.FLAGS, Task.FLAG_REPEAT_AFTER_COMPLETION, false); recurrence = recurrence.replaceAll("BYDAY=;", ""); if (fromCompletion.equals(recurrence)) recurrence = ""; else if (repeatAfterCompletion) recurrence = recurrence + fromCompletion; template.setValue(Task.RECURRENCE, recurrence); template.putTransitory(SyncFlags.GTASKS_SUPPRESS_SYNC, true); taskDao.saveExisting(template); } } catch (Exception e) { Log.e(LOG_TAG, "Error migrating recurrence", e); Crittercism.logHandledException(e); } } } catch (Exception e) { Log.e(LOG_TAG, "Error migrating recurrence", e); Crittercism.logHandledException(e); } finally { if (tasksWithRecurrence != null) tasksWithRecurrence.close(); } // -------------- // Migrate unsynced task comments to UserActivity table // -------------- TodorooCursor<Update> updates = null; try { updates = updateDao.query(Query.select(Update.PROPERTIES).where( Criterion.and(Criterion.or(Update.REMOTE_ID.eq(0), Update.REMOTE_ID.isNull()), Criterion.or(Update.ACTION_CODE.eq(UserActivity.ACTION_TAG_COMMENT), Update.ACTION_CODE.eq(UserActivity.ACTION_TASK_COMMENT))))); Update update = new Update(); UserActivity userActivity = new UserActivity(); for (updates.moveToFirst(); !updates.isAfterLast(); updates.moveToNext()) { try { update.clear(); userActivity.clear(); update.readFromCursor(updates); boolean setTarget = true; if (!RemoteModel.isUuidEmpty(update.getValue(Update.TASK).toString())) { userActivity.setValue(UserActivity.TARGET_ID, update.getValue(Update.TASK).toString()); } else if (update.getValue(Update.TASK_LOCAL) > 0) { Task local = taskDao.fetch(update.getValue(Update.TASK_LOCAL), Task.UUID); if (local != null && !RemoteModel.isUuidEmpty(local.getUuid())) userActivity.setValue(UserActivity.TARGET_ID, local.getUuid()); else setTarget = false; } else { setTarget = false; } if (setTarget) { userActivity.setValue(UserActivity.USER_UUID, update.getValue(Update.USER_ID).toString()); userActivity.setValue(UserActivity.ACTION, update.getValue(Update.ACTION_CODE)); userActivity.setValue(UserActivity.MESSAGE, update.getValue(Update.MESSAGE)); userActivity.setValue(UserActivity.CREATED_AT, update.getValue(Update.CREATION_DATE)); userActivityDao.createNew(userActivity); } } catch (Exception e) { Log.e(LOG_TAG, "Error migrating updates", e); Crittercism.logHandledException(e); } } } catch (Exception e) { Log.e(LOG_TAG, "Error migrating updates", e); Crittercism.logHandledException(e); } finally { if (updates != null) updates.close(); } // -------------- // Drop any entries from the Users table that don't have a UUID // -------------- try { userDao.deleteWhere(Criterion.or(User.UUID.isNull(), User.UUID.eq(""), User.UUID.eq("0"))); } catch (Exception e) { Log.e(LOG_TAG, "Error deleting incomplete user entries", e); Crittercism.logHandledException(e); } // -------------- // Migrate legacy FileMetadata models to new TaskAttachment models // -------------- TodorooCursor<Metadata> fmCursor = null; try { fmCursor = metadataService.query(Query.select(Metadata.PROPERTIES) .where(MetadataCriteria.withKey(FileMetadata.METADATA_KEY))); Metadata m = new Metadata(); TaskAttachment attachment = new TaskAttachment(); for (fmCursor.moveToFirst(); !fmCursor.isAfterLast(); fmCursor.moveToNext()) { try { attachment.clear(); m.clear(); m.readFromCursor(fmCursor); Task task = taskDao.fetch(m.getValue(Metadata.TASK), Task.UUID); if (task == null || !RemoteModel.isValidUuid(task.getUuid())) continue; Long oldRemoteId = m.getValue(FileMetadata.REMOTE_ID); boolean synced = false; if (oldRemoteId != null && oldRemoteId > 0) { synced = true; attachment.setValue(TaskAttachment.UUID, Long.toString(oldRemoteId)); } attachment.setValue(TaskAttachment.TASK_UUID, task.getUuid()); if (m.containsNonNullValue(FileMetadata.NAME)) attachment.setValue(TaskAttachment.NAME, m.getValue(FileMetadata.NAME)); if (m.containsNonNullValue(FileMetadata.URL)) attachment.setValue(TaskAttachment.URL, m.getValue(FileMetadata.URL)); if (m.containsNonNullValue(FileMetadata.FILE_PATH)) attachment.setValue(TaskAttachment.FILE_PATH, m.getValue(FileMetadata.FILE_PATH)); if (m.containsNonNullValue(FileMetadata.FILE_TYPE)) attachment.setValue(TaskAttachment.CONTENT_TYPE, m.getValue(FileMetadata.FILE_TYPE)); if (m.containsNonNullValue(FileMetadata.DELETION_DATE)) attachment.setValue(TaskAttachment.DELETED_AT, m.getValue(FileMetadata.DELETION_DATE)); if (synced) { attachment.putTransitory(SyncFlags.ACTFM_SUPPRESS_OUTSTANDING_ENTRIES, true); } if (!ActFmPreferenceService.isPremiumUser()) attachment.putTransitory(SyncFlags.ACTFM_SUPPRESS_OUTSTANDING_ENTRIES, true); taskAttachmentDao.createNew(attachment); } catch (Exception e) { Log.e(LOG_TAG, "Error migrating task attachment metadata", e); Crittercism.logHandledException(e); } } } catch (Exception e) { Log.e(LOG_TAG, "Error migrating task attachment metadata", e); Crittercism.logHandledException(e); } finally { if (fmCursor != null) fmCursor.close(); } // -------------- // Create task list metadata entries for each tag // -------------- TaskListMetadata tlm = new TaskListMetadata(); try { String activeTasksOrder = Preferences.getStringValue(SubtasksUpdater.ACTIVE_TASKS_ORDER); if (TextUtils.isEmpty(activeTasksOrder)) activeTasksOrder = "[]"; activeTasksOrder = SubtasksHelper.convertTreeToRemoteIds(activeTasksOrder); tlm.setValue(TaskListMetadata.FILTER, TaskListMetadata.FILTER_ID_ALL); tlm.setValue(TaskListMetadata.TASK_IDS, activeTasksOrder); if (taskListMetadataDao.update(TaskListMetadata.FILTER.eq(TaskListMetadata.FILTER_ID_ALL), tlm) <= 0) { taskListMetadataDao.createNew(tlm); } } catch (Exception e) { Log.e(LOG_TAG, "Error migrating active tasks ordering", e); Crittercism.logHandledException(e); } try { tlm.clear(); String todayTasksOrder = Preferences.getStringValue(SubtasksUpdater.TODAY_TASKS_ORDER); if (TextUtils.isEmpty(todayTasksOrder)) todayTasksOrder = "[]"; todayTasksOrder = SubtasksHelper.convertTreeToRemoteIds(todayTasksOrder); tlm.setValue(TaskListMetadata.FILTER, TaskListMetadata.FILTER_ID_TODAY); tlm.setValue(TaskListMetadata.TASK_IDS, todayTasksOrder); if (taskListMetadataDao.update(TaskListMetadata.FILTER.eq(TaskListMetadata.FILTER_ID_TODAY), tlm) <= 0) { taskListMetadataDao.createNew(tlm); } } catch (Exception e) { Log.e(LOG_TAG, "Error migrating today ordering", e); Crittercism.logHandledException(e); } TodorooCursor<TagData> allTagData = null; try { allTagData = tagDataDao.query(Query.select(TagData.ID, TagData.UUID, TagData.TAG_ORDERING)); TagData td = new TagData(); for (allTagData.moveToFirst(); !allTagData.isAfterLast(); allTagData.moveToNext()) { try { tlm.clear(); td.clear(); td.readFromCursor(allTagData); String tagOrdering = td.getValue(TagData.TAG_ORDERING); tagOrdering = SubtasksHelper.convertTreeToRemoteIds(tagOrdering); tlm.setValue(TaskListMetadata.TASK_IDS, tagOrdering); tlm.setValue(TaskListMetadata.TAG_UUID, td.getUuid()); taskListMetadataDao.createNew(tlm); } catch (Exception e) { Log.e(LOG_TAG, "Error migrating tag ordering", e); Crittercism.logHandledException(e); } } } catch (Exception e) { Log.e(LOG_TAG, "Error migrating tag ordering", e); Crittercism.logHandledException(e); } finally { if (allTagData != null) allTagData.close(); } // -------------- // Ensure that all tag metadata entities have all important fields filled in // -------------- TodorooCursor<Metadata> incompleteMetadata = null; try { Query incompleteQuery = Query.select(Metadata.PROPERTIES).where(Criterion.and( MetadataCriteria.withKey(TaskToTagMetadata.KEY), Criterion.or(TaskToTagMetadata.TASK_UUID.eq(0), TaskToTagMetadata.TASK_UUID.isNull(), TaskToTagMetadata.TAG_UUID.eq(0), TaskToTagMetadata.TAG_UUID.isNull()))); incompleteMetadata = metadataService.query(incompleteQuery);; Metadata m = new Metadata(); for (incompleteMetadata.moveToFirst(); !incompleteMetadata.isAfterLast(); incompleteMetadata.moveToNext()) { try { m.clear(); // Need this since some properties may be null m.readFromCursor(incompleteMetadata); if (ActFmInvoker.SYNC_DEBUG) Log.w(LOG_TAG, "Incomplete linking task " + m.getValue(Metadata.TASK) + " to " + m.getValue(TaskToTagMetadata.TAG_NAME)); if (!m.containsNonNullValue(TaskToTagMetadata.TASK_UUID) || RemoteModel.isUuidEmpty(m.getValue(TaskToTagMetadata.TASK_UUID))) { if (ActFmInvoker.SYNC_DEBUG) Log.w(LOG_TAG, "No task uuid"); updateTaskUuid(m); } if (!m.containsNonNullValue(TaskToTagMetadata.TAG_UUID) || RemoteModel.isUuidEmpty(m.getValue(TaskToTagMetadata.TAG_UUID))) { if (ActFmInvoker.SYNC_DEBUG) Log.w(LOG_TAG, "No tag uuid"); updateTagUuid(m); } if (m.getSetValues() != null && m.getSetValues().size() > 0) metadataService.save(m); } catch (Exception e) { Log.e(LOG_TAG, "Error validating task to tag metadata", e); Crittercism.logHandledException(e); } } } catch (Exception e) { Log.e(LOG_TAG, "Error validating task to tag metadata", e); Crittercism.logHandledException(e); } finally { if (incompleteMetadata != null) incompleteMetadata.close(); } // -------------- // Delete all featured list data // -------------- try { tagDataDao.deleteWhere(Functions.bitwiseAnd(TagData.FLAGS, TagData.FLAG_FEATURED).gt(0)); } catch (Exception e) { Log.e(LOG_TAG, "Error deleting featured list data", e); Crittercism.logHandledException(e); } // -------------- // Finally, create oustanding entries for tags on unsynced tasks // -------------- TodorooCursor<Metadata> tagsAdded = null; try { Long[] ids = tasksThatNeedTagSync.toArray(new Long[tasksThatNeedTagSync.size()]); tagsAdded = metadataService.query(Query.select(Metadata.PROPERTIES) .where(Criterion.and(MetadataCriteria.withKey(TaskToTagMetadata.KEY), Metadata.TASK.in(ids))).orderBy(Order.asc(Metadata.TASK))); Metadata m = new Metadata(); for (tagsAdded.moveToFirst(); !tagsAdded.isAfterLast(); tagsAdded.moveToNext()) { try { m.clear(); m.readFromCursor(tagsAdded); Long deletionDate = m.getValue(Metadata.DELETION_DATE); String tagUuid = m.getValue(TaskToTagMetadata.TAG_UUID); if (!RemoteModel.isValidUuid(tagUuid)) continue; TaskOutstanding to = new TaskOutstanding(); to.setValue(OutstandingEntry.ENTITY_ID_PROPERTY, m.getValue(Metadata.TASK)); to.setValue(OutstandingEntry.CREATED_AT_PROPERTY, DateUtilities.now()); String addedOrRemoved = NameMaps.TAG_ADDED_COLUMN; if (deletionDate != null && deletionDate > 0) addedOrRemoved = NameMaps.TAG_REMOVED_COLUMN; to.setValue(OutstandingEntry.COLUMN_STRING_PROPERTY, addedOrRemoved); to.setValue(OutstandingEntry.VALUE_STRING_PROPERTY, tagUuid); taskOutstandingDao.createNew(to); } catch (Exception e) { Log.e(LOG_TAG, "Error creating tag_added outstanding entries", e); Crittercism.logHandledException(e); } } } catch (Exception e) { Log.e(LOG_TAG, "Error creating tag_added outstanding entries", e); Crittercism.logHandledException(e); } finally { if (tagsAdded != null) tagsAdded.close(); } Preferences.setBoolean(PREF_SYNC_MIGRATION, true); ActFmSyncMonitor monitor = ActFmSyncMonitor.getInstance(); synchronized (monitor) { monitor.notifyAll(); } } private interface UUIDAssertionExtras<TYPE extends RemoteModel> { boolean shouldCreateOutstandingEntries(TYPE instance); void afterSave(TYPE instance, boolean createdOutstanding); } private <TYPE extends RemoteModel, OE extends OutstandingEntry<TYPE>> void assertUUIDsExist(Query query, TYPE instance, DatabaseDao<TYPE> dao, OutstandingEntryDao<OE> oeDao, OE oe, Property<?>[] propertiesForOutstanding, UUIDAssertionExtras<TYPE> extras) { TodorooCursor<TYPE> cursor = null; try { cursor = dao.query(query); for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) { try { instance.clear(); instance.readPropertiesFromCursor(cursor); boolean unsyncedModel = false; if (!instance.containsNonNullValue(RemoteModel.UUID_PROPERTY) || RemoteModel.NO_UUID.equals(instance.getValue(RemoteModel.UUID_PROPERTY)) || "".equals(instance.getValue(RemoteModel.UUID_PROPERTY)) || "null".equals(instance.getValue(RemoteModel.UUID_PROPERTY))) { // No remote id exists, just create a UUID unsyncedModel = true; instance.setValue(RemoteModel.UUID_PROPERTY, UUIDHelper.newUUID()); } instance.putTransitory(SyncFlags.ACTFM_SUPPRESS_OUTSTANDING_ENTRIES, true); instance.putTransitory(SyncFlags.GTASKS_SUPPRESS_SYNC, true); dao.saveExisting(instance); boolean createdOutstanding = false; if (propertiesForOutstanding != null && (unsyncedModel || (extras != null && extras.shouldCreateOutstandingEntries(instance)))) { createdOutstanding = true; createOutstandingEntries(instance.getId(), dao, oeDao, oe, propertiesForOutstanding); } if (extras != null) extras.afterSave(instance, createdOutstanding); } catch (Exception e) { Log.e(LOG_TAG, "Error asserting UUIDs", e); Crittercism.logHandledException(e); } } } catch (Exception e) { Log.e(LOG_TAG, "Error asserting UUIDs", e); Crittercism.logHandledException(e); } finally { if (cursor != null) cursor.close(); } } private <TYPE extends RemoteModel, OE extends OutstandingEntry<TYPE>> void createOutstandingEntries(long id, DatabaseDao<TYPE> dao, OutstandingEntryDao<OE> oeDao, OE oe, Property<?>[] propertiesForOutstanding) { TYPE instance = dao.fetch(id, propertiesForOutstanding); long now = DateUtilities.now(); for (Property<?> property : propertiesForOutstanding) { oe.clear(); oe.setValue(OutstandingEntry.ENTITY_ID_PROPERTY, id); oe.setValue(OutstandingEntry.COLUMN_STRING_PROPERTY, property.name); Object value = instance.getValue(property); if (value == null) value = ""; oe.setValue(OutstandingEntry.VALUE_STRING_PROPERTY, value.toString()); oe.setValue(OutstandingEntry.CREATED_AT_PROPERTY, now); oeDao.createNew(oe); } } private void updateTaskUuid(Metadata m) { long taskId = m.getValue(Metadata.TASK); Task task = taskDao.fetch(taskId, Task.UUID); if (task != null) { if (ActFmInvoker.SYNC_DEBUG) Log.w(LOG_TAG, "Linking with task uuid " + task.getValue(Task.UUID)); m.setValue(TaskToTagMetadata.TASK_UUID, task.getValue(Task.UUID)); } else { if (ActFmInvoker.SYNC_DEBUG) Log.w(LOG_TAG, "Task not found, deleting link"); m.setValue(Metadata.DELETION_DATE, DateUtilities.now()); } } private void updateTagUuid(Metadata m) { String tag = m.getValue(TaskToTagMetadata.TAG_NAME); TagData tagData = tagDataService.getTagByName(tag, TagData.UUID); if (tagData != null) { if (ActFmInvoker.SYNC_DEBUG) Log.w(LOG_TAG, "Linking with tag uuid " + tagData.getValue(TagData.UUID)); m.setValue(TaskToTagMetadata.TAG_UUID, tagData.getValue(TagData.UUID)); } else { if (ActFmInvoker.SYNC_DEBUG) Log.w(LOG_TAG, "Tag not found, deleting link"); m.setValue(Metadata.DELETION_DATE, DateUtilities.now()); } } }