Use transaction annotation

pull/3311/head
Alex Baker 10 months ago
parent 8ef23c1593
commit 8a27f5df4b

@ -103,7 +103,6 @@ import org.tasks.data.entity.Task
import org.tasks.data.listSettingsClass
import org.tasks.data.open
import org.tasks.data.sql.QueryTemplate
import org.tasks.data.withTransaction
import org.tasks.databinding.FragmentTaskListBinding
import org.tasks.dialogs.DateTimePicker.Companion.newDateTimePicker
import org.tasks.dialogs.DialogBuilder
@ -1058,10 +1057,7 @@ class TaskListFragment : Fragment(), OnRefreshListener, Toolbar.OnMenuItemClickL
(intent.getSerializableExtra(EXTRAS_TASK_ID) as? ArrayList<Long>)
?.let {
Timber.d("Repeating tasks: $it")
// hack to wait for task save transaction to complete
database.withTransaction {
taskDao.fetch(it)
}
taskDao.fetch(it)
}
?.filterNot { it.readOnly }
?.takeIf { it.isNotEmpty() }

@ -98,22 +98,19 @@ class TaskDao @Inject constructor(
*/
suspend fun save(task: Task) = save(task, fetch(task.id))
suspend fun save(tasks: List<Task>, originals: List<Task>) {
Timber.d("Saving $tasks")
taskDao.updateInternal(tasks)
tasks.forEach { task -> afterUpdate(task, originals.find { it.id == task.id }) }
}
suspend fun save(task: Task, original: Task?) {
if (taskDao.update(task, original)) {
Timber.d("Saved $task")
afterUpdate(task, original)
if (!task.isSuppressRefresh()) {
localBroadcastManager.broadcastRefresh()
}
workManager.triggerNotifications()
workManager.scheduleRefresh()
}
}
private suspend fun afterUpdate(task: Task, original: Task?) {
suspend fun afterUpdate(task: Task, original: Task?) {
val completionDateModified = task.completionDate != (original?.completionDate ?: 0)
val deletionDateModified = task.deletionDate != (original?.deletionDate ?: 0)
val justCompleted = completionDateModified && task.isCompleted
@ -131,9 +128,6 @@ class TaskDao @Inject constructor(
if (completionDateModified || deletionDateModified) {
geofenceApi.update(task.id)
}
if (!task.isSuppressRefresh()) {
localBroadcastManager.broadcastRefresh()
}
syncAdapters.sync(task, original)
}

@ -11,7 +11,6 @@ import com.todoroo.astrid.gcal.GCalHelper
import net.fortuna.ical4j.model.Date
import net.fortuna.ical4j.model.Recur
import net.fortuna.ical4j.model.WeekDay
import org.tasks.LocalBroadcastManager
import org.tasks.data.createDueDate
import org.tasks.data.entity.Alarm
import org.tasks.data.entity.Alarm.Companion.TYPE_SNOOZE
@ -33,12 +32,11 @@ class RepeatTaskHelper @Inject constructor(
private val gcalHelper: GCalHelper,
private val alarmService: AlarmService,
private val taskDao: TaskDao,
private val localBroadcastManager: LocalBroadcastManager,
) {
suspend fun handleRepeat(task: Task) {
suspend fun handleRepeat(task: Task): Boolean {
val recurrence = task.recurrence
if (recurrence.isNullOrBlank()) {
return
return false
}
val repeatAfterCompletion = task.repeatFrom == RepeatFrom.COMPLETION_DATE
val newDueDate: Long
@ -48,17 +46,15 @@ class RepeatTaskHelper @Inject constructor(
rrule = initRRule(recurrence)
count = rrule.count
if (count == 1) {
broadcastCompletion(task)
return
return true
}
newDueDate = computeNextDueDate(task, recurrence, repeatAfterCompletion)
if (newDueDate == -1L) {
broadcastCompletion(task)
return
return true
}
} catch (e: ParseException) {
Timber.e(e)
return
return false
}
if (count > 1) {
rrule.count = count - 1
@ -70,18 +66,9 @@ class RepeatTaskHelper @Inject constructor(
task.setDueDateAdjustingHideUntil(newDueDate)
gcalHelper.rescheduleRepeatingTask(task)
taskDao.save(task)
val previousDueDate =
oldDueDate
.takeIf { it > 0 }
?: (newDueDate - (computeNextDueDate(task, recurrence, repeatAfterCompletion) - newDueDate))
val previousDueDate = oldDueDate.takeIf { it > 0 } ?: computePreviousDueDate(task)
rescheduleAlarms(task.id, previousDueDate, newDueDate)
broadcastCompletion(task, previousDueDate)
}
private fun broadcastCompletion(task: Task, oldDueDate: Long = 0L) {
if (!task.isSuppressRefresh()) {
localBroadcastManager.broadcastTaskCompleted(task.id, oldDueDate)
}
return true
}
suspend fun undoRepeat(task: Task, oldDueDate: Long) {
@ -131,6 +118,9 @@ class RepeatTaskHelper @Inject constructor(
companion object {
private val weekdayCompare = Comparator { object1: WeekDay, object2: WeekDay -> WeekDay.getCalendarDay(object1) - WeekDay.getCalendarDay(object2) }
fun computePreviousDueDate(task: Task): Long =
task.dueDate - (computeNextDueDate(task, task.recurrence!!, task.repeatFrom == RepeatFrom.COMPLETION_DATE) - task.dueDate)
/** Compute next due date */
@Throws(ParseException::class)
fun computeNextDueDate(task: Task, recurrence: String, repeatAfterCompletion: Boolean): Long {

@ -8,13 +8,12 @@ import android.media.RingtoneManager
import com.todoroo.astrid.dao.TaskDao
import com.todoroo.astrid.gcal.GCalHelper
import com.todoroo.astrid.repeats.RepeatTaskHelper
import com.todoroo.astrid.repeats.RepeatTaskHelper.Companion.computePreviousDueDate
import dagger.hilt.android.qualifiers.ApplicationContext
import org.tasks.LocalBroadcastManager
import org.tasks.data.dao.AlarmDao
import org.tasks.data.dao.CaldavDao
import org.tasks.data.db.Database
import org.tasks.data.dao.CompletionDao
import org.tasks.data.entity.Task
import org.tasks.data.withTransaction
import org.tasks.jobs.WorkManager
import org.tasks.notifications.NotificationManager
import org.tasks.preferences.Preferences
@ -24,7 +23,6 @@ import javax.inject.Inject
class TaskCompleter @Inject internal constructor(
@ApplicationContext private val context: Context,
private val database: Database,
private val taskDao: TaskDao,
private val preferences: Preferences,
private val notificationManager: NotificationManager,
@ -33,7 +31,7 @@ class TaskCompleter @Inject internal constructor(
private val caldavDao: CaldavDao,
private val gCalHelper: GCalHelper,
private val workManager: WorkManager,
private val alarmDao: AlarmDao,
private val completionDao: CompletionDao,
) {
suspend fun setComplete(taskId: Long, completed: Boolean = true) =
taskDao
@ -56,10 +54,10 @@ class TaskCompleter @Inject internal constructor(
.filterNotNull()
.filter { it.isCompleted != completionDate > 0 }
.filterNot { it.readOnly }
.let {
setComplete(it, completionDate)
.let { tasks ->
setComplete(tasks, completionDate)
if (completed && !item.isRecurring) {
localBroadcastManager.broadcastTaskCompleted(ArrayList(it.map(Task::id)))
localBroadcastManager.broadcastTaskCompleted(tasks.map { it.id })
}
}
}
@ -70,27 +68,24 @@ class TaskCompleter @Inject internal constructor(
}
tasks.forEach { notificationManager.cancel(it.id) }
val completed = completionDate > 0
val modified = currentTimeMillis()
val repeated = ArrayList<Task>()
Timber.d("Completing $tasks")
database.withTransaction {
alarmDao.deleteSnoozed(tasks.map { it.id })
tasks
.map {
it.copy(
completionDate = completionDate,
modificationDate = modified,
)
completionDao.complete(
tasks = tasks,
completionDate = completionDate,
afterSave = { updated ->
updated.forEach { saved ->
val original = tasks.find { it.id == saved.id }
taskDao.afterUpdate(saved, original)
}
.also { completed ->
completed.subList(0, completed.lastIndex).forEach { it.suppressRefresh() }
taskDao.save(completed, tasks)
}
.forEach { task ->
updated.forEach { task ->
if (completed && task.isRecurring) {
gCalHelper.updateEvent(task)
if (caldavDao.getAccountForTask(task.id)?.isSuppressRepeatingTasks != true) {
repeatTaskHelper.handleRepeat(task)
if (repeatTaskHelper.handleRepeat(task)) {
repeated.add(task)
}
if (task.completionDate == 0L) {
// un-complete children
setComplete(task, false)
@ -98,9 +93,16 @@ class TaskCompleter @Inject internal constructor(
}
}
}
}
}
)
localBroadcastManager.broadcastRefresh()
workManager.triggerNotifications()
workManager.scheduleRefresh()
repeated.lastOrNull()?.let { task ->
val oldDueDate = tasks.find { it.id == task.id }?.dueDate?.takeIf { it > 0 }
?: computePreviousDueDate(task)
localBroadcastManager.broadcastTaskCompleted(arrayListOf(task.id), oldDueDate)
}
if (completed && notificationManager.currentInterruptionFilter == INTERRUPTION_FILTER_ALL) {
preferences
.completionSound

@ -10,23 +10,19 @@ import org.tasks.data.dao.DeletionDao
import org.tasks.data.dao.LocationDao
import org.tasks.data.dao.TaskDao
import org.tasks.data.dao.UserActivityDao
import org.tasks.data.db.Database
import org.tasks.data.db.SuspendDbUtils.chunkedMap
import org.tasks.data.entity.CaldavAccount
import org.tasks.data.entity.CaldavCalendar
import org.tasks.data.entity.Task
import org.tasks.data.pictureUri
import org.tasks.data.withTransaction
import org.tasks.files.FileHelper
import org.tasks.location.GeofenceApi
import org.tasks.notifications.NotificationManager
import org.tasks.sync.SyncAdapters
import timber.log.Timber
import javax.inject.Inject
class TaskDeleter @Inject constructor(
@ApplicationContext private val context: Context,
private val database: Database,
private val deletionDao: DeletionDao,
private val taskDao: TaskDao,
private val localBroadcastManager: LocalBroadcastManager,
@ -47,11 +43,10 @@ class TaskDeleter @Inject constructor(
.let { taskDao.fetch(it.toList()) }
.filterNot { it.readOnly }
.map { it.id }
Timber.d("markDeleted $ids")
database.withTransaction {
deletionDao.markDeleted(ids)
cleanup(ids)
}
deletionDao.markDeleted(
ids = ids,
cleanup = { cleanup(it) }
)
syncAdapters.sync()
localBroadcastManager.broadcastRefresh()
taskDao.fetch(ids)
@ -62,31 +57,28 @@ class TaskDeleter @Inject constructor(
suspend fun delete(task: Long) = delete(listOf(task))
suspend fun delete(tasks: List<Long>) {
Timber.d("Deleting $tasks")
database.withTransaction {
deletionDao.delete(tasks)
cleanup(tasks)
}
deletionDao.delete(
ids = tasks,
cleanup = { cleanup(it) }
)
localBroadcastManager.broadcastRefresh()
}
suspend fun delete(list: CaldavCalendar) {
vtodoCache.delete(list)
Timber.d("Deleting $list")
database.withTransaction {
val tasks = deletionDao.delete(list)
delete(tasks)
}
deletionDao.delete(
caldavCalendar = list,
cleanup = { cleanup(it) }
)
localBroadcastManager.broadcastRefreshList()
}
suspend fun delete(account: CaldavAccount) {
vtodoCache.delete(account)
Timber.d("Deleting $account")
database.withTransaction {
val tasks = deletionDao.delete(account)
delete(tasks)
}
deletionDao.delete(
caldavAccount = account,
cleanup = { cleanup(it) }
)
localBroadcastManager.broadcastRefreshList()
}

@ -5,7 +5,6 @@ import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import com.google.common.collect.Lists
import com.todoroo.astrid.api.AstridApiConstants
import dagger.hilt.android.qualifiers.ApplicationContext
import org.tasks.widget.AppWidgetManager
@ -56,17 +55,9 @@ class LocalBroadcastManager @Inject constructor(
localBroadcastManager.sendBroadcast(Intent(REFRESH_PREFERENCES))
}
fun broadcastTaskCompleted(id: Long, oldDueDate: Long) {
broadcastTaskCompleted(Lists.newArrayList(id), oldDueDate)
}
fun broadcastTaskCompleted(id: ArrayList<Long>) {
broadcastTaskCompleted(id, 0)
}
private fun broadcastTaskCompleted(id: ArrayList<Long>, oldDueDate: Long) {
fun broadcastTaskCompleted(id: List<Long>, oldDueDate: Long = 0L) {
val intent = Intent(TASK_COMPLETED)
intent.putExtra(AstridApiConstants.EXTRAS_TASK_ID, id)
intent.putExtra(AstridApiConstants.EXTRAS_TASK_ID, ArrayList(id))
intent.putExtra(AstridApiConstants.EXTRAS_OLD_DUE_DATE, oldDueDate)
localBroadcastManager.sendBroadcast(intent)
}

@ -15,6 +15,7 @@ import org.tasks.analytics.Firebase
import org.tasks.billing.BillingClient
import org.tasks.billing.BillingClientImpl
import org.tasks.billing.Inventory
import org.tasks.compose.drawer.DrawerConfiguration
import org.tasks.data.dao.AlarmDao
import org.tasks.data.dao.Astrid2ContentProviderDao
import org.tasks.data.dao.CaldavDao
@ -30,13 +31,12 @@ import org.tasks.data.dao.TaskDao
import org.tasks.data.dao.TaskListMetadataDao
import org.tasks.data.dao.UserActivityDao
import org.tasks.data.db.Database
import org.tasks.filters.FilterProvider
import org.tasks.filters.PreferenceDrawerConfiguration
import org.tasks.jobs.WorkManager
import org.tasks.kmp.createDataStore
import org.tasks.compose.drawer.DrawerConfiguration
import org.tasks.filters.FilterProvider
import org.tasks.preferences.TasksPreferences
import org.tasks.preferences.Preferences
import org.tasks.preferences.TasksPreferences
import java.util.Locale
import javax.inject.Singleton
@ -117,6 +117,10 @@ class ApplicationModule {
@Singleton
fun getPrincipalDao(db: Database) = db.principalDao()
@Provides
@Singleton
fun getCompletionDao(db: Database) = db.completionDao()
@Provides
fun getBillingClient(
@ApplicationContext context: Context,

@ -1,13 +0,0 @@
package org.tasks.data
import androidx.room.RoomDatabase
import androidx.room.TransactionScope
import androidx.room.Transactor
import androidx.room.useWriterConnection
suspend fun <T> RoomDatabase.withTransaction(block: suspend TransactionScope<T>.() -> T): T =
useWriterConnection { transactor ->
transactor.withTransaction(Transactor.SQLiteTransactionType.IMMEDIATE) {
block()
}
}

@ -5,6 +5,7 @@ import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Transaction
import androidx.room.Update
import co.touchlab.kermit.Logger
import kotlinx.coroutines.flow.Flow
@ -12,7 +13,6 @@ import org.tasks.data.CaldavFilters
import org.tasks.data.CaldavTaskContainer
import org.tasks.data.NO_ORDER
import org.tasks.data.TaskContainer
import org.tasks.data.db.Database
import org.tasks.data.db.DbUtils.dbchunk
import org.tasks.data.db.SuspendDbUtils.chunkedMap
import org.tasks.data.entity.CaldavAccount
@ -22,13 +22,12 @@ import org.tasks.data.entity.CaldavAccount.Companion.TYPE_TASKS
import org.tasks.data.entity.CaldavCalendar
import org.tasks.data.entity.CaldavTask
import org.tasks.data.entity.Task
import org.tasks.data.withTransaction
import org.tasks.time.DateTimeUtils2.currentTimeMillis
const val APPLE_EPOCH = 978307200000L // 1/1/2001 GMT
@Dao
abstract class CaldavDao(private val database: Database) {
abstract class CaldavDao {
@Query("SELECT COUNT(*) FROM caldav_lists WHERE cdl_account = :account")
abstract suspend fun listCount(account: String): Int
@ -104,23 +103,21 @@ ORDER BY CASE cda_account_type
suspend fun insert(task: Task, caldavTask: CaldavTask, addToTop: Boolean): Long {
Logger.d("CaldavDao") { "insert task=$task caldavTask=$caldavTask addToTop=$addToTop)" }
return database.withTransaction {
if (task.order != null) {
return@withTransaction insert(caldavTask)
}
if (addToTop) {
task.order = findFirstTask(caldavTask.calendar!!, task.parent)
?.takeIf { task.creationDate.toAppleEpoch() >= it }
?.minus(1)
} else {
task.order = findLastTask(caldavTask.calendar!!, task.parent)
?.takeIf { task.creationDate.toAppleEpoch() <= it }
?.plus(1)
}
val id = insert(caldavTask)
update(task)
id
if (task.order != null) {
return insert(caldavTask)
}
if (addToTop) {
task.order = findFirstTask(caldavTask.calendar!!, task.parent)
?.takeIf { task.creationDate.toAppleEpoch() >= it }
?.minus(1)
} else {
task.order = findLastTask(caldavTask.calendar!!, task.parent)
?.takeIf { task.creationDate.toAppleEpoch() <= it }
?.plus(1)
}
val id = insert(caldavTask)
update(task)
return id
}
@Query("""
@ -309,51 +306,49 @@ GROUP BY caldav_lists.cdl_uuid
+ "WHERE _id IN (SELECT _id FROM tasks INNER JOIN caldav_tasks ON _id = cd_task WHERE cd_deleted = 0 AND cd_calendar = :calendar)")
abstract suspend fun updateParents(calendar: String)
suspend fun move(
@Transaction
open suspend fun move(
task: TaskContainer,
previousParent: Long,
newParent: Long,
newPosition: Long?,
) {
Logger.d("CaldavDao") { "move task=$task previousParent=$previousParent newParent=$newParent newPosition=$newPosition" }
database.withTransaction {
val previousPosition = task.caldavSortOrder
if (newPosition != null) {
if (newParent == previousParent && newPosition < previousPosition) {
shiftDown(task.caldav!!, newParent, newPosition, previousPosition)
} else {
val list =
newParent.takeIf { it > 0 }?.let { getTask(it)?.calendar } ?: task.caldav!!
shiftDown(list, newParent, newPosition)
}
val previousPosition = task.caldavSortOrder
if (newPosition != null) {
if (newParent == previousParent && newPosition < previousPosition) {
shiftDown(task.caldav!!, newParent, newPosition, previousPosition)
} else {
val list =
newParent.takeIf { it > 0 }?.let { getTask(it)?.calendar } ?: task.caldav!!
shiftDown(list, newParent, newPosition)
}
task.task.order = newPosition
setTaskOrder(task.id, newPosition)
}
task.task.order = newPosition
setTaskOrder(task.id, newPosition)
}
suspend fun shiftDown(calendar: String, parent: Long, from: Long, to: Long? = null) {
@Transaction
open suspend fun shiftDown(calendar: String, parent: Long, from: Long, to: Long? = null) {
Logger.d("CaldavDao") { "shiftDown calendar=$calendar parent=$parent from=$from to=$to" }
database.withTransaction {
val updated = ArrayList<Task>()
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 task = task.task
task.order = current + 1
updated.add(task)
} else if (task.sortOrder > current) {
break
}
val updated = ArrayList<Task>()
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 task = task.task
task.order = current + 1
updated.add(task)
} else if (task.sortOrder > current) {
break
}
updateTasks(updated)
updated
.map(Task::id)
.dbchunk()
.forEach { touchInternal(it) }
}
updateTasks(updated)
updated
.map(Task::id)
.dbchunk()
.forEach { touchInternal(it) }
}
@Query("UPDATE tasks SET modified = :modificationTime WHERE _id in (:ids)")

@ -0,0 +1,31 @@
package org.tasks.data.dao
import androidx.room.Dao
import androidx.room.Transaction
import co.touchlab.kermit.Logger
import org.tasks.data.db.Database
import org.tasks.data.entity.Task
import org.tasks.time.DateTimeUtils2.currentTimeMillis
@Dao
abstract class CompletionDao(private val db: Database) {
@Transaction
open suspend fun complete(
tasks: List<Task>,
completionDate: Long,
afterSave: suspend (List<Task>) -> Unit,
) {
Logger.d("CompletionDao") { "complete tasks=$tasks completionDate=$completionDate" }
val modified = currentTimeMillis()
val updated = tasks
.map {
it.copy(
completionDate = completionDate,
modificationDate = modified,
)
}
db.alarmDao().deleteSnoozed(tasks.map { it.id })
db.taskDao().updateInternal(updated)
afterSave(updated)
}
}

@ -3,17 +3,16 @@ package org.tasks.data.dao
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Query
import androidx.room.Transaction
import co.touchlab.kermit.Logger
import org.tasks.data.dao.CaldavDao.Companion.LOCAL
import org.tasks.data.db.Database
import org.tasks.data.db.SuspendDbUtils.chunkedMap
import org.tasks.data.db.SuspendDbUtils.eachChunk
import org.tasks.data.entity.CaldavAccount
import org.tasks.data.entity.CaldavCalendar
import org.tasks.data.withTransaction
@Dao
abstract class DeletionDao(private val database: Database) {
abstract class DeletionDao {
@Query("DELETE FROM tasks WHERE _id IN(:ids)")
internal abstract suspend fun deleteTasks(ids: List<Long>)
@ -43,15 +42,29 @@ WHERE recurring = 1
""")
abstract suspend fun internalHasRecurringAncestors(ids: List<Long>): List<Long>
suspend fun delete(ids: List<Long>) { ids.eachChunk { deleteTasks(it) } }
@Transaction
open suspend fun delete(
ids: List<Long>,
cleanup: suspend (List<Long>) -> Unit,
) {
Logger.d("DeletionDao") { "delete ids=$ids" }
ids.eachChunk { deleteTasks(it) }
cleanup(ids)
}
@Query("UPDATE tasks "
+ "SET modified = (strftime('%s','now')*1000), deleted = (strftime('%s','now')*1000)"
+ "WHERE _id IN(:ids)")
internal abstract suspend fun markDeletedInternal(ids: List<Long>)
suspend fun markDeleted(ids: Iterable<Long>) {
@Transaction
open suspend fun markDeleted(
ids: Iterable<Long>,
cleanup: suspend (List<Long>) -> Unit,
) {
Logger.d("DeletionDao") { "markDeleted ids=$ids" }
ids.eachChunk(this::markDeletedInternal)
cleanup(ids.toList())
}
@Query("SELECT cd_task FROM caldav_tasks WHERE cd_calendar = :calendar AND cd_deleted = 0")
@ -60,14 +73,15 @@ WHERE recurring = 1
@Delete
internal abstract suspend fun deleteCaldavCalendar(caldavCalendar: CaldavCalendar)
suspend fun delete(caldavCalendar: CaldavCalendar): List<Long> {
@Transaction
open suspend fun delete(
caldavCalendar: CaldavCalendar,
cleanup: suspend (List<Long>) -> Unit,
) {
Logger.d("DeletionDao") { "deleting $caldavCalendar" }
return database.withTransaction {
val tasks = getActiveCaldavTasks(caldavCalendar.uuid!!)
delete(tasks)
deleteCaldavCalendar(caldavCalendar)
tasks
}
val tasks = getActiveCaldavTasks(caldavCalendar.uuid!!)
delete(tasks, cleanup)
deleteCaldavCalendar(caldavCalendar)
}
@Query("SELECT * FROM caldav_lists WHERE cdl_account = :account")
@ -79,15 +93,15 @@ WHERE recurring = 1
@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 AND cd_deleted = 0)")
abstract suspend fun purgeDeleted()
suspend fun delete(caldavAccount: CaldavAccount): List<Long> {
@Transaction
open suspend fun delete(
caldavAccount: CaldavAccount,
cleanup: suspend (List<Long>) -> Unit,
) {
Logger.d("DeletionDao") { "deleting $caldavAccount" }
return database.withTransaction {
val deleted = ArrayList<Long>()
for (calendar in getCalendars(caldavAccount.uuid!!)) {
deleted.addAll(delete(calendar))
}
deleteCaldavAccount(caldavAccount)
deleted
for (calendar in getCalendars(caldavAccount.uuid!!)) {
delete(calendar, cleanup)
}
deleteCaldavAccount(caldavAccount)
}
}

@ -4,34 +4,32 @@ import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Transaction
import androidx.room.Update
import co.touchlab.kermit.Logger
import org.tasks.data.db.Database
import org.tasks.data.entity.CaldavAccount.Companion.TYPE_GOOGLE_TASKS
import org.tasks.data.entity.CaldavTask
import org.tasks.data.entity.Task
import org.tasks.data.withTransaction
@Dao
abstract class GoogleTaskDao(private val database: Database) {
abstract class GoogleTaskDao {
@Insert
abstract suspend fun insert(task: CaldavTask): Long
@Insert
abstract suspend fun insert(tasks: Iterable<CaldavTask>)
suspend fun insertAndShift(task: Task, caldavTask: CaldavTask, top: Boolean) {
@Transaction
open suspend fun insertAndShift(task: Task, caldavTask: CaldavTask, top: Boolean) {
Logger.d("GoogleTaskDao") { "insertAndShift task=$task caldavTask=$caldavTask top=$top" }
database.withTransaction {
if (top) {
task.order = 0
shiftDown(caldavTask.calendar!!, task.parent, 0)
} else {
task.order = getBottom(caldavTask.calendar!!, task.parent)
}
insert(caldavTask)
update(task)
if (top) {
task.order = 0
shiftDown(caldavTask.calendar!!, task.parent, 0)
} else {
task.order = getBottom(caldavTask.calendar!!, task.parent)
}
insert(caldavTask)
update(task)
}
@Query("UPDATE tasks SET `order` = `order` + 1 WHERE parent = :parent AND `order` >= :position AND _id IN (SELECT cd_task FROM caldav_tasks WHERE cd_calendar = :listId)")
@ -46,26 +44,25 @@ abstract class GoogleTaskDao(private val database: Database) {
@Query("UPDATE tasks SET `order` = `order` - 1 WHERE parent = :parent AND `order` >= :position AND _id IN (SELECT cd_task FROM caldav_tasks WHERE cd_calendar = :listId)")
internal abstract suspend fun shiftUp(listId: String, parent: Long, position: Long)
suspend fun move(task: Task, list: String, newParent: Long, newPosition: Long) {
@Transaction
open suspend fun move(task: Task, list: String, newParent: Long, newPosition: Long) {
Logger.d("GoogleTaskDao") { "move task=$task list=$list newParent=$newParent newPosition=$newPosition" }
database.withTransaction {
val previousParent = task.parent
val previousPosition = task.order!!
if (newParent == previousParent) {
if (previousPosition < newPosition) {
shiftUp(list, newParent, previousPosition, newPosition)
} else {
shiftDown(list, newParent, previousPosition, newPosition)
}
val previousParent = task.parent
val previousPosition = task.order!!
if (newParent == previousParent) {
if (previousPosition < newPosition) {
shiftUp(list, newParent, previousPosition, newPosition)
} else {
shiftUp(list, previousParent, previousPosition)
shiftDown(list, newParent, newPosition)
shiftDown(list, newParent, previousPosition, newPosition)
}
task.parent = newParent
task.order = newPosition
update(task)
setMoved(task.id, list)
} else {
shiftUp(list, previousParent, previousPosition)
shiftDown(list, newParent, newPosition)
}
task.parent = newParent
task.order = newPosition
update(task)
setMoved(task.id, list)
}
@Query("UPDATE caldav_tasks SET gt_moved = 1 WHERE cd_task = :task and cd_calendar = :list")
@ -170,26 +167,24 @@ WHERE cd_remote_id = :id
suspend fun reposition(caldavDao: CaldavDao, listId: String) {
Logger.d("GoogleTaskDao") { "reposition listId=$listId" }
database.withTransaction {
caldavDao.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.order = subtasks
update(task)
}
subtasks++
} else {
subtasks = 0
if (task.order != parent) {
task.order = parent
update(task)
}
parent++
caldavDao.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.order = subtasks
update(task)
}
subtasks++
} else {
subtasks = 0
if (task.order != parent) {
task.order = parent
update(task)
}
parent++
}
}
}

@ -4,19 +4,17 @@ import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Transaction
import androidx.room.Update
import co.touchlab.kermit.Logger
import kotlinx.coroutines.flow.Flow
import org.tasks.data.PrincipalWithAccess
import org.tasks.data.db.Database
import org.tasks.data.entity.CaldavAccount
import org.tasks.data.entity.CaldavCalendar
import org.tasks.data.entity.Principal
import org.tasks.data.entity.PrincipalAccess
import org.tasks.data.withTransaction
@Dao
abstract class PrincipalDao(private val database: Database) {
abstract class PrincipalDao {
@Insert
abstract suspend fun insert(principal: Principal): Long
@ -36,15 +34,9 @@ WHERE list = :list
@Delete
abstract suspend fun delete(access: PrincipalAccess)
suspend fun getAll(): List<PrincipalWithAccess> {
Logger.d("PrincipalDao") { "getAll" }
return database.withTransaction {
getAllInternal()
}
}
@Transaction
@Query("SELECT * FROM principal_access")
internal abstract suspend fun getAllInternal(): List<PrincipalWithAccess>
abstract suspend fun getAll(): List<PrincipalWithAccess>
suspend fun getOrCreatePrincipal(account: CaldavAccount, href: String, displayName: String? = null) =
findPrincipal(account.id, href)

@ -4,15 +4,14 @@ import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Transaction
import co.touchlab.kermit.Logger
import org.tasks.data.db.Database
import org.tasks.data.entity.Tag
import org.tasks.data.entity.TagData
import org.tasks.data.entity.Task
import org.tasks.data.withTransaction
@Dao
abstract class TagDao(private val database: Database) {
abstract class TagDao {
@Query("UPDATE tags SET name = :name WHERE tag_uid = :tagUid")
abstract suspend fun rename(tagUid: String, name: String)
@ -37,17 +36,16 @@ abstract class TagDao(private val database: Database) {
@Delete
abstract suspend fun delete(tags: List<Tag>)
@Transaction
open suspend fun applyTags(task: Task, tagDataDao: TagDataDao, current: Collection<TagData>) {
Logger.d("TagDao") { "applyTags task=$task current=$current" }
database.withTransaction {
val taskId = task.id
val existing = HashSet(tagDataDao.getTagDataForTask(taskId))
val selected = current.toMutableSet()
val added = selected subtract existing
val removed = existing subtract selected
deleteTags(taskId, removed.map { td -> td.remoteId!! })
insert(task, added)
}
val taskId = task.id
val existing = HashSet(tagDataDao.getTagDataForTask(taskId))
val selected = current.toMutableSet()
val added = selected subtract existing
val removed = existing subtract selected
deleteTags(taskId, removed.map { td -> td.remoteId!! })
insert(task, added)
}
suspend fun insert(task: Task, tags: Collection<TagData>) {

@ -4,21 +4,20 @@ import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Transaction
import androidx.room.Update
import co.touchlab.kermit.Logger
import kotlinx.coroutines.flow.Flow
import org.tasks.data.NO_ORDER
import org.tasks.data.TagFilters
import org.tasks.data.db.Database
import org.tasks.data.db.DbUtils
import org.tasks.data.entity.Tag
import org.tasks.data.entity.TagData
import org.tasks.data.entity.Task
import org.tasks.data.withTransaction
import org.tasks.time.DateTimeUtils2.currentTimeMillis
@Dao
abstract class TagDataDao(private val database: Database) {
abstract class TagDataDao {
@Query("SELECT * FROM tagdata")
abstract fun subscribeToTags(): Flow<List<TagData>>
@ -81,46 +80,44 @@ abstract class TagDataDao(private val database: Database) {
+ " GROUP BY tasks._id")
internal abstract suspend fun getAllTags(tasks: List<Long>): List<String?>
suspend fun applyTags(
@Transaction
open suspend fun applyTags(
tasks: List<Task>,
partiallySelected: List<TagData>,
selected: List<TagData>
): List<Long> {
Logger.d("TagDataDao") { "applyTags tasks=$tasks partiallySelected=$partiallySelected selected=$selected" }
return database.withTransaction {
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 = task.id,
taskUid = task.uuid,
name = it.name,
tagUid = it.remoteId
)
}
)
}
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 = task.id,
taskUid = task.uuid,
name = it.name,
tagUid = it.remoteId
)
}
)
}
ArrayList(modified)
}
return ArrayList(modified)
}
suspend fun delete(tagData: TagData) {
@Transaction
open suspend fun delete(tagData: TagData) {
Logger.d("TagDataDao") { "deleting $tagData" }
database.withTransaction {
deleteTags(tagData.remoteId!!)
deleteTagData(tagData)
}
deleteTags(tagData.remoteId!!)
deleteTagData(tagData)
}
@Delete

@ -6,6 +6,7 @@ import androidx.room.RoomDatabase
import org.tasks.data.dao.AlarmDao
import org.tasks.data.dao.Astrid2ContentProviderDao
import org.tasks.data.dao.CaldavDao
import org.tasks.data.dao.CompletionDao
import org.tasks.data.dao.DeletionDao
import org.tasks.data.dao.FilterDao
import org.tasks.data.dao.GoogleTaskDao
@ -80,6 +81,7 @@ abstract class Database : RoomDatabase() {
abstract fun contentProviderDao(): Astrid2ContentProviderDao
abstract fun upgraderDao(): UpgraderDao
abstract fun principalDao(): PrincipalDao
abstract fun completionDao(): CompletionDao
/** @return human-readable database name for debugging
*/

Loading…
Cancel
Save