From 056c19478020bb1c6f7efb81efbc8efe5e2ae314 Mon Sep 17 00:00:00 2001 From: Alex Baker Date: Sun, 17 May 2020 10:23:14 -0500 Subject: [PATCH] CaldavDao shift down --- .../org/tasks/data/CaldavDaoShiftTests.kt | 168 ++++++++++++++++++ .../java/org/tasks/injection/TestComponent.kt | 3 +- .../org/tasks/makers/TaskContainerMaker.kt | 7 +- .../java/org/tasks/makers/TaskMaker.kt | 1 + .../com/todoroo/astrid/core/SortHelper.java | 2 +- app/src/main/java/org/tasks/data/CaldavDao.kt | 27 +++ .../org/tasks/data/CaldavTaskContainer.kt | 4 + .../main/java/org/tasks/time/DateTime.java | 5 + 8 files changed, 214 insertions(+), 3 deletions(-) create mode 100644 app/src/androidTest/java/org/tasks/data/CaldavDaoShiftTests.kt diff --git a/app/src/androidTest/java/org/tasks/data/CaldavDaoShiftTests.kt b/app/src/androidTest/java/org/tasks/data/CaldavDaoShiftTests.kt new file mode 100644 index 000000000..2f5064762 --- /dev/null +++ b/app/src/androidTest/java/org/tasks/data/CaldavDaoShiftTests.kt @@ -0,0 +1,168 @@ +package org.tasks.data + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.natpryce.makeiteasy.MakeItEasy.with +import com.natpryce.makeiteasy.PropertyValue +import com.todoroo.andlib.utility.DateUtilities.now +import com.todoroo.astrid.dao.TaskDao +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNull +import org.junit.Test +import org.junit.runner.RunWith +import org.tasks.Freeze.Companion.freezeAt +import org.tasks.injection.InjectingTestCase +import org.tasks.injection.TestComponent +import org.tasks.makers.TaskContainerMaker +import org.tasks.makers.TaskContainerMaker.CREATED +import org.tasks.time.DateTime +import javax.inject.Inject + +@RunWith(AndroidJUnit4::class) +class CaldavDaoShiftTests : InjectingTestCase() { + + @Inject lateinit var taskDao: TaskDao + @Inject lateinit var caldavDao: CaldavDao + + private val tasks = ArrayList() + + @Test + fun basicShiftDown() { + val created = DateTime(2020, 5, 17, 9, 53, 17) + addTask(with(CREATED, created)) + addTask(with(CREATED, created.plusSeconds(1))) + addTask(with(CREATED, created.plusSeconds(2))) + + caldavDao.shiftDown("calendar", 0, created.plusSeconds(1).toAppleEpoch()) + + checkOrder(null, tasks[0]) + checkOrder(created.plusSeconds(2), tasks[1]) + checkOrder(created.plusSeconds(3), tasks[2]) + } + + @Test + fun shiftDownOnlyWhenNecessary() { + val created = DateTime(2020, 5, 17, 9, 53, 17) + addTask(with(CREATED, created)) + addTask(with(CREATED, created.plusSeconds(1))) + addTask(with(CREATED, created.plusSeconds(3))) + addTask(with(CREATED, created.plusSeconds(4))) + + caldavDao.shiftDown("calendar", 0, created.plusSeconds(1).toAppleEpoch()) + + checkOrder(null, tasks[0]) + checkOrder(created.plusSeconds(2), tasks[1]) + checkOrder(null, tasks[2]) + checkOrder(null, tasks[3]) + } + + @Test + fun ignoreUnnecessaryShiftDown() { + val created = DateTime(2020, 5, 17, 9, 53, 17) + addTask(with(CREATED, created)) + addTask(with(CREATED, created.plusSeconds(2))) + + caldavDao.shiftDown("calendar", 0, created.plusSeconds(1).toAppleEpoch()) + + checkOrder(null, tasks[0]) + checkOrder(null, tasks[1]) + } + + @Test + fun ignoreOtherCalendarWhenShiftingDown() { + val created = DateTime(2020, 5, 17, 9, 53, 17) + addTask("calendar1", with(CREATED, created)) + addTask("calendar2", with(CREATED, created)) + + caldavDao.shiftDown("calendar1", 0, created.toAppleEpoch()) + + checkOrder(created.plusSeconds(1), tasks[0]) + checkOrder(null, tasks[1]) + } + + @Test + fun partialShiftDown() { + val created = DateTime(2020, 5, 17, 9, 53, 17) + addTask(with(CREATED, created)) + addTask(with(CREATED, created.plusSeconds(1))) + addTask(with(CREATED, created.plusSeconds(2))) + addTask(with(CREATED, created.plusSeconds(3))) + addTask(with(CREATED, created.plusSeconds(4))) + + caldavDao.shiftDown("calendar", 0, created.toAppleEpoch(), created.plusSeconds(3).toAppleEpoch()) + + checkOrder(created.plusSeconds(1), tasks[0]) + checkOrder(created.plusSeconds(2), tasks[1]) + checkOrder(created.plusSeconds(3), tasks[2]) + checkOrder(null, tasks[3]) + checkOrder(null, tasks[4]) + } + + @Test + fun ignoreMovedTasksWhenShiftingDown() { + val created = DateTime(2020, 5, 17, 9, 53, 17) + addTask(with(CREATED, created)) + caldavDao.update(caldavDao.getTask(tasks[0].id).apply { this?.deleted = now() }!!) + + caldavDao.shiftDown("calendar", 0, created.toAppleEpoch()) + + assertNull(caldavDao.getTasks(tasks[0].id)[0].order) + } + + @Test + fun ignoreDeletedTasksWhenShiftingDown() { + val created = DateTime(2020, 5, 17, 9, 53, 17) + addTask(with(CREATED, created)) + taskDao.update(taskDao.fetch(tasks[0].id).apply { this?.deletionDate = now() }!!) + + caldavDao.shiftDown("calendar", 0, created.toAppleEpoch()) + + assertNull(caldavDao.getTasks(tasks[0].id)[0].order) + } + + @Test + fun touchShiftedTasks() { + val created = DateTime(2020, 5, 17, 9, 53, 17) + addTask(with(CREATED, created)) + addTask(with(CREATED, created.plusSeconds(1))) + + freezeAt(created.plusMinutes(1)) { + caldavDao.shiftDown("calendar", 0, created.toAppleEpoch()) + } + + assertEquals(created.plusMinutes(1).millis, taskDao.fetch(tasks[0].id)!!.modificationDate) + assertEquals(created.plusMinutes(1).millis, taskDao.fetch(tasks[1].id)!!.modificationDate) + } + + private fun checkOrder(dateTime: DateTime?, task: TaskContainer) { + if (dateTime == null) { + assertNull(caldavDao.getTask(task.id)!!.order) + } else { + assertEquals(dateTime.toAppleEpoch(), caldavDao.getTask(task.id)!!.order) + } + } + + private fun addTask(vararg properties: PropertyValue) = addTask("calendar", *properties) + + private fun addTask(calendar: String, vararg properties: PropertyValue) { + val t = TaskContainerMaker.newTaskContainer(*properties) + tasks.add(t) + val task = t.task + taskDao.createNew(task) + val caldavTask = CaldavTask(t.id, calendar) + if (task.parent > 0) { + caldavTask.remoteParent = caldavDao.getRemoteIdForTask(task.parent) + } + caldavTask.id = caldavDao.insert(caldavTask) + t.caldavTask = caldavTask.toSubset() + } + + private fun CaldavTask.toSubset(): SubsetCaldav { + val result = SubsetCaldav() + result.cd_id = id + result.cd_calendar = calendar + result.cd_remote_parent = remoteParent + return result + } + + override fun inject(component: TestComponent) = component.inject(this) +} \ No newline at end of file diff --git a/app/src/androidTest/java/org/tasks/injection/TestComponent.kt b/app/src/androidTest/java/org/tasks/injection/TestComponent.kt index acd32b05b..84e3be151 100644 --- a/app/src/androidTest/java/org/tasks/injection/TestComponent.kt +++ b/app/src/androidTest/java/org/tasks/injection/TestComponent.kt @@ -43,4 +43,5 @@ interface TestComponent : ApplicationComponent { fun inject(tests: GoogleTaskListDaoTest) fun inject(tests: CaldavTaskAdapterTest) fun inject(tests: ManualGoogleTaskQueryTest) -} \ No newline at end of file + fun inject(tests: CaldavDaoShiftTests) +} diff --git a/app/src/commonTest/java/org/tasks/makers/TaskContainerMaker.kt b/app/src/commonTest/java/org/tasks/makers/TaskContainerMaker.kt index 6708c8e45..24cadd5ee 100644 --- a/app/src/commonTest/java/org/tasks/makers/TaskContainerMaker.kt +++ b/app/src/commonTest/java/org/tasks/makers/TaskContainerMaker.kt @@ -8,19 +8,24 @@ import com.natpryce.makeiteasy.PropertyLookup import com.natpryce.makeiteasy.PropertyValue import com.todoroo.astrid.data.Task.Companion.NO_ID import org.tasks.data.TaskContainer +import org.tasks.date.DateTimeUtils.newDateTime import org.tasks.makers.Maker.make import org.tasks.makers.TaskMaker.newTask +import org.tasks.time.DateTime object TaskContainerMaker { val ID: Property = newProperty() - val PARENT: Property = newProperty() + val PARENT: Property = newProperty() + val CREATED: Property = newProperty() private val instantiator = Instantiator { lookup: PropertyLookup -> val container = TaskContainer() val parent = lookup.valueOf(PARENT, null as TaskContainer?) val taskId = lookup.valueOf(ID, NO_ID) + val created = lookup.valueOf(CREATED, newDateTime()) container.task = newTask( with(TaskMaker.ID, taskId), + with(TaskMaker.CREATION_TIME, created), with(TaskMaker.PARENT, parent?.id ?: 0L)) container.indent = parent?.indent?.plus(1) ?: 0 container diff --git a/app/src/commonTest/java/org/tasks/makers/TaskMaker.kt b/app/src/commonTest/java/org/tasks/makers/TaskMaker.kt index 20540169e..1b27353b3 100644 --- a/app/src/commonTest/java/org/tasks/makers/TaskMaker.kt +++ b/app/src/commonTest/java/org/tasks/makers/TaskMaker.kt @@ -89,6 +89,7 @@ object TaskMaker { task.uuid = lookup.valueOf(UUID, NO_UUID) val creationTime = lookup.valueOf(CREATION_TIME, DateTimeUtils.newDateTime()) task.creationDate = creationTime.millis + task.modificationDate = creationTime.millis task.parent = lookup.valueOf(PARENT, 0L) task } diff --git a/app/src/main/java/com/todoroo/astrid/core/SortHelper.java b/app/src/main/java/com/todoroo/astrid/core/SortHelper.java index 2e268d7ee..495844936 100644 --- a/app/src/main/java/com/todoroo/astrid/core/SortHelper.java +++ b/app/src/main/java/com/todoroo/astrid/core/SortHelper.java @@ -33,7 +33,7 @@ public class SortHelper { public static final int SORT_GTASKS = 6; public static final int SORT_CALDAV = 7; - private static long APPLE_EPOCH = 978307200000L; // 1/1/2001 GMT + public static final long APPLE_EPOCH = 978307200000L; // 1/1/2001 GMT @SuppressLint("DefaultLocale") public static final String CALDAV_ORDER_COLUMN = String.format("IFNULL(caldav_tasks.cd_order, (tasks.created - %d) / 1000)", APPLE_EPOCH); diff --git a/app/src/main/java/org/tasks/data/CaldavDao.kt b/app/src/main/java/org/tasks/data/CaldavDao.kt index 041e9f56d..db875f7f1 100644 --- a/app/src/main/java/org/tasks/data/CaldavDao.kt +++ b/app/src/main/java/org/tasks/data/CaldavDao.kt @@ -2,6 +2,8 @@ package org.tasks.data import androidx.lifecycle.LiveData import androidx.room.* +import com.todoroo.andlib.utility.DateUtilities.now +import com.todoroo.astrid.core.SortHelper.APPLE_EPOCH import io.reactivex.Single import org.tasks.db.DbUtils import org.tasks.filters.CaldavFilters @@ -164,4 +166,29 @@ abstract class CaldavDao { + " AND caldav_tasks.cd_deleted = 0), 0)" + "WHERE _id IN (SELECT _id FROM tasks INNER JOIN caldav_tasks ON _id = cd_task WHERE cd_deleted = 0 AND cd_calendar = :calendar)") abstract fun updateParents(calendar: String) + + @Transaction + open fun shiftDown(calendar: String, parent: Long, from: Long, to: Long? = null) { + val updated = ArrayList() + val tasks = getTasksToShift(calendar, parent, from, to) + for (i in tasks.indices) { + val task = tasks[i] + val current = from + i + if (task.sortOrder == current) { + val caldavTask = task.caldavTask + caldavTask.order = current + 1 + updated.add(caldavTask) + } else if (task.sortOrder > current) { + break + } + } + update(updated) + touchInternal(updated.map(CaldavTask::task)) + } + + @Query("UPDATE tasks SET modified = :modificationTime WHERE _id in (:ids)") + internal abstract fun touchInternal(ids: List, modificationTime: Long = now()) + + @Query("SELECT task.*, caldav_task.*, IFNULL(cd_order, (created - $APPLE_EPOCH) / 1000) AS primary_sort FROM caldav_tasks AS caldav_task INNER JOIN tasks AS task ON _id = cd_task WHERE cd_calendar = :calendar AND parent = :parent AND cd_deleted = 0 AND deleted = 0 AND primary_sort >= :from AND primary_sort < IFNULL(:to, ${Long.MAX_VALUE}) ORDER BY primary_sort") + internal abstract fun getTasksToShift(calendar: String, parent: Long, from: Long, to: Long?): List } \ No newline at end of file diff --git a/app/src/main/java/org/tasks/data/CaldavTaskContainer.kt b/app/src/main/java/org/tasks/data/CaldavTaskContainer.kt index 305707395..70c969a4e 100644 --- a/app/src/main/java/org/tasks/data/CaldavTaskContainer.kt +++ b/app/src/main/java/org/tasks/data/CaldavTaskContainer.kt @@ -2,6 +2,7 @@ package org.tasks.data import androidx.room.Embedded import com.todoroo.astrid.data.Task +import org.tasks.time.DateTime class CaldavTaskContainer { @Embedded lateinit var task: Task @@ -16,6 +17,9 @@ class CaldavTaskContainer { val vtodo: String? get() = caldavTask.vtodo + val sortOrder: Long + get() = caldavTask.order ?: DateTime(task.creationDate).toAppleEpoch() + override fun toString(): String { return "CaldavTaskContainer{task=$task, caldavTask=$caldavTask}" } diff --git a/app/src/main/java/org/tasks/time/DateTime.java b/app/src/main/java/org/tasks/time/DateTime.java index 8057b2f0a..ebb3e500e 100644 --- a/app/src/main/java/org/tasks/time/DateTime.java +++ b/app/src/main/java/org/tasks/time/DateTime.java @@ -1,5 +1,6 @@ package org.tasks.time; +import static com.todoroo.astrid.core.SortHelper.APPLE_EPOCH; import static java.util.Calendar.FRIDAY; import static java.util.Calendar.MONDAY; import static java.util.Calendar.SATURDAY; @@ -340,6 +341,10 @@ public class DateTime { return timestamp == 0 ? null : LocalDateTime.of(getYear(), getMonthOfYear(), getDayOfMonth(), getHourOfDay(), getMinuteOfHour()); } + public long toAppleEpoch() { + return (timestamp - APPLE_EPOCH) / 1000; + } + public int getDayOfWeekInMonth() { return getCalendar().get(Calendar.DAY_OF_WEEK_IN_MONTH); }