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

Commit de5368d

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

File tree

4 files changed

+54
-20
lines changed

4 files changed

+54
-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: 34 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,7 @@ class BatchOperation(
96105

97106
try {
98107
val ops = toCPO(start, end)
99-
logger.fine("Running ${ops.size} operations ($start .. ${end - 1})")
108+
logger.log(Level.FINEST, "Running ${ops.size} operations ($start .. ${end - 1})", ops)
100109
val partResults = providerClient.applyBatch(ops)
101110

102111
val n = end - start
@@ -133,6 +142,7 @@ class BatchOperation(
133142
* 2. If a back reference points to a row outside of start/end,
134143
* replace it by the actual result, which has already been calculated. */
135144

145+
var currentIdx = 1
136146
for (cpoBuilder in queue.subList(start, end)) {
137147
for ((backrefKey, backref) in cpoBuilder.valueBackrefs) {
138148
val originalIdx = backref.originalIndex
@@ -148,17 +158,21 @@ class BatchOperation(
148158
backref.setIndex(originalIdx - start)
149159
}
150160

161+
// Set a possible yield point every MAX_OPERATIONS_PER_YIELD_POINT operations for SQLiteContentProvider
162+
if ((++currentIdx).mod(MAX_OPERATIONS_PER_YIELD_POINT) == 0)
163+
cpoBuilder.withYieldAllowed()
164+
151165
cpo += cpoBuilder.build()
152166
}
153167
return cpo
154168
}
155169

156170

157171
class BackReference(
158-
/** index of the referenced row in the original, nonsplitted transaction */
159-
val originalIndex: Int
172+
/** index of the referenced row in the original, non-splitted transaction */
173+
val originalIndex: Int
160174
) {
161-
/** overriden index, i.e. index within the splitted transaction */
175+
/** overridden index, i.e. index within the splitted transaction */
162176
private var index: Int? = null
163177

164178
/**
@@ -182,8 +196,8 @@ class BatchOperation(
182196
* value back references.
183197
*/
184198
class CpoBuilder private constructor(
185-
val uri: Uri,
186-
val type: Type
199+
val uri: Uri,
200+
val type: Type
187201
) {
188202

189203
enum class Type { INSERT, UPDATE, DELETE }
@@ -197,11 +211,13 @@ class BatchOperation(
197211
}
198212

199213

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

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

206222

207223
fun withSelection(select: String, args: Array<String>): CpoBuilder {
@@ -226,6 +242,10 @@ class BatchOperation(
226242
return this
227243
}
228244

245+
fun withYieldAllowed() {
246+
yieldAllowed = true
247+
}
248+
229249

230250
fun build(): ContentProviderOperation {
231251
val builder = when (type) {
@@ -242,6 +262,9 @@ class BatchOperation(
242262
for ((key, backref) in valueBackrefs)
243263
builder.withValueBackReference(key, backref.getIndex())
244264

265+
if (yieldAllowed)
266+
builder.withYieldAllowed(true)
267+
245268
return builder.build()
246269
}
247270

0 commit comments

Comments
 (0)