Lists are now case insensitive, with a migration process to save and merge old lists with the same names but different cases

pull/14/head
Sam Bosley 13 years ago
parent de9b8623a4
commit f71ae1fbe9

@ -33,6 +33,8 @@ public class Field extends DBObject<Field> {
*/
@SuppressWarnings("nls")
public Criterion eqCaseInsensitive(String value) {
if(value == null)
return UnaryCriterion.isNull(this);
String escaped = value.replace("\\", "\\\\").replace("%", "\\%").replace("_", "\\_");
return UnaryCriterion.like(this, escaped, "\\");
}

@ -397,7 +397,7 @@ public class TagViewActivity extends TaskListActivity implements OnTabChangeList
if(newTag)
getIntent().putExtra(TOKEN_FILTER, Filter.emptyFilter(getString(R.string.tag_new_list)));
TodorooCursor<TagData> cursor = tagDataService.query(Query.select(TagData.PROPERTIES).where(Criterion.or(TagData.NAME.eq(tag),
TodorooCursor<TagData> cursor = tagDataService.query(Query.select(TagData.PROPERTIES).where(Criterion.or(TagData.NAME.eqCaseInsensitive(tag),
Criterion.and(TagData.REMOTE_ID.gt(0), TagData.REMOTE_ID.eq(remoteId)))));
try {
tagData = new TagData();
@ -638,10 +638,16 @@ public class TagViewActivity extends TaskListActivity implements OnTabChangeList
boolean nameChanged = !oldName.equals(newName);
if (nameChanged) {
tagData.setValue(TagData.NAME, newName);
TagService.getInstance().rename(oldName, newName);
tagData.setFlag(TagData.FLAGS, TagData.FLAG_EMERGENT, false);
TagService service = TagService.getInstance();
newName = service.getTagWithCase(newName);
tagName.setText(newName);
if (!newName.equals(oldName)) {
tagData.setValue(TagData.NAME, newName);
service.rename(oldName, newName);
tagData.setFlag(TagData.FLAGS, TagData.FLAG_EMERGENT, false);
} else {
nameChanged = false;
}
}
if(newName.length() > 0 && oldName.length() == 0) {

@ -174,7 +174,7 @@ public final class ActFmDataService {
TodorooCursor<TagData> cursor = tagDataService.query(Query.select(TagData.PROPERTIES).where(
Criterion.or(TagData.REMOTE_ID.eq(tagObject.get("id")),
Criterion.and(TagData.REMOTE_ID.eq(0),
TagData.NAME.eq(tagObject.getString("name"))))));
TagData.NAME.eqCaseInsensitive(tagObject.getString("name"))))));
try {
cursor.moveToNext();
TagData tagData = new TagData();

@ -0,0 +1,123 @@
package com.todoroo.astrid.tags;
import java.util.HashMap;
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.Join;
import com.todoroo.andlib.sql.Query;
import com.todoroo.andlib.utility.Preferences;
import com.todoroo.astrid.data.Metadata;
import com.todoroo.astrid.data.TagData;
import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.service.MetadataService;
import com.todoroo.astrid.service.TagDataService;
import com.todoroo.astrid.service.TaskService;
public class TagCaseMigrator {
@Autowired TaskService taskService;
@Autowired TagDataService tagDataService;
@Autowired MetadataService metadataService;
private static final String PREF_CASE_MIGRATION_PERFORMED = "tag_case_migration"; //$NON-NLS-1$
public TagCaseMigrator() {
DependencyInjectionService.getInstance().inject(this);
}
private final HashMap<String, String> renameMap = new HashMap<String, String>();
private final HashMap<String, Long> nameToRemoteId = new HashMap<String, Long>();
private final HashMap<String, Integer> nameCountMap = new HashMap<String, Integer>();
public void performTagCaseMigration() {
if (!Preferences.getBoolean(PREF_CASE_MIGRATION_PERFORMED, false)) {
TagService.Tag[] allTagData = TagService.getInstance().getGroupedTags(TagService.GROUPED_TAGS_BY_ALPHA, Criterion.all);
for (int i = 0; i < allTagData.length - 1; i++) {
TagService.Tag first = allTagData[i];
TagService.Tag second = allTagData[i+1];
if (first.tag.equalsIgnoreCase(second.tag)) {
markForRenaming(first.tag, first.remoteId);
markForRenaming(second.tag, second.remoteId);
}
}
for (String key : renameMap.keySet()) {
TagService.getInstance().renameCaseSensitive(key, renameMap.get(key));
updateTagData(key);
}
Preferences.setBoolean(PREF_CASE_MIGRATION_PERFORMED, true);
}
}
private String targetNameForTag(String tag) {
String targetName = tag.toLowerCase();
targetName = targetName.substring(0, 1).toUpperCase() + targetName.substring(1);
return targetName;
}
private void markForRenaming(String tag, long remoteId) {
if (renameMap.containsKey(tag)) return;
String targetName = targetNameForTag(tag);
int suffix = 1;
if (nameCountMap.containsKey(targetName)) {
suffix = nameCountMap.get(targetName);
}
String newName = targetName + "_" + suffix; //$NON-NLS-1$
nameCountMap.put(targetName, suffix + 1);
renameMap.put(tag, newName);
nameToRemoteId.put(tag, remoteId);
}
private void updateTagData(String tag) {
long remoteId = nameToRemoteId.get(tag);
TodorooCursor<TagData> tagData = tagDataService.query(Query.select(TagData.NAME, TagData.REMOTE_ID)
.where(Criterion.and(
TagData.NAME.eq(tag), TagData.REMOTE_ID.eq(remoteId))));
try {
for (tagData.moveToFirst(); !tagData.isAfterLast(); tagData.moveToNext()) {
TagData curr = new TagData(tagData);
curr.setValue(TagData.NAME, renameMap.get(tag));
tagDataService.save(curr);
}
} finally {
tagData.close();
}
addTasksToTargetTag(renameMap.get(tag), targetNameForTag(tag));
}
private void addTasksToTargetTag(String tag, String target) {
TodorooCursor<Task> tasks = taskService.query(Query.select(Task.ID).join(Join.inner(Metadata.TABLE,
Task.ID.eq(Metadata.TASK))).where(TagService.tagEq(tag, null)));
try {
for (tasks.moveToFirst(); !tasks.isAfterLast(); tasks.moveToNext()) {
Task curr = new Task(tasks);
TodorooCursor<Metadata> tagMetadata = metadataService.query(Query.select(TagService.TAG)
.where(Criterion.and(TagService.TAG.eq(target), Metadata.KEY.eq(TagService.KEY))));
try {
if (tagMetadata.getCount() == 0) {
Metadata newTag = new Metadata();
newTag.setValue(Metadata.KEY, TagService.KEY);
newTag.setValue(Metadata.TASK, curr.getId());
newTag.setValue(TagService.TAG, target);
metadataService.save(newTag);
} // else already exists for some weird reason
} finally {
tagMetadata.close();
}
}
} finally {
tasks.close();
}
}
}

@ -1,6 +1,7 @@
package com.todoroo.astrid.tags;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashSet;
import com.todoroo.andlib.data.Property.CountProperty;
@ -116,16 +117,23 @@ public final class TagService {
*/
public QueryTemplate queryTemplate(Criterion criterion) {
return new QueryTemplate().join(Join.inner(Metadata.TABLE,
Task.ID.eq(Metadata.TASK))).where(tagEq(tag, criterion));
Task.ID.eq(Metadata.TASK))).where(tagEqIgnoreCase(tag, criterion));
}
}
private static Criterion tagEq(String tag, Criterion additionalCriterion) {
public static Criterion tagEq(String tag, Criterion additionalCriterion) {
return Criterion.and(
MetadataCriteria.withKey(KEY), TAG.eq(tag),
additionalCriterion);
}
public static Criterion tagEqIgnoreCase(String tag, Criterion additionalCriterion) {
return Criterion.and(
MetadataCriteria.withKey(KEY), TAG.eqCaseInsensitive(tag),
additionalCriterion);
}
public QueryTemplate untaggedTemplate() {
return new QueryTemplate().where(Criterion.and(
Criterion.not(Task.ID.in(Query.select(Metadata.TASK).from(Metadata.TABLE).where(MetadataCriteria.withKey(KEY)))),
@ -214,12 +222,17 @@ public final class TagService {
public boolean synchronizeTags(long taskId, LinkedHashSet<String> tags) {
MetadataService service = PluginServices.getMetadataService();
HashSet<String> addedTags = new HashSet<String>();
ArrayList<Metadata> metadata = new ArrayList<Metadata>();
for(String tag : tags) {
String tagWithCase = getTagWithCase(tag); // Find if any tag exists that matches with case ignore
if (addedTags.contains(tagWithCase)) // Prevent two identical tags from being added twice (e.g. don't add "Tag, tag" as "tag, tag")
continue;
addedTags.add(tagWithCase);
Metadata item = new Metadata();
item.setValue(Metadata.KEY, KEY);
item.setValue(TAG, tag);
TagData tagData = tagDataService.getTag(tag, TagData.REMOTE_ID);
item.setValue(TAG, tagWithCase);
TagData tagData = tagDataService.getTag(tagWithCase, TagData.REMOTE_ID);
if(tagData != null)
item.setValue(REMOTE_ID, tagData.getValue(TagData.REMOTE_ID));
@ -229,13 +242,53 @@ public final class TagService {
return service.synchronizeMetadata(taskId, metadata, Metadata.KEY.eq(KEY));
}
/**
* If a tag already exists in the database that case insensitively matches the
* given tag, return that. Otherwise, return the argument
* @param tag
* @return
*/
public String getTagWithCase(String tag) {
MetadataService service = PluginServices.getMetadataService();
String tagWithCase = tag;
TodorooCursor<Metadata> tagMetadata = service.query(Query.select(TAG).where(TagService.tagEqIgnoreCase(tag, Criterion.all)).limit(1));
try {
if (tagMetadata.getCount() > 0) {
tagMetadata.moveToFirst();
Metadata tagMatch = new Metadata(tagMetadata);
tagWithCase = tagMatch.getValue(TagService.TAG);
} else {
TodorooCursor<TagData> tagData = tagDataService.query(Query.select(TagData.NAME).where(TagData.NAME.eqCaseInsensitive(tag)));
try {
if (tagData.getCount() > 0) {
tagData.moveToFirst();
tagWithCase = new TagData(tagData).getValue(TagData.NAME);
}
} finally {
tagData.close();
}
}
} finally {
tagMetadata.close();
}
return tagWithCase;
}
public int delete(String tag) {
invalidateTaskCache(tag);
return PluginServices.getMetadataService().deleteWhere(tagEq(tag, Criterion.all));
return PluginServices.getMetadataService().deleteWhere(tagEqIgnoreCase(tag, Criterion.all));
}
public int rename(String oldTag, String newTag) {
// First remove newTag from all tasks that have both oldTag and newTag.
return renameHelper(oldTag, newTag, false);
}
public int renameCaseSensitive(String oldTag, String newTag) { // Need this for tag case migration process
return renameHelper(oldTag, newTag, true);
}
private int renameHelper(String oldTag, String newTag, boolean caseSensitive) {
// First remove newTag from all tasks that have both oldTag and newTag.
MetadataService metadataService = PluginServices.getMetadataService();
metadataService.deleteWhere(
Criterion.and(
@ -245,11 +298,16 @@ public final class TagService {
// Then rename all instances of oldTag to newTag.
Metadata metadata = new Metadata();
metadata.setValue(TAG, newTag);
int ret = metadataService.update(tagEq(oldTag, Criterion.all), metadata);
int ret;
if (caseSensitive)
ret = metadataService.update(tagEq(oldTag, Criterion.all), metadata);
else
ret = metadataService.update(tagEqIgnoreCase(oldTag, Criterion.all), metadata);
invalidateTaskCache(newTag);
return ret;
}
private Query rowsWithTag(String tag, Field... projections) {
return Query.select(projections).from(Metadata.TABLE).where(Metadata.VALUE1.eq(tag));
}

@ -80,7 +80,7 @@ public class TagDataService {
* @return null if doesn't exist
*/
public TagData getTag(String name, Property<?>... properties) {
TodorooCursor<TagData> cursor = tagDataDao.query(Query.select(properties).where(TagData.NAME.eq(name)));
TodorooCursor<TagData> cursor = tagDataDao.query(Query.select(properties).where(TagData.NAME.eqCaseInsensitive(name)));
try {
if(cursor.getCount() == 0)
return null;

@ -30,6 +30,7 @@ import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.gtasks.GtasksPreferenceService;
import com.todoroo.astrid.notes.NoteMetadata;
import com.todoroo.astrid.producteev.sync.ProducteevDataService;
import com.todoroo.astrid.tags.TagCaseMigrator;
import com.todoroo.astrid.utility.AstridPreferences;
@ -110,6 +111,9 @@ public final class UpgradeService {
if(from < V3_1_0)
new Astrid2To3UpgradeHelper().upgrade3To3_1(context, from);
if (from <= V3_8_3_1)
new TagCaseMigrator().performTagCaseMigration();
} finally {
DialogUtilities.dismissDialog((Activity)context, dialog);
}

Loading…
Cancel
Save