@@ -8,15 +8,10 @@ package at.bitfire.synctools.mapping.calendar
88
99import android.content.ContentValues
1010import android.content.Entity
11- import android.provider.CalendarContract.Attendees
1211import android.provider.CalendarContract.Colors
1312import android.provider.CalendarContract.Events
14- import android.provider.CalendarContract.ExtendedProperties
15- import android.provider.CalendarContract.Reminders
1613import androidx.core.content.contentValuesOf
1714import at.bitfire.ical4android.Event
18- import at.bitfire.ical4android.ICalendar
19- import at.bitfire.ical4android.UnknownProperty
2015import at.bitfire.ical4android.util.AndroidTimeUtils
2116import at.bitfire.ical4android.util.DateUtils
2217import at.bitfire.ical4android.util.MiscUtils.asSyncAdapter
@@ -28,31 +23,32 @@ import at.bitfire.ical4android.util.TimeApiExtensions.toLocalTime
2823import at.bitfire.ical4android.util.TimeApiExtensions.toRfc5545Duration
2924import at.bitfire.ical4android.util.TimeApiExtensions.toZonedDateTime
3025import at.bitfire.synctools.exception.InvalidLocalResourceException
31- import at.bitfire.synctools.mapping.calendar.builder.AndroidEventFieldBuilder
26+ import at.bitfire.synctools.mapping.calendar.builder.AndroidEntityBuilder
27+ import at.bitfire.synctools.mapping.calendar.builder.AttendeesBuilder
28+ import at.bitfire.synctools.mapping.calendar.builder.CategoriesBuilder
29+ import at.bitfire.synctools.mapping.calendar.builder.DescriptionBuilder
30+ import at.bitfire.synctools.mapping.calendar.builder.LocationBuilder
31+ import at.bitfire.synctools.mapping.calendar.builder.RemindersBuilder
32+ import at.bitfire.synctools.mapping.calendar.builder.RetainedClassificationBuilder
3233import at.bitfire.synctools.mapping.calendar.builder.TitleBuilder
34+ import at.bitfire.synctools.mapping.calendar.builder.UnknownPropertiesBuilder
35+ import at.bitfire.synctools.mapping.calendar.builder.UrlBuilder
3336import at.bitfire.synctools.storage.calendar.AndroidCalendar
3437import at.bitfire.synctools.storage.calendar.AndroidEvent2
3538import at.bitfire.synctools.storage.calendar.EventAndExceptions
3639import net.fortuna.ical4j.model.Date
3740import net.fortuna.ical4j.model.DateList
3841import net.fortuna.ical4j.model.DateTime
3942import net.fortuna.ical4j.model.Parameter
40- import net.fortuna.ical4j.model.Property
4143import net.fortuna.ical4j.model.TimeZoneRegistryFactory
42- import net.fortuna.ical4j.model.component.VAlarm
43- import net.fortuna.ical4j.model.parameter.Cn
4444import net.fortuna.ical4j.model.parameter.Email
45- import net.fortuna.ical4j.model.parameter.PartStat
46- import net.fortuna.ical4j.model.property.Action
47- import net.fortuna.ical4j.model.property.Attendee
4845import net.fortuna.ical4j.model.property.Clazz
4946import net.fortuna.ical4j.model.property.DtEnd
5047import net.fortuna.ical4j.model.property.RDate
5148import net.fortuna.ical4j.model.property.Status
5249import java.time.Duration
5350import java.time.Period
5451import java.time.ZonedDateTime
55- import java.util.Locale
5652import java.util.logging.Logger
5753
5854/* *
@@ -77,6 +73,20 @@ class LegacyAndroidEventBuilder2(
7773 private val flags : Int
7874) {
7975
76+ private val fieldBuilders: Array <AndroidEntityBuilder > = arrayOf(
77+ // event fields (order as in CalendarContract.EventsColumns)
78+ TitleBuilder (),
79+ DescriptionBuilder (),
80+ LocationBuilder (),
81+ // sub-rows (alphabetically, by class name)
82+ AttendeesBuilder (calendar),
83+ CategoriesBuilder (),
84+ RemindersBuilder (),
85+ RetainedClassificationBuilder (),
86+ UnknownPropertiesBuilder (),
87+ UrlBuilder ()
88+ )
89+
8090 private val logger
8191 get() = Logger .getLogger(javaClass.name)
8292
@@ -92,43 +102,13 @@ class LegacyAndroidEventBuilder2(
92102 )
93103
94104 fun buildEvent (recurrence : Event ? ): Entity {
105+ // build main row from legacy builders
95106 val row = buildEventRow(recurrence)
96107
108+ // additionally apply new builders
97109 val entity = Entity (row)
98- val from = recurrence ? : event
99-
100- // new builders
101-
102- for (builder in fieldBuilders())
103- builder.build(from = from, main = event, to = entity)
104-
105- // legacy fields
106-
107- for (reminder in from.alarms)
108- entity.addSubValue(Reminders .CONTENT_URI , buildReminder(reminder))
109-
110- for (attendee in from.attendees)
111- entity.addSubValue(Attendees .CONTENT_URI , buildAttendee(attendee))
112-
113- // extended properties
114- if (event.categories.isNotEmpty())
115- entity.addSubValue(ExtendedProperties .CONTENT_URI , buildCategories(event.categories))
116-
117- event.classification?.let { classification ->
118- val values = buildRetainedClassification(classification)
119- if (values != null )
120- entity.addSubValue(ExtendedProperties .CONTENT_URI , values)
121- }
122-
123- event.url?.let { url ->
124- entity.addSubValue(ExtendedProperties .CONTENT_URI , buildUrl(url.toString()))
125- }
126-
127- for (unknownProperty in event.unknownProperties) {
128- val values = buildUnknownProperty(unknownProperty)
129- if (values != null )
130- entity.addSubValue(ExtendedProperties .CONTENT_URI , values)
131- }
110+ for (builder in fieldBuilders)
111+ builder.build(from = recurrence ? : event, main = event, to = entity)
132112
133113 return entity
134114 }
@@ -339,10 +319,6 @@ class LegacyAndroidEventBuilder2(
339319 row.putNull(Events .EXDATE )
340320 }
341321
342- // text fields
343- row.put(Events .EVENT_LOCATION , from.location)
344- row.put(Events .DESCRIPTION , from.description)
345-
346322 // color
347323 val color = from.color
348324 if (color != null ) {
@@ -404,114 +380,4 @@ class LegacyAndroidEventBuilder2(
404380 return row
405381 }
406382
407- private fun buildAttendee (attendee : Attendee ): ContentValues {
408- val values = ContentValues ()
409- val organizer = event.organizerEmail ? :
410- /* no ORGANIZER, use current account owner as ORGANIZER */
411- calendar.ownerAccount ? : calendar.account.name
412-
413- val member = attendee.calAddress
414- if (member.scheme.equals(" mailto" , true )) // attendee identified by email
415- values.put(Attendees .ATTENDEE_EMAIL , member.schemeSpecificPart)
416- else {
417- // attendee identified by other URI
418- values.put(Attendees .ATTENDEE_ID_NAMESPACE , member.scheme)
419- values.put(Attendees .ATTENDEE_IDENTITY , member.schemeSpecificPart)
420-
421- attendee.getParameter<Email >(Parameter .EMAIL )?.let { email ->
422- values.put(Attendees .ATTENDEE_EMAIL , email.value)
423- }
424- }
425-
426- attendee.getParameter<Cn >(Parameter .CN )?.let { cn ->
427- values.put(Attendees .ATTENDEE_NAME , cn.value)
428- }
429-
430- // type/relation mapping is complex and thus outsourced to AttendeeMappings
431- AttendeeMappings .iCalendarToAndroid(attendee, values, organizer)
432-
433- val status = when (attendee.getParameter(Parameter .PARTSTAT ) as ? PartStat ) {
434- PartStat .ACCEPTED -> Attendees .ATTENDEE_STATUS_ACCEPTED
435- PartStat .DECLINED -> Attendees .ATTENDEE_STATUS_DECLINED
436- PartStat .TENTATIVE -> Attendees .ATTENDEE_STATUS_TENTATIVE
437- PartStat .DELEGATED -> Attendees .ATTENDEE_STATUS_NONE
438- else /* default: PartStat.NEEDS_ACTION */ -> Attendees .ATTENDEE_STATUS_INVITED
439- }
440- values.put(Attendees .ATTENDEE_STATUS , status)
441-
442- return values
443- }
444-
445- private fun buildReminder (alarm : VAlarm ): ContentValues {
446- val method = when (alarm.action?.value?.uppercase(Locale .ROOT )) {
447- Action .DISPLAY .value,
448- Action .AUDIO .value -> Reminders .METHOD_ALERT // will trigger an alarm on the Android device
449-
450- // Note: The calendar provider doesn't support saving specific attendees for email reminders.
451- Action .EMAIL .value -> Reminders .METHOD_EMAIL
452-
453- else -> Reminders .METHOD_DEFAULT // won't trigger an alarm on the Android device
454- }
455-
456- val minutes = ICalendar .vAlarmToMin(alarm, event, false )?.second ? : Reminders .MINUTES_DEFAULT
457-
458- return contentValuesOf(
459- Reminders .METHOD to method,
460- Reminders .MINUTES to minutes
461- )
462- }
463-
464- private fun buildCategories (categories : List <String >): ContentValues {
465- // concatenate, separate by backslash
466- val rawCategories = categories.joinToString(AndroidEvent2 .CATEGORIES_SEPARATOR .toString()) { category ->
467- // drop occurrences of CATEGORIES_SEPARATOR in category names
468- category.filter { it != AndroidEvent2 .CATEGORIES_SEPARATOR }
469- }
470- return contentValuesOf(
471- ExtendedProperties .NAME to AndroidEvent2 .EXTNAME_CATEGORIES ,
472- ExtendedProperties .VALUE to rawCategories
473- )
474- }
475-
476- /* *
477- * Retain classification other than PUBLIC and PRIVATE as unknown property so
478- * that it can be reused when "server default" is selected.
479- *
480- * Should not be returned as an unknown property in the future, but as explicit extended property.
481- */
482- private fun buildRetainedClassification (classification : Clazz ): ContentValues ? {
483- if (classification != Clazz .PUBLIC && classification != Clazz .PRIVATE )
484- return contentValuesOf(
485- ExtendedProperties .NAME to UnknownProperty .CONTENT_ITEM_TYPE ,
486- ExtendedProperties .VALUE to UnknownProperty .toJsonString(classification)
487- )
488- return null
489- }
490-
491- private fun buildUnknownProperty (property : Property ): ContentValues ? {
492- if (property.value == null ) {
493- logger.warning(" Ignoring unknown property with null value" )
494- return null
495- }
496- if (property.value.length > UnknownProperty .MAX_UNKNOWN_PROPERTY_SIZE ) {
497- logger.warning(" Ignoring unknown property with ${property.value.length} octets (too long)" )
498- return null
499- }
500-
501- return contentValuesOf(
502- ExtendedProperties .NAME to UnknownProperty .CONTENT_ITEM_TYPE ,
503- ExtendedProperties .VALUE to UnknownProperty .toJsonString(property)
504- )
505- }
506-
507- private fun buildUrl (url : String ) = contentValuesOf(
508- ExtendedProperties .NAME to AndroidEvent2 .EXTNAME_URL ,
509- ExtendedProperties .VALUE to url
510- )
511-
512-
513- private fun fieldBuilders (): Array <AndroidEventFieldBuilder > = arrayOf(
514- TitleBuilder ()
515- )
516-
517383}
0 commit comments