Use ical4android to build content provider ops

pull/1309/head
Alex Baker 4 years ago
parent ebbd4ec365
commit d1d076a3d7

@ -142,7 +142,7 @@ val googleplayImplementation by configurations
dependencies { dependencies {
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.1.1") coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.1.1")
implementation("com.gitlab.bitfireAT:dav4jvm:2.1.1") implementation("com.gitlab.bitfireAT:dav4jvm:2.1.1")
implementation("com.gitlab.abaker:ical4android:007f7751d5") implementation("com.gitlab.abaker:ical4android:0e928b567c")
implementation("com.gitlab.bitfireAT:cert4android:26a91a729f") implementation("com.gitlab.bitfireAT:cert4android:26a91a729f")
implementation("com.github.dmfs.opentasks:opentasks-provider:1.2.4") { implementation("com.github.dmfs.opentasks:opentasks-provider:1.2.4") {
exclude("com.github.dmfs.opentasks", "opentasks-contract") exclude("com.github.dmfs.opentasks", "opentasks-contract")

@ -21,11 +21,12 @@ object TestUtilities {
return task return task
} }
fun setup(path: String): Pair<Task, CaldavTask> { fun setup(path: String): Triple<Task, CaldavTask, at.bitfire.ical4android.Task> {
val task = Task() val task = Task()
val vtodo = readFile(path) val vtodo = readFile(path)
CaldavConverter.apply(task, fromString(vtodo)) val remote = fromString(vtodo)
return Pair(task, CaldavTask().apply { this.vtodo = vtodo }) CaldavConverter.apply(task, remote)
return Triple(task, CaldavTask().apply { this.vtodo = vtodo }, remote)
} }
private fun fromResource(path: String): at.bitfire.ical4android.Task = private fun fromResource(path: String): at.bitfire.ical4android.Task =

@ -3,7 +3,6 @@ package org.tasks.caldav;
import static com.todoroo.andlib.utility.DateUtilities.now; import static com.todoroo.andlib.utility.DateUtilities.now;
import static com.todoroo.astrid.data.Task.URGENCY_SPECIFIC_DAY; import static com.todoroo.astrid.data.Task.URGENCY_SPECIFIC_DAY;
import static com.todoroo.astrid.data.Task.URGENCY_SPECIFIC_DAY_TIME; import static com.todoroo.astrid.data.Task.URGENCY_SPECIFIC_DAY_TIME;
import static org.tasks.Strings.isNullOrEmpty;
import static org.tasks.date.DateTimeUtils.newDateTime; import static org.tasks.date.DateTimeUtils.newDateTime;
import static org.tasks.time.DateTimeUtils.startOfDay; import static org.tasks.time.DateTimeUtils.startOfDay;
@ -96,18 +95,7 @@ public class CaldavConverter {
} }
} }
public static at.bitfire.ical4android.Task toCaldav(CaldavTask caldavTask, Task task) { public static void toCaldav(CaldavTask caldavTask, Task task, at.bitfire.ical4android.Task remote) {
at.bitfire.ical4android.Task remote = null;
try {
if (!isNullOrEmpty(caldavTask.getVtodo())) {
remote = iCalendar.Companion.fromVtodo(caldavTask.getVtodo());
}
} catch (Exception e) {
Timber.e(e);
}
if (remote == null) {
remote = new at.bitfire.ical4android.Task();
}
remote.setCreatedAt(newDateTime(task.getCreationDate()).toUTC().getMillis()); remote.setCreatedAt(newDateTime(task.getCreationDate()).toUTC().getMillis());
remote.setSummary(task.getTitle()); remote.setSummary(task.getTitle());
remote.setDescription(task.getNotes()); remote.setDescription(task.getNotes());
@ -153,8 +141,6 @@ public class CaldavConverter {
remote.setLastModified(newDateTime(task.getModificationDate()).toUTC().getMillis()); remote.setLastModified(newDateTime(task.getModificationDate()).toUTC().getMillis());
remote.setPriority(toRemote(remote.getPriority(), task.getPriority())); remote.setPriority(toRemote(remote.getPriority(), task.getPriority()));
iCalendar.Companion.setParent(remote, task.getParent() == 0 ? null : caldavTask.getRemoteParent()); iCalendar.Companion.setParent(remote, task.getParent() == 0 ? null : caldavTask.getRemoteParent());
return remote;
} }
private static DateTime getDateTime(long timestamp) { private static DateTime getDateTime(long timestamp) {

@ -88,7 +88,27 @@ class iCalendar @Inject constructor(
} }
suspend fun toVtodo(caldavTask: CaldavTask, task: com.todoroo.astrid.data.Task): ByteArray { suspend fun toVtodo(caldavTask: CaldavTask, task: com.todoroo.astrid.data.Task): ByteArray {
val remoteModel = CaldavConverter.toCaldav(caldavTask, task) var remoteModel: Task? = null
try {
if (!isNullOrEmpty(caldavTask.vtodo)) {
remoteModel = fromVtodo(caldavTask.vtodo!!)
}
} catch (e: java.lang.Exception) {
Timber.e(e)
}
if (remoteModel == null) {
remoteModel = Task()
}
toVtodo(caldavTask, task, remoteModel)
val os = ByteArrayOutputStream()
remoteModel.write(os)
return os.toByteArray()
}
suspend fun toVtodo(caldavTask: CaldavTask, task: com.todoroo.astrid.data.Task, remoteModel: Task) {
CaldavConverter.toCaldav(caldavTask, task, remoteModel)
remoteModel.order = caldavTask.order remoteModel.order = caldavTask.order
val categories = remoteModel.categories val categories = remoteModel.categories
categories.clear() categories.clear()
@ -105,10 +125,6 @@ class iCalendar @Inject constructor(
if (localGeo == null || !localGeo.equalish(remoteModel.geoPosition)) { if (localGeo == null || !localGeo.equalish(remoteModel.geoPosition)) {
remoteModel.geoPosition = localGeo remoteModel.geoPosition = localGeo
} }
val os = ByteArrayOutputStream()
remoteModel.write(os)
return os.toByteArray()
} }
suspend fun fromVtodo( suspend fun fromVtodo(

@ -1,7 +1,11 @@
package org.tasks.data package org.tasks.data
import android.database.Cursor import android.database.Cursor
import android.net.Uri
import at.bitfire.ical4android.AndroidTask import at.bitfire.ical4android.AndroidTask
import at.bitfire.ical4android.BatchOperation
import at.bitfire.ical4android.BatchOperation.CpoBuilder.Companion.newInsert
import at.bitfire.ical4android.BatchOperation.CpoBuilder.Companion.newUpdate
import at.bitfire.ical4android.MiscUtils.CursorHelper.toValues import at.bitfire.ical4android.MiscUtils.CursorHelper.toValues
import at.bitfire.ical4android.Task import at.bitfire.ical4android.Task
import org.dmfs.tasks.contract.TaskContract import org.dmfs.tasks.contract.TaskContract
@ -23,4 +27,21 @@ class MyAndroidTask() : AndroidTask(null) {
} }
} }
} }
constructor(task: Task) : this() {
this.task = task
}
fun toBuilder(uri: Uri, isNew: Boolean): BatchOperation.CpoBuilder {
val builder = if (isNew) newInsert(uri) else newUpdate(uri)
buildTask(builder, true)
if (!isNew) {
builder.remove(TaskContract.Tasks._UID)
}
return builder
.remove(TaskContract.Tasks.CREATED)
.remove(TaskContract.Tasks.LAST_MODIFIED)
.remove(TaskContract.Tasks._DIRTY)
.remove(TaskContract.Tasks.SYNC_VERSION)
}
} }

