CaldavDao shift down

pull/996/head
Alex Baker 5 years ago
parent aa00da4739
commit 056c194780

@ -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<TaskContainer>()
@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<in TaskContainer?, *>) = addTask("calendar", *properties)
private fun addTask(calendar: String, vararg properties: PropertyValue<in TaskContainer?, *>) {
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)
}

@ -43,4 +43,5 @@ interface TestComponent : ApplicationComponent {
fun inject(tests: GoogleTaskListDaoTest) fun inject(tests: GoogleTaskListDaoTest)
fun inject(tests: CaldavTaskAdapterTest) fun inject(tests: CaldavTaskAdapterTest)
fun inject(tests: ManualGoogleTaskQueryTest) fun inject(tests: ManualGoogleTaskQueryTest)
fun inject(tests: CaldavDaoShiftTests)
} }

@ -8,19 +8,24 @@ import com.natpryce.makeiteasy.PropertyLookup
import com.natpryce.makeiteasy.PropertyValue import com.natpryce.makeiteasy.PropertyValue
import com.todoroo.astrid.data.Task.Companion.NO_ID import com.todoroo.astrid.data.Task.Companion.NO_ID
import org.tasks.data.TaskContainer import org.tasks.data.TaskContainer
import org.tasks.date.DateTimeUtils.newDateTime
import org.tasks.makers.Maker.make import org.tasks.makers.Maker.make
import org.tasks.makers.TaskMaker.newTask import org.tasks.makers.TaskMaker.newTask
import org.tasks.time.DateTime
object TaskContainerMaker { object TaskContainerMaker {
val ID: Property<TaskContainer, Long> = newProperty() val ID: Property<TaskContainer, Long> = newProperty()
val PARENT: Property<TaskContainer, TaskContainer?> = newProperty() val PARENT: Property<TaskContainer, TaskContainer> = newProperty()
val CREATED: Property<TaskContainer, DateTime> = newProperty()
private val instantiator = Instantiator { lookup: PropertyLookup<TaskContainer> -> private val instantiator = Instantiator { lookup: PropertyLookup<TaskContainer> ->
val container = TaskContainer() val container = TaskContainer()
val parent = lookup.valueOf(PARENT, null as TaskContainer?) val parent = lookup.valueOf(PARENT, null as TaskContainer?)
val taskId = lookup.valueOf(ID, NO_ID) val taskId = lookup.valueOf(ID, NO_ID)
val created = lookup.valueOf(CREATED, newDateTime())
container.task = newTask( container.task = newTask(
with(TaskMaker.ID, taskId), with(TaskMaker.ID, taskId),
with(TaskMaker.CREATION_TIME, created),
with(TaskMaker.PARENT, parent?.id ?: 0L)) with(TaskMaker.PARENT, parent?.id ?: 0L))
container.indent = parent?.indent?.plus(1) ?: 0 container.indent = parent?.indent?.plus(1) ?: 0
container container

@ -89,6 +89,7 @@ object TaskMaker {
task.uuid = lookup.valueOf(UUID, NO_UUID) task.uuid = lookup.valueOf(UUID, NO_UUID)
val creationTime = lookup.valueOf(CREATION_TIME, DateTimeUtils.newDateTime()) val creationTime = lookup.valueOf(CREATION_TIME, DateTimeUtils.newDateTime())
task.creationDate = creationTime.millis task.creationDate = creationTime.millis
task.modificationDate = creationTime.millis
task.parent = lookup.valueOf(PARENT, 0L) task.parent = lookup.valueOf(PARENT, 0L)
task task
} }

@ -33,7 +33,7 @@ public class SortHelper {
public static final int SORT_GTASKS = 6; public static final int SORT_GTASKS = 6;
public static final int SORT_CALDAV = 7; 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") @SuppressLint("DefaultLocale")
public static final String CALDAV_ORDER_COLUMN = public static final String CALDAV_ORDER_COLUMN =
String.format("IFNULL(caldav_tasks.cd_order, (tasks.created - %d) / 1000)", APPLE_EPOCH); String.format("IFNULL(caldav_tasks.cd_order, (tasks.created - %d) / 1000)", APPLE_EPOCH);

@ -2,6 +2,8 @@ package org.tasks.data
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.room.* import androidx.room.*
import com.todoroo.andlib.utility.DateUtilities.now
import com.todoroo.astrid.core.SortHelper.APPLE_EPOCH
import io.reactivex.Single import io.reactivex.Single
import org.tasks.db.DbUtils import org.tasks.db.DbUtils
import org.tasks.filters.CaldavFilters import org.tasks.filters.CaldavFilters
@ -164,4 +166,29 @@ abstract class CaldavDao {
+ " AND caldav_tasks.cd_deleted = 0), 0)" + " 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)") + "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) abstract fun updateParents(calendar: String)
@Transaction
open fun shiftDown(calendar: String, parent: Long, from: Long, to: Long? = null) {
val updated = ArrayList<CaldavTask>()
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<Long>, 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<CaldavTaskContainer>
} }

@ -2,6 +2,7 @@ package org.tasks.data
import androidx.room.Embedded import androidx.room.Embedded
import com.todoroo.astrid.data.Task import com.todoroo.astrid.data.Task
import org.tasks.time.DateTime
class CaldavTaskContainer { class CaldavTaskContainer {
@Embedded lateinit var task: Task @Embedded lateinit var task: Task
@ -16,6 +17,9 @@ class CaldavTaskContainer {
val vtodo: String? val vtodo: String?
get() = caldavTask.vtodo get() = caldavTask.vtodo
val sortOrder: Long
get() = caldavTask.order ?: DateTime(task.creationDate).toAppleEpoch()
override fun toString(): String { override fun toString(): String {
return "CaldavTaskContainer{task=$task, caldavTask=$caldavTask}" return "CaldavTaskContainer{task=$task, caldavTask=$caldavTask}"
} }

@ -1,5 +1,6 @@
package org.tasks.time; package org.tasks.time;
import static com.todoroo.astrid.core.SortHelper.APPLE_EPOCH;
import static java.util.Calendar.FRIDAY; import static java.util.Calendar.FRIDAY;
import static java.util.Calendar.MONDAY; import static java.util.Calendar.MONDAY;
import static java.util.Calendar.SATURDAY; import static java.util.Calendar.SATURDAY;
@ -340,6 +341,10 @@ public class DateTime {
return timestamp == 0 ? null : LocalDateTime.of(getYear(), getMonthOfYear(), getDayOfMonth(), getHourOfDay(), getMinuteOfHour()); return timestamp == 0 ? null : LocalDateTime.of(getYear(), getMonthOfYear(), getDayOfMonth(), getHourOfDay(), getMinuteOfHour());
} }
public long toAppleEpoch() {
return (timestamp - APPLE_EPOCH) / 1000;
}
public int getDayOfWeekInMonth() { public int getDayOfWeekInMonth() {
return getCalendar().get(Calendar.DAY_OF_WEEK_IN_MONTH); return getCalendar().get(Calendar.DAY_OF_WEEK_IN_MONTH);
} }

Loading…
Cancel
Save