From aa477a7b118557343d8276a2bda5f7c466f790a6 Mon Sep 17 00:00:00 2001 From: Alex Baker Date: Fri, 27 Dec 2024 02:38:38 -0600 Subject: [PATCH] vtodo cache improvements --- .../org/tasks/caldav/CaldavSynchronizer.kt | 9 ++- .../main/java/org/tasks/caldav/FileStorage.kt | 10 ++- .../main/java/org/tasks/caldav/VtodoCache.kt | 68 ++++++++++--------- app/src/main/java/org/tasks/db/Migrations.kt | 5 +- .../tasks/opentasks/OpenTasksSynchronizer.kt | 3 +- .../tasks/preferences/fragments/Advanced.kt | 13 ++-- .../kotlin/org/tasks/data/dao/CaldavDao.kt | 12 ++-- 7 files changed, 70 insertions(+), 50 deletions(-) diff --git a/app/src/main/java/org/tasks/caldav/CaldavSynchronizer.kt b/app/src/main/java/org/tasks/caldav/CaldavSynchronizer.kt index 4c854cb41..f304d11b8 100644 --- a/app/src/main/java/org/tasks/caldav/CaldavSynchronizer.kt +++ b/app/src/main/java/org/tasks/caldav/CaldavSynchronizer.kt @@ -25,6 +25,7 @@ import at.bitfire.ical4android.ICalendar.Companion.prodId import com.todoroo.astrid.dao.TaskDao import com.todoroo.astrid.service.TaskDeleter import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.runBlocking import net.fortuna.ical4j.model.property.ProdId import okhttp3.Headers import okhttp3.HttpUrl @@ -291,7 +292,9 @@ class CaldavSynchronizer @Inject constructor( .takeIf { it.isNotEmpty() } ?.let { 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 Timber.d("UPDATE %s", caldavCalendar) @@ -369,7 +372,9 @@ class CaldavSynchronizer @Inject constructor( fromResponse(it)?.eTag?.takeIf(String::isNotBlank)?.let { etag -> caldavTask.etag = etag } - vtodoCache.putVtodo(calendar, caldavTask, String(data)) + runBlocking { + vtodoCache.putVtodo(calendar, caldavTask, String(data)) + } } } } catch (e: HttpException) { diff --git a/app/src/main/java/org/tasks/caldav/FileStorage.kt b/app/src/main/java/org/tasks/caldav/FileStorage.kt index 29d06945a..bfd388b0d 100644 --- a/app/src/main/java/org/tasks/caldav/FileStorage.kt +++ b/app/src/main/java/org/tasks/caldav/FileStorage.kt @@ -2,6 +2,8 @@ package org.tasks.caldav import android.content.Context import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext import java.io.File import javax.inject.Inject @@ -18,13 +20,15 @@ class FileStorage @Inject constructor( 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()) { file.delete() } else { file.writeText(data) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/tasks/caldav/VtodoCache.kt b/app/src/main/java/org/tasks/caldav/VtodoCache.kt index 726585b76..3b3ab3d25 100644 --- a/app/src/main/java/org/tasks/caldav/VtodoCache.kt +++ b/app/src/main/java/org/tasks/caldav/VtodoCache.kt @@ -1,5 +1,7 @@ package org.tasks.caldav +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext import org.tasks.data.dao.CaldavDao import org.tasks.data.entity.CaldavAccount import org.tasks.data.entity.CaldavCalendar @@ -13,20 +15,21 @@ 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.obj) - if (source?.exists() != true) { - return + suspend fun move(from: CaldavCalendar, to: CaldavCalendar, task: CaldavTask) = + withContext(Dispatchers.IO) { + val source = + fileStorage.getFile(from.account, from.uuid, task.obj) + 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? { if (caldavTask == null) { @@ -36,7 +39,7 @@ class VtodoCache @Inject constructor( return getVtodo(calendar, caldavTask) } - fun getVtodo(calendar: CaldavCalendar?, caldavTask: CaldavTask?): String? { + suspend fun getVtodo(calendar: CaldavCalendar?, caldavTask: CaldavTask?): String? { val file = fileStorage.getFile( calendar?.account, caldavTask?.calendar, @@ -45,36 +48,37 @@ class VtodoCache @Inject constructor( 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 directory = - fileStorage - .getFile(calendar.account, caldavTask.calendar) - ?.apply { mkdirs() } - ?: return - fileStorage.write(File(directory, `object`), vtodo) + withContext(Dispatchers.IO) { + val directory = + fileStorage + .getFile(calendar.account, caldavTask.calendar) + ?.apply { mkdirs() } + ?: return@withContext + fileStorage.write(File(directory, `object`), vtodo) + } } - suspend fun delete(taskIds: List) { - val tasks = caldavDao.getTasks(taskIds).groupBy { it.calendar!! } - tasks.forEach { (c, t) -> - val calendar = caldavDao.getCalendar(c) ?: return@forEach - t.forEach { delete(calendar, it) } - } + suspend fun delete(calendar: CaldavCalendar, tasks: List) { + tasks.forEach { delete(calendar, it) } } - fun delete(calendar: CaldavCalendar, caldavTask: CaldavTask) { + suspend fun delete(calendar: CaldavCalendar, caldavTask: CaldavTask) = withContext(Dispatchers.IO) { fileStorage .getFile(calendar.account, caldavTask.calendar, caldavTask.obj) ?.delete() } - fun delete(calendar: CaldavCalendar) = + suspend fun delete(calendar: CaldavCalendar) = withContext(Dispatchers.IO) { fileStorage.getFile(calendar.account, calendar.uuid)?.deleteRecursively() + } - fun delete(account: CaldavAccount) = + suspend fun delete(account: CaldavAccount) = withContext(Dispatchers.IO) { fileStorage.getFile(account.uuid)?.deleteRecursively() + } - fun clear() = + suspend fun clear() = withContext(Dispatchers.IO) { fileStorage.getFile()?.deleteRecursively() -} \ No newline at end of file + } +} diff --git a/app/src/main/java/org/tasks/db/Migrations.kt b/app/src/main/java/org/tasks/db/Migrations.kt index 866477836..4ca57431c 100644 --- a/app/src/main/java/org/tasks/db/Migrations.kt +++ b/app/src/main/java/org/tasks/db/Migrations.kt @@ -6,6 +6,7 @@ import androidx.room.migration.Migration import androidx.sqlite.SQLiteConnection import androidx.sqlite.execSQL import androidx.sqlite.use +import kotlinx.coroutines.runBlocking import org.tasks.R import org.tasks.caldav.FileStorage import org.tasks.data.NO_ORDER @@ -466,7 +467,9 @@ object Migrations { ?: continue val `object` = it.getTextOrNull(2) ?: 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`") diff --git a/app/src/main/java/org/tasks/opentasks/OpenTasksSynchronizer.kt b/app/src/main/java/org/tasks/opentasks/OpenTasksSynchronizer.kt index 149c9ca41..1d4d392dd 100644 --- a/app/src/main/java/org/tasks/opentasks/OpenTasksSynchronizer.kt +++ b/app/src/main/java/org/tasks/opentasks/OpenTasksSynchronizer.kt @@ -179,7 +179,8 @@ class OpenTasksSynchronizer @Inject constructor( .takeIf { it.isNotEmpty() } ?.let { Timber.d("DELETED $it") - taskDeleter.delete(caldavDao.getTasksByRemoteId(calendar, it.toList())) + val tasks = caldavDao.getTasksByRemoteId(calendar, it.toList()) + taskDeleter.delete(tasks.map { it.task }) } } diff --git a/app/src/main/java/org/tasks/preferences/fragments/Advanced.kt b/app/src/main/java/org/tasks/preferences/fragments/Advanced.kt index f73e0189a..a73ab9770 100644 --- a/app/src/main/java/org/tasks/preferences/fragments/Advanced.kt +++ b/app/src/main/java/org/tasks/preferences/fragments/Advanced.kt @@ -4,13 +4,14 @@ import android.app.Activity import android.content.Intent import android.os.Bundle import androidx.lifecycle.lifecycleScope -import org.tasks.data.db.Database import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.launch import org.tasks.R import org.tasks.caldav.VtodoCache import org.tasks.calendars.CalendarEventProvider import org.tasks.data.dao.TaskDao +import org.tasks.data.db.Database import org.tasks.etebase.EtebaseLocalCache import org.tasks.extensions.Context.toast import org.tasks.files.FileHelper @@ -141,10 +142,12 @@ class Advanced : InjectingPreferenceFragment() { .setMessage(R.string.EPr_delete_task_data_warning) .setPositiveButton(R.string.EPr_delete_task_data) { _, _ -> val context = requireContext() - context.deleteDatabase(database.name) - vtodoCache.clear() - EtebaseLocalCache.clear(context) - restart() + lifecycleScope.launch(NonCancellable) { + context.deleteDatabase(database.name) + vtodoCache.clear() + EtebaseLocalCache.clear(context) + restart() + } } .setNegativeButton(R.string.cancel, null) .show() diff --git a/data/src/commonMain/kotlin/org/tasks/data/dao/CaldavDao.kt b/data/src/commonMain/kotlin/org/tasks/data/dao/CaldavDao.kt index 633a2fb24..e3d8330b1 100644 --- a/data/src/commonMain/kotlin/org/tasks/data/dao/CaldavDao.kt +++ b/data/src/commonMain/kotlin/org/tasks/data/dao/CaldavDao.kt @@ -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") abstract suspend fun getRemoteIds(calendar: String): List - suspend fun getTasksByRemoteId(calendar: String, remoteIds: List): List = + suspend fun getTasksByRemoteId(calendar: String, remoteIds: List): List = remoteIds.chunkedMap { getTasksByRemoteIdInternal(calendar, it) } - @Query("SELECT cd_task FROM caldav_tasks WHERE cd_calendar = :calendar AND cd_remote_id IN (:remoteIds)") - internal abstract suspend fun getTasksByRemoteIdInternal(calendar: String, remoteIds: List): List + @Query("SELECT * FROM caldav_tasks WHERE cd_calendar = :calendar AND cd_remote_id IN (:remoteIds)") + internal abstract suspend fun getTasksByRemoteIdInternal(calendar: String, remoteIds: List): List - suspend fun getTasks(calendar: String, objects: List): List = + suspend fun getTasks(calendar: String, objects: List): List = objects.chunkedMap { getTasksInternal(calendar, it) } - @Query("SELECT cd_task FROM caldav_tasks WHERE cd_calendar = :calendar AND cd_object IN (:objects)") - internal abstract suspend fun getTasksInternal(calendar: String, objects: List): List + @Query("SELECT * FROM caldav_tasks WHERE cd_calendar = :calendar AND cd_object IN (:objects)") + internal abstract suspend fun getTasksInternal(calendar: String, objects: List): List @Query("SELECT * FROM caldav_lists WHERE cdl_account = :account AND cdl_url NOT IN (:urls)") abstract suspend fun findDeletedCalendars(account: String, urls: List): List