@ -1,10 +1,12 @@
package org.tasks.data package org.tasks.data
import android.content.ContentProviderOperation import android.content.ContentProviderOperation
import android.content.ContentProviderOperation.* import android.content.ContentProviderOperation.newDelete
import android.content.ContentProviderOperation.newInsert
import android.content.ContentValues import android.content.ContentValues
import android.content.Context import android.content.Context
import android.database.Cursor import android.database.Cursor
import at.bitfire.ical4android.BatchOperation
import at.bitfire.ical4android.Task import at.bitfire.ical4android.Task
import at.bitfire.ical4android.UnknownProperty import at.bitfire.ical4android.UnknownProperty
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
@ -26,7 +28,7 @@ class OpenTaskDao @Inject constructor(
) { ) {
private val cr = context.contentResolver private val cr = context.contentResolver
val authority = context.getString(R.string.opentasks_authority) val authority = context.getString(R.string.opentasks_authority)
private val tasks = Tasks.getContentUri(authority) val tasks = Tasks.getContentUri(authority)
private val properties = Properties.getContentUri(authority) private val properties = Properties.getContentUri(authority)
suspend fun newAccounts(): List<String> = getListsByAccount().newAccounts(caldavDao) suspend fun newAccounts(): List<String> = getListsByAccount().newAccounts(caldavDao)
@ -85,17 +87,14 @@ class OpenTaskDao @Inject constructor(
null) null)
.build() .build()
fun insert(values: ContentValues): ContentProviderOperation = fun insert(builder: BatchOperation.CpoBuilder): ContentProviderOperation = builder.build()
newInsert(tasks)
.withValues(values)
.build()
fun update(listId: Long, uid: String, values: ContentValues): ContentProviderOperation = fun update(listId: Long, uid: String, builder: BatchOperation.CpoBuilder): ContentProviderOperation =
newUpdate(tasks) builder
.withSelection( .withSelection(
"${Tasks.LIST_ID} = $listId AND ${Tasks._UID} = '$uid'", "${Tasks.LIST_ID} = $listId AND ${Tasks._UID} = '$uid'",
null) emptyArray()
.withValues(values) )
.build() .build()
suspend fun getId(listId: Long, uid: String?): Long? = suspend fun getId(listId: Long, uid: String?): Long? =
@ -235,7 +234,7 @@ class OpenTaskDao @Inject constructor(
private fun Cursor.getString(columnName: String): String? = private fun Cursor.getString(columnName: String): String? =
getString(getColumnIndex(columnName)) getString(getColumnIndex(columnName))
fun Cursor.getInt(columnName: String): Int = private fun Cursor.getInt(columnName: String): Int =
getInt(getColumnIndex(columnName)) getInt(getColumnIndex(columnName))
private fun Cursor.getLong(columnName: String): Long = private fun Cursor.getLong(columnName: String): Long =

@ -1,31 +1,24 @@
package org.tasks.opentasks package org.tasks.opentasks
import android.content.ContentProviderOperation import android.content.ContentProviderOperation
import android.content.ContentValues
import android.content.Context import android.content.Context
import com.todoroo.astrid.dao.TaskDao import com.todoroo.astrid.dao.TaskDao
import com.todoroo.astrid.data.Task import com.todoroo.astrid.data.Task
import com.todoroo.astrid.data.Task.Companion.sanitizeRRule
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 net.fortuna.ical4j.model.property.RRule
import org.dmfs.tasks.contract.TaskContract.Tasks import org.dmfs.tasks.contract.TaskContract.Tasks
import org.tasks.LocalBroadcastManager import org.tasks.LocalBroadcastManager
import org.tasks.R import org.tasks.R
import org.tasks.analytics.Constants import org.tasks.analytics.Constants
import org.tasks.analytics.Firebase import org.tasks.analytics.Firebase
import org.tasks.billing.Inventory import org.tasks.billing.Inventory
import org.tasks.caldav.CaldavConverter.toRemote
import org.tasks.caldav.iCalendar import org.tasks.caldav.iCalendar
import org.tasks.data.* import org.tasks.data.*
import org.tasks.data.CaldavAccount.Companion.openTaskType import org.tasks.data.CaldavAccount.Companion.openTaskType
import org.tasks.data.OpenTaskDao.Companion.getInt
import org.tasks.data.OpenTaskDao.Companion.isDavx5 import org.tasks.data.OpenTaskDao.Companion.isDavx5
import org.tasks.data.OpenTaskDao.Companion.isDecSync import org.tasks.data.OpenTaskDao.Companion.isDecSync
import org.tasks.data.OpenTaskDao.Companion.isEteSync import org.tasks.data.OpenTaskDao.Companion.isEteSync
import org.tasks.data.OpenTaskDao.Companion.newAccounts import org.tasks.data.OpenTaskDao.Companion.newAccounts
import org.tasks.time.DateTime
import org.tasks.time.DateTimeUtils.startOfDay
import timber.log.Timber import timber.log.Timber
import java.util.* import java.util.*
import javax.inject.Inject import javax.inject.Inject
@ -40,13 +33,10 @@ class OpenTasksSynchronizer @Inject constructor(
private val taskDao: TaskDao, private val taskDao: TaskDao,
private val firebase: Firebase, private val firebase: Firebase,
private val iCalendar: iCalendar, private val iCalendar: iCalendar,
private val locationDao: LocationDao,
private val openTaskDao: OpenTaskDao, private val openTaskDao: OpenTaskDao,
private val tagDataDao: TagDataDao, private val tagDataDao: TagDataDao,
private val inventory: Inventory) { private val inventory: Inventory) {
private val cr = context.contentResolver
suspend fun sync() { suspend fun sync() {
val lists = openTaskDao.getListsByAccount() val lists = openTaskDao.getListsByAccount()
lists.newAccounts(caldavDao) lists.newAccounts(caldavDao)
@ -212,60 +202,21 @@ class OpenTasksSynchronizer @Inject constructor(
): Pair<CaldavTask, ContentProviderOperation>? { ): Pair<CaldavTask, ContentProviderOperation>? {
val caldavTask = caldavDao.getTask(task.id) ?: return null val caldavTask = caldavDao.getTask(task.id) ?: return null
caldavTask.lastSync = task.modificationDate caldavTask.lastSync = task.modificationDate
val values = ContentValues() val remoteModel = openTaskDao.getTask(listId, caldavTask.remoteId!!)
values.put(Tasks.LIST_ID, listId) ?: at.bitfire.ical4android.Task()
values.put(Tasks.TITLE, task.title) val isNew = remoteModel.uid.isNullOrBlank()
values.put(Tasks.DESCRIPTION, task.notes) iCalendar.toVtodo(caldavTask, task, remoteModel)
values.put(Tasks.GEO, locationDao.getGeofences(task.id).toGeoString()) val builder = MyAndroidTask(remoteModel).toBuilder(openTaskDao.tasks, isNew)
values.put(Tasks.RRULE, if (task.isRecurring) {
val rrule = RRule(task.getRecurrenceWithoutFrom()!!.replace("RRULE:", ""))
if (task.repeatUntil > 0) {
rrule.recur.until = DateTime(task.repeatUntil).toUTC().toDateTime()
}
RRule(rrule.value.sanitizeRRule()).value
} else null)
val allDay = !task.hasDueTime() && !task.hasStartTime()
values.put(Tasks.IS_ALLDAY, if (allDay) 1 else 0)
values.put(Tasks.DUE, when {
task.hasDueTime() -> task.dueDate
task.hasDueDate() -> task.dueDate.startOfDay()
else -> null
})
values.put(Tasks.DTSTART, when {
task.hasStartTime() -> task.hideUntil
task.hasStartDate() -> task.hideUntil.startOfDay()
else -> null
})
values.put(Tasks.COMPLETED_IS_ALLDAY, 0)
values.put(Tasks.COMPLETED, if (task.isCompleted) task.completionDate else null)
values.put(Tasks.STATUS, if (task.isCompleted) Tasks.STATUS_COMPLETED else null)
values.put(Tasks.PERCENT_COMPLETE, if (task.isCompleted) 100 else null)
if (!allDay || task.isCompleted) {
values.put(Tasks.TZ, TimeZone.getDefault().id)
}
values.put(Tasks.PARENT_ID, null as Long?)
val existing = cr.query(
Tasks.getContentUri(openTaskDao.authority),
arrayOf(Tasks.PRIORITY),
"${Tasks.LIST_ID} = $listId AND ${Tasks._UID} = '${caldavTask.remoteId}'",
null,
null)?.use {
if (!it.moveToFirst()) {
return@use false
}
values.put(Tasks.PRIORITY, toRemote(it.getInt(Tasks.PRIORITY), task.priority))
true
} ?: false
val operation = try { val operation = try {
if (existing) { if (isNew) {
openTaskDao.update(listId, caldavTask.remoteId!!, values)
} else {
if (isEteSync) { if (isEteSync) {
values.put(Tasks.SYNC2, caldavTask.remoteId) builder.withValue(Tasks.SYNC2, caldavTask.remoteId)
} }
values.put(Tasks._UID, caldavTask.remoteId) builder.withValue(Tasks.LIST_ID, listId)
values.put(Tasks.PRIORITY, toRemote(task.priority, task.priority)) openTaskDao.insert(builder)
openTaskDao.insert(values) } else {
openTaskDao.update(listId, caldavTask.remoteId!!, builder)
} }
} catch (e: Exception) { } catch (e: Exception) {
firebase.reportException(e) firebase.reportException(e)
@ -285,8 +236,4 @@ class OpenTasksSynchronizer @Inject constructor(
iCalendar.fromVtodo(calendar, existing, it, null, null, etag) iCalendar.fromVtodo(calendar, existing, it, null, null, etag)
} }
} }
companion object {
private fun Location?.toGeoString(): String? = this?.let { "$longitude,$latitude" }
}
} }

@ -95,8 +95,8 @@ class ThunderbirdTests {
@Test @Test
fun dontTruncateTimeFromUntil() { fun dontTruncateTimeFromUntil() {
val (task, caldavTask) = setup("thunderbird/repeat_until_date_time.txt") val (task, caldavTask, remote) = setup("thunderbird/repeat_until_date_time.txt")
val remote = CaldavConverter.toCaldav(caldavTask, task) CaldavConverter.toCaldav(caldavTask, task, remote)
assertEquals( assertEquals(
"FREQ=WEEKLY;UNTIL=20200731T160000Z;BYDAY=MO,TU,WE,TH,FR", "FREQ=WEEKLY;UNTIL=20200731T160000Z;BYDAY=MO,TU,WE,TH,FR",
remote.rRule!!.value) remote.rRule!!.value)

@ -97,7 +97,7 @@
++--- com.gitlab.bitfireAT:dav4jvm:2.1.1 ++--- com.gitlab.bitfireAT:dav4jvm:2.1.1
+| +--- org.jetbrains.kotlin:kotlin-stdlib:1.3.72 -> 1.4.21 (*) +| +--- org.jetbrains.kotlin:kotlin-stdlib:1.3.72 -> 1.4.21 (*)
+| \--- org.apache.commons:commons-lang3:3.9 +| \--- org.apache.commons:commons-lang3:3.9
++--- com.gitlab.abaker:ical4android:007f7751d5 ++--- com.gitlab.abaker:ical4android:0e928b567c
+| +--- org.mnode.ical4j:ical4j:3.0.21 +| +--- org.mnode.ical4j:ical4j:3.0.21
+| | +--- org.slf4j:slf4j-api:1.7.25 -> 1.7.30 +| | +--- org.slf4j:slf4j-api:1.7.25 -> 1.7.30
+| | +--- commons-codec:commons-codec:1.11 +| | +--- commons-codec:commons-codec:1.11

@ -249,7 +249,7 @@
++--- com.gitlab.bitfireAT:dav4jvm:2.1.1 ++--- com.gitlab.bitfireAT:dav4jvm:2.1.1
+| +--- org.jetbrains.kotlin:kotlin-stdlib:1.3.72 -> 1.4.21 (*) +| +--- org.jetbrains.kotlin:kotlin-stdlib:1.3.72 -> 1.4.21 (*)
+| \--- org.apache.commons:commons-lang3:3.9 +| \--- org.apache.commons:commons-lang3:3.9
++--- com.gitlab.abaker:ical4android:007f7751d5 ++--- com.gitlab.abaker:ical4android:0e928b567c
+| +--- org.mnode.ical4j:ical4j:3.0.21 +| +--- org.mnode.ical4j:ical4j:3.0.21
+| | +--- org.slf4j:slf4j-api:1.7.25 -> 1.7.30 +| | +--- org.slf4j:slf4j-api:1.7.25 -> 1.7.30
+| | +--- commons-codec:commons-codec:1.11 +| | +--- commons-codec:commons-codec:1.11

Loading…
Cancel
Save