Skip to content
This repository was archived by the owner on Jun 9, 2025. It is now read-only.

Commit d63b6e5

Browse files
committed
BatchOperation: set "yield allowed" after every 500 operations
1 parent 1accbfd commit d63b6e5

File tree

4 files changed

+53
-20
lines changed

4 files changed

+53
-20
lines changed

lib/src/androidTest/kotlin/at/bitfire/ical4android/AbstractTasksTest.kt renamed to lib/src/androidTest/kotlin/at/bitfire/ical4android/DmfsStyleProvidersTaskTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import java.util.logging.Logger
1616

1717
@RunWith(Parameterized::class)
1818

19-
abstract class AbstractTasksTest(
19+
abstract class DmfsStyleProvidersTaskTest(
2020
val providerName: TaskProvider.ProviderName
2121
) {
2222

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import org.junit.Assert.assertTrue
2121
import org.junit.Test
2222

2323
class DmfsTaskListTest(providerName: TaskProvider.ProviderName):
24-
AbstractTasksTest(providerName) {
24+
DmfsStyleProvidersTaskTest(providerName) {
2525

2626
private val testAccount = Account("AndroidTaskListTest", TaskContract.LOCAL_ACCOUNT_TYPE)
2727

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

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@ import android.content.ContentUris
99
import android.content.ContentValues
1010
import android.database.DatabaseUtils
1111
import android.net.Uri
12-
import androidx.test.filters.MediumTest
1312
import at.bitfire.ical4android.impl.TestTask
1413
import at.bitfire.ical4android.impl.TestTaskList
1514
import at.bitfire.ical4android.util.DateUtils
1615
import net.fortuna.ical4j.model.Date
1716
import net.fortuna.ical4j.model.DateList
1817
import net.fortuna.ical4j.model.DateTime
18+
import net.fortuna.ical4j.model.component.VAlarm
1919
import net.fortuna.ical4j.model.parameter.Email
2020
import net.fortuna.ical4j.model.parameter.RelType
2121
import net.fortuna.ical4j.model.parameter.TzId
@@ -52,8 +52,8 @@ import org.junit.Test
5252
import java.time.ZoneId
5353

5454
class DmfsTaskTest(
55-
providerName: TaskProvider.ProviderName
56-
): AbstractTasksTest(providerName) {
55+
providerName: TaskProvider.ProviderName
56+
): DmfsStyleProvidersTaskTest(providerName) {
5757

5858
private val tzVienna = DateUtils.ical4jTimeZone("Europe/Vienna")!!
5959
private val tzChicago = DateUtils.ical4jTimeZone("America/Chicago")!!
@@ -643,7 +643,6 @@ class DmfsTaskTest(
643643
}
644644

645645

646-
@MediumTest
647646
@Test
648647
fun testAddTask() {
649648
// build and write event to calendar provider
@@ -693,7 +692,6 @@ class DmfsTaskTest(
693692
}
694693
}
695694

696-
@MediumTest
697695
@Test(expected = CalendarStorageException::class)
698696
fun testAddTaskWithInvalidDue() {
699697
val task = Task()
@@ -705,7 +703,21 @@ class DmfsTaskTest(
705703
TestTask(taskList!!, task).add()
706704
}
707705

708-
@MediumTest
706+
@Test
707+
fun testAddTaskWithManyAlarms() {
708+
val task = Task()
709+
task.uid = "TaskWithManyAlarms"
710+
task.summary = "Task with many alarms"
711+
task.dtStart = DtStart(Date("20150102"))
712+
713+
for (i in 1..1050)
714+
task.alarms += VAlarm(java.time.Duration.ofMinutes(i.toLong()))
715+
716+
val uri = TestTask(taskList!!, task).add()
717+
val task2 = taskList!!.findById(ContentUris.parseId(uri))
718+
assertEquals(1050, task2.task?.alarms?.size)
719+
}
720+
709721
@Test
710722
fun testUpdateTask() {
711723
// add test event without reminder
@@ -739,7 +751,6 @@ class DmfsTaskTest(
739751
}
740752
}
741753

742-
@MediumTest
743754
@Test
744755
fun testBuildAllDayTask() {
745756
// add all-day event to calendar provider

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

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,16 @@ import java.util.logging.Logger
1919
class BatchOperation(
2020
private val providerClient: ContentProviderClient
2121
) {
22-
22+
23+
companion object {
24+
25+
/** Maximum number of operations per yield point. SQLiteContentProvider, which most content providers
26+
* are based on, indicates a value of 500. However to correct value to avoid the [OperationApplicationException]
27+
* seems to be 499. */
28+
const val MAX_OPERATIONS_PER_YIELD_POINT = 499
29+
30+
}
31+
2332
private val logger = Logger.getLogger(javaClass.name)
2433

2534
private val queue = LinkedList<CpoBuilder>()
@@ -96,7 +105,6 @@ class BatchOperation(
96105

97106
try {
98107
val ops = toCPO(start, end)
99-
logger.fine("Running ${ops.size} operations ($start .. ${end - 1})")
100108
val partResults = providerClient.applyBatch(ops)
101109

102110
val n = end - start
@@ -133,6 +141,7 @@ class BatchOperation(
133141
* 2. If a back reference points to a row outside of start/end,
134142
* replace it by the actual result, which has already been calculated. */
135143

144+
var currentIdx = 1
136145
for (cpoBuilder in queue.subList(start, end)) {
137146
for ((backrefKey, backref) in cpoBuilder.valueBackrefs) {
138147
val originalIdx = backref.originalIndex
@@ -148,17 +157,21 @@ class BatchOperation(
148157
backref.setIndex(originalIdx - start)
149158
}
150159

160+
// Set a possible yield point every MAX_OPERATIONS_PER_YIELD_POINT operations for SQLiteContentProvider
161+
if ((++currentIdx).mod(MAX_OPERATIONS_PER_YIELD_POINT) == 0)
162+
cpoBuilder.withYieldAllowed()
163+
151164
cpo += cpoBuilder.build()
152165
}
153166
return cpo
154167
}
155168

156169

157170
class BackReference(
158-
/** index of the referenced row in the original, nonsplitted transaction */
159-
val originalIndex: Int
171+
/** index of the referenced row in the original, non-splitted transaction */
172+
val originalIndex: Int
160173
) {
161-
/** overriden index, i.e. index within the splitted transaction */
174+
/** overridden index, i.e. index within the splitted transaction */
162175
private var index: Int? = null
163176

164177
/**
@@ -182,8 +195,8 @@ class BatchOperation(
182195
* value back references.
183196
*/
184197
class CpoBuilder private constructor(
185-
val uri: Uri,
186-
val type: Type
198+
val uri: Uri,
199+
val type: Type
187200
) {
188201

189202
enum class Type { INSERT, UPDATE, DELETE }
@@ -197,11 +210,13 @@ class BatchOperation(
197210
}
198211

199212

200-
var selection: String? = null
201-
var selectionArguments: Array<String>? = null
213+
private var selection: String? = null
214+
private var selectionArguments: Array<String>? = null
215+
216+
internal val values = mutableMapOf<String, Any?>()
217+
internal val valueBackrefs = mutableMapOf<String, BackReference>()
202218

203-
val values = mutableMapOf<String, Any?>()
204-
val valueBackrefs = mutableMapOf<String, BackReference>()
219+
private var yieldAllowed = false
205220

206221

207222
fun withSelection(select: String, args: Array<String>): CpoBuilder {
@@ -226,6 +241,10 @@ class BatchOperation(
226241
return this
227242
}
228243

244+
fun withYieldAllowed() {
245+
yieldAllowed = true
246+
}
247+
229248

230249
fun build(): ContentProviderOperation {
231250
val builder = when (type) {
@@ -242,6 +261,9 @@ class BatchOperation(
242261
for ((key, backref) in valueBackrefs)
243262
builder.withValueBackReference(key, backref.getIndex())
244263

264+
if (yieldAllowed)
265+
builder.withYieldAllowed(true)
266+
245267
return builder.build()
246268
}
247269

0 commit comments

Comments
 (0)