Sorry if I’ve been a bit optimistic. I agree that it might be more complex than I thought, even if automagical™ fixes are always nice.
I’ve taken a look at some classes in the code of Tasks .org on github, and in particular the CaldavSynchronizer class.
I think that it’s javascript, which I’m not really good at reading yet; but from what I understand, it seems like the app just “pushes” its modifications/its own version of the task toward the ICal files that are online, as shown in the functions used in the “sync” function:
private suspend fun pushLocalChanges(
caldavCalendar: CaldavCalendar, httpClient: OkHttpClient, httpUrl: HttpUrl) {
for (task in caldavDao.getMoved(caldavCalendar.uuid!!)) {
deleteRemoteResource(httpClient, httpUrl, task)
}
for (task in taskDao.getCaldavTasksToPush(caldavCalendar.uuid!!)) {
try {
pushTask(task, httpClient, httpUrl)
} catch (e: IOException) {
Timber.e(e)
}
}
}
private suspend fun deleteRemoteResource(
httpClient: OkHttpClient, httpUrl: HttpUrl, caldavTask: CaldavTask): Boolean {
try {
if (!isNullOrEmpty(caldavTask.`object`)) {
val remote = DavResource(
httpClient, httpUrl.newBuilder().addPathSegment(caldavTask.`object`!!).build())
remote.delete(null) {}
}
} catch (e: HttpException) {
if (e.code != 404) {
Timber.e(e)
return false
}
} catch (e: IOException) {
Timber.e(e)
return false
}
caldavDao.delete(caldavTask)
return true
}
private suspend fun pushTask(task: Task, httpClient: OkHttpClient, httpUrl: HttpUrl) {
Timber.d("pushing %s", task)
val caldavTask = caldavDao.getTask(task.id) ?: return
if (task.isDeleted) {
if (deleteRemoteResource(httpClient, httpUrl, caldavTask)) {
taskDeleter.delete(task)
}
return
}
val data = iCal.toVtodo(caldavTask, task)
val requestBody = RequestBody.create(MIME_ICALENDAR, data)
try {
val remote = DavResource(
httpClient, httpUrl.newBuilder().addPathSegment(caldavTask.`object`!!).build())
remote.put(requestBody) {
val getETag = fromResponse(it)
if (getETag != null && !isNullOrEmpty(getETag.eTag)) {
caldavTask.etag = getETag.eTag
caldavTask.vtodo = String(data)
}
}
} catch (e: HttpException) {
Timber.e(e)
return
}
caldavTask.lastSync = task.modificationDate
caldavDao.update(caldavTask)
Timber.d("SENT %s", caldavTask)
}
I’ve just tried it myself by creating a task on Nextcloud Tasks, which is synchronized with Tasks .org on my phone. I’ve created a “Test” task, and then synced Tasks .org to receive it in my phone. I then put my phone offline. On Nextcloud task, I wrote “1” as a description of the task; and in Tasks .org, I wrote “2”. Waited a bit, then put my phone online and let Tasks .org synchronize. The result is that the task had a description that said “2”.
What I get from that is that 1) ICal sync rules might not depend on the ICal protocol, but on the software used; and 2) that in the case of Tasks .org, the philosophy seems to be “my recent edits to a task always have the priority on other recent edits made after the last sync elsewhere”. I guess that this could be a good way to do it too ? It means that the a user would have to make an edit to a task in order for a conflict to form, and the client where they made the modification would have the priority.
I made a second experiment to try and see if Tasks .org was able to only “impose” modifications on characteristics of a task that were modified. For example, if I go offline with my phone, and change the due date of a task; while online, the description of the task has been changed; by syncing once my phone gets online, will I get the date entered on my offline phone AND the new description wrote online ? The answer is no. Tasks .org just pushes its own version of the task, all characteristics taken into account. Vikunja could maybe be a bit smarter on this aspect, and allow changes only to characteristics of a task that have been changed while offline.
I hope that this makes sense, and that it could be of some help !