vtodo cache improvements

pull/3212/head
Alex Baker 11 months ago
parent f17a287e4f
commit aa477a7b11

@ -25,6 +25,7 @@ import at.bitfire.ical4android.ICalendar.Companion.prodId
import com.todoroo.astrid.dao.TaskDao import com.todoroo.astrid.dao.TaskDao
import com.todoroo.astrid.service.TaskDeleter import com.todoroo.astrid.service.TaskDeleter
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.runBlocking
import net.fortuna.ical4j.model.property.ProdId import net.fortuna.ical4j.model.property.ProdId
import okhttp3.Headers import okhttp3.Headers
import okhttp3.HttpUrl import okhttp3.HttpUrl
@ -291,7 +292,9 @@ class CaldavSynchronizer @Inject constructor(
.takeIf { it.isNotEmpty() } .takeIf { it.isNotEmpty() }
?.let { ?.let {
Timber.d("DELETED $it") Timber.d("DELETED $it")
taskDeleter.delete(caldavDao.getTasks(caldavCalendar.uuid!!, it.toList())) val tasks = caldavDao.getTasks(caldavCalendar.uuid!!, it.toList())
vtodoCache.delete(caldavCalendar, tasks)
taskDeleter.delete(tasks.map { it.task })
} }
caldavCalendar.ctag = remoteCtag caldavCalendar.ctag = remoteCtag
Timber.d("UPDATE %s", caldavCalendar) Timber.d("UPDATE %s", caldavCalendar)
@ -369,7 +372,9 @@ class CaldavSynchronizer @Inject constructor(
fromResponse(it)?.eTag?.takeIf(String::isNotBlank)?.let { etag -> fromResponse(it)?.eTag?.takeIf(String::isNotBlank)?.let { etag ->
caldavTask.etag = etag caldavTask.etag = etag
} }
vtodoCache.putVtodo(calendar, caldavTask, String(data)) runBlocking {
vtodoCache.putVtodo(calendar, caldavTask, String(data))
}
} }
} }
} catch (e: HttpException) { } catch (e: HttpException) {

@ -2,6 +2,8 @@ package org.tasks.caldav
import android.content.Context import android.content.Context
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.File import java.io.File
import javax.inject.Inject import javax.inject.Inject
@ -18,13 +20,15 @@ class FileStorage @Inject constructor(
null null
} }
fun read(file: File?): String? = file?.takeIf { it.exists() }?.readText() suspend fun read(file: File?): String? = withContext(Dispatchers.IO) {
file?.takeIf { it.exists() }?.readText()
}
fun write(file: File, data: String?) { suspend fun write(file: File, data: String?) = withContext(Dispatchers.IO) {
if (data.isNullOrBlank()) { if (data.isNullOrBlank()) {
file.delete() file.delete()
} else { } else {
file.writeText(data) file.writeText(data)
} }
} }
} }

