mirror of https://github.com/tasks/tasks
Create blocking room daos
parent
ad92b68b32
commit
f33ecdda7d
@ -0,0 +1,280 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2012 Todoroo Inc
|
||||||
|
*
|
||||||
|
* See the file "LICENSE" for the full license governing this code.
|
||||||
|
*/
|
||||||
|
package com.todoroo.astrid.dao
|
||||||
|
|
||||||
|
import androidx.paging.DataSource
|
||||||
|
import androidx.room.*
|
||||||
|
import androidx.sqlite.db.SimpleSQLiteQuery
|
||||||
|
import com.todoroo.andlib.sql.Field
|
||||||
|
import com.todoroo.andlib.utility.AndroidUtilities.assertNotMainThread
|
||||||
|
import com.todoroo.andlib.utility.DateUtilities
|
||||||
|
import com.todoroo.astrid.api.Filter
|
||||||
|
import com.todoroo.astrid.dao.TaskDao.Companion.getQuery
|
||||||
|
import com.todoroo.astrid.data.Task
|
||||||
|
import com.todoroo.astrid.data.Task.Companion.NO_ID
|
||||||
|
import com.todoroo.astrid.helper.UUIDHelper
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import org.tasks.BuildConfig
|
||||||
|
import org.tasks.data.Place
|
||||||
|
import org.tasks.data.SubtaskInfo
|
||||||
|
import org.tasks.data.TaskContainer
|
||||||
|
import org.tasks.data.TaskListQuery
|
||||||
|
import org.tasks.db.DbUtils.chunkedMap
|
||||||
|
import org.tasks.db.DbUtils.eachChunk
|
||||||
|
import org.tasks.jobs.WorkManager
|
||||||
|
import org.tasks.preferences.Preferences
|
||||||
|
import org.tasks.time.DateTimeUtils.currentTimeMillis
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
abstract class TaskDaoBlocking(private val database: Database) {
|
||||||
|
private lateinit var workManager: WorkManager
|
||||||
|
|
||||||
|
fun initialize(workManager: WorkManager) {
|
||||||
|
this.workManager = workManager
|
||||||
|
}
|
||||||
|
|
||||||
|
fun needsRefresh(): List<Task> {
|
||||||
|
return needsRefresh(DateUtilities.now())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Query("SELECT * FROM tasks WHERE completed = 0 AND deleted = 0 AND (hideUntil > :now OR dueDate > :now)")
|
||||||
|
abstract fun needsRefresh(now: Long): List<Task>
|
||||||
|
|
||||||
|
fun fetchBlocking(id: Long) = runBlocking {
|
||||||
|
fetch(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Query("SELECT * FROM tasks WHERE _id = :id LIMIT 1")
|
||||||
|
abstract suspend fun fetch(id: Long): Task?
|
||||||
|
|
||||||
|
fun fetch(ids: List<Long>): List<Task> = ids.chunkedMap(this::fetchInternal)
|
||||||
|
|
||||||
|
@Query("SELECT * FROM tasks WHERE _id IN (:ids)")
|
||||||
|
internal abstract fun fetchInternal(ids: List<Long>): List<Task>
|
||||||
|
|
||||||
|
@Query("SELECT COUNT(1) FROM tasks WHERE timerStart > 0 AND deleted = 0")
|
||||||
|
abstract fun activeTimers(): Int
|
||||||
|
|
||||||
|
@Query("SELECT tasks.* FROM tasks INNER JOIN notification ON tasks._id = notification.task")
|
||||||
|
abstract fun activeNotifications(): List<Task>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM tasks WHERE remoteId = :remoteId")
|
||||||
|
abstract fun fetch(remoteId: String): Task?
|
||||||
|
|
||||||
|
@Query("SELECT * FROM tasks WHERE completed = 0 AND deleted = 0")
|
||||||
|
abstract fun getActiveTasks(): List<Task>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM tasks WHERE hideUntil < (strftime('%s','now')*1000)")
|
||||||
|
abstract fun getVisibleTasks(): List<Task>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM tasks WHERE remoteId IN (:remoteIds) "
|
||||||
|
+ "AND recurrence IS NOT NULL AND LENGTH(recurrence) > 0")
|
||||||
|
abstract fun getRecurringTasks(remoteIds: List<String>): List<Task>
|
||||||
|
|
||||||
|
@Query("UPDATE tasks SET completed = :completionDate " + "WHERE remoteId = :remoteId")
|
||||||
|
abstract fun setCompletionDate(remoteId: String, completionDate: Long)
|
||||||
|
|
||||||
|
@Query("UPDATE tasks SET snoozeTime = :millis WHERE _id in (:taskIds)")
|
||||||
|
abstract fun snooze(taskIds: List<Long>, millis: Long)
|
||||||
|
|
||||||
|
@Query("SELECT tasks.* FROM tasks "
|
||||||
|
+ "LEFT JOIN google_tasks ON tasks._id = google_tasks.gt_task "
|
||||||
|
+ "WHERE gt_list_id IN (SELECT gtl_remote_id FROM google_task_lists WHERE gtl_account = :account)"
|
||||||
|
+ "AND (tasks.modified > google_tasks.gt_last_sync OR google_tasks.gt_remote_id = '' OR google_tasks.gt_deleted > 0) "
|
||||||
|
+ "ORDER BY CASE WHEN gt_parent = 0 THEN 0 ELSE 1 END, gt_order ASC")
|
||||||
|
abstract fun getGoogleTasksToPush(account: String): List<Task>
|
||||||
|
|
||||||
|
@Query("""
|
||||||
|
SELECT tasks.*
|
||||||
|
FROM tasks
|
||||||
|
INNER JOIN caldav_tasks ON tasks._id = caldav_tasks.cd_task
|
||||||
|
WHERE caldav_tasks.cd_calendar = :calendar
|
||||||
|
AND (tasks.modified > caldav_tasks.cd_last_sync OR caldav_tasks.cd_last_sync = 0)""")
|
||||||
|
abstract fun getCaldavTasksToPush(calendar: String): List<Task>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM TASKS "
|
||||||
|
+ "WHERE completed = 0 AND deleted = 0 AND (notificationFlags > 0 OR notifications > 0)")
|
||||||
|
abstract fun getTasksWithReminders(): List<Task>
|
||||||
|
|
||||||
|
// --- SQL clause generators
|
||||||
|
@Query("SELECT * FROM tasks")
|
||||||
|
abstract fun getAll(): List<Task>
|
||||||
|
|
||||||
|
@Query("SELECT calendarUri FROM tasks " + "WHERE calendarUri IS NOT NULL AND calendarUri != ''")
|
||||||
|
abstract fun getAllCalendarEvents(): List<String>
|
||||||
|
|
||||||
|
@Query("UPDATE tasks SET calendarUri = '' " + "WHERE calendarUri IS NOT NULL AND calendarUri != ''")
|
||||||
|
abstract fun clearAllCalendarEvents(): Int
|
||||||
|
|
||||||
|
@Query("SELECT calendarUri FROM tasks "
|
||||||
|
+ "WHERE completed > 0 AND calendarUri IS NOT NULL AND calendarUri != ''")
|
||||||
|
abstract fun getCompletedCalendarEvents(): List<String>
|
||||||
|
|
||||||
|
@Query("UPDATE tasks SET calendarUri = '' "
|
||||||
|
+ "WHERE completed > 0 AND calendarUri IS NOT NULL AND calendarUri != ''")
|
||||||
|
abstract fun clearCompletedCalendarEvents(): Int
|
||||||
|
|
||||||
|
@Transaction
|
||||||
|
open fun fetchTasks(callback: (SubtaskInfo) -> List<String>): List<TaskContainer> {
|
||||||
|
return runBlocking {
|
||||||
|
fetchTasks(callback, getSubtaskInfo())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transaction
|
||||||
|
open fun fetchTasks(callback: (SubtaskInfo) -> List<String>, subtasks: SubtaskInfo): List<TaskContainer> {
|
||||||
|
assertNotMainThread()
|
||||||
|
|
||||||
|
val start = if (BuildConfig.DEBUG) DateUtilities.now() else 0
|
||||||
|
val queries = callback.invoke(subtasks)
|
||||||
|
val db = database.openHelper.writableDatabase
|
||||||
|
val last = queries.size - 1
|
||||||
|
for (i in 0 until last) {
|
||||||
|
db.execSQL(queries[i])
|
||||||
|
}
|
||||||
|
val result = fetchTasks(SimpleSQLiteQuery(queries[last]))
|
||||||
|
Timber.v("%sms: %s", DateUtilities.now() - start, queries.joinToString(";\n"))
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
fun fetchTasks(preferences: Preferences, filter: Filter): List<TaskContainer> {
|
||||||
|
return fetchTasks {
|
||||||
|
TaskListQuery.getQuery(preferences, filter, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@RawQuery
|
||||||
|
abstract fun fetchTasks(query: SimpleSQLiteQuery): List<TaskContainer>
|
||||||
|
|
||||||
|
@RawQuery
|
||||||
|
abstract fun count(query: SimpleSQLiteQuery): Int
|
||||||
|
|
||||||
|
@Query("""
|
||||||
|
SELECT EXISTS(SELECT 1 FROM tasks WHERE parent > 0 AND deleted = 0) AS hasSubtasks,
|
||||||
|
EXISTS(SELECT 1
|
||||||
|
FROM google_tasks
|
||||||
|
INNER JOIN tasks ON gt_task = _id
|
||||||
|
WHERE deleted = 0
|
||||||
|
AND gt_parent > 0
|
||||||
|
AND gt_deleted = 0) AS hasGoogleSubtasks
|
||||||
|
""")
|
||||||
|
abstract suspend fun getSubtaskInfo(): SubtaskInfo
|
||||||
|
|
||||||
|
@RawQuery(observedEntities = [Place::class])
|
||||||
|
abstract fun getTaskFactory(
|
||||||
|
query: SimpleSQLiteQuery): DataSource.Factory<Int, TaskContainer>
|
||||||
|
|
||||||
|
fun touch(id: Long) = touch(listOf(id))
|
||||||
|
|
||||||
|
fun touch(ids: List<Long>) {
|
||||||
|
ids.eachChunk { touchInternal(it) }
|
||||||
|
workManager.sync(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Query("UPDATE tasks SET modified = :now WHERE _id in (:ids)")
|
||||||
|
abstract fun touchInternal(ids: List<Long>, now: Long = currentTimeMillis())
|
||||||
|
|
||||||
|
fun setParent(parent: Long, tasks: List<Long>) =
|
||||||
|
tasks.eachChunk { setParentInternal(parent, it) }
|
||||||
|
|
||||||
|
@Query("UPDATE tasks SET parent = :parent WHERE _id IN (:children)")
|
||||||
|
internal abstract fun setParentInternal(parent: Long, children: List<Long>)
|
||||||
|
|
||||||
|
@Transaction
|
||||||
|
open fun fetchChildren(id: Long): List<Task> {
|
||||||
|
return fetch(getChildren(id))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getChildren(id: Long): List<Long> {
|
||||||
|
return getChildren(listOf(id))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Query("WITH RECURSIVE "
|
||||||
|
+ " recursive_tasks (task) AS ( "
|
||||||
|
+ " SELECT _id "
|
||||||
|
+ " FROM tasks "
|
||||||
|
+ "WHERE parent IN (:ids)"
|
||||||
|
+ "UNION ALL "
|
||||||
|
+ " SELECT _id "
|
||||||
|
+ " FROM tasks "
|
||||||
|
+ " INNER JOIN recursive_tasks "
|
||||||
|
+ " ON recursive_tasks.task = tasks.parent"
|
||||||
|
+ " WHERE tasks.deleted = 0)"
|
||||||
|
+ "SELECT task FROM recursive_tasks")
|
||||||
|
abstract fun getChildren(ids: List<Long>): List<Long>
|
||||||
|
|
||||||
|
@Query("UPDATE tasks SET collapsed = :collapsed WHERE _id = :id")
|
||||||
|
abstract fun setCollapsed(id: Long, collapsed: Boolean)
|
||||||
|
|
||||||
|
@Transaction
|
||||||
|
open fun setCollapsed(preferences: Preferences, filter: Filter, collapsed: Boolean) {
|
||||||
|
fetchTasks(preferences, filter)
|
||||||
|
.filter(TaskContainer::hasChildren)
|
||||||
|
.map(TaskContainer::getId)
|
||||||
|
.eachChunk { collapse(it, collapsed) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Query("UPDATE tasks SET collapsed = :collapsed WHERE _id IN (:ids)")
|
||||||
|
abstract fun collapse(ids: List<Long>, collapsed: Boolean)
|
||||||
|
|
||||||
|
// --- save
|
||||||
|
// TODO: get rid of this super-hack
|
||||||
|
/**
|
||||||
|
* Saves the given task to the database.getDatabase(). Task must already exist. Returns true on
|
||||||
|
* success.
|
||||||
|
*/
|
||||||
|
@JvmOverloads
|
||||||
|
fun save(task: Task, original: Task? = fetchBlocking(task.id)) {
|
||||||
|
if (!task.insignificantChange(original)) {
|
||||||
|
task.modificationDate = DateUtilities.now()
|
||||||
|
}
|
||||||
|
if (update(task) == 1) {
|
||||||
|
workManager.afterSave(task, original)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Insert
|
||||||
|
abstract fun insert(task: Task): Long
|
||||||
|
|
||||||
|
@Update
|
||||||
|
abstract fun update(task: Task): Int
|
||||||
|
|
||||||
|
fun createNew(task: Task) {
|
||||||
|
task.id = NO_ID
|
||||||
|
if (task.creationDate == 0L) {
|
||||||
|
task.creationDate = DateUtilities.now()
|
||||||
|
}
|
||||||
|
if (Task.isUuidEmpty(task.remoteId)) {
|
||||||
|
task.remoteId = UUIDHelper.newUUID()
|
||||||
|
}
|
||||||
|
val insert = insert(task)
|
||||||
|
task.id = insert
|
||||||
|
}
|
||||||
|
|
||||||
|
fun count(filter: Filter): Int {
|
||||||
|
val query = getQuery(filter.sqlQuery, Field.COUNT)
|
||||||
|
val start = if (BuildConfig.DEBUG) DateUtilities.now() else 0
|
||||||
|
val count = count(query)
|
||||||
|
Timber.v("%sms: %s", DateUtilities.now() - start, query.sql)
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
||||||
|
fun fetchFiltered(filter: Filter): List<Task> {
|
||||||
|
return fetchFiltered(filter.getSqlQuery())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun fetchFiltered(queryTemplate: String): List<Task> {
|
||||||
|
val query = getQuery(queryTemplate, Task.FIELDS)
|
||||||
|
val start = if (BuildConfig.DEBUG) DateUtilities.now() else 0
|
||||||
|
val tasks = fetchTasks(query)
|
||||||
|
Timber.v("%sms: %s", DateUtilities.now() - start, query.sql)
|
||||||
|
return tasks.map(TaskContainer::getTask)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Query("SELECT _id FROM tasks LEFT JOIN google_tasks ON _id = gt_task AND gt_deleted = 0 LEFT JOIN caldav_tasks ON _id = cd_task AND cd_deleted = 0 WHERE gt_id IS NULL AND cd_id IS NULL AND parent = 0")
|
||||||
|
abstract fun getLocalTasks(): List<Long>
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
package org.tasks.data
|
||||||
|
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Delete
|
||||||
|
import androidx.room.Insert
|
||||||
|
import androidx.room.Query
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface AlarmDaoBlocking {
|
||||||
|
@Query("SELECT alarms.* FROM alarms INNER JOIN tasks ON tasks._id = alarms.task "
|
||||||
|
+ "WHERE tasks.completed = 0 AND tasks.deleted = 0 AND tasks.lastNotified < alarms.time "
|
||||||
|
+ "ORDER BY time ASC")
|
||||||
|
fun getActiveAlarms(): List<Alarm>
|
||||||
|
|
||||||
|
@Query("SELECT alarms.* FROM alarms INNER JOIN tasks ON tasks._id = alarms.task "
|
||||||
|
+ "WHERE tasks._id = :taskId AND tasks.completed = 0 AND tasks.deleted = 0 AND tasks.lastNotified < alarms.time "
|
||||||
|
+ "ORDER BY time ASC")
|
||||||
|
fun getActiveAlarms(taskId: Long): List<Alarm>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM alarms WHERE task = :taskId ORDER BY time ASC")
|
||||||
|
fun getAlarms(taskId: Long): List<Alarm>
|
||||||
|
|
||||||
|
@Delete
|
||||||
|
fun delete(alarm: Alarm)
|
||||||
|
|
||||||
|
@Insert
|
||||||
|
fun insert(alarm: Alarm): Long
|
||||||
|
|
||||||
|
@Insert
|
||||||
|
fun insert(alarms: Iterable<Alarm>)
|
||||||
|
}
|
@ -0,0 +1,275 @@
|
|||||||
|
package org.tasks.data
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.room.*
|
||||||
|
import com.todoroo.andlib.utility.DateUtilities.now
|
||||||
|
import com.todoroo.astrid.api.FilterListItem.NO_ORDER
|
||||||
|
import com.todoroo.astrid.core.SortHelper.APPLE_EPOCH
|
||||||
|
import com.todoroo.astrid.data.Task
|
||||||
|
import com.todoroo.astrid.helper.UUIDHelper
|
||||||
|
import org.tasks.R
|
||||||
|
import org.tasks.data.CaldavDao.Companion.LOCAL
|
||||||
|
import org.tasks.date.DateTimeUtils.toAppleEpoch
|
||||||
|
import org.tasks.db.DbUtils.chunkedMap
|
||||||
|
import org.tasks.filters.CaldavFilters
|
||||||
|
import org.tasks.time.DateTimeUtils.currentTimeMillis
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
abstract class CaldavDaoBlocking {
|
||||||
|
@Query("SELECT * FROM caldav_lists")
|
||||||
|
abstract fun subscribeToCalendars(): LiveData<List<CaldavCalendar>>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM caldav_lists WHERE cdl_uuid = :uuid LIMIT 1")
|
||||||
|
abstract fun getCalendarByUuid(uuid: String): CaldavCalendar?
|
||||||
|
|
||||||
|
@Query("SELECT * FROM caldav_lists WHERE cdl_account = :uuid")
|
||||||
|
abstract fun getCalendarsByAccount(uuid: String): List<CaldavCalendar>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM caldav_accounts WHERE cda_uuid = :uuid LIMIT 1")
|
||||||
|
abstract fun getAccountByUuid(uuid: String): CaldavAccount?
|
||||||
|
|
||||||
|
@Query("SELECT COUNT(*) FROM caldav_accounts WHERE cda_account_type != 2")
|
||||||
|
abstract fun accountCount(): Int
|
||||||
|
|
||||||
|
@Query("SELECT * FROM caldav_accounts ORDER BY cda_account_type, UPPER(cda_name)")
|
||||||
|
abstract fun getAccounts(): List<CaldavAccount>
|
||||||
|
|
||||||
|
@Query("UPDATE caldav_accounts SET cda_collapsed = :collapsed WHERE cda_id = :id")
|
||||||
|
abstract fun setCollapsed(id: Long, collapsed: Boolean)
|
||||||
|
|
||||||
|
@Insert
|
||||||
|
abstract fun insert(caldavAccount: CaldavAccount): Long
|
||||||
|
|
||||||
|
@Update
|
||||||
|
abstract fun update(caldavAccount: CaldavAccount)
|
||||||
|
|
||||||
|
fun insert(caldavCalendar: CaldavCalendar) {
|
||||||
|
caldavCalendar.id = insertInternal(caldavCalendar)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Insert
|
||||||
|
abstract fun insertInternal(caldavCalendar: CaldavCalendar): Long
|
||||||
|
|
||||||
|
@Update
|
||||||
|
abstract fun update(caldavCalendar: CaldavCalendar)
|
||||||
|
|
||||||
|
@Transaction
|
||||||
|
open fun insert(task: Task, caldavTask: CaldavTask, addToTop: Boolean): Long {
|
||||||
|
if (caldavTask.order != null) {
|
||||||
|
return insert(caldavTask)
|
||||||
|
}
|
||||||
|
if (addToTop) {
|
||||||
|
caldavTask.order = findFirstTask(caldavTask.calendar!!, task.parent)
|
||||||
|
?.takeIf { task.creationDate.toAppleEpoch() >= it }
|
||||||
|
?.minus(1)
|
||||||
|
} else {
|
||||||
|
caldavTask.order = findLastTask(caldavTask.calendar!!, task.parent)
|
||||||
|
?.takeIf { task.creationDate.toAppleEpoch() <= it }
|
||||||
|
?.plus(1)
|
||||||
|
}
|
||||||
|
return insert(caldavTask)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Query("SELECT MIN(IFNULL(cd_order, (created - $APPLE_EPOCH) / 1000)) FROM caldav_tasks INNER JOIN tasks ON _id = cd_task WHERE cd_calendar = :calendar AND cd_deleted = 0 AND deleted = 0 AND parent = :parent")
|
||||||
|
internal abstract fun findFirstTask(calendar: String, parent: Long): Long?
|
||||||
|
|
||||||
|
@Query("SELECT MAX(IFNULL(cd_order, (created - $APPLE_EPOCH) / 1000)) FROM caldav_tasks INNER JOIN tasks ON _id = cd_task WHERE cd_calendar = :calendar AND cd_deleted = 0 AND deleted = 0 AND parent = :parent")
|
||||||
|
internal abstract fun findLastTask(calendar: String, parent: Long): Long?
|
||||||
|
|
||||||
|
@Insert
|
||||||
|
abstract fun insert(caldavTask: CaldavTask): Long
|
||||||
|
|
||||||
|
@Insert
|
||||||
|
abstract fun insert(tasks: Iterable<CaldavTask>)
|
||||||
|
|
||||||
|
@Update
|
||||||
|
abstract fun update(caldavTask: CaldavTask)
|
||||||
|
|
||||||
|
fun update(caldavTask: SubsetCaldav) {
|
||||||
|
update(caldavTask.cd_id, caldavTask.cd_order, caldavTask.cd_remote_parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Query("UPDATE caldav_tasks SET cd_order = :position, cd_remote_parent = :parent WHERE cd_id = :id")
|
||||||
|
internal abstract fun update(id: Long, position: Long?, parent: String?)
|
||||||
|
|
||||||
|
@Query("UPDATE caldav_tasks SET cd_order = :position WHERE cd_id = :id")
|
||||||
|
internal abstract fun update(id: Long, position: Long?)
|
||||||
|
|
||||||
|
@Query("UPDATE caldav_tasks SET cd_remote_parent = :remoteParent WHERE cd_id = :id")
|
||||||
|
internal abstract fun update(id: Long, remoteParent: String?)
|
||||||
|
|
||||||
|
@Update
|
||||||
|
abstract fun update(tasks: Iterable<CaldavTask>)
|
||||||
|
|
||||||
|
@Delete
|
||||||
|
abstract fun delete(caldavTask: CaldavTask)
|
||||||
|
|
||||||
|
@Query("SELECT * FROM caldav_tasks WHERE cd_deleted > 0 AND cd_calendar = :calendar")
|
||||||
|
abstract fun getDeleted(calendar: String): List<CaldavTask>
|
||||||
|
|
||||||
|
@Query("UPDATE caldav_tasks SET cd_deleted = :now WHERE cd_task IN (:tasks)")
|
||||||
|
abstract fun markDeleted(tasks: List<Long>, now: Long = currentTimeMillis())
|
||||||
|
|
||||||
|
@Query("SELECT * FROM caldav_tasks WHERE cd_task = :taskId AND cd_deleted = 0 LIMIT 1")
|
||||||
|
abstract fun getTask(taskId: Long): CaldavTask?
|
||||||
|
|
||||||
|
@Query("SELECT cd_remote_id FROM caldav_tasks WHERE cd_task = :taskId AND cd_deleted = 0")
|
||||||
|
abstract fun getRemoteIdForTask(taskId: Long): String?
|
||||||
|
|
||||||
|
@Query("SELECT * FROM caldav_tasks WHERE cd_calendar = :calendar AND cd_object = :obj LIMIT 1")
|
||||||
|
abstract fun getTask(calendar: String, obj: String): CaldavTask?
|
||||||
|
|
||||||
|
@Query("SELECT * FROM caldav_tasks WHERE cd_calendar = :calendar AND cd_remote_id = :remoteId")
|
||||||
|
abstract fun getTaskByRemoteId(calendar: String, remoteId: String): CaldavTask?
|
||||||
|
|
||||||
|
@Query("SELECT * FROM caldav_tasks WHERE cd_task = :taskId")
|
||||||
|
abstract fun getTasks(taskId: Long): List<CaldavTask>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM caldav_tasks WHERE cd_task in (:taskIds) AND cd_deleted = 0")
|
||||||
|
abstract fun getTasks(taskIds: List<Long>): List<CaldavTask>
|
||||||
|
|
||||||
|
@Query("SELECT task.*, caldav_task.* FROM tasks AS task "
|
||||||
|
+ "INNER JOIN caldav_tasks AS caldav_task ON _id = cd_task "
|
||||||
|
+ "WHERE cd_deleted = 0 AND cd_vtodo IS NOT NULL AND cd_vtodo != ''")
|
||||||
|
abstract fun getTasks(): List<CaldavTaskContainer>
|
||||||
|
|
||||||
|
@Query("SELECT task.*, caldav_task.* FROM tasks AS task "
|
||||||
|
+ "INNER JOIN caldav_tasks AS caldav_task ON _id = cd_task "
|
||||||
|
+ "WHERE cd_calendar = :calendar "
|
||||||
|
+ "AND modified > cd_last_sync "
|
||||||
|
+ "AND cd_deleted = 0")
|
||||||
|
abstract fun getCaldavTasksToPush(calendar: String): List<CaldavTaskContainer>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM caldav_lists ORDER BY cdl_name COLLATE NOCASE")
|
||||||
|
abstract fun getCalendars(): List<CaldavCalendar>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM caldav_lists WHERE cdl_uuid = :uuid LIMIT 1")
|
||||||
|
abstract fun getCalendar(uuid: String): CaldavCalendar?
|
||||||
|
|
||||||
|
@Query("SELECT cd_object FROM caldav_tasks WHERE cd_calendar = :calendar")
|
||||||
|
abstract fun getObjects(calendar: String): List<String>
|
||||||
|
|
||||||
|
fun getTasks(calendar: String, objects: List<String>): List<Long> =
|
||||||
|
objects.chunkedMap { getTasksInternal(calendar, it) }
|
||||||
|
|
||||||
|
@Query("SELECT cd_task FROM caldav_tasks WHERE cd_calendar = :calendar AND cd_object IN (:objects)")
|
||||||
|
abstract fun getTasksInternal(calendar: String, objects: List<String>): List<Long>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM caldav_lists WHERE cdl_account = :account AND cdl_url NOT IN (:urls)")
|
||||||
|
abstract fun findDeletedCalendars(account: String, urls: List<String>): List<CaldavCalendar>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM caldav_lists WHERE cdl_account = :account AND cdl_url = :url LIMIT 1")
|
||||||
|
abstract fun getCalendarByUrl(account: String, url: String): CaldavCalendar?
|
||||||
|
|
||||||
|
@Query("SELECT caldav_accounts.* from caldav_accounts"
|
||||||
|
+ " INNER JOIN caldav_tasks ON cd_task = :task"
|
||||||
|
+ " INNER JOIN caldav_lists ON cd_calendar = cdl_uuid"
|
||||||
|
+ " WHERE cdl_account = cda_uuid")
|
||||||
|
abstract fun getAccountForTask(task: Long): CaldavAccount?
|
||||||
|
|
||||||
|
@Query("SELECT DISTINCT cd_calendar FROM caldav_tasks WHERE cd_deleted = 0 AND cd_task IN (:tasks)")
|
||||||
|
abstract fun getCalendars(tasks: List<Long>): List<String>
|
||||||
|
|
||||||
|
@Query("SELECT caldav_lists.*, COUNT(tasks._id) AS count"
|
||||||
|
+ " FROM caldav_lists"
|
||||||
|
+ " LEFT JOIN caldav_tasks ON caldav_tasks.cd_calendar = caldav_lists.cdl_uuid"
|
||||||
|
+ " LEFT JOIN tasks ON caldav_tasks.cd_task = tasks._id AND tasks.deleted = 0 AND tasks.completed = 0 AND tasks.hideUntil < :now AND cd_deleted = 0"
|
||||||
|
+ " WHERE caldav_lists.cdl_account = :uuid"
|
||||||
|
+ " GROUP BY caldav_lists.cdl_uuid")
|
||||||
|
abstract fun getCaldavFilters(uuid: String, now: Long = currentTimeMillis()): List<CaldavFilters>
|
||||||
|
|
||||||
|
@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")
|
||||||
|
abstract fun getTasksWithTags(): List<Long>
|
||||||
|
|
||||||
|
@Query("UPDATE tasks SET parent = IFNULL(("
|
||||||
|
+ " SELECT p.cd_task FROM caldav_tasks AS p"
|
||||||
|
+ " INNER JOIN caldav_tasks ON caldav_tasks.cd_task = tasks._id"
|
||||||
|
+ " WHERE p.cd_remote_id = caldav_tasks.cd_remote_parent"
|
||||||
|
+ " AND p.cd_calendar = caldav_tasks.cd_calendar"
|
||||||
|
+ " AND p.cd_deleted = 0), 0)"
|
||||||
|
+ "WHERE _id IN (SELECT _id FROM tasks INNER JOIN caldav_tasks ON _id = cd_task WHERE cd_deleted = 0)")
|
||||||
|
abstract fun updateParents()
|
||||||
|
|
||||||
|
@Query("UPDATE tasks SET parent = IFNULL(("
|
||||||
|
+ " SELECT p.cd_task FROM caldav_tasks AS p"
|
||||||
|
+ " INNER JOIN caldav_tasks "
|
||||||
|
+ " ON caldav_tasks.cd_task = tasks._id"
|
||||||
|
+ " AND caldav_tasks.cd_calendar = :calendar"
|
||||||
|
+ " WHERE p.cd_remote_id = caldav_tasks.cd_remote_parent"
|
||||||
|
+ " AND p.cd_calendar = caldav_tasks.cd_calendar"
|
||||||
|
+ " 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 move(task: TaskContainer, newParent: Long, newPosition: Long?) {
|
||||||
|
val previousParent = task.parent
|
||||||
|
val caldavTask = task.caldavTask
|
||||||
|
val previousPosition = task.caldavSortOrder
|
||||||
|
if (newPosition != null) {
|
||||||
|
if (newParent == previousParent && newPosition < previousPosition) {
|
||||||
|
shiftDown(task.caldav!!, newParent, newPosition, previousPosition)
|
||||||
|
} else {
|
||||||
|
shiftDown(task.caldav!!, newParent, newPosition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
caldavTask.cd_order = newPosition
|
||||||
|
update(caldavTask.cd_id, caldavTask.cd_order)
|
||||||
|
}
|
||||||
|
|
||||||
|
@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>
|
||||||
|
|
||||||
|
@Query("UPDATE caldav_lists SET cdl_order = $NO_ORDER")
|
||||||
|
abstract fun resetOrders()
|
||||||
|
|
||||||
|
@Query("UPDATE caldav_lists SET cdl_order = :order WHERE cdl_id = :id")
|
||||||
|
abstract fun setOrder(id: Long, order: Int)
|
||||||
|
|
||||||
|
fun setupLocalAccount(context: Context): CaldavAccount {
|
||||||
|
val account = getLocalAccount()
|
||||||
|
getLocalList(context, account)
|
||||||
|
return account
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getLocalList(context: Context) = getLocalList(context, getLocalAccount())
|
||||||
|
|
||||||
|
private fun getLocalAccount() = getAccountByUuid(LOCAL) ?: CaldavAccount().apply {
|
||||||
|
accountType = CaldavAccount.TYPE_LOCAL
|
||||||
|
uuid = LOCAL
|
||||||
|
id = insert(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getLocalList(context: Context, account: CaldavAccount): CaldavCalendar =
|
||||||
|
getCalendarsByAccount(account.uuid!!).getOrNull(0)
|
||||||
|
?: CaldavCalendar(context.getString(R.string.default_list), UUIDHelper.newUUID()).apply {
|
||||||
|
this.account = account.uuid
|
||||||
|
insert(this)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,47 @@
|
|||||||
|
package org.tasks.data
|
||||||
|
|
||||||
|
import android.database.Cursor
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Query
|
||||||
|
import androidx.room.RawQuery
|
||||||
|
import androidx.sqlite.db.SupportSQLiteQuery
|
||||||
|
import com.todoroo.astrid.data.Task
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface ContentProviderDaoBlocking {
|
||||||
|
@Query("SELECT name FROM tags WHERE task = :taskId ORDER BY UPPER(name) ASC")
|
||||||
|
fun getTagNames(taskId: Long): List<String>
|
||||||
|
|
||||||
|
@Query("""
|
||||||
|
SELECT *
|
||||||
|
FROM tasks
|
||||||
|
WHERE completed = 0
|
||||||
|
AND deleted = 0
|
||||||
|
AND hideUntil < (strftime('%s', 'now') * 1000)
|
||||||
|
ORDER BY (CASE
|
||||||
|
WHEN (dueDate = 0) THEN
|
||||||
|
(strftime('%s', 'now') * 1000) * 2
|
||||||
|
ELSE ((CASE WHEN (dueDate / 1000) % 60 > 0 THEN dueDate ELSE (dueDate + 43140000) END)) END) +
|
||||||
|
172800000 * importance
|
||||||
|
ASC
|
||||||
|
LIMIT 100""")
|
||||||
|
fun getAstrid2TaskProviderTasks(): List<Task>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM tagdata WHERE name IS NOT NULL AND name != '' ORDER BY UPPER(name) ASC")
|
||||||
|
fun tagDataOrderedByName(): List<TagData>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM tasks")
|
||||||
|
fun getTasks(): Cursor
|
||||||
|
|
||||||
|
@Query("""
|
||||||
|
SELECT caldav_lists.*, caldav_accounts.cda_name
|
||||||
|
FROM caldav_lists
|
||||||
|
INNER JOIN caldav_accounts ON cdl_account = cda_uuid""")
|
||||||
|
fun getLists(): Cursor
|
||||||
|
|
||||||
|
@Query("SELECT * FROM google_task_lists")
|
||||||
|
fun getGoogleTaskLists(): Cursor
|
||||||
|
|
||||||
|
@RawQuery
|
||||||
|
fun rawQuery(query: SupportSQLiteQuery): Cursor
|
||||||
|
}
|
@ -0,0 +1,114 @@
|
|||||||
|
package org.tasks.data
|
||||||
|
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Delete
|
||||||
|
import androidx.room.Query
|
||||||
|
import androidx.room.Transaction
|
||||||
|
import org.tasks.data.CaldavDao.Companion.LOCAL
|
||||||
|
import org.tasks.db.DbUtils.eachChunk
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
abstract class DeletionDaoBlocking {
|
||||||
|
@Query("DELETE FROM caldav_tasks WHERE cd_task IN(:ids)")
|
||||||
|
abstract fun deleteCaldavTasks(ids: List<Long>)
|
||||||
|
|
||||||
|
@Query("DELETE FROM google_tasks WHERE gt_task IN(:ids)")
|
||||||
|
abstract fun deleteGoogleTasks(ids: List<Long>)
|
||||||
|
|
||||||
|
@Query("DELETE FROM tags WHERE task IN(:ids)")
|
||||||
|
abstract fun deleteTags(ids: List<Long>)
|
||||||
|
|
||||||
|
@Query("DELETE FROM geofences WHERE task IN(:ids)")
|
||||||
|
abstract fun deleteGeofences(ids: List<Long>)
|
||||||
|
|
||||||
|
@Query("DELETE FROM alarms WHERE task IN(:ids)")
|
||||||
|
abstract fun deleteAlarms(ids: List<Long>)
|
||||||
|
|
||||||
|
@Query("DELETE FROM tasks WHERE _id IN(:ids)")
|
||||||
|
abstract fun deleteTasks(ids: List<Long>)
|
||||||
|
|
||||||
|
@Transaction
|
||||||
|
open fun delete(ids: List<Long>) {
|
||||||
|
ids.eachChunk {
|
||||||
|
deleteAlarms(it)
|
||||||
|
deleteGeofences(it)
|
||||||
|
deleteTags(it)
|
||||||
|
deleteGoogleTasks(it)
|
||||||
|
deleteCaldavTasks(it)
|
||||||
|
deleteTasks(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Query("UPDATE tasks "
|
||||||
|
+ "SET modified = (strftime('%s','now')*1000), deleted = (strftime('%s','now')*1000)"
|
||||||
|
+ "WHERE _id IN(:ids)")
|
||||||
|
abstract fun markDeletedInternal(ids: List<Long>)
|
||||||
|
|
||||||
|
fun markDeleted(ids: Iterable<Long>) {
|
||||||
|
ids.eachChunk(this::markDeletedInternal)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Query("SELECT gt_task FROM google_tasks WHERE gt_deleted = 0 AND gt_list_id = :listId")
|
||||||
|
abstract fun getActiveGoogleTasks(listId: String): List<Long>
|
||||||
|
|
||||||
|
@Delete
|
||||||
|
abstract fun deleteGoogleTaskList(googleTaskList: GoogleTaskList)
|
||||||
|
|
||||||
|
@Transaction
|
||||||
|
open fun delete(googleTaskList: GoogleTaskList): List<Long> {
|
||||||
|
val tasks = getActiveGoogleTasks(googleTaskList.remoteId!!)
|
||||||
|
delete(tasks)
|
||||||
|
deleteGoogleTaskList(googleTaskList)
|
||||||
|
return tasks
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete
|
||||||
|
abstract fun deleteGoogleTaskAccount(googleTaskAccount: GoogleTaskAccount)
|
||||||
|
|
||||||
|
@Query("SELECT * FROM google_task_lists WHERE gtl_account = :account ORDER BY gtl_title ASC")
|
||||||
|
abstract fun getLists(account: String): List<GoogleTaskList>
|
||||||
|
|
||||||
|
@Transaction
|
||||||
|
open fun delete(googleTaskAccount: GoogleTaskAccount): List<Long> {
|
||||||
|
val deleted = ArrayList<Long>()
|
||||||
|
for (list in getLists(googleTaskAccount.account!!)) {
|
||||||
|
deleted.addAll(delete(list))
|
||||||
|
}
|
||||||
|
deleteGoogleTaskAccount(googleTaskAccount)
|
||||||
|
return deleted
|
||||||
|
}
|
||||||
|
|
||||||
|
@Query("SELECT cd_task FROM caldav_tasks WHERE cd_calendar = :calendar AND cd_deleted = 0")
|
||||||
|
abstract fun getActiveCaldavTasks(calendar: String): List<Long>
|
||||||
|
|
||||||
|
@Delete
|
||||||
|
abstract fun deleteCaldavCalendar(caldavCalendar: CaldavCalendar)
|
||||||
|
|
||||||
|
@Transaction
|
||||||
|
open fun delete(caldavCalendar: CaldavCalendar): List<Long> {
|
||||||
|
val tasks = getActiveCaldavTasks(caldavCalendar.uuid!!)
|
||||||
|
delete(tasks)
|
||||||
|
deleteCaldavCalendar(caldavCalendar)
|
||||||
|
return tasks
|
||||||
|
}
|
||||||
|
|
||||||
|
@Query("SELECT * FROM caldav_lists WHERE cdl_account = :account")
|
||||||
|
abstract fun getCalendars(account: String): List<CaldavCalendar>
|
||||||
|
|
||||||
|
@Delete
|
||||||
|
abstract fun deleteCaldavAccount(caldavAccount: CaldavAccount)
|
||||||
|
|
||||||
|
@Query("DELETE FROM tasks WHERE _id IN (SELECT _id FROM tasks INNER JOIN caldav_tasks ON _id = cd_task INNER JOIN caldav_lists ON cdl_uuid = cd_calendar WHERE cdl_account = '$LOCAL' AND deleted > 0)")
|
||||||
|
abstract fun purgeDeleted()
|
||||||
|
|
||||||
|
@Transaction
|
||||||
|
open fun delete(caldavAccount: CaldavAccount): List<Long> {
|
||||||
|
val deleted = ArrayList<Long>()
|
||||||
|
for (calendar in getCalendars(caldavAccount.uuid!!)) {
|
||||||
|
deleted.addAll(delete(calendar))
|
||||||
|
}
|
||||||
|
deleteCaldavAccount(caldavAccount)
|
||||||
|
return deleted
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
package org.tasks.data
|
||||||
|
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Insert
|
||||||
|
import androidx.room.Query
|
||||||
|
import androidx.room.Update
|
||||||
|
import com.todoroo.astrid.api.FilterListItem.NO_ORDER
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface FilterDaoBlocking {
|
||||||
|
@Update
|
||||||
|
fun update(filter: Filter)
|
||||||
|
|
||||||
|
@Query("DELETE FROM filters WHERE _id = :id")
|
||||||
|
fun delete(id: Long)
|
||||||
|
|
||||||
|
@Query("SELECT * FROM filters WHERE title = :title COLLATE NOCASE LIMIT 1")
|
||||||
|
fun getByName(title: String): Filter?
|
||||||
|
|
||||||
|
@Insert
|
||||||
|
fun insert(filter: Filter): Long
|
||||||
|
|
||||||
|
@Query("SELECT * FROM filters")
|
||||||
|
fun getFilters(): List<Filter>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM filters WHERE _id = :id LIMIT 1")
|
||||||
|
fun getById(id: Long): Filter?
|
||||||
|
|
||||||
|
@Query("SELECT * FROM filters")
|
||||||
|
fun getAll(): List<Filter>
|
||||||
|
|
||||||
|
@Query("UPDATE filters SET f_order = $NO_ORDER")
|
||||||
|
fun resetOrders()
|
||||||
|
|
||||||
|
@Query("UPDATE filters SET f_order = :order WHERE _id = :id")
|
||||||
|
fun setOrder(id: Long, order: Int)
|
||||||
|
}
|
@ -0,0 +1,179 @@
|
|||||||
|
package org.tasks.data
|
||||||
|
|
||||||
|
import androidx.room.*
|
||||||
|
import com.todoroo.astrid.data.Task
|
||||||
|
import org.tasks.time.DateTimeUtils.currentTimeMillis
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
abstract class GoogleTaskDaoBlocking {
|
||||||
|
@Insert
|
||||||
|
abstract fun insert(task: GoogleTask): Long
|
||||||
|
|
||||||
|
@Insert
|
||||||
|
abstract fun insert(tasks: Iterable<GoogleTask>)
|
||||||
|
|
||||||
|
@Transaction
|
||||||
|
open fun insertAndShift(task: GoogleTask, top: Boolean) {
|
||||||
|
if (top) {
|
||||||
|
task.order = 0
|
||||||
|
shiftDown(task.listId!!, task.parent, 0)
|
||||||
|
} else {
|
||||||
|
task.order = getBottom(task.listId!!, task.parent)
|
||||||
|
}
|
||||||
|
task.id = insert(task)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Query("UPDATE google_tasks SET gt_order = gt_order + 1 WHERE gt_list_id = :listId AND gt_parent = :parent AND gt_order >= :position")
|
||||||
|
abstract fun shiftDown(listId: String, parent: Long, position: Long)
|
||||||
|
|
||||||
|
@Query("UPDATE google_tasks SET gt_order = gt_order - 1 WHERE gt_list_id = :listId AND gt_parent = :parent AND gt_order > :from AND gt_order <= :to")
|
||||||
|
abstract fun shiftUp(listId: String, parent: Long, from: Long, to: Long)
|
||||||
|
|
||||||
|
@Query("UPDATE google_tasks SET gt_order = gt_order + 1 WHERE gt_list_id = :listId AND gt_parent = :parent AND gt_order < :from AND gt_order >= :to")
|
||||||
|
abstract fun shiftDown(listId: String, parent: Long, from: Long, to: Long)
|
||||||
|
|
||||||
|
@Query("UPDATE google_tasks SET gt_order = gt_order - 1 WHERE gt_list_id = :listId AND gt_parent = :parent AND gt_order >= :position")
|
||||||
|
abstract fun shiftUp(listId: String, parent: Long, position: Long)
|
||||||
|
|
||||||
|
@Transaction
|
||||||
|
open fun move(task: SubsetGoogleTask, newParent: Long, newPosition: Long) {
|
||||||
|
val previousParent = task.parent
|
||||||
|
val previousPosition = task.order
|
||||||
|
if (newParent == previousParent) {
|
||||||
|
if (previousPosition < newPosition) {
|
||||||
|
shiftUp(task.listId, newParent, previousPosition, newPosition)
|
||||||
|
} else {
|
||||||
|
shiftDown(task.listId, newParent, previousPosition, newPosition)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
shiftUp(task.listId, previousParent, previousPosition)
|
||||||
|
shiftDown(task.listId, newParent, newPosition)
|
||||||
|
}
|
||||||
|
task.parent = newParent
|
||||||
|
task.order = newPosition
|
||||||
|
update(task)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Query("UPDATE google_task_accounts SET gta_collapsed = :collapsed WHERE gta_id = :id")
|
||||||
|
abstract fun setCollapsed(id: Long, collapsed: Boolean)
|
||||||
|
|
||||||
|
@Query("SELECT * FROM google_tasks WHERE gt_task = :taskId AND gt_deleted = 0 LIMIT 1")
|
||||||
|
abstract fun getByTaskId(taskId: Long): GoogleTask?
|
||||||
|
|
||||||
|
@Update
|
||||||
|
abstract fun update(googleTask: GoogleTask)
|
||||||
|
|
||||||
|
private fun update(googleTask: SubsetGoogleTask) {
|
||||||
|
update(googleTask.id, googleTask.parent, googleTask.order)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Query("UPDATE google_tasks SET gt_order = :order, gt_parent = :parent, gt_moved = 1 WHERE gt_id = :id")
|
||||||
|
abstract fun update(id: Long, parent: Long, order: Long)
|
||||||
|
|
||||||
|
@Query("UPDATE google_tasks SET gt_deleted = :now WHERE gt_task = :task OR gt_parent = :task")
|
||||||
|
abstract fun markDeleted(task: Long, now: Long = currentTimeMillis())
|
||||||
|
|
||||||
|
@Delete
|
||||||
|
abstract fun delete(deleted: GoogleTask)
|
||||||
|
|
||||||
|
@Query("SELECT * FROM google_tasks WHERE gt_remote_id = :remoteId LIMIT 1")
|
||||||
|
abstract fun getByRemoteId(remoteId: String): GoogleTask?
|
||||||
|
|
||||||
|
@Query("SELECT * FROM google_tasks WHERE gt_task = :taskId AND gt_deleted > 0")
|
||||||
|
abstract fun getDeletedByTaskId(taskId: Long): List<GoogleTask>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM google_tasks WHERE gt_task = :taskId")
|
||||||
|
abstract fun getAllByTaskId(taskId: Long): List<GoogleTask>
|
||||||
|
|
||||||
|
@Query("SELECT DISTINCT gt_list_id FROM google_tasks WHERE gt_deleted = 0 AND gt_task IN (:tasks)")
|
||||||
|
abstract fun getLists(tasks: List<Long>): List<String>
|
||||||
|
|
||||||
|
@Query("SELECT gt_task FROM google_tasks WHERE gt_parent IN (:ids) AND gt_deleted = 0")
|
||||||
|
abstract fun getChildren(ids: List<Long>): List<Long>
|
||||||
|
|
||||||
|
@Query("SELECT tasks.* FROM tasks JOIN google_tasks ON tasks._id = gt_task WHERE gt_parent = :taskId")
|
||||||
|
abstract fun getChildTasks(taskId: Long): List<Task>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM google_tasks WHERE gt_parent = :id AND gt_deleted = 0")
|
||||||
|
abstract fun getChildren(id: Long): List<GoogleTask>
|
||||||
|
|
||||||
|
@Query("SELECT IFNULL(MAX(gt_order), -1) + 1 FROM google_tasks WHERE gt_list_id = :listId AND gt_parent = :parent")
|
||||||
|
abstract fun getBottom(listId: String, parent: Long): Long
|
||||||
|
|
||||||
|
@Query("SELECT gt_remote_id FROM google_tasks JOIN tasks ON tasks._id = gt_task WHERE deleted = 0 AND gt_list_id = :listId AND gt_parent = :parent AND gt_order < :order AND gt_remote_id IS NOT NULL AND gt_remote_id != '' ORDER BY gt_order DESC")
|
||||||
|
abstract fun getPrevious(listId: String, parent: Long, order: Long): String?
|
||||||
|
|
||||||
|
@Query("SELECT gt_remote_id FROM google_tasks WHERE gt_task = :task")
|
||||||
|
abstract fun getRemoteId(task: Long): String?
|
||||||
|
|
||||||
|
@Query("SELECT gt_task FROM google_tasks WHERE gt_remote_id = :remoteId")
|
||||||
|
abstract fun getTask(remoteId: String): Long
|
||||||
|
|
||||||
|
@SuppressWarnings(RoomWarnings.CURSOR_MISMATCH)
|
||||||
|
@Query("SELECT google_tasks.*, gt_order AS primary_sort, NULL AS secondary_sort FROM google_tasks JOIN tasks ON tasks._id = gt_task WHERE gt_parent = 0 AND gt_list_id = :listId AND tasks.deleted = 0 UNION SELECT c.*, p.gt_order AS primary_sort, c.gt_order AS secondary_sort FROM google_tasks AS c LEFT JOIN google_tasks AS p ON c.gt_parent = p.gt_task JOIN tasks ON tasks._id = c.gt_task WHERE c.gt_parent > 0 AND c.gt_list_id = :listId AND tasks.deleted = 0 ORDER BY primary_sort ASC, secondary_sort ASC")
|
||||||
|
abstract fun getByLocalOrder(listId: String): List<GoogleTask>
|
||||||
|
|
||||||
|
@SuppressWarnings(RoomWarnings.CURSOR_MISMATCH)
|
||||||
|
@Query("SELECT google_tasks.*, gt_remote_order AS primary_sort, NULL AS secondary_sort FROM google_tasks JOIN tasks ON tasks._id = gt_task WHERE gt_parent = 0 AND gt_list_id = :listId AND tasks.deleted = 0 UNION SELECT c.*, p.gt_remote_order AS primary_sort, c.gt_remote_order AS secondary_sort FROM google_tasks AS c LEFT JOIN google_tasks AS p ON c.gt_parent = p.gt_task JOIN tasks ON tasks._id = c.gt_task WHERE c.gt_parent > 0 AND c.gt_list_id = :listId AND tasks.deleted = 0 ORDER BY primary_sort ASC, secondary_sort ASC")
|
||||||
|
abstract fun getByRemoteOrder(listId: String): List<GoogleTask>
|
||||||
|
|
||||||
|
@Query("UPDATE google_tasks"
|
||||||
|
+ " SET gt_parent = IFNULL(("
|
||||||
|
+ " SELECT gt_task FROM google_tasks AS p"
|
||||||
|
+ " WHERE p.gt_remote_id = google_tasks.gt_remote_parent"
|
||||||
|
+ " AND p.gt_list_id = google_tasks.gt_list_id "
|
||||||
|
+ " AND p.gt_deleted = 0),"
|
||||||
|
+ " 0)"
|
||||||
|
+ " WHERE gt_moved = 0")
|
||||||
|
abstract fun updateParents()
|
||||||
|
|
||||||
|
@Query("UPDATE google_tasks SET gt_parent = IFNULL((SELECT gt_task FROM google_tasks AS p WHERE p.gt_remote_id = google_tasks.gt_remote_parent), 0) WHERE gt_list_id = :listId AND gt_moved = 0")
|
||||||
|
abstract fun updateParents(listId: String)
|
||||||
|
|
||||||
|
@Query("UPDATE google_tasks SET gt_remote_parent = :parent, gt_remote_order = :position WHERE gt_remote_id = :id")
|
||||||
|
abstract fun updatePosition(id: String, parent: String, position: String)
|
||||||
|
|
||||||
|
@Transaction
|
||||||
|
open fun reposition(listId: String) {
|
||||||
|
updateParents(listId)
|
||||||
|
val orderedTasks = getByRemoteOrder(listId)
|
||||||
|
var subtasks = 0L
|
||||||
|
var parent = 0L
|
||||||
|
for (task in orderedTasks) {
|
||||||
|
if (task.parent > 0) {
|
||||||
|
if (task.order != subtasks && !task.isMoved) {
|
||||||
|
task.order = subtasks
|
||||||
|
update(task)
|
||||||
|
}
|
||||||
|
subtasks++
|
||||||
|
} else {
|
||||||
|
subtasks = 0
|
||||||
|
if (task.order != parent && !task.isMoved) {
|
||||||
|
task.order = parent
|
||||||
|
update(task)
|
||||||
|
}
|
||||||
|
parent++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun validateSorting(listId: String) {
|
||||||
|
val orderedTasks = getByLocalOrder(listId)
|
||||||
|
var subtasks = 0L
|
||||||
|
var parent = 0L
|
||||||
|
for (task in orderedTasks) {
|
||||||
|
if (task.parent > 0) {
|
||||||
|
if (task.order != subtasks) {
|
||||||
|
throw IllegalStateException("Subtask violation, expected $subtasks but was ${task.order}")
|
||||||
|
}
|
||||||
|
subtasks++
|
||||||
|
} else {
|
||||||
|
subtasks = 0
|
||||||
|
if (task.order != parent) {
|
||||||
|
throw IllegalStateException("Parent violation, expected $parent but was ${task.order}")
|
||||||
|
}
|
||||||
|
parent++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,72 @@
|
|||||||
|
package org.tasks.data
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.room.*
|
||||||
|
import com.todoroo.astrid.api.FilterListItem.NO_ORDER
|
||||||
|
import org.tasks.filters.GoogleTaskFilters
|
||||||
|
import org.tasks.time.DateTimeUtils.currentTimeMillis
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface GoogleTaskListDaoBlocking {
|
||||||
|
@Query("SELECT COUNT(*) FROM google_task_accounts")
|
||||||
|
fun accountCount(): Int
|
||||||
|
|
||||||
|
@Query("SELECT * FROM google_task_accounts")
|
||||||
|
fun getAccounts(): List<GoogleTaskAccount>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM google_task_accounts WHERE gta_account = :account COLLATE NOCASE LIMIT 1")
|
||||||
|
fun getAccount(account: String): GoogleTaskAccount?
|
||||||
|
|
||||||
|
@Query("SELECT * FROM google_task_lists WHERE gtl_id = :id")
|
||||||
|
fun getById(id: Long): GoogleTaskList?
|
||||||
|
|
||||||
|
@Query("SELECT * FROM google_task_lists WHERE gtl_account = :account ORDER BY gtl_title ASC")
|
||||||
|
fun getLists(account: String): List<GoogleTaskList>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM google_task_lists WHERE gtl_remote_id = :remoteId LIMIT 1")
|
||||||
|
fun getByRemoteId(remoteId: String): GoogleTaskList?
|
||||||
|
|
||||||
|
@Query("SELECT * FROM google_task_lists WHERE gtl_remote_id IN (:remoteIds)")
|
||||||
|
fun getByRemoteId(remoteIds: List<String>): List<GoogleTaskList>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM google_task_lists")
|
||||||
|
fun subscribeToLists(): LiveData<List<GoogleTaskList>>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM google_task_lists WHERE gtl_remote_id = :remoteId AND IFNULL(gtl_account, '') = ''")
|
||||||
|
fun findExistingList(remoteId: String): GoogleTaskList?
|
||||||
|
|
||||||
|
@Query("SELECT * FROM google_task_lists")
|
||||||
|
fun getAllLists(): List<GoogleTaskList>
|
||||||
|
|
||||||
|
@Query("UPDATE google_task_lists SET gtl_last_sync = 0 WHERE gtl_account = :account")
|
||||||
|
fun resetLastSync(account: String)
|
||||||
|
|
||||||
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
|
fun insertOrReplace(googleTaskList: GoogleTaskList): Long
|
||||||
|
|
||||||
|
@Insert
|
||||||
|
fun insert(googleTaskList: GoogleTaskList): Long
|
||||||
|
|
||||||
|
@Insert
|
||||||
|
fun insert(googleTaskAccount: GoogleTaskAccount)
|
||||||
|
|
||||||
|
@Update
|
||||||
|
fun update(account: GoogleTaskAccount)
|
||||||
|
|
||||||
|
@Update
|
||||||
|
fun update(list: GoogleTaskList)
|
||||||
|
|
||||||
|
@Query("SELECT google_task_lists.*, COUNT(tasks._id) AS count"
|
||||||
|
+ " FROM google_task_lists "
|
||||||
|
+ " LEFT JOIN google_tasks ON google_tasks.gt_list_id = google_task_lists.gtl_remote_id"
|
||||||
|
+ " LEFT JOIN tasks ON google_tasks.gt_task = tasks._id AND tasks.deleted = 0 AND tasks.completed = 0 AND tasks.hideUntil < :now AND gt_deleted = 0"
|
||||||
|
+ " WHERE google_task_lists.gtl_account = :account"
|
||||||
|
+ " GROUP BY google_task_lists.gtl_remote_id")
|
||||||
|
fun getGoogleTaskFilters(account: String, now: Long = currentTimeMillis()): List<GoogleTaskFilters>
|
||||||
|
|
||||||
|
@Query("UPDATE google_task_lists SET gtl_remote_order = $NO_ORDER")
|
||||||
|
fun resetOrders()
|
||||||
|
|
||||||
|
@Query("UPDATE google_task_lists SET gtl_remote_order = :order WHERE gtl_id = :id")
|
||||||
|
fun setOrder(id: Long, order: Int)
|
||||||
|
}
|
@ -0,0 +1,118 @@
|
|||||||
|
package org.tasks.data
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.room.*
|
||||||
|
import com.todoroo.astrid.api.FilterListItem.NO_ORDER
|
||||||
|
import org.tasks.filters.LocationFilters
|
||||||
|
import org.tasks.time.DateTimeUtils.currentTimeMillis
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface LocationDaoBlocking {
|
||||||
|
@Query("SELECT places.*"
|
||||||
|
+ " FROM places"
|
||||||
|
+ " INNER JOIN geofences ON geofences.place = places.uid"
|
||||||
|
+ " INNER JOIN tasks ON geofences.task = tasks._id"
|
||||||
|
+ " WHERE tasks.completed = 0 AND tasks.deleted = 0"
|
||||||
|
+ " AND (geofences.arrival > 0 OR geofences.departure > 0)"
|
||||||
|
+ " GROUP BY places.uid")
|
||||||
|
fun getPlacesWithGeofences(): List<Place>
|
||||||
|
|
||||||
|
@Query("SELECT places.*,"
|
||||||
|
+ " max(geofences.arrival) as arrival,"
|
||||||
|
+ " max(geofences.departure) as departure,"
|
||||||
|
+ " min(geofences.radius) as radius"
|
||||||
|
+ " FROM places"
|
||||||
|
+ " INNER JOIN geofences ON geofences.place = places.uid"
|
||||||
|
+ " INNER JOIN tasks ON tasks._id = geofences.task"
|
||||||
|
+ " WHERE place = :uid AND tasks.completed = 0 AND tasks.deleted = 0"
|
||||||
|
+ " AND (geofences.arrival > 0 OR geofences.departure > 0)"
|
||||||
|
+ " GROUP BY places.uid")
|
||||||
|
fun getGeofencesByPlace(uid: String): MergedGeofence?
|
||||||
|
|
||||||
|
@Query("DELETE FROM geofences WHERE place = :place")
|
||||||
|
fun deleteGeofencesByPlace(place: String)
|
||||||
|
|
||||||
|
@Query("SELECT geofences.* FROM geofences"
|
||||||
|
+ " INNER JOIN tasks ON tasks._id = geofences.task"
|
||||||
|
+ " WHERE place = :place AND arrival = 1 AND tasks.completed = 0"
|
||||||
|
+ " AND tasks.deleted = 0 AND tasks.snoozeTime < :now AND tasks.hideUntil < :now")
|
||||||
|
fun getArrivalGeofences(place: String, now: Long): List<Geofence>
|
||||||
|
|
||||||
|
@Query("SELECT geofences.* FROM geofences"
|
||||||
|
+ " INNER JOIN tasks ON tasks._id = geofences.task"
|
||||||
|
+ " WHERE place = :place AND departure = 1 AND tasks.completed = 0"
|
||||||
|
+ " AND tasks.deleted = 0 AND tasks.snoozeTime < :now AND tasks.hideUntil < :now")
|
||||||
|
fun getDepartureGeofences(place: String, now: Long): List<Geofence>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM geofences"
|
||||||
|
+ " INNER JOIN places ON geofences.place = places.uid"
|
||||||
|
+ " WHERE task = :taskId ORDER BY name ASC LIMIT 1")
|
||||||
|
fun getGeofences(taskId: Long): Location?
|
||||||
|
|
||||||
|
@Query("SELECT geofences.*, places.* FROM geofences INNER JOIN places ON geofences.place = places.uid INNER JOIN tasks ON tasks._id = geofences.task WHERE tasks._id = :taskId AND tasks.deleted = 0 AND tasks.completed = 0")
|
||||||
|
fun getActiveGeofences(taskId: Long): List<Location>
|
||||||
|
|
||||||
|
@Query("SELECT places.*"
|
||||||
|
+ " FROM places"
|
||||||
|
+ " INNER JOIN geofences ON geofences.place = places.uid"
|
||||||
|
+ " WHERE geofences.task = :taskId")
|
||||||
|
fun getPlaceForTask(taskId: Long): Place?
|
||||||
|
|
||||||
|
@Query("SELECT geofences.*, places.* FROM geofences INNER JOIN places ON geofences.place = places.uid INNER JOIN tasks ON tasks._id = geofences.task WHERE tasks.deleted = 0 AND tasks.completed = 0")
|
||||||
|
fun getActiveGeofences(): List<Location>
|
||||||
|
|
||||||
|
@Query("SELECT COUNT(*) FROM geofences")
|
||||||
|
suspend fun geofenceCount(): Int
|
||||||
|
|
||||||
|
@Delete
|
||||||
|
fun delete(location: Geofence)
|
||||||
|
|
||||||
|
@Delete
|
||||||
|
fun delete(place: Place)
|
||||||
|
|
||||||
|
@Insert
|
||||||
|
fun insert(location: Geofence): Long
|
||||||
|
|
||||||
|
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||||
|
fun insert(place: Place): Long
|
||||||
|
|
||||||
|
@Update
|
||||||
|
fun update(place: Place)
|
||||||
|
|
||||||
|
@Update
|
||||||
|
fun update(geofence: Geofence)
|
||||||
|
|
||||||
|
@Query("SELECT * FROM places WHERE uid = :uid LIMIT 1")
|
||||||
|
fun getByUid(uid: String): Place?
|
||||||
|
|
||||||
|
@Query("SELECT * FROM geofences WHERE task = :taskId")
|
||||||
|
fun getGeofencesForTask(taskId: Long): List<Geofence>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM places")
|
||||||
|
fun getPlaces(): List<Place>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM places WHERE place_id = :id")
|
||||||
|
fun getPlace(id: Long): Place?
|
||||||
|
|
||||||
|
@Query("SELECT * FROM places WHERE uid = :uid")
|
||||||
|
fun getPlace(uid: String): Place?
|
||||||
|
|
||||||
|
@Query("SELECT places.*, IFNULL(COUNT(geofence_id),0) AS count FROM places LEFT OUTER JOIN geofences ON geofences.place = places.uid GROUP BY uid ORDER BY COUNT(geofence_id) DESC")
|
||||||
|
fun getPlaceUsage(): LiveData<List<PlaceUsage>>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM places WHERE latitude LIKE :latitude AND longitude LIKE :longitude")
|
||||||
|
fun findPlace(latitude: String, longitude: String): Place?
|
||||||
|
|
||||||
|
@Query("SELECT places.*, COUNT(tasks._id) AS count FROM places "
|
||||||
|
+ " LEFT JOIN geofences ON geofences.place = places.uid "
|
||||||
|
+ " LEFT JOIN tasks ON geofences.task = tasks._id AND tasks.completed = 0 AND tasks.deleted = 0 AND tasks.hideUntil < :now"
|
||||||
|
+ " GROUP BY places.uid"
|
||||||
|
+ " ORDER BY name COLLATE NOCASE ASC")
|
||||||
|
fun getPlaceFilters(now: Long = currentTimeMillis()): List<LocationFilters>
|
||||||
|
|
||||||
|
@Query("UPDATE places SET place_order = $NO_ORDER")
|
||||||
|
fun resetOrders()
|
||||||
|
|
||||||
|
@Query("UPDATE places SET place_order = :order WHERE place_id = :id")
|
||||||
|
fun setOrder(id: Long, order: Int)
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
package org.tasks.data
|
||||||
|
|
||||||
|
import androidx.room.*
|
||||||
|
import com.todoroo.astrid.data.Task
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
abstract class TagDaoBlocking {
|
||||||
|
@Query("UPDATE tags SET name = :name WHERE tag_uid = :tagUid")
|
||||||
|
abstract fun rename(tagUid: String, name: String)
|
||||||
|
|
||||||
|
@Insert
|
||||||
|
abstract fun insert(tag: Tag)
|
||||||
|
|
||||||
|
@Insert
|
||||||
|
abstract fun insert(tags: Iterable<Tag>)
|
||||||
|
|
||||||
|
@Query("DELETE FROM tags WHERE task = :taskId AND tag_uid in (:tagUids)")
|
||||||
|
abstract fun deleteTags(taskId: Long, tagUids: List<String>)
|
||||||
|
|
||||||
|
@Query("SELECT * FROM tags WHERE tag_uid = :tagUid")
|
||||||
|
abstract fun getByTagUid(tagUid: String): List<Tag>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM tags WHERE task = :taskId")
|
||||||
|
abstract fun getTagsForTask(taskId: Long): List<Tag>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM tags WHERE task = :taskId AND tag_uid = :tagUid")
|
||||||
|
abstract fun getTagByTaskAndTagUid(taskId: Long, tagUid: String): Tag?
|
||||||
|
|
||||||
|
@Delete
|
||||||
|
abstract fun delete(tags: List<Tag>)
|
||||||
|
|
||||||
|
@Transaction
|
||||||
|
open fun applyTags(task: Task, tagDataDao: TagDataDaoBlocking, current: List<TagData>): Boolean {
|
||||||
|
val taskId = task.id
|
||||||
|
val existing = HashSet(tagDataDao.getTagDataForTask(taskId))
|
||||||
|
val selected = HashSet<TagData>(current)
|
||||||
|
val added = selected subtract existing
|
||||||
|
val removed = existing subtract selected
|
||||||
|
deleteTags(taskId, removed.map { td -> td.remoteId!! })
|
||||||
|
insert(task, added)
|
||||||
|
return removed.isNotEmpty() || added.isNotEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun insert(task: Task, tags: Collection<TagData>) {
|
||||||
|
if (!tags.isEmpty()) {
|
||||||
|
insert(tags.map { Tag(task, it) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,158 @@
|
|||||||
|
package org.tasks.data
|
||||||
|
|
||||||
|
import androidx.core.util.Pair
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.room.*
|
||||||
|
import com.todoroo.astrid.api.FilterListItem.NO_ORDER
|
||||||
|
import com.todoroo.astrid.data.Task
|
||||||
|
import com.todoroo.astrid.helper.UUIDHelper
|
||||||
|
import org.tasks.db.DbUtils
|
||||||
|
import org.tasks.filters.AlphanumComparator
|
||||||
|
import org.tasks.filters.TagFilters
|
||||||
|
import org.tasks.time.DateTimeUtils.currentTimeMillis
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.collections.HashSet
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
abstract class TagDataDaoBlocking {
|
||||||
|
@Query("SELECT * FROM tagdata")
|
||||||
|
abstract fun subscribeToTags(): LiveData<List<TagData>>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM tagdata WHERE name = :name COLLATE NOCASE LIMIT 1")
|
||||||
|
abstract fun getTagByName(name: String): TagData?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If a tag already exists in the database that case insensitively matches the given tag, return
|
||||||
|
* that. Otherwise, return the argument
|
||||||
|
*/
|
||||||
|
fun getTagWithCase(tag: String): String? {
|
||||||
|
return getTagByName(tag)?.name ?: tag
|
||||||
|
}
|
||||||
|
|
||||||
|
fun searchTags(query: String): List<TagData> {
|
||||||
|
return searchTagsInternal("%$query%")
|
||||||
|
.sortedWith(AlphanumComparator(TagData::name))
|
||||||
|
.toMutableList()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Query("SELECT * FROM tagdata WHERE name LIKE :query AND name NOT NULL AND name != ''")
|
||||||
|
protected abstract fun searchTagsInternal(query: String): List<TagData>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM tagdata")
|
||||||
|
abstract fun getAll(): List<TagData>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM tagdata WHERE remoteId = :uuid LIMIT 1")
|
||||||
|
abstract fun getByUuid(uuid: String): TagData?
|
||||||
|
|
||||||
|
@Query("SELECT * FROM tagdata WHERE remoteId IN (:uuids)")
|
||||||
|
abstract fun getByUuid(uuids: Collection<String>): List<TagData>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM tagdata WHERE name IS NOT NULL AND name != '' ORDER BY UPPER(name) ASC")
|
||||||
|
abstract fun tagDataOrderedByName(): List<TagData>
|
||||||
|
|
||||||
|
@Delete
|
||||||
|
abstract fun deleteTagData(tagData: TagData)
|
||||||
|
|
||||||
|
@Query("DELETE FROM tags WHERE tag_uid = :tagUid")
|
||||||
|
abstract fun deleteTags(tagUid: String)
|
||||||
|
|
||||||
|
@Query("SELECT * FROM tags WHERE task IN (:tasks) AND tag_uid NOT IN (:tagsToKeep)")
|
||||||
|
abstract fun tagsToDelete(tasks: List<Long>, tagsToKeep: List<String>): List<Tag>
|
||||||
|
|
||||||
|
fun getTagSelections(tasks: List<Long>): Pair<Set<String>, Set<String>> {
|
||||||
|
val allTags = getAllTags(tasks)
|
||||||
|
val tags = allTags.map { t: String? -> HashSet<String>(t?.split(",") ?: emptySet()) }
|
||||||
|
val partialTags = tags.flatten().toMutableSet()
|
||||||
|
var commonTags: MutableSet<String>? = null
|
||||||
|
if (tags.isEmpty()) {
|
||||||
|
commonTags = HashSet()
|
||||||
|
} else {
|
||||||
|
for (s in tags) {
|
||||||
|
if (commonTags == null) {
|
||||||
|
commonTags = s.toMutableSet()
|
||||||
|
} else {
|
||||||
|
commonTags.retainAll(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
partialTags.removeAll(commonTags!!)
|
||||||
|
return Pair(partialTags, commonTags)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Query("SELECT GROUP_CONCAT(DISTINCT(tag_uid)) FROM tasks"
|
||||||
|
+ " LEFT JOIN tags ON tags.task = tasks._id"
|
||||||
|
+ " WHERE tasks._id IN (:tasks)"
|
||||||
|
+ " GROUP BY tasks._id")
|
||||||
|
abstract fun getAllTags(tasks: List<Long>): List<String>
|
||||||
|
|
||||||
|
@Transaction
|
||||||
|
open fun applyTags(
|
||||||
|
tasks: List<Task>, partiallySelected: List<TagData>, selected: List<TagData>): List<Long> {
|
||||||
|
val modified = HashSet<Long>()
|
||||||
|
val keep = partiallySelected.plus(selected).map { it.remoteId!! }
|
||||||
|
for (sublist in tasks.chunked(DbUtils.MAX_SQLITE_ARGS - keep.size)) {
|
||||||
|
val tags = tagsToDelete(sublist.map(Task::id), keep)
|
||||||
|
deleteTags(tags)
|
||||||
|
modified.addAll(tags.map(Tag::task))
|
||||||
|
}
|
||||||
|
for (task in tasks) {
|
||||||
|
val added = selected subtract getTagDataForTask(task.id)
|
||||||
|
if (added.isNotEmpty()) {
|
||||||
|
modified.add(task.id)
|
||||||
|
insert(added.map { Tag(task, it) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ArrayList(modified)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transaction
|
||||||
|
open fun delete(tagData: TagData) {
|
||||||
|
deleteTags(tagData.remoteId!!)
|
||||||
|
deleteTagData(tagData)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete
|
||||||
|
abstract fun delete(tagData: List<TagData>)
|
||||||
|
|
||||||
|
@Delete
|
||||||
|
abstract fun deleteTags(tags: List<Tag>)
|
||||||
|
|
||||||
|
@Query("SELECT tagdata.* FROM tagdata "
|
||||||
|
+ "INNER JOIN tags ON tags.tag_uid = tagdata.remoteId "
|
||||||
|
+ "WHERE tags.task = :id "
|
||||||
|
+ "ORDER BY UPPER(tagdata.name) ASC")
|
||||||
|
abstract fun getTagDataForTask(id: Long): List<TagData>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM tagdata WHERE name IN (:names)")
|
||||||
|
abstract fun getTags(names: List<String>): List<TagData>
|
||||||
|
|
||||||
|
@Update
|
||||||
|
abstract fun update(tagData: TagData)
|
||||||
|
|
||||||
|
@Insert
|
||||||
|
abstract fun insert(tag: TagData): Long
|
||||||
|
|
||||||
|
@Insert
|
||||||
|
abstract fun insert(tags: Iterable<Tag>)
|
||||||
|
|
||||||
|
fun createNew(tag: TagData) {
|
||||||
|
if (Task.isUuidEmpty(tag.remoteId)) {
|
||||||
|
tag.remoteId = UUIDHelper.newUUID()
|
||||||
|
}
|
||||||
|
tag.id = insert(tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Query("SELECT tagdata.*, COUNT(tasks._id) AS count"
|
||||||
|
+ " FROM tagdata"
|
||||||
|
+ " LEFT JOIN tags ON tags.tag_uid = tagdata.remoteId"
|
||||||
|
+ " LEFT JOIN tasks ON tags.task = tasks._id AND tasks.deleted = 0 AND tasks.completed = 0 AND tasks.hideUntil < :now"
|
||||||
|
+ " WHERE tagdata.name IS NOT NULL AND tagdata.name != ''"
|
||||||
|
+ " GROUP BY tagdata.remoteId")
|
||||||
|
abstract fun getTagFilters(now: Long = currentTimeMillis()): List<TagFilters>
|
||||||
|
|
||||||
|
@Query("UPDATE tagdata SET td_order = $NO_ORDER")
|
||||||
|
abstract fun resetOrders()
|
||||||
|
|
||||||
|
@Query("UPDATE tagdata SET td_order = :order WHERE _id = :id")
|
||||||
|
abstract fun setOrder(id: Long, order: Int)
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
package org.tasks.data
|
||||||
|
|
||||||
|
import androidx.room.*
|
||||||
|
import com.todoroo.astrid.data.Task
|
||||||
|
import com.todoroo.astrid.helper.UUIDHelper
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
abstract class TaskAttachmentDaoBlocking {
|
||||||
|
@Query("SELECT * FROM task_attachments WHERE task_id = :taskUuid")
|
||||||
|
abstract fun getAttachments(taskUuid: String): List<TaskAttachment>
|
||||||
|
|
||||||
|
@Query("SELECT task_attachments.* FROM task_attachments INNER JOIN tasks ON tasks._id = :task WHERE task_id = tasks.remoteId")
|
||||||
|
abstract fun getAttachments(task: Long): List<TaskAttachment>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM task_attachments")
|
||||||
|
abstract fun getAttachments(): List<TaskAttachment>
|
||||||
|
|
||||||
|
@Delete
|
||||||
|
abstract fun delete(taskAttachment: TaskAttachment)
|
||||||
|
|
||||||
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
|
abstract fun insert(attachment: TaskAttachment)
|
||||||
|
|
||||||
|
@Update
|
||||||
|
abstract fun update(attachment: TaskAttachment)
|
||||||
|
|
||||||
|
fun createNew(attachment: TaskAttachment) {
|
||||||
|
if (Task.isUuidEmpty(attachment.remoteId)) {
|
||||||
|
attachment.remoteId = UUIDHelper.newUUID()
|
||||||
|
}
|
||||||
|
insert(attachment)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
package org.tasks.data
|
||||||
|
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Insert
|
||||||
|
import androidx.room.Query
|
||||||
|
import androidx.room.Update
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
abstract class TaskListMetadataDaoBlocking {
|
||||||
|
@Query("SELECT * from task_list_metadata where tag_uuid = :tagUuid OR filter = :tagUuid LIMIT 1")
|
||||||
|
abstract fun fetchByTagOrFilter(tagUuid: String): TaskListMetadata?
|
||||||
|
|
||||||
|
@Query("SELECT * FROM task_list_metadata")
|
||||||
|
abstract fun getAll(): List<TaskListMetadata>
|
||||||
|
|
||||||
|
@Update
|
||||||
|
abstract fun update(taskListMetadata: TaskListMetadata)
|
||||||
|
|
||||||
|
@Insert
|
||||||
|
abstract fun insert(taskListMetadata: TaskListMetadata): Long
|
||||||
|
|
||||||
|
fun createNew(taskListMetadata: TaskListMetadata) {
|
||||||
|
taskListMetadata.id = insert(taskListMetadata)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
package org.tasks.data
|
||||||
|
|
||||||
|
import androidx.room.*
|
||||||
|
import com.todoroo.andlib.utility.DateUtilities
|
||||||
|
import com.todoroo.astrid.data.Task
|
||||||
|
import com.todoroo.astrid.helper.UUIDHelper
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
abstract class UserActivityDaoBlocking {
|
||||||
|
@Insert
|
||||||
|
abstract fun insert(userActivity: UserActivity)
|
||||||
|
|
||||||
|
@Update
|
||||||
|
abstract fun update(userActivity: UserActivity)
|
||||||
|
|
||||||
|
@Delete
|
||||||
|
abstract fun delete(userActivity: UserActivity)
|
||||||
|
|
||||||
|
@Query("SELECT * FROM userActivity WHERE target_id = :taskUuid ORDER BY created_at DESC ")
|
||||||
|
abstract fun getCommentsForTask(taskUuid: String): List<UserActivity>
|
||||||
|
|
||||||
|
@Query("SELECT userActivity.* FROM userActivity INNER JOIN tasks ON tasks._id = :task WHERE target_id = tasks.remoteId")
|
||||||
|
abstract fun getComments(task: Long): List<UserActivity>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM userActivity")
|
||||||
|
abstract fun getComments(): List<UserActivity>
|
||||||
|
|
||||||
|
fun createNew(item: UserActivity) {
|
||||||
|
if (item.created == null || item.created == 0L) {
|
||||||
|
item.created = DateUtilities.now()
|
||||||
|
}
|
||||||
|
if (Task.isUuidEmpty(item.remoteId)) {
|
||||||
|
item.remoteId = UUIDHelper.newUUID()
|
||||||
|
}
|
||||||
|
insert(item)
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue