Skip to content

Commit 496a783

Browse files
committed
Update SEQUENCE and UID handling; tests
1 parent b3522b1 commit 496a783

File tree

7 files changed

+258
-116
lines changed

7 files changed

+258
-116
lines changed

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

Lines changed: 31 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import android.content.Entity
1010
import android.provider.CalendarContract
1111
import android.provider.CalendarContract.Events
1212
import android.provider.CalendarContract.ExtendedProperties
13+
import androidx.annotation.VisibleForTesting
1314
import at.bitfire.synctools.icalendar.AssociatedEvents
1415
import at.bitfire.synctools.mapping.calendar.processor.AccessLevelProcessor
1516
import at.bitfire.synctools.mapping.calendar.processor.AndroidEventFieldProcessor
@@ -23,7 +24,6 @@ import at.bitfire.synctools.mapping.calendar.processor.EndTimeProcessor
2324
import at.bitfire.synctools.mapping.calendar.processor.LocationProcessor
2425
import at.bitfire.synctools.mapping.calendar.processor.OrganizerProcessor
2526
import at.bitfire.synctools.mapping.calendar.processor.OriginalInstanceTimeProcessor
26-
import at.bitfire.synctools.mapping.calendar.processor.ProdIdGenerator
2727
import at.bitfire.synctools.mapping.calendar.processor.RecurrenceFieldsProcessor
2828
import at.bitfire.synctools.mapping.calendar.processor.RemindersProcessor
2929
import at.bitfire.synctools.mapping.calendar.processor.SequenceProcessor
@@ -196,36 +196,43 @@ class AndroidEventProcessor(
196196
*
197197
* @return updated sequence (or *null* if sequence was not increased/modified)
198198
*/
199-
private fun increaseSequence(main: Entity): Int? {
199+
@VisibleForTesting
200+
internal fun increaseSequence(main: Entity): Int? {
200201
val mainValues = main.entityValues
202+
val currentSeq = mainValues.getAsInteger(EventsContract.COLUMN_SEQUENCE)
201203

202-
val weAreOrganizer: Boolean by lazy {
203-
mainValues.getAsInteger(Events.IS_ORGANIZER) == 1
204-
}
205-
val groupScheduled: Boolean by lazy {
206-
main.subValues.any { it.uri == CalendarContract.Attendees.CONTENT_URI }
204+
if (currentSeq == null) {
205+
/* First upload, request to set to 0 in calendar provider after upload.
206+
We can let it empty in the Entity because then no SEQUENCE property will be generated,
207+
which is equal to SEQUENCE:0. */
208+
return 0
207209
}
208210

209-
val currentSeq = mainValues.getAsInteger(EventsContract.COLUMN_SEQUENCE)
210-
return when {
211-
currentSeq == null -> {
212-
/* First upload, request to set to 0 in calendar provider after upload.
213-
We can let it empty in the Entity because then no SEQUENCE property will be generated,
214-
which is equal to SEQUENCE:0. */
215-
0
216-
}
217-
// currentSequence != null for the following branches
218-
groupScheduled && weAreOrganizer -> {
211+
val groupScheduled = main.subValues.any { it.uri == CalendarContract.Attendees.CONTENT_URI }
212+
if (groupScheduled) {
213+
val weAreOrganizer = mainValues.getAsInteger(Events.IS_ORGANIZER) == 1
214+
215+
return if (weAreOrganizer) {
219216
/* Upload of a group-scheduled event and we are the organizer, so we increase the SEQUENCE.
220217
We also have to store it into the Entity so that it the new value will be mapped. */
221218
(currentSeq + 1).also { newSeq ->
222219
mainValues.put(EventsContract.COLUMN_SEQUENCE, newSeq)
223220
}
224-
}
225-
else -> {
226-
/* Standard upload (either not group-scheduled or we are not the organizer). We don't
227-
increase the SEQUENCE and so we don't have to modify the Entity, too. */
221+
} else
222+
/* Upload of a group-scheduled event and we are not the organizer, so we don't increase the SEQUENCE. */
228223
null
224+
225+
} else /* not group-scheduled */ {
226+
return if (currentSeq == 0) {
227+
/* The event was uploaded once and has SEQUENCE of 0 (which is mapped to an empty SEQUENCE property).
228+
We don't need to increase the SEQUENCE because the event is not group-scheduled. */
229+
null
230+
} else {
231+
/* Upload of a non-group-scheduled event where a SEQUENCE > 0 is present. Increase by one after upload.
232+
We also have to store it into the Entity so that it the new value will be mapped. */
233+
(currentSeq + 1).also { newSeq ->
234+
mainValues.put(EventsContract.COLUMN_SEQUENCE, newSeq)
235+
}
229236
}
230237
}
231238
}
@@ -239,7 +246,9 @@ class AndroidEventProcessor(
239246
* @return generated data object
240247
*/
241248
private fun populateEvent(entity: Entity, main: Entity): VEvent {
242-
val vEvent = VEvent()
249+
// initialization adds DTSTAMP
250+
val vEvent = VEvent(/* initialise = */ true)
251+
243252
for (processor in fieldProcessors)
244253
processor.process(from = entity, main = main, to = vEvent)
245254
return vEvent
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* This file is part of bitfireAT/synctools which is released under GPLv3.
3+
* Copyright © All Contributors. See the LICENSE and AUTHOR files in the root directory for details.
4+
* SPDX-License-Identifier: GPL-3.0-or-later
5+
*/
6+
7+
package at.bitfire.synctools.mapping.calendar
8+
9+
fun interface ProdIdGenerator {
10+
11+
/**
12+
* Generates a `PRODID` string using additional package names.
13+
*
14+
* @param packages package names that have modified/generated the iCalendar (like `com.example.app.calendar`; may be empty)
15+
*
16+
* @return the full `PRODID` string, with the package names and probably additional information added
17+
* (like `MyApp/1.0 (com.example.app.calendar)`)
18+
*/
19+
fun generateProdId(packages: List<String>): String
20+
21+
}
22+
23+
class DefaultProdIdGenerator(
24+
private val baseId: String
25+
): ProdIdGenerator {
26+
27+
override fun generateProdId(packages: List<String>): String {
28+
val builder = StringBuilder(baseId)
29+
if (packages.isNotEmpty())
30+
builder .append(" (")
31+
.append(packages.joinToString(", "))
32+
.append(")")
33+
return builder.toString()
34+
}
35+
36+
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ import net.fortuna.ical4j.model.component.VEvent
1313
class UidBuilder: AndroidEntityBuilder {
1414

1515
override fun build(from: VEvent, main: VEvent, to: Entity) {
16-
// always take UID from main event because exceptions must have the same UID anyway
16+
// Always take UID from main event because exceptions must have the same UID anyway.
17+
// Note: RFC 5545 requires UID for VEVENTs, however the obsoleted RFC 2445 does not.
1718
to.entityValues.put(Events.UID_2445, main.uid?.value)
1819
}
1920

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

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

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

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

99
import android.content.Entity
1010
import android.provider.CalendarContract.Events
11-
import at.bitfire.synctools.exception.InvalidLocalResourceException
1211
import net.fortuna.ical4j.model.component.VEvent
1312
import net.fortuna.ical4j.model.property.Uid
1413

1514
class UidProcessor(): AndroidEventFieldProcessor {
1615

1716
override fun process(from: Entity, main: Entity, to: VEvent) {
17+
// Should always be available because AndroidEventProcessor ensures there's a UID to be RFC 5545-compliant.
18+
// However technically it can be null (and no UID is OK according to RFC 2445).
1819
val uid = main.entityValues.getAsString(Events.UID_2445)
19-
?: throw InvalidLocalResourceException("Main event doesn't have a UID")
20-
21-
to.properties += Uid(uid)
20+
if (uid != null)
21+
to.properties += Uid(uid)
2222
}
2323

2424
}

0 commit comments

Comments
 (0)