@ -1,5 +1,7 @@
package org.tasks.caldav package org.tasks.caldav
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.tasks.data.dao.CaldavDao import org.tasks.data.dao.CaldavDao
import org.tasks.data.entity.CaldavAccount import org.tasks.data.entity.CaldavAccount
import org.tasks.data.entity.CaldavCalendar import org.tasks.data.entity.CaldavCalendar
@ -13,20 +15,21 @@ class VtodoCache @Inject constructor(
private val caldavDao: CaldavDao, private val caldavDao: CaldavDao,
private val fileStorage: FileStorage, private val fileStorage: FileStorage,
) { ) {
fun move(from: CaldavCalendar, to: CaldavCalendar, task: CaldavTask) { suspend fun move(from: CaldavCalendar, to: CaldavCalendar, task: CaldavTask) =
val source = withContext(Dispatchers.IO) {
fileStorage.getFile(from.account, from.uuid, task.obj) val source =
if (source?.exists() != true) { fileStorage.getFile(from.account, from.uuid, task.obj)
return if (source?.exists() != true) {
return@withContext
}
val target =
fileStorage.getFile(to.account, to.uuid)
?.apply { mkdirs() }
?.let { File(it, task.obj!!) }
?: return@withContext
source.copyTo(target, overwrite = true)
source.delete()
} }
val target =
fileStorage.getFile(to.account, to.uuid)
?.apply { mkdirs() }
?.let { File(it, task.obj!!) }
?: return
source.copyTo(target, overwrite = true)
source.delete()
}
suspend fun getVtodo(caldavTask: CaldavTask?): String? { suspend fun getVtodo(caldavTask: CaldavTask?): String? {
if (caldavTask == null) { if (caldavTask == null) {
@ -36,7 +39,7 @@ class VtodoCache @Inject constructor(
return getVtodo(calendar, caldavTask) return getVtodo(calendar, caldavTask)
} }
fun getVtodo(calendar: CaldavCalendar?, caldavTask: CaldavTask?): String? { suspend fun getVtodo(calendar: CaldavCalendar?, caldavTask: CaldavTask?): String? {
val file = fileStorage.getFile( val file = fileStorage.getFile(
calendar?.account, calendar?.account,
caldavTask?.calendar, caldavTask?.calendar,
@ -45,36 +48,37 @@ class VtodoCache @Inject constructor(
return fileStorage.read(file) return fileStorage.read(file)
} }
fun putVtodo(calendar: CaldavCalendar, caldavTask: CaldavTask, vtodo: String?) { suspend fun putVtodo(calendar: CaldavCalendar, caldavTask: CaldavTask, vtodo: String?) {
val `object` = caldavTask.obj?.takeIf { it.isNotBlank() } ?: return val `object` = caldavTask.obj?.takeIf { it.isNotBlank() } ?: return
val directory = withContext(Dispatchers.IO) {
fileStorage val directory =
.getFile(calendar.account, caldavTask.calendar) fileStorage
?.apply { mkdirs() } .getFile(calendar.account, caldavTask.calendar)
?: return ?.apply { mkdirs() }
fileStorage.write(File(directory, `object`), vtodo) ?: return@withContext
fileStorage.write(File(directory, `object`), vtodo)
}
} }
suspend fun delete(taskIds: List<Long>) { suspend fun delete(calendar: CaldavCalendar, tasks: List<CaldavTask>) {
val tasks = caldavDao.getTasks(taskIds).groupBy { it.calendar!! } tasks.forEach { delete(calendar, it) }
tasks.forEach { (c, t) ->
val calendar = caldavDao.getCalendar(c) ?: return@forEach
t.forEach { delete(calendar, it) }
}
} }
fun delete(calendar: CaldavCalendar, caldavTask: CaldavTask) { suspend fun delete(calendar: CaldavCalendar, caldavTask: CaldavTask) = withContext(Dispatchers.IO) {
fileStorage fileStorage
.getFile(calendar.account, caldavTask.calendar, caldavTask.obj) .getFile(calendar.account, caldavTask.calendar, caldavTask.obj)
?.delete() ?.delete()
} }
fun delete(calendar: CaldavCalendar) = suspend fun delete(calendar: CaldavCalendar) = withContext(Dispatchers.IO) {
fileStorage.getFile(calendar.account, calendar.uuid)?.deleteRecursively() fileStorage.getFile(calendar.account, calendar.uuid)?.deleteRecursively()
}
fun delete(account: CaldavAccount) = suspend fun delete(account: CaldavAccount) = withContext(Dispatchers.IO) {
fileStorage.getFile(account.uuid)?.deleteRecursively() fileStorage.getFile(account.uuid)?.deleteRecursively()
}
fun clear() = suspend fun clear() = withContext(Dispatchers.IO) {
fileStorage.getFile()?.deleteRecursively() fileStorage.getFile()?.deleteRecursively()
} }
}

@ -6,6 +6,7 @@ import androidx.room.migration.Migration
import androidx.sqlite.SQLiteConnection import androidx.sqlite.SQLiteConnection
import androidx.sqlite.execSQL import androidx.sqlite.execSQL
import androidx.sqlite.use import androidx.sqlite.use
import kotlinx.coroutines.runBlocking
import org.tasks.R import org.tasks.R
import org.tasks.caldav.FileStorage import org.tasks.caldav.FileStorage
import org.tasks.data.NO_ORDER import org.tasks.data.NO_ORDER
@ -466,7 +467,9 @@ object Migrations {
?: continue ?: continue
val `object` = it.getTextOrNull(2) ?: continue val `object` = it.getTextOrNull(2) ?: continue
val data = it.getTextOrNull(3) ?: continue val data = it.getTextOrNull(3) ?: continue
fileStorage.write(File(file, `object`), data) runBlocking {
fileStorage.write(File(file, `object`), data)
}
} }
} }
connection.execSQL("ALTER TABLE `caldav_tasks` RENAME TO `caldav_tasks-temp`") connection.execSQL("ALTER TABLE `caldav_tasks` RENAME TO `caldav_tasks-temp`")

@ -179,7 +179,8 @@ class OpenTasksSynchronizer @Inject constructor(
.takeIf { it.isNotEmpty() } .takeIf { it.isNotEmpty() }
?.let { ?.let {
Timber.d("DELETED $it") Timber.d("DELETED $it")
taskDeleter.delete(caldavDao.getTasksByRemoteId(calendar, it.toList())) val tasks = caldavDao.getTasksByRemoteId(calendar, it.toList())
taskDeleter.delete(tasks.map { it.task })
} }
} }

@ -4,13 +4,14 @@ import android.app.Activity
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import org.tasks.data.db.Database
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.tasks.R import org.tasks.R
import org.tasks.caldav.VtodoCache import org.tasks.caldav.VtodoCache
import org.tasks.calendars.CalendarEventProvider import org.tasks.calendars.CalendarEventProvider
import org.tasks.data.dao.TaskDao import org.tasks.data.dao.TaskDao
import org.tasks.data.db.Database
import org.tasks.etebase.EtebaseLocalCache import org.tasks.etebase.EtebaseLocalCache
import org.tasks.extensions.Context.toast import org.tasks.extensions.Context.toast
import org.tasks.files.FileHelper import org.tasks.files.FileHelper
@ -141,10 +142,12 @@ class Advanced : InjectingPreferenceFragment() {
.setMessage(R.string.EPr_delete_task_data_warning) .setMessage(R.string.EPr_delete_task_data_warning)
.setPositiveButton(R.string.EPr_delete_task_data) { _, _ -> .setPositiveButton(R.string.EPr_delete_task_data) { _, _ ->
val context = requireContext() val context = requireContext()
context.deleteDatabase(database.name) lifecycleScope.launch(NonCancellable) {
vtodoCache.clear() context.deleteDatabase(database.name)
EtebaseLocalCache.clear(context) vtodoCache.clear()
restart() EtebaseLocalCache.clear(context)
restart()
}
} }
.setNegativeButton(R.string.cancel, null) .setNegativeButton(R.string.cancel, null)
.show() .show()

@ -243,17 +243,17 @@ SELECT EXISTS(SELECT 1
@Query("SELECT cd_remote_id FROM caldav_tasks WHERE cd_calendar = :calendar AND cd_deleted = 0 AND cd_last_sync > 0") @Query("SELECT cd_remote_id FROM caldav_tasks WHERE cd_calendar = :calendar AND cd_deleted = 0 AND cd_last_sync > 0")
abstract suspend fun getRemoteIds(calendar: String): List<String> abstract suspend fun getRemoteIds(calendar: String): List<String>
suspend fun getTasksByRemoteId(calendar: String, remoteIds: List<String>): List<Long> = suspend fun getTasksByRemoteId(calendar: String, remoteIds: List<String>): List<CaldavTask> =
remoteIds.chunkedMap { getTasksByRemoteIdInternal(calendar, it) } remoteIds.chunkedMap { getTasksByRemoteIdInternal(calendar, it) }
@Query("SELECT cd_task FROM caldav_tasks WHERE cd_calendar = :calendar AND cd_remote_id IN (:remoteIds)") @Query("SELECT * FROM caldav_tasks WHERE cd_calendar = :calendar AND cd_remote_id IN (:remoteIds)")
internal abstract suspend fun getTasksByRemoteIdInternal(calendar: String, remoteIds: List<String>): List<Long> internal abstract suspend fun getTasksByRemoteIdInternal(calendar: String, remoteIds: List<String>): List<CaldavTask>
suspend fun getTasks(calendar: String, objects: List<String>): List<Long> = suspend fun getTasks(calendar: String, objects: List<String>): List<CaldavTask> =
objects.chunkedMap { getTasksInternal(calendar, it) } objects.chunkedMap { getTasksInternal(calendar, it) }
@Query("SELECT cd_task FROM caldav_tasks WHERE cd_calendar = :calendar AND cd_object IN (:objects)") @Query("SELECT * FROM caldav_tasks WHERE cd_calendar = :calendar AND cd_object IN (:objects)")
internal abstract suspend fun getTasksInternal(calendar: String, objects: List<String>): List<Long> internal abstract suspend fun getTasksInternal(calendar: String, objects: List<String>): List<CaldavTask>
@Query("SELECT * FROM caldav_lists WHERE cdl_account = :account AND cdl_url NOT IN (:urls)") @Query("SELECT * FROM caldav_lists WHERE cdl_account = :account AND cdl_url NOT IN (:urls)")
abstract suspend fun findDeletedCalendars(account: String, urls: List<String>): List<CaldavCalendar> abstract suspend fun findDeletedCalendars(account: String, urls: List<String>): List<CaldavCalendar>

Loading…
Cancel
Save