Skip to content

Commit d26e315

Browse files
committed
LegacyAndroidCalendar: add getEvent() for event retrieval
1 parent df873d2 commit d26e315

File tree

6 files changed

+75
-114
lines changed

6 files changed

+75
-114
lines changed

lib/src/androidTest/kotlin/at/bitfire/ical4android/AndroidEventTest.kt

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -44,15 +44,17 @@ class AndroidEventTest {
4444

4545
private val testAccount = Account(javaClass.name, ACCOUNT_TYPE_LOCAL)
4646

47-
private lateinit var calendar: AndroidCalendar
4847
lateinit var client: ContentProviderClient
48+
private lateinit var calendar: AndroidCalendar
49+
private lateinit var legacyCalendar: LegacyAndroidCalendar
4950

5051
@Before
5152
fun prepare() {
5253
val context = getInstrumentation().targetContext
5354
client = context.contentResolver.acquireContentProviderClient(AUTHORITY)!!
5455

5556
calendar = TestCalendar.findOrCreate(testAccount, client, withColors = true)
57+
legacyCalendar = LegacyAndroidCalendar(calendar)
5658
}
5759

5860
@After
@@ -90,11 +92,11 @@ class AndroidEventTest {
9092
event.dtStart = DtStart("20150502T120000Z")
9193
event.dtEnd = DtEnd("20150502T130000Z")
9294
event.organizer = Organizer(URI("mailto:organizer@example.com"))
93-
val uri = LegacyAndroidCalendar(calendar).add(event)
95+
val uri = legacyCalendar.add(event)
9496

9597
// update test event in calendar
9698
val testEvent = calendar.getLegacyEvent(ContentUris.parseId(uri))!!
97-
val event2 = testEvent.event!!
99+
val event2 = legacyCalendar.getEvent(testEvent.id)!!
98100
event2.summary = "Updated event"
99101
// add data rows
100102
event2.alarms += VAlarm(Duration.parse("-P1DT2H3M4S"))
@@ -107,7 +109,7 @@ class AndroidEventTest {
107109
// read again and verify result
108110
val updatedEvent = calendar.getLegacyEvent(ContentUris.parseId(uri2))!!
109111
try {
110-
val event3 = updatedEvent.event!!
112+
val event3 = legacyCalendar.getEvent(updatedEvent.id)!!
111113
assertEquals(event2.summary, event3.summary)
112114
assertEquals(1, event3.alarms.size)
113115
assertEquals(1, event3.attendees.size)
@@ -124,20 +126,20 @@ class AndroidEventTest {
124126
dtStart = DtStart(DateTime())
125127
color = Css3Color.silver
126128
}
127-
val uri = LegacyAndroidCalendar(calendar).add(event)
129+
val uri = legacyCalendar.add(event)
128130
val id = ContentUris.parseId(uri)
129131

130132
// verify that it has color
131133
val beforeUpdate = calendar.getLegacyEvent(id)!!
132-
assertNotNull(beforeUpdate.event?.color)
134+
assertNotNull(legacyCalendar.getEvent(beforeUpdate.id)!!.color)
133135

134136
// update: reset color
135137
event.color = null
136138
beforeUpdate.update(event)
137139

138140
// verify that it doesn't have color anymore
139141
val afterUpdate = calendar.getLegacyEvent(id)!!
140-
assertNull(afterUpdate.event!!.color)
142+
assertNull(legacyCalendar.getEvent(afterUpdate.id)!!.color)
141143
}
142144

143145
@Test
@@ -147,11 +149,11 @@ class AndroidEventTest {
147149
event.summary = "Sample event with STATUS"
148150
event.dtStart = DtStart("20150502T120000Z")
149151
event.dtEnd = DtEnd("20150502T130000Z")
150-
val uri = LegacyAndroidCalendar(calendar).add(event)
152+
val uri = legacyCalendar.add(event)
151153

152154
// update test event in calendar
153155
val testEvent = calendar.getLegacyEvent(ContentUris.parseId(uri))!!
154-
val event2 = testEvent.event!!
156+
val event2 = legacyCalendar.getEvent(testEvent.id)!!
155157
event2.summary = "Sample event without STATUS"
156158
event2.status = Status.VEVENT_CONFIRMED
157159
val uri2 = testEvent.update(event2)
@@ -162,7 +164,7 @@ class AndroidEventTest {
162164
// read again and verify result
163165
val updatedEvent = calendar.getLegacyEvent(ContentUris.parseId(uri2))!!
164166
try {
165-
val event3 = updatedEvent.event!!
167+
val event3 = legacyCalendar.getEvent(updatedEvent.id)!!
166168
assertEquals(Status.VEVENT_CONFIRMED, event3.status)
167169
} finally {
168170
updatedEvent.delete()
@@ -177,11 +179,11 @@ class AndroidEventTest {
177179
event.dtStart = DtStart("20150502T120000Z")
178180
event.dtEnd = DtEnd("20150502T130000Z")
179181
event.status = Status.VEVENT_CONFIRMED
180-
val uri = LegacyAndroidCalendar(calendar).add(event)
182+
val uri = legacyCalendar.add(event)
181183

182184
// update test event in calendar
183185
val testEvent = calendar.getLegacyEvent(ContentUris.parseId(uri))!!
184-
val event2 = testEvent.event!!
186+
val event2 = legacyCalendar.getEvent(testEvent.id)!!
185187
event2.summary = "Sample event without STATUS"
186188
event2.status = null
187189
val uri2 = testEvent.update(event2)
@@ -192,7 +194,7 @@ class AndroidEventTest {
192194
// read again and verify result
193195
val updatedEvent = calendar.getLegacyEvent(ContentUris.parseId(uri2))!!
194196
try {
195-
val event3 = updatedEvent.event!!
197+
val event3 = legacyCalendar.getEvent(updatedEvent.id)!!
196198
assertNull(event3.status)
197199
} finally {
198200
updatedEvent.delete()
@@ -209,11 +211,11 @@ class AndroidEventTest {
209211
event.dtEnd = DtEnd("20150502T130000Z")
210212
for (i in 0 until 20)
211213
event.attendees += Attendee(URI("mailto:att$i@example.com"))
212-
val uri = LegacyAndroidCalendar(calendar).add(event)
214+
val uri = legacyCalendar.add(event)
213215

214216
val testEvent = calendar.getLegacyEvent(ContentUris.parseId(uri))!!
215217
try {
216-
assertEquals(20, testEvent.event!!.attendees.size)
218+
assertEquals(20, legacyCalendar.getEvent(testEvent.id)!!.attendees.size)
217219
} finally {
218220
testEvent.delete()
219221
}

lib/src/androidTest/kotlin/at/bitfire/synctools/mapping/calendar/LegacyAndroidEventProcessorTest.kt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import androidx.core.content.contentValuesOf
2222
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
2323
import at.bitfire.ical4android.AndroidEvent
2424
import at.bitfire.ical4android.Event
25+
import at.bitfire.ical4android.LegacyAndroidCalendar
2526
import at.bitfire.ical4android.UnknownProperty
2627
import at.bitfire.ical4android.impl.TestCalendar
2728
import at.bitfire.ical4android.util.AndroidTimeUtils
@@ -85,7 +86,6 @@ class LegacyAndroidEventProcessorTest {
8586
client = context.contentResolver.acquireContentProviderClient(AUTHORITY)!!
8687

8788
calendar = TestCalendar.findOrCreate(testAccount, client)
88-
assertNotNull(calendar)
8989
calendarUri = ContentUris.withAppendedId(Calendars.CONTENT_URI, calendar.id)
9090
}
9191

@@ -145,14 +145,15 @@ class LegacyAndroidEventProcessorTest {
145145
extendedProperties: Map<String, String> = emptyMap(),
146146
valuesBuilder: ContentValues.() -> Unit = {}
147147
): Event {
148-
return populateAndroidEvent(
148+
val androidEvent = populateAndroidEvent(
149149
automaticDates,
150150
destinationCalendar,
151151
asSyncAdapter,
152152
insertCallback,
153153
extendedProperties,
154154
valuesBuilder
155-
).event!!
155+
)
156+
return LegacyAndroidCalendar(destinationCalendar).getEvent(androidEvent.id)!!
156157
}
157158

158159
@Test

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

Lines changed: 3 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,19 @@ package at.bitfire.ical4android
99
import android.content.ContentResolver
1010
import android.content.ContentUris
1111
import android.content.ContentValues
12-
import android.content.EntityIterator
1312
import android.net.Uri
1413
import android.os.RemoteException
1514
import android.provider.CalendarContract.Attendees
1615
import android.provider.CalendarContract.Events
17-
import android.provider.CalendarContract.EventsEntity
1816
import android.provider.CalendarContract.ExtendedProperties
1917
import android.provider.CalendarContract.Reminders
2018
import at.bitfire.ical4android.AndroidEvent.Companion.CATEGORIES_SEPARATOR
2119
import at.bitfire.ical4android.util.MiscUtils.asSyncAdapter
2220
import at.bitfire.synctools.mapping.calendar.LegacyAndroidEventBuilder
23-
import at.bitfire.synctools.mapping.calendar.LegacyAndroidEventProcessor
2421
import at.bitfire.synctools.storage.BatchOperation.CpoBuilder
2522
import at.bitfire.synctools.storage.LocalStorageException
2623
import at.bitfire.synctools.storage.calendar.AndroidCalendar
2724
import at.bitfire.synctools.storage.calendar.CalendarBatchOperation
28-
import java.io.FileNotFoundException
2925

3026
/**
3127
* Stores and retrieves VEVENT iCalendar objects (represented as [Event]s) to/from the
@@ -52,64 +48,15 @@ class AndroidEvent(
5248
var scheduleTag: String? = values.getAsString(COLUMN_SCHEDULE_TAG)
5349
var flags: Int = values.getAsInteger(COLUMN_FLAGS) ?: 0
5450

55-
private var _event: Event? = null
56-
57-
/**
58-
* Returns the full event data, either from [event] or, if [event] is null, by reading event
59-
* number [id] from the Android calendar storage.
60-
*
61-
* @throws IllegalArgumentException if event has not been saved yet
62-
* @throws FileNotFoundException if there's no event with [id] in the calendar storage
63-
* @throws RemoteException on calendar provider errors
64-
*/
65-
var event: Event?
66-
private set(value) {
67-
_event = value
68-
}
69-
get() {
70-
if (_event != null)
71-
return _event
72-
val id = requireNotNull(id)
73-
74-
var iterEvents: EntityIterator? = null
75-
try {
76-
iterEvents = EventsEntity.newEntityIterator(
77-
calendar.client.query(
78-
ContentUris.withAppendedId(EventsEntity.CONTENT_URI, id).asSyncAdapter(calendar.account),
79-
null, null, null, null),
80-
calendar.client
81-
)
82-
83-
if (iterEvents.hasNext()) {
84-
val entity = iterEvents.next()
85-
return Event().also { newEvent ->
86-
val processor = LegacyAndroidEventProcessor(calendar, id, entity)
87-
processor.populate(to = newEvent)
88-
89-
_event = newEvent
90-
}
91-
}
92-
} catch (e: Exception) {
93-
/* Populating event has been interrupted by an exception, so we reset the event to
94-
avoid an inconsistent state. This also ensures that the exception will be thrown
95-
again on the next get() call. */
96-
_event = null
97-
throw e
98-
} finally {
99-
iterEvents?.close()
100-
}
101-
102-
throw FileNotFoundException("Couldn't find event $id")
103-
}
104-
10551
/**
10652
* Updates an already existing event in the calendar storage with the values
10753
* from the instance.
54+
*
10855
* @throws LocalStorageException when the calendar provider doesn't return a result row
10956
* @throws RemoteException on calendar provider errors
11057
*/
11158
fun update(event: Event): Uri {
112-
this.event = event
59+
//this.event = event
11360
val existingId = requireNotNull(id)
11461

11562
// There are cases where the event cannot be updated, but must be completely re-created.
@@ -194,7 +141,7 @@ class AndroidEvent(
194141
return ContentUris.withAppendedId(Events.CONTENT_URI, id).asSyncAdapter(calendar.account)
195142
}
196143

197-
override fun toString(): String = "AndroidEvent(calendar=$calendar, id=$id, event=$_event)"
144+
override fun toString(): String = "AndroidEvent(calendar=$calendar, id=$id)"
198145

199146

200147
companion object {

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ package at.bitfire.ical4android
99
import android.net.Uri
1010
import android.os.RemoteException
1111
import at.bitfire.synctools.mapping.calendar.LegacyAndroidEventBuilder
12+
import at.bitfire.synctools.mapping.calendar.LegacyAndroidEventProcessor
1213
import at.bitfire.synctools.storage.LocalStorageException
1314
import at.bitfire.synctools.storage.calendar.AndroidCalendar
1415
import at.bitfire.synctools.storage.calendar.CalendarBatchOperation
@@ -48,4 +49,20 @@ class LegacyAndroidCalendar(
4849
return resultUri
4950
}
5051

52+
/**
53+
* Gets an [Event] data object from an Android event with a specific ID.
54+
*
55+
* @param id event ID
56+
*
57+
* @return event data object
58+
*/
59+
fun getEvent(id: Long): Event? {
60+
val entity = calendar.getEventEntity(id) ?: return null
61+
return Event().also { event ->
62+
val processor = LegacyAndroidEventProcessor(calendar, id, entity)
63+
processor.populate(to = event)
64+
}
65+
66+
}
67+
5168
}

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

Lines changed: 32 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -13,24 +13,23 @@ import android.provider.CalendarContract.Events
1313
import android.provider.CalendarContract.ExtendedProperties
1414
import android.provider.CalendarContract.Reminders
1515
import android.util.Patterns
16-
import at.bitfire.ical4android.AndroidEvent
1716
import at.bitfire.ical4android.AndroidEvent.Companion.CATEGORIES_SEPARATOR
1817
import at.bitfire.ical4android.AndroidEvent.Companion.COLUMN_SEQUENCE
1918
import at.bitfire.ical4android.AndroidEvent.Companion.EXTNAME_CATEGORIES
2019
import at.bitfire.ical4android.AndroidEvent.Companion.EXTNAME_ICAL_UID
2120
import at.bitfire.ical4android.AndroidEvent.Companion.EXTNAME_URL
2221
import at.bitfire.ical4android.AndroidEvent.Companion.MUTATORS_SEPARATOR
2322
import at.bitfire.ical4android.Event
23+
import at.bitfire.ical4android.LegacyAndroidCalendar
2424
import at.bitfire.ical4android.UnknownProperty
2525
import at.bitfire.ical4android.util.AndroidTimeUtils
2626
import at.bitfire.ical4android.util.DateUtils
27-
import at.bitfire.ical4android.util.MiscUtils.asSyncAdapter
2827
import at.bitfire.ical4android.util.TimeApiExtensions
2928
import at.bitfire.ical4android.util.TimeApiExtensions.toZonedDateTime
3029
import at.bitfire.synctools.exception.InvalidLocalResourceException
3130
import at.bitfire.synctools.icalendar.Css3Color
3231
import at.bitfire.synctools.storage.calendar.AndroidCalendar
33-
import at.bitfire.synctools.storage.toContentValues
32+
import at.bitfire.synctools.storage.calendar.AndroidEvent2
3433
import net.fortuna.ical4j.model.Date
3534
import net.fortuna.ical4j.model.DateList
3635
import net.fortuna.ical4j.model.DateTime
@@ -411,42 +410,38 @@ class LegacyAndroidEventProcessor(
411410
}
412411

413412
private fun populateExceptions(to: Event) {
414-
calendar.client.query(Events.CONTENT_URI.asSyncAdapter(calendar.account),
415-
null,
416-
Events.ORIGINAL_ID + "=?", arrayOf(id.toString()), null)?.use { c ->
417-
while (c.moveToNext()) {
418-
val values = c.toContentValues()
419-
try {
420-
val exception = AndroidEvent(calendar, values)
421-
val exceptionEvent = exception.event!!
422-
val recurrenceId = exceptionEvent.recurrenceId!!
423-
424-
// generate EXDATE instead of RECURRENCE-ID exceptions for cancelled instances
425-
if (exceptionEvent.status == Status.VEVENT_CANCELLED) {
426-
val list = DateList(
427-
if (DateUtils.isDate(recurrenceId)) Value.DATE else Value.DATE_TIME,
428-
recurrenceId.timeZone
429-
)
430-
list.add(recurrenceId.date)
431-
to.exDates += ExDate(list).apply {
432-
if (DateUtils.isDateTime(recurrenceId)) {
433-
if (recurrenceId.isUtc)
434-
setUtc(true)
435-
else
436-
timeZone = recurrenceId.timeZone
437-
}
438-
}
439-
440-
} else /* exceptionEvent.status != Status.VEVENT_CANCELLED */ {
441-
// make sure that all components have the same ORGANIZER [RFC 6638 3.1]
442-
exceptionEvent.organizer = to.organizer
443-
444-
// add exception to list of exceptions
445-
to.exceptions += exceptionEvent
413+
val legacyCalendar = LegacyAndroidCalendar(calendar)
414+
calendar.iterateEvents(Events.ORIGINAL_ID + "=?", arrayOf(id.toString())) { entity ->
415+
val exception = AndroidEvent2(calendar, entity)
416+
417+
val exceptionEvent = legacyCalendar.getEvent(exception.id)
418+
if (exceptionEvent == null)
419+
return@iterateEvents
420+
421+
val recurrenceId = exceptionEvent.recurrenceId!!
422+
423+
// generate EXDATE instead of RECURRENCE-ID exceptions for cancelled instances
424+
if (exceptionEvent.status == Status.VEVENT_CANCELLED) {
425+
val list = DateList(
426+
if (DateUtils.isDate(recurrenceId)) Value.DATE else Value.DATE_TIME,
427+
recurrenceId.timeZone
428+
)
429+
list.add(recurrenceId.date)
430+
to.exDates += ExDate(list).apply {
431+
if (DateUtils.isDateTime(recurrenceId)) {
432+
if (recurrenceId.isUtc)
433+
setUtc(true)
434+
else
435+
timeZone = recurrenceId.timeZone
446436
}
447-
} catch (e: Exception) {
448-
logger.log(Level.WARNING, "Couldn't find exception details", e)
449437
}
438+
439+
} else /* exceptionEvent.status != Status.VEVENT_CANCELLED */ {
440+
// make sure that all components have the same ORGANIZER [RFC 6638 3.1]
441+
exceptionEvent.organizer = to.organizer
442+
443+
// add exception to list of exceptions
444+
to.exceptions += exceptionEvent
450445
}
451446
}
452447
}

0 commit comments

Comments
 (0)