Compare commits

...

2 Commits

Author SHA1 Message Date
Ricki Hirner
a89483d8c7 [WIP] synctools: use new builders 2025-08-12 20:55:06 +02:00
Ricki Hirner
60eba44541 Rename 2025-08-09 17:26:49 +02:00
11 changed files with 83 additions and 71 deletions

View File

@@ -32,7 +32,7 @@ class LocalTestResource: LocalResource<Any> {
this.flags = flags
}
override fun update(data: Any, fileName: String?, eTag: String?, scheduleTag: String?, flags: Int) = throw NotImplementedError()
override fun updateFromRemote(data: Any, fileName: String?, eTag: String?, scheduleTag: String?, flags: Int) = throw NotImplementedError()
override fun deleteLocal() = throw NotImplementedError()
override fun resetDeleted() = throw NotImplementedError()

View File

@@ -10,7 +10,8 @@ import android.provider.CalendarContract.Events
import androidx.core.content.contentValuesOf
import at.bitfire.ical4android.Event
import at.bitfire.ical4android.util.MiscUtils.asSyncAdapter
import at.bitfire.synctools.mapping.calendar.LegacyAndroidEventBuilder2
import at.bitfire.synctools.icalendar.AssociatedEvents
import at.bitfire.synctools.mapping.calendar.AndroidEventBuilder
import at.bitfire.synctools.storage.BatchOperation
import at.bitfire.synctools.storage.calendar.AndroidCalendar
import at.bitfire.synctools.storage.calendar.AndroidEvent2
@@ -63,16 +64,17 @@ class LocalCalendar @AssistedInject constructor(
private val recurringCalendar = AndroidRecurringCalendar(androidCalendar)
fun add(event: Event, fileName: String, eTag: String?, scheduleTag: String?, flags: Int) {
val mapped = LegacyAndroidEventBuilder2(
calendar = androidCalendar,
event = event,
id = null,
fun addFromRemote(associatedEvents: AssociatedEvents, legacyEvent: Event, fileName: String, eTag: String?, scheduleTag: String?, flags: Int) {
val mapped = AndroidEventBuilder(
syncId = fileName,
eTag = eTag,
scheduleTag = scheduleTag,
flags = flags
).build()
flags = flags,
associatedEvents = associatedEvents,
androidCalendar = androidCalendar,
event = legacyEvent,
id = null
).build() ?: TODO("Handle invalid event")
recurringCalendar.addEventAndExceptions(mapped)
}

View File

@@ -114,7 +114,7 @@ class LocalContact: AndroidContact, LocalAddress {
addressBook.provider!!.update(rawContactSyncURI(), values, null, null)
}
override fun update(data: Contact, fileName: String?, eTag: String?, scheduleTag: String?, flags: Int) {
override fun updateFromRemote(data: Contact, fileName: String?, eTag: String?, scheduleTag: String?, flags: Int) {
this.fileName = fileName
this.eTag = eTag
this.flags = flags

View File

@@ -8,7 +8,8 @@ import android.provider.CalendarContract.Events
import androidx.core.content.contentValuesOf
import at.bitfire.ical4android.Event
import at.bitfire.ical4android.LegacyAndroidCalendar
import at.bitfire.synctools.mapping.calendar.LegacyAndroidEventBuilder2
import at.bitfire.synctools.icalendar.AssociatedEvents
import at.bitfire.synctools.mapping.calendar.AndroidEventBuilder
import at.bitfire.synctools.storage.LocalStorageException
import at.bitfire.synctools.storage.calendar.AndroidEvent2
import at.bitfire.synctools.storage.calendar.AndroidRecurringCalendar
@@ -18,7 +19,7 @@ import java.util.UUID
class LocalEvent(
val recurringCalendar: AndroidRecurringCalendar,
val androidEvent: AndroidEvent2
) : LocalResource<Event> {
) : LocalResource<AssociatedEvents> {
override val id: Long
get() = androidEvent.id
@@ -36,17 +37,21 @@ class LocalEvent(
get() = androidEvent.flags
override fun update(data: Event, fileName: String?, eTag: String?, scheduleTag: String?, flags: Int) {
val eventAndExceptions = LegacyAndroidEventBuilder2(
calendar = androidEvent.calendar,
event = data,
id = id,
syncId = fileName,
override fun updateFromRemote(data: AssociatedEvents, fileName: String?, eTag: String?, scheduleTag: String?, flags: Int) {
val eventAndExceptions = AndroidEventBuilder(
associatedEvents = data,
calendarId = androidEvent.calendar.id,
syncId = fileName ?: TODO(),
eTag = eTag,
scheduleTag = scheduleTag,
flags = flags
).build()
recurringCalendar.updateEventAndExceptions(id, eventAndExceptions)
if (eventAndExceptions != null)
recurringCalendar.updateEventAndExceptions(id, eventAndExceptions)
else {
// TODO handle invalid event
}
}

View File

@@ -212,7 +212,7 @@ class LocalGroup: AndroidGroup, LocalAddress {
batch.commit()
}
override fun update(data: Contact, fileName: String?, eTag: String?, scheduleTag: String?, flags: Int) {
override fun updateFromRemote(data: Contact, fileName: String?, eTag: String?, scheduleTag: String?, flags: Int) {
this.fileName = fileName
this.eTag = eTag
this.scheduleTag = scheduleTag

View File

@@ -50,7 +50,7 @@ class LocalJtxICalObject(
}
override fun update(data: JtxICalObject, fileName: String?, eTag: String?, scheduleTag: String?, flags: Int) {
override fun updateFromRemote(data: JtxICalObject, fileName: String?, eTag: String?, scheduleTag: String?, flags: Int) {
this.fileName = fileName
this.eTag = eTag
this.scheduleTag = scheduleTag

View File

@@ -81,7 +81,7 @@ interface LocalResource<in TData: Any> {
*
* @return content URI of the updated row (e.g. event URI)
*/
fun update(data: TData, fileName: String?, eTag: String?, scheduleTag: String?, flags: Int)
fun updateFromRemote(data: TData, fileName: String?, eTag: String?, scheduleTag: String?, flags: Int)
/**
* Deletes the data object from the content provider.

View File

@@ -94,7 +94,7 @@ class LocalTask: DmfsTask, LocalResource<Task> {
this.eTag = eTag
}
override fun update(data: Task, fileName: String?, eTag: String?, scheduleTag: String?, flags: Int) {
override fun updateFromRemote(data: Task, fileName: String?, eTag: String?, scheduleTag: String?, flags: Int) {
this.fileName = fileName
this.eTag = eTag
this.scheduleTag = scheduleTag

View File

@@ -28,26 +28,23 @@ import at.bitfire.davdroid.resource.LocalResource
import at.bitfire.davdroid.resource.SyncState
import at.bitfire.davdroid.settings.AccountSettings
import at.bitfire.davdroid.util.DavUtils.lastSegment
import at.bitfire.ical4android.Event
import at.bitfire.ical4android.EventReader
import at.bitfire.ical4android.EventWriter
import at.bitfire.ical4android.util.DateUtils
import at.bitfire.synctools.exception.InvalidRemoteResourceException
import at.bitfire.synctools.icalendar.CalendarUidSplitter
import at.bitfire.synctools.icalendar.ICalendarParser
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.runInterruptible
import net.fortuna.ical4j.model.Component
import net.fortuna.ical4j.model.component.VAlarm
import net.fortuna.ical4j.model.property.Action
import net.fortuna.ical4j.model.component.VEvent
import okhttp3.HttpUrl
import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import java.io.Reader
import java.io.StringReader
import java.io.StringWriter
import java.time.Duration
import java.time.ZonedDateTime
import java.util.Optional
import java.util.logging.Level
@@ -262,55 +259,63 @@ class CalendarSyncManager @AssistedInject constructor(
// helpers
private fun processVEvent(fileName: String, eTag: String, scheduleTag: String?, reader: Reader) {
val events: List<Event>
try {
events = EventReader().readEvents(reader)
val vEvent = try {
val iCalendar = ICalendarParser().parse(reader)
val vEvents = CalendarUidSplitter<VEvent>().associateByUid(iCalendar, Component.VEVENT)
if (vEvents.size > 1)
logger.warning("Received iCalendar with more than one UID; ignoring all but first one")
vEvents.values.firstOrNull()
} catch (e: InvalidRemoteResourceException) {
logger.log(Level.SEVERE, "Received invalid iCalendar, ignoring", e)
notifyInvalidResource(e, fileName)
return
}
if (events.size == 1) {
val event = events.first()
if (vEvent == null) {
logger.warning("Ignoring iCalendar without VEVENTs ($fileName)")
return
}
// set default reminder for non-full-day events, if requested
val defaultAlarmMinBefore = accountSettings.getDefaultAlarm()
if (defaultAlarmMinBefore != null && DateUtils.isDateTime(event.dtStart) && event.alarms.isEmpty()) {
val alarm = VAlarm(Duration.ofMinutes(-defaultAlarmMinBefore.toLong())).apply {
// Sets METHOD_ALERT instead of METHOD_DEFAULT in the calendar provider.
// Needed for calendars to actually show a notification.
properties += Action.DISPLAY
}
logger.log(Level.FINE, "${event.uid}: Adding default alarm", alarm)
event.alarms += alarm
}
// TODO we need both the new associated event and the legacy event here,
// then pass both to addupdateFromRemote / updateFromRemote.
// update local event, if it exists
val local = localCollection.findByName(fileName)
SyncException.wrapWithLocalResource(local) {
if (local != null) {
logger.log(Level.INFO, "Updating $fileName in local calendar", event)
local.update(
data = event,
fileName = fileName,
eTag = eTag,
scheduleTag = scheduleTag,
flags = LocalResource.FLAG_REMOTELY_PRESENT
)
} else {
logger.log(Level.INFO, "Adding $fileName to local calendar", event)
localCollection.add(
event = event,
fileName = fileName,
eTag = eTag,
scheduleTag = scheduleTag,
flags = LocalResource.FLAG_REMOTELY_PRESENT
)
}
// TODO add default reminder
/* set default reminder for non-full-day events, if requested
val defaultAlarmMinBefore = accountSettings.getDefaultAlarm()
if (defaultAlarmMinBefore != null && DateUtils.isDateTime(event.dtStart) && event.alarms.isEmpty()) {
val alarm = VAlarm(Duration.ofMinutes(-defaultAlarmMinBefore.toLong())).apply {
// Sets METHOD_ALERT instead of METHOD_DEFAULT in the calendar provider.
// Needed for calendars to actually show a notification.
properties += Action.DISPLAY
}
} else
logger.info("Received VCALENDAR with not exactly one VEVENT with UID and without RECURRENCE-ID; ignoring $fileName")
logger.log(Level.FINE, "${event.uid}: Adding default alarm", alarm)
event.alarms += alarm
} */
val localEvent = localCollection.findByName(fileName)
SyncException.wrapWithLocalResource(localEvent) {
if (localEvent != null) {
logger.log(Level.INFO, "Updating $fileName in local calendar", vEvent)
localEvent.updateFromRemote(
data = vEvent,
fileName = fileName,
eTag = eTag,
scheduleTag = scheduleTag,
flags = LocalResource.FLAG_REMOTELY_PRESENT
)
} else {
logger.log(Level.INFO, "Adding $fileName to local calendar", vEvent)
localCollection.addFromRemote(
associatedEvents = vEvent,
fileName = fileName,
eTag = eTag,
scheduleTag = scheduleTag,
flags = LocalResource.FLAG_REMOTELY_PRESENT
)
}
}
}
override fun notifyInvalidResourceTitle(): String =

View File

@@ -421,7 +421,7 @@ class ContactsSyncManager @AssistedInject constructor(
if ((existing is LocalGroup && newData.group) || (existing is LocalContact && !newData.group)) {
// update contact / group
existing.update(
existing.updateFromRemote(
data = newData,
fileName = fileName,
eTag = eTag,

View File

@@ -20,7 +20,7 @@ androidx-test-junit = "1.3.0"
androidx-work = "2.10.3"
bitfire-cert4android = "41009d48ed"
bitfire-dav4jvm = "cb6065b262"
bitfire-synctools = "54557601b8"
bitfire-synctools = "4d847edf27"
compose-accompanist = "0.37.3"
compose-bom = "2025.07.00"
dnsjava = "3.6.3"