77package at.bitfire.synctools.mapping.calendar
88
99import android.content.Entity
10+ import android.provider.CalendarContract
1011import android.provider.CalendarContract.Events
1112import android.provider.CalendarContract.ExtendedProperties
1213import at.bitfire.synctools.icalendar.AssociatedEvents
@@ -90,21 +91,27 @@ class AndroidEventProcessor(
9091 /* *
9192 * Result of the [mapToVEvents] operation.
9293 *
93- * @param uid UID of the generated iCalendar
94- * @param generatedUid whether [uid] was generated by [mapToVEvents] (*false*: UID was already present before mapping)
9594 * @param associatedEvents mapped events
95+ * @param uid UID of the mapped events
96+ * @param generatedUid whether [uid] was generated by [mapToVEvents] (*false*: `UID` was already present before mapping)
97+ * @param updatedSequence the new increased `SEQUENCE` of the main event (*null* if sequence was not increased by [mapToVEvents])
9698 */
9799 class MappingResult (
100+ val associatedEvents : AssociatedEvents ,
98101 val uid : String ,
99102 val generatedUid : Boolean ,
100- val associatedEvents : AssociatedEvents
103+ val updatedSequence : Int?
101104 )
102105
103106 /* *
104107 * Maps an Android event with its exceptions to VEVENTs.
105108 *
106- * VEVENTs must have a valid UID, so this method (or better so say, the [UidProcessor] that it calls)
107- * generates an UID, if necessary.
109+ * VEVENTs must have a valid `UID`, so this method (or better so say, the [UidProcessor] that it calls)
110+ * generates an UID, if necessary. If an `UID` was generated, it is noted in the result.
111+ *
112+ * This method also increases the `SEQUENCE`, if necessary, so that the mapped `SEQUENCE`
113+ * can be different than the original `SEQUENCE` in [eventAndExceptions]. If the `SEQUENCE` was
114+ * increased, it is noted in the result.
108115 */
109116 fun mapToVEvents (eventAndExceptions : EventAndExceptions ): MappingResult {
110117 // make sure that main event has a UID
@@ -114,6 +121,8 @@ class AndroidEventProcessor(
114121 UUID .randomUUID().toString()
115122 }
116123
124+ val updatedSequence = increaseSequence(eventAndExceptions.main)
125+
117126 // map main event
118127 val main = populateEvent(
119128 entity = eventAndExceptions.main,
@@ -149,9 +158,10 @@ class AndroidEventProcessor(
149158 prodId = generateProdId(eventAndExceptions.main)
150159 )
151160 return MappingResult (
161+ associatedEvents = mappedEvents,
152162 uid = uid,
153163 generatedUid = generatedUid,
154- associatedEvents = mappedEvents
164+ updatedSequence = updatedSequence
155165 )
156166 }
157167
@@ -179,6 +189,47 @@ class AndroidEventProcessor(
179189 return prodIdGenerator.generateProdId(packages)
180190 }
181191
192+ /* *
193+ * Increases the event's SEQUENCE, if necessary.
194+ *
195+ * @param main event to be checked (**will be modified** when SEQUENCE needs to be increased)
196+ *
197+ * @return updated sequence (or *null* if sequence was not increased/modified)
198+ */
199+ private fun increaseSequence (main : Entity ): Int? {
200+ val mainValues = main.entityValues
201+
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 }
207+ }
208+
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 -> {
219+ /* Upload of a group-scheduled event and we are the organizer, so we increase the SEQUENCE.
220+ We also have to store it into the Entity so that it the new value will be mapped. */
221+ (currentSeq + 1 ).also { newSeq ->
222+ mainValues.put(EventsContract .COLUMN_SEQUENCE , newSeq)
223+ }
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. */
228+ null
229+ }
230+ }
231+ }
232+
182233 /* *
183234 * Reads data of an event from the calendar provider, i.e. converts the [entity] values into a [VEvent].
184235 *
@@ -198,23 +249,23 @@ class AndroidEventProcessor(
198249 * Makes sure that the event has a UID ([Events.UID_2445] in the main event row).
199250 *
200251 * If the event doesn't have a UID, a new one is generated using [uidGenerator] and
201- * put into [entity ].
252+ * put into [main ].
202253 *
203- * @param entity event to be checked (**will be modified** if it doesn't already have a UID)
254+ * @param main event to be checked (**will be modified** if it doesn't already have a UID)
204255 */
205256 private fun provideUid (
206- entity : Entity ,
257+ main : Entity ,
207258 generateUid : () -> String
208259 ): String {
209- val mainValues = entity .entityValues
260+ val mainValues = main .entityValues
210261 val existingUid = mainValues.getAsString(Events .UID_2445 )
211262 if (existingUid != null ) {
212263 // UID already present, nothing to do
213264 return existingUid
214265 }
215266
216267 // have a look at extended properties (Google Calendar)
217- val googleCalendarUid = entity .subValues.firstOrNull {
268+ val googleCalendarUid = main .subValues.firstOrNull {
218269 it.uri == ExtendedProperties .CONTENT_URI &&
219270 it.values.getAsString(ExtendedProperties .NAME ) == EventsContract .EXTNAME_GOOGLE_CALENDAR_UID
220271 }?.values?.getAsString(ExtendedProperties .VALUE )
0 commit comments