@@ -11,8 +11,6 @@ import android.content.Entity
1111import android.provider.CalendarContract.Attendees
1212import android.provider.CalendarContract.Events
1313import android.provider.CalendarContract.ExtendedProperties
14- import android.provider.CalendarContract.Reminders
15- import android.util.Patterns
1614import at.bitfire.ical4android.Event
1715import at.bitfire.ical4android.UnknownProperty
1816import at.bitfire.ical4android.util.AndroidTimeUtils
@@ -21,22 +19,18 @@ import at.bitfire.ical4android.util.TimeApiExtensions
2119import at.bitfire.ical4android.util.TimeApiExtensions.toZonedDateTime
2220import at.bitfire.synctools.exception.InvalidLocalResourceException
2321import at.bitfire.synctools.icalendar.Css3Color
22+ import at.bitfire.synctools.mapping.calendar.processor.AndroidEventFieldProcessor
23+ import at.bitfire.synctools.mapping.calendar.processor.AttendeesProcessor
24+ import at.bitfire.synctools.mapping.calendar.processor.RemindersProcessor
25+ import at.bitfire.synctools.mapping.calendar.processor.UidProcessor
2426import at.bitfire.synctools.storage.calendar.AndroidEvent2
2527import at.bitfire.synctools.storage.calendar.EventAndExceptions
2628import net.fortuna.ical4j.model.Date
2729import net.fortuna.ical4j.model.DateList
2830import net.fortuna.ical4j.model.DateTime
2931import net.fortuna.ical4j.model.TimeZoneRegistryFactory
30- import net.fortuna.ical4j.model.component.VAlarm
31- import net.fortuna.ical4j.model.parameter.Cn
32- import net.fortuna.ical4j.model.parameter.Email
33- import net.fortuna.ical4j.model.parameter.PartStat
34- import net.fortuna.ical4j.model.parameter.Rsvp
3532import net.fortuna.ical4j.model.parameter.Value
36- import net.fortuna.ical4j.model.property.Action
37- import net.fortuna.ical4j.model.property.Attendee
3833import net.fortuna.ical4j.model.property.Clazz
39- import net.fortuna.ical4j.model.property.Description
4034import net.fortuna.ical4j.model.property.DtEnd
4135import net.fortuna.ical4j.model.property.DtStart
4236import net.fortuna.ical4j.model.property.ExDate
@@ -46,7 +40,6 @@ import net.fortuna.ical4j.model.property.RDate
4640import net.fortuna.ical4j.model.property.RRule
4741import net.fortuna.ical4j.model.property.RecurrenceId
4842import net.fortuna.ical4j.model.property.Status
49- import net.fortuna.ical4j.model.property.Summary
5043import net.fortuna.ical4j.util.TimeZones
5144import java.net.URI
5245import java.net.URISyntaxException
@@ -76,11 +69,22 @@ class LegacyAndroidEventProcessor(
7669
7770 private val tzRegistry by lazy { TimeZoneRegistryFactory .getInstance().createRegistry() }
7871
72+ private val fieldProcessors: Array <AndroidEventFieldProcessor > = arrayOf(
73+ UidProcessor (),
74+ AttendeesProcessor (),
75+ RemindersProcessor (accountName)
76+ )
77+
7978
8079 fun populate (eventAndExceptions : EventAndExceptions , to : Event ) {
81- populateEvent(eventAndExceptions.main, to = to)
80+ populateEvent(
81+ entity = eventAndExceptions.main,
82+ main = eventAndExceptions.main,
83+ to = to
84+ )
8285 populateExceptions(
8386 exceptions = eventAndExceptions.exceptions,
87+ main = eventAndExceptions.main,
8488 originalAllDay = DateUtils .isDate(to.dtStart),
8589 to = to
8690 )
@@ -94,25 +98,25 @@ class LegacyAndroidEventProcessor(
9498 * an [Event] data object.
9599 *
96100 * @param entity event row as returned by the calendar provider
97- * @param groupScheduled whether the event is group-scheduled (= the main event has attendees)
101+ * @param main main event row as returned by the calendar provider
98102 * @param to destination data object
99103 */
100- private fun populateEvent (entity : Entity , to : Event ) {
101- // calculate some scheduling properties
104+ private fun populateEvent (entity : Entity , main : Entity , to : Event ) {
105+ // legacy processors
102106 val hasAttendees = entity.subValues.any { it.uri == Attendees .CONTENT_URI }
103-
104- // main row
105107 populateEventRow(entity.entityValues, groupScheduled = hasAttendees, to = to)
106108
107109 // data rows
108110 for (subValue in entity.subValues) {
109111 val subValues = subValue.values
110112 when (subValue.uri) {
111- Attendees .CONTENT_URI -> populateAttendee(subValues, to = to)
112- Reminders .CONTENT_URI -> populateReminder(subValues, to = to)
113113 ExtendedProperties .CONTENT_URI -> populateExtended(subValues, to = to)
114114 }
115115 }
116+
117+ // new processors
118+ for (processor in fieldProcessors)
119+ processor.process(from = entity, main = main, to = to)
116120 }
117121
118122 private fun populateEventRow (row : ContentValues , groupScheduled : Boolean , to : Event ) {
@@ -236,7 +240,6 @@ class LegacyAndroidEventProcessor(
236240 logger.log(Level .WARNING , " Couldn't parse recurrence rules, ignoring" , e)
237241 }
238242
239- to.uid = row.getAsString(Events .UID_2445 )
240243 to.sequence = row.getAsInteger(AndroidEvent2 .COLUMN_SEQUENCE )
241244 to.isOrganizer = row.getAsBoolean(Events .IS_ORGANIZER )
242245
@@ -306,79 +309,6 @@ class LegacyAndroidEventProcessor(
306309 }
307310 }
308311
309- private fun populateAttendee (row : ContentValues , to : Event ) {
310- logger.log(Level .FINE , " Read event attendee from calender provider" , row)
311-
312- try {
313- val attendee: Attendee
314- val email = row.getAsString(Attendees .ATTENDEE_EMAIL )
315- val idNS = row.getAsString(Attendees .ATTENDEE_ID_NAMESPACE )
316- val id = row.getAsString(Attendees .ATTENDEE_IDENTITY )
317-
318- if (idNS != null || id != null ) {
319- // attendee identified by namespace and ID
320- attendee = Attendee (URI (idNS, id, null ))
321- email?.let { attendee.parameters.add(Email (it)) }
322- } else
323- // attendee identified by email address
324- attendee = Attendee (URI (" mailto" , email, null ))
325- val params = attendee.parameters
326-
327- // always add RSVP (offer attendees to accept/decline)
328- params.add(Rsvp .TRUE )
329-
330- row.getAsString(Attendees .ATTENDEE_NAME )?.let { cn -> params.add(Cn (cn)) }
331-
332- // type/relation mapping is complex and thus outsourced to AttendeeMappings
333- AttendeeMappings .androidToICalendar(row, attendee)
334-
335- // status
336- when (row.getAsInteger(Attendees .ATTENDEE_STATUS )) {
337- Attendees .ATTENDEE_STATUS_INVITED -> params.add(PartStat .NEEDS_ACTION )
338- Attendees .ATTENDEE_STATUS_ACCEPTED -> params.add(PartStat .ACCEPTED )
339- Attendees .ATTENDEE_STATUS_DECLINED -> params.add(PartStat .DECLINED )
340- Attendees .ATTENDEE_STATUS_TENTATIVE -> params.add(PartStat .TENTATIVE )
341- Attendees .ATTENDEE_STATUS_NONE -> { /* no information, don't add PARTSTAT */ }
342- }
343-
344- to.attendees.add(attendee)
345- } catch (e: URISyntaxException ) {
346- logger.log(Level .WARNING , " Couldn't parse attendee information, ignoring" , e)
347- }
348- }
349-
350- private fun populateReminder (row : ContentValues , to : Event ) {
351- logger.log(Level .FINE , " Read event reminder from calender provider" , row)
352-
353- val alarm = VAlarm (Duration .ofMinutes(- row.getAsLong(Reminders .MINUTES )))
354-
355- val props = alarm.properties
356- when (row.getAsInteger(Reminders .METHOD )) {
357- Reminders .METHOD_EMAIL -> {
358- if (Patterns .EMAIL_ADDRESS .matcher(accountName).matches()) {
359- props + = Action .EMAIL
360- // ACTION:EMAIL requires SUMMARY, DESCRIPTION, ATTENDEE
361- props + = Summary (to.summary)
362- props + = Description (to.description ? : to.summary)
363- // Android doesn't allow to save email reminder recipients, so we always use the
364- // account name (should be account owner's email address)
365- props + = Attendee (URI (" mailto" , accountName, null ))
366- } else {
367- logger.warning(" Account name is not an email address; changing EMAIL reminder to DISPLAY" )
368- props + = Action .DISPLAY
369- props + = Description (to.summary)
370- }
371- }
372-
373- // default: set ACTION:DISPLAY (requires DESCRIPTION)
374- else -> {
375- props + = Action .DISPLAY
376- props + = Description (to.summary)
377- }
378- }
379- to.alarms + = alarm
380- }
381-
382312 private fun populateExtended (row : ContentValues , to : Event ) {
383313 val name = row.getAsString(ExtendedProperties .NAME )
384314 val rawValue = row.getAsString(ExtendedProperties .VALUE )
@@ -396,11 +326,6 @@ class LegacyAndroidEventProcessor(
396326 logger.warning(" Won't process invalid local URL: $rawValue " )
397327 }
398328
399- AndroidEvent2 .EXTNAME_ICAL_UID ->
400- // only consider iCalUid when there's no uid
401- if (to.uid == null )
402- to.uid = rawValue
403-
404329 UnknownProperty .CONTENT_ITEM_TYPE ->
405330 to.unknownProperties + = UnknownProperty .fromJsonString(rawValue)
406331 }
@@ -409,12 +334,12 @@ class LegacyAndroidEventProcessor(
409334 }
410335 }
411336
412- private fun populateExceptions (exceptions : List <Entity >, originalAllDay : Boolean , to : Event ) {
337+ private fun populateExceptions (exceptions : List <Entity >, main : Entity , originalAllDay : Boolean , to : Event ) {
413338 for (exception in exceptions) {
414339 val exceptionEvent = Event ()
415340
416341 // convert exception row to Event
417- populateEvent(exception, to = exceptionEvent)
342+ populateEvent(exception, main, to = exceptionEvent)
418343
419344 // exceptions are required to have a RECURRENCE-ID
420345 val recurrenceId = exceptionEvent.recurrenceId ? : continue
0 commit comments