From 122a2c21706a1628623177780c85f2df2000c9f6 Mon Sep 17 00:00:00 2001 From: Alex Baker Date: Mon, 29 Jun 2020 10:29:50 -0500 Subject: [PATCH] Make daos suspending --- .../java/com/todoroo/astrid/dao/TaskDao.kt | 107 ++++++++-------- app/src/main/java/org/tasks/data/AlarmDao.kt | 12 +- app/src/main/java/org/tasks/data/CaldavDao.kt | 114 +++++++++--------- .../java/org/tasks/data/ContentProviderDao.kt | 6 +- .../main/java/org/tasks/data/DeletionDao.kt | 46 +++---- app/src/main/java/org/tasks/data/FilterDao.kt | 18 +-- .../main/java/org/tasks/data/GoogleTaskDao.kt | 66 +++++----- .../java/org/tasks/data/GoogleTaskListDao.kt | 36 +++--- .../main/java/org/tasks/data/LocationDao.kt | 48 ++++---- app/src/main/java/org/tasks/data/TagDao.kt | 20 +-- .../main/java/org/tasks/data/TagDataDao.kt | 52 ++++---- .../java/org/tasks/data/TaskAttachmentDao.kt | 14 +-- .../org/tasks/data/TaskListMetadataDao.kt | 10 +- .../java/org/tasks/data/UserActivityDao.kt | 14 +-- .../main/java/org/tasks/db/SuspendDbUtils.kt | 11 ++ .../tasks/notifications/NotificationDao.kt | 12 +- 16 files changed, 296 insertions(+), 290 deletions(-) create mode 100644 app/src/main/java/org/tasks/db/SuspendDbUtils.kt diff --git a/app/src/main/java/com/todoroo/astrid/dao/TaskDao.kt b/app/src/main/java/com/todoroo/astrid/dao/TaskDao.kt index 3057cb5fb..9f3349157 100644 --- a/app/src/main/java/com/todoroo/astrid/dao/TaskDao.kt +++ b/app/src/main/java/com/todoroo/astrid/dao/TaskDao.kt @@ -24,8 +24,8 @@ 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.db.SuspendDbUtils.chunkedMap +import org.tasks.db.SuspendDbUtils.eachChunk import org.tasks.jobs.WorkManager import org.tasks.preferences.Preferences import org.tasks.time.DateTimeUtils.currentTimeMillis @@ -39,56 +39,52 @@ abstract class TaskDao(private val database: Database) { this.workManager = workManager } - fun needsRefresh(): List { - 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 + abstract suspend fun needsRefresh(now: Long = DateUtilities.now()): List - fun fetchBlocking(id: Long) = runBlocking { + suspend 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): List = ids.chunkedMap(this::fetchInternal) + suspend fun fetch(ids: List): List = ids.chunkedMap(this::fetchInternal) @Query("SELECT * FROM tasks WHERE _id IN (:ids)") - internal abstract fun fetchInternal(ids: List): List + internal abstract suspend fun fetchInternal(ids: List): List @Query("SELECT COUNT(1) FROM tasks WHERE timerStart > 0 AND deleted = 0") - abstract fun activeTimers(): Int + abstract suspend fun activeTimers(): Int @Query("SELECT tasks.* FROM tasks INNER JOIN notification ON tasks._id = notification.task") - abstract fun activeNotifications(): List + abstract suspend fun activeNotifications(): List @Query("SELECT * FROM tasks WHERE remoteId = :remoteId") - abstract fun fetch(remoteId: String): Task? + abstract suspend fun fetch(remoteId: String): Task? @Query("SELECT * FROM tasks WHERE completed = 0 AND deleted = 0") - abstract fun getActiveTasks(): List + abstract suspend fun getActiveTasks(): List @Query("SELECT * FROM tasks WHERE hideUntil < (strftime('%s','now')*1000)") - abstract fun getVisibleTasks(): List + abstract suspend fun getVisibleTasks(): List @Query("SELECT * FROM tasks WHERE remoteId IN (:remoteIds) " + "AND recurrence IS NOT NULL AND LENGTH(recurrence) > 0") - abstract fun getRecurringTasks(remoteIds: List): List + abstract suspend fun getRecurringTasks(remoteIds: List): List @Query("UPDATE tasks SET completed = :completionDate " + "WHERE remoteId = :remoteId") - abstract fun setCompletionDate(remoteId: String, completionDate: Long) + abstract suspend fun setCompletionDate(remoteId: String, completionDate: Long) @Query("UPDATE tasks SET snoozeTime = :millis WHERE _id in (:taskIds)") - abstract fun snooze(taskIds: List, millis: Long) + abstract suspend fun snooze(taskIds: List, 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 + abstract suspend fun getGoogleTasksToPush(account: String): List @Query(""" SELECT tasks.* @@ -96,39 +92,37 @@ abstract class TaskDao(private val database: Database) { 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 + abstract suspend fun getCaldavTasksToPush(calendar: String): List @Query("SELECT * FROM TASKS " + "WHERE completed = 0 AND deleted = 0 AND (notificationFlags > 0 OR notifications > 0)") - abstract fun getTasksWithReminders(): List + abstract suspend fun getTasksWithReminders(): List // --- SQL clause generators @Query("SELECT * FROM tasks") - abstract fun getAll(): List + abstract suspend fun getAll(): List @Query("SELECT calendarUri FROM tasks " + "WHERE calendarUri IS NOT NULL AND calendarUri != ''") - abstract fun getAllCalendarEvents(): List + abstract suspend fun getAllCalendarEvents(): List @Query("UPDATE tasks SET calendarUri = '' " + "WHERE calendarUri IS NOT NULL AND calendarUri != ''") - abstract fun clearAllCalendarEvents(): Int + abstract suspend fun clearAllCalendarEvents(): Int @Query("SELECT calendarUri FROM tasks " + "WHERE completed > 0 AND calendarUri IS NOT NULL AND calendarUri != ''") - abstract fun getCompletedCalendarEvents(): List + abstract suspend fun getCompletedCalendarEvents(): List @Query("UPDATE tasks SET calendarUri = '' " + "WHERE completed > 0 AND calendarUri IS NOT NULL AND calendarUri != ''") - abstract fun clearCompletedCalendarEvents(): Int + abstract suspend fun clearCompletedCalendarEvents(): Int @Transaction - open fun fetchTasks(callback: (SubtaskInfo) -> List): List { - return runBlocking { - fetchTasks(callback, getSubtaskInfo()) - } + open suspend fun fetchTasks(callback: (SubtaskInfo) -> List): List { + return fetchTasks(callback, getSubtaskInfo()) } @Transaction - open fun fetchTasks(callback: (SubtaskInfo) -> List, subtasks: SubtaskInfo): List { + open suspend fun fetchTasks(callback: (SubtaskInfo) -> List, subtasks: SubtaskInfo): List { assertNotMainThread() val start = if (BuildConfig.DEBUG) DateUtilities.now() else 0 @@ -143,17 +137,17 @@ abstract class TaskDao(private val database: Database) { return result } - fun fetchTasks(preferences: Preferences, filter: Filter): List { + suspend fun fetchTasks(preferences: Preferences, filter: Filter): List { return fetchTasks { TaskListQuery.getQuery(preferences, filter, it) } } @RawQuery - abstract fun fetchTasks(query: SimpleSQLiteQuery): List + abstract suspend fun fetchTasks(query: SimpleSQLiteQuery): List @RawQuery - abstract fun count(query: SimpleSQLiteQuery): Int + abstract suspend fun count(query: SimpleSQLiteQuery): Int @Query(""" SELECT EXISTS(SELECT 1 FROM tasks WHERE parent > 0 AND deleted = 0) AS hasSubtasks, @@ -167,31 +161,30 @@ SELECT EXISTS(SELECT 1 FROM tasks WHERE parent > 0 AND deleted = 0) AS hasSubtas abstract suspend fun getSubtaskInfo(): SubtaskInfo @RawQuery(observedEntities = [Place::class]) - abstract fun getTaskFactory( - query: SimpleSQLiteQuery): DataSource.Factory + abstract fun getTaskFactory(query: SimpleSQLiteQuery): DataSource.Factory - fun touch(id: Long) = touch(listOf(id)) + suspend fun touch(id: Long) = touch(listOf(id)) - fun touch(ids: List) { + suspend fun touch(ids: List) { ids.eachChunk { touchInternal(it) } workManager.sync(false) } @Query("UPDATE tasks SET modified = :now WHERE _id in (:ids)") - abstract fun touchInternal(ids: List, now: Long = currentTimeMillis()) + abstract suspend fun touchInternal(ids: List, now: Long = currentTimeMillis()) - fun setParent(parent: Long, tasks: List) = + suspend fun setParent(parent: Long, tasks: List) = tasks.eachChunk { setParentInternal(parent, it) } @Query("UPDATE tasks SET parent = :parent WHERE _id IN (:children)") - internal abstract fun setParentInternal(parent: Long, children: List) + internal abstract suspend fun setParentInternal(parent: Long, children: List) @Transaction - open fun fetchChildren(id: Long): List { + open suspend fun fetchChildren(id: Long): List { return fetch(getChildren(id)) } - fun getChildren(id: Long): List { + suspend fun getChildren(id: Long): List { return getChildren(listOf(id)) } @@ -207,13 +200,13 @@ SELECT EXISTS(SELECT 1 FROM tasks WHERE parent > 0 AND deleted = 0) AS hasSubtas + " ON recursive_tasks.task = tasks.parent" + " WHERE tasks.deleted = 0)" + "SELECT task FROM recursive_tasks") - abstract fun getChildren(ids: List): List + abstract suspend fun getChildren(ids: List): List @Query("UPDATE tasks SET collapsed = :collapsed WHERE _id = :id") - abstract fun setCollapsed(id: Long, collapsed: Boolean) + abstract suspend fun setCollapsed(id: Long, collapsed: Boolean) @Transaction - open fun setCollapsed(preferences: Preferences, filter: Filter, collapsed: Boolean) { + open suspend fun setCollapsed(preferences: Preferences, filter: Filter, collapsed: Boolean) { fetchTasks(preferences, filter) .filter(TaskContainer::hasChildren) .map(TaskContainer::getId) @@ -221,7 +214,7 @@ SELECT EXISTS(SELECT 1 FROM tasks WHERE parent > 0 AND deleted = 0) AS hasSubtas } @Query("UPDATE tasks SET collapsed = :collapsed WHERE _id IN (:ids)") - abstract fun collapse(ids: List, collapsed: Boolean) + abstract suspend fun collapse(ids: List, collapsed: Boolean) // --- save // TODO: get rid of this super-hack @@ -229,8 +222,10 @@ SELECT EXISTS(SELECT 1 FROM tasks WHERE parent > 0 AND deleted = 0) AS hasSubtas * 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)) { + + suspend fun save(task: Task) = save(task, fetchBlocking(task.id)) + + suspend fun save(task: Task, original: Task?) { if (!task.insignificantChange(original)) { task.modificationDate = DateUtilities.now() } @@ -240,12 +235,12 @@ SELECT EXISTS(SELECT 1 FROM tasks WHERE parent > 0 AND deleted = 0) AS hasSubtas } @Insert - abstract fun insert(task: Task): Long + abstract suspend fun insert(task: Task): Long @Update - abstract fun update(task: Task): Int + abstract suspend fun update(task: Task): Int - fun createNew(task: Task) { + suspend fun createNew(task: Task) { task.id = NO_ID if (task.creationDate == 0L) { task.creationDate = DateUtilities.now() @@ -257,7 +252,7 @@ SELECT EXISTS(SELECT 1 FROM tasks WHERE parent > 0 AND deleted = 0) AS hasSubtas task.id = insert } - fun count(filter: Filter): Int { + suspend 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) @@ -265,11 +260,11 @@ SELECT EXISTS(SELECT 1 FROM tasks WHERE parent > 0 AND deleted = 0) AS hasSubtas return count } - fun fetchFiltered(filter: Filter): List { + suspend fun fetchFiltered(filter: Filter): List { return fetchFiltered(filter.getSqlQuery()) } - fun fetchFiltered(queryTemplate: String): List { + suspend fun fetchFiltered(queryTemplate: String): List { val query = getQuery(queryTemplate, Task.FIELDS) val start = if (BuildConfig.DEBUG) DateUtilities.now() else 0 val tasks = fetchTasks(query) @@ -278,7 +273,7 @@ SELECT EXISTS(SELECT 1 FROM tasks WHERE parent > 0 AND deleted = 0) AS hasSubtas } @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 + abstract suspend fun getLocalTasks(): List /** Generates SQL clauses */ object TaskCriteria { diff --git a/app/src/main/java/org/tasks/data/AlarmDao.kt b/app/src/main/java/org/tasks/data/AlarmDao.kt index 5110fb30a..2c3c5e9d9 100644 --- a/app/src/main/java/org/tasks/data/AlarmDao.kt +++ b/app/src/main/java/org/tasks/data/AlarmDao.kt @@ -10,22 +10,22 @@ interface AlarmDao { @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 + suspend fun getActiveAlarms(): List @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 + suspend fun getActiveAlarms(taskId: Long): List @Query("SELECT * FROM alarms WHERE task = :taskId ORDER BY time ASC") - fun getAlarms(taskId: Long): List + suspend fun getAlarms(taskId: Long): List @Delete - fun delete(alarm: Alarm) + suspend fun delete(alarm: Alarm) @Insert - fun insert(alarm: Alarm): Long + suspend fun insert(alarm: Alarm): Long @Insert - fun insert(alarms: Iterable) + suspend fun insert(alarms: Iterable) } \ No newline at end of file diff --git a/app/src/main/java/org/tasks/data/CaldavDao.kt b/app/src/main/java/org/tasks/data/CaldavDao.kt index cd5a2d553..3fe2b5f74 100644 --- a/app/src/main/java/org/tasks/data/CaldavDao.kt +++ b/app/src/main/java/org/tasks/data/CaldavDao.kt @@ -10,7 +10,7 @@ import com.todoroo.astrid.data.Task import com.todoroo.astrid.helper.UUIDHelper import org.tasks.R import org.tasks.date.DateTimeUtils.toAppleEpoch -import org.tasks.db.DbUtils.chunkedMap +import org.tasks.db.SuspendDbUtils.chunkedMap import org.tasks.filters.CaldavFilters import org.tasks.time.DateTimeUtils.currentTimeMillis @@ -20,41 +20,41 @@ abstract class CaldavDao { abstract fun subscribeToCalendars(): LiveData> @Query("SELECT * FROM caldav_lists WHERE cdl_uuid = :uuid LIMIT 1") - abstract fun getCalendarByUuid(uuid: String): CaldavCalendar? + abstract suspend fun getCalendarByUuid(uuid: String): CaldavCalendar? @Query("SELECT * FROM caldav_lists WHERE cdl_account = :uuid") - abstract fun getCalendarsByAccount(uuid: String): List + abstract suspend fun getCalendarsByAccount(uuid: String): List @Query("SELECT * FROM caldav_accounts WHERE cda_uuid = :uuid LIMIT 1") - abstract fun getAccountByUuid(uuid: String): CaldavAccount? + abstract suspend fun getAccountByUuid(uuid: String): CaldavAccount? @Query("SELECT COUNT(*) FROM caldav_accounts WHERE cda_account_type != 2") - abstract fun accountCount(): Int + abstract suspend fun accountCount(): Int @Query("SELECT * FROM caldav_accounts ORDER BY cda_account_type, UPPER(cda_name)") - abstract fun getAccounts(): List + abstract suspend fun getAccounts(): List @Query("UPDATE caldav_accounts SET cda_collapsed = :collapsed WHERE cda_id = :id") - abstract fun setCollapsed(id: Long, collapsed: Boolean) + abstract suspend fun setCollapsed(id: Long, collapsed: Boolean) @Insert - abstract fun insert(caldavAccount: CaldavAccount): Long + abstract suspend fun insert(caldavAccount: CaldavAccount): Long @Update - abstract fun update(caldavAccount: CaldavAccount) + abstract suspend fun update(caldavAccount: CaldavAccount) - fun insert(caldavCalendar: CaldavCalendar) { + suspend fun insert(caldavCalendar: CaldavCalendar) { caldavCalendar.id = insertInternal(caldavCalendar) } @Insert - abstract fun insertInternal(caldavCalendar: CaldavCalendar): Long + abstract suspend fun insertInternal(caldavCalendar: CaldavCalendar): Long @Update - abstract fun update(caldavCalendar: CaldavCalendar) + abstract suspend fun update(caldavCalendar: CaldavCalendar) @Transaction - open fun insert(task: Task, caldavTask: CaldavTask, addToTop: Boolean): Long { + open suspend fun insert(task: Task, caldavTask: CaldavTask, addToTop: Boolean): Long { if (caldavTask.order != null) { return insert(caldavTask) } @@ -71,104 +71,104 @@ abstract class CaldavDao { } @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? + internal abstract suspend 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? + internal abstract suspend fun findLastTask(calendar: String, parent: Long): Long? @Insert - abstract fun insert(caldavTask: CaldavTask): Long + abstract suspend fun insert(caldavTask: CaldavTask): Long @Insert - abstract fun insert(tasks: Iterable) + abstract suspend fun insert(tasks: Iterable) @Update - abstract fun update(caldavTask: CaldavTask) + abstract suspend fun update(caldavTask: CaldavTask) - fun update(caldavTask: SubsetCaldav) { + suspend 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?) + internal abstract suspend 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?) + internal abstract suspend 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?) + internal abstract suspend fun update(id: Long, remoteParent: String?) @Update - abstract fun update(tasks: Iterable) + abstract suspend fun update(tasks: Iterable) @Delete - abstract fun delete(caldavTask: CaldavTask) + abstract suspend fun delete(caldavTask: CaldavTask) @Query("SELECT * FROM caldav_tasks WHERE cd_deleted > 0 AND cd_calendar = :calendar") - abstract fun getDeleted(calendar: String): List + abstract suspend fun getDeleted(calendar: String): List @Query("UPDATE caldav_tasks SET cd_deleted = :now WHERE cd_task IN (:tasks)") - abstract fun markDeleted(tasks: List, now: Long = currentTimeMillis()) + abstract suspend fun markDeleted(tasks: List, now: Long = currentTimeMillis()) @Query("SELECT * FROM caldav_tasks WHERE cd_task = :taskId AND cd_deleted = 0 LIMIT 1") - abstract fun getTask(taskId: Long): CaldavTask? + abstract suspend 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? + abstract suspend 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? + abstract suspend 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? + abstract suspend fun getTaskByRemoteId(calendar: String, remoteId: String): CaldavTask? @Query("SELECT * FROM caldav_tasks WHERE cd_task = :taskId") - abstract fun getTasks(taskId: Long): List + abstract suspend fun getTasks(taskId: Long): List @Query("SELECT * FROM caldav_tasks WHERE cd_task in (:taskIds) AND cd_deleted = 0") - abstract fun getTasks(taskIds: List): List + abstract suspend fun getTasks(taskIds: List): List @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 + abstract suspend fun getTasks(): List @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 + abstract suspend fun getCaldavTasksToPush(calendar: String): List @Query("SELECT * FROM caldav_lists ORDER BY cdl_name COLLATE NOCASE") - abstract fun getCalendars(): List + abstract suspend fun getCalendars(): List @Query("SELECT * FROM caldav_lists WHERE cdl_uuid = :uuid LIMIT 1") - abstract fun getCalendar(uuid: String): CaldavCalendar? + abstract suspend fun getCalendar(uuid: String): CaldavCalendar? @Query("SELECT cd_object FROM caldav_tasks WHERE cd_calendar = :calendar") - abstract fun getObjects(calendar: String): List + abstract suspend fun getObjects(calendar: String): List - fun getTasks(calendar: String, objects: List): List = + suspend fun getTasks(calendar: String, objects: List): List = 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): List + abstract suspend fun getTasksInternal(calendar: String, objects: List): List @Query("SELECT * FROM caldav_lists WHERE cdl_account = :account AND cdl_url NOT IN (:urls)") - abstract fun findDeletedCalendars(account: String, urls: List): List + abstract suspend fun findDeletedCalendars(account: String, urls: List): List @Query("SELECT * FROM caldav_lists WHERE cdl_account = :account AND cdl_url = :url LIMIT 1") - abstract fun getCalendarByUrl(account: String, url: String): CaldavCalendar? + abstract suspend 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? + abstract suspend 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): List + abstract suspend fun getCalendars(tasks: List): List @Query("SELECT caldav_lists.*, COUNT(tasks._id) AS count" + " FROM caldav_lists" @@ -176,13 +176,13 @@ abstract class CaldavDao { + " 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 + abstract suspend fun getCaldavFilters(uuid: String, now: Long = currentTimeMillis()): List @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 + abstract suspend fun getTasksWithTags(): List @Query("UPDATE tasks SET parent = IFNULL((" + " SELECT p.cd_task FROM caldav_tasks AS p" @@ -191,7 +191,7 @@ abstract class CaldavDao { + " 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() + abstract suspend fun updateParents() @Query("UPDATE tasks SET parent = IFNULL((" + " SELECT p.cd_task FROM caldav_tasks AS p" @@ -202,10 +202,10 @@ abstract class CaldavDao { + " 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) + abstract suspend fun updateParents(calendar: String) @Transaction - open fun move(task: TaskContainer, newParent: Long, newPosition: Long?) { + open suspend fun move(task: TaskContainer, newParent: Long, newPosition: Long?) { val previousParent = task.parent val caldavTask = task.caldavTask val previousPosition = task.caldavSortOrder @@ -221,7 +221,7 @@ abstract class CaldavDao { } @Transaction - open fun shiftDown(calendar: String, parent: Long, from: Long, to: Long? = null) { + open suspend fun shiftDown(calendar: String, parent: Long, from: Long, to: Long? = null) { val updated = ArrayList() val tasks = getTasksToShift(calendar, parent, from, to) for (i in tasks.indices) { @@ -240,32 +240,32 @@ abstract class CaldavDao { } @Query("UPDATE tasks SET modified = :modificationTime WHERE _id in (:ids)") - internal abstract fun touchInternal(ids: List, modificationTime: Long = now()) + internal abstract suspend fun touchInternal(ids: List, modificationTime: Long = now()) @Query("SELECT task.*, caldav_task.*, IFNULL(cd_order, (created - $APPLE_EPOCH) / 1000) AS primary_sort FROM caldav_tasks AS caldav_task INNER JOIN tasks AS task ON _id = cd_task WHERE cd_calendar = :calendar AND parent = :parent AND cd_deleted = 0 AND deleted = 0 AND primary_sort >= :from AND primary_sort < IFNULL(:to, ${Long.MAX_VALUE}) ORDER BY primary_sort") - internal abstract fun getTasksToShift(calendar: String, parent: Long, from: Long, to: Long?): List + internal abstract suspend fun getTasksToShift(calendar: String, parent: Long, from: Long, to: Long?): List @Query("UPDATE caldav_lists SET cdl_order = $NO_ORDER") - abstract fun resetOrders() + abstract suspend fun resetOrders() @Query("UPDATE caldav_lists SET cdl_order = :order WHERE cdl_id = :id") - abstract fun setOrder(id: Long, order: Int) + abstract suspend fun setOrder(id: Long, order: Int) - fun setupLocalAccount(context: Context): CaldavAccount { + suspend fun setupLocalAccount(context: Context): CaldavAccount { val account = getLocalAccount() getLocalList(context, account) return account } - fun getLocalList(context: Context) = getLocalList(context, getLocalAccount()) + suspend fun getLocalList(context: Context) = getLocalList(context, getLocalAccount()) - private fun getLocalAccount() = getAccountByUuid(LOCAL) ?: CaldavAccount().apply { + private suspend fun getLocalAccount() = getAccountByUuid(LOCAL) ?: CaldavAccount().apply { accountType = CaldavAccount.TYPE_LOCAL uuid = LOCAL id = insert(this) } - private fun getLocalList(context: Context, account: CaldavAccount): CaldavCalendar = + private suspend 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 diff --git a/app/src/main/java/org/tasks/data/ContentProviderDao.kt b/app/src/main/java/org/tasks/data/ContentProviderDao.kt index f31aaad3b..8a0b68fed 100644 --- a/app/src/main/java/org/tasks/data/ContentProviderDao.kt +++ b/app/src/main/java/org/tasks/data/ContentProviderDao.kt @@ -10,7 +10,7 @@ import com.todoroo.astrid.data.Task @Dao interface ContentProviderDao { @Query("SELECT name FROM tags WHERE task = :taskId ORDER BY UPPER(name) ASC") - fun getTagNames(taskId: Long): List + suspend fun getTagNames(taskId: Long): List @Query(""" SELECT * @@ -25,10 +25,10 @@ interface ContentProviderDao { 172800000 * importance ASC LIMIT 100""") - fun getAstrid2TaskProviderTasks(): List + suspend fun getAstrid2TaskProviderTasks(): List @Query("SELECT * FROM tagdata WHERE name IS NOT NULL AND name != '' ORDER BY UPPER(name) ASC") - fun tagDataOrderedByName(): List + suspend fun tagDataOrderedByName(): List @Query("SELECT * FROM tasks") fun getTasks(): Cursor diff --git a/app/src/main/java/org/tasks/data/DeletionDao.kt b/app/src/main/java/org/tasks/data/DeletionDao.kt index 69ef648cf..2ef3e9803 100644 --- a/app/src/main/java/org/tasks/data/DeletionDao.kt +++ b/app/src/main/java/org/tasks/data/DeletionDao.kt @@ -5,31 +5,31 @@ 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 org.tasks.db.SuspendDbUtils.eachChunk import java.util.* @Dao abstract class DeletionDao { @Query("DELETE FROM caldav_tasks WHERE cd_task IN(:ids)") - abstract fun deleteCaldavTasks(ids: List) + abstract suspend fun deleteCaldavTasks(ids: List) @Query("DELETE FROM google_tasks WHERE gt_task IN(:ids)") - abstract fun deleteGoogleTasks(ids: List) + abstract suspend fun deleteGoogleTasks(ids: List) @Query("DELETE FROM tags WHERE task IN(:ids)") - abstract fun deleteTags(ids: List) + abstract suspend fun deleteTags(ids: List) @Query("DELETE FROM geofences WHERE task IN(:ids)") - abstract fun deleteGeofences(ids: List) + abstract suspend fun deleteGeofences(ids: List) @Query("DELETE FROM alarms WHERE task IN(:ids)") - abstract fun deleteAlarms(ids: List) + abstract suspend fun deleteAlarms(ids: List) @Query("DELETE FROM tasks WHERE _id IN(:ids)") - abstract fun deleteTasks(ids: List) + abstract suspend fun deleteTasks(ids: List) @Transaction - open fun delete(ids: List) { + open suspend fun delete(ids: List) { ids.eachChunk { deleteAlarms(it) deleteGeofences(it) @@ -43,20 +43,20 @@ abstract class DeletionDao { @Query("UPDATE tasks " + "SET modified = (strftime('%s','now')*1000), deleted = (strftime('%s','now')*1000)" + "WHERE _id IN(:ids)") - abstract fun markDeletedInternal(ids: List) + abstract suspend fun markDeletedInternal(ids: List) - fun markDeleted(ids: Iterable) { + suspend fun markDeleted(ids: Iterable) { 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 + abstract suspend fun getActiveGoogleTasks(listId: String): List @Delete - abstract fun deleteGoogleTaskList(googleTaskList: GoogleTaskList) + abstract suspend fun deleteGoogleTaskList(googleTaskList: GoogleTaskList) @Transaction - open fun delete(googleTaskList: GoogleTaskList): List { + open suspend fun delete(googleTaskList: GoogleTaskList): List { val tasks = getActiveGoogleTasks(googleTaskList.remoteId!!) delete(tasks) deleteGoogleTaskList(googleTaskList) @@ -64,13 +64,13 @@ abstract class DeletionDao { } @Delete - abstract fun deleteGoogleTaskAccount(googleTaskAccount: GoogleTaskAccount) + abstract suspend 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 + abstract suspend fun getLists(account: String): List @Transaction - open fun delete(googleTaskAccount: GoogleTaskAccount): List { + open suspend fun delete(googleTaskAccount: GoogleTaskAccount): List { val deleted = ArrayList() for (list in getLists(googleTaskAccount.account!!)) { deleted.addAll(delete(list)) @@ -80,13 +80,13 @@ abstract class DeletionDao { } @Query("SELECT cd_task FROM caldav_tasks WHERE cd_calendar = :calendar AND cd_deleted = 0") - abstract fun getActiveCaldavTasks(calendar: String): List + abstract suspend fun getActiveCaldavTasks(calendar: String): List @Delete - abstract fun deleteCaldavCalendar(caldavCalendar: CaldavCalendar) + abstract suspend fun deleteCaldavCalendar(caldavCalendar: CaldavCalendar) @Transaction - open fun delete(caldavCalendar: CaldavCalendar): List { + open suspend fun delete(caldavCalendar: CaldavCalendar): List { val tasks = getActiveCaldavTasks(caldavCalendar.uuid!!) delete(tasks) deleteCaldavCalendar(caldavCalendar) @@ -94,16 +94,16 @@ abstract class DeletionDao { } @Query("SELECT * FROM caldav_lists WHERE cdl_account = :account") - abstract fun getCalendars(account: String): List + abstract suspend fun getCalendars(account: String): List @Delete - abstract fun deleteCaldavAccount(caldavAccount: CaldavAccount) + abstract suspend 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() + abstract suspend fun purgeDeleted() @Transaction - open fun delete(caldavAccount: CaldavAccount): List { + open suspend fun delete(caldavAccount: CaldavAccount): List { val deleted = ArrayList() for (calendar in getCalendars(caldavAccount.uuid!!)) { deleted.addAll(delete(calendar)) diff --git a/app/src/main/java/org/tasks/data/FilterDao.kt b/app/src/main/java/org/tasks/data/FilterDao.kt index 02e8c1170..b65b291dd 100644 --- a/app/src/main/java/org/tasks/data/FilterDao.kt +++ b/app/src/main/java/org/tasks/data/FilterDao.kt @@ -9,29 +9,29 @@ import com.todoroo.astrid.api.FilterListItem.NO_ORDER @Dao interface FilterDao { @Update - fun update(filter: Filter) + suspend fun update(filter: Filter) @Query("DELETE FROM filters WHERE _id = :id") - fun delete(id: Long) + suspend fun delete(id: Long) @Query("SELECT * FROM filters WHERE title = :title COLLATE NOCASE LIMIT 1") - fun getByName(title: String): Filter? + suspend fun getByName(title: String): Filter? @Insert - fun insert(filter: Filter): Long + suspend fun insert(filter: Filter): Long @Query("SELECT * FROM filters") - fun getFilters(): List + suspend fun getFilters(): List @Query("SELECT * FROM filters WHERE _id = :id LIMIT 1") - fun getById(id: Long): Filter? + suspend fun getById(id: Long): Filter? @Query("SELECT * FROM filters") - fun getAll(): List + suspend fun getAll(): List @Query("UPDATE filters SET f_order = $NO_ORDER") - fun resetOrders() + suspend fun resetOrders() @Query("UPDATE filters SET f_order = :order WHERE _id = :id") - fun setOrder(id: Long, order: Int) + suspend fun setOrder(id: Long, order: Int) } \ No newline at end of file diff --git a/app/src/main/java/org/tasks/data/GoogleTaskDao.kt b/app/src/main/java/org/tasks/data/GoogleTaskDao.kt index 0f1a69da6..f35f7d536 100644 --- a/app/src/main/java/org/tasks/data/GoogleTaskDao.kt +++ b/app/src/main/java/org/tasks/data/GoogleTaskDao.kt @@ -7,13 +7,13 @@ import org.tasks.time.DateTimeUtils.currentTimeMillis @Dao abstract class GoogleTaskDao { @Insert - abstract fun insert(task: GoogleTask): Long + abstract suspend fun insert(task: GoogleTask): Long @Insert - abstract fun insert(tasks: Iterable) + abstract suspend fun insert(tasks: Iterable) @Transaction - open fun insertAndShift(task: GoogleTask, top: Boolean) { + open suspend fun insertAndShift(task: GoogleTask, top: Boolean) { if (top) { task.order = 0 shiftDown(task.listId!!, task.parent, 0) @@ -24,19 +24,19 @@ abstract class GoogleTaskDao { } @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) + abstract suspend 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) + abstract suspend 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) + abstract suspend 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) + abstract suspend fun shiftUp(listId: String, parent: Long, position: Long) @Transaction - open fun move(task: SubsetGoogleTask, newParent: Long, newPosition: Long) { + open suspend fun move(task: SubsetGoogleTask, newParent: Long, newPosition: Long) { val previousParent = task.parent val previousPosition = task.order if (newParent == previousParent) { @@ -55,67 +55,67 @@ abstract class GoogleTaskDao { } @Query("UPDATE google_task_accounts SET gta_collapsed = :collapsed WHERE gta_id = :id") - abstract fun setCollapsed(id: Long, collapsed: Boolean) + abstract suspend 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? + abstract suspend fun getByTaskId(taskId: Long): GoogleTask? @Update - abstract fun update(googleTask: GoogleTask) + abstract suspend fun update(googleTask: GoogleTask) - private fun update(googleTask: SubsetGoogleTask) { + private suspend 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) + abstract suspend 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()) + abstract suspend fun markDeleted(task: Long, now: Long = currentTimeMillis()) @Delete - abstract fun delete(deleted: GoogleTask) + abstract suspend fun delete(deleted: GoogleTask) @Query("SELECT * FROM google_tasks WHERE gt_remote_id = :remoteId LIMIT 1") - abstract fun getByRemoteId(remoteId: String): GoogleTask? + abstract suspend fun getByRemoteId(remoteId: String): GoogleTask? @Query("SELECT * FROM google_tasks WHERE gt_task = :taskId AND gt_deleted > 0") - abstract fun getDeletedByTaskId(taskId: Long): List + abstract suspend fun getDeletedByTaskId(taskId: Long): List @Query("SELECT * FROM google_tasks WHERE gt_task = :taskId") - abstract fun getAllByTaskId(taskId: Long): List + abstract suspend fun getAllByTaskId(taskId: Long): List @Query("SELECT DISTINCT gt_list_id FROM google_tasks WHERE gt_deleted = 0 AND gt_task IN (:tasks)") - abstract fun getLists(tasks: List): List + abstract suspend fun getLists(tasks: List): List @Query("SELECT gt_task FROM google_tasks WHERE gt_parent IN (:ids) AND gt_deleted = 0") - abstract fun getChildren(ids: List): List + abstract suspend fun getChildren(ids: List): List @Query("SELECT tasks.* FROM tasks JOIN google_tasks ON tasks._id = gt_task WHERE gt_parent = :taskId") - abstract fun getChildTasks(taskId: Long): List + abstract suspend fun getChildTasks(taskId: Long): List @Query("SELECT * FROM google_tasks WHERE gt_parent = :id AND gt_deleted = 0") - abstract fun getChildren(id: Long): List + abstract suspend fun getChildren(id: Long): List @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 + abstract suspend 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? + abstract suspend 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? + abstract suspend fun getRemoteId(task: Long): String? @Query("SELECT gt_task FROM google_tasks WHERE gt_remote_id = :remoteId") - abstract fun getTask(remoteId: String): Long + abstract suspend 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 + abstract suspend fun getByLocalOrder(listId: String): List @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 + abstract suspend fun getByRemoteOrder(listId: String): List @Query("UPDATE google_tasks" + " SET gt_parent = IFNULL((" @@ -125,16 +125,16 @@ abstract class GoogleTaskDao { + " AND p.gt_deleted = 0)," + " 0)" + " WHERE gt_moved = 0") - abstract fun updateParents() + abstract suspend 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) + abstract suspend 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) + abstract suspend fun updatePosition(id: String, parent: String, position: String) @Transaction - open fun reposition(listId: String) { + open suspend fun reposition(listId: String) { updateParents(listId) val orderedTasks = getByRemoteOrder(listId) var subtasks = 0L @@ -157,7 +157,7 @@ abstract class GoogleTaskDao { } } - fun validateSorting(listId: String) { + suspend fun validateSorting(listId: String) { val orderedTasks = getByLocalOrder(listId) var subtasks = 0L var parent = 0L diff --git a/app/src/main/java/org/tasks/data/GoogleTaskListDao.kt b/app/src/main/java/org/tasks/data/GoogleTaskListDao.kt index cffc12887..5344a5579 100644 --- a/app/src/main/java/org/tasks/data/GoogleTaskListDao.kt +++ b/app/src/main/java/org/tasks/data/GoogleTaskListDao.kt @@ -9,52 +9,52 @@ import org.tasks.time.DateTimeUtils.currentTimeMillis @Dao interface GoogleTaskListDao { @Query("SELECT COUNT(*) FROM google_task_accounts") - fun accountCount(): Int + suspend fun accountCount(): Int @Query("SELECT * FROM google_task_accounts") - fun getAccounts(): List + suspend fun getAccounts(): List @Query("SELECT * FROM google_task_accounts WHERE gta_account = :account COLLATE NOCASE LIMIT 1") - fun getAccount(account: String): GoogleTaskAccount? + suspend fun getAccount(account: String): GoogleTaskAccount? @Query("SELECT * FROM google_task_lists WHERE gtl_id = :id") - fun getById(id: Long): GoogleTaskList? + suspend 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 + suspend fun getLists(account: String): List @Query("SELECT * FROM google_task_lists WHERE gtl_remote_id = :remoteId LIMIT 1") - fun getByRemoteId(remoteId: String): GoogleTaskList? + suspend fun getByRemoteId(remoteId: String): GoogleTaskList? @Query("SELECT * FROM google_task_lists WHERE gtl_remote_id IN (:remoteIds)") - fun getByRemoteId(remoteIds: List): List + suspend fun getByRemoteId(remoteIds: List): List @Query("SELECT * FROM google_task_lists") fun subscribeToLists(): LiveData> @Query("SELECT * FROM google_task_lists WHERE gtl_remote_id = :remoteId AND IFNULL(gtl_account, '') = ''") - fun findExistingList(remoteId: String): GoogleTaskList? + suspend fun findExistingList(remoteId: String): GoogleTaskList? @Query("SELECT * FROM google_task_lists") - fun getAllLists(): List + suspend fun getAllLists(): List @Query("UPDATE google_task_lists SET gtl_last_sync = 0 WHERE gtl_account = :account") - fun resetLastSync(account: String) + suspend fun resetLastSync(account: String) @Insert(onConflict = OnConflictStrategy.REPLACE) - fun insertOrReplace(googleTaskList: GoogleTaskList): Long + suspend fun insertOrReplace(googleTaskList: GoogleTaskList): Long @Insert - fun insert(googleTaskList: GoogleTaskList): Long + suspend fun insert(googleTaskList: GoogleTaskList): Long @Insert - fun insert(googleTaskAccount: GoogleTaskAccount) + suspend fun insert(googleTaskAccount: GoogleTaskAccount) @Update - fun update(account: GoogleTaskAccount) + suspend fun update(account: GoogleTaskAccount) @Update - fun update(list: GoogleTaskList) + suspend fun update(list: GoogleTaskList) @Query("SELECT google_task_lists.*, COUNT(tasks._id) AS count" + " FROM google_task_lists " @@ -62,11 +62,11 @@ interface GoogleTaskListDao { + " 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 + suspend fun getGoogleTaskFilters(account: String, now: Long = currentTimeMillis()): List @Query("UPDATE google_task_lists SET gtl_remote_order = $NO_ORDER") - fun resetOrders() + suspend fun resetOrders() @Query("UPDATE google_task_lists SET gtl_remote_order = :order WHERE gtl_id = :id") - fun setOrder(id: Long, order: Int) + suspend fun setOrder(id: Long, order: Int) } \ No newline at end of file diff --git a/app/src/main/java/org/tasks/data/LocationDao.kt b/app/src/main/java/org/tasks/data/LocationDao.kt index a7ef18ad6..760bc653a 100644 --- a/app/src/main/java/org/tasks/data/LocationDao.kt +++ b/app/src/main/java/org/tasks/data/LocationDao.kt @@ -15,7 +15,7 @@ interface LocationDao { + " WHERE tasks.completed = 0 AND tasks.deleted = 0" + " AND (geofences.arrival > 0 OR geofences.departure > 0)" + " GROUP BY places.uid") - fun getPlacesWithGeofences(): List + suspend fun getPlacesWithGeofences(): List @Query("SELECT places.*," + " max(geofences.arrival) as arrival," @@ -27,92 +27,92 @@ interface LocationDao { + " 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? + suspend fun getGeofencesByPlace(uid: String): MergedGeofence? @Query("DELETE FROM geofences WHERE place = :place") - fun deleteGeofencesByPlace(place: String) + suspend 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 + suspend fun getArrivalGeofences(place: String, now: Long): List @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 + suspend fun getDepartureGeofences(place: String, now: Long): List @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? + suspend 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 + suspend fun getActiveGeofences(taskId: Long): List @Query("SELECT places.*" + " FROM places" + " INNER JOIN geofences ON geofences.place = places.uid" + " WHERE geofences.task = :taskId") - fun getPlaceForTask(taskId: Long): Place? + suspend 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 + suspend fun getActiveGeofences(): List @Query("SELECT COUNT(*) FROM geofences") suspend fun geofenceCount(): Int @Delete - fun delete(location: Geofence) + suspend fun delete(location: Geofence) @Delete - fun delete(place: Place) + suspend fun delete(place: Place) @Insert - fun insert(location: Geofence): Long + suspend fun insert(location: Geofence): Long @Insert(onConflict = OnConflictStrategy.IGNORE) - fun insert(place: Place): Long + suspend fun insert(place: Place): Long @Update - fun update(place: Place) + suspend fun update(place: Place) @Update - fun update(geofence: Geofence) + suspend fun update(geofence: Geofence) @Query("SELECT * FROM places WHERE uid = :uid LIMIT 1") - fun getByUid(uid: String): Place? + suspend fun getByUid(uid: String): Place? @Query("SELECT * FROM geofences WHERE task = :taskId") - fun getGeofencesForTask(taskId: Long): List + suspend fun getGeofencesForTask(taskId: Long): List @Query("SELECT * FROM places") - fun getPlaces(): List + suspend fun getPlaces(): List @Query("SELECT * FROM places WHERE place_id = :id") - fun getPlace(id: Long): Place? + suspend fun getPlace(id: Long): Place? @Query("SELECT * FROM places WHERE uid = :uid") - fun getPlace(uid: String): Place? + suspend 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> @Query("SELECT * FROM places WHERE latitude LIKE :latitude AND longitude LIKE :longitude") - fun findPlace(latitude: String, longitude: String): Place? + suspend 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 + suspend fun getPlaceFilters(now: Long = currentTimeMillis()): List @Query("UPDATE places SET place_order = $NO_ORDER") - fun resetOrders() + suspend fun resetOrders() @Query("UPDATE places SET place_order = :order WHERE place_id = :id") - fun setOrder(id: Long, order: Int) + suspend fun setOrder(id: Long, order: Int) } \ No newline at end of file diff --git a/app/src/main/java/org/tasks/data/TagDao.kt b/app/src/main/java/org/tasks/data/TagDao.kt index ca9dc57c9..37d6705e2 100644 --- a/app/src/main/java/org/tasks/data/TagDao.kt +++ b/app/src/main/java/org/tasks/data/TagDao.kt @@ -6,31 +6,31 @@ import com.todoroo.astrid.data.Task @Dao abstract class TagDao { @Query("UPDATE tags SET name = :name WHERE tag_uid = :tagUid") - abstract fun rename(tagUid: String, name: String) + abstract suspend fun rename(tagUid: String, name: String) @Insert - abstract fun insert(tag: Tag) + abstract suspend fun insert(tag: Tag) @Insert - abstract fun insert(tags: Iterable) + abstract suspend fun insert(tags: Iterable) @Query("DELETE FROM tags WHERE task = :taskId AND tag_uid in (:tagUids)") - abstract fun deleteTags(taskId: Long, tagUids: List) + abstract suspend fun deleteTags(taskId: Long, tagUids: List) @Query("SELECT * FROM tags WHERE tag_uid = :tagUid") - abstract fun getByTagUid(tagUid: String): List + abstract suspend fun getByTagUid(tagUid: String): List @Query("SELECT * FROM tags WHERE task = :taskId") - abstract fun getTagsForTask(taskId: Long): List + abstract suspend fun getTagsForTask(taskId: Long): List @Query("SELECT * FROM tags WHERE task = :taskId AND tag_uid = :tagUid") - abstract fun getTagByTaskAndTagUid(taskId: Long, tagUid: String): Tag? + abstract suspend fun getTagByTaskAndTagUid(taskId: Long, tagUid: String): Tag? @Delete - abstract fun delete(tags: List) + abstract suspend fun delete(tags: List) @Transaction - open fun applyTags(task: Task, tagDataDao: TagDataDaoBlocking, current: List): Boolean { + open suspend fun applyTags(task: Task, tagDataDao: TagDataDaoBlocking, current: List): Boolean { val taskId = task.id val existing = HashSet(tagDataDao.getTagDataForTask(taskId)) val selected = HashSet(current) @@ -41,7 +41,7 @@ abstract class TagDao { return removed.isNotEmpty() || added.isNotEmpty() } - fun insert(task: Task, tags: Collection) { + suspend fun insert(task: Task, tags: Collection) { if (!tags.isEmpty()) { insert(tags.map { Tag(task, it) }) } diff --git a/app/src/main/java/org/tasks/data/TagDataDao.kt b/app/src/main/java/org/tasks/data/TagDataDao.kt index fd3a2d553..a90e9012a 100644 --- a/app/src/main/java/org/tasks/data/TagDataDao.kt +++ b/app/src/main/java/org/tasks/data/TagDataDao.kt @@ -19,47 +19,47 @@ abstract class TagDataDao { abstract fun subscribeToTags(): LiveData> @Query("SELECT * FROM tagdata WHERE name = :name COLLATE NOCASE LIMIT 1") - abstract fun getTagByName(name: String): TagData? + abstract suspend 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? { + suspend fun getTagWithCase(tag: String): String? { return getTagByName(tag)?.name ?: tag } - fun searchTags(query: String): List { + suspend fun searchTags(query: String): List { 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 + protected abstract suspend fun searchTagsInternal(query: String): List @Query("SELECT * FROM tagdata") - abstract fun getAll(): List + abstract suspend fun getAll(): List @Query("SELECT * FROM tagdata WHERE remoteId = :uuid LIMIT 1") - abstract fun getByUuid(uuid: String): TagData? + abstract suspend fun getByUuid(uuid: String): TagData? @Query("SELECT * FROM tagdata WHERE remoteId IN (:uuids)") - abstract fun getByUuid(uuids: Collection): List + abstract suspend fun getByUuid(uuids: Collection): List @Query("SELECT * FROM tagdata WHERE name IS NOT NULL AND name != '' ORDER BY UPPER(name) ASC") - abstract fun tagDataOrderedByName(): List + abstract suspend fun tagDataOrderedByName(): List @Delete - abstract fun deleteTagData(tagData: TagData) + abstract suspend fun deleteTagData(tagData: TagData) @Query("DELETE FROM tags WHERE tag_uid = :tagUid") - abstract fun deleteTags(tagUid: String) + abstract suspend fun deleteTags(tagUid: String) @Query("SELECT * FROM tags WHERE task IN (:tasks) AND tag_uid NOT IN (:tagsToKeep)") - abstract fun tagsToDelete(tasks: List, tagsToKeep: List): List + abstract suspend fun tagsToDelete(tasks: List, tagsToKeep: List): List - fun getTagSelections(tasks: List): Pair, Set> { + suspend fun getTagSelections(tasks: List): Pair, Set> { val allTags = getAllTags(tasks) val tags = allTags.map { t: String? -> HashSet(t?.split(",") ?: emptySet()) } val partialTags = tags.flatten().toMutableSet() @@ -83,10 +83,10 @@ abstract class TagDataDao { + " LEFT JOIN tags ON tags.task = tasks._id" + " WHERE tasks._id IN (:tasks)" + " GROUP BY tasks._id") - abstract fun getAllTags(tasks: List): List + abstract suspend fun getAllTags(tasks: List): List @Transaction - open fun applyTags( + open suspend fun applyTags( tasks: List, partiallySelected: List, selected: List): List { val modified = HashSet() val keep = partiallySelected.plus(selected).map { it.remoteId!! } @@ -106,36 +106,36 @@ abstract class TagDataDao { } @Transaction - open fun delete(tagData: TagData) { + open suspend fun delete(tagData: TagData) { deleteTags(tagData.remoteId!!) deleteTagData(tagData) } @Delete - abstract fun delete(tagData: List) + abstract suspend fun delete(tagData: List) @Delete - abstract fun deleteTags(tags: List) + abstract suspend fun deleteTags(tags: List) @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 + abstract suspend fun getTagDataForTask(id: Long): List @Query("SELECT * FROM tagdata WHERE name IN (:names)") - abstract fun getTags(names: List): List + abstract suspend fun getTags(names: List): List @Update - abstract fun update(tagData: TagData) + abstract suspend fun update(tagData: TagData) @Insert - abstract fun insert(tag: TagData): Long + abstract suspend fun insert(tag: TagData): Long @Insert - abstract fun insert(tags: Iterable) + abstract suspend fun insert(tags: Iterable) - fun createNew(tag: TagData) { + suspend fun createNew(tag: TagData) { if (Task.isUuidEmpty(tag.remoteId)) { tag.remoteId = UUIDHelper.newUUID() } @@ -148,11 +148,11 @@ abstract class TagDataDao { + " 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 + abstract suspend fun getTagFilters(now: Long = currentTimeMillis()): List @Query("UPDATE tagdata SET td_order = $NO_ORDER") - abstract fun resetOrders() + abstract suspend fun resetOrders() @Query("UPDATE tagdata SET td_order = :order WHERE _id = :id") - abstract fun setOrder(id: Long, order: Int) + abstract suspend fun setOrder(id: Long, order: Int) } \ No newline at end of file diff --git a/app/src/main/java/org/tasks/data/TaskAttachmentDao.kt b/app/src/main/java/org/tasks/data/TaskAttachmentDao.kt index 67fbfa8a9..69f3c4296 100644 --- a/app/src/main/java/org/tasks/data/TaskAttachmentDao.kt +++ b/app/src/main/java/org/tasks/data/TaskAttachmentDao.kt @@ -7,24 +7,24 @@ import com.todoroo.astrid.helper.UUIDHelper @Dao abstract class TaskAttachmentDao { @Query("SELECT * FROM task_attachments WHERE task_id = :taskUuid") - abstract fun getAttachments(taskUuid: String): List + abstract suspend fun getAttachments(taskUuid: String): List @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 + abstract suspend fun getAttachments(task: Long): List @Query("SELECT * FROM task_attachments") - abstract fun getAttachments(): List + abstract suspend fun getAttachments(): List @Delete - abstract fun delete(taskAttachment: TaskAttachment) + abstract suspend fun delete(taskAttachment: TaskAttachment) @Insert(onConflict = OnConflictStrategy.REPLACE) - abstract fun insert(attachment: TaskAttachment) + abstract suspend fun insert(attachment: TaskAttachment) @Update - abstract fun update(attachment: TaskAttachment) + abstract suspend fun update(attachment: TaskAttachment) - fun createNew(attachment: TaskAttachment) { + suspend fun createNew(attachment: TaskAttachment) { if (Task.isUuidEmpty(attachment.remoteId)) { attachment.remoteId = UUIDHelper.newUUID() } diff --git a/app/src/main/java/org/tasks/data/TaskListMetadataDao.kt b/app/src/main/java/org/tasks/data/TaskListMetadataDao.kt index e65d78ce5..5a4fb580e 100644 --- a/app/src/main/java/org/tasks/data/TaskListMetadataDao.kt +++ b/app/src/main/java/org/tasks/data/TaskListMetadataDao.kt @@ -8,18 +8,18 @@ import androidx.room.Update @Dao abstract class TaskListMetadataDao { @Query("SELECT * from task_list_metadata where tag_uuid = :tagUuid OR filter = :tagUuid LIMIT 1") - abstract fun fetchByTagOrFilter(tagUuid: String): TaskListMetadata? + abstract suspend fun fetchByTagOrFilter(tagUuid: String): TaskListMetadata? @Query("SELECT * FROM task_list_metadata") - abstract fun getAll(): List + abstract suspend fun getAll(): List @Update - abstract fun update(taskListMetadata: TaskListMetadata) + abstract suspend fun update(taskListMetadata: TaskListMetadata) @Insert - abstract fun insert(taskListMetadata: TaskListMetadata): Long + abstract suspend fun insert(taskListMetadata: TaskListMetadata): Long - fun createNew(taskListMetadata: TaskListMetadata) { + suspend fun createNew(taskListMetadata: TaskListMetadata) { taskListMetadata.id = insert(taskListMetadata) } } \ No newline at end of file diff --git a/app/src/main/java/org/tasks/data/UserActivityDao.kt b/app/src/main/java/org/tasks/data/UserActivityDao.kt index d497694d1..b5cdedd55 100644 --- a/app/src/main/java/org/tasks/data/UserActivityDao.kt +++ b/app/src/main/java/org/tasks/data/UserActivityDao.kt @@ -8,24 +8,24 @@ import com.todoroo.astrid.helper.UUIDHelper @Dao abstract class UserActivityDao { @Insert - abstract fun insert(userActivity: UserActivity) + abstract suspend fun insert(userActivity: UserActivity) @Update - abstract fun update(userActivity: UserActivity) + abstract suspend fun update(userActivity: UserActivity) @Delete - abstract fun delete(userActivity: UserActivity) + abstract suspend fun delete(userActivity: UserActivity) @Query("SELECT * FROM userActivity WHERE target_id = :taskUuid ORDER BY created_at DESC ") - abstract fun getCommentsForTask(taskUuid: String): List + abstract suspend fun getCommentsForTask(taskUuid: String): List @Query("SELECT userActivity.* FROM userActivity INNER JOIN tasks ON tasks._id = :task WHERE target_id = tasks.remoteId") - abstract fun getComments(task: Long): List + abstract suspend fun getComments(task: Long): List @Query("SELECT * FROM userActivity") - abstract fun getComments(): List + abstract suspend fun getComments(): List - fun createNew(item: UserActivity) { + suspend fun createNew(item: UserActivity) { if (item.created == null || item.created == 0L) { item.created = DateUtilities.now() } diff --git a/app/src/main/java/org/tasks/db/SuspendDbUtils.kt b/app/src/main/java/org/tasks/db/SuspendDbUtils.kt new file mode 100644 index 000000000..4119fb4a6 --- /dev/null +++ b/app/src/main/java/org/tasks/db/SuspendDbUtils.kt @@ -0,0 +1,11 @@ +package org.tasks.db + +import org.tasks.db.DbUtils.dbchunk + +object SuspendDbUtils { + suspend fun Iterable.eachChunk(action: suspend (List) -> Unit) = + dbchunk().forEach { action.invoke(it) } + + suspend fun Iterable.chunkedMap(transform: suspend (List) -> Iterable): List = + dbchunk().flatMap { transform.invoke(it) } +} \ No newline at end of file diff --git a/app/src/main/java/org/tasks/notifications/NotificationDao.kt b/app/src/main/java/org/tasks/notifications/NotificationDao.kt index 1dfcf0def..e88b0b252 100644 --- a/app/src/main/java/org/tasks/notifications/NotificationDao.kt +++ b/app/src/main/java/org/tasks/notifications/NotificationDao.kt @@ -8,20 +8,20 @@ import androidx.room.Query @Dao interface NotificationDao { @Query("SELECT task FROM notification") - fun getAll(): List + suspend fun getAll(): List @Query("SELECT * FROM notification ORDER BY timestamp DESC") - fun getAllOrdered(): List + suspend fun getAllOrdered(): List @Insert(onConflict = OnConflictStrategy.REPLACE) - fun insertAll(notifications: List) + suspend fun insertAll(notifications: List) @Query("DELETE FROM notification WHERE task = :taskId") - fun delete(taskId: Long) + suspend fun delete(taskId: Long) @Query("DELETE FROM notification WHERE task IN(:taskIds)") - fun deleteAll(taskIds: List) + suspend fun deleteAll(taskIds: List) @Query("SELECT MAX(timestamp) FROM notification") - fun latestTimestamp(): Long + suspend fun latestTimestamp(): Long } \ No newline at end of file