Push local Etebase changes before fetching updates

pull/1244/head
Alex Baker 5 years ago
parent 58520bcc29
commit f126e7e462

@ -3,9 +3,6 @@ package org.tasks.etebase
import android.content.Context import android.content.Context
import com.etebase.client.* import com.etebase.client.*
import com.etebase.client.Collection import com.etebase.client.Collection
import com.etesync.journalmanager.Exceptions
import com.etesync.journalmanager.Exceptions.IntegrityException
import com.etesync.journalmanager.Exceptions.VersionTooNewException
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.tasks.data.CaldavCalendar import org.tasks.data.CaldavCalendar
@ -13,7 +10,6 @@ import org.tasks.data.CaldavDao
import org.tasks.data.CaldavTask import org.tasks.data.CaldavTask
import org.tasks.time.DateTimeUtils.currentTimeMillis import org.tasks.time.DateTimeUtils.currentTimeMillis
import timber.log.Timber import timber.log.Timber
import java.io.IOException
class EtebaseClient( class EtebaseClient(
private val context: Context, private val context: Context,
@ -23,10 +19,8 @@ class EtebaseClient(
) { ) {
private val cache = EtebaseLocalCache.getInstance(context, username) private val cache = EtebaseLocalCache.getInstance(context, username)
@Throws(IOException::class, Exceptions.HttpException::class)
fun getSession(): String = etebase.save(null) fun getSession(): String = etebase.save(null)
@Throws(Exceptions.HttpException::class)
suspend fun getCollections(): List<Collection> { suspend fun getCollections(): List<Collection> {
val collectionManager = etebase.collectionManager val collectionManager = etebase.collectionManager
var stoken: String? = cache.loadStoken() var stoken: String? = cache.loadStoken()
@ -49,7 +43,6 @@ class EtebaseClient(
return cache.collectionList(collectionManager) return cache.collectionList(collectionManager)
} }
@Throws(IntegrityException::class, Exceptions.HttpException::class, VersionTooNewException::class)
suspend fun fetchItems( suspend fun fetchItems(
collection: Collection, collection: Collection,
calendar: CaldavCalendar, calendar: CaldavCalendar,
@ -118,20 +111,17 @@ class EtebaseClient(
} }
} }
@Throws(VersionTooNewException::class, IntegrityException::class, Exceptions.HttpException::class)
suspend fun makeCollection(name: String, color: Int) = suspend fun makeCollection(name: String, color: Int) =
etebase etebase
.collectionManager .collectionManager
.create(TYPE_TASKS, ItemMetadata(), "") .create(TYPE_TASKS, ItemMetadata(), "")
.let { setAndUpload(it, name, color) } .let { setAndUpload(it, name, color) }
@Throws(VersionTooNewException::class, IntegrityException::class, Exceptions.HttpException::class)
suspend fun updateCollection(calendar: CaldavCalendar, name: String, color: Int) = suspend fun updateCollection(calendar: CaldavCalendar, name: String, color: Int) =
cache cache
.collectionGet(etebase.collectionManager, calendar.url!!) .collectionGet(etebase.collectionManager, calendar.url!!)
.let { setAndUpload(it, name, color) } .let { setAndUpload(it, name, color) }
@Throws(Exceptions.HttpException::class)
suspend fun deleteCollection(calendar: CaldavCalendar) = suspend fun deleteCollection(calendar: CaldavCalendar) =
cache cache
.collectionGet(etebase.collectionManager, calendar.url!!) .collectionGet(etebase.collectionManager, calendar.url!!)

@ -5,9 +5,8 @@ import android.graphics.Color
import at.bitfire.ical4android.ICalendar.Companion.prodId import at.bitfire.ical4android.ICalendar.Companion.prodId
import com.etebase.client.Collection import com.etebase.client.Collection
import com.etebase.client.Item import com.etebase.client.Item
import com.etesync.journalmanager.Exceptions import com.etebase.client.exceptions.*
import com.etesync.journalmanager.Exceptions.IntegrityException import com.todoroo.andlib.utility.DateUtilities.now
import com.etesync.journalmanager.Exceptions.VersionTooNewException
import com.todoroo.astrid.helper.UUIDHelper import com.todoroo.astrid.helper.UUIDHelper
import com.todoroo.astrid.service.TaskDeleter import com.todoroo.astrid.service.TaskDeleter
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
@ -22,10 +21,8 @@ import org.tasks.caldav.iCalendar.Companion.fromVtodo
import org.tasks.data.CaldavAccount import org.tasks.data.CaldavAccount
import org.tasks.data.CaldavCalendar import org.tasks.data.CaldavCalendar
import org.tasks.data.CaldavDao import org.tasks.data.CaldavDao
import org.tasks.data.CaldavTaskContainer import org.tasks.time.DateTimeUtils.currentTimeMillis
import timber.log.Timber import timber.log.Timber
import java.security.KeyManagementException
import java.security.NoSuchAlgorithmException
import java.util.* import java.util.*
import javax.inject.Inject import javax.inject.Inject
@ -56,20 +53,19 @@ class EtebaseSynchronizer @Inject constructor(
} }
try { try {
synchronize(account) synchronize(account)
} catch (e: KeyManagementException) { } catch (e: ConnectionException) {
setError(account, e.message) setError(account, e)
} catch (e: NoSuchAlgorithmException) { } catch (e: PermissionDeniedException) {
setError(account, e.message) setError(account, e)
} catch (e: Exceptions.HttpException) { } catch (e: ServerErrorException) {
setError(account, e.message) setError(account, e)
} catch (e: IntegrityException) { } catch (e: TemporaryServerErrorException) {
setError(account, e.message) setError(account, e)
} catch (e: VersionTooNewException) { } catch (e: UnauthorizedException) {
setError(account, e.message) setError(account, e)
} }
} }
@Throws(KeyManagementException::class, NoSuchAlgorithmException::class, Exceptions.HttpException::class, IntegrityException::class, VersionTooNewException::class)
private suspend fun synchronize(account: CaldavAccount) { private suspend fun synchronize(account: CaldavAccount) {
val client = clientProvider.forAccount(account) val client = clientProvider.forAccount(account)
val collections = client.getCollections() val collections = client.getCollections()
@ -104,6 +100,9 @@ class EtebaseSynchronizer @Inject constructor(
setError(account, "") setError(account, "")
} }
private suspend fun setError(account: CaldavAccount, e: Throwable) =
setError(account, e.message)
private suspend fun setError(account: CaldavAccount, message: String?) { private suspend fun setError(account: CaldavAccount, message: String?) {
account.error = message account.error = message
caldavDao.update(account) caldavDao.update(account)
@ -113,34 +112,41 @@ class EtebaseSynchronizer @Inject constructor(
} }
} }
@Throws(IntegrityException::class, Exceptions.HttpException::class, VersionTooNewException::class)
private suspend fun sync( private suspend fun sync(
client: EtebaseClient, client: EtebaseClient,
caldavCalendar: CaldavCalendar, caldavCalendar: CaldavCalendar,
collection: Collection collection: Collection
) { ) {
Timber.d("sync(%s)", caldavCalendar) Timber.d("sync(%s)", caldavCalendar)
val localChanges = HashMap<String?, CaldavTaskContainer>() pushLocalChanges(client, caldavCalendar, collection)
for (task in caldavDao.getCaldavTasksToPush(caldavCalendar.uuid!!)) { val localCtag = caldavCalendar.ctag
localChanges[task.remoteId] = task if (localCtag != null && localCtag == collection.stoken) {
Timber.d("${caldavCalendar.name} up to date")
return
} }
val remoteCtag = collection.stoken client.fetchItems(collection, caldavCalendar) { (stoken, items) ->
if (isNullOrEmpty(remoteCtag) || remoteCtag != caldavCalendar.ctag) { applyEntries(caldavCalendar, items, stoken)
Timber.d("${caldavCalendar.name}: Applying remote changes") client.updateCache(collection, items)
client.fetchItems(collection, caldavCalendar) {
applyEntries(caldavCalendar, it, localChanges.keys)
client.updateCache(collection, it.second)
} }
} else { Timber.d("UPDATE %s", caldavCalendar)
Timber.d("${caldavCalendar.name} up to date") caldavDao.update(caldavCalendar)
caldavDao.updateParents(caldavCalendar.uuid!!)
localBroadcastManager.broadcastRefresh()
} }
private suspend fun pushLocalChanges(
client: EtebaseClient,
caldavCalendar: CaldavCalendar,
collection: Collection
) {
val changes = ArrayList<Item>() val changes = ArrayList<Item>()
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) ?: caldavDao.delete(caldavTask)
} }
for (change in localChanges.values) { val syncTime = now()
for (change in caldavDao.getCaldavTasksToPush(caldavCalendar.uuid!!)) {
val task = change.task val task = change.task
val caldavTask = change.caldavTask val caldavTask = change.caldavTask
if (task.isDeleted) { if (task.isDeleted) {
@ -148,35 +154,30 @@ class EtebaseSynchronizer @Inject constructor(
?.let { changes.add(it) } ?.let { changes.add(it) }
?: taskDeleter.delete(task) ?: taskDeleter.delete(task)
} else { } else {
changes.add(client.updateItem( changes.add(
collection, client.updateItem(collection, caldavTask, iCal.toVtodo(caldavTask, task))
caldavTask, )
iCal.toVtodo(caldavTask, task)
))
} }
} }
if (changes.isNotEmpty()) { if (changes.isNotEmpty()) {
client.uploadChanges(collection, changes) client.uploadChanges(collection, changes)
applyEntries(caldavCalendar, Pair(caldavCalendar.ctag, changes), HashSet()) applyEntries(caldavCalendar, changes, syncTime = syncTime, isLocalChange = true)
client.updateCache(collection, changes) client.updateCache(collection, changes)
} }
Timber.d("UPDATE %s", caldavCalendar)
caldavDao.update(caldavCalendar)
caldavDao.updateParents(caldavCalendar.uuid!!)
localBroadcastManager.broadcastRefresh()
} }
private suspend fun applyEntries( private suspend fun applyEntries(
caldavCalendar: CaldavCalendar, caldavCalendar: CaldavCalendar,
items: Pair<String?, List<Item>>, items: List<Item>,
dirty: MutableSet<String?>) { stoken: String? = null,
for (item in items.second) { syncTime: Long = currentTimeMillis(),
isLocalChange: Boolean = false) {
for (item in items) {
val vtodo = item.contentString val vtodo = item.contentString
val task = fromVtodo(vtodo) ?: continue val task = fromVtodo(vtodo) ?: continue
val remoteId = task.uid val remoteId = task.uid
val caldavTask = caldavDao.getTaskByRemoteId(caldavCalendar.uuid!!, remoteId!!) val caldavTask = caldavDao.getTaskByRemoteId(caldavCalendar.uuid!!, remoteId!!)
if (item.isDeleted) { if (item.isDeleted) {
dirty.remove(remoteId)
if (caldavTask != null) { if (caldavTask != null) {
if (caldavTask.isDeleted()) { if (caldavTask.isDeleted()) {
caldavDao.delete(caldavTask) caldavDao.delete(caldavTask)
@ -184,18 +185,20 @@ class EtebaseSynchronizer @Inject constructor(
taskDeleter.delete(caldavTask.task) taskDeleter.delete(caldavTask.task)
} }
} }
} else if (isLocalChange) {
caldavTask?.let {
it.vtodo = vtodo
it.lastSync = syncTime
caldavDao.update(it)
}
} else { } else {
caldavTask?.`object` = item.uid caldavTask?.`object` = item.uid
if (dirty.contains(remoteId)) {
caldavTask!!.vtodo = vtodo
caldavDao.update(caldavTask)
} else {
iCal.fromVtodo(caldavCalendar, caldavTask, task, vtodo, item.uid, null) iCal.fromVtodo(caldavCalendar, caldavTask, task, vtodo, item.uid, null)
} }
} }
} stoken?.let {
caldavCalendar.ctag = items.first caldavCalendar.ctag = it
Timber.d("Setting stoken to ${caldavCalendar.ctag}")
caldavDao.update(caldavCalendar) caldavDao.update(caldavCalendar)
} }
}
} }
Loading…
Cancel
Save