Legacy TaskDao delegates to new TaskDao

pull/1055/head
Alex Baker 4 years ago
parent 8710dab252
commit 9559d90c25

@ -9,7 +9,6 @@ import dagger.hilt.android.testing.UninstallModules
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test import org.junit.Test
import org.tasks.data.CaldavCalendar import org.tasks.data.CaldavCalendar
import org.tasks.data.CaldavDao import org.tasks.data.CaldavDao
@ -43,12 +42,6 @@ class TaskMoverTest : InjectingTestCase() {
@Inject lateinit var caldavDao: CaldavDao @Inject lateinit var caldavDao: CaldavDao
@Inject lateinit var taskMover: TaskMover @Inject lateinit var taskMover: TaskMover
@Before
override fun setUp() {
super.setUp()
taskDaoAsync.initialize(workManager)
}
@Test @Test
fun moveBetweenGoogleTaskLists() = runBlocking { fun moveBetweenGoogleTaskLists() = runBlocking {
createTasks(1) createTasks(1)

@ -4,6 +4,7 @@ import androidx.room.Database
import androidx.room.RoomDatabase import androidx.room.RoomDatabase
import com.todoroo.astrid.data.Task import com.todoroo.astrid.data.Task
import org.tasks.data.* import org.tasks.data.*
import org.tasks.data.TaskDao
import org.tasks.notifications.Notification import org.tasks.notifications.Notification
import org.tasks.notifications.NotificationDao import org.tasks.notifications.NotificationDao

@ -6,204 +6,107 @@
package com.todoroo.astrid.dao package com.todoroo.astrid.dao
import androidx.paging.DataSource import androidx.paging.DataSource
import androidx.room.*
import androidx.sqlite.db.SimpleSQLiteQuery import androidx.sqlite.db.SimpleSQLiteQuery
import com.todoroo.andlib.sql.Criterion import com.todoroo.andlib.sql.Criterion
import com.todoroo.andlib.sql.Field
import com.todoroo.andlib.sql.Functions import com.todoroo.andlib.sql.Functions
import com.todoroo.andlib.utility.DateUtilities import com.todoroo.andlib.utility.DateUtilities
import com.todoroo.astrid.api.Filter import com.todoroo.astrid.api.Filter
import com.todoroo.astrid.api.PermaSql
import com.todoroo.astrid.data.Task import com.todoroo.astrid.data.Task
import com.todoroo.astrid.data.Task.Companion.NO_ID
import com.todoroo.astrid.helper.UUIDHelper
import org.tasks.BuildConfig
import org.tasks.data.Place
import org.tasks.data.SubtaskInfo import org.tasks.data.SubtaskInfo
import org.tasks.data.TaskContainer import org.tasks.data.TaskContainer
import org.tasks.data.TaskListQuery import org.tasks.data.TaskDao
import org.tasks.db.SuspendDbUtils.chunkedMap
import org.tasks.db.SuspendDbUtils.eachChunk import org.tasks.db.SuspendDbUtils.eachChunk
import org.tasks.jobs.WorkManager import org.tasks.jobs.WorkManager
import org.tasks.preferences.Preferences import org.tasks.preferences.Preferences
import org.tasks.time.DateTimeUtils.currentTimeMillis import javax.inject.Inject
import timber.log.Timber
@Dao class TaskDao @Inject constructor(
abstract class TaskDao(private val database: Database) { private val workManager: WorkManager,
private lateinit var workManager: WorkManager private val taskDao: TaskDao) {
fun initialize(workManager: WorkManager) { internal suspend fun needsRefresh(now: Long = DateUtilities.now()): List<Task> =
this.workManager = workManager taskDao.needsRefresh(now)
}
@Query("SELECT * FROM tasks WHERE completed = 0 AND deleted = 0 AND (hideUntil > :now OR dueDate > :now)")
internal abstract suspend fun needsRefresh(now: Long = DateUtilities.now()): List<Task>
@Query("SELECT * FROM tasks WHERE _id = :id LIMIT 1")
abstract suspend fun fetch(id: Long): Task?
suspend fun fetch(ids: List<Long>): List<Task> = ids.chunkedMap(this::fetchInternal) suspend fun fetch(id: Long): Task? = taskDao.fetch(id)
@Query("SELECT * FROM tasks WHERE _id IN (:ids)") suspend fun fetch(ids: List<Long>): List<Task> = taskDao.fetch(ids)
internal abstract suspend fun fetchInternal(ids: List<Long>): List<Task>
@Query("SELECT COUNT(1) FROM tasks WHERE timerStart > 0 AND deleted = 0") suspend fun activeTimers(): Int = taskDao.activeTimers()
abstract suspend fun activeTimers(): Int
@Query("SELECT tasks.* FROM tasks INNER JOIN notification ON tasks._id = notification.task") suspend fun activeNotifications(): List<Task> = taskDao.activeNotifications()
abstract suspend fun activeNotifications(): List<Task>
@Query("SELECT * FROM tasks WHERE remoteId = :remoteId") suspend fun fetch(remoteId: String): Task? = taskDao.fetch(remoteId)
abstract suspend fun fetch(remoteId: String): Task?
@Query("SELECT * FROM tasks WHERE completed = 0 AND deleted = 0") suspend fun getActiveTasks(): List<Task> = taskDao.getActiveTasks()
abstract suspend fun getActiveTasks(): List<Task>
@Query("SELECT * FROM tasks WHERE remoteId IN (:remoteIds) " suspend fun getRecurringTasks(remoteIds: List<String>): List<Task> =
+ "AND recurrence IS NOT NULL AND LENGTH(recurrence) > 0") taskDao.getRecurringTasks(remoteIds)
abstract suspend fun getRecurringTasks(remoteIds: List<String>): List<Task>
@Query("UPDATE tasks SET completed = :completionDate " + "WHERE remoteId = :remoteId") suspend fun setCompletionDate(remoteId: String, completionDate: Long) =
abstract suspend fun setCompletionDate(remoteId: String, completionDate: Long) taskDao.setCompletionDate(remoteId, completionDate)
@Query("UPDATE tasks SET snoozeTime = :millis WHERE _id in (:taskIds)") suspend fun snooze(taskIds: List<Long>, millis: Long) = taskDao.snooze(taskIds, millis)
abstract suspend fun snooze(taskIds: List<Long>, millis: Long)
@Query("SELECT tasks.* FROM tasks " suspend fun getGoogleTasksToPush(account: String): List<Task> =
+ "LEFT JOIN google_tasks ON tasks._id = google_tasks.gt_task " taskDao.getGoogleTasksToPush(account)
+ "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 suspend fun getGoogleTasksToPush(account: String): List<Task>
@Query(""" suspend fun getCaldavTasksToPush(calendar: String): List<Task> =
SELECT tasks.* taskDao.getCaldavTasksToPush(calendar)
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 suspend fun getCaldavTasksToPush(calendar: String): List<Task>
@Query("SELECT * FROM TASKS " suspend fun getTasksWithReminders(): List<Task> = taskDao.getTasksWithReminders()
+ "WHERE completed = 0 AND deleted = 0 AND (notificationFlags > 0 OR notifications > 0)")
abstract suspend fun getTasksWithReminders(): List<Task>
// --- SQL clause generators suspend fun getAll(): List<Task> = taskDao.getAll()
@Query("SELECT * FROM tasks")
abstract suspend fun getAll(): List<Task>
@Query("SELECT calendarUri FROM tasks " + "WHERE calendarUri IS NOT NULL AND calendarUri != ''") suspend fun getAllCalendarEvents(): List<String> = taskDao.getAllCalendarEvents()
abstract suspend fun getAllCalendarEvents(): List<String>
@Query("UPDATE tasks SET calendarUri = '' " + "WHERE calendarUri IS NOT NULL AND calendarUri != ''") suspend fun clearAllCalendarEvents(): Int = taskDao.clearAllCalendarEvents()
abstract suspend fun clearAllCalendarEvents(): Int
@Query("SELECT calendarUri FROM tasks " suspend fun getCompletedCalendarEvents(): List<String> = taskDao.getCompletedCalendarEvents()
+ "WHERE completed > 0 AND calendarUri IS NOT NULL AND calendarUri != ''")
abstract suspend fun getCompletedCalendarEvents(): List<String>
@Query("UPDATE tasks SET calendarUri = '' " suspend fun clearCompletedCalendarEvents(): Int = taskDao.clearCompletedCalendarEvents()
+ "WHERE completed > 0 AND calendarUri IS NOT NULL AND calendarUri != ''")
abstract suspend fun clearCompletedCalendarEvents(): Int
@Transaction suspend fun fetchTasks(callback: suspend (SubtaskInfo) -> List<String>): List<TaskContainer> =
open suspend fun fetchTasks(callback: suspend (SubtaskInfo) -> List<String>): List<TaskContainer> { taskDao.fetchTasks(callback)
return fetchTasks(callback, getSubtaskInfo())
}
@Transaction suspend fun fetchTasks(callback: suspend (SubtaskInfo) -> List<String>, subtasks: SubtaskInfo): List<TaskContainer> =
open suspend fun fetchTasks(callback: suspend (SubtaskInfo) -> List<String>, subtasks: SubtaskInfo): List<TaskContainer> { taskDao.fetchTasks(callback, subtasks)
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
}
suspend fun fetchTasks(preferences: Preferences, filter: Filter): List<TaskContainer> { suspend fun fetchTasks(preferences: Preferences, filter: Filter): List<TaskContainer> =
return fetchTasks { taskDao.fetchTasks(preferences, filter)
TaskListQuery.getQuery(preferences, filter, it)
}
}
@RawQuery suspend fun fetchTasks(query: SimpleSQLiteQuery): List<TaskContainer> =
abstract suspend fun fetchTasks(query: SimpleSQLiteQuery): List<TaskContainer> taskDao.fetchTasks(query)
@RawQuery suspend fun count(query: SimpleSQLiteQuery): Int = taskDao.count(query)
abstract suspend fun count(query: SimpleSQLiteQuery): Int
@Query(""" suspend fun getSubtaskInfo(): SubtaskInfo = taskDao.getSubtaskInfo()
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]) fun getTaskFactory(query: SimpleSQLiteQuery): DataSource.Factory<Int, TaskContainer> =
abstract fun getTaskFactory(query: SimpleSQLiteQuery): DataSource.Factory<Int, TaskContainer> taskDao.getTaskFactory(query)
suspend fun touch(id: Long) = touch(listOf(id)) suspend fun touch(id: Long) = touch(listOf(id))
suspend fun touch(ids: List<Long>) { suspend fun touch(ids: List<Long>) {
ids.eachChunk { touchInternal(it) } ids.eachChunk { taskDao.touch(ids) }
workManager.sync(false) workManager.sync(false)
} }
@Query("UPDATE tasks SET modified = :now WHERE _id in (:ids)")
internal abstract suspend fun touchInternal(ids: List<Long>, now: Long = currentTimeMillis())
suspend fun setParent(parent: Long, tasks: List<Long>) = suspend fun setParent(parent: Long, tasks: List<Long>) =
tasks.eachChunk { setParentInternal(parent, it) } tasks.eachChunk { setParentInternal(parent, it) }
@Query("UPDATE tasks SET parent = :parent WHERE _id IN (:children)") private suspend fun setParentInternal(parent: Long, children: List<Long>) =
internal abstract suspend fun setParentInternal(parent: Long, children: List<Long>) taskDao.setParentInternal(parent, children)
@Transaction suspend fun fetchChildren(id: Long): List<Task> = taskDao.fetchChildren(id)
open suspend fun fetchChildren(id: Long): List<Task> {
return fetch(getChildren(id))
}
suspend fun getChildren(id: Long): List<Long> { suspend fun getChildren(id: Long): List<Long> = taskDao.getChildren(id)
return getChildren(listOf(id))
}
@Query("WITH RECURSIVE " suspend fun getChildren(ids: List<Long>): List<Long> = taskDao.getChildren(ids)
+ " 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 suspend fun getChildren(ids: List<Long>): List<Long>
@Query("UPDATE tasks SET collapsed = :collapsed WHERE _id = :id")
abstract suspend fun setCollapsed(id: Long, collapsed: Boolean)
@Transaction
open suspend 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)") suspend fun setCollapsed(id: Long, collapsed: Boolean) = taskDao.setCollapsed(id, collapsed)
internal abstract suspend fun collapse(ids: List<Long>, collapsed: Boolean)
suspend fun setCollapsed(preferences: Preferences, filter: Filter, collapsed: Boolean) =
taskDao.setCollapsed(preferences, filter, collapsed)
// --- save // --- save
// TODO: get rid of this super-hack // TODO: get rid of this super-hack
@ -226,49 +129,20 @@ SELECT EXISTS(SELECT 1 FROM tasks WHERE parent > 0 AND deleted = 0) AS hasSubtas
} }
} }
@Insert suspend fun insert(task: Task): Long = taskDao.insert(task)
abstract suspend fun insert(task: Task): Long
@Update suspend fun update(task: Task): Int = taskDao.update(task)
abstract suspend fun update(task: Task): Int
suspend fun createNew(task: Task) { suspend fun createNew(task: Task) = taskDao.createNew(task)
task.id = NO_ID
if (task.creationDate == 0L) {
task.creationDate = DateUtilities.now()
}
if (Task.isUuidEmpty(task.remoteId)) {
task.remoteId = UUIDHelper.newUUID()
}
if (BuildConfig.DEBUG) {
require(task.remoteId?.isNotBlank() == true && task.remoteId != "0")
}
val insert = insert(task)
task.id = insert
}
suspend fun count(filter: Filter): Int { suspend fun count(filter: Filter): Int = taskDao.count(filter)
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
}
suspend fun fetchFiltered(filter: Filter): List<Task> { suspend fun fetchFiltered(filter: Filter): List<Task> = taskDao.fetchFiltered(filter)
return fetchFiltered(filter.getSqlQuery())
}
suspend fun fetchFiltered(queryTemplate: String): List<Task> { suspend fun fetchFiltered(queryTemplate: String): List<Task> =
val query = getQuery(queryTemplate, Task.FIELDS) taskDao.fetchFiltered(queryTemplate)
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") suspend fun getLocalTasks(): List<Long> = taskDao.getLocalTasks()
abstract suspend fun getLocalTasks(): List<Long>
/** Generates SQL clauses */ /** Generates SQL clauses */
object TaskCriteria { object TaskCriteria {
@ -281,15 +155,4 @@ SELECT EXISTS(SELECT 1 FROM tasks WHERE parent > 0 AND deleted = 0) AS hasSubtas
Task.HIDE_UNTIL.lte(Functions.now())) Task.HIDE_UNTIL.lte(Functions.now()))
} }
} }
companion object {
const val TRANS_SUPPRESS_REFRESH = "suppress-refresh"
fun getQuery(queryTemplate: String, vararg fields: Field): SimpleSQLiteQuery {
return SimpleSQLiteQuery(
com.todoroo.andlib.sql.Query.select(*fields)
.withQueryTemplate(PermaSql.replacePlaceholdersForQuery(queryTemplate))
.from(Task.TABLE)
.toString())
}
}
} }

