@@ -11,6 +11,8 @@ import androidx.core.content.ContextCompat
1111import io.branch.indexing.BranchUniversalObject
1212import io.branch.referral.util.ContentMetadata
1313import io.branch.referral.util.LinkProperties
14+ import kotlinx.coroutines.delay
15+ import kotlinx.coroutines.runBlocking
1416import org.openedx.core.data.storage.CorePreferences
1517import org.openedx.core.domain.model.CalendarData
1618import org.openedx.core.domain.model.CourseDateBlock
@@ -181,31 +183,60 @@ class CalendarManager(
181183 courseName : String ,
182184 courseDateBlock : CourseDateBlock
183185 ): Long {
184- val date = courseDateBlock.date.toCalendar()
185- val startMillis = date.timeInMillis - TimeUnit .HOURS .toMillis(1 )
186- val endMillis = date.timeInMillis
186+ var eventId = EVENT_DOES_NOT_EXIST
187+ var attempts = 0
188+
189+ while (eventId == EVENT_DOES_NOT_EXIST && attempts < EVENT_ATTEMPTS ) {
190+ attempts++
191+ try {
192+ val date = courseDateBlock.date.toCalendar()
193+ val startMillis = date.timeInMillis - TimeUnit .HOURS .toMillis(1 )
194+ val endMillis = date.timeInMillis
195+
196+ val values = ContentValues ().apply {
197+ put(CalendarContract .Events .DTSTART , startMillis)
198+ put(CalendarContract .Events .DTEND , endMillis)
199+ put(
200+ CalendarContract .Events .TITLE ,
201+ " ${courseDateBlock.title} : $courseName "
202+ )
203+ put(
204+ CalendarContract .Events .DESCRIPTION ,
205+ getEventDescription(
206+ courseId = courseId,
207+ courseDateBlock = courseDateBlock,
208+ isDeeplinkEnabled = corePreferences.appConfig.courseDatesCalendarSync.isDeepLinkEnabled
209+ )
210+ )
211+ put(CalendarContract .Events .CALENDAR_ID , calendarId)
212+ put(CalendarContract .Events .EVENT_TIMEZONE , TimeZone .getDefault().id)
213+ }
214+ val uri =
215+ context.contentResolver.insert(CalendarContract .Events .CONTENT_URI , values)
216+ val insertedEventId = uri?.lastPathSegment?.toLong() ? : EVENT_DOES_NOT_EXIST
217+
218+ if (insertedEventId != EVENT_DOES_NOT_EXIST && isEventExists(insertedEventId)) {
219+ uri?.let { addReminderToEvent(uri = it) }
220+ eventId = insertedEventId
221+ logger.d { " Event created successfully: $eventId (attempt $attempts )" }
222+ } else {
223+ logger.d { " Event creation failed, retrying... (attempt $attempts /$EVENT_ATTEMPTS )" }
224+ if (attempts < EVENT_ATTEMPTS ) {
225+ runBlocking { delay(ACTION_RETRY_DELAY ) }
226+ }
227+ }
228+ } catch (e: Exception ) {
229+ logger.d { " Event creation error on attempt $attempts : ${e.message} " }
230+ if (attempts < EVENT_ATTEMPTS ) {
231+ runBlocking { delay(ACTION_RETRY_DELAY ) }
232+ }
233+ }
234+ }
187235
188- val values = ContentValues ().apply {
189- put(CalendarContract .Events .DTSTART , startMillis)
190- put(CalendarContract .Events .DTEND , endMillis)
191- put(
192- CalendarContract .Events .TITLE ,
193- " ${courseDateBlock.title} : $courseName "
194- )
195- put(
196- CalendarContract .Events .DESCRIPTION ,
197- getEventDescription(
198- courseId = courseId,
199- courseDateBlock = courseDateBlock,
200- isDeeplinkEnabled = corePreferences.appConfig.courseDatesCalendarSync.isDeepLinkEnabled
201- )
202- )
203- put(CalendarContract .Events .CALENDAR_ID , calendarId)
204- put(CalendarContract .Events .EVENT_TIMEZONE , TimeZone .getDefault().id)
236+ if (eventId == EVENT_DOES_NOT_EXIST ) {
237+ logger.d { " Failed to create event after $EVENT_ATTEMPTS attempts" }
205238 }
206- val uri = context.contentResolver.insert(CalendarContract .Events .CONTENT_URI , values)
207- uri?.let { addReminderToEvent(uri = it) }
208- val eventId = uri?.lastPathSegment?.toLong() ? : EVENT_DOES_NOT_EXIST
239+
209240 return eventId
210241 }
211242
@@ -254,11 +285,9 @@ class CalendarManager(
254285 }
255286
256287 fun deleteCalendar (calendarId : Long ) {
257- deleteAllEventsInCalendar(calendarId)
258-
259288 val calendarAccount = getCalendarAccountById(calendarId)
260289 if (calendarAccount?.type == GOOGLE_ACCOUNT_TYPE ) {
261- logger.d { " Cannot delete Google Calendar, only events were removed " }
290+ logger.d { " Cannot delete Google Calendar" }
262291 return
263292 }
264293
@@ -276,16 +305,60 @@ class CalendarManager(
276305 }
277306 }
278307
279- private fun deleteAllEventsInCalendar (calendarId : Long ) {
280- val selection = " ${CalendarContract .Events .CALENDAR_ID } = ?"
281- val selectionArgs = arrayOf(calendarId.toString())
308+ suspend fun deleteEvents (eventIds : List <Long >) {
309+ val deletedCount = eventIds.count { eventId ->
310+ var deleted = false
311+ var attempts = 0
312+
313+ while (! deleted && attempts < EVENT_ATTEMPTS ) {
314+ attempts++
315+ try {
316+ deleted = deleteEventWithRetry(eventId)
317+ if (! deleted && attempts < EVENT_ATTEMPTS ) {
318+ delay(ACTION_RETRY_DELAY )
319+ }
320+ } catch (e: Exception ) {
321+ logger.d { " Failed to delete event $eventId on attempt $attempts : ${e.message} " }
322+ if (attempts < EVENT_ATTEMPTS ) {
323+ delay(ACTION_RETRY_DELAY )
324+ }
325+ }
326+ }
282327
283- val rowsDeleted = context.contentResolver.delete(
284- CalendarContract .Events .CONTENT_URI ,
285- selection,
286- selectionArgs
287- )
288- logger.d { " Deleted $rowsDeleted events from calendar $calendarId " }
328+ if (! deleted) {
329+ logger.d { " Failed to delete event $eventId after $EVENT_ATTEMPTS attempts" }
330+ }
331+
332+ deleted
333+ }
334+ logger.d { " Successfully deleted $deletedCount out of ${eventIds.size} events" }
335+ }
336+
337+ private fun deleteEventWithRetry (eventId : Long ): Boolean {
338+ val deleteUri = ContentUris .withAppendedId(CalendarContract .Events .CONTENT_URI , eventId)
339+ val rows = context.contentResolver.delete(deleteUri, null , null )
340+
341+ if (rows > 0 ) {
342+ // Verify event is actually deleted
343+ val stillExists = isEventExists(eventId)
344+ if (! stillExists) {
345+ logger.d { " Event $eventId deleted successfully" }
346+ return true
347+ } else {
348+ logger.d { " Event $eventId deletion reported success but event still exists" }
349+ return false
350+ }
351+ } else {
352+ // Check if event doesn't exist (might have been already deleted)
353+ val exists = isEventExists(eventId)
354+ if (! exists) {
355+ logger.d { " Event $eventId doesn't exist (already deleted)" }
356+ return true
357+ } else {
358+ logger.d { " Event $eventId deletion failed" }
359+ return false
360+ }
361+ }
289362 }
290363
291364 private fun getCalendarOwnerAccount (): CalendarAccount {
@@ -408,11 +481,34 @@ class CalendarManager(
408481 }
409482 }
410483
484+ private fun isEventExists (eventId : Long ): Boolean {
485+ if (eventId == EVENT_DOES_NOT_EXIST ) return false
486+
487+ val projection = arrayOf(CalendarContract .Events ._ID )
488+ val selection = " ${CalendarContract .Events ._ID } = ?"
489+ val selectionArgs = arrayOf(eventId.toString())
490+
491+ val cursor = context.contentResolver.query(
492+ CalendarContract .Events .CONTENT_URI ,
493+ projection,
494+ selection,
495+ selectionArgs,
496+ null
497+ )
498+
499+ return cursor?.use {
500+ it.count > 0
501+ } ? : false
502+ }
503+
411504 companion object {
412505 const val CALENDAR_DOES_NOT_EXIST = - 1L
413506 const val EVENT_DOES_NOT_EXIST = - 1L
414507 private const val TAG = " CalendarManager"
415508 private const val LOCAL_USER = " local_user"
416509 private const val GOOGLE_ACCOUNT_TYPE = " com.google"
510+
511+ private const val ACTION_RETRY_DELAY = 500L
512+ private const val EVENT_ATTEMPTS = 3
417513 }
418514}
0 commit comments