Skip to content

Commit 95d0ee5

Browse files
authored
Remove Event class usage from event processors (#108)
* [WIP] Refactor processors to use VEvent and update properties * Update processors to use VEvent instead of Event * Remove legacy Android calendar and mutators processor, update event processor to generate PRODID based on mutators * Remove event sequence if zero or null, update sequence builder logic, add ProdId KDoc * Remove redundant variable shadowing in UrlProcessor Update AndroidEventProcessor constructor parameter * - Update SequenceBuilderTest to handle sequence value 0 - Add test case for sequence value 1 * Add replacement import for AssociatedEvents
1 parent b670236 commit 95d0ee5

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+506
-472
lines changed

lib/src/main/kotlin/at/bitfire/ical4android/Event.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ import java.util.LinkedList
3434
* - as it is extracted from an iCalendar or
3535
* - as it should be generated into an iCalendar.
3636
*/
37-
@Deprecated("Use net.fortuna.ical4j.model.Calendar instead")
37+
@Deprecated("Use AssociatedEvents instead", replaceWith = ReplaceWith("AssociatedEvents", "at.bitfire.synctools.icalendar"))
3838
data class Event(
3939
override var uid: String? = null,
4040
override var sequence: Int? = null,

lib/src/main/kotlin/at/bitfire/ical4android/LegacyAndroidCalendar.kt

Lines changed: 0 additions & 38 deletions
This file was deleted.

lib/src/main/kotlin/at/bitfire/synctools/icalendar/AssociatedComponents.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,16 @@ import net.fortuna.ical4j.model.component.VEvent
2424
* @param exceptions exceptions (each without RECURRENCE-ID); UID must be
2525
* 1. the same as the UID of [main],
2626
* 2. the same for all exceptions.
27+
* @param prodId optional `PRODID` related to the components
2728
*
2829
* If no [main] is present, [exceptions] must not be empty.
2930
*
3031
* @throws IllegalArgumentException when the constraints above are violated
3132
*/
3233
data class AssociatedComponents<T: CalendarComponent>(
3334
val main: T?,
34-
val exceptions: List<T>
35+
val exceptions: List<T>,
36+
val prodId: String? = null
3537
) {
3638

3739
init {
Lines changed: 55 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ package at.bitfire.synctools.mapping.calendar
88

99
import android.content.Entity
1010
import android.provider.CalendarContract.Events
11-
import at.bitfire.ical4android.Event
11+
import at.bitfire.synctools.icalendar.AssociatedEvents
1212
import at.bitfire.synctools.mapping.calendar.processor.AccessLevelProcessor
1313
import at.bitfire.synctools.mapping.calendar.processor.AndroidEventFieldProcessor
1414
import at.bitfire.synctools.mapping.calendar.processor.AttendeesProcessor
@@ -19,9 +19,9 @@ import at.bitfire.synctools.mapping.calendar.processor.DescriptionProcessor
1919
import at.bitfire.synctools.mapping.calendar.processor.DurationProcessor
2020
import at.bitfire.synctools.mapping.calendar.processor.EndTimeProcessor
2121
import at.bitfire.synctools.mapping.calendar.processor.LocationProcessor
22-
import at.bitfire.synctools.mapping.calendar.processor.MutatorsProcessor
2322
import at.bitfire.synctools.mapping.calendar.processor.OrganizerProcessor
2423
import at.bitfire.synctools.mapping.calendar.processor.OriginalInstanceTimeProcessor
24+
import at.bitfire.synctools.mapping.calendar.processor.ProdIdGenerator
2525
import at.bitfire.synctools.mapping.calendar.processor.RecurrenceFieldsProcessor
2626
import at.bitfire.synctools.mapping.calendar.processor.RemindersProcessor
2727
import at.bitfire.synctools.mapping.calendar.processor.SequenceProcessor
@@ -33,29 +33,31 @@ import at.bitfire.synctools.mapping.calendar.processor.UnknownPropertiesProcesso
3333
import at.bitfire.synctools.mapping.calendar.processor.UrlProcessor
3434
import at.bitfire.synctools.storage.calendar.EventAndExceptions
3535
import net.fortuna.ical4j.model.DateList
36+
import net.fortuna.ical4j.model.Property
3637
import net.fortuna.ical4j.model.TimeZoneRegistryFactory
38+
import net.fortuna.ical4j.model.component.VEvent
3739
import net.fortuna.ical4j.model.parameter.Value
3840
import net.fortuna.ical4j.model.property.ExDate
41+
import net.fortuna.ical4j.model.property.RDate
42+
import net.fortuna.ical4j.model.property.RRule
3943
import net.fortuna.ical4j.model.property.RecurrenceId
44+
import java.util.LinkedList
4045

4146
/**
42-
* Legacy mapper from Android event main + data rows to an [Event]
43-
* (former "populate..." methods).
47+
* Mapper from Android event main + data rows to [VEvent].
4448
*
45-
* Important: To use recurrence exceptions, you MUST set _SYNC_ID and ORIGINAL_SYNC_ID
46-
* in populateEvent() / buildEvent. Setting _ID and ORIGINAL_ID is not sufficient.
47-
*
48-
* @param accountName account name (used to generate self-attendee)
49+
* @param accountName account name (used to generate self-attendee)
50+
* @param prodIdGenerator generator for `PRODID`
4951
*/
50-
class LegacyAndroidEventProcessor(
51-
private val accountName: String
52+
class AndroidEventProcessor(
53+
accountName: String,
54+
private val prodIdGenerator: ProdIdGenerator
5255
) {
5356

5457
private val tzRegistry = TimeZoneRegistryFactory.getInstance().createRegistry()
5558

5659
private val fieldProcessors: Array<AndroidEventFieldProcessor> = arrayOf(
5760
// event row fields
58-
MutatorsProcessor(), // for PRODID
5961
UidProcessor(),
6062
OriginalInstanceTimeProcessor(tzRegistry),
6163
TitleProcessor(),
@@ -82,46 +84,51 @@ class LegacyAndroidEventProcessor(
8284
)
8385

8486

85-
fun populate(eventAndExceptions: EventAndExceptions, to: Event) {
87+
fun populate(eventAndExceptions: EventAndExceptions): AssociatedEvents {
8688
// main event
87-
populateEvent(
89+
val main = populateEvent(
8890
entity = eventAndExceptions.main,
89-
main = eventAndExceptions.main,
90-
to = to
91+
main = eventAndExceptions.main
9192
)
9293

9394
// Add exceptions of recurring main event
94-
if (to.rRules.isNotEmpty() || to.rDates.isNotEmpty()) {
95+
val rRules = main.getProperties<RRule>(Property.RRULE)
96+
val rDates = main.getProperties<RDate>(Property.RDATE)
97+
val exceptions = LinkedList<VEvent>()
98+
if (rRules.isNotEmpty() || rDates.isNotEmpty()) {
9599
for (exception in eventAndExceptions.exceptions) {
96-
val exceptionEvent = Event()
97-
98100
// convert exception to Event
99-
populateEvent(
101+
val exceptionEvent = populateEvent(
100102
entity = exception,
101-
main = eventAndExceptions.main,
102-
to = exceptionEvent
103+
main = eventAndExceptions.main
103104
)
104105

105106
// make sure that exception has a RECURRENCE-ID
106107
val recurrenceId = exceptionEvent.recurrenceId ?: continue
107108

108109
// generate EXDATE instead of VEVENT with RECURRENCE-ID for cancelled instances
109110
if (exception.entityValues.getAsInteger(Events.STATUS) == Events.STATUS_CANCELED)
110-
addAsExDate(exception, recurrenceId, to = to)
111+
main.properties += asExDate(exception, recurrenceId)
111112
else
112-
to.exceptions += exceptionEvent
113+
exceptions += exceptionEvent
113114
}
114115
}
116+
117+
return AssociatedEvents(
118+
main = main,
119+
exceptions = exceptions,
120+
prodId = generateProdId(eventAndExceptions.main)
121+
)
115122
}
116123

117-
private fun addAsExDate(entity: Entity, recurrenceId: RecurrenceId, to: Event) {
124+
private fun asExDate(entity: Entity, recurrenceId: RecurrenceId): ExDate {
118125
val originalAllDay = (entity.entityValues.getAsInteger(Events.ORIGINAL_ALL_DAY) ?: 0) != 0
119126
val list = DateList(
120127
if (originalAllDay) Value.DATE else Value.DATE_TIME,
121128
recurrenceId.timeZone
122129
)
123130
list.add(recurrenceId.date)
124-
to.exDates += ExDate(list).apply {
131+
return ExDate(list).apply {
125132
// also set TZ properties of ExDate (not only the list)
126133
if (!originalAllDay) {
127134
if (recurrenceId.isUtc)
@@ -132,18 +139,35 @@ class LegacyAndroidEventProcessor(
132139
}
133140
}
134141

142+
private fun generateProdId(main: Entity): String {
143+
val mutators: String? = main.entityValues.getAsString(Events.MUTATORS)
144+
val packages: List<String> = mutators?.split(MUTATORS_SEPARATOR)?.toList() ?: emptyList()
145+
return prodIdGenerator.generateProdId(packages)
146+
}
147+
135148
/**
136-
* Reads data of an event from the calendar provider, i.e. converts the [entity] values into
137-
* an [Event] data object.
149+
* Reads data of an event from the calendar provider, i.e. converts the [entity] values into a [VEvent].
138150
*
139151
* @param entity event row as returned by the calendar provider
140152
* @param main main event row as returned by the calendar provider
141-
* @param to destination data object
153+
*
154+
* @return generated data object
142155
*/
143-
private fun populateEvent(entity: Entity, main: Entity, to: Event) {
144-
// new processors
156+
private fun populateEvent(entity: Entity, main: Entity): VEvent {
157+
val vEvent = VEvent()
145158
for (processor in fieldProcessors)
146-
processor.process(from = entity, main = main, to = to)
159+
processor.process(from = entity, main = main, to = vEvent)
160+
return vEvent
147161
}
148162

163+
164+
companion object {
165+
166+
/**
167+
* The [Events.MUTATORS] field contains a list of unique package names that have modified the event,
168+
* separated by this separator.
169+
*/
170+
const val MUTATORS_SEPARATOR = ','
171+
172+
}
149173
}

lib/src/main/kotlin/at/bitfire/synctools/mapping/calendar/builder/SequenceBuilder.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ import at.bitfire.synctools.storage.calendar.AndroidEvent2
1313
class SequenceBuilder: AndroidEntityBuilder {
1414

1515
override fun build(from: Event, main: Event, to: Entity) {
16-
to.entityValues.put(AndroidEvent2.COLUMN_SEQUENCE, from.sequence)
16+
/* When we build the SEQUENCE column from a real event, we set the sequence to 0 (not null), so that we
17+
can distinguish it from events which have been created locally and have never been uploaded yet. */
18+
to.entityValues.put(AndroidEvent2.COLUMN_SEQUENCE, from.sequence ?: 0)
1719
}
1820

1921
}

lib/src/main/kotlin/at/bitfire/synctools/mapping/calendar/processor/AccessLevelProcessor.kt

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,18 @@ package at.bitfire.synctools.mapping.calendar.processor
99
import android.content.Entity
1010
import android.provider.CalendarContract.Events
1111
import android.provider.CalendarContract.ExtendedProperties
12-
import at.bitfire.ical4android.Event
1312
import at.bitfire.ical4android.UnknownProperty
13+
import net.fortuna.ical4j.model.component.VEvent
1414
import net.fortuna.ical4j.model.property.Clazz
1515
import org.json.JSONException
1616

1717
class AccessLevelProcessor: AndroidEventFieldProcessor {
1818

19-
override fun process(from: Entity, main: Entity, to: Event) {
19+
override fun process(from: Entity, main: Entity, to: VEvent) {
2020
val values = from.entityValues
2121

2222
// take classification from main row
23-
to.classification = when (values.getAsInteger(Events.ACCESS_LEVEL)) {
23+
val classification = when (values.getAsInteger(Events.ACCESS_LEVEL)) {
2424
Events.ACCESS_PUBLIC ->
2525
Clazz.PUBLIC
2626

@@ -33,6 +33,8 @@ class AccessLevelProcessor: AndroidEventFieldProcessor {
3333
else /* Events.ACCESS_DEFAULT */ ->
3434
retainedClassification(from)
3535
}
36+
if (classification != null)
37+
to.properties += classification
3638
}
3739

3840
private fun retainedClassification(from: Entity): Clazz? {

lib/src/main/kotlin/at/bitfire/synctools/mapping/calendar/processor/AndroidEventFieldProcessor.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ package at.bitfire.synctools.mapping.calendar.processor
99
import android.content.Entity
1010
import at.bitfire.ical4android.Event
1111
import at.bitfire.synctools.exception.InvalidLocalResourceException
12+
import net.fortuna.ical4j.model.component.VEvent
1213

1314
interface AndroidEventFieldProcessor {
1415

@@ -35,6 +36,6 @@ interface AndroidEventFieldProcessor {
3536
*
3637
* @throws InvalidLocalResourceException on missing or invalid required fields (like [android.provider.CalendarContract.Events.DTSTART])
3738
*/
38-
fun process(from: Entity, main: Entity, to: Event)
39+
fun process(from: Entity, main: Entity, to: VEvent)
3940

4041
}

lib/src/main/kotlin/at/bitfire/synctools/mapping/calendar/processor/AttendeesProcessor.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ package at.bitfire.synctools.mapping.calendar.processor
99
import android.content.ContentValues
1010
import android.content.Entity
1111
import android.provider.CalendarContract.Attendees
12-
import at.bitfire.ical4android.Event
1312
import at.bitfire.synctools.mapping.calendar.AttendeeMappings
13+
import net.fortuna.ical4j.model.component.VEvent
1414
import net.fortuna.ical4j.model.parameter.Cn
1515
import net.fortuna.ical4j.model.parameter.Email
1616
import net.fortuna.ical4j.model.parameter.PartStat
@@ -26,12 +26,12 @@ class AttendeesProcessor: AndroidEventFieldProcessor {
2626
private val logger
2727
get() = Logger.getLogger(javaClass.name)
2828

29-
override fun process(from: Entity, main: Entity, to: Event) {
29+
override fun process(from: Entity, main: Entity, to: VEvent) {
3030
for (row in from.subValues.filter { it.uri == Attendees.CONTENT_URI })
3131
populateAttendee(row.values, to)
3232
}
3333

34-
private fun populateAttendee(row: ContentValues, to: Event) {
34+
private fun populateAttendee(row: ContentValues, to: VEvent) {
3535
logger.log(Level.FINE, "Read event attendee from calendar provider", row)
3636

3737
try {
@@ -66,7 +66,7 @@ class AttendeesProcessor: AndroidEventFieldProcessor {
6666
Attendees.ATTENDEE_STATUS_NONE -> { /* no information, don't add PARTSTAT */ }
6767
}
6868

69-
to.attendees.add(attendee)
69+
to.properties += attendee
7070
} catch (e: URISyntaxException) {
7171
logger.log(Level.WARNING, "Couldn't parse attendee information, ignoring", e)
7272
}

lib/src/main/kotlin/at/bitfire/synctools/mapping/calendar/processor/AvailabilityProcessor.kt

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,25 @@ package at.bitfire.synctools.mapping.calendar.processor
88

99
import android.content.Entity
1010
import android.provider.CalendarContract.Events
11-
import at.bitfire.ical4android.Event
11+
import net.fortuna.ical4j.model.component.VEvent
12+
import net.fortuna.ical4j.model.property.Transp
1213

1314
class AvailabilityProcessor: AndroidEventFieldProcessor {
1415

15-
override fun process(from: Entity, main: Entity, to: Event) {
16-
to.opaque = from.entityValues.getAsInteger(Events.AVAILABILITY) != Events.AVAILABILITY_FREE
16+
override fun process(from: Entity, main: Entity, to: VEvent) {
17+
val transp = when (from.entityValues.getAsInteger(Events.AVAILABILITY)) {
18+
Events.AVAILABILITY_BUSY,
19+
Events.AVAILABILITY_TENTATIVE ->
20+
Transp.OPAQUE
21+
22+
Events.AVAILABILITY_FREE ->
23+
Transp.TRANSPARENT
24+
25+
else ->
26+
null // defaults to OPAQUE in iCalendar
27+
}
28+
if (transp != null)
29+
to.properties += transp
1730
}
1831

1932
}

lib/src/main/kotlin/at/bitfire/synctools/mapping/calendar/processor/CategoriesProcessor.kt

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,22 @@ package at.bitfire.synctools.mapping.calendar.processor
88

99
import android.content.Entity
1010
import android.provider.CalendarContract.ExtendedProperties
11-
import at.bitfire.ical4android.Event
1211
import at.bitfire.synctools.storage.calendar.AndroidEvent2
12+
import net.fortuna.ical4j.model.TextList
13+
import net.fortuna.ical4j.model.component.VEvent
14+
import net.fortuna.ical4j.model.property.Categories
1315

1416
class CategoriesProcessor: AndroidEventFieldProcessor {
1517

16-
override fun process(from: Entity, main: Entity, to: Event) {
18+
override fun process(from: Entity, main: Entity, to: VEvent) {
1719
val extended = from.subValues.filter { it.uri == ExtendedProperties.CONTENT_URI }.map { it.values }
1820
val categories = extended.firstOrNull { it.getAsString(ExtendedProperties.NAME) == AndroidEvent2.EXTNAME_CATEGORIES }
1921
val listValue = categories?.getAsString(ExtendedProperties.VALUE)
20-
if (listValue != null)
21-
to.categories += listValue.split(AndroidEvent2.CATEGORIES_SEPARATOR)
22+
if (listValue != null) {
23+
to.properties += Categories(TextList(
24+
listValue.split(AndroidEvent2.CATEGORIES_SEPARATOR).toTypedArray()
25+
))
26+
}
2227
}
2328

2429
}

0 commit comments

Comments
 (0)