@ -376,7 +376,7 @@ class Task : Parcelable {
@Synchronized @Synchronized
fun suppressRefresh() { fun suppressRefresh() {
putTransitory(TaskDao.TRANS_SUPPRESS_REFRESH, true) putTransitory(TRANS_SUPPRESS_REFRESH, true)
} }
@Synchronized @Synchronized
@ -560,6 +560,8 @@ class Task : Parcelable {
const val URGENCY_NEXT_WEEK = 4 const val URGENCY_NEXT_WEEK = 4
const val URGENCY_IN_TWO_WEEKS = 5 const val URGENCY_IN_TWO_WEEKS = 5
const val TRANS_SUPPRESS_REFRESH = "suppress-refresh"
/** /**
* Creates due date for this task. If this due date has no time associated, we move it to the last * Creates due date for this task. If this due date has no time associated, we move it to the last
* millisecond of the day. * millisecond of the day.

@ -0,0 +1,252 @@
package org.tasks.data
import androidx.paging.DataSource
import androidx.room.*
import androidx.sqlite.db.SimpleSQLiteQuery
import com.todoroo.andlib.sql.Criterion
import com.todoroo.andlib.sql.Field
import com.todoroo.andlib.sql.Functions
import com.todoroo.andlib.utility.DateUtilities
import com.todoroo.astrid.api.Filter
import com.todoroo.astrid.api.PermaSql
import com.todoroo.astrid.dao.Database
import com.todoroo.astrid.data.Task
import com.todoroo.astrid.data.Task.Companion.NO_ID
import com.todoroo.astrid.helper.UUIDHelper
import org.tasks.BuildConfig
import org.tasks.db.SuspendDbUtils.chunkedMap
import org.tasks.db.SuspendDbUtils.eachChunk
import org.tasks.preferences.Preferences
import org.tasks.time.DateTimeUtils.currentTimeMillis
import timber.log.Timber
@Dao
abstract class TaskDao(private val database: Database) {
@Query("SELECT * FROM tasks WHERE completed = 0 AND deleted = 0 AND (hideUntil > :now OR dueDate > :now)")
internal abstract suspend fun needsRefresh(now: Long = DateUtilities.now()): List<Task>
@Query("SELECT * FROM tasks WHERE _id = :id LIMIT 1")
abstract suspend fun fetch(id: Long): Task?
suspend fun fetch(ids: List<Long>): List<Task> = ids.chunkedMap(this::fetchInternal)
@Query("SELECT * FROM tasks WHERE _id IN (:ids)")
internal abstract suspend fun fetchInternal(ids: List<Long>): List<Task>
@Query("SELECT COUNT(1) FROM tasks WHERE timerStart > 0 AND deleted = 0")
abstract suspend fun activeTimers(): Int
@Query("SELECT tasks.* FROM tasks INNER JOIN notification ON tasks._id = notification.task")
abstract suspend fun activeNotifications(): List<Task>
@Query("SELECT * FROM tasks WHERE remoteId = :remoteId")
abstract suspend fun fetch(remoteId: String): Task?
@Query("SELECT * FROM tasks WHERE completed = 0 AND deleted = 0")
abstract suspend fun getActiveTasks(): List<Task>
@Query("SELECT * FROM tasks WHERE remoteId IN (:remoteIds) "
+ "AND recurrence IS NOT NULL AND LENGTH(recurrence) > 0")
abstract suspend fun getRecurringTasks(remoteIds: List<String>): List<Task>
@Query("UPDATE tasks SET completed = :completionDate " + "WHERE remoteId = :remoteId")
abstract suspend fun setCompletionDate(remoteId: String, completionDate: Long)
@Query("UPDATE tasks SET snoozeTime = :millis WHERE _id in (:taskIds)")
abstract suspend 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 suspend 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 suspend fun getCaldavTasksToPush(calendar: String): List<Task>
@Query("SELECT * FROM TASKS "
+ "WHERE completed = 0 AND deleted = 0 AND (notificationFlags > 0 OR notifications > 0)")
abstract suspend fun getTasksWithReminders(): List<Task>
// --- SQL clause generators
@Query("SELECT * FROM tasks")
abstract suspend fun getAll(): List<Task>
@Query("SELECT calendarUri FROM tasks " + "WHERE calendarUri IS NOT NULL AND calendarUri != ''")
abstract suspend fun getAllCalendarEvents(): List<String>
@Query("UPDATE tasks SET calendarUri = '' " + "WHERE calendarUri IS NOT NULL AND calendarUri != ''")
abstract suspend fun clearAllCalendarEvents(): Int
@Query("SELECT calendarUri FROM tasks "
+ "WHERE completed > 0 AND calendarUri IS NOT NULL AND calendarUri != ''")
abstract suspend fun getCompletedCalendarEvents(): List<String>
@Query("UPDATE tasks SET calendarUri = '' "
+ "WHERE completed > 0 AND calendarUri IS NOT NULL AND calendarUri != ''")
abstract suspend fun clearCompletedCalendarEvents(): Int
@Transaction
open suspend fun fetchTasks(callback: suspend (SubtaskInfo) -> List<String>): List<TaskContainer> {
return fetchTasks(callback, getSubtaskInfo())
}
@Transaction
open suspend fun fetchTasks(callback: suspend (SubtaskInfo) -> List<String>, subtasks: SubtaskInfo): List<TaskContainer> {
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
}
suspend fun fetchTasks(preferences: Preferences, filter: Filter): List<TaskContainer> {
return fetchTasks {
TaskListQuery.getQuery(preferences, filter, it)
}
}
@RawQuery
abstract suspend fun fetchTasks(query: SimpleSQLiteQuery): List<TaskContainer>
@RawQuery
abstract suspend 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>
@Query("UPDATE tasks SET modified = :now WHERE _id in (:ids)")
abstract suspend fun touch(ids: List<Long>, now: Long = currentTimeMillis())
suspend fun setParent(parent: Long, tasks: List<Long>) =
tasks.eachChunk { setParentInternal(parent, it) }
@Query("UPDATE tasks SET parent = :parent WHERE _id IN (:children)")
internal abstract suspend fun setParentInternal(parent: Long, children: List<Long>)
@Transaction
open suspend fun fetchChildren(id: Long): List<Task> {
return fetch(getChildren(id))
}
suspend 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 suspend fun getChildren(ids: List<Long>): List<Long>
@Query("UPDATE tasks SET collapsed = :collapsed WHERE _id = :id")
abstract suspend fun setCollapsed(id: Long, collapsed: Boolean)
@Transaction
open suspend 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)")
internal abstract suspend fun collapse(ids: List<Long>, collapsed: Boolean)
@Insert
abstract suspend fun insert(task: Task): Long
@Update
abstract suspend fun update(task: Task): Int
suspend 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()
}
if (BuildConfig.DEBUG) {
require(task.remoteId?.isNotBlank() == true && task.remoteId != "0")
}
val insert = insert(task)
task.id = insert
}
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)
Timber.v("%sms: %s", DateUtilities.now() - start, query.sql)
return count
}
suspend fun fetchFiltered(filter: Filter): List<Task> {
return fetchFiltered(filter.getSqlQuery())
}
suspend 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 suspend fun getLocalTasks(): List<Long>
/** Generates SQL clauses */
object TaskCriteria {
/** @return tasks that have not yet been completed or deleted
*/
@JvmStatic fun activeAndVisible(): Criterion {
return Criterion.and(
Task.COMPLETION_DATE.lte(0),
Task.DELETION_DATE.lte(0),
Task.HIDE_UNTIL.lte(Functions.now()))
}
}
companion object {
fun getQuery(queryTemplate: String, vararg fields: Field): SimpleSQLiteQuery {
return SimpleSQLiteQuery(
com.todoroo.andlib.sql.Query.select(*fields)
.withQueryTemplate(PermaSql.replacePlaceholdersForQuery(queryTemplate))
.from(Task.TABLE)
.toString())
}
}
}

@ -2,7 +2,6 @@ package org.tasks.injection
import android.content.Context import android.content.Context
import com.todoroo.astrid.dao.Database import com.todoroo.astrid.dao.Database
import com.todoroo.astrid.dao.TaskDao
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import dagger.hilt.InstallIn import dagger.hilt.InstallIn
@ -13,7 +12,6 @@ import org.tasks.billing.BillingClient
import org.tasks.billing.BillingClientImpl import org.tasks.billing.BillingClientImpl
import org.tasks.billing.Inventory import org.tasks.billing.Inventory
import org.tasks.data.* import org.tasks.data.*
import org.tasks.jobs.WorkManager
import org.tasks.locale.Locale import org.tasks.locale.Locale
import org.tasks.location.Geocoder import org.tasks.location.Geocoder
import org.tasks.location.MapboxGeocoder import org.tasks.location.MapboxGeocoder
@ -81,11 +79,7 @@ class ApplicationModule {
@Provides @Provides
@Singleton @Singleton
fun getTaskDao(db: Database, workManager: WorkManager): TaskDao { fun getTaskDao(db: Database): TaskDao = db.taskDao
val taskDao = db.taskDao
taskDao.initialize(workManager)
return taskDao
}
@Provides @Provides
@Singleton @Singleton

@ -114,7 +114,7 @@ class AfterSaveWork @WorkerInject constructor(
.putBoolean(EXTRA_PUSH_GTASKS, !suppress && !current.googleTaskUpToDate(original)) .putBoolean(EXTRA_PUSH_GTASKS, !suppress && !current.googleTaskUpToDate(original))
.putBoolean( .putBoolean(
EXTRA_PUSH_CALDAV, !suppress && (!current.caldavUpToDate(original) || forceCaldav)) EXTRA_PUSH_CALDAV, !suppress && (!current.caldavUpToDate(original) || forceCaldav))
.putBoolean(EXTRA_SUPPRESS_REFRESH, current.checkTransitory(TaskDao.TRANS_SUPPRESS_REFRESH)) .putBoolean(EXTRA_SUPPRESS_REFRESH, current.checkTransitory(Task.TRANS_SUPPRESS_REFRESH))
if (original != null) { if (original != null) {
builder builder
.putLong(EXTRA_ORIG_COMPLETED, original.completionDate) .putLong(EXTRA_ORIG_COMPLETED, original.completionDate)

Loading…
Cancel
Save