/** * Copyright (c) 2012 Todoroo Inc * * See the file "LICENSE" for the full license governing this code. */ package com.todoroo.astrid.dao; import java.util.HashMap; import java.util.HashSet; import java.util.Set; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import android.content.ContentValues; import android.text.TextUtils; import com.todoroo.andlib.data.AbstractModel; import com.todoroo.andlib.data.DatabaseDao; 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.Query; import com.todoroo.andlib.utility.DateUtilities; import com.todoroo.astrid.actfm.sync.ActFmSyncThread; import com.todoroo.astrid.actfm.sync.messages.ChangesHappened; import com.todoroo.astrid.actfm.sync.messages.NameMaps; import com.todoroo.astrid.core.PluginServices; 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.TagMetadata; import com.todoroo.astrid.data.TagOutstanding; import com.todoroo.astrid.tags.TagMemberMetadata; /** * Data Access layer for {@link Metadata}-related operations. * * @author Tim Su <tim@todoroo.com> * */ public class TagMetadataDao extends DatabaseDao<TagMetadata> { @Autowired private Database database; @edu.umd.cs.findbugs.annotations.SuppressWarnings(value="UR_UNINIT_READ") public TagMetadataDao() { super(TagMetadata.class); DependencyInjectionService.getInstance().inject(this); setDatabase(database); } public static class TagMetadataCriteria { /** Returns all metadata associated with a given task */ public static Criterion byTag(String tagUuid) { return TagMetadata.TAG_UUID.eq(tagUuid); } /** Returns all metadata associated with a given key */ public static Criterion withKey(String key) { return TagMetadata.KEY.eq(key); } /** Returns all metadata associated with a given key */ public static Criterion byTagAndWithKey(String tagUuid, String key) { return Criterion.and(withKey(key), byTag(tagUuid)); } } @Override protected boolean shouldRecordOutstanding(TagMetadata item) { ContentValues cv = item.getSetValues(); return super.shouldRecordOutstanding(item) && cv != null && ((cv.containsKey(TagMetadata.KEY.name) && TagMemberMetadata.KEY.equals(item.getValue(TagMetadata.KEY))) || (cv.containsKey(TagMetadata.DELETION_DATE.name) && item.getValue(TagMetadata.DELETION_DATE) > 0)) && RemoteModelDao.getOutstandingEntryFlag(RemoteModelDao.OUTSTANDING_ENTRY_FLAG_RECORD_OUTSTANDING); } @Override protected int createOutstandingEntries(long modelId, ContentValues modelSetValues) { Long tagDataId = modelSetValues.getAsLong(TagMetadata.TAG_ID.name); String memberId = modelSetValues.getAsString(TagMemberMetadata.USER_UUID.name); Long deletionDate = modelSetValues.getAsLong(TagMetadata.DELETION_DATE.name); if (tagDataId == null || tagDataId == AbstractModel.NO_ID || RemoteModel.isUuidEmpty(memberId)) return -1; TagOutstanding to = new TagOutstanding(); to.setValue(OutstandingEntry.ENTITY_ID_PROPERTY, tagDataId); to.setValue(OutstandingEntry.CREATED_AT_PROPERTY, DateUtilities.now()); String addedOrRemoved = NameMaps.MEMBER_ADDED_COLUMN; if (deletionDate != null && deletionDate > 0) addedOrRemoved = NameMaps.MEMBER_REMOVED_COLUMN; to.setValue(OutstandingEntry.COLUMN_STRING_PROPERTY, addedOrRemoved); to.setValue(OutstandingEntry.VALUE_STRING_PROPERTY, memberId); database.insert(outstandingTable.name, null, to.getSetValues()); ActFmSyncThread.getInstance().enqueueMessage(new ChangesHappened<TagData, TagOutstanding>(tagDataId, TagData.class, PluginServices.getTagDataDao(), PluginServices.getTagOutstandingDao()), null); return 1; } public void createMemberLink(long tagId, String tagUuid, String memberId, boolean suppressOutstanding) { createMemberLink(tagId, tagUuid, memberId, false, suppressOutstanding); } public void createMemberLink(long tagId, String tagUuid, String memberId, boolean removedMember, boolean suppressOutstanding) { TagMetadata newMetadata = TagMemberMetadata.newMemberMetadata(tagId, tagUuid, memberId); if (removedMember) newMetadata.setValue(TagMetadata.DELETION_DATE, DateUtilities.now()); if (suppressOutstanding) newMetadata.putTransitory(SyncFlags.ACTFM_SUPPRESS_OUTSTANDING_ENTRIES, true); if (update(Criterion.and(TagMetadataCriteria.byTagAndWithKey(tagUuid, TagMemberMetadata.KEY), TagMemberMetadata.USER_UUID.eq(memberId)), newMetadata) <= 0) { if (suppressOutstanding) newMetadata.putTransitory(SyncFlags.ACTFM_SUPPRESS_OUTSTANDING_ENTRIES, true); createNew(newMetadata); } } public void removeMemberLink(long tagId, String tagUuid, String memberId, boolean suppressOutstanding) { TagMetadata deleteTemplate = new TagMetadata(); deleteTemplate.setValue(TagMetadata.TAG_ID, tagId); // Need this for recording changes in outstanding table deleteTemplate.setValue(TagMetadata.DELETION_DATE, DateUtilities.now()); deleteTemplate.setValue(TagMemberMetadata.USER_UUID, memberId); // Need this for recording changes in outstanding table if (suppressOutstanding) deleteTemplate.putTransitory(SyncFlags.ACTFM_SUPPRESS_OUTSTANDING_ENTRIES, true); update(Criterion.and(TagMetadataCriteria.withKey(TagMemberMetadata.KEY), TagMetadata.DELETION_DATE.eq(0), TagMetadata.TAG_UUID.eq(tagUuid), TagMemberMetadata.USER_UUID.eq(memberId)), deleteTemplate); } public void removeMemberLinks(long tagId, String tagUuid, String[] memberIds, boolean suppressOutstanding) { TagMetadata deleteTemplate = new TagMetadata(); deleteTemplate.setValue(TagMetadata.TAG_ID, tagId); // Need this for recording changes in outstanding table deleteTemplate.setValue(TagMetadata.DELETION_DATE, DateUtilities.now()); if (memberIds != null) { for (String uuid : memberIds) { // TODO: Right now this is in a loop because each deleteTemplate needs the individual tagUuid in order to record // the outstanding entry correctly. If possible, this should be improved to a single query deleteTemplate.setValue(TagMemberMetadata.USER_UUID, uuid); // Need this for recording changes in outstanding table if (suppressOutstanding) deleteTemplate.putTransitory(SyncFlags.ACTFM_SUPPRESS_OUTSTANDING_ENTRIES, true); update(Criterion.and(TagMetadataCriteria.withKey(TagMemberMetadata.KEY), TagMetadata.DELETION_DATE.eq(0), TagMetadata.TAG_UUID.eq(tagUuid), TagMemberMetadata.USER_UUID.eq(uuid)), deleteTemplate); } } } public void synchronizeMembers(TagData tagData, String legacyMembersString, String tagUuid, JSONArray members) { long tagId = tagData.getId(); Set<String> emails = new HashSet<String>(); Set<String> ids = new HashSet<String>(); HashMap<String, String> idToEmail = new HashMap<String, String>(); for (int i = 0; i < members.length(); i++) { JSONObject person = members.optJSONObject(i); if (person != null) { String id = person.optString("id"); //$NON-NLS-1$ if (!TextUtils.isEmpty(id)) ids.add(id); String email = person.optString("email"); //$NON-NLS-1$ if (!TextUtils.isEmpty(email)) emails.add(email); if (!TextUtils.isEmpty(id) && !TextUtils.isEmpty(email)) { idToEmail.put(id, email); } } } if (!TextUtils.isEmpty(legacyMembersString)) { try { JSONArray legacyMembers = new JSONArray(legacyMembersString); for (int i = 0; i < legacyMembers.length(); i++) { JSONObject user = legacyMembers.optJSONObject(i); if (user != null) { String id = user.optString("id"); //$NON-NLS-1$ String email = user.optString("email"); //$NON-NLS-1$ if (!TextUtils.isEmpty(id)) { createMemberLink(tagId, tagUuid, id, !ids.contains(id), false); } else if (!TextUtils.isEmpty(email)) { createMemberLink(tagId, tagUuid, email, !emails.contains(email), false); } } } } catch (JSONException e) { // } tagData.setValue(TagData.MEMBERS, ""); //$NON-NLS-1$ PluginServices.getTagDataDao().saveExisting(tagData); } TodorooCursor<TagMetadata> currentMembers = query(Query.select(TagMemberMetadata.USER_UUID).where(TagMetadataCriteria.byTagAndWithKey(tagUuid, TagMemberMetadata.KEY))); try { TagMetadata m = new TagMetadata(); for (currentMembers.moveToNext(); !currentMembers.isAfterLast(); currentMembers.moveToNext()) { m.clear(); m.readFromCursor(currentMembers); String userId = m.getValue(TagMemberMetadata.USER_UUID); boolean exists = ids.remove(userId) || emails.remove(userId); if (exists && idToEmail.containsKey(userId)) { String email = idToEmail.get(userId); emails.remove(email); } if (!exists) { // Was in database, but not in new members list removeMemberLink(tagId, tagUuid, userId, false); } } } finally { currentMembers.close(); } for (String email : emails) { createMemberLink(tagId, tagUuid, email, false); } for (String id : ids) { createMemberLink(tagId, tagUuid, id, false); } } public boolean tagHasMembers(String uuid) { TodorooCursor<TagMetadata> metadata = query(Query.select(TagMetadata.ID).where(Criterion.and(TagMetadataCriteria.byTagAndWithKey(uuid, TagMemberMetadata.KEY), TagMetadata.DELETION_DATE.eq(0)))); try { return metadata.getCount() > 0; } finally { metadata.close(); } } public boolean memberOfTagData(String email, String tagId, String memberId) { Criterion criterion; if (!RemoteModel.isUuidEmpty(memberId) && !TextUtils.isEmpty(email)) criterion = Criterion.or(TagMemberMetadata.USER_UUID.eq(email), TagMemberMetadata.USER_UUID.eq(memberId)); else if (!RemoteModel.isUuidEmpty(memberId)) criterion = TagMemberMetadata.USER_UUID.eq(memberId); else if (!TextUtils.isEmpty(email)) criterion = TagMemberMetadata.USER_UUID.eq(email); else return false; TodorooCursor<TagMetadata> count = query(Query.select(TagMetadata.ID).where( Criterion.and(TagMetadataCriteria.withKey(TagMemberMetadata.KEY), TagMetadata.TAG_UUID.eq(tagId), criterion))); try { return count.getCount() > 0; } finally { // } } }