Remove gt_parent from CaldavTask

pull/2146/head
Alex Baker 3 years ago
parent f6ca98e096
commit c2222657ec

@ -2,7 +2,7 @@
"formatVersion": 1, "formatVersion": 1,
"database": { "database": {
"version": 88, "version": 88,
"identityHash": "2b5bd6d7c523006b902d035f71093be2", "identityHash": "a2597380bdfaeeb040d40bab2fc653c1",
"entities": [ "entities": [
{ {
"tableName": "notification", "tableName": "notification",
@ -875,7 +875,7 @@
}, },
{ {
"tableName": "caldav_tasks", "tableName": "caldav_tasks",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`cd_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `cd_task` INTEGER NOT NULL, `cd_calendar` TEXT, `cd_object` TEXT, `cd_remote_id` TEXT, `cd_etag` TEXT, `cd_last_sync` INTEGER NOT NULL, `cd_deleted` INTEGER NOT NULL, `cd_remote_parent` TEXT, `gt_moved` INTEGER NOT NULL, `gt_remote_order` INTEGER NOT NULL, `gt_parent` INTEGER NOT NULL, `cd_order` INTEGER, FOREIGN KEY(`cd_task`) REFERENCES `tasks`(`_id`) ON UPDATE NO ACTION ON DELETE CASCADE )", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`cd_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `cd_task` INTEGER NOT NULL, `cd_calendar` TEXT, `cd_object` TEXT, `cd_remote_id` TEXT, `cd_etag` TEXT, `cd_last_sync` INTEGER NOT NULL, `cd_deleted` INTEGER NOT NULL, `cd_remote_parent` TEXT, `gt_moved` INTEGER NOT NULL, `gt_remote_order` INTEGER NOT NULL, FOREIGN KEY(`cd_task`) REFERENCES `tasks`(`_id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [ "fields": [
{ {
"fieldPath": "id", "fieldPath": "id",
@ -942,18 +942,6 @@
"columnName": "gt_remote_order", "columnName": "gt_remote_order",
"affinity": "INTEGER", "affinity": "INTEGER",
"notNull": true "notNull": true
},
{
"fieldPath": "parent",
"columnName": "gt_parent",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "order",
"columnName": "cd_order",
"affinity": "INTEGER",
"notNull": false
} }
], ],
"primaryKey": { "primaryKey": {
@ -1309,7 +1297,7 @@
"views": [], "views": [],
"setupQueries": [ "setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", "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, '2b5bd6d7c523006b902d035f71093be2')" "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'a2597380bdfaeeb040d40bab2fc653c1')"
] ]
} }
} }

@ -151,13 +151,6 @@ class GoogleTaskDaoTests : InjectingTestCase() {
assertEquals(2, googleTaskDao.getByRemoteId("1")!!.order) assertEquals(2, googleTaskDao.getByRemoteId("1")!!.order)
} }
@Test
fun findChildrenInList() = runBlocking {
insert(newGoogleTask(with(TASK, 1), with(LIST, "1")))
insert(newGoogleTask(with(TASK, 2), with(LIST, "1"), with(PARENT, 1L)))
assertEquals(listOf(2L), googleTaskDao.getChildren(listOf(1L, 2L)))
}
@Test @Test
fun dontAllowEmptyParent() = runBlocking { fun dontAllowEmptyParent() = runBlocking {
insert(newGoogleTask(with(TASK, 1), with(LIST, "1"), with(REMOTE_ID, "1234"))) insert(newGoogleTask(with(TASK, 1), with(LIST, "1"), with(REMOTE_ID, "1234")))

@ -17,21 +17,75 @@ class GoogleTaskManualSortAdapter internal constructor(
val task = getTask(from) val task = getTask(from)
val googleTask = task.caldavTask val googleTask = task.caldavTask
val previous = if (to > 0) getTask(to - 1) else null val previous = if (to > 0) getTask(to - 1) else null
val list = googleTask.calendar!!
if (previous == null) { if (previous == null) {
googleTaskDao.move(googleTask, 0, 0) googleTaskDao.move(
task = task.task,
list = list,
newParent = 0,
newPosition = 0,
)
} else if (to == count || to <= from) { } else if (to == count || to <= from) {
when { when {
indent == 0 -> googleTaskDao.move(googleTask, 0, previous.getPrimarySort() + if (to == count) 0 else 1) indent == 0 ->
previous.hasParent() && previous.parent == task.parent -> googleTaskDao.move(googleTask, previous.parent, previous.getSecondarySort() + if (to == count) 0 else 1) googleTaskDao.move(
previous.hasParent() -> googleTaskDao.move(googleTask, previous.parent, previous.getSecondarySort() + 1) task = task.task,
else -> googleTaskDao.move(googleTask, previous.id, 0) list = list,
newParent = 0,
newPosition = previous.getPrimarySort() + if (to == count) 0 else 1,
)
previous.hasParent() && previous.parent == task.parent ->
googleTaskDao.move(
task = task.task,
list = list,
newParent = previous.parent,
newPosition = previous.getSecondarySort() + if (to == count) 0 else 1,
)
previous.hasParent() ->
googleTaskDao.move(
task = task.task,
list = list,
newParent = previous.parent,
newPosition = previous.getSecondarySort() + 1,
)
else ->
googleTaskDao.move(
task = task.task,
list = list,
newParent = previous.id,
newPosition = 0,
)
} }
} else { } else {
when { when {
indent == 0 -> googleTaskDao.move(googleTask, 0, previous.getPrimarySort() + if (task.hasParent()) 1 else 0) indent == 0 ->
previous.hasParent() && previous.parent == task.parent -> googleTaskDao.move(googleTask, previous.parent, previous.getSecondarySort()) googleTaskDao.move(
previous.hasParent() -> googleTaskDao.move(googleTask, previous.parent, previous.getSecondarySort() + 1) task = task.task,
else -> googleTaskDao.move(googleTask, previous.id, 0) list = list,
newParent = 0,
newPosition = previous.getPrimarySort() + if (task.hasParent()) 1 else 0,
)
previous.hasParent() && previous.parent == task.parent ->
googleTaskDao.move(
task = task.task,
list = list,
newParent = previous.parent,
newPosition = previous.getSecondarySort(),
)
previous.hasParent() ->
googleTaskDao.move(
task = task.task,
list = list,
newParent = previous.parent,
newPosition = previous.getSecondarySort() + 1,
)
else ->
googleTaskDao.move(
task = task.task,
list = list,
newParent = previous.id,
newPosition = 0,
)
} }
} }
taskDao.touch(task.id) taskDao.touch(task.id)

@ -11,7 +11,10 @@ import com.todoroo.astrid.data.Task
import com.todoroo.astrid.data.Task.Companion.HIDE_UNTIL_SPECIFIC_DAY import com.todoroo.astrid.data.Task.Companion.HIDE_UNTIL_SPECIFIC_DAY
import org.tasks.BuildConfig import org.tasks.BuildConfig
import org.tasks.LocalBroadcastManager import org.tasks.LocalBroadcastManager
import org.tasks.data.* import org.tasks.data.CaldavDao
import org.tasks.data.CaldavTask
import org.tasks.data.GoogleTaskDao
import org.tasks.data.TaskContainer
import org.tasks.date.DateTimeUtils.toAppleEpoch import org.tasks.date.DateTimeUtils.toAppleEpoch
import org.tasks.date.DateTimeUtils.toDateTime import org.tasks.date.DateTimeUtils.toDateTime
import org.tasks.tasklist.SectionedDataSource.Companion.HEADER_COMPLETED import org.tasks.tasklist.SectionedDataSource.Companion.HEADER_COMPLETED
@ -241,15 +244,15 @@ open class TaskAdapter(
val list = newParent?.caldav ?: task.caldav!! val list = newParent?.caldav ?: task.caldav!!
if (newParent == null || task.caldav == newParent.caldav) { if (newParent == null || task.caldav == newParent.caldav) {
googleTaskDao.move( googleTaskDao.move(
task.caldavTask, task.task,
list,
newParent?.id ?: 0, newParent?.id ?: 0,
if (newTasksOnTop) 0 else googleTaskDao.getBottom(list, newParent?.id ?: 0) if (newTasksOnTop) 0 else googleTaskDao.getBottom(list, newParent?.id ?: 0)
) )
} else { } else {
task.caldavTask = CaldavTask(task.id, list).apply { task.parent = newParent.id
parent = newParent.id task.caldavTask = CaldavTask(task.id, list)
} googleTaskDao.insertAndShift(task.task, task.caldavTask, newTasksOnTop)
googleTaskDao.insertAndShift(task.caldavTask, newTasksOnTop)
} }
taskDao.touch(task.id) taskDao.touch(task.id)
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {

@ -72,8 +72,8 @@ public class GtasksFilter extends Filter {
return values; return values;
} }
public long getStoreId() { public String getListId() {
return list.getId(); return list.getUuid();
} }
public String getAccount() { public String getAccount() {

@ -176,7 +176,7 @@ public class SortHelper {
select = "tasks.created AS sort_created"; select = "tasks.created AS sort_created";
break; break;
case SORT_GTASKS: case SORT_GTASKS:
select = "caldav_tasks.cd_order AS sort_manual"; select = "tasks.`order` AS sort_manual";
break; break;
case SORT_CALDAV: case SORT_CALDAV:
select = CALDAV_ORDER_COLUMN + " AS sort_manual"; select = CALDAV_ORDER_COLUMN + " AS sort_manual";

@ -35,11 +35,9 @@ class TaskCompleter @Inject internal constructor(
ArrayList<Task?>() ArrayList<Task?>()
.apply { .apply {
if (includeChildren) { if (includeChildren) {
addAll(googleTaskDao.getChildTasks(item.id))
addAll(taskDao.getChildren(item.id).let { taskDao.fetch(it) }) addAll(taskDao.getChildren(item.id).let { taskDao.fetch(it) })
} }
if (!completed) { if (!completed) {
add(googleTaskDao.getParentTask(item.id))
addAll(taskDao.getParents(item.id).let { taskDao.fetch(it) }) addAll(taskDao.getParents(item.id).let { taskDao.fetch(it) })
} }
add(item) add(item)

@ -55,7 +55,10 @@ class TaskCreator @Inject constructor(
val addToTop = preferences.addTasksToTop() val addToTop = preferences.addTasksToTop()
if (task.hasTransitory(GoogleTask.KEY)) { if (task.hasTransitory(GoogleTask.KEY)) {
googleTaskDao.insertAndShift( googleTaskDao.insertAndShift(
CaldavTask(task.id, task.getTransitory<String>(GoogleTask.KEY)!!), addToTop) task,
CaldavTask(task.id, task.getTransitory<String>(GoogleTask.KEY)!!),
addToTop
)
} else if (task.hasTransitory(CaldavTask.KEY)) { } else if (task.hasTransitory(CaldavTask.KEY)) {
caldavDao.insert( caldavDao.insert(
task, CaldavTask(task.id, task.getTransitory<String>(CaldavTask.KEY)), addToTop) task, CaldavTask(task.id, task.getTransitory<String>(CaldavTask.KEY)), addToTop)
@ -63,7 +66,10 @@ class TaskCreator @Inject constructor(
val remoteList = defaultFilterProvider.getDefaultList() val remoteList = defaultFilterProvider.getDefaultList()
if (remoteList is GtasksFilter) { if (remoteList is GtasksFilter) {
googleTaskDao.insertAndShift( googleTaskDao.insertAndShift(
CaldavTask(task.id, remoteList.remoteId), addToTop) task,
CaldavTask(task.id, remoteList.remoteId),
addToTop
)
} else if (remoteList is CaldavFilter) { } else if (remoteList is CaldavFilter) {
caldavDao.insert( caldavDao.insert(
task, CaldavTask(task.id, remoteList.uuid), addToTop) task, CaldavTask(task.id, remoteList.uuid), addToTop)

@ -28,7 +28,6 @@ class TaskDeleter @Inject constructor(
suspend fun markDeleted(taskIds: List<Long>): List<Task> { suspend fun markDeleted(taskIds: List<Long>): List<Task> {
val ids = taskIds val ids = taskIds
.toSet() .toSet()
.plus(taskIds.chunkedMap(googleTaskDao::getChildren))
.plus(taskIds.chunkedMap(taskDao::getChildren)) .plus(taskIds.chunkedMap(taskDao::getChildren))
.let { taskDao.fetch(it.toList()) } .let { taskDao.fetch(it.toList()) }
.filterNot { it.readOnly } .filterNot { it.readOnly }
@ -50,7 +49,6 @@ class TaskDeleter @Inject constructor(
.map(TaskContainer::getId) .map(TaskContainer::getId)
.toMutableList() .toMutableList()
completed.removeAll(deletionDao.hasRecurringAncestors(completed)) completed.removeAll(deletionDao.hasRecurringAncestors(completed))
completed.removeAll(googleTaskDao.hasRecurringParent(completed))
markDeleted(completed) markDeleted(completed)
return completed.size return completed.size
} }

@ -28,8 +28,7 @@ class TaskDuplicator @Inject constructor(
return taskIds return taskIds
.dbchunk() .dbchunk()
.flatMap { .flatMap {
it.minus(googleTaskDao.getChildren(it).toSet()) it.minus(taskDao.getChildren(it).toSet())
.minus(taskDao.getChildren(it).toSet())
} }
.let { taskDao.fetch(it) } .let { taskDao.fetch(it) }
.filterNot { it.readOnly } .filterNot { it.readOnly }
@ -58,7 +57,7 @@ class TaskDuplicator @Inject constructor(
val googleTask = googleTaskDao.getByTaskId(originalId) val googleTask = googleTaskDao.getByTaskId(originalId)
val addToTop = preferences.addTasksToTop() val addToTop = preferences.addTasksToTop()
if (googleTask != null) { if (googleTask != null) {
googleTaskDao.insertAndShift(CaldavTask(clone.id, googleTask.calendar!!), addToTop) googleTaskDao.insertAndShift(clone, CaldavTask(clone.id, googleTask.calendar!!), addToTop)
} }
val caldavTask = caldavDao.getTask(originalId) val caldavTask = caldavDao.getTask(originalId)
if (caldavTask != null) { if (caldavTask != null) {

@ -46,11 +46,8 @@ class TaskMover @Inject constructor(
suspend fun move(ids: List<Long>, selectedList: Filter) { suspend fun move(ids: List<Long>, selectedList: Filter) {
val tasks = ids val tasks = ids
.dbchunk() .dbchunk()
.flatMap { .flatMap { taskDao.getChildren(it) }
it.minus(googleTaskDao.getChildren(it).toSet()) .let { taskDao.fetch(ids.minus(it.toSet())) }
.minus(taskDao.getChildren(it).toSet())
}
.let { taskDao.fetch(it) }
.filterNot { it.readOnly } .filterNot { it.readOnly }
val taskIds = tasks.map { it.id } val taskIds = tasks.map { it.id }
taskDao.setParent(0, ids.intersect(taskIds.toSet()).toList()) taskDao.setParent(0, ids.intersect(taskIds.toSet()).toList())
@ -86,28 +83,22 @@ class TaskMover @Inject constructor(
if (selected is GtasksFilter && googleTask.calendar == selected.remoteId) { if (selected is GtasksFilter && googleTask.calendar == selected.remoteId) {
return return
} }
val id = googleTask.task val id = task.id
val children = googleTaskDao.getChildren(id) val children = taskDao.getChildren(id)
val childIds = children.map(CaldavTask::task) caldavDao.markDeleted(children + id, DateUtilities.now())
googleTaskDao.markDeleted(id, DateUtilities.now())
when(selected) { when(selected) {
is GtasksFilter -> { is GtasksFilter -> {
val listId = selected.remoteId val listId = selected.remoteId
googleTaskDao.insertAndShift(CaldavTask(id, listId), preferences.addTasksToTop()) googleTaskDao.insertAndShift(task, CaldavTask(id, listId), preferences.addTasksToTop())
children.takeIf { it.isNotEmpty() } children.takeIf { it.isNotEmpty() }
?.map { ?.map { CaldavTask(it, listId) }
val newChild = CaldavTask(it.task, listId)
newChild.order = it.order
newChild.parent = id
newChild
}
?.let { googleTaskDao.insert(it) } ?.let { googleTaskDao.insert(it) }
} }
is CaldavFilter -> { is CaldavFilter -> {
val listId = selected.uuid val listId = selected.uuid
val newParent = CaldavTask(id, listId) val newParent = CaldavTask(id, listId)
caldavDao.insert(task, newParent, preferences.addTasksToTop()) caldavDao.insert(task, newParent, preferences.addTasksToTop())
childIds.map { children.map {
val newChild = CaldavTask(it, listId) val newChild = CaldavTask(it, listId)
newChild.remoteParent = newParent.remoteId newChild.remoteParent = newParent.remoteId
newChild newChild
@ -176,16 +167,16 @@ class TaskMover @Inject constructor(
} }
private suspend fun moveToGoogleTasks(id: Long, children: List<Long>, filter: GtasksFilter) { private suspend fun moveToGoogleTasks(id: Long, children: List<Long>, filter: GtasksFilter) {
taskDao.setParent(0, children) val task = taskDao.fetch(id) ?: return
taskDao.setParent(id, children)
val listId = filter.remoteId val listId = filter.remoteId
googleTaskDao.insertAndShift(CaldavTask(id, listId), preferences.addTasksToTop()) googleTaskDao.insertAndShift(
task,
CaldavTask(id, listId, remoteId = null, `object` = null),
preferences.addTasksToTop()
)
children.takeIf { it.isNotEmpty() } children.takeIf { it.isNotEmpty() }
?.mapIndexed { index, task -> ?.map { CaldavTask(it, listId, remoteId = null, `object` = null) }
val newChild = CaldavTask(task, listId)
newChild.order = index.toLong()
newChild.parent = id
newChild
}
?.let { googleTaskDao.insert(it) } ?.let { googleTaskDao.insert(it) }
} }
} }

@ -72,7 +72,7 @@ class Upgrader @Inject constructor(
taskMover.migrateLocalTasks() taskMover.migrateLocalTasks()
} }
run(from, V9_7) { caldavDao.resetOrders() } run(from, V9_7) { caldavDao.resetOrders() }
run(from, V9_7_3) { googleTaskDao.updateParents() } run(from, V9_7_3) { caldavDao.updateParents() }
run(from, V10_0_2) { run(from, V10_0_2) {
filterDao.getFilters() filterDao.getFilters()
.filter { it.getSql().trim() == "WHERE" } .filter { it.getSql().trim() == "WHERE" }

@ -39,7 +39,6 @@ class TasksJsonImporter @Inject constructor(
private val localBroadcastManager: LocalBroadcastManager, private val localBroadcastManager: LocalBroadcastManager,
private val alarmDao: AlarmDao, private val alarmDao: AlarmDao,
private val tagDao: TagDao, private val tagDao: TagDao,
private val googleTaskDao: GoogleTaskDao,
private val filterDao: FilterDao, private val filterDao: FilterDao,
private val taskAttachmentDao: TaskAttachmentDao, private val taskAttachmentDao: TaskAttachmentDao,
private val caldavDao: CaldavDao, private val caldavDao: CaldavDao,
@ -187,13 +186,17 @@ class TasksJsonImporter @Inject constructor(
userActivityDao.createNew(comment) userActivityDao.createNew(comment)
} }
for (googleTask in backup.google) { for (googleTask in backup.google) {
googleTaskDao.insert( caldavDao.insert(
CaldavTask( CaldavTask(
task = taskId, task = taskId,
calendar = googleTask.listId, calendar = googleTask.listId,
remoteId = googleTask.remoteId, remoteId = googleTask.remoteId,
`object` = null, `object` = null,
) ).apply {
remoteOrder = googleTask.remoteOrder
remoteParent = googleTask.remoteParent
lastSync = googleTask.lastSync
}
) )
} }
for (location in backup.locations) { for (location in backup.locations) {
@ -246,7 +249,6 @@ class TasksJsonImporter @Inject constructor(
} }
result.importCount++ result.importCount++
} }
googleTaskDao.updateParents()
caldavDao.updateParents() caldavDao.updateParents()
val ignoreKeys = ignorePrefs.map { context.getString(it) } val ignoreKeys = ignorePrefs.map { context.getString(it) }
backupContainer backupContainer

@ -35,6 +35,7 @@ import org.tasks.data.TaskContainer
fun SubtaskRow( fun SubtaskRow(
filter: Filter?, filter: Filter?,
googleTask: CaldavTask?, googleTask: CaldavTask?,
hasParent: Boolean,
desaturate: Boolean, desaturate: Boolean,
existingSubtasks: List<TaskContainer>, existingSubtasks: List<TaskContainer>,
newSubtasks: List<Task>, newSubtasks: List<Task>,
@ -63,7 +64,7 @@ fun SubtaskRow(
Column { Column {
val isGoogleTaskChild = val isGoogleTaskChild =
filter is GtasksFilter && googleTask != null && googleTask.parent > 0 && googleTask.calendar == filter.remoteId filter is GtasksFilter && hasParent && googleTask?.calendar == filter.remoteId
if (isGoogleTaskChild) { if (isGoogleTaskChild) {
DisabledText( DisabledText(
text = stringResource(id = org.tasks.R.string.subtasks_multilevel_google_task), text = stringResource(id = org.tasks.R.string.subtasks_multilevel_google_task),
@ -209,6 +210,7 @@ fun NoSubtasks() {
SubtaskRow( SubtaskRow(
filter = null, filter = null,
googleTask = null, googleTask = null,
hasParent = false,
desaturate = true, desaturate = true,
existingSubtasks = emptyList(), existingSubtasks = emptyList(),
newSubtasks = emptyList(), newSubtasks = emptyList(),
@ -230,6 +232,7 @@ fun SubtasksPreview() {
SubtaskRow( SubtaskRow(
filter = null, filter = null,
googleTask = null, googleTask = null,
hasParent = false,
desaturate = true, desaturate = true,
existingSubtasks = listOf( existingSubtasks = listOf(
TaskContainer().apply { TaskContainer().apply {

@ -54,12 +54,9 @@ class CaldavTask {
@ColumnInfo(name = "gt_remote_order") @ColumnInfo(name = "gt_remote_order")
var remoteOrder: Long = 0 var remoteOrder: Long = 0
@ColumnInfo(name = "gt_parent")
var parent: Long = 0
@Transient @Transient
@Deprecated("For google tasks and importing old backup files") @Ignore
@ColumnInfo(name = "cd_order") @Deprecated("For importing old backup files")
var order: Long? = null var order: Long? = null
constructor() constructor()
@ -89,7 +86,6 @@ class CaldavTask {
const val KEY = "caldav" const val KEY = "caldav"
@JvmField val TABLE = Table("caldav_tasks") @JvmField val TABLE = Table("caldav_tasks")
val ID = TABLE.column("cd_id") val ID = TABLE.column("cd_id")
val PARENT = TABLE.column("gt_parent")
@JvmField val TASK = TABLE.column("cd_task") @JvmField val TASK = TABLE.column("cd_task")
@JvmField val DELETED = TABLE.column("cd_deleted") @JvmField val DELETED = TABLE.column("cd_deleted")
@JvmField val CALENDAR = TABLE.column("cd_calendar") @JvmField val CALENDAR = TABLE.column("cd_calendar")

@ -3,8 +3,7 @@ package org.tasks.data
import androidx.room.* import androidx.room.*
import com.todoroo.astrid.data.Task import com.todoroo.astrid.data.Task
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import org.tasks.db.SuspendDbUtils.chunkedMap import org.tasks.data.CaldavAccount.Companion.TYPE_GOOGLE_TASKS
import org.tasks.time.DateTimeUtils.currentTimeMillis
@Dao @Dao
abstract class GoogleTaskDao { abstract class GoogleTaskDao {
@ -15,61 +14,63 @@ abstract class GoogleTaskDao {
abstract suspend fun insert(tasks: Iterable<CaldavTask>) abstract suspend fun insert(tasks: Iterable<CaldavTask>)
@Transaction @Transaction
open suspend fun insertAndShift(task: CaldavTask, top: Boolean) { open suspend fun insertAndShift(task: Task, caldavTask: CaldavTask, top: Boolean) {
if (top) { if (top) {
task.order = 0 task.order = 0
shiftDown(task.calendar!!, task.parent, 0) shiftDown(caldavTask.calendar!!, task.parent, 0)
} else { } else {
task.order = getBottom(task.calendar!!, task.parent) task.order = getBottom(caldavTask.calendar!!, task.parent)
} }
task.id = insert(task) caldavTask.id = insert(caldavTask)
update(task)
} }
@Query("UPDATE caldav_tasks SET cd_order = cd_order + 1 WHERE cd_calendar = :listId AND gt_parent = :parent AND cd_order >= :position") @Query("UPDATE tasks SET `order` = `order` + 1 WHERE parent = :parent AND `order` >= :position AND _id IN (SELECT cd_task FROM caldav_tasks WHERE cd_calendar = :listId)")
internal abstract suspend fun shiftDown(listId: String, parent: Long, position: Long) internal abstract suspend fun shiftDown(listId: String, parent: Long, position: Long)
@Query("UPDATE caldav_tasks SET cd_order = cd_order - 1 WHERE cd_calendar = :listId AND gt_parent = :parent AND cd_order > :from AND cd_order <= :to") @Query("UPDATE tasks SET `order` = `order` - 1 WHERE parent = :parent AND `order` > :from AND `order` <= :to AND _id IN (SELECT cd_task FROM caldav_tasks WHERE cd_calendar = :listId)")
internal abstract suspend fun shiftUp(listId: String, parent: Long, from: Long, to: Long) internal abstract suspend fun shiftUp(listId: String, parent: Long, from: Long, to: Long)
@Query("UPDATE caldav_tasks SET cd_order = cd_order + 1 WHERE cd_calendar = :listId AND gt_parent = :parent AND cd_order < :from AND cd_order >= :to") @Query("UPDATE tasks SET `order` = `order` + 1 WHERE parent = :parent AND `order` < :from AND `order` >= :to AND _id IN (SELECT cd_task FROM caldav_tasks WHERE cd_calendar = :listId)")
internal abstract suspend fun shiftDown(listId: String, parent: Long, from: Long, to: Long) internal abstract suspend fun shiftDown(listId: String, parent: Long, from: Long, to: Long)
@Query("UPDATE caldav_tasks SET cd_order = cd_order - 1 WHERE cd_calendar = :listId AND gt_parent = :parent AND cd_order >= :position") @Query("UPDATE tasks SET `order` = `order` - 1 WHERE parent = :parent AND `order` >= :position AND _id IN (SELECT cd_task FROM caldav_tasks WHERE cd_calendar = :listId)")
internal abstract suspend fun shiftUp(listId: String, parent: Long, position: Long) internal abstract suspend fun shiftUp(listId: String, parent: Long, position: Long)
@Transaction @Transaction
open suspend fun move(task: CaldavTask, newParent: Long, newPosition: Long) { open suspend fun move(task: Task, list: String, newParent: Long, newPosition: Long) {
val previousParent = task.parent val previousParent = task.parent
val previousPosition = task.order!! val previousPosition = task.order!!
if (newParent == previousParent) { if (newParent == previousParent) {
if (previousPosition < newPosition) { if (previousPosition < newPosition) {
shiftUp(task.calendar!!, newParent, previousPosition, newPosition) shiftUp(list, newParent, previousPosition, newPosition)
} else { } else {
shiftDown(task.calendar!!, newParent, previousPosition, newPosition) shiftDown(list, newParent, previousPosition, newPosition)
} }
} else { } else {
shiftUp(task.calendar!!, previousParent, previousPosition) shiftUp(list, previousParent, previousPosition)
shiftDown(task.calendar!!, newParent, newPosition) shiftDown(list, newParent, newPosition)
} }
task.parent = newParent task.parent = newParent
task.order = newPosition task.order = newPosition
update(task) update(task)
setMoved(task.id, list)
} }
@Query("SELECT * FROM caldav_tasks WHERE cd_task = :taskId AND cd_deleted = 0 LIMIT 1") @Query("UPDATE caldav_tasks SET gt_moved = 1 WHERE cd_task = :task and cd_calendar = :list")
internal abstract suspend fun setMoved(task: Long, list: String)
@Query("SELECT caldav_tasks.* FROM caldav_tasks INNER JOIN caldav_lists ON cdl_uuid = cd_calendar INNER JOIN caldav_accounts ON cda_uuid = cdl_account WHERE cd_task = :taskId AND cd_deleted = 0 AND cda_account_type = $TYPE_GOOGLE_TASKS LIMIT 1")
abstract suspend fun getByTaskId(taskId: Long): CaldavTask? abstract suspend fun getByTaskId(taskId: Long): CaldavTask?
@Query("SELECT * FROM caldav_tasks WHERE cd_task = :taskId AND cd_deleted = 0 LIMIT 1") @Query("SELECT caldav_tasks.* FROM caldav_tasks INNER JOIN caldav_lists ON cdl_uuid = cd_calendar INNER JOIN caldav_accounts ON cda_uuid = cdl_account WHERE cd_task = :taskId AND cd_deleted = 0 AND cda_account_type = $TYPE_GOOGLE_TASKS LIMIT 1")
abstract fun watchGoogleTask(taskId: Long): Flow<CaldavTask?> abstract fun watchGoogleTask(taskId: Long): Flow<CaldavTask?>
@Update @Update
abstract suspend fun update(googleTask: CaldavTask) abstract suspend fun update(googleTask: CaldavTask)
@Query("UPDATE caldav_tasks SET cd_order = :order, gt_parent = :parent, gt_moved = 1 WHERE cd_id = :id") @Update
abstract suspend fun update(id: Long, parent: Long, order: Long) abstract suspend fun update(task: Task)
@Query("UPDATE caldav_tasks SET cd_deleted = :now WHERE cd_task = :task OR gt_parent = :task")
abstract suspend fun markDeleted(task: Long, now: Long = currentTimeMillis())
@Delete @Delete
abstract suspend fun delete(deleted: CaldavTask) abstract suspend fun delete(deleted: CaldavTask)
@ -86,48 +87,21 @@ abstract class GoogleTaskDao {
@Query("SELECT DISTINCT cd_calendar FROM caldav_tasks WHERE cd_deleted = 0 AND cd_task IN (:tasks)") @Query("SELECT DISTINCT cd_calendar FROM caldav_tasks WHERE cd_deleted = 0 AND cd_task IN (:tasks)")
abstract suspend fun getLists(tasks: List<Long>): List<String> abstract suspend fun getLists(tasks: List<Long>): List<String>
@Query("SELECT cd_task FROM caldav_tasks WHERE gt_parent IN (:ids) AND cd_deleted = 0") @Query("SELECT IFNULL(MAX(`order`), -1) + 1 FROM tasks INNER JOIN caldav_tasks ON cd_task = tasks._id WHERE cd_calendar = :listId AND parent = :parent")
abstract suspend fun getChildren(ids: List<Long>): List<Long>
suspend fun hasRecurringParent(ids: List<Long>): List<Long> =
ids.chunkedMap { internalHasRecurringParent(it) }
@Query("""
SELECT cd_task
FROM caldav_tasks
INNER JOIN tasks ON gt_parent = _id
WHERE cd_task IN (:ids)
AND cd_deleted = 0
AND tasks.recurrence IS NOT NULL
AND tasks.recurrence != ''
AND tasks.completed = 0
""")
abstract suspend fun internalHasRecurringParent(ids: List<Long>): List<Long>
@Query("SELECT tasks.* FROM tasks JOIN caldav_tasks ON tasks._id = cd_task WHERE gt_parent = :taskId")
abstract suspend fun getChildTasks(taskId: Long): List<Task>
@Query("SELECT tasks.* FROM tasks JOIN caldav_tasks ON tasks._id = gt_parent WHERE cd_task = :taskId")
abstract suspend fun getParentTask(taskId: Long): Task?
@Query("SELECT * FROM caldav_tasks WHERE gt_parent = :id AND cd_deleted = 0")
abstract suspend fun getChildren(id: Long): List<CaldavTask>
@Query("SELECT IFNULL(MAX(cd_order), -1) + 1 FROM caldav_tasks WHERE cd_calendar = :listId AND gt_parent = :parent")
abstract suspend fun getBottom(listId: String, parent: Long): Long abstract suspend fun getBottom(listId: String, parent: Long): Long
@Query( @Query(
""" """
SELECT cd_remote_id SELECT cd_remote_id
FROM caldav_tasks FROM caldav_tasks
JOIN tasks ON tasks._id = cd_task INNER JOIN tasks ON tasks._id = cd_task
WHERE deleted = 0 WHERE deleted = 0
AND cd_calendar = :listId AND cd_calendar = :listId
AND gt_parent = :parent AND parent = :parent
AND cd_order < :order AND `order` < :order
AND cd_remote_id IS NOT NULL AND cd_remote_id IS NOT NULL
AND cd_remote_id != '' AND cd_remote_id != ''
ORDER BY cd_order DESC ORDER BY `order` DESC
""" """
) )
abstract suspend fun getPrevious(listId: String, parent: Long, order: Long): String? abstract suspend fun getPrevious(listId: String, parent: Long, order: Long): String?
@ -138,80 +112,48 @@ ORDER BY cd_order DESC
@Query("SELECT cd_task FROM caldav_tasks WHERE cd_remote_id = :remoteId") @Query("SELECT cd_task FROM caldav_tasks WHERE cd_remote_id = :remoteId")
abstract suspend fun getTask(remoteId: String): Long? abstract suspend fun getTask(remoteId: String): Long?
@SuppressWarnings(RoomWarnings.CURSOR_MISMATCH)
@Query( @Query(
""" """
SELECT caldav_tasks.*, cd_order AS primary_sort, NULL AS secondary_sort SELECT tasks.*, `order` AS primary_sort, NULL AS secondary_sort
FROM caldav_tasks FROM tasks
JOIN tasks ON tasks._id = cd_task INNER JOIN caldav_tasks ON tasks._id = cd_task
WHERE gt_parent = 0 WHERE parent = 0
AND cd_calendar = :listId AND cd_calendar = :listId
AND tasks.deleted = 0 AND tasks.deleted = 0
UNION UNION
SELECT c.*, p.cd_order AS primary_sort, c.cd_order AS secondary_sort SELECT c.*, p.`order` AS primary_sort, c.`order` AS secondary_sort
FROM caldav_tasks AS c FROM tasks AS c
LEFT JOIN caldav_tasks AS p ON c.gt_parent = p.cd_task INNER JOIN tasks AS p ON c.parent = p._id
JOIN tasks ON tasks._id = c.cd_task INNER JOIN caldav_tasks ON c._id = cd_task
WHERE c.gt_parent > 0 WHERE c.parent > 0
AND c.cd_calendar = :listId AND cd_calendar = :listId
AND tasks.deleted = 0 AND c.deleted = 0
ORDER BY primary_sort ASC, secondary_sort ASC ORDER BY primary_sort ASC, secondary_sort ASC
""" """
) )
abstract suspend fun getByLocalOrder(listId: String): List<CaldavTask> internal abstract suspend fun getByLocalOrder(listId: String): List<Task>
@SuppressWarnings(RoomWarnings.CURSOR_MISMATCH)
@Query( @Query(
""" """
SELECT caldav_tasks.*, gt_remote_order AS primary_sort, NULL AS secondary_sort SELECT tasks.*, gt_remote_order AS primary_sort, NULL AS secondary_sort
FROM caldav_tasks FROM tasks
JOIN tasks ON tasks._id = cd_task JOIN caldav_tasks ON tasks._id = cd_task
WHERE gt_parent = 0 WHERE parent = 0
AND cd_calendar = :listId AND cd_calendar = :listId
AND tasks.deleted = 0 AND tasks.deleted = 0
UNION UNION
SELECT c.*, p.gt_remote_order AS primary_sort, c.gt_remote_order AS secondary_sort SELECT c.*, parent.gt_remote_order AS primary_sort, child.gt_remote_order AS secondary_sort
FROM caldav_tasks AS c FROM tasks AS c
LEFT JOIN caldav_tasks AS p ON c.gt_parent = p.cd_task INNER JOIN tasks AS p ON c.parent = p._id
JOIN tasks ON tasks._id = c.cd_task INNER JOIN caldav_tasks AS child ON c._id = child.cd_task
WHERE c.gt_parent > 0 INNER JOIN caldav_tasks AS parent ON p._id = parent.cd_task
AND c.cd_calendar = :listId WHERE c.parent > 0
AND tasks.deleted = 0 AND child.cd_calendar = :listId
AND c.deleted = 0
ORDER BY primary_sort ASC, secondary_sort ASC ORDER BY primary_sort ASC, secondary_sort ASC
""" """
) )
internal abstract suspend fun getByRemoteOrder(listId: String): List<CaldavTask> internal abstract suspend fun getByRemoteOrder(listId: String): List<Task>
@Query(
"""
UPDATE caldav_tasks
SET gt_parent = IFNULL((SELECT cd_task
FROM caldav_tasks AS p
WHERE caldav_tasks.cd_remote_parent IS NOT NULL
AND caldav_tasks.cd_remote_parent != ''
AND p.cd_remote_id = caldav_tasks.cd_remote_parent
AND p.cd_calendar = caldav_tasks.cd_calendar
AND p.cd_deleted = 0), 0)
WHERE gt_moved = 0
"""
)
abstract suspend fun updateParents()
@Query(
"""
UPDATE caldav_tasks
SET gt_parent = IFNULL((SELECT cd_task
FROM caldav_tasks AS p
WHERE caldav_tasks.cd_remote_parent IS NOT NULL
AND caldav_tasks.cd_remote_parent != ''
AND p.cd_remote_id = caldav_tasks.cd_remote_parent
AND p.cd_calendar = caldav_tasks.cd_calendar
AND p.cd_deleted = 0), 0)
WHERE cd_calendar = :listId
AND gt_moved = 0
"""
)
abstract suspend fun updateParents(listId: String)
@Query(""" @Query("""
UPDATE caldav_tasks UPDATE caldav_tasks
@ -222,21 +164,21 @@ WHERE cd_remote_id = :id
abstract suspend fun updatePosition(id: String, parent: String?, position: String) abstract suspend fun updatePosition(id: String, parent: String?, position: String)
@Transaction @Transaction
open suspend fun reposition(listId: String) { open suspend fun reposition(caldavDao: CaldavDao, listId: String) {
updateParents(listId) caldavDao.updateParents(listId)
val orderedTasks = getByRemoteOrder(listId) val orderedTasks = getByRemoteOrder(listId)
var subtasks = 0L var subtasks = 0L
var parent = 0L var parent = 0L
for (task in orderedTasks) { for (task in orderedTasks) {
if (task.parent > 0) { if (task.parent > 0) {
if (task.order != subtasks && !task.isMoved) { if (task.order != subtasks) {
task.order = subtasks task.order = subtasks
update(task) update(task)
} }
subtasks++ subtasks++
} else { } else {
subtasks = 0 subtasks = 0
if (task.order != parent && !task.isMoved) { if (task.order != parent) {
task.order = parent task.order = parent
update(task) update(task)
} }

@ -2,9 +2,7 @@ package org.tasks.data;
public class SubtaskInfo { public class SubtaskInfo {
public boolean hasSubtasks; public boolean hasSubtasks;
public boolean hasGoogleSubtasks;
public boolean usesSubtasks() { public boolean usesSubtasks() {
return hasSubtasks || hasGoogleSubtasks; return hasSubtasks;
} }
} }

@ -175,19 +175,11 @@ public class TaskContainer {
} }
public long getParent() { public long getParent() {
if (isGoogleTask) { return task.getParent();
return caldavTask.getParent();
} else {
return task.getParent();
}
} }
public void setParent(long parent) { public void setParent(long parent) {
if (isGoogleTask) { task.setParent(parent);
caldavTask.setParent(parent);
} else {
task.setParent(parent);
}
} }
public boolean hasParent() { public boolean hasParent() {

@ -64,7 +64,7 @@ abstract class TaskDao(private val database: Database) {
+ "LEFT JOIN caldav_tasks ON tasks._id = caldav_tasks.cd_task " + "LEFT JOIN caldav_tasks ON tasks._id = caldav_tasks.cd_task "
+ "LEFT JOIN caldav_lists ON caldav_tasks.cd_calendar = caldav_lists.cdl_uuid " + "LEFT JOIN caldav_lists ON caldav_tasks.cd_calendar = caldav_lists.cdl_uuid "
+ "WHERE cdl_account = :account " + "WHERE cdl_account = :account "
+ "AND (tasks.modified > caldav_tasks.cd_last_sync OR caldav_tasks.cd_remote_id = '' OR caldav_tasks.cd_deleted > 0) " + "AND (tasks.modified > caldav_tasks.cd_last_sync OR caldav_tasks.cd_remote_id = '' OR caldav_tasks.cd_remote_id IS NULL OR caldav_tasks.cd_deleted > 0) "
+ "ORDER BY CASE WHEN parent = 0 THEN 0 ELSE 1 END, `order` ASC") + "ORDER BY CASE WHEN parent = 0 THEN 0 ELSE 1 END, `order` ASC")
abstract suspend fun getGoogleTasksToPush(account: String): List<Task> abstract suspend fun getGoogleTasksToPush(account: String): List<Task>
@ -127,15 +127,7 @@ abstract class TaskDao(private val database: Database) {
@RawQuery @RawQuery
abstract suspend fun count(query: SimpleSQLiteQuery): Int abstract suspend fun count(query: SimpleSQLiteQuery): Int
@Query(""" @Query("SELECT EXISTS(SELECT 1 FROM tasks WHERE parent > 0 AND deleted = 0) AS hasSubtasks")
SELECT EXISTS(SELECT 1 FROM tasks WHERE parent > 0 AND deleted = 0) AS hasSubtasks,
EXISTS(SELECT 1
FROM caldav_tasks
INNER JOIN tasks ON cd_task = _id
WHERE deleted = 0
AND gt_parent > 0
AND cd_deleted = 0) AS hasGoogleSubtasks
""")
abstract suspend fun getSubtaskInfo(): SubtaskInfo abstract suspend fun getSubtaskInfo(): SubtaskInfo
@RawQuery(observedEntities = [Place::class]) @RawQuery(observedEntities = [Place::class])

@ -16,9 +16,9 @@ object TaskListQuery {
Task.ID.eq(field("${TaskListFragment.CALDAV_METADATA_JOIN}.cd_task")), Task.ID.eq(field("${TaskListFragment.CALDAV_METADATA_JOIN}.cd_task")),
field("${TaskListFragment.CALDAV_METADATA_JOIN}.cd_deleted").eq(0)) field("${TaskListFragment.CALDAV_METADATA_JOIN}.cd_deleted").eq(0))
val JOINS = """ val JOINS = """
${Join.left(CaldavTask.TABLE.`as`(TaskListFragment.CALDAV_METADATA_JOIN), JOIN_CALDAV)} ${Join.inner(CaldavTask.TABLE.`as`(TaskListFragment.CALDAV_METADATA_JOIN), JOIN_CALDAV)}
${Join.left(CaldavCalendar.TABLE, field("${TaskListFragment.CALDAV_METADATA_JOIN}.cd_calendar").eq(CaldavCalendar.UUID))} ${Join.inner(CaldavCalendar.TABLE, field("${TaskListFragment.CALDAV_METADATA_JOIN}.cd_calendar").eq(CaldavCalendar.UUID))}
${Join.left(CaldavAccount.TABLE, CaldavCalendar.ACCOUNT.eq(CaldavAccount.UUID))} ${Join.inner(CaldavAccount.TABLE, CaldavCalendar.ACCOUNT.eq(CaldavAccount.UUID))}
${Join.left(Geofence.TABLE, Geofence.TASK.eq(Task.ID))} ${Join.left(Geofence.TABLE, Geofence.TASK.eq(Task.ID))}
${Join.left(Place.TABLE, Place.UID.eq(Geofence.PLACE))} ${Join.left(Place.TABLE, Place.UID.eq(Geofence.PLACE))}
""".trimIndent() """.trimIndent()
@ -36,11 +36,11 @@ object TaskListQuery {
subtasks: SubtaskInfo subtasks: SubtaskInfo
): MutableList<String> = when { ): MutableList<String> = when {
filter.supportsManualSort() && preferences.isManualSort -> filter.supportsManualSort() && preferences.isManualSort ->
getRecursiveQuery(filter, preferences, subtasks) getRecursiveQuery(filter, preferences)
filter.supportsAstridSorting() && preferences.isAstridSort -> filter.supportsAstridSorting() && preferences.isAstridSort ->
getNonRecursiveQuery(filter, preferences) getNonRecursiveQuery(filter, preferences)
filter.supportsSubtasks() && subtasks.usesSubtasks() && !preferences.usePagedQueries() -> filter.supportsSubtasks() && subtasks.usesSubtasks() && !preferences.usePagedQueries() ->
getRecursiveQuery(filter, preferences, subtasks) getRecursiveQuery(filter, preferences)
else -> getNonRecursiveQuery(filter, preferences) else -> getNonRecursiveQuery(filter, preferences)
} }
} }

@ -33,21 +33,15 @@ internal object TaskListQueryRecursive {
LEFT JOIN (SELECT parent, count(distinct recursive_tasks.task) AS children FROM recursive_tasks GROUP BY parent) AS recursive_children ON recursive_children.parent = tasks._id LEFT JOIN (SELECT parent, count(distinct recursive_tasks.task) AS children FROM recursive_tasks GROUP BY parent) AS recursive_children ON recursive_children.parent = tasks._id
${TaskListQuery.JOINS} ${TaskListQuery.JOINS}
""".trimIndent() """.trimIndent()
private val GOOGLE_SUBTASKS = private val SUBTASK_QUERY =
QueryTemplate() QueryTemplate()
.join(Join.inner(RECURSIVE, CaldavTask.PARENT.eq(RECURSIVE_TASK))) .join(Join.inner(RECURSIVE, Task.PARENT.eq(RECURSIVE_TASK)))
.join(Join.inner(CaldavTask.TABLE, Criterion.and(CaldavTask.TASK.eq(Task.ID), CaldavTask.DELETED.eq(0)))) // .join(Join.left(CaldavTask.TABLE, Criterion.and(CaldavTask.TASK.eq(Task.ID), CaldavTask.DELETED.eq(0))))
.where(activeAndVisible())
private val ALL_SUBTASKS =
QueryTemplate()
.join(Join.inner(RECURSIVE, Criterion.or(CaldavTask.PARENT.eq(RECURSIVE_TASK), Task.PARENT.eq(RECURSIVE_TASK))))
.join(Join.left(CaldavTask.TABLE, Criterion.and(CaldavTask.TASK.eq(Task.ID), CaldavTask.DELETED.eq(0))))
.where(activeAndVisible()) .where(activeAndVisible())
fun getRecursiveQuery( fun getRecursiveQuery(
filter: Filter, filter: Filter,
preferences: QueryPreferences, preferences: QueryPreferences,
subtasks: SubtaskInfo
): MutableList<String> { ): MutableList<String> {
var joinedQuery = JOINS var joinedQuery = JOINS
var where = " WHERE recursive_tasks.hidden = 0" var where = " WHERE recursive_tasks.hidden = 0"
@ -55,25 +49,16 @@ internal object TaskListQueryRecursive {
val subtaskQuery: QueryTemplate val subtaskQuery: QueryTemplate
when (filter) { when (filter) {
is CaldavFilter -> { is CaldavFilter -> {
parentQuery = newCaldavQuery(filter) parentQuery = newCaldavQuery(filter.uuid)
subtaskQuery = QueryTemplate() subtaskQuery = SUBTASK_QUERY
.join(Join.inner(RECURSIVE, Task.PARENT.eq(RECURSIVE_TASK)))
.join(Join.inner(CaldavTask.TABLE, Criterion.and(CaldavTask.TASK.eq(Task.ID), CaldavTask.DELETED.eq(0))))
.where(activeAndVisible())
} }
is GtasksFilter -> { is GtasksFilter -> {
parentQuery = newGoogleTaskQuery(filter) parentQuery = newCaldavQuery(filter.list.uuid!!)
subtaskQuery = GOOGLE_SUBTASKS subtaskQuery = SUBTASK_QUERY
} }
else -> { else -> {
parentQuery = PermaSql.replacePlaceholdersForQuery(filter.getSqlQuery()) parentQuery = PermaSql.replacePlaceholdersForQuery(filter.getSqlQuery())
subtaskQuery = when { subtaskQuery = SUBTASK_QUERY
subtasks.hasGoogleSubtasks && subtasks.hasSubtasks -> ALL_SUBTASKS
subtasks.hasGoogleSubtasks -> GOOGLE_SUBTASKS
else -> QueryTemplate()
.join(Join.inner(RECURSIVE, Task.PARENT.eq(RECURSIVE_TASK)))
.where(activeAndVisible())
}
joinedQuery += " LEFT JOIN (SELECT task, max(indent) AS max_indent FROM recursive_tasks GROUP BY task) AS recursive_indents ON recursive_indents.task = tasks._id " joinedQuery += " LEFT JOIN (SELECT task, max(indent) AS max_indent FROM recursive_tasks GROUP BY task) AS recursive_indents ON recursive_indents.task = tasks._id "
where += " AND indent = max_indent " where += " AND indent = max_indent "
} }
@ -85,7 +70,7 @@ internal object TaskListQueryRecursive {
when { when {
manualSort && filter is GtasksFilter -> { manualSort && filter is GtasksFilter -> {
sortMode = SortHelper.SORT_GTASKS sortMode = SortHelper.SORT_GTASKS
sortField = "caldav_tasks.cd_order" sortField = "tasks.`order`"
} }
manualSort && filter is CaldavFilter -> { manualSort && filter is CaldavFilter -> {
sortMode = SortHelper.SORT_CALDAV sortMode = SortHelper.SORT_CALDAV
@ -127,26 +112,14 @@ internal object TaskListQueryRecursive {
.toString()) .toString())
} }
private fun newCaldavQuery(filter: CaldavFilter) = private fun newCaldavQuery(list: String) =
QueryTemplate() QueryTemplate()
.join(Join.inner( .join(Join.inner(
CaldavTask.TABLE, CaldavTask.TABLE,
Criterion.and( Criterion.and(
CaldavTask.CALENDAR.eq(filter.uuid), CaldavTask.CALENDAR.eq(list),
CaldavTask.TASK.eq(Task.ID), CaldavTask.TASK.eq(Task.ID),
CaldavTask.DELETED.eq(0)))) CaldavTask.DELETED.eq(0))))
.where(Criterion.and(activeAndVisible(), Task.PARENT.eq(0))) .where(Criterion.and(activeAndVisible(), Task.PARENT.eq(0)))
.toString() .toString()
private fun newGoogleTaskQuery(filter: GtasksFilter) =
QueryTemplate()
.join(Join.inner(
CaldavTask.TABLE,
Criterion.and(
CaldavTask.CALENDAR.eq(filter.remoteId),
CaldavTask.PARENT.eq(0),
CaldavTask.TASK.eq(Task.ID),
CaldavTask.DELETED.eq(0))))
.where(activeAndVisible())
.toString()
} }

@ -6,7 +6,6 @@ import com.todoroo.andlib.sql.Criterion.Companion.exists
import com.todoroo.andlib.sql.Criterion.Companion.or import com.todoroo.andlib.sql.Criterion.Companion.or
import com.todoroo.andlib.sql.Field.Companion.field import com.todoroo.andlib.sql.Field.Companion.field
import com.todoroo.andlib.sql.Join.Companion.inner import com.todoroo.andlib.sql.Join.Companion.inner
import com.todoroo.andlib.sql.Join.Companion.left
import com.todoroo.andlib.sql.Query.Companion.select import com.todoroo.andlib.sql.Query.Companion.select
import com.todoroo.andlib.sql.UnaryCriterion.Companion.isNotNull import com.todoroo.andlib.sql.UnaryCriterion.Companion.isNotNull
import com.todoroo.astrid.api.* import com.todoroo.astrid.api.*
@ -136,12 +135,8 @@ class FilterCriteriaProvider @Inject constructor(
context.getString(R.string.custom_filter_has_subtask), context.getString(R.string.custom_filter_has_subtask),
select(Task.ID) select(Task.ID)
.from(Task.TABLE) .from(Task.TABLE)
.join(left(Task.TABLE.`as`("children"), Task.ID.eq(field("children.parent")))) .join(inner(Task.TABLE.`as`("children"), Task.ID.eq(field("children.parent"))))
.join(left(CaldavTask.TABLE, CaldavTask.PARENT.eq(Task.ID))) .where(isNotNull(field("children._id")))
.where(or(
isNotNull(field("children._id")),
isNotNull(CaldavTask.ID)
))
.toString() .toString()
) )
@ -151,11 +146,8 @@ class FilterCriteriaProvider @Inject constructor(
context.getString(R.string.custom_filter_is_subtask), context.getString(R.string.custom_filter_is_subtask),
select(Task.ID) select(Task.ID)
.from(Task.TABLE) .from(Task.TABLE)
.join(left(CaldavTask.TABLE, CaldavTask.TASK.eq(Task.ID))) .join(inner(CaldavTask.TABLE, CaldavTask.TASK.eq(Task.ID)))
.where(or( .where(field("${Task.PARENT}>0").eq(1))
field("${Task.PARENT}>0").eq(1),
field("${CaldavTask.PARENT}>0").eq(1)
))
.toString() .toString()
) )

@ -137,7 +137,7 @@ class GoogleTaskSynchronizer @Inject constructor(
} }
fetchAndApplyRemoteChanges(gtasksInvoker, list) fetchAndApplyRemoteChanges(gtasksInvoker, list)
if (!preferences.isPositionHackEnabled) { if (!preferences.isPositionHackEnabled) {
googleTaskDao.reposition(list.uuid!!) googleTaskDao.reposition(caldavDao, list.uuid!!)
} }
} }
if (preferences.isPositionHackEnabled) { if (preferences.isPositionHackEnabled) {
@ -146,7 +146,7 @@ class GoogleTaskSynchronizer @Inject constructor(
for (task in tasks) { for (task in tasks) {
googleTaskDao.updatePosition(task.id, task.parent, task.position) googleTaskDao.updatePosition(task.id, task.parent, task.position)
} }
googleTaskDao.reposition(list.id) googleTaskDao.reposition(caldavDao, list.id)
} }
} }
// account.etag = eTag // account.etag = eTag
@ -224,12 +224,11 @@ class GoogleTaskSynchronizer @Inject constructor(
remoteModel.status = "needsAction" // $NON-NLS-1$ remoteModel.status = "needsAction" // $NON-NLS-1$
} }
if (newlyCreated) { if (newlyCreated) {
val parent = gtasksMetadata.parent val parent = task.parent
val localParent = if (parent > 0) googleTaskDao.getRemoteId(parent) else null val localParent = if (parent > 0) googleTaskDao.getRemoteId(parent) else null
val previous = googleTaskDao.getPrevious( val previous = googleTaskDao.getPrevious(
listId!!, if (isNullOrEmpty(localParent)) 0 else parent, gtasksMetadata.order ?: 0) listId!!, if (isNullOrEmpty(localParent)) 0 else parent, task.order ?: 0)
val created: Task? val created: Task? = try {
created = try {
gtasksInvoker.createGtask(listId, remoteModel, localParent, previous) gtasksInvoker.createGtask(listId, remoteModel, localParent, previous)
} catch (e: HttpNotFoundException) { } catch (e: HttpNotFoundException) {
gtasksInvoker.createGtask(listId, remoteModel, null, null) gtasksInvoker.createGtask(listId, remoteModel, null, null)
@ -238,7 +237,7 @@ class GoogleTaskSynchronizer @Inject constructor(
// Update the metadata for the newly created task // Update the metadata for the newly created task
gtasksMetadata.remoteId = created.id gtasksMetadata.remoteId = created.id
gtasksMetadata.calendar = listId gtasksMetadata.calendar = listId
setOrderAndParent(gtasksMetadata, created) setOrderAndParent(gtasksMetadata, created, task)
} else { } else {
return return
} }
@ -246,15 +245,15 @@ class GoogleTaskSynchronizer @Inject constructor(
try { try {
if (!task.isDeleted && gtasksMetadata.isMoved) { if (!task.isDeleted && gtasksMetadata.isMoved) {
try { try {
val parent = gtasksMetadata.parent val parent = task.parent
val localParent = if (parent > 0) googleTaskDao.getRemoteId(parent) else null val localParent = if (parent > 0) googleTaskDao.getRemoteId(parent) else null
val previous = googleTaskDao.getPrevious( val previous = googleTaskDao.getPrevious(
listId!!, listId!!,
if (isNullOrEmpty(localParent)) 0 else parent, if (localParent.isNullOrBlank()) 0 else parent,
gtasksMetadata.order ?: 0) task.order ?: 0)
gtasksInvoker gtasksInvoker
.moveGtask(listId, remoteModel.id, localParent, previous) .moveGtask(listId, remoteModel.id, localParent, previous)
?.let { setOrderAndParent(gtasksMetadata, it) } ?.let { setOrderAndParent(gtasksMetadata, it, task) }
} catch (e: GoogleJsonResponseException) { } catch (e: GoogleJsonResponseException) {
if (e.statusCode == 400) { if (e.statusCode == 400) {
Timber.e(e) Timber.e(e)
@ -328,12 +327,12 @@ class GoogleTaskSynchronizer @Inject constructor(
continue continue
} }
} else { } else {
setOrderAndParent(googleTask, gtask) if (task == null) {
task = taskCreator.createWithValues("")
}
setOrderAndParent(googleTask, gtask, task)
googleTask.remoteId = gtask.id googleTask.remoteId = gtask.id
} }
if (task == null) {
task = taskCreator.createWithValues("")
}
task.title = getTruncatedValue(task.title, gtask.title, MAX_TITLE_LENGTH) task.title = getTruncatedValue(task.title, gtask.title, MAX_TITLE_LENGTH)
task.completionDate = GtasksApiUtilities.gtasksCompletedTimeToUnixTime(gtask.completed?.let(::DateTime)) task.completionDate = GtasksApiUtilities.gtasksCompletedTimeToUnixTime(gtask.completed?.let(::DateTime))
val dueDate = GtasksApiUtilities.gtasksDueTimeToUnixTime(gtask.due?.let(::DateTime)) val dueDate = GtasksApiUtilities.gtasksDueTimeToUnixTime(gtask.due?.let(::DateTime))
@ -351,10 +350,10 @@ class GoogleTaskSynchronizer @Inject constructor(
) )
} }
private suspend fun setOrderAndParent(googleTask: CaldavTask, task: Task) { private suspend fun setOrderAndParent(googleTask: CaldavTask, task: Task, local: com.todoroo.astrid.data.Task) {
task.position?.toLongOrNull()?.let { googleTask.remoteOrder = it } task.position?.toLongOrNull()?.let { googleTask.remoteOrder = it }
googleTask.remoteParent = task.parent?.takeIf { it.isNotBlank() } googleTask.remoteParent = task.parent?.takeIf { it.isNotBlank() }
googleTask.parent = googleTask.remoteParent?.let { googleTaskDao.getTask(it) } ?: 0L local.parent = googleTask.remoteParent?.let { googleTaskDao.getTask(it) } ?: 0L
} }
private suspend fun write(task: com.todoroo.astrid.data.Task, googleTask: CaldavTask) { private suspend fun write(task: com.todoroo.astrid.data.Task, googleTask: CaldavTask) {

@ -115,7 +115,7 @@ class DefaultFilterProvider @Inject constructor(
TYPE_FILTER -> getFilterPreference(filterType, getBuiltInFilterId(filter)) TYPE_FILTER -> getFilterPreference(filterType, getBuiltInFilterId(filter))
TYPE_CUSTOM_FILTER -> getFilterPreference(filterType, (filter as CustomFilter).id) TYPE_CUSTOM_FILTER -> getFilterPreference(filterType, (filter as CustomFilter).id)
TYPE_TAG -> getFilterPreference(filterType, (filter as TagFilter).uuid) TYPE_TAG -> getFilterPreference(filterType, (filter as TagFilter).uuid)
TYPE_GOOGLE_TASKS -> getFilterPreference(filterType, (filter as GtasksFilter).storeId) TYPE_GOOGLE_TASKS -> getFilterPreference(filterType, (filter as GtasksFilter).listId)
TYPE_CALDAV -> getFilterPreference(filterType, (filter as CaldavFilter).uuid) TYPE_CALDAV -> getFilterPreference(filterType, (filter as CaldavFilter).uuid)
TYPE_LOCATION -> getFilterPreference(filterType, (filter as PlaceFilter).uid) TYPE_LOCATION -> getFilterPreference(filterType, (filter as PlaceFilter).uid)
else -> null else -> null

@ -10,7 +10,6 @@ import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.google.android.material.composethemeadapter.MdcTheme import com.google.android.material.composethemeadapter.MdcTheme
import com.todoroo.andlib.sql.Criterion import com.todoroo.andlib.sql.Criterion
import com.todoroo.andlib.sql.Join
import com.todoroo.andlib.sql.QueryTemplate import com.todoroo.andlib.sql.QueryTemplate
import com.todoroo.andlib.utility.DateUtilities.now import com.todoroo.andlib.utility.DateUtilities.now
import com.todoroo.astrid.api.Filter import com.todoroo.astrid.api.Filter
@ -23,7 +22,6 @@ import kotlinx.coroutines.launch
import org.tasks.R import org.tasks.R
import org.tasks.compose.collectAsStateLifecycleAware import org.tasks.compose.collectAsStateLifecycleAware
import org.tasks.compose.edit.SubtaskRow import org.tasks.compose.edit.SubtaskRow
import org.tasks.data.CaldavTask
import org.tasks.data.GoogleTaskDao import org.tasks.data.GoogleTaskDao
import org.tasks.data.TaskDao.TaskCriteria.activeAndVisible import org.tasks.data.TaskDao.TaskCriteria.activeAndVisible
import org.tasks.preferences.Preferences import org.tasks.preferences.Preferences
@ -59,6 +57,7 @@ class SubtaskControlSet : TaskEditControlFragment() {
SubtaskRow( SubtaskRow(
filter = viewModel.selectedList.collectAsStateLifecycleAware().value, filter = viewModel.selectedList.collectAsStateLifecycleAware().value,
googleTask = googleTaskDao.watchGoogleTask(viewModel.task.id).collectAsStateLifecycleAware(initial = null).value, googleTask = googleTaskDao.watchGoogleTask(viewModel.task.id).collectAsStateLifecycleAware(initial = null).value,
hasParent = viewModel.hasParent,
desaturate = preferences.desaturateDarkMode, desaturate = preferences.desaturateDarkMode,
existingSubtasks = listViewModel.tasks.observeAsState(initial = emptyList()).value, existingSubtasks = listViewModel.tasks.observeAsState(initial = emptyList()).value,
newSubtasks = viewModel.newSubtasks.collectAsStateLifecycleAware().value, newSubtasks = viewModel.newSubtasks.collectAsStateLifecycleAware().value,
@ -109,20 +108,10 @@ class SubtaskControlSet : TaskEditControlFragment() {
companion object { companion object {
const val TAG = R.string.TEA_ctrl_subtask_pref const val TAG = R.string.TEA_ctrl_subtask_pref
private fun getQueryTemplate(task: Task): QueryTemplate = QueryTemplate() private fun getQueryTemplate(task: Task): QueryTemplate = QueryTemplate()
.join(
Join.left(
CaldavTask.TABLE,
Criterion.and(
CaldavTask.PARENT.eq(task.id),
CaldavTask.TASK.eq(Task.ID),
CaldavTask.DELETED.eq(0)
)
)
)
.where( .where(
Criterion.and( Criterion.and(
activeAndVisible(), activeAndVisible(),
Criterion.or(Task.PARENT.eq(task.id), CaldavTask.TASK.gt(0)) Task.PARENT.eq(task.id)
) )
) )
} }

@ -99,6 +99,8 @@ class TaskEditViewModel @Inject constructor(
val estimatedSeconds = MutableStateFlow(task.estimatedSeconds) val estimatedSeconds = MutableStateFlow(task.estimatedSeconds)
val elapsedSeconds = MutableStateFlow(task.elapsedSeconds) val elapsedSeconds = MutableStateFlow(task.elapsedSeconds)
var newSubtasks = MutableStateFlow(emptyList<Task>()) var newSubtasks = MutableStateFlow(emptyList<Task>())
val hasParent: Boolean
get() = task.parent > 0
val dueDate = MutableStateFlow(task.dueDate) val dueDate = MutableStateFlow(task.dueDate)
@ -293,9 +295,9 @@ class TaskEditViewModel @Inject constructor(
when (selectedList.value) { when (selectedList.value) {
is GtasksFilter -> { is GtasksFilter -> {
val googleTask = CaldavTask(subtask.id, (selectedList.value as GtasksFilter).remoteId) val googleTask = CaldavTask(subtask.id, (selectedList.value as GtasksFilter).remoteId)
googleTask.parent = task.id subtask.parent = task.id
googleTask.isMoved = true googleTask.isMoved = true
googleTaskDao.insertAndShift(googleTask, false) googleTaskDao.insertAndShift(subtask, googleTask, false)
} }
is CaldavFilter -> { is CaldavFilter -> {
val caldavTask = CaldavTask(subtask.id, (selectedList.value as CaldavFilter).uuid) val caldavTask = CaldavTask(subtask.id, (selectedList.value as CaldavFilter).uuid)

Loading…
Cancel
Save