Apply existing categories on upgrade

pull/848/head
Alex Baker 5 years ago
parent 31ce07641d
commit 3f71329f61

@ -34,8 +34,8 @@ android {
defaultConfig {
testApplicationId = "org.tasks.test"
applicationId = "org.tasks"
versionCode = 607
versionName = "6.8.1"
versionCode = 608
versionName = "6.9"
targetSdkVersion(Versions.targetSdk)
minSdkVersion(Versions.minSdk)
multiDexEnabled = true

@ -0,0 +1,63 @@
package org.tasks.data;
import static com.natpryce.makeiteasy.MakeItEasy.with;
import static java.util.Collections.singletonList;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.tasks.makers.TagDataMaker.newTagData;
import static org.tasks.makers.TagMaker.TAGDATA;
import static org.tasks.makers.TagMaker.TASK;
import static org.tasks.makers.TagMaker.newTag;
import static org.tasks.makers.TaskMaker.ID;
import static org.tasks.makers.TaskMaker.newTask;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.todoroo.astrid.dao.TaskDao;
import com.todoroo.astrid.data.Task;
import javax.inject.Inject;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
public class CaldavDaoTests {
@Inject TaskDao taskDao;
@Inject TagDao tagDao;
@Inject TagDataDao tagDataDao;
@Inject CaldavDao caldavDao;
@Test
public void getCaldavTasksWithTags() {
Task task = newTask(with(ID, 1L));
taskDao.createNew(task);
TagData one = newTagData();
TagData two = newTagData();
tagDataDao.createNew(one);
tagDataDao.createNew(two);
tagDao.insert(newTag(with(TASK, task), with(TAGDATA, one)));
tagDao.insert(newTag(with(TASK, task), with(TAGDATA, two)));
caldavDao.insert(new CaldavTask(task.getId(), "calendar"));
assertEquals(singletonList(task.getId()), caldavDao.getTasksWithTags());
}
@Test
public void ignoreNonCaldavTaskWithTags() {
Task task = newTask(with(ID, 1L));
taskDao.createNew(task);
TagData tag = newTagData();
tagDataDao.createNew(tag);
tagDao.insert(newTag(with(TASK, task), with(TAGDATA, tag)));
assertTrue(caldavDao.getTasksWithTags().isEmpty());
}
@Test
public void ignoreCaldavTaskWithoutTags() {
Task task = newTask(with(ID, 1L));
taskDao.createNew(task);
tagDataDao.createNew(newTagData());
caldavDao.insert(new CaldavTask(task.getId(), "calendar"));
assertTrue(caldavDao.getTasksWithTags().isEmpty());
}
}

@ -142,6 +142,9 @@ public abstract class TaskDao {
@RawQuery(observedEntities = {Task.class, GoogleTask.class, CaldavTask.class, Tag.class})
public abstract DataSource.Factory<Integer, TaskContainer> getTaskFactory(SimpleSQLiteQuery query);
@Query("UPDATE tasks SET modified = datetime('now', 'localtime') WHERE _id in (:ids)")
public abstract void touch(List<Long> ids);
/**
* Saves the given task to the database.getDatabase(). Task must already exist. Returns true on
* success.

@ -1,6 +1,7 @@
package com.todoroo.astrid.service;
import static com.google.common.base.Strings.isNullOrEmpty;
import static org.tasks.db.DbUtils.batch;
import android.os.Environment;
import com.google.common.base.Strings;
@ -9,12 +10,16 @@ import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Multimaps;
import com.todoroo.astrid.api.GtasksFilter;
import com.todoroo.astrid.dao.TaskDao;
import java.io.File;
import java.util.List;
import javax.inject.Inject;
import org.tasks.R;
import org.tasks.analytics.Tracker;
import org.tasks.analytics.Tracking;
import org.tasks.caldav.CaldavUtils;
import org.tasks.data.CaldavDao;
import org.tasks.data.CaldavTaskContainer;
import org.tasks.data.Filter;
import org.tasks.data.FilterDao;
import org.tasks.data.GoogleTaskAccount;
@ -41,6 +46,7 @@ public class Upgrader {
private static final int V6_4 = 546;
private static final int V6_7 = 585;
private static final int V6_8_1 = 607;
private static final int V6_9 = 608;
private final Preferences preferences;
private final Tracker tracker;
private final TagDataDao tagDataDao;
@ -50,6 +56,8 @@ public class Upgrader {
private final GoogleTaskListDao googleTaskListDao;
private final UserActivityDao userActivityDao;
private final TaskAttachmentDao taskAttachmentDao;
private final CaldavDao caldavDao;
private final TaskDao taskDao;
@Inject
public Upgrader(
@ -61,7 +69,9 @@ public class Upgrader {
DefaultFilterProvider defaultFilterProvider,
GoogleTaskListDao googleTaskListDao,
UserActivityDao userActivityDao,
TaskAttachmentDao taskAttachmentDao) {
TaskAttachmentDao taskAttachmentDao,
CaldavDao caldavDao,
TaskDao taskDao) {
this.preferences = preferences;
this.tracker = tracker;
this.tagDataDao = tagDataDao;
@ -71,6 +81,8 @@ public class Upgrader {
this.googleTaskListDao = googleTaskListDao;
this.userActivityDao = userActivityDao;
this.taskAttachmentDao = taskAttachmentDao;
this.caldavDao = caldavDao;
this.taskDao = taskDao;
}
public void upgrade(int from, int to) {
@ -83,6 +95,7 @@ public class Upgrader {
run(from, V6_4, this::migrateUris);
run(from, V6_7, this::migrateGoogleTaskFilters);
run(from, V6_8_1, this::migrateCaldavFilters);
run(from, V6_9, this::applyCaldavCategories);
tracker.reportEvent(Tracking.Events.UPGRADE, Integer.toString(from));
}
preferences.setCurrentVersion(to);
@ -95,6 +108,18 @@ public class Upgrader {
}
}
private void applyCaldavCategories() {
List<Long> tasksWithTags = caldavDao.getTasksWithTags();
for (CaldavTaskContainer container : caldavDao.getTasks()) {
at.bitfire.ical4android.Task remoteTask =
CaldavUtils.fromVtodo(container.caldavTask.getVtodo());
if (remoteTask != null) {
tagDao.insert(container.task, CaldavUtils.getTags(tagDataDao, remoteTask.getCategories()));
}
}
batch(tasksWithTags, taskDao::touch);
}
private void performMarshmallowMigration() {
try {
// preserve pre-marshmallow default backup location

@ -6,10 +6,7 @@
package com.todoroo.astrid.tags;
import static com.google.common.collect.Iterables.transform;
import static com.google.common.collect.Lists.newArrayList;
import static com.google.common.collect.Lists.transform;
import static com.google.common.collect.Sets.difference;
import static com.google.common.collect.Sets.newHashSet;
import static com.todoroo.andlib.utility.AndroidUtilities.atLeastJellybeanMR1;
@ -48,7 +45,6 @@ import java.util.List;
import java.util.Set;
import javax.inject.Inject;
import org.tasks.R;
import org.tasks.data.Tag;
import org.tasks.data.TagDao;
import org.tasks.data.TagData;
import org.tasks.data.TagDataDao;
@ -366,22 +362,12 @@ public final class TagsControlSet extends TaskEditControlFragment {
}
private boolean synchronizeTags(Task task) {
long taskId = task.getId();
for (TagData tagData : selectedTags) {
if (Task.NO_UUID.equals(tagData.getRemoteId())) {
tagDataDao.createNew(tagData);
}
}
Set<TagData> existing = newHashSet(tagDataDao.getTagDataForTask(taskId));
Set<TagData> selected = newHashSet(selectedTags);
Set<TagData> added = difference(selected, existing);
Set<TagData> removed = difference(existing, selected);
tagDao.deleteTags(taskId, newArrayList(transform(removed, TagData::getRemoteId)));
for (TagData tagData : added) {
Tag newLink = new Tag(task, tagData);
tagDao.insert(newLink);
}
return !removed.isEmpty() || !added.isEmpty();
return tagDao.applyTags(task, tagDataDao, selectedTags);
}
@Override

@ -33,7 +33,6 @@ import com.todoroo.astrid.service.TaskCreator;
import com.todoroo.astrid.service.TaskDeleter;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.StringReader;
import java.net.ConnectException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
@ -56,7 +55,6 @@ import org.tasks.data.CaldavAccount;
import org.tasks.data.CaldavCalendar;
import org.tasks.data.CaldavDao;
import org.tasks.data.CaldavTask;
import org.tasks.data.Tag;
import org.tasks.data.TagDao;
import org.tasks.data.TagData;
import org.tasks.data.TagDataDao;
@ -350,58 +348,37 @@ public class CaldavSynchronizer {
private void processVTodo(
String fileName, CaldavCalendar caldavCalendar, String eTag, String vtodo) {
List<at.bitfire.ical4android.Task> tasks =
at.bitfire.ical4android.Task.Companion.fromReader(new StringReader(vtodo));
if (tasks.size() == 1) {
at.bitfire.ical4android.Task remote = tasks.get(0);
Task task;
CaldavTask caldavTask = caldavDao.getTask(caldavCalendar.getUuid(), fileName);
if (caldavTask == null) {
task = taskCreator.createWithValues("");
taskDao.createNew(task);
caldavTask =
new CaldavTask(task.getId(), caldavCalendar.getUuid(), remote.getUid(), fileName);
} else {
task = taskDao.fetch(caldavTask.getTask());
}
CaldavConverter.apply(task, remote);
applyCategories(task, remote.getCategories());
task.putTransitory(SyncFlags.GTASKS_SUPPRESS_SYNC, true);
taskDao.save(task);
caldavTask.setVtodo(vtodo);
caldavTask.setEtag(eTag);
caldavTask.setLastSync(DateUtilities.now() + 1000L);
if (caldavTask.getId() == Task.NO_ID) {
caldavTask.setId(caldavDao.insert(caldavTask));
Timber.d("NEW %s", caldavTask);
} else {
caldavDao.update(caldavTask);
Timber.d("UPDATE %s", caldavTask);
}
} else {
Timber.e("Received VCALENDAR with %s VTODOs; ignoring %s", tasks.size(), fileName);
at.bitfire.ical4android.Task remote = CaldavUtils.fromVtodo(vtodo);
if (remote == null) {
Timber.e("Invalid VCALENDAR: %s", fileName);
return;
}
}
private void applyCategories(Task task, List<String> categories) {
long taskId = task.getId();
List<TagData> selectedTags = tagDataDao.getTags(categories);
Set<String> toCreate =
difference(newHashSet(categories), newHashSet(transform(selectedTags, TagData::getName)));
for (String name : toCreate) {
TagData tag = new TagData(name);
tagDataDao.createNew(tag);
selectedTags.add(tag);
Task task;
CaldavTask caldavTask = caldavDao.getTask(caldavCalendar.getUuid(), fileName);
if (caldavTask == null) {
task = taskCreator.createWithValues("");
taskDao.createNew(task);
caldavTask =
new CaldavTask(task.getId(), caldavCalendar.getUuid(), remote.getUid(), fileName);
} else {
task = taskDao.fetch(caldavTask.getTask());
}
Set<TagData> existing = newHashSet(tagDataDao.getTagDataForTask(taskId));
Set<TagData> selected = newHashSet(selectedTags);
Set<TagData> added = difference(selected, existing);
Set<TagData> removed = difference(existing, selected);
tagDao.deleteTags(taskId, newArrayList(Iterables.transform(removed, TagData::getRemoteId)));
for (TagData tagData : added) {
Tag newLink = new Tag(task, tagData);
tagDao.insert(newLink);
CaldavConverter.apply(task, remote);
tagDao.applyTags(task, tagDataDao, CaldavUtils.getTags(tagDataDao, remote.getCategories()));
task.putTransitory(SyncFlags.GTASKS_SUPPRESS_SYNC, true);
taskDao.save(task);
caldavTask.setVtodo(vtodo);
caldavTask.setEtag(eTag);
caldavTask.setLastSync(DateUtilities.now() + 1000L);
if (caldavTask.getId() == Task.NO_ID) {
caldavTask.setId(caldavDao.insert(caldavTask));
Timber.d("NEW %s", caldavTask);
} else {
caldavDao.update(caldavTask);
Timber.d("UPDATE %s", caldavTask);
}
}
}

@ -0,0 +1,36 @@
package org.tasks.caldav;
import static com.google.common.collect.Lists.transform;
import static com.google.common.collect.Sets.difference;
import static com.google.common.collect.Sets.newHashSet;
import androidx.annotation.Nullable;
import java.io.StringReader;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import org.tasks.data.TagData;
import org.tasks.data.TagDataDao;
public class CaldavUtils {
public static @Nullable at.bitfire.ical4android.Task fromVtodo(String vtodo) {
List<at.bitfire.ical4android.Task> tasks =
at.bitfire.ical4android.Task.Companion.fromReader(new StringReader(vtodo));
return tasks.size() == 1 ? tasks.get(0) : null;
}
public static List<TagData> getTags(TagDataDao tagDataDao, List<String> categories) {
if (categories.isEmpty()) {
return Collections.emptyList();
}
List<TagData> selectedTags = tagDataDao.getTags(categories);
Set<String> toCreate =
difference(newHashSet(categories), newHashSet(transform(selectedTags, TagData::getName)));
for (String name : toCreate) {
TagData tag = new TagData(name);
tagDataDao.createNew(tag);
selectedTags.add(tag);
}
return selectedTags;
}
}

@ -64,6 +64,12 @@ public interface CaldavDao {
@Query("SELECT * FROM caldav_tasks WHERE cd_task = :taskId")
List<CaldavTask> getTasks(long taskId);
@Query(
"SELECT task.*, caldav_task.* FROM tasks AS task "
+ "INNER JOIN caldav_tasks AS caldav_task ON _id = cd_task "
+ "WHERE cd_deleted = 0")
List<CaldavTaskContainer> getTasks();
@Query("SELECT * FROM caldav_lists ORDER BY cdl_name COLLATE NOCASE")
List<CaldavCalendar> getCalendars();
@ -97,4 +103,11 @@ public interface CaldavDao {
+ " GROUP BY caldav_lists.cdl_uuid"
+ " ORDER BY caldav_accounts.cda_name COLLATE NOCASE, caldav_lists.cdl_name COLLATE NOCASE")
List<CaldavFilters> getCaldavFilters(long now);
@Query(
"SELECT tasks._id FROM tasks "
+ "INNER JOIN tags ON tags.task = tasks._id "
+ "INNER JOIN caldav_tasks ON cd_task = tasks._id "
+ "GROUP BY tasks._id")
List<Long> getTasksWithTags();
}

@ -0,0 +1,9 @@
package org.tasks.data;
import androidx.room.Embedded;
import com.todoroo.astrid.data.Task;
public class CaldavTaskContainer {
@Embedded public Task task;
@Embedded public CaldavTask caldavTask;
}

@ -45,8 +45,13 @@ public class Tag {
@Ignore
public Tag(Task task, String name, String tagUid) {
this.task = task.getId();
this.taskUid = task.getUuid();
this(task.getId(), task.getUuid(), name, tagUid);
}
@Ignore
public Tag(long taskId, String taskUid, String name, String tagUid) {
this.task = taskId;
this.taskUid = taskUid;
this.name = name;
this.tagUid = tagUid;
}

@ -1,40 +1,67 @@
package org.tasks.data;
import static com.google.common.collect.Iterables.transform;
import static com.google.common.collect.Lists.newArrayList;
import static com.google.common.collect.Sets.difference;
import static com.google.common.collect.Sets.newHashSet;
import androidx.room.Dao;
import androidx.room.Insert;
import androidx.room.Query;
import androidx.room.Transaction;
import com.todoroo.astrid.data.Task;
import java.util.Collection;
import java.util.List;
import java.util.Set;
@Dao
public interface TagDao {
public abstract class TagDao {
@Query("UPDATE tags SET name = :name WHERE tag_uid = :tagUid")
void rename(String tagUid, String name);
public abstract void rename(String tagUid, String name);
@Query("DELETE FROM tags WHERE tag_uid = :tagUid")
void deleteTag(String tagUid);
public abstract void deleteTag(String tagUid);
@Insert
void insert(Tag tag);
public abstract void insert(Tag tag);
@Insert
void insert(Iterable<Tag> tags);
public abstract void insert(Iterable<Tag> tags);
@Query("DELETE FROM tags WHERE task = :taskId AND tag_uid in (:tagUids)")
void deleteTags(long taskId, List<String> tagUids);
public abstract void deleteTags(long taskId, List<String> tagUids);
@Query("SELECT name FROM tags WHERE task = :taskId ORDER BY UPPER(name) ASC")
List<String> getTagNames(long taskId);
public abstract List<String> getTagNames(long taskId);
@Query("SELECT * FROM tags WHERE tag_uid = :tagUid")
List<Tag> getByTagUid(String tagUid);
public abstract List<Tag> getByTagUid(String tagUid);
@Query("SELECT * FROM tags WHERE task = :taskId")
List<Tag> getTagsForTask(long taskId);
public abstract List<Tag> getTagsForTask(long taskId);
@Query("SELECT * FROM tags WHERE task = :taskId AND tag_uid = :tagUid")
Tag getTagByTaskAndTagUid(long taskId, String tagUid);
public abstract Tag getTagByTaskAndTagUid(long taskId, String tagUid);
@Query("DELETE FROM tags WHERE _id = :id")
void deleteById(long id);
public abstract void deleteById(long id);
@Transaction
public boolean applyTags(Task task, TagDataDao tagDataDao,List<TagData> current) {
long taskId = task.getId();
Set<TagData> existing = newHashSet(tagDataDao.getTagDataForTask(taskId));
Set<TagData> selected = newHashSet(current);
Set<TagData> added = difference(selected, existing);
Set<TagData> removed = difference(existing, selected);
deleteTags(taskId, newArrayList(transform(removed, TagData::getRemoteId)));
insert(task, added);
return !(removed.isEmpty() && added.isEmpty());
}
public void insert(Task task, Collection<TagData> tags) {
if (!tags.isEmpty()) {
insert(transform(tags, td -> new Tag(task, td)));
}
}
}

@ -0,0 +1,28 @@
package org.tasks.db;
import static com.google.common.collect.Lists.partition;
import java.util.List;
import org.tasks.Callback;
public class DbUtils {
private static final int MAX_SQLITE_ARGS = 999;
public static <T> void batch(List<T> items, Callback<List<T>> callback) {
batch(items, MAX_SQLITE_ARGS, callback);
}
public static <T> void batch(List<T> items, int size, Callback<List<T>> callback) {
if (items.isEmpty()) {
return;
}
if (items.size() <= size) {
callback.call(items);
} else {
for (List<T> sublist : partition(items, size)) {
callback.call(sublist);
}
}
}
}
Loading…
Cancel
Save