Add 'tags' table

pull/618/head
Alex Baker 6 years ago
parent b8e4474e64
commit 515223b6a7

@ -0,0 +1,723 @@
{
"formatVersion": 1,
"database": {
"version": 49,
"identityHash": "75f166e3168884f68b0be6dfeb79de24",
"entities": [
{
"tableName": "notification",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `task` INTEGER NOT NULL, `timestamp` INTEGER NOT NULL, `type` INTEGER NOT NULL)",
"fields": [
{
"fieldPath": "uid",
"columnName": "uid",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "taskId",
"columnName": "task",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "timestamp",
"columnName": "timestamp",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"uid"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_notification_task",
"unique": true,
"columnNames": [
"task"
],
"createSql": "CREATE UNIQUE INDEX `index_notification_task` ON `${TABLE_NAME}` (`task`)"
}
],
"foreignKeys": []
},
{
"tableName": "tagdata",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `remoteId` TEXT, `name` TEXT, `color` INTEGER, `tagOrdering` TEXT, `deleted` INTEGER)",
"fields": [
{
"fieldPath": "id",
"columnName": "_id",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "remoteId",
"columnName": "remoteId",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "color",
"columnName": "color",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "tagOrdering",
"columnName": "tagOrdering",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "deleted",
"columnName": "deleted",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"_id"
],
"autoGenerate": true
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "userActivity",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `remoteId` TEXT, `action` TEXT, `message` TEXT, `picture` TEXT, `target_id` TEXT, `created_at` INTEGER, `deleted_at` INTEGER)",
"fields": [
{
"fieldPath": "id",
"columnName": "_id",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "remoteId",
"columnName": "remoteId",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "action",
"columnName": "action",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "message",
"columnName": "message",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "picture",
"columnName": "picture",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "targetId",
"columnName": "target_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "created",
"columnName": "created_at",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "deleted",
"columnName": "deleted_at",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"_id"
],
"autoGenerate": true
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "task_attachments",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `remoteId` TEXT, `task_id` TEXT, `name` TEXT, `path` TEXT, `content_type` TEXT, `deleted_at` INTEGER)",
"fields": [
{
"fieldPath": "id",
"columnName": "_id",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "remoteId",
"columnName": "remoteId",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "taskId",
"columnName": "task_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "path",
"columnName": "path",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "contentType",
"columnName": "content_type",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "deleted",
"columnName": "deleted_at",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"_id"
],
"autoGenerate": true
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "task_list_metadata",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `remoteId` TEXT, `tag_uuid` TEXT, `filter` TEXT, `task_ids` TEXT)",
"fields": [
{
"fieldPath": "id",
"columnName": "_id",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "remoteId",
"columnName": "remoteId",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "tagUuid",
"columnName": "tag_uuid",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "filter",
"columnName": "filter",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "taskIds",
"columnName": "task_ids",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"_id"
],
"autoGenerate": true
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "store",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `type` TEXT, `item` TEXT, `value` TEXT, `value2` TEXT, `value3` TEXT, `value4` TEXT, `deleted` INTEGER)",
"fields": [
{
"fieldPath": "id",
"columnName": "_id",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "item",
"columnName": "item",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "value",
"columnName": "value",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "value2",
"columnName": "value2",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "value3",
"columnName": "value3",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "value4",
"columnName": "value4",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "deleted",
"columnName": "deleted",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"_id"
],
"autoGenerate": true
},
"indices": [
{
"name": "so_id",
"unique": false,
"columnNames": [
"type",
"item"
],
"createSql": "CREATE INDEX `so_id` ON `${TABLE_NAME}` (`type`, `item`)"
}
],
"foreignKeys": []
},
{
"tableName": "tasks",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `title` TEXT, `importance` INTEGER, `dueDate` INTEGER, `hideUntil` INTEGER, `created` INTEGER, `modified` INTEGER, `completed` INTEGER, `deleted` INTEGER, `notes` TEXT, `estimatedSeconds` INTEGER, `elapsedSeconds` INTEGER, `timerStart` INTEGER, `notificationFlags` INTEGER, `notifications` INTEGER, `lastNotified` INTEGER, `snoozeTime` INTEGER, `recurrence` TEXT, `repeatUntil` INTEGER, `calendarUri` TEXT, `remoteId` TEXT)",
"fields": [
{
"fieldPath": "id",
"columnName": "_id",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "title",
"columnName": "title",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "importance",
"columnName": "importance",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "dueDate",
"columnName": "dueDate",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "hideUntil",
"columnName": "hideUntil",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "created",
"columnName": "created",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "modified",
"columnName": "modified",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "completed",
"columnName": "completed",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "deleted",
"columnName": "deleted",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "notes",
"columnName": "notes",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "estimatedSeconds",
"columnName": "estimatedSeconds",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "elapsedSeconds",
"columnName": "elapsedSeconds",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "timerStart",
"columnName": "timerStart",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "notificationFlags",
"columnName": "notificationFlags",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "notifications",
"columnName": "notifications",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "lastNotified",
"columnName": "lastNotified",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "snoozeTime",
"columnName": "snoozeTime",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "recurrence",
"columnName": "recurrence",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "repeatUntil",
"columnName": "repeatUntil",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "calendarUri",
"columnName": "calendarUri",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "remoteId",
"columnName": "remoteId",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"_id"
],
"autoGenerate": true
},
"indices": [
{
"name": "t_rid",
"unique": true,
"columnNames": [
"remoteId"
],
"createSql": "CREATE UNIQUE INDEX `t_rid` ON `${TABLE_NAME}` (`remoteId`)"
}
],
"foreignKeys": []
},
{
"tableName": "metadata",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `task` INTEGER, `key` TEXT, `value` TEXT, `value2` TEXT, `value3` TEXT, `value4` TEXT, `value5` TEXT, `value6` TEXT, `value7` TEXT, `created` INTEGER, `deleted` INTEGER)",
"fields": [
{
"fieldPath": "id",
"columnName": "_id",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "task",
"columnName": "task",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "key",
"columnName": "key",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "value1",
"columnName": "value",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "value2",
"columnName": "value2",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "value3",
"columnName": "value3",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "value4",
"columnName": "value4",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "value5",
"columnName": "value5",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "value6",
"columnName": "value6",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "value7",
"columnName": "value7",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "created",
"columnName": "created",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "deleted",
"columnName": "deleted",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"_id"
],
"autoGenerate": true
},
"indices": [
{
"name": "md_tid",
"unique": false,
"columnNames": [
"task"
],
"createSql": "CREATE INDEX `md_tid` ON `${TABLE_NAME}` (`task`)"
},
{
"name": "md_tkid",
"unique": false,
"columnNames": [
"task",
"key"
],
"createSql": "CREATE INDEX `md_tkid` ON `${TABLE_NAME}` (`task`, `key`)"
}
],
"foreignKeys": []
},
{
"tableName": "alarms",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `task` INTEGER NOT NULL, `time` INTEGER NOT NULL)",
"fields": [
{
"fieldPath": "id",
"columnName": "_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "task",
"columnName": "task",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "time",
"columnName": "time",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"_id"
],
"autoGenerate": true
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "locations",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `task` INTEGER NOT NULL, `name` TEXT, `latitude` REAL NOT NULL, `longitude` REAL NOT NULL, `radius` INTEGER NOT NULL)",
"fields": [
{
"fieldPath": "id",
"columnName": "_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "task",
"columnName": "task",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "latitude",
"columnName": "latitude",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "longitude",
"columnName": "longitude",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "radius",
"columnName": "radius",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"_id"
],
"autoGenerate": true
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "tags",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `task` INTEGER NOT NULL, `name` TEXT, `tag_uid` TEXT, `task_uid` TEXT)",
"fields": [
{
"fieldPath": "id",
"columnName": "_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "task",
"columnName": "task",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "tagUid",
"columnName": "tag_uid",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "taskUid",
"columnName": "task_uid",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"_id"
],
"autoGenerate": true
},
"indices": [],
"foreignKeys": []
}
],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"75f166e3168884f68b0be6dfeb79de24\")"
]
}
}

