mirror of https://github.com/tasks/tasks
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
213 lines
8.0 KiB
Kotlin
213 lines
8.0 KiB
Kotlin
package org.tasks.etebase
|
|
|
|
import android.content.Context
|
|
import android.graphics.Color
|
|
import at.bitfire.ical4android.ICalendar.Companion.prodId
|
|
import com.etebase.client.Collection
|
|
import com.etebase.client.Item
|
|
import com.etebase.client.exceptions.ConnectionException
|
|
import com.etebase.client.exceptions.PermissionDeniedException
|
|
import com.etebase.client.exceptions.ServerErrorException
|
|
import com.etebase.client.exceptions.TemporaryServerErrorException
|
|
import com.etebase.client.exceptions.UnauthorizedException
|
|
import com.todoroo.astrid.helper.UUIDHelper
|
|
import com.todoroo.astrid.service.TaskDeleter
|
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
|
import net.fortuna.ical4j.model.property.ProdId
|
|
import org.tasks.BuildConfig
|
|
import org.tasks.LocalBroadcastManager
|
|
import org.tasks.R
|
|
import org.tasks.Strings.isNullOrEmpty
|
|
import org.tasks.billing.Inventory
|
|
import org.tasks.caldav.VtodoCache
|
|
import org.tasks.caldav.iCalendar
|
|
import org.tasks.caldav.iCalendar.Companion.fromVtodo
|
|
import org.tasks.data.CaldavAccount
|
|
import org.tasks.data.CaldavCalendar
|
|
import org.tasks.data.CaldavDao
|
|
import org.tasks.time.DateTimeUtils.currentTimeMillis
|
|
import timber.log.Timber
|
|
import javax.inject.Inject
|
|
|
|
class EtebaseSynchronizer @Inject constructor(
|
|
@param:ApplicationContext private val context: Context,
|
|
private val caldavDao: CaldavDao,
|
|
private val localBroadcastManager: LocalBroadcastManager,
|
|
private val taskDeleter: TaskDeleter,
|
|
private val inventory: Inventory,
|
|
private val clientProvider: EtebaseClientProvider,
|
|
private val iCal: iCalendar,
|
|
private val vtodoCache: VtodoCache,
|
|
) {
|
|
companion object {
|
|
init {
|
|
prodId = ProdId("+//IDN tasks.org//android-" + BuildConfig.VERSION_CODE + "//EN")
|
|
}
|
|
}
|
|
|
|
suspend fun sync(account: CaldavAccount) {
|
|
Thread.currentThread().contextClassLoader = context.classLoader
|
|
|
|
if (!inventory.hasPro) {
|
|
setError(account, context.getString(R.string.requires_pro_subscription))
|
|
return
|
|
}
|
|
if (isNullOrEmpty(account.password)) {
|
|
setError(account, context.getString(R.string.password_required))
|
|
return
|
|
}
|
|
try {
|
|
synchronize(account)
|
|
} catch (e: ConnectionException) {
|
|
setError(account, e)
|
|
} catch (e: PermissionDeniedException) {
|
|
setError(account, e)
|
|
} catch (e: ServerErrorException) {
|
|
setError(account, e)
|
|
} catch (e: TemporaryServerErrorException) {
|
|
setError(account, e)
|
|
} catch (e: UnauthorizedException) {
|
|
setError(account, e)
|
|
}
|
|
}
|
|
|
|
private suspend fun synchronize(account: CaldavAccount) {
|
|
val client = clientProvider.forAccount(account)
|
|
val collections = client.getCollections()
|
|
val uids = collections.map { it.uid }
|
|
Timber.d("Found uids: %s", uids)
|
|
for (calendar in caldavDao.findDeletedCalendars(account.uuid!!, uids)) {
|
|
taskDeleter.delete(calendar)
|
|
}
|
|
for (collection in collections) {
|
|
val uid = collection.uid
|
|
var calendar = caldavDao.getCalendarByUrl(account.uuid!!, uid)
|
|
val meta = collection.meta
|
|
val color = meta.color?.let { Color.parseColor(it) } ?: 0
|
|
if (calendar == null) {
|
|
calendar = CaldavCalendar()
|
|
calendar.name = meta.name
|
|
calendar.account = account.uuid
|
|
calendar.url = collection.uid
|
|
calendar.uuid = UUIDHelper.newUUID()
|
|
calendar.color = color
|
|
caldavDao.insert(calendar)
|
|
} else if (calendar.name != meta.name || calendar.color != color) {
|
|
calendar.name = meta.name
|
|
calendar.color = color
|
|
caldavDao.update(calendar)
|
|
localBroadcastManager.broadcastRefreshList()
|
|
}
|
|
fetchChanges(client, calendar, collection)
|
|
pushLocalChanges(client, calendar, collection)
|
|
}
|
|
setError(account, "")
|
|
}
|
|
|
|
private suspend fun setError(account: CaldavAccount, e: Throwable) =
|
|
setError(account, e.message)
|
|
|
|
private suspend fun setError(account: CaldavAccount, message: String?) {
|
|
account.error = message
|
|
caldavDao.update(account)
|
|
localBroadcastManager.broadcastRefreshList()
|
|
if (!isNullOrEmpty(message)) {
|
|
Timber.e(message)
|
|
}
|
|
}
|
|
|
|
private suspend fun fetchChanges(
|
|
client: EtebaseClient,
|
|
caldavCalendar: CaldavCalendar,
|
|
collection: Collection
|
|
) {
|
|
if (caldavCalendar.ctag?.equals(collection.stoken) == true) {
|
|
Timber.d("${caldavCalendar.name} up to date")
|
|
return
|
|
}
|
|
Timber.d("updating $caldavCalendar")
|
|
client.fetchItems(collection, caldavCalendar) { (stoken, items) ->
|
|
applyEntries(caldavCalendar, items, stoken)
|
|
client.updateCache(collection, items)
|
|
}
|
|
Timber.d("UPDATE %s", caldavCalendar)
|
|
caldavDao.update(caldavCalendar)
|
|
caldavDao.updateParents(caldavCalendar.uuid!!)
|
|
localBroadcastManager.broadcastRefresh()
|
|
}
|
|
|
|
private suspend fun pushLocalChanges(
|
|
client: EtebaseClient,
|
|
caldavCalendar: CaldavCalendar,
|
|
collection: Collection
|
|
) {
|
|
val changes = ArrayList<Item>()
|
|
for (caldavTask in caldavDao.getMoved(caldavCalendar.uuid!!)) {
|
|
client.deleteItem(collection, caldavTask)
|
|
?.let { changes.add(it) }
|
|
?: run {
|
|
vtodoCache.delete(caldavCalendar, caldavTask)
|
|
caldavDao.delete(caldavTask)
|
|
}
|
|
}
|
|
for (change in caldavDao.getCaldavTasksToPush(caldavCalendar.uuid!!)) {
|
|
val task = change.task
|
|
val caldavTask = change.caldavTask
|
|
caldavTask.lastSync = task.modificationDate
|
|
if (task.isDeleted) {
|
|
client.deleteItem(collection, caldavTask)
|
|
?.let { changes.add(it) }
|
|
?: taskDeleter.delete(task)
|
|
} else {
|
|
changes.add(
|
|
client.updateItem(
|
|
collection,
|
|
caldavTask,
|
|
iCal.toVtodo(caldavCalendar, caldavTask, task)
|
|
)
|
|
)
|
|
}
|
|
}
|
|
if (changes.isNotEmpty()) {
|
|
client.uploadChanges(collection, changes)
|
|
applyEntries(caldavCalendar, changes, isLocalChange = true)
|
|
client.updateCache(collection, changes)
|
|
}
|
|
}
|
|
|
|
private suspend fun applyEntries(
|
|
caldavCalendar: CaldavCalendar,
|
|
items: List<Item>,
|
|
stoken: String? = null,
|
|
isLocalChange: Boolean = false) {
|
|
for (item in items) {
|
|
val vtodo = item.contentString
|
|
val task = fromVtodo(vtodo) ?: continue
|
|
val remoteId = task.uid
|
|
val caldavTask = caldavDao.getTaskByRemoteId(caldavCalendar.uuid!!, remoteId!!)
|
|
if (item.isDeleted) {
|
|
if (caldavTask != null) {
|
|
if (caldavTask.isDeleted()) {
|
|
vtodoCache.delete(caldavCalendar, caldavTask)
|
|
caldavDao.delete(caldavTask)
|
|
} else {
|
|
taskDeleter.delete(caldavTask.task)
|
|
}
|
|
}
|
|
} else if (isLocalChange) {
|
|
caldavTask?.let {
|
|
vtodoCache.putVtodo(caldavCalendar, it, vtodo)
|
|
it.lastSync = item.meta.mtime ?: currentTimeMillis()
|
|
caldavDao.update(it)
|
|
}
|
|
} else {
|
|
caldavTask?.`object` = item.uid
|
|
iCal.fromVtodo(caldavCalendar, caldavTask, task, vtodo, item.uid, null)
|
|
}
|
|
}
|
|
stoken?.let {
|
|
caldavCalendar.ctag = it
|
|
caldavDao.update(caldavCalendar)
|
|
}
|
|
}
|
|
} |