Store icalendar data on disk

pull/1793/head
Alex Baker 2 years ago
parent 433ae41200
commit 1154ba4be4

File diff suppressed because it is too large Load Diff

@ -16,6 +16,8 @@ import org.tasks.data.GoogleTaskDao
import org.tasks.injection.InjectingTestCase import org.tasks.injection.InjectingTestCase
import org.tasks.injection.ProductionModule import org.tasks.injection.ProductionModule
import org.tasks.jobs.WorkManager import org.tasks.jobs.WorkManager
import org.tasks.makers.CaldavCalendarMaker.UUID
import org.tasks.makers.CaldavCalendarMaker.newCaldavCalendar
import org.tasks.makers.CaldavTaskMaker import org.tasks.makers.CaldavTaskMaker
import org.tasks.makers.CaldavTaskMaker.CALENDAR import org.tasks.makers.CaldavTaskMaker.CALENDAR
import org.tasks.makers.CaldavTaskMaker.REMOTE_ID import org.tasks.makers.CaldavTaskMaker.REMOTE_ID
@ -79,6 +81,7 @@ class TaskMoverTest : InjectingTestCase() {
@Test @Test
fun moveBetweenCaldavList() = runBlocking { fun moveBetweenCaldavList() = runBlocking {
createTasks(1) createTasks(1)
caldavDao.insert(newCaldavCalendar(with(UUID, "1")))
caldavDao.insert(newCaldavTask(with(CaldavTaskMaker.TASK, 1L), with(CALENDAR, "1"))) caldavDao.insert(newCaldavTask(with(CaldavTaskMaker.TASK, 1L), with(CALENDAR, "1")))
moveToCaldavList("2", 1) moveToCaldavList("2", 1)
assertEquals("2", caldavDao.getTask(1)!!.calendar) assertEquals("2", caldavDao.getTask(1)!!.calendar)
@ -87,6 +90,7 @@ class TaskMoverTest : InjectingTestCase() {
@Test @Test
fun deleteCaldavTaskAfterMove() = runBlocking { fun deleteCaldavTaskAfterMove() = runBlocking {
createTasks(1) createTasks(1)
caldavDao.insert(newCaldavCalendar(with(UUID, "1")))
caldavDao.insert(newCaldavTask(with(CaldavTaskMaker.TASK, 1L), with(CALENDAR, "1"))) caldavDao.insert(newCaldavTask(with(CaldavTaskMaker.TASK, 1L), with(CALENDAR, "1")))
moveToCaldavList("2", 1) moveToCaldavList("2", 1)
val deleted = caldavDao.getMoved("1") val deleted = caldavDao.getMoved("1")
@ -100,6 +104,7 @@ class TaskMoverTest : InjectingTestCase() {
createTasks(1) createTasks(1)
createSubtask(2, 1) createSubtask(2, 1)
createSubtask(3, 2) createSubtask(3, 2)
caldavDao.insert(newCaldavCalendar(with(UUID, "1")))
caldavDao.insert( caldavDao.insert(
listOf( listOf(
newCaldavTask( newCaldavTask(
@ -196,6 +201,7 @@ class TaskMoverTest : InjectingTestCase() {
fun moveCaldavChildWithoutParent() = runBlocking { fun moveCaldavChildWithoutParent() = runBlocking {
createTasks(1) createTasks(1)
createSubtask(2, 1) createSubtask(2, 1)
caldavDao.insert(newCaldavCalendar(with(UUID, "1")))
caldavDao.insert( caldavDao.insert(
listOf( listOf(
newCaldavTask( newCaldavTask(
@ -266,6 +272,7 @@ class TaskMoverTest : InjectingTestCase() {
fun dontDuplicateWhenParentAndChildCaldavMoved() = runBlocking { fun dontDuplicateWhenParentAndChildCaldavMoved() = runBlocking {
createTasks(1) createTasks(1)
createSubtask(2, 1) createSubtask(2, 1)
caldavDao.insert(newCaldavCalendar(with(UUID, "1")))
caldavDao.insert( caldavDao.insert(
listOf( listOf(
newCaldavTask( newCaldavTask(

@ -7,17 +7,20 @@ import com.todoroo.astrid.data.Task
import dagger.hilt.android.testing.HiltAndroidTest import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.UninstallModules import dagger.hilt.android.testing.UninstallModules
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.junit.Before
import org.junit.Test import org.junit.Test
import org.tasks.SuspendFreeze.Companion.freezeAt import org.tasks.SuspendFreeze.Companion.freezeAt
import org.tasks.TestUtilities.assertEquals import org.tasks.TestUtilities.assertEquals
import org.tasks.caldav.VtodoCache
import org.tasks.data.CaldavCalendar
import org.tasks.data.CaldavDao import org.tasks.data.CaldavDao
import org.tasks.data.TaskDao import org.tasks.data.TaskDao
import org.tasks.injection.InjectingTestCase import org.tasks.injection.InjectingTestCase
import org.tasks.injection.ProductionModule import org.tasks.injection.ProductionModule
import org.tasks.makers.CaldavCalendarMaker.newCaldavCalendar
import org.tasks.makers.CaldavTaskMaker.CALENDAR import org.tasks.makers.CaldavTaskMaker.CALENDAR
import org.tasks.makers.CaldavTaskMaker.REMOTE_ID import org.tasks.makers.CaldavTaskMaker.REMOTE_ID
import org.tasks.makers.CaldavTaskMaker.TASK import org.tasks.makers.CaldavTaskMaker.TASK
import org.tasks.makers.CaldavTaskMaker.VTODO
import org.tasks.makers.CaldavTaskMaker.newCaldavTask import org.tasks.makers.CaldavTaskMaker.newCaldavTask
import org.tasks.makers.TaskMaker.DUE_DATE import org.tasks.makers.TaskMaker.DUE_DATE
import org.tasks.makers.TaskMaker.HIDE_TYPE import org.tasks.makers.TaskMaker.HIDE_TYPE
@ -34,11 +37,26 @@ class Upgrade_11_3_Test : InjectingTestCase() {
@Inject lateinit var caldavDao: CaldavDao @Inject lateinit var caldavDao: CaldavDao
@Inject lateinit var openTaskDao: TestOpenTaskDao @Inject lateinit var openTaskDao: TestOpenTaskDao
@Inject lateinit var upgrader: Upgrade_11_3 @Inject lateinit var upgrader: Upgrade_11_3
@Inject lateinit var vtodoCache: VtodoCache
private lateinit var calendar: CaldavCalendar
@Before
override fun setUp() {
super.setUp()
calendar = newCaldavCalendar()
runBlocking {
caldavDao.insert(calendar)
}
}
@Test @Test
fun applyRemoteiCalendarStartDate() = runBlocking { fun applyRemoteiCalendarStartDate() = runBlocking {
val taskId = taskDao.insert(newTask()) val taskId = taskDao.insert(newTask())
caldavDao.insert(newCaldavTask(with(TASK, taskId), with(VTODO, VTODO_WITH_START_DATE))) val caldavTask = newCaldavTask(with(TASK, taskId), with(CALENDAR, calendar.uuid))
caldavDao.insert(caldavTask)
vtodoCache.putVtodo(calendar, caldavTask, VTODO_WITH_START_DATE)
upgrader.applyiCalendarStartDates() upgrader.applyiCalendarStartDates()
assertEquals(DateTime(2021, 1, 21), taskDao.fetch(taskId)?.hideUntil) assertEquals(DateTime(2021, 1, 21), taskDao.fetch(taskId)?.hideUntil)
@ -50,7 +68,10 @@ class Upgrade_11_3_Test : InjectingTestCase() {
with(DUE_DATE, DateTime(2021, 1, 20)), with(DUE_DATE, DateTime(2021, 1, 20)),
with(HIDE_TYPE, Task.HIDE_UNTIL_DUE) with(HIDE_TYPE, Task.HIDE_UNTIL_DUE)
)) ))
caldavDao.insert(newCaldavTask(with(TASK, taskId), with(VTODO, VTODO_WITH_START_DATE))) val caldavTask = newCaldavTask(with(TASK, taskId), with(CALENDAR, calendar.uuid))
caldavDao.insert(caldavTask)
vtodoCache.putVtodo(calendar, caldavTask, VTODO_WITH_START_DATE)
upgrader.applyiCalendarStartDates() upgrader.applyiCalendarStartDates()
assertEquals(DateTime(2021, 1, 20), taskDao.fetch(taskId)?.hideUntil) assertEquals(DateTime(2021, 1, 20), taskDao.fetch(taskId)?.hideUntil)
@ -64,7 +85,9 @@ class Upgrade_11_3_Test : InjectingTestCase() {
with(HIDE_TYPE, Task.HIDE_UNTIL_DUE), with(HIDE_TYPE, Task.HIDE_UNTIL_DUE),
with(MODIFICATION_TIME, DateTime(2021, 1, 21, 9, 50, 4, 348)) with(MODIFICATION_TIME, DateTime(2021, 1, 21, 9, 50, 4, 348))
)) ))
caldavDao.insert(newCaldavTask(with(TASK, taskId), with(VTODO, VTODO_WITH_START_DATE))) val caldavTask = newCaldavTask(with(TASK, taskId), with(CALENDAR, calendar.uuid))
caldavDao.insert(caldavTask)
vtodoCache.putVtodo(calendar, caldavTask, VTODO_WITH_START_DATE)
freezeAt(upgradeTime) { freezeAt(upgradeTime) {
upgrader.applyiCalendarStartDates() upgrader.applyiCalendarStartDates()
@ -77,7 +100,9 @@ class Upgrade_11_3_Test : InjectingTestCase() {
fun dontTouchWhenNoiCalendarStartDate() = runBlocking { fun dontTouchWhenNoiCalendarStartDate() = runBlocking {
val modificationTime = DateTime(2021, 1, 21, 9, 50, 4, 348) val modificationTime = DateTime(2021, 1, 21, 9, 50, 4, 348)
val taskId = taskDao.insert(newTask(with(MODIFICATION_TIME, modificationTime))) val taskId = taskDao.insert(newTask(with(MODIFICATION_TIME, modificationTime)))
caldavDao.insert(newCaldavTask(with(TASK, taskId), with(VTODO, VTODO_NO_START_DATE))) val caldavTask = newCaldavTask(with(TASK, taskId), with(CALENDAR, calendar.uuid))
caldavDao.insert(caldavTask)
vtodoCache.putVtodo(calendar, caldavTask, VTODO_NO_START_DATE)
upgrader.applyiCalendarStartDates() upgrader.applyiCalendarStartDates()

@ -54,7 +54,7 @@ object TestUtilities {
val vtodo = readFile(path) val vtodo = readFile(path)
val remote = fromString(vtodo) val remote = fromString(vtodo)
task.applyRemote(remote, null) task.applyRemote(remote, null)
return Triple(task, CaldavTask().apply { this.vtodo = vtodo }, remote) return Triple(task, CaldavTask(), remote)
} }
private fun fromResource(path: String): at.bitfire.ical4android.Task = private fun fromResource(path: String): at.bitfire.ical4android.Task =

@ -0,0 +1,24 @@
package org.tasks.makers
import com.natpryce.makeiteasy.Instantiator
import com.natpryce.makeiteasy.Property
import com.natpryce.makeiteasy.Property.newProperty
import com.natpryce.makeiteasy.PropertyValue
import org.tasks.data.CaldavCalendar
import org.tasks.makers.Maker.make
object CaldavCalendarMaker {
val ACCOUNT: Property<CaldavCalendar, String> = newProperty()
val UUID: Property<CaldavCalendar, String> = newProperty()
private val instantiator = Instantiator<CaldavCalendar> { lookup ->
val calendar = CaldavCalendar()
calendar.account = lookup.valueOf(ACCOUNT, "account")
calendar.uuid = lookup.valueOf(UUID, "uuid")
calendar
}
fun newCaldavCalendar(vararg properties: PropertyValue<in CaldavCalendar?, *>): CaldavCalendar {
return make(instantiator, *properties)
}
}

@ -13,7 +13,6 @@ object CaldavTaskMaker {
val REMOTE_ID: Property<CaldavTask, String?> = newProperty() val REMOTE_ID: Property<CaldavTask, String?> = newProperty()
val REMOTE_PARENT: Property<CaldavTask, String?> = newProperty() val REMOTE_PARENT: Property<CaldavTask, String?> = newProperty()
val REMOTE_ORDER: Property<CaldavTask, Long?> = newProperty() val REMOTE_ORDER: Property<CaldavTask, Long?> = newProperty()
val VTODO: Property<CaldavTask, String?> = newProperty()
val ETAG: Property<CaldavTask, String?> = newProperty() val ETAG: Property<CaldavTask, String?> = newProperty()
val OBJECT: Property<CaldavTask, String?> = newProperty() val OBJECT: Property<CaldavTask, String?> = newProperty()
@ -21,7 +20,6 @@ object CaldavTaskMaker {
val task = CaldavTask(it.valueOf(TASK, 1L), it.valueOf(CALENDAR, "calendar")) val task = CaldavTask(it.valueOf(TASK, 1L), it.valueOf(CALENDAR, "calendar"))
task.remoteId = it.valueOf(REMOTE_ID, task.remoteId) task.remoteId = it.valueOf(REMOTE_ID, task.remoteId)
task.remoteParent = it.valueOf(REMOTE_PARENT, null as String?) task.remoteParent = it.valueOf(REMOTE_PARENT, null as String?)
task.vtodo = it.valueOf(VTODO, null as String?)
task.order = it.valueOf(REMOTE_ORDER, null as Long?) task.order = it.valueOf(REMOTE_ORDER, null as Long?)
task.etag = it.valueOf(ETAG, null as String?) task.etag = it.valueOf(ETAG, null as String?)
task.`object` = it.valueOf(OBJECT, task.remoteId?.let { id -> "$id.ics" }) task.`object` = it.valueOf(OBJECT, task.remoteId?.let { id -> "$id.ics" })

@ -61,7 +61,7 @@ import org.tasks.notifications.NotificationDao
Principal::class, Principal::class,
PrincipalAccess::class PrincipalAccess::class
], ],
version = 81 version = 82
) )
abstract class Database : RoomDatabase() { abstract class Database : RoomDatabase() {
abstract fun notificationDao(): NotificationDao abstract fun notificationDao(): NotificationDao

@ -5,13 +5,20 @@ import com.todoroo.astrid.data.Task
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.tasks.LocalBroadcastManager import org.tasks.LocalBroadcastManager
import org.tasks.data.* import org.tasks.caldav.VtodoCache
import org.tasks.data.CaldavAccount
import org.tasks.data.CaldavCalendar
import org.tasks.data.DeletionDao
import org.tasks.data.GoogleTaskAccount
import org.tasks.data.GoogleTaskDao
import org.tasks.data.GoogleTaskList
import org.tasks.data.TaskContainer
import org.tasks.data.TaskDao
import org.tasks.db.QueryUtils import org.tasks.db.QueryUtils
import org.tasks.db.SuspendDbUtils.chunkedMap import org.tasks.db.SuspendDbUtils.chunkedMap
import org.tasks.jobs.WorkManager import org.tasks.jobs.WorkManager
import org.tasks.preferences.Preferences import org.tasks.preferences.Preferences
import org.tasks.sync.SyncAdapters import org.tasks.sync.SyncAdapters
import java.util.*
import javax.inject.Inject import javax.inject.Inject
class TaskDeleter @Inject constructor( class TaskDeleter @Inject constructor(
@ -21,7 +28,9 @@ class TaskDeleter @Inject constructor(
private val localBroadcastManager: LocalBroadcastManager, private val localBroadcastManager: LocalBroadcastManager,
private val googleTaskDao: GoogleTaskDao, private val googleTaskDao: GoogleTaskDao,
private val preferences: Preferences, private val preferences: Preferences,
private val syncAdapters: SyncAdapters) { private val syncAdapters: SyncAdapters,
private val vtodoCache: VtodoCache,
) {
suspend fun markDeleted(item: Task) = markDeleted(persistentListOf(item.id)) suspend fun markDeleted(item: Task) = markDeleted(persistentListOf(item.id))
@ -73,12 +82,14 @@ class TaskDeleter @Inject constructor(
} }
suspend fun delete(list: CaldavCalendar) { suspend fun delete(list: CaldavCalendar) {
vtodoCache.delete(list)
val tasks = deletionDao.delete(list) val tasks = deletionDao.delete(list)
delete(tasks) delete(tasks)
localBroadcastManager.broadcastRefreshList() localBroadcastManager.broadcastRefreshList()
} }
suspend fun delete(list: CaldavAccount) { suspend fun delete(list: CaldavAccount) {
vtodoCache.delete(list)
val tasks = deletionDao.delete(list) val tasks = deletionDao.delete(list)
delete(tasks) delete(tasks)
localBroadcastManager.broadcastRefreshList() localBroadcastManager.broadcastRefreshList()

@ -9,11 +9,16 @@ import com.todoroo.astrid.data.Task
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import org.tasks.BuildConfig import org.tasks.BuildConfig
import org.tasks.LocalBroadcastManager import org.tasks.LocalBroadcastManager
import org.tasks.data.* import org.tasks.caldav.VtodoCache
import org.tasks.data.CaldavDao
import org.tasks.data.CaldavTask
import org.tasks.data.GoogleTask
import org.tasks.data.GoogleTaskDao
import org.tasks.data.GoogleTaskListDao
import org.tasks.data.TaskDao
import org.tasks.db.DbUtils.dbchunk import org.tasks.db.DbUtils.dbchunk
import org.tasks.preferences.Preferences import org.tasks.preferences.Preferences
import org.tasks.sync.SyncAdapters import org.tasks.sync.SyncAdapters
import java.util.*
import javax.inject.Inject import javax.inject.Inject
class TaskMover @Inject constructor( class TaskMover @Inject constructor(
@ -24,7 +29,9 @@ class TaskMover @Inject constructor(
private val googleTaskListDao: GoogleTaskListDao, private val googleTaskListDao: GoogleTaskListDao,
private val preferences: Preferences, private val preferences: Preferences,
private val localBroadcastManager: LocalBroadcastManager, private val localBroadcastManager: LocalBroadcastManager,
private val syncAdapters: SyncAdapters) { private val syncAdapters: SyncAdapters,
private val vtodoCache: VtodoCache,
) {
suspend fun getSingleFilter(tasks: List<Long>): Filter? { suspend fun getSingleFilter(tasks: List<Long>): Filter? {
val caldavCalendars = caldavDao.getCalendars(tasks) val caldavCalendars = caldavDao.getCalendars(tasks)
@ -129,15 +136,16 @@ class TaskMover @Inject constructor(
caldavDao.markDeleted(toDelete, DateUtilities.now()) caldavDao.markDeleted(toDelete, DateUtilities.now())
when (selected) { when (selected) {
is CaldavFilter -> { is CaldavFilter -> {
val from = caldavDao.getCalendar(caldavTask.calendar!!)
val id1 = caldavTask.task val id1 = caldavTask.task
val listId = selected.uuid val listId = selected.uuid
val newParent = CaldavTask(id1, listId, caldavTask.remoteId, caldavTask.`object`) val newParent = CaldavTask(id1, listId, caldavTask.remoteId, caldavTask.`object`)
newParent.vtodo = caldavTask.vtodo vtodoCache.move(from!!, selected.calendar, caldavTask)
caldavDao.insert(task, newParent, preferences.addTasksToTop()) caldavDao.insert(task, newParent, preferences.addTasksToTop())
children.takeIf { it.isNotEmpty() } children.takeIf { it.isNotEmpty() }
?.map { ?.map {
val newChild = CaldavTask(it.task, listId, it.remoteId, it.`object`) val newChild = CaldavTask(it.task, listId, it.remoteId, it.`object`)
newChild.vtodo = it.vtodo vtodoCache.move(from, selected.calendar, it)
newChild.remoteParent = it.remoteParent newChild.remoteParent = it.remoteParent
newChild newChild
} }

@ -2,6 +2,7 @@
package com.todoroo.astrid.service package com.todoroo.astrid.service
import org.tasks.caldav.VtodoCache
import org.tasks.caldav.iCalendar import org.tasks.caldav.iCalendar
import org.tasks.caldav.iCalendar.Companion.apply import org.tasks.caldav.iCalendar.Companion.apply
import org.tasks.data.OpenTaskDao import org.tasks.data.OpenTaskDao
@ -12,16 +13,21 @@ import javax.inject.Inject
class Upgrade_11_3 @Inject constructor( class Upgrade_11_3 @Inject constructor(
private val upgraderDao: UpgraderDao, private val upgraderDao: UpgraderDao,
private val openTaskDao: OpenTaskDao, private val openTaskDao: OpenTaskDao,
private val taskDao: TaskDao private val taskDao: TaskDao,
private val vtodoCache: VtodoCache,
) { ) {
internal suspend fun applyiCalendarStartDates() { internal suspend fun applyiCalendarStartDates() {
val (hasStartDate, noStartDate) = val (hasStartDate, noStartDate) =
upgraderDao.tasksWithVtodos().partition { it.startDate > 0 } upgraderDao.tasksWithVtodos().partition { it.startDate > 0 }
for (task in noStartDate) { for (task in noStartDate) {
task.vtodo?.let { iCalendar.fromVtodo(it) }?.dtStart?.let { vtodoCache
it.apply(task.task) .getVtodo(task.caldavTask)
upgraderDao.setStartDate(task.id, task.startDate) ?.let { iCalendar.fromVtodo(it) }
} ?.dtStart
?.let {
it.apply(task.task)
upgraderDao.setStartDate(task.id, task.startDate)
}
} }
hasStartDate hasStartDate
.map { it.id } .map { it.id }

@ -2,9 +2,11 @@
package com.todoroo.astrid.service package com.todoroo.astrid.service
import org.tasks.caldav.VtodoCache
import org.tasks.caldav.iCalendar import org.tasks.caldav.iCalendar
import org.tasks.caldav.iCalendar.Companion.reminders import org.tasks.caldav.iCalendar.Companion.reminders
import org.tasks.data.AlarmDao import org.tasks.data.AlarmDao
import org.tasks.data.CaldavTaskContainer
import org.tasks.data.TaskDao import org.tasks.data.TaskDao
import org.tasks.data.UpgraderDao import org.tasks.data.UpgraderDao
import javax.inject.Inject import javax.inject.Inject
@ -13,15 +15,17 @@ class Upgrade_12_4 @Inject constructor(
private val alarmDao: AlarmDao, private val alarmDao: AlarmDao,
private val taskDao: TaskDao, private val taskDao: TaskDao,
private val upgraderDao: UpgraderDao, private val upgraderDao: UpgraderDao,
private val vtodoCache: VtodoCache,
) { ) {
internal suspend fun syncExistingAlarms() { internal suspend fun syncExistingAlarms() {
val existingAlarms = alarmDao.getActiveAlarms() val existingAlarms = alarmDao.getActiveAlarms()
upgraderDao.tasksWithVtodos().forEach { caldav -> upgraderDao.tasksWithVtodos().map(CaldavTaskContainer::caldavTask).forEach { task ->
val remoteTask = caldav.vtodo?.let(iCalendar::fromVtodo) ?: return@forEach val remoteTask =
vtodoCache.getVtodo(task)?.let(iCalendar::fromVtodo) ?: return@forEach
remoteTask remoteTask
.reminders .reminders
.filter { existingAlarms.find { e -> e.task == caldav.id && e.same(it) } == null } .filter { existingAlarms.find { e -> e.task == task.task && e.same(it) } == null }
.onEach { it.task = caldav.id } .onEach { it.task = task.task }
.let { alarmDao.insert(it) } .let { alarmDao.insert(it) }
} }
taskDao.touch(existingAlarms.map { it.task }.toSet().toList()) taskDao.touch(existingAlarms.map { it.task }.toSet().toList())

@ -12,6 +12,7 @@ import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.tasks.R import org.tasks.R
import org.tasks.Strings.isNullOrEmpty import org.tasks.Strings.isNullOrEmpty
import org.tasks.caldav.VtodoCache
import org.tasks.caldav.iCalendar import org.tasks.caldav.iCalendar
import org.tasks.caldav.iCalendar.Companion.fromVtodo import org.tasks.caldav.iCalendar.Companion.fromVtodo
import org.tasks.caldav.iCalendar.Companion.order import org.tasks.caldav.iCalendar.Companion.order
@ -57,6 +58,7 @@ class Upgrader @Inject constructor(
private val widgetManager: AppWidgetManager, private val widgetManager: AppWidgetManager,
private val taskMover: TaskMover, private val taskMover: TaskMover,
private val upgraderDao: UpgraderDao, private val upgraderDao: UpgraderDao,
private val vtodoCache: VtodoCache,
private val upgrade_11_3: Lazy<Upgrade_11_3>, private val upgrade_11_3: Lazy<Upgrade_11_3>,
private val upgrade_11_12_3: Lazy<Upgrade_11_12_3>, private val upgrade_11_12_3: Lazy<Upgrade_11_12_3>,
private val upgrade_12_4: Lazy<Upgrade_12_4>, private val upgrade_12_4: Lazy<Upgrade_12_4>,
@ -150,10 +152,9 @@ class Upgrader @Inject constructor(
return getAndroidColor(context, index) return getAndroidColor(context, index)
} }
private suspend fun applyCaldavOrder() { private suspend fun applyCaldavOrder() {
for (task in upgraderDao.tasksWithVtodos().map(CaldavTaskContainer::caldavTask)) { for (task in upgraderDao.tasksWithVtodos().map(CaldavTaskContainer::caldavTask)) {
val remoteTask = fromVtodo(task.vtodo!!) ?: continue val remoteTask = vtodoCache.getVtodo(task)?.let { fromVtodo(it) } ?: continue
val order: Long? = remoteTask.order val order: Long? = remoteTask.order
if (order != null) { if (order != null) {
task.order = order task.order = order
@ -169,7 +170,7 @@ class Upgrader @Inject constructor(
if (tasksWithLocations.contains(taskId)) { if (tasksWithLocations.contains(taskId)) {
continue continue
} }
val remoteTask = fromVtodo(task.vtodo!!) ?: continue val remoteTask = vtodoCache.getVtodo(task)?.let { fromVtodo(it) } ?: continue
val geo = remoteTask.geoPosition ?: continue val geo = remoteTask.geoPosition ?: continue
iCal.setPlace(taskId, geo) iCal.setPlace(taskId, geo)
} }
@ -179,7 +180,7 @@ class Upgrader @Inject constructor(
private suspend fun applyCaldavSubtasks() { private suspend fun applyCaldavSubtasks() {
val updated: MutableList<CaldavTask> = ArrayList() val updated: MutableList<CaldavTask> = ArrayList()
for (task in upgraderDao.tasksWithVtodos().map(CaldavTaskContainer::caldavTask)) { for (task in upgraderDao.tasksWithVtodos().map(CaldavTaskContainer::caldavTask)) {
val remoteTask = fromVtodo(task.vtodo!!) ?: continue val remoteTask = vtodoCache.getVtodo(task)?.let { fromVtodo(it) } ?: continue
task.remoteParent = remoteTask.parent task.remoteParent = remoteTask.parent
if (!isNullOrEmpty(task.remoteParent)) { if (!isNullOrEmpty(task.remoteParent)) {
updated.add(task) updated.add(task)
@ -192,10 +193,9 @@ class Upgrader @Inject constructor(
private suspend fun applyCaldavCategories() { private suspend fun applyCaldavCategories() {
val tasksWithTags: List<Long> = upgraderDao.tasksWithTags() val tasksWithTags: List<Long> = upgraderDao.tasksWithTags()
for (container in upgraderDao.tasksWithVtodos()) { for (container in upgraderDao.tasksWithVtodos()) {
val remoteTask = fromVtodo(container.vtodo!!) val remoteTask =
if (remoteTask != null) { vtodoCache.getVtodo(container.caldavTask)?.let { fromVtodo(it) } ?: continue
tagDao.insert(container.task, iCal.getTags(remoteTask.categories)) tagDao.insert(container.task, iCal.getTags(remoteTask.categories))
}
} }
taskDao.touch(tasksWithTags) taskDao.touch(tasksWithTags)
} }

@ -2,7 +2,21 @@ package org.tasks.backup
import com.todoroo.astrid.data.Task import com.todoroo.astrid.data.Task
import org.tasks.backup.TasksJsonImporter.LegacyLocation import org.tasks.backup.TasksJsonImporter.LegacyLocation
import org.tasks.data.* import org.tasks.data.Alarm
import org.tasks.data.CaldavAccount
import org.tasks.data.CaldavCalendar
import org.tasks.data.CaldavTask
import org.tasks.data.Filter
import org.tasks.data.Geofence
import org.tasks.data.GoogleTask
import org.tasks.data.GoogleTaskAccount
import org.tasks.data.GoogleTaskList
import org.tasks.data.Place
import org.tasks.data.Tag
import org.tasks.data.TagData
import org.tasks.data.TaskAttachment
import org.tasks.data.TaskListMetadata
import org.tasks.data.UserActivity
class BackupContainer( class BackupContainer(
val tasks: List<TaskBackup>?, val tasks: List<TaskBackup>?,
@ -28,8 +42,9 @@ class BackupContainer(
val google: List<GoogleTask>, val google: List<GoogleTask>,
val comments: List<UserActivity>, val comments: List<UserActivity>,
val attachments: List<TaskAttachment>?, val attachments: List<TaskAttachment>?,
val caldavTasks: List<CaldavTask>?) { val caldavTasks: List<CaldavTask>?,
val vtodo: String?,
) {
val locations: List<LegacyLocation> = emptyList() val locations: List<LegacyLocation> = emptyList()
} }
} }

@ -14,7 +14,19 @@ import com.todoroo.astrid.data.Task
import org.tasks.BuildConfig import org.tasks.BuildConfig
import org.tasks.R import org.tasks.R
import org.tasks.backup.BackupContainer.TaskBackup import org.tasks.backup.BackupContainer.TaskBackup
import org.tasks.data.* import org.tasks.caldav.VtodoCache
import org.tasks.data.AlarmDao
import org.tasks.data.CaldavDao
import org.tasks.data.FilterDao
import org.tasks.data.GoogleTaskDao
import org.tasks.data.GoogleTaskListDao
import org.tasks.data.LocationDao
import org.tasks.data.TagDao
import org.tasks.data.TagDataDao
import org.tasks.data.TaskAttachmentDao
import org.tasks.data.TaskDao
import org.tasks.data.TaskListMetadataDao
import org.tasks.data.UserActivityDao
import org.tasks.date.DateTimeUtils.newDateTime import org.tasks.date.DateTimeUtils.newDateTime
import org.tasks.extensions.Context.toast import org.tasks.extensions.Context.toast
import org.tasks.files.FileHelper import org.tasks.files.FileHelper
@ -26,7 +38,6 @@ import java.io.IOException
import java.io.OutputStream import java.io.OutputStream
import java.io.OutputStreamWriter import java.io.OutputStreamWriter
import java.nio.charset.Charset import java.nio.charset.Charset
import java.util.*
import javax.inject.Inject import javax.inject.Inject
class TasksJsonExporter @Inject constructor( class TasksJsonExporter @Inject constructor(
@ -43,7 +54,9 @@ class TasksJsonExporter @Inject constructor(
private val taskAttachmentDao: TaskAttachmentDao, private val taskAttachmentDao: TaskAttachmentDao,
private val caldavDao: CaldavDao, private val caldavDao: CaldavDao,
private val workManager: WorkManager, private val workManager: WorkManager,
private val taskListMetadataDao: TaskListMetadataDao) { private val taskListMetadataDao: TaskListMetadataDao,
private val vtodoCache: VtodoCache,
) {
private var context: Context? = null private var context: Context? = null
private var exportCount = 0 private var exportCount = 0
@ -108,16 +121,19 @@ class TasksJsonExporter @Inject constructor(
for (task in tasks) { for (task in tasks) {
setProgress(taskBackups.size, tasks.size) setProgress(taskBackups.size, tasks.size)
val taskId = task.id val taskId = task.id
val caldavTasks = caldavDao.getTasks(taskId)
taskBackups.add( taskBackups.add(
TaskBackup( TaskBackup(
task, task,
alarmDao.getAlarms(taskId), alarmDao.getAlarms(taskId),
locationDao.getGeofencesForTask(taskId), locationDao.getGeofencesForTask(taskId),
tagDao.getTagsForTask(taskId), tagDao.getTagsForTask(taskId),
googleTaskDao.getAllByTaskId(taskId), googleTaskDao.getAllByTaskId(taskId),
userActivityDao.getCommentsForTask(task.uuid), userActivityDao.getCommentsForTask(task.uuid),
taskAttachmentDao.getAttachments(task.uuid), taskAttachmentDao.getAttachments(task.uuid),
caldavDao.getTasks(taskId))) caldavTasks,
vtodoCache.getVtodo( caldavTasks.firstOrNull { !it.isDeleted() })
))
} }
val data: MutableMap<String, Any> = HashMap() val data: MutableMap<String, Any> = HashMap()
data["version"] = BuildConfig.VERSION_CODE data["version"] = BuildConfig.VERSION_CODE

@ -16,6 +16,7 @@ import com.todoroo.astrid.service.Upgrader.Companion.V6_4
import com.todoroo.astrid.service.Upgrader.Companion.getAndroidColor import com.todoroo.astrid.service.Upgrader.Companion.getAndroidColor
import org.tasks.LocalBroadcastManager import org.tasks.LocalBroadcastManager
import org.tasks.R import org.tasks.R
import org.tasks.caldav.VtodoCache
import org.tasks.data.Alarm import org.tasks.data.Alarm
import org.tasks.data.Alarm.Companion.TYPE_SNOOZE import org.tasks.data.Alarm.Companion.TYPE_SNOOZE
import org.tasks.data.AlarmDao import org.tasks.data.AlarmDao
@ -56,7 +57,9 @@ class TasksJsonImporter @Inject constructor(
private val caldavDao: CaldavDao, private val caldavDao: CaldavDao,
private val preferences: Preferences, private val preferences: Preferences,
private val taskMover: TaskMover, private val taskMover: TaskMover,
private val taskListMetadataDao: TaskListMetadataDao) { private val taskListMetadataDao: TaskListMetadataDao,
private val vtodoCache: VtodoCache,
) {
private val result = ImportResult() private val result = ImportResult()
@ -219,6 +222,12 @@ class TasksJsonImporter @Inject constructor(
caldavTask.task = taskId caldavTask.task = taskId
caldavDao.insert(caldavTask) caldavDao.insert(caldavTask)
} }
backup.vtodo?.let {
val caldavTask =
backup.caldavTasks?.firstOrNull { t -> !t.isDeleted() } ?: return@let
val caldavCalendar = caldavDao.getCalendar(caldavTask.calendar!!) ?: return@let
vtodoCache.putVtodo(caldavCalendar, caldavTask, it)
}
result.importCount++ result.importCount++
} }
googleTaskDao.updateParents() googleTaskDao.updateParents()

@ -92,6 +92,7 @@ class CaldavSynchronizer @Inject constructor(
private val provider: CaldavClientProvider, private val provider: CaldavClientProvider,
private val iCal: iCalendar, private val iCal: iCalendar,
private val principalDao: PrincipalDao, private val principalDao: PrincipalDao,
private val vtodoCache: VtodoCache,
) { ) {
suspend fun sync(account: CaldavAccount) { suspend fun sync(account: CaldavAccount) {
Thread.currentThread().contextClassLoader = context.classLoader Thread.currentThread().contextClassLoader = context.classLoader
@ -293,11 +294,11 @@ class CaldavSynchronizer @Inject constructor(
private suspend fun pushLocalChanges( private suspend fun pushLocalChanges(
caldavCalendar: CaldavCalendar, httpClient: OkHttpClient, httpUrl: HttpUrl) { caldavCalendar: CaldavCalendar, httpClient: OkHttpClient, httpUrl: HttpUrl) {
for (task in caldavDao.getMoved(caldavCalendar.uuid!!)) { for (task in caldavDao.getMoved(caldavCalendar.uuid!!)) {
deleteRemoteResource(httpClient, httpUrl, task) deleteRemoteResource(httpClient, httpUrl, caldavCalendar, task)
} }
for (task in taskDao.getCaldavTasksToPush(caldavCalendar.uuid!!)) { for (task in taskDao.getCaldavTasksToPush(caldavCalendar.uuid!!)) {
try { try {
pushTask(task, httpClient, httpUrl) pushTask(caldavCalendar, task, httpClient, httpUrl)
} catch (e: IOException) { } catch (e: IOException) {
Timber.e(e) Timber.e(e)
} }
@ -305,7 +306,11 @@ class CaldavSynchronizer @Inject constructor(
} }
private suspend fun deleteRemoteResource( private suspend fun deleteRemoteResource(
httpClient: OkHttpClient, httpUrl: HttpUrl, caldavTask: CaldavTask): Boolean { httpClient: OkHttpClient,
httpUrl: HttpUrl,
calendar: CaldavCalendar,
caldavTask: CaldavTask
): Boolean {
try { try {
if (!isNullOrEmpty(caldavTask.`object`)) { if (!isNullOrEmpty(caldavTask.`object`)) {
val remote = DavResource( val remote = DavResource(
@ -321,20 +326,26 @@ class CaldavSynchronizer @Inject constructor(
Timber.e(e) Timber.e(e)
return false return false
} }
vtodoCache.delete(calendar, caldavTask)
caldavDao.delete(caldavTask) caldavDao.delete(caldavTask)
return true return true
} }
private suspend fun pushTask(task: Task, httpClient: OkHttpClient, httpUrl: HttpUrl) { private suspend fun pushTask(
calendar: CaldavCalendar,
task: Task,
httpClient: OkHttpClient,
httpUrl: HttpUrl
) {
Timber.d("pushing %s", task) Timber.d("pushing %s", task)
val caldavTask = caldavDao.getTask(task.id) ?: return val caldavTask = caldavDao.getTask(task.id) ?: return
if (task.isDeleted) { if (task.isDeleted) {
if (deleteRemoteResource(httpClient, httpUrl, caldavTask)) { if (deleteRemoteResource(httpClient, httpUrl, calendar, caldavTask)) {
taskDeleter.delete(task) taskDeleter.delete(task)
} }
return return
} }
val data = iCal.toVtodo(caldavTask, task) val data = iCal.toVtodo(calendar, caldavTask, task)
val requestBody = RequestBody.create(MIME_ICALENDAR, data) val requestBody = RequestBody.create(MIME_ICALENDAR, data)
try { try {
val remote = DavResource( val remote = DavResource(
@ -343,7 +354,7 @@ class CaldavSynchronizer @Inject constructor(
val getETag = fromResponse(it) val getETag = fromResponse(it)
if (getETag != null && !isNullOrEmpty(getETag.eTag)) { if (getETag != null && !isNullOrEmpty(getETag.eTag)) {
caldavTask.etag = getETag.eTag caldavTask.etag = getETag.eTag
caldavTask.vtodo = String(data) vtodoCache.putVtodo(calendar, caldavTask, String(data))
} }
} }
} catch (e: HttpException) { } catch (e: HttpException) {

@ -0,0 +1,30 @@
package org.tasks.caldav
import android.content.Context
import dagger.hilt.android.qualifiers.ApplicationContext
import java.io.File
import javax.inject.Inject
class FileStorage @Inject constructor(
@ApplicationContext context: Context
) {
val root = File(context.filesDir, "vtodo")
@Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS")
fun getFile(vararg segments: String?): File? =
if (segments.none { it.isNullOrBlank() }) {
segments.fold(root) { f, p -> File(f, p) }
} else {
null
}
fun read(file: File?): String? = file?.takeIf { it.exists() }?.readText()
fun write(file: File, data: String?) {
if (data.isNullOrBlank()) {
file.delete()
} else {
file.writeText(data)
}
}
}

@ -0,0 +1,76 @@
package org.tasks.caldav
import org.tasks.data.CaldavAccount
import org.tasks.data.CaldavCalendar
import org.tasks.data.CaldavDao
import org.tasks.data.CaldavTask
import java.io.File
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class VtodoCache @Inject constructor(
private val caldavDao: CaldavDao,
private val fileStorage: FileStorage,
) {
fun move(from: CaldavCalendar, to: CaldavCalendar, task: CaldavTask) {
val source =
fileStorage.getFile(from.account, from.uuid, task.`object`)
if (source?.exists() != true) {
return
}
val target =
fileStorage.getFile(to.account, to.uuid)
?.apply { mkdirs() }
?.let { File(it, task.`object`!!) }
?: return
source.copyTo(target, overwrite = true)
source.delete()
}
suspend fun getVtodo(caldavTask: CaldavTask?): String? {
if (caldavTask == null) {
return null
}
val calendar = caldavDao.getCalendar(caldavTask.calendar!!) ?: return null
return getVtodo(calendar, caldavTask)
}
fun getVtodo(calendar: CaldavCalendar?, caldavTask: CaldavTask?): String? {
val file = fileStorage.getFile(
calendar?.account,
caldavTask?.calendar,
caldavTask?.`object`
)
return fileStorage.read(file)
}
fun putVtodo(calendar: CaldavCalendar, caldavTask: CaldavTask, vtodo: String?) {
val directory =
fileStorage
.getFile(calendar.account, caldavTask.calendar)
?.apply { mkdirs() }
?: return
fileStorage.write(File(directory, caldavTask.`object`!!), vtodo)
}
suspend fun delete(taskIds: List<Long>) {
val tasks = caldavDao.getTasks(taskIds).groupBy { it.calendar!! }
tasks.forEach { (c, t) ->
val calendar = caldavDao.getCalendar(c) ?: return@forEach
t.forEach { delete(calendar, it) }
}
}
fun delete(calendar: CaldavCalendar, caldavTask: CaldavTask) {
fileStorage
.getFile(calendar.account!!, caldavTask.calendar!!, caldavTask.`object`!!)
?.delete()
}
fun delete(calendar: CaldavCalendar) =
fileStorage.getFile(calendar.account!!, calendar.uuid!!)?.deleteRecursively()
fun delete(account: CaldavAccount) =
fileStorage.getFile(account.uuid!!)?.deleteRecursively()
}

@ -76,6 +76,7 @@ class iCalendar @Inject constructor(
private val caldavDao: CaldavDao, private val caldavDao: CaldavDao,
private val alarmDao: AlarmDao, private val alarmDao: AlarmDao,
private val alarmService: AlarmService, private val alarmService: AlarmService,
private val vtodoCache: VtodoCache,
) { ) {
suspend fun setPlace(taskId: Long, geo: Geo?) { suspend fun setPlace(taskId: Long, geo: Geo?) {
@ -123,11 +124,16 @@ class iCalendar @Inject constructor(
return tags return tags
} }
suspend fun toVtodo(caldavTask: CaldavTask, task: com.todoroo.astrid.data.Task): ByteArray { suspend fun toVtodo(
calendar: CaldavCalendar,
caldavTask: CaldavTask,
task: com.todoroo.astrid.data.Task
): ByteArray {
var remoteModel: Task? = null var remoteModel: Task? = null
try { try {
if (!isNullOrEmpty(caldavTask.vtodo)) { val vtodo = vtodoCache.getVtodo(calendar, caldavTask)
remoteModel = fromVtodo(caldavTask.vtodo!!) if (vtodo?.isNotBlank() == true) {
remoteModel = fromVtodo(vtodo)
} }
} catch (e: java.lang.Exception) { } catch (e: java.lang.Exception) {
Timber.e(e) Timber.e(e)
@ -180,7 +186,7 @@ class iCalendar @Inject constructor(
} }
val caldavTask = existing ?: CaldavTask(task.id, calendar.uuid, remote.uid, obj) val caldavTask = existing ?: CaldavTask(task.id, calendar.uuid, remote.uid, obj)
val dirty = task.modificationDate > caldavTask.lastSync || caldavTask.lastSync == 0L val dirty = task.modificationDate > caldavTask.lastSync || caldavTask.lastSync == 0L
val local = caldavTask.vtodo?.let { fromVtodo(it) } val local = vtodoCache.getVtodo(calendar, caldavTask)?.let { fromVtodo(it) }
task.applyRemote(remote, local) task.applyRemote(remote, local)
caldavTask.applyRemote(remote, local) caldavTask.applyRemote(remote, local)
@ -207,7 +213,7 @@ class iCalendar @Inject constructor(
task.suppressSync() task.suppressSync()
task.suppressRefresh() task.suppressRefresh()
taskDao.save(task) taskDao.save(task)
caldavTask.vtodo = vtodo vtodoCache.putVtodo(calendar, caldavTask, vtodo)
caldavTask.etag = eTag caldavTask.etag = eTag
if (!dirty) { if (!dirty) {
caldavTask.lastSync = task.modificationDate caldavTask.lastSync = task.modificationDate

@ -1,6 +1,10 @@
package org.tasks.data package org.tasks.data
import androidx.room.* import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.Ignore
import androidx.room.Index
import androidx.room.PrimaryKey
import com.todoroo.andlib.data.Table import com.todoroo.andlib.data.Table
import com.todoroo.astrid.helper.UUIDHelper import com.todoroo.astrid.helper.UUIDHelper
@ -34,9 +38,6 @@ class CaldavTask {
@ColumnInfo(name = "cd_deleted") @ColumnInfo(name = "cd_deleted")
var deleted: Long = 0 var deleted: Long = 0
@ColumnInfo(name = "cd_vtodo")
var vtodo: String? = null
@ColumnInfo(name = "cd_remote_parent") @ColumnInfo(name = "cd_remote_parent")
var remoteParent: String? = null var remoteParent: String? = null
@ -65,7 +66,7 @@ class CaldavTask {
fun isDeleted() = deleted > 0 fun isDeleted() = deleted > 0
override fun toString(): String = override fun toString(): String =
"CaldavTask(id=$id, task=$task, calendar=$calendar, `object`=$`object`, remoteId=$remoteId, etag=$etag, lastSync=$lastSync, deleted=$deleted, vtodo=$vtodo, remoteParent=$remoteParent, order=$order)" "CaldavTask(id=$id, task=$task, calendar=$calendar, `object`=$`object`, remoteId=$remoteId, etag=$etag, lastSync=$lastSync, deleted=$deleted, remoteParent=$remoteParent, order=$order)"
companion object { companion object {
const val KEY = "caldav" const val KEY = "caldav"

@ -17,9 +17,6 @@ class CaldavTaskContainer {
val isDeleted: Boolean val isDeleted: Boolean
get() = task.isDeleted get() = task.isDeleted
val vtodo: String?
get() = caldavTask.vtodo
val sortOrder: Long val sortOrder: Long
get() = caldavTask.order ?: DateTime(task.creationDate).toAppleEpoch() get() = caldavTask.order ?: DateTime(task.creationDate).toAppleEpoch()

@ -7,7 +7,6 @@ import androidx.room.Transaction
import org.tasks.data.CaldavDao.Companion.LOCAL import org.tasks.data.CaldavDao.Companion.LOCAL
import org.tasks.db.SuspendDbUtils.chunkedMap import org.tasks.db.SuspendDbUtils.chunkedMap
import org.tasks.db.SuspendDbUtils.eachChunk import org.tasks.db.SuspendDbUtils.eachChunk
import java.util.*
@Dao @Dao
abstract class DeletionDao { abstract class DeletionDao {

@ -10,8 +10,6 @@ SELECT task.*, caldav_task.*
FROM tasks AS task FROM tasks AS task
INNER JOIN caldav_tasks AS caldav_task ON _id = cd_task INNER JOIN caldav_tasks AS caldav_task ON _id = cd_task
WHERE cd_deleted = 0 WHERE cd_deleted = 0
AND cd_vtodo IS NOT NULL
AND cd_vtodo != ''
""") """)
suspend fun tasksWithVtodos(): List<CaldavTaskContainer> suspend fun tasksWithVtodos(): List<CaldavTaskContainer>

@ -9,11 +9,13 @@ import com.todoroo.astrid.data.Task.Companion.NOTIFY_AT_DEADLINE
import com.todoroo.astrid.data.Task.Companion.NOTIFY_AT_START import com.todoroo.astrid.data.Task.Companion.NOTIFY_AT_START
import com.todoroo.astrid.data.Task.Companion.NOTIFY_MODE_FIVE import com.todoroo.astrid.data.Task.Companion.NOTIFY_MODE_FIVE
import com.todoroo.astrid.data.Task.Companion.NOTIFY_MODE_NONSTOP import com.todoroo.astrid.data.Task.Companion.NOTIFY_MODE_NONSTOP
import org.tasks.caldav.FileStorage
import org.tasks.data.Alarm.Companion.TYPE_RANDOM import org.tasks.data.Alarm.Companion.TYPE_RANDOM
import org.tasks.data.Alarm.Companion.TYPE_REL_END import org.tasks.data.Alarm.Companion.TYPE_REL_END
import org.tasks.data.Alarm.Companion.TYPE_REL_START import org.tasks.data.Alarm.Companion.TYPE_REL_START
import org.tasks.data.Alarm.Companion.TYPE_SNOOZE import org.tasks.data.Alarm.Companion.TYPE_SNOOZE
import org.tasks.data.CaldavAccount.Companion.SERVER_UNKNOWN import org.tasks.data.CaldavAccount.Companion.SERVER_UNKNOWN
import org.tasks.extensions.getString
import timber.log.Timber import timber.log.Timber
import java.util.concurrent.TimeUnit.HOURS import java.util.concurrent.TimeUnit.HOURS
@ -443,7 +445,35 @@ object Migrations {
} }
} }
val MIGRATIONS = arrayOf( @Suppress("FunctionName")
private fun migration_81_82(fileStorage: FileStorage) = object : Migration(81, 82) {
override fun migrate(database: SupportSQLiteDatabase) {
database
.query("SELECT `cdl_account`, `cd_calendar`, `cd_object`, `cd_vtodo` FROM `caldav_tasks` INNER JOIN `caldav_lists` ON `cdl_uuid` = `cd_calendar`")
.use {
while (it.moveToNext()) {
val file = fileStorage.getFile(
it.getString("cdl_account"),
it.getString("cd_calendar"),
it.getString("cd_object"),
) ?: continue
fileStorage.write(file, it.getString("cd_vtodo"))
}
}
database.execSQL("ALTER TABLE `caldav_tasks` RENAME TO `caldav_tasks-temp`")
database.execSQL(
"CREATE TABLE IF NOT EXISTS `caldav_tasks` (`cd_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `cd_task` INTEGER NOT NULL, `cd_calendar` TEXT, `cd_object` TEXT, `cd_remote_id` TEXT, `cd_etag` TEXT, `cd_last_sync` INTEGER NOT NULL, `cd_deleted` INTEGER NOT NULL, `cd_remote_parent` TEXT, `cd_order` INTEGER)"
)
database.execSQL("DROP INDEX `cd_task`")
database.execSQL("CREATE INDEX IF NOT EXISTS `cd_task` ON `caldav_tasks` (`cd_task`)")
database.execSQL(
"INSERT INTO `caldav_tasks` (`cd_id`, `cd_task`, `cd_calendar`, `cd_object`, `cd_remote_id`, `cd_etag`, `cd_last_sync`, `cd_deleted`, `cd_remote_parent`, `cd_order`) SELECT `cd_id`, `cd_task`, `cd_calendar`, `cd_object`, `cd_remote_id`, `cd_etag`, `cd_last_sync`, `cd_deleted`, `cd_remote_parent`, `cd_order` FROM `caldav_tasks-temp`"
)
database.execSQL("DROP TABLE `caldav_tasks-temp`")
}
}
fun migrations(fileStorage: FileStorage) = arrayOf(
MIGRATION_35_36, MIGRATION_35_36,
MIGRATION_36_37, MIGRATION_36_37,
MIGRATION_37_38, MIGRATION_37_38,
@ -481,6 +511,7 @@ object Migrations {
MIGRATION_78_79, MIGRATION_78_79,
MIGRATION_79_80, MIGRATION_79_80,
MIGRATION_80_81, MIGRATION_80_81,
migration_81_82(fileStorage),
) )
private fun noop(from: Int, to: Int): Migration = object : Migration(from, to) { private fun noop(from: Int, to: Int): Migration = object : Migration(from, to) {

@ -19,6 +19,7 @@ import org.tasks.LocalBroadcastManager
import org.tasks.R import org.tasks.R
import org.tasks.Strings.isNullOrEmpty import org.tasks.Strings.isNullOrEmpty
import org.tasks.billing.Inventory import org.tasks.billing.Inventory
import org.tasks.caldav.VtodoCache
import org.tasks.caldav.iCalendar import org.tasks.caldav.iCalendar
import org.tasks.caldav.iCalendar.Companion.fromVtodo import org.tasks.caldav.iCalendar.Companion.fromVtodo
import org.tasks.data.CaldavAccount import org.tasks.data.CaldavAccount
@ -35,7 +36,9 @@ class EtebaseSynchronizer @Inject constructor(
private val taskDeleter: TaskDeleter, private val taskDeleter: TaskDeleter,
private val inventory: Inventory, private val inventory: Inventory,
private val clientProvider: EtebaseClientProvider, private val clientProvider: EtebaseClientProvider,
private val iCal: iCalendar) { private val iCal: iCalendar,
private val vtodoCache: VtodoCache,
) {
companion object { companion object {
init { init {
prodId = ProdId("+//IDN tasks.org//android-" + BuildConfig.VERSION_CODE + "//EN") prodId = ProdId("+//IDN tasks.org//android-" + BuildConfig.VERSION_CODE + "//EN")
@ -142,7 +145,10 @@ class EtebaseSynchronizer @Inject constructor(
for (caldavTask in caldavDao.getMoved(caldavCalendar.uuid!!)) { for (caldavTask in caldavDao.getMoved(caldavCalendar.uuid!!)) {
client.deleteItem(collection, caldavTask) client.deleteItem(collection, caldavTask)
?.let { changes.add(it) } ?.let { changes.add(it) }
?: caldavDao.delete(caldavTask) ?: run {
vtodoCache.delete(caldavCalendar, caldavTask)
caldavDao.delete(caldavTask)
}
} }
for (change in caldavDao.getCaldavTasksToPush(caldavCalendar.uuid!!)) { for (change in caldavDao.getCaldavTasksToPush(caldavCalendar.uuid!!)) {
val task = change.task val task = change.task
@ -154,7 +160,11 @@ class EtebaseSynchronizer @Inject constructor(
?: taskDeleter.delete(task) ?: taskDeleter.delete(task)
} else { } else {
changes.add( changes.add(
client.updateItem(collection, caldavTask, iCal.toVtodo(caldavTask, task)) client.updateItem(
collection,
caldavTask,
iCal.toVtodo(caldavCalendar, caldavTask, task)
)
) )
} }
} }
@ -178,6 +188,7 @@ class EtebaseSynchronizer @Inject constructor(
if (item.isDeleted) { if (item.isDeleted) {
if (caldavTask != null) { if (caldavTask != null) {
if (caldavTask.isDeleted()) { if (caldavTask.isDeleted()) {
vtodoCache.delete(caldavCalendar, caldavTask)
caldavDao.delete(caldavTask) caldavDao.delete(caldavTask)
} else { } else {
taskDeleter.delete(caldavTask.task) taskDeleter.delete(caldavTask.task)
@ -185,7 +196,7 @@ class EtebaseSynchronizer @Inject constructor(
} }
} else if (isLocalChange) { } else if (isLocalChange) {
caldavTask?.let { caldavTask?.let {
it.vtodo = vtodo vtodoCache.putVtodo(caldavCalendar, it, vtodo)
it.lastSync = item.meta.mtime ?: currentTimeMillis() it.lastSync = item.meta.mtime ?: currentTimeMillis()
caldavDao.update(it) caldavDao.update(it)
} }

@ -0,0 +1,6 @@
package org.tasks.extensions
import android.database.Cursor
fun Cursor.getString(columnName: String): String? =
getColumnIndex(columnName).takeIf { it >= 0 }?.let { getString(it) }

@ -10,6 +10,7 @@ import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent import dagger.hilt.components.SingletonComponent
import org.tasks.BuildConfig import org.tasks.BuildConfig
import org.tasks.R import org.tasks.R
import org.tasks.caldav.FileStorage
import org.tasks.data.CaldavDao import org.tasks.data.CaldavDao
import org.tasks.data.GoogleTaskListDao import org.tasks.data.GoogleTaskListDao
import org.tasks.data.OpenTaskDao import org.tasks.data.OpenTaskDao
@ -26,9 +27,13 @@ import javax.inject.Singleton
internal class ProductionModule { internal class ProductionModule {
@Provides @Provides
@Singleton @Singleton
fun getAppDatabase(@ApplicationContext context: Context, preferences: Preferences): Database { fun getAppDatabase(
@ApplicationContext context: Context,
preferences: Preferences,
fileStorage: FileStorage,
): Database {
val builder = Room.databaseBuilder(context, Database::class.java, Database.NAME) val builder = Room.databaseBuilder(context, Database::class.java, Database.NAME)
.addMigrations(*Migrations.MIGRATIONS) .addMigrations(*Migrations.migrations(fileStorage))
if (!BuildConfig.DEBUG || !preferences.getBoolean(R.string.p_crash_main_queries, false)) { if (!BuildConfig.DEBUG || !preferences.getBoolean(R.string.p_crash_main_queries, false)) {
builder.allowMainThreadQueries() builder.allowMainThreadQueries()
} }

Loading…
Cancel
Save