@ -21,7 +21,6 @@ import com.todoroo.astrid.activity.TaskListFragment;
import com.todoroo.astrid.data.RemoteModel;
import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.data.TaskAttachment;
import com.todoroo.astrid.tags.TaskToTagMetadata;
import java.util.ArrayList;
import java.util.List;
@ -51,7 +50,7 @@ public class TaskAdapter extends CursorAdapter implements Filterable {
void onCompletedTask(Task item, boolean newState);
}
public static final StringProperty TAGS = new StringProperty(null, "group_concat(nullif(" + TaskListFragment.TAGS_METADATA_JOIN + "." + TaskToTagMetadata.TAG_UUID.name + ", '')"+ ", ',')").as("tags");
public static final StringProperty TAGS = new StringProperty(null, "group_concat(nullif(" + TaskListFragment.TAGS_METADATA_JOIN + ".tag_uid, '')"+ ", ',')").as("tags");
public static final LongProperty FILE_ID_PROPERTY = TaskAttachment.ID.cloneAs(TaskListFragment.FILE_METADATA_JOIN, "fileId");
public static final IntegerProperty HAS_NOTES_PROPERTY = new IntegerProperty(null, "length(" + Task.NOTES + ") > 0").as("hasNotes");

@ -85,7 +85,9 @@ public class Filter extends FilterListItem {
this.listingTitle = listingTitle;
this.sqlQuery = sqlQuery;
this.filterOverride = null;
this.valuesForNewTasks.putAll(valuesForNewTasks);
if (valuesForNewTasks != null) {
this.valuesForNewTasks.putAll(valuesForNewTasks);
}
}
public String getSqlQuery() {

@ -8,12 +8,11 @@ import com.todoroo.andlib.sql.Field;
import com.todoroo.andlib.sql.Join;
import com.todoroo.andlib.sql.QueryTemplate;
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.tags.TaskToTagMetadata;
import org.tasks.R;
import org.tasks.data.Tag;
import java.util.HashMap;
import java.util.Map;
@ -40,20 +39,16 @@ public class TagFilter extends Filter {
}
private static QueryTemplate queryTemplate(String uuid) {
Criterion fullCriterion = Criterion.and(
Field.field("mtags." + Metadata.KEY.name).eq(TaskToTagMetadata.KEY),
Field.field("mtags." + TaskToTagMetadata.TAG_UUID.name).eq(uuid),
Field.field("mtags." + Metadata.DELETION_DATE.name).eq(0),
TaskDao.TaskCriteria.activeAndVisible());
return new QueryTemplate().join(Join.inner(Metadata.TABLE.as("mtags"), Task.UUID.eq(Field.field("mtags." + TaskToTagMetadata.TASK_UUID.name))))
.where(fullCriterion);
return new QueryTemplate()
.join(Join.inner(Tag.TABLE.as("mtags"), Task.UUID.eq(Field.field("mtags.task_uid"))))
.where(Criterion.and(
Field.field("mtags.tag_uid").eq(uuid),
TaskDao.TaskCriteria.activeAndVisible()));
}
private static Map<String, Object> getValuesForNewTask(TagData tagData) {
Map<String, Object> values = new HashMap<>();
values.put(Metadata.KEY.name, TaskToTagMetadata.KEY);
values.put(TaskToTagMetadata.TAG_NAME.name, tagData.getName());
values.put(TaskToTagMetadata.TAG_UUID.name, tagData.getRemoteId());
values.put(Tag.KEY, tagData.getName());
return values;
}

@ -41,6 +41,8 @@ class BackupConstants {
public static final String LOCATION_TAG = "location";
public static final String TAG_TAG = "tag";
/** Tag containing a tagdata item */
public static final String TAGDATA_TAG = "tagdata";

@ -35,6 +35,8 @@ import org.tasks.data.Alarm;
import org.tasks.data.AlarmDao;
import org.tasks.data.Location;
import org.tasks.data.LocationDao;
import org.tasks.data.Tag;
import org.tasks.data.TagDao;
import org.tasks.preferences.Preferences;
import org.xmlpull.v1.XmlSerializer;
@ -67,6 +69,7 @@ public class TasksXmlExporter {
private final MetadataDao metadataDao;
private final AlarmDao alarmDao;
private final LocationDao locationDao;
private final TagDao tagDao;
private final TaskDao taskDao;
private UserActivityDao userActivityDao;
private final Preferences preferences;
@ -97,7 +100,7 @@ public class TasksXmlExporter {
@Inject
public TasksXmlExporter(TagDataDao tagDataDao, MetadataDao metadataDao, TaskDao taskDao, UserActivityDao userActivityDao,
Preferences preferences, AlarmDao alarmDao, LocationDao locationDao) {
Preferences preferences, AlarmDao alarmDao, LocationDao locationDao, TagDao tagDao) {
this.tagDataDao = tagDataDao;
this.metadataDao = metadataDao;
this.taskDao = taskDao;
@ -105,6 +108,7 @@ public class TasksXmlExporter {
this.preferences = preferences;
this.alarmDao = alarmDao;
this.locationDao = locationDao;
this.tagDao = tagDao;
}
public void exportTasks(final Context context, final ExportType exportType, @Nullable final ProgressDialog progressDialog) {
@ -224,19 +228,29 @@ public class TasksXmlExporter {
throw new RuntimeException(e);
}
}
XmlWriter writer = new XmlWriter(xml);
for (Alarm alarm : alarmDao.getAlarms(task.getId())) {
try {
xml.startTag(null, BackupConstants.ALARM_TAG);
alarm.writeToXml(new XmlWriter(xml));
alarm.writeToXml(writer);
xml.endTag(null, BackupConstants.ALARM_TAG);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
for (Tag tag : tagDao.getTagsForTask(task.getId())) {
try {
xml.startTag(null, BackupConstants.TAG_TAG);
tag.writeToXml(writer);
xml.endTag(null, BackupConstants.TAG_TAG);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
for (Location location : locationDao.getGeofences(task.getId())) {
try {
xml.startTag(null, BackupConstants.LOCATION_TAG);
location.writeToXml(new XmlWriter(xml));
location.writeToXml(writer);
xml.endTag(null, BackupConstants.LOCATION_TAG);
} catch (IOException e) {
throw new RuntimeException(e);

@ -11,7 +11,6 @@ import android.content.res.Resources;
import android.os.Handler;
import android.text.TextUtils;
import com.google.common.collect.Iterables;
import com.todoroo.andlib.data.AbstractModel;
import com.todoroo.andlib.data.Property;
import com.todoroo.andlib.data.Property.PropertyVisitor;
@ -23,19 +22,19 @@ import com.todoroo.astrid.dao.TagDataDao;
import com.todoroo.astrid.dao.TaskDao;
import com.todoroo.astrid.dao.UserActivityDao;
import com.todoroo.astrid.data.Metadata;
import com.todoroo.astrid.data.RemoteModel;
import com.todoroo.astrid.data.TagData;
import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.data.UserActivity;
import com.todoroo.astrid.tags.TaskToTagMetadata;
import org.tasks.LocalBroadcastManager;
import org.tasks.R;
import org.tasks.backup.XmlReader;
import org.tasks.data.Alarm;
import org.tasks.data.AlarmDao;
import org.tasks.data.LocationDao;
import org.tasks.data.Location;
import org.tasks.data.LocationDao;
import org.tasks.data.Tag;
import org.tasks.data.TagDao;
import org.tasks.dialogs.DialogBuilder;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@ -43,7 +42,6 @@ import org.xmlpull.v1.XmlPullParserFactory;
import java.io.FileReader;
import java.io.IOException;
import java.util.List;
import javax.inject.Inject;
@ -58,6 +56,7 @@ public class TasksXmlImporter {
private final TaskDao taskDao;
private LocalBroadcastManager localBroadcastManager;
private final AlarmDao alarmDao;
private final TagDao tagDao;
private final LocationDao locationDao;
private Activity activity;
@ -76,7 +75,7 @@ public class TasksXmlImporter {
@Inject
public TasksXmlImporter(TagDataDao tagDataDao, MetadataDao metadataDao, UserActivityDao userActivityDao,
DialogBuilder dialogBuilder, TaskDao taskDao, LocationDao locationDao,
LocalBroadcastManager localBroadcastManager, AlarmDao alarmDao) {
LocalBroadcastManager localBroadcastManager, AlarmDao alarmDao, TagDao tagDao) {
this.tagDataDao = tagDataDao;
this.metadataDao = metadataDao;
this.userActivityDao = userActivityDao;
@ -85,6 +84,7 @@ public class TasksXmlImporter {
this.locationDao = locationDao;
this.localBroadcastManager = localBroadcastManager;
this.alarmDao = alarmDao;
this.tagDao = tagDao;
}
public void importTasks(Activity activity, String input, ProgressDialog progressDialog) {
@ -259,6 +259,16 @@ public class TasksXmlImporter {
locationDao.insert(location);
}
void parseTag() {
if (!currentTask.isSaved()) {
return;
}
Tag tag = new Tag(new XmlReader(xpp));
tagDao.insert(tag);
}
void parseMetadata(int format) {
if(!currentTask.isSaved()) {
return;
@ -269,38 +279,36 @@ public class TasksXmlImporter {
Alarm alarm = new Alarm();
alarm.setTask(currentTask.getId());
alarm.setTime(Long.valueOf(metadata.getValue(Metadata.VALUE1)));
alarmDao.insert(new Alarm());
return;
}
if (metadata.getKey().equals(TaskToTagMetadata.KEY)) {
String uuid = metadata.getValue(TaskToTagMetadata.TAG_UUID);
List<Metadata> metadatas = metadataDao.byTaskAndKey(currentTask.getId(), TaskToTagMetadata.KEY);
if (Iterables.any(metadatas, existing -> uuid.equals(existing.getValue(TaskToTagMetadata.TAG_UUID)))) {
return;
}
}
metadata.setId(Metadata.NO_ID);
metadata.setTask(currentTask.getId());
metadataDao.persist(metadata);
// Construct the TagData from Metadata
// Fix for failed backup, Version before 4.6.10
if (format == 2) {
String key = metadata.getKey();
alarmDao.insert(alarm);
} else if (metadata.getKey().equals("geofence")) {
Location location = new Location();
location.setTask(currentTask.getId());
location.setName(metadata.getValue(Metadata.VALUE1));
location.setLatitude(Double.valueOf(metadata.getValue(Metadata.VALUE2)));
location.setLongitude(Double.valueOf(metadata.getValue(Metadata.VALUE3)));
location.setRadius(Integer.valueOf(metadata.getValue(Metadata.VALUE4)));
locationDao.insert(location);
} else if (metadata.getKey().equals("tags-tag")) {
String tagUid = metadata.getValue(Metadata.VALUE2);
String name = metadata.getValue(Metadata.VALUE1);
String uuid = metadata.getValue(Metadata.VALUE2);
long deletionDate = metadata.getDeletionDate();
// UUID is uniquely for every TagData, so we don't need to test the name
TagData tagData = tagDataDao.getByUuid(uuid);
//If you sync with Google tasks it adds some Google task metadata.
//For this metadata we don't create a list!
if(key.equals(TaskToTagMetadata.KEY) && tagData == null && deletionDate == 0) {
tagData = new TagData();
tagData.setId(RemoteModel.NO_ID);
tagData.setRemoteId(uuid);
tagData.setName(name);
tagDataDao.insert(tagData);
if (tagDao.getTagByTaskAndTagUid(currentTask.getId(), tagUid) == null) {
tagDao.insert(new Tag(currentTask.getId(), currentTask.getUuid(), name, tagUid));
}
// Construct the TagData from Metadata
// Fix for failed backup, Version before 4.6.10
if (format == 2) {
TagData tagData = tagDataDao.getByUuid(tagUid);
if (tagData == null) {
tagData = new TagData();
tagData.setRemoteId(tagUid);
tagData.setName(name);
tagDataDao.insert(tagData);
}
}
} else {
metadata.setId(Metadata.NO_ID);
metadata.setTask(currentTask.getId());
metadataDao.persist(metadata);
}
}
@ -398,6 +406,9 @@ public class TasksXmlImporter {
case BackupConstants.LOCATION_TAG:
parseLocation();
break;
case BackupConstants.TAG_TAG:
parseTag();
break;
}
} catch (Exception e) {
errorCount++;

@ -9,19 +9,18 @@ import android.content.Context;
import android.content.res.Resources;
import com.todoroo.andlib.sql.Criterion;
import com.todoroo.andlib.sql.Field;
import com.todoroo.andlib.sql.Order;
import com.todoroo.andlib.sql.Query;
import com.todoroo.andlib.sql.QueryTemplate;
import com.todoroo.andlib.utility.AndroidUtilities;
import com.todoroo.astrid.api.Filter;
import com.todoroo.astrid.api.PermaSql;
import com.todoroo.astrid.dao.MetadataDao;
import com.todoroo.astrid.dao.TaskDao.TaskCriteria;
import com.todoroo.astrid.data.Metadata;
import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.tags.TaskToTagMetadata;
import org.tasks.R;
import org.tasks.data.Tag;
import org.tasks.injection.ForApplication;
import org.tasks.preferences.Preferences;
@ -109,8 +108,7 @@ public final class BuiltInFilterExposer {
public static Filter getUncategorizedFilter(Resources r) {
return new Filter(r.getString(R.string.tag_FEx_untagged),
new QueryTemplate().where(Criterion.and(
Criterion.not(Task.UUID.in(Query.select(TaskToTagMetadata.TASK_UUID).from(Metadata.TABLE)
.where(Criterion.and(MetadataDao.MetadataCriteria.withKey(TaskToTagMetadata.KEY), Metadata.DELETION_DATE.eq(0))))),
Criterion.not(Task.UUID.in(Query.select(Field.field("task_uid")).from(Tag.TABLE))),
TaskCriteria.isActive(),
TaskCriteria.isVisible())));
}

@ -27,6 +27,8 @@ import org.tasks.data.Alarm;
import org.tasks.data.AlarmDao;
import org.tasks.data.Location;
import org.tasks.data.LocationDao;
import org.tasks.data.Tag;
import org.tasks.data.TagDao;
import org.tasks.notifications.Notification;
import org.tasks.notifications.NotificationDao;
@ -51,9 +53,10 @@ import timber.log.Timber;
Task.class,
Metadata.class,
Alarm.class,
Location.class
Location.class,
Tag.class
},
version = 48)
version = 49)
public abstract class Database extends RoomDatabase {
public abstract NotificationDao notificationDao();
@ -64,6 +67,7 @@ public abstract class Database extends RoomDatabase {
public abstract StoreObjectDao getStoreObjectDao();
public abstract AlarmDao getAlarmDao();
public abstract LocationDao getGeofenceDao();
public abstract TagDao getTagDao();
public static final String NAME = "database";

@ -29,6 +29,9 @@ public abstract class TagDataDao {
@Query("SELECT * FROM tagdata WHERE remoteId = :uuid LIMIT 1")
public abstract TagData getByUuid(String uuid);
@Query("SELECT * FROM tagdata WHERE remoteId = :uuid")
public abstract List<TagData> getAllByUuid(String uuid);
@Query("SELECT * FROM tagdata WHERE deleted = 0 AND name IS NOT NULL ORDER BY UPPER(name) ASC")
public abstract List<TagData> tagDataOrderedByName();

@ -10,8 +10,10 @@ import android.os.Parcelable;
import org.tasks.backup.XmlReader;
import org.tasks.backup.XmlWriter;
import java.io.Serializable;
@Entity(tableName = "tagdata")
public final class TagData implements Parcelable {
public final class TagData implements Parcelable, Serializable {
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "_id")

@ -23,6 +23,7 @@ import com.todoroo.astrid.data.TagData;
import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.tags.TagService;
import org.tasks.data.TagDao;
import org.tasks.injection.ContentProviderComponent;
import org.tasks.injection.InjectingContentProvider;
import org.tasks.ui.CheckBoxes;
@ -80,6 +81,7 @@ public class Astrid2TaskProvider extends InjectingContentProvider {
@Inject Lazy<TagService> tagService;
@Inject Lazy<CheckBoxes> checkBoxes;
@Inject Lazy<TaskDao> taskDao;
@Inject Lazy<TagDao> tagDao;
static {
URI_MATCHER.addURI(AUTHORITY, "tasks", URI_TASKS);
@ -120,8 +122,7 @@ public class Astrid2TaskProvider extends InjectingContentProvider {
*/
private Cursor getTags() {
TagData[] tags = tagService.get().getGroupedTags(TagService.GROUPED_TAGS_BY_SIZE,
Criterion.all);
TagData[] tags = tagService.get().getGroupedTags();
MatrixCursor ret = new MatrixCursor(TAGS_FIELD_LIST);
@ -216,6 +217,6 @@ public class Astrid2TaskProvider extends InjectingContentProvider {
* @return empty string if no tags, otherwise string
*/
private String getTagsAsString(long taskId, String separator) {
return Joiner.on(separator).join(tagService.get().getTagNames(taskId));
return Joiner.on(separator).join(tagDao.get().getTagNames(taskId));
}
}

@ -13,20 +13,19 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Multimaps;
import com.todoroo.andlib.sql.Criterion;
import com.todoroo.astrid.dao.Database;
import com.todoroo.astrid.dao.MetadataDao;
import com.todoroo.astrid.dao.TagDataDao;
import com.todoroo.astrid.data.Metadata;
import com.todoroo.astrid.data.TagData;
import com.todoroo.astrid.tags.TagService;
import com.todoroo.astrid.tags.TaskToTagMetadata;
import org.tasks.BuildConfig;
import org.tasks.LocalBroadcastManager;
import org.tasks.R;
import org.tasks.analytics.Tracker;
import org.tasks.analytics.Tracking;
import org.tasks.data.Tag;
import org.tasks.data.TagDao;
import org.tasks.injection.ForApplication;
import org.tasks.preferences.Preferences;
import org.tasks.scheduling.BackgroundScheduler;
@ -52,12 +51,13 @@ public class StartupService {
private final MetadataDao metadataDao;
private final LocalBroadcastManager localBroadcastManager;
private final Context context;
private final TagDao tagDao;
@Inject
public StartupService(Database database, Preferences preferences, TaskDeleter taskDeleter,
Tracker tracker, TagDataDao tagDataDao, TagService tagService,
MetadataDao metadataDao, LocalBroadcastManager localBroadcastManager,
@ForApplication Context context) {
@ForApplication Context context, TagDao tagDao) {
this.database = database;
this.preferences = preferences;
this.taskDeleter = taskDeleter;
@ -67,6 +67,7 @@ public class StartupService {
this.metadataDao = metadataDao;
this.localBroadcastManager = localBroadcastManager;
this.context = context;
this.tagDao = tagDao;
}
/** Called when this application is started up */
@ -152,14 +153,10 @@ public class StartupService {
}
private void removeDuplicateTagMetadata(String uuid) {
Criterion fullCriterion = Criterion.and(
Metadata.KEY.eq(TaskToTagMetadata.KEY),
TaskToTagMetadata.TAG_UUID.eq(uuid),
Metadata.DELETION_DATE.eq(0));
List<Metadata> metadatas = metadataDao.toList(fullCriterion);
ImmutableListMultimap<Long, Metadata> metadataByTask = Multimaps.index(metadatas, Metadata::getTask);
List<Tag> metadatas = tagDao.getByTagUid(uuid);
ImmutableListMultimap<Long, Tag> metadataByTask = Multimaps.index(metadatas, Tag::getTask);
for (Long key : metadataByTask.keySet()) {
ImmutableList<Metadata> tagData = metadataByTask.get(key);
ImmutableList<Tag> tagData = metadataByTask.get(key);
for (int i = 1 ; i < tagData.size() ; i++) {
metadataDao.delete(tagData.get(i).getId());
}

@ -5,22 +5,21 @@ import android.net.Uri;
import android.text.TextUtils;
import com.todoroo.andlib.data.Property;
import com.todoroo.andlib.sql.Criterion;
import com.todoroo.andlib.utility.AndroidUtilities;
import com.todoroo.astrid.api.PermaSql;
import com.todoroo.astrid.dao.MetadataDao;
import com.todoroo.astrid.dao.TagDataDao;
import com.todoroo.astrid.dao.TaskDao;
import com.todoroo.astrid.data.Metadata;
import com.todoroo.astrid.data.RemoteModel;
import com.todoroo.astrid.data.SyncFlags;
import com.todoroo.astrid.data.TagData;
import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.gcal.GCalHelper;
import com.todoroo.astrid.tags.TagService;
import com.todoroo.astrid.tags.TaskToTagMetadata;
import com.todoroo.astrid.utility.TitleParser;
import org.tasks.data.Tag;
import org.tasks.data.TagDao;
import org.tasks.preferences.Preferences;
import java.util.ArrayList;
@ -35,19 +34,21 @@ public class TaskCreator {
private final GCalHelper gcalHelper;
private final Preferences preferences;
private final MetadataDao metadataDao;
private final TagDao tagDao;
private final TagDataDao tagDataDao;
private final TaskDao taskDao;
private final TagService tagService;
@Inject
public TaskCreator(GCalHelper gcalHelper, Preferences preferences, MetadataDao metadataDao,
TagDataDao tagDataDao, TaskDao taskDao, TagService tagService) {
TagDataDao tagDataDao, TaskDao taskDao, TagService tagService, TagDao tagDao) {
this.gcalHelper = gcalHelper;
this.preferences = preferences;
this.metadataDao = metadataDao;
this.tagDataDao = tagDataDao;
this.taskDao = taskDao;
this.tagService = tagService;
this.tagDao = tagDao;
}
public Task basicQuickAddTask(String title) {
@ -96,6 +97,10 @@ public class TaskCreator {
outer: for (Map.Entry<String, Object> item : values.entrySet()) {
String key = item.getKey();
Object value = item.getValue();
if (key.equals(Tag.KEY)) {
continue;
}
if (value instanceof String) {
value = PermaSql.replacePlaceholders((String) value);
}
@ -113,6 +118,11 @@ public class TaskCreator {
}
saveWithoutPublishingFilterUpdate(task);
if (values != null && values.containsKey(Tag.KEY)) {
createLink(task, (String) values.get(Tag.KEY));
}
for(String tag : tags) {
createLink(task, tag);
}
@ -121,17 +131,7 @@ public class TaskCreator {
Metadata metadata = new Metadata();
metadata.setTask(task.getId());
metadata.mergeWith(forMetadata);
if (TaskToTagMetadata.KEY.equals(metadata.getKey())) {
if (metadata.containsNonNullValue(TaskToTagMetadata.TAG_UUID) && !RemoteModel.NO_UUID.equals(metadata.getValue(TaskToTagMetadata.TAG_UUID))) {
// This is more efficient
createLink(task, metadata.getValue(TaskToTagMetadata.TAG_NAME), metadata.getValue(TaskToTagMetadata.TAG_UUID));
} else {
// This is necessary for backwards compatibility
createLink(task, metadata.getValue(TaskToTagMetadata.TAG_NAME));
}
} else {
metadataDao.persist(metadata);
}
metadataDao.persist(metadata);
}
return task;
@ -152,11 +152,8 @@ public class TaskCreator {
}
private void createLink(Task task, String tagName, String tagUuid) {
Metadata link = TaskToTagMetadata.newTagMetadata(task.getId(), task.getUuid(), tagName, tagUuid);
if (metadataDao.update(Criterion.and(MetadataDao.MetadataCriteria.byTaskAndwithKey(task.getId(), TaskToTagMetadata.KEY),
TaskToTagMetadata.TASK_UUID.eq(task.getUUID()), TaskToTagMetadata.TAG_UUID.eq(tagUuid)), link) <= 0) {
metadataDao.createNew(link);
}
Tag link = new Tag(task.getId(), task.getUuid(), tagName, tagUuid);
tagDao.insert(link);
}
/**

@ -1,5 +1,6 @@
package com.todoroo.astrid.service;
import com.google.common.collect.Lists;
import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.astrid.dao.MetadataDao;
import com.todoroo.astrid.dao.TaskDao;
@ -8,9 +9,10 @@ import com.todoroo.astrid.data.SyncFlags;
import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.gcal.GCalHelper;
import com.todoroo.astrid.gtasks.GtasksMetadata;
import com.todoroo.astrid.tags.TaskToTagMetadata;
import org.tasks.LocalBroadcastManager;
import org.tasks.data.Tag;
import org.tasks.data.TagDao;
import java.util.ArrayList;
import java.util.List;
@ -24,15 +26,17 @@ public class TaskDuplicator {
private final GCalHelper gcalHelper;
private final MetadataDao metadataDao;
private final TaskDao taskDao;
private final TagDao tagDao;
private final LocalBroadcastManager localBroadcastManager;
@Inject
public TaskDuplicator(GCalHelper gcalHelper, MetadataDao metadataDao, TaskDao taskDao,
LocalBroadcastManager localBroadcastManager) {
LocalBroadcastManager localBroadcastManager, TagDao tagDao) {
this.gcalHelper = gcalHelper;
this.metadataDao = metadataDao;
this.taskDao = taskDao;
this.localBroadcastManager = localBroadcastManager;
this.tagDao = tagDao;
}
public List<Task> duplicate(List<Task> tasks) {
@ -63,6 +67,10 @@ public class TaskDuplicator {
taskDao.save(clone);
tagDao.insert(Lists.transform(
tagDao.getTagsForTask(original.getId()),
tag -> new Tag(clone.getId(), clone.getUuid(), tag.getName(), tag.getTagUid())));
for (Metadata oldMetadata : metadataList) {
if(!oldMetadata.containsNonNullValue(Metadata.KEY)) {
continue;
@ -72,12 +80,6 @@ public class TaskDuplicator {
Metadata gtaskMetadata = GtasksMetadata.createEmptyMetadataWithoutList(clone.getId());
gtaskMetadata.setValue(GtasksMetadata.LIST_ID, oldMetadata.getValue(GtasksMetadata.LIST_ID));
metadataDao.createNew(gtaskMetadata);
} else if (TaskToTagMetadata.KEY.equals(oldMetadata.getKey())) {
Metadata metadata = new Metadata(oldMetadata);
metadata.setValue(TaskToTagMetadata.TASK_UUID, clone.getUuid());
metadata.setTask(clone.getId());
metadata.clearValue(Metadata.ID);
metadataDao.createNew(metadata);
}
}

@ -7,20 +7,10 @@ package com.todoroo.astrid.tags;
import android.text.TextUtils;
import com.google.common.collect.Iterables;
import com.todoroo.andlib.data.Property.CountProperty;
import com.todoroo.andlib.sql.Criterion;
import com.todoroo.andlib.sql.Functions;
import com.todoroo.andlib.sql.Join;
import com.todoroo.andlib.sql.Order;
import com.todoroo.andlib.sql.Query;
import com.todoroo.astrid.dao.MetadataDao;
import com.todoroo.astrid.dao.MetadataDao.MetadataCriteria;
import com.todoroo.astrid.dao.TagDataDao;
import com.todoroo.astrid.data.Metadata;
import com.todoroo.astrid.data.TagData;
import com.todoroo.astrid.data.Task;
import org.tasks.data.TagDao;
import org.tasks.injection.ApplicationScope;
import java.util.ArrayList;
@ -40,43 +30,23 @@ import static com.google.common.collect.Lists.transform;
@ApplicationScope
public final class TagService {
private final MetadataDao metadataDao;
private final TagDataDao tagDataDao;
private final TagDao tagDao;
@Inject
public TagService(MetadataDao metadataDao, TagDataDao tagDataDao) {
this.metadataDao = metadataDao;
public TagService(TagDataDao tagDataDao, TagDao tagDao) {
this.tagDataDao = tagDataDao;
this.tagDao = tagDao;
}
/**
* Property for retrieving count of aggregated rows
*/
private static final CountProperty COUNT = new CountProperty();
public static final Order GROUPED_TAGS_BY_SIZE = Order.desc(COUNT);
/**
* Return all tags ordered by given clause
*
* @param order ordering
* @param activeStatus criterion for specifying completed or uncompleted
* @return empty array if no tags, otherwise array
*/
public TagData[] getGroupedTags(Order order, Criterion activeStatus) {
Query query = Query.select(TaskToTagMetadata.TAG_NAME, TaskToTagMetadata.TAG_UUID, COUNT).
join(Join.inner(Task.TABLE, Metadata.TASK.eq(Task.ID))).
where(Criterion.and(
activeStatus,
MetadataCriteria.withKey(TaskToTagMetadata.KEY))).
orderBy(order).groupBy(TaskToTagMetadata.TAG_NAME);
final List<TagData> array = new ArrayList<>();
for (Metadata metadata : metadataDao.toList(query)) {
TagData tag = tagFromUUID(metadata.getValue(TaskToTagMetadata.TAG_UUID));
if (tag != null) {
array.add(tag);
}
}
return array.toArray(new TagData[array.size()]);
public TagData[] getGroupedTags() {
List<TagData> tags = tagDataDao.tagDataOrderedByName();
return tags.toArray(new TagData[tags.size()]);
}
public TagData tagFromUUID(String uuid) {
@ -84,35 +54,13 @@ public final class TagService {
}
public List<TagData> getTagDataForTask(String uuid) {
List<Metadata> tags = metadataDao.toList(Query.select(TaskToTagMetadata.TAG_UUID).where(
Criterion.and(
MetadataCriteria.withKey(TaskToTagMetadata.KEY),
Metadata.DELETION_DATE.eq(0),
TaskToTagMetadata.TASK_UUID.eq(uuid))));
return newArrayList(Iterables.transform(tags, metadata -> tagFromUUID(metadata.getValue(TaskToTagMetadata.TAG_UUID))));
List<String> uuids = tagDao.getTagUids(uuid);
return newArrayList(transform(uuids, this::tagFromUUID));
}
public ArrayList<TagData> getTagDataForTask(long taskId) {
List<Metadata> tags = metadataDao.toList(Query.select(TaskToTagMetadata.TAG_UUID).where(
Criterion.and(
MetadataCriteria.withKey(TaskToTagMetadata.KEY),
Metadata.DELETION_DATE.eq(0),
MetadataCriteria.byTask(taskId))));
return newArrayList(transform(tags, metadata -> tagFromUUID(metadata.getValue(TaskToTagMetadata.TAG_UUID))));
}
public ArrayList<String> getTagNames(long taskId) {
Query query = Query.select(TaskToTagMetadata.TAG_NAME, TaskToTagMetadata.TAG_UUID).where(
Criterion.and(
MetadataCriteria.withKey(TaskToTagMetadata.KEY),
Metadata.DELETION_DATE.eq(0),
MetadataCriteria.byTask(taskId)))
.orderBy(Order.asc(Functions.upper(TaskToTagMetadata.TAG_NAME)));
final ArrayList<String> tagNames = new ArrayList<>();
for (Metadata entry : metadataDao.toList(query)) {
tagNames.add(entry.getValue(TaskToTagMetadata.TAG_NAME));
}
return tagNames;
List<String> uuids = tagDao.getTagUids(taskId);
return newArrayList(transform(uuids, this::tagFromUUID));
}
/**
@ -137,11 +85,6 @@ public final class TagService {
* given tag, return that. Otherwise, return the argument
*/
public String getTagWithCase(String tag) {
Metadata tagMetadata = metadataDao.getFirst(Query.select(TaskToTagMetadata.TAG_NAME).where(tagEqIgnoreCase(tag, Criterion.all)).limit(1));
if (tagMetadata != null) {
return tagMetadata.getValue(TaskToTagMetadata.TAG_NAME);
}
TagData tagData = tagDataDao.getTagByName(tag);
if (tagData != null) {
return tagData.getName();
@ -149,18 +92,8 @@ public final class TagService {
return tag;
}
private static Criterion tagEqIgnoreCase(String tag, Criterion additionalCriterion) {
return Criterion.and(
MetadataCriteria.withKey(TaskToTagMetadata.KEY), TaskToTagMetadata.TAG_NAME.eqCaseInsensitive(tag),
additionalCriterion);
}
public int rename(String uuid, String newName) {
public void rename(String uuid, String newName) {
tagDataDao.rename(uuid, newName);
Metadata metadataTemplate = new Metadata();
metadataTemplate.setValue(TaskToTagMetadata.TAG_NAME, newName);
return metadataDao.update(Criterion.and(MetadataCriteria.withKey(TaskToTagMetadata.KEY), TaskToTagMetadata.TAG_UUID.eq(uuid)), metadataTemplate);
tagDao.rename(uuid, newName);
}
}

@ -38,9 +38,7 @@ import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Ordering;
import com.google.common.collect.Sets;
import com.todoroo.andlib.sql.Criterion;
import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.astrid.dao.MetadataDao;
import com.todoroo.astrid.dao.TagDataDao;
import com.todoroo.astrid.data.Metadata;
import com.todoroo.astrid.data.RemoteModel;
@ -49,6 +47,8 @@ import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.utility.Flags;
import org.tasks.R;
import org.tasks.data.Tag;
import org.tasks.data.TagDao;
import org.tasks.dialogs.DialogBuilder;
import org.tasks.injection.FragmentComponent;
import org.tasks.themes.ThemeCache;
@ -86,7 +86,7 @@ public final class TagsControlSet extends TaskEditControlFragment {
private static final String EXTRA_NEW_TAGS = "extra_new_tags";
private static final String EXTRA_SELECTED_TAGS = "extra_selected_tags";
@Inject MetadataDao metadataDao;
@Inject TagDao tagDao;
@Inject TagDataDao tagDataDao;
@Inject TagService tagService;
@Inject DialogBuilder dialogBuilder;
@ -398,10 +398,10 @@ public final class TagsControlSet extends TaskEditControlFragment {
Set<TagData> selectedHash = newHashSet(selectedTags);
Sets.SetView<TagData> added = difference(selectedHash, existingHash);
Sets.SetView<TagData> removed = difference(existingHash, selectedHash);
deleteLinks(taskId, taskUuid, filter(removed, notNull()));
deleteLinks(taskId, filter(removed, notNull()));
for (TagData tagData : added) {
Metadata newLink = TaskToTagMetadata.newTagMetadata(taskId, taskUuid, tagData.getName(), tagData.getRemoteId());
metadataDao.createNew(newLink);
Tag newLink = new Tag(taskId, taskUuid, tagData.getName(), tagData.getRemoteId());
tagDao.insert(newLink);
}
return !removed.isEmpty() || !added.isEmpty();
}
@ -409,16 +409,14 @@ public final class TagsControlSet extends TaskEditControlFragment {
/**
* Delete all links between the specified task and the list of tags
*/
private void deleteLinks(long taskId, String taskUuid, Iterable<TagData> tags) {
private void deleteLinks(long taskId, Iterable<TagData> tags) {
Metadata deleteTemplate = new Metadata();
deleteTemplate.setTask(taskId); // Need this for recording changes in outstanding table
deleteTemplate.setDeletionDate(DateUtilities.now());
for (TagData tag : tags) {
// 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(TaskToTagMetadata.TAG_UUID, tag.getRemoteId()); // Need this for recording changes in outstanding table
metadataDao.update(Criterion.and(MetadataDao.MetadataCriteria.withKey(TaskToTagMetadata.KEY), Metadata.DELETION_DATE.eq(0),
TaskToTagMetadata.TASK_UUID.eq(taskUuid), TaskToTagMetadata.TAG_UUID.eq(tag.getRemoteId())), deleteTemplate);
tagDao.deleteTag(taskId, tag.getRemoteId());
}
}

@ -1,40 +0,0 @@
package com.todoroo.astrid.tags;
import com.todoroo.andlib.data.Property.StringProperty;
import com.todoroo.astrid.data.Metadata;
public class TaskToTagMetadata {
/** Metadata key for tag data */
public static final String KEY = "tags-tag"; //$NON-NLS-1$
/** Property for reading tag values */
public static final StringProperty TAG_NAME = Metadata.VALUE1;
/** Tag uuid */
public static final StringProperty TAG_UUID = new StringProperty(
Metadata.TABLE, Metadata.VALUE2.name);
/** Task uuid */
public static final StringProperty TASK_UUID = new StringProperty(
Metadata.TABLE, Metadata.VALUE3.name);
// Creation date and deletion date are already included as part of the normal metadata entity
/**
* New metadata object for linking a task to the specified tag. The task
* object should be saved and have the uuid property. All parameters
* are manditory
*/
public static Metadata newTagMetadata(long taskId, String taskUuid, String tagName, String tagUuid) {
Metadata link = new Metadata();
link.setKey(KEY);
link.setTask(taskId);
link.setValue(TAG_NAME, tagName);
link.setValue(TASK_UUID, taskUuid);
link.setValue(TAG_UUID, tagUuid);
link.setDeletionDate(0L);
return link;
}
}

@ -16,20 +16,17 @@ import android.text.InputType;
import android.view.MenuItem;
import android.view.inputmethod.InputMethodManager;
import com.todoroo.andlib.sql.Criterion;
import com.todoroo.astrid.activity.TaskListActivity;
import com.todoroo.astrid.api.TagFilter;
import com.todoroo.astrid.dao.MetadataDao;
import com.todoroo.astrid.dao.TagDataDao;
import com.todoroo.astrid.data.Metadata;
import com.todoroo.astrid.data.TagData;
import com.todoroo.astrid.helper.UUIDHelper;
import com.todoroo.astrid.tags.TagService;
import com.todoroo.astrid.tags.TaskToTagMetadata;
import org.tasks.R;
import org.tasks.analytics.Tracker;
import org.tasks.analytics.Tracking;
import org.tasks.data.TagDao;
import org.tasks.dialogs.ColorPickerDialog;
import org.tasks.dialogs.DialogBuilder;
import org.tasks.injection.ActivityComponent;
@ -67,7 +64,7 @@ public class TagSettingsActivity extends ThemedInjectingAppCompatActivity implem
@Inject TagService tagService;
@Inject TagDataDao tagDataDao;
@Inject MetadataDao metadataDao;
@Inject TagDao tagDao;
@Inject DialogBuilder dialogBuilder;
@Inject Preferences preferences;
@Inject ThemeCache themeCache;
@ -197,11 +194,7 @@ public class TagSettingsActivity extends ThemedInjectingAppCompatActivity implem
tagData.setColor(selectedTheme);
tagService.rename(tagData.getRemoteId(), newName);
tagDataDao.persist(tagData);
Metadata m = new Metadata();
m.setValue(TaskToTagMetadata.TAG_NAME, newName);
metadataDao.update(Criterion.and(
MetadataDao.MetadataCriteria.withKey(TaskToTagMetadata.KEY),
TaskToTagMetadata.TAG_UUID.eq(tagData.getRemoteId())), m);
tagDao.rename(tagData.getRemoteId(), newName);
setResult(RESULT_OK, new Intent(ACTION_RELOAD).putExtra(TaskListActivity.OPEN_FILTER, new TagFilter(tagData)));
}
@ -250,7 +243,7 @@ public class TagSettingsActivity extends ThemedInjectingAppCompatActivity implem
.setPositiveButton(R.string.delete, (dialog, which) -> {
if (tagData != null) {
String uuid = tagData.getRemoteId();
metadataDao.deleteWhere(Criterion.and(MetadataDao.MetadataCriteria.withKey(TaskToTagMetadata.KEY), TaskToTagMetadata.TAG_UUID.eq(uuid)));
tagDao.deleteTag(uuid);
tagDataDao.delete(tagData.getId());
setResult(RESULT_OK, new Intent(ACTION_DELETED).putExtra(EXTRA_TAG_UUID, uuid));
}

@ -0,0 +1,105 @@
package org.tasks.data;
import android.arch.persistence.room.ColumnInfo;
import android.arch.persistence.room.Entity;
import android.arch.persistence.room.Ignore;
import android.arch.persistence.room.PrimaryKey;
import android.support.annotation.NonNull;
import com.todoroo.andlib.data.Table;
import org.tasks.backup.XmlReader;
import org.tasks.backup.XmlWriter;
@Entity(tableName = "tags")
public class Tag {
public static final String KEY = "tags-tag"; //$NON-NLS-1$
@Deprecated
public static final Table TABLE = new Table("tags", null);
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "_id")
private long id;
@ColumnInfo(name = "task")
private long task;
@ColumnInfo(name = "name")
private String name;
@ColumnInfo(name = "tag_uid")
private String tagUid;
@ColumnInfo(name = "task_uid")
private String taskUid;
public Tag() {
}
@Ignore
public Tag(long task, String taskUid, String name, String tagUid) {
this.task = task;
this.taskUid = taskUid;
this.name = name;
this.tagUid = tagUid;
}
@Ignore
public Tag(XmlReader xmlReader) {
xmlReader.readLong("task", this::setTask);
xmlReader.readString("name", this::setName);
xmlReader.readString("tag_uid", this::setTagUid);
xmlReader.readString("task_uid", this::setTaskUid);
}
public void writeToXml(XmlWriter xmlWriter) {
xmlWriter.writeLong("task", task);
xmlWriter.writeString("name", name);
xmlWriter.writeString("tag_uid", tagUid);
xmlWriter.writeString("task_uid", taskUid);
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public long getTask() {
return task;
}
public void setTask(long task) {
this.task = task;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getTagUid() {
return tagUid;
}
public void setTagUid(String tagUid) {
this.tagUid = tagUid;
}
@NonNull
public String getTaskUid() {
return taskUid;
}
public void setTaskUid(@NonNull String taskUid) {
this.taskUid = taskUid;
}
}

@ -0,0 +1,43 @@
package org.tasks.data;
import android.arch.persistence.room.Dao;
import android.arch.persistence.room.Insert;
import android.arch.persistence.room.Query;
import java.util.List;
@Dao
public interface TagDao {
@Query("UPDATE tags SET name = :name WHERE tag_uid = :tagUid")
void rename(String tagUid, String name);
@Query("DELETE FROM tags WHERE tag_uid = :tagUid")
void deleteTag(String tagUid);
@Insert
void insert(Tag tag);
@Insert
void insert(List<Tag> tags);
@Query("DELETE FROM tags WHERE task = :taskId AND tag_uid = :tagUid")
void deleteTag(long taskId, String tagUid);
@Query("SELECT name FROM tags WHERE task = :taskId ORDER BY UPPER(name) ASC")
List<String> getTagNames(long taskId);
@Query("SELECT tag_uid FROM tags WHERE task = :taskId")
List<String> getTagUids(long taskId);
@Query("SELECT tag_uid FROM tags WHERE task_uid = :taskUid")
List<String> getTagUids(String taskUid);
@Query("SELECT * FROM tags WHERE tag_uid = :tagUid")
List<Tag> getByTagUid(String tagUid);
@Query("SELECT * FROM tags WHERE task = :taskId")
List<Tag> getTagsForTask(long taskId);
@Query("SELECT * FROM tags WHERE task = :taskId AND tag_uid = :tagUid")
Tag getTagByTaskAndTagUid(long taskId, String tagUid);
}

@ -3,6 +3,7 @@ package org.tasks.data;
import android.database.sqlite.SQLiteException;
import com.todoroo.andlib.data.Property;
import com.todoroo.andlib.data.Table;
import com.todoroo.andlib.data.TodorooCursor;
import com.todoroo.andlib.sql.Criterion;
import com.todoroo.andlib.sql.Field;
@ -11,10 +12,8 @@ import com.todoroo.astrid.api.Filter;
import com.todoroo.astrid.api.TagFilter;
import com.todoroo.astrid.core.SortHelper;
import com.todoroo.astrid.dao.TaskDao;
import com.todoroo.astrid.data.Metadata;
import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.data.TaskAttachment;
import com.todoroo.astrid.tags.TaskToTagMetadata;
import org.tasks.preferences.Preferences;
@ -41,19 +40,17 @@ public class TaskListDataProvider {
public TodorooCursor<Task> constructCursor(Filter filter, Property<?>[] properties) {
Criterion tagsJoinCriterion = Criterion.and(
Field.field(TAGS_METADATA_JOIN + "." + Metadata.KEY.name).eq(TaskToTagMetadata.KEY), //$NON-NLS-1$
Field.field(TAGS_METADATA_JOIN + "." + Metadata.DELETION_DATE.name).eq(0),
Task.ID.eq(Field.field(TAGS_METADATA_JOIN + "." + Metadata.TASK.name)));
Task.ID.eq(Field.field(TAGS_METADATA_JOIN + ".task")));
if (filter instanceof TagFilter) {
String uuid = ((TagFilter) filter).getUuid();
tagsJoinCriterion = Criterion.and(tagsJoinCriterion, Field.field(TAGS_METADATA_JOIN + "." + TaskToTagMetadata.TAG_UUID.name).neq(uuid));
tagsJoinCriterion = Criterion.and(tagsJoinCriterion, Field.field(TAGS_METADATA_JOIN + ".tag_uid").neq(uuid));
}
// TODO: For now, we'll modify the query to join and include the things like tag data here.
// Eventually, we might consider restructuring things so that this query is constructed elsewhere.
String joinedQuery =
Join.left(Metadata.TABLE.as(TAGS_METADATA_JOIN),
Join.left(Tag.TABLE.as(TAGS_METADATA_JOIN),
tagsJoinCriterion).toString() //$NON-NLS-1$
+ Join.left(TaskAttachment.TABLE.as(FILE_METADATA_JOIN), Task.UUID.eq(Field.field(FILE_METADATA_JOIN + ".task_id")))
+ filter.getSqlQuery();

@ -38,7 +38,7 @@ public class Migrations {
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
database.execSQL("CREATE TABLE IF NOT EXISTS `alarms` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `task` INTEGER, `time` INTEGER)");
database.execSQL("INSERT INTO `alarms` (`task`, `time`) SELECT `task`, `value` FROM `metadata` WHERE `key` = 'alarm'");
database.execSQL("INSERT INTO `alarms` (`task`, `time`) SELECT `task`, `value` FROM `metadata` WHERE `key` = 'alarm' AND `deleted` = 0");
database.execSQL("DELETE FROM `metadata` WHERE `key` = 'alarm'");
}
};
@ -48,11 +48,21 @@ public class Migrations {
public void migrate(@NonNull SupportSQLiteDatabase database) {
database.execSQL("CREATE TABLE IF NOT EXISTS `locations` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `task` INTEGER NOT NULL, `name` TEXT, `latitude` REAL NOT NULL, `longitude` REAL NOT NULL, `radius` INTEGER NOT NULL)");
database.execSQL("INSERT INTO `locations` (`task`, `name`, `latitude`, `longitude`, `radius`) " +
"SELECT `task`, `value`, `value2`, `value3`, `value4` FROM `metadata` WHERE `key` = 'geofence'");
"SELECT `task`, `value`, `value2`, `value3`, `value4` FROM `metadata` WHERE `key` = 'geofence' AND `deleted` = 0");
database.execSQL("DELETE FROM `metadata` WHERE `key` = 'geofence'");
}
};
private static final Migration MIGRATION_48_49 = new Migration(48, 49) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
database.execSQL("CREATE TABLE IF NOT EXISTS `tags` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `task` INTEGER NOT NULL, `name` TEXT, `tag_uid` TEXT, `task_uid` TEXT)");
database.execSQL("INSERT INTO `tags` (`task`, `name`, `tag_uid`, `task_uid`) " +
"SELECT `task`, `value`, `value2`, `value3` FROM `metadata` WHERE `key` = 'tags-tag' AND `deleted` = 0");
database.execSQL("DELETE FROM `metadata` WHERE `key` = 'tags-tag'");
}
};
private static Migration NOOP(int from, int to) {
return new Migration(from, to) {
@Override
@ -75,6 +85,7 @@ public class Migrations {
NOOP(44, 45),
NOOP(45, 46),
MIGRATION_46_47,
MIGRATION_47_48
MIGRATION_47_48,
MIGRATION_48_49
};
}

@ -22,9 +22,9 @@ import com.todoroo.astrid.gtasks.GtasksList;
import com.todoroo.astrid.gtasks.GtasksListService;
import com.todoroo.astrid.gtasks.GtasksMetadata;
import com.todoroo.astrid.tags.TagService;
import com.todoroo.astrid.tags.TaskToTagMetadata;
import org.tasks.R;
import org.tasks.data.Tag;
import org.tasks.gtasks.SyncAdapterHelper;
import org.tasks.injection.ForApplication;
@ -88,16 +88,14 @@ public class FilterCriteriaProvider {
List<String> tags = newArrayList(newLinkedHashSet(transform(tagService.getTagList(), TagData::getName)));
String[] tagNames = tags.toArray(new String[tags.size()]);
Map<String, Object> values = new HashMap<>();
values.put(Metadata.KEY.name, TaskToTagMetadata.KEY);
values.put(TaskToTagMetadata.TAG_NAME.name, "?");
values.put(Tag.KEY, "?");
return new MultipleSelectCriterion(
IDENTIFIER_TAG_IS,
context.getString(R.string.CFC_tag_text),
Query.select(Metadata.TASK).from(Metadata.TABLE).join(Join.inner(
Task.TABLE, Metadata.TASK.eq(Task.ID))).where(Criterion.and(
Query.select(Field.field("task")).from(Tag.TABLE).join(Join.inner(
Task.TABLE, Field.field("task").eq(Task.ID))).where(Criterion.and(
TaskDao.TaskCriteria.activeAndVisible(),
MetadataDao.MetadataCriteria.withKey(TaskToTagMetadata.KEY),
TaskToTagMetadata.TAG_NAME.eq("?"), Metadata.DELETION_DATE.eq(0))).toString(),
Field.field("name").eq("?"))).toString(),
values, tagNames, tagNames,
null,
context.getString(R.string.CFC_tag_name));
@ -107,11 +105,10 @@ public class FilterCriteriaProvider {
return new TextInputCriterion(
IDENTIFIER_TAG_CONTAINS,
context.getString(R.string.CFC_tag_contains_text),
Query.select(Metadata.TASK).from(Metadata.TABLE).join(Join.inner(
Task.TABLE, Metadata.TASK.eq(Task.ID))).where(Criterion.and(
Query.select(Field.field("task")).from(Tag.TABLE).join(Join.inner(
Task.TABLE, Field.field("task").eq(Task.ID))).where(Criterion.and(
TaskDao.TaskCriteria.activeAndVisible(),
MetadataDao.MetadataCriteria.withKey(TaskToTagMetadata.KEY),
TaskToTagMetadata.TAG_NAME.like("%?%"), Metadata.DELETION_DATE.eq(0))).toString(),
Field.field("name").like("%?%"))).toString(),
context.getString(R.string.CFC_tag_contains_name), "",
null,
context.getString(R.string.CFC_tag_contains_name));

@ -9,12 +9,14 @@ import com.todoroo.astrid.dao.TagDataDao;
import com.todoroo.astrid.dao.TaskAttachmentDao;
import com.todoroo.astrid.dao.TaskListMetadataDao;
import com.todoroo.astrid.dao.UserActivityDao;
import com.todoroo.astrid.data.TagData;
import com.todoroo.astrid.provider.Astrid2TaskProvider;
import org.tasks.ErrorReportingSingleThreadExecutor;
import org.tasks.analytics.Tracker;
import org.tasks.data.AlarmDao;
import org.tasks.data.LocationDao;
import org.tasks.data.TagDao;
import org.tasks.db.Migrations;
import org.tasks.locale.Locale;
import org.tasks.notifications.NotificationDao;
@ -102,4 +104,9 @@ public class ApplicationModule {
public LocationDao getGeofenceDao(Database database) {
return database.getGeofenceDao();
}
@Provides
public TagDao getTagDao(Database database) {
return database.getTagDao();
}
}

@ -3,6 +3,7 @@ package org.tasks.tasklist;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.os.Parcelable;
import android.support.v7.widget.Toolbar;
import android.view.MenuItem;
@ -51,7 +52,7 @@ public class TagListFragment extends TaskListFragment {
switch (item.getItemId()) {
case R.id.menu_tag_settings:
Intent intent = new Intent(getActivity(), TagSettingsActivity.class);
intent.putExtra(TagSettingsActivity.EXTRA_TAG_DATA, tagData);
intent.putExtra(TagSettingsActivity.EXTRA_TAG_DATA, (Parcelable) tagData);
startActivityForResult(intent, REQUEST_EDIT_TAG);
return true;
default:

Loading…
Cancel
Save