Skip to content

Commit f6fcfee

Browse files
committed
Merge branch 'main' into 171-add-_sync_id-property
2 parents 1ecb919 + 12df9bf commit f6fcfee

File tree

10 files changed

+123
-39
lines changed

10 files changed

+123
-39
lines changed

gradle/libs.versions.toml

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[versions]
2-
agp = "8.7.2"
2+
agp = "8.7.3"
33
android-desugar = "2.1.3"
44
androidx-core = "1.15.0"
55
androidx-test-rules = "1.6.1"
@@ -10,7 +10,7 @@ dokka ="1.9.20"
1010
# noinspection GradleDependency
1111
ical4j = "3.2.19" # final version; update to 4.x will require much work
1212
junit = "4.13.2"
13-
kotlin = "2.0.21"
13+
kotlin = "2.1.0"
1414
mockk = "1.13.13"
1515
slf4j = "2.0.16"
1616

gradle/wrapper/gradle-wrapper.properties

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
distributionBase=GRADLE_USER_HOME
22
distributionPath=wrapper/dists
3-
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
3+
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
44
networkTimeout=10000
55
validateDistributionUrl=true
66
zipStoreBase=GRADLE_USER_HOME

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

+1-1
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

+1-1
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

+18-7
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

+34-12
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,17 @@ import java.util.logging.Level
1717
import java.util.logging.Logger
1818

1919
class BatchOperation(
20-
private val providerClient: ContentProviderClient
20+
private val providerClient: ContentProviderClient,
21+
private val maxOperationsPerYieldPoint: Int? = null
2122
) {
22-
23+
24+
companion object {
25+
26+
/** Maximum number of operations per yield point in task providers that are based on SQLiteContentProvider. */
27+
const val TASKS_OPERATIONS_PER_YIELD_POINT = 499
28+
29+
}
30+
2331
private val logger = Logger.getLogger(javaClass.name)
2432

2533
private val queue = LinkedList<CpoBuilder>()
@@ -96,7 +104,6 @@ class BatchOperation(
96104

97105
try {
98106
val ops = toCPO(start, end)
99-
logger.fine("Running ${ops.size} operations ($start .. ${end - 1})")
100107
val partResults = providerClient.applyBatch(ops)
101108

102109
val n = end - start
@@ -133,6 +140,7 @@ class BatchOperation(
133140
* 2. If a back reference points to a row outside of start/end,
134141
* replace it by the actual result, which has already been calculated. */
135142

143+
var currentIdx = 0
136144
for (cpoBuilder in queue.subList(start, end)) {
137145
for ((backrefKey, backref) in cpoBuilder.valueBackrefs) {
138146
val originalIdx = backref.originalIndex
@@ -148,17 +156,22 @@ class BatchOperation(
148156
backref.setIndex(originalIdx - start)
149157
}
150158

159+
// Set a possible yield point every MAX_OPERATIONS_PER_YIELD_POINT operations for SQLiteContentProvider
160+
currentIdx += 1
161+
if (maxOperationsPerYieldPoint != null && currentIdx.mod(maxOperationsPerYieldPoint) == 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

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,7 @@ abstract class DmfsTask(
330330

331331

332332
fun add(): Uri {
333-
val batch = BatchOperation(taskList.provider)
333+
val batch = BatchOperation(taskList.provider, BatchOperation.TASKS_OPERATIONS_PER_YIELD_POINT)
334334

335335
val builder = CpoBuilder.newInsert(taskList.tasksSyncUri())
336336
buildTask(builder, false)
@@ -350,7 +350,7 @@ abstract class DmfsTask(
350350
this.task = task
351351
val existingId = requireNotNull(id)
352352

353-
val batch = BatchOperation(taskList.provider)
353+
val batch = BatchOperation(taskList.provider, BatchOperation.TASKS_OPERATIONS_PER_YIELD_POINT)
354354

355355
// remove associated rows which are added later again
356356
batch.enqueue(CpoBuilder

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ abstract class DmfsTaskList<out T : DmfsTask>(
163163
*/
164164
fun touchRelations(): Int {
165165
logger.fine("Touching relations to set parent_id")
166-
val batchOperation = BatchOperation(provider)
166+
val batchOperation = BatchOperation(provider, BatchOperation.TASKS_OPERATIONS_PER_YIELD_POINT)
167167
provider.query(
168168
tasksSyncUri(true), null,
169169
"${Tasks.LIST_ID}=? AND ${Tasks.PARENT_ID} IS NULL AND ${Relation.MIMETYPE}=? AND ${Relation.RELATED_ID} IS NOT NULL",

lib/src/main/kotlin/at/bitfire/ical4android/validation/EventValidator.kt

+11-7
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ package at.bitfire.ical4android.validation
66

77
import androidx.annotation.VisibleForTesting
88
import at.bitfire.ical4android.Event
9-
import at.bitfire.ical4android.Ical4Android
109
import at.bitfire.ical4android.util.DateUtils
1110
import at.bitfire.ical4android.util.TimeApiExtensions.toIcal4jDate
1211
import at.bitfire.ical4android.util.TimeApiExtensions.toLocalDate
@@ -47,7 +46,7 @@ object EventValidator {
4746
val dtStart = correctStartAndEndTime(event)
4847
sameTypeForDtStartAndRruleUntil(dtStart, event.rRules)
4948
removeRRulesWithUntilBeforeDtStart(dtStart, event.rRules)
50-
removeRRulesOfExceptions(event.exceptions)
49+
removeRecurrenceOfExceptions(event.exceptions)
5150
}
5251

5352

@@ -169,15 +168,20 @@ object EventValidator {
169168

170169

171170
/**
172-
* Removes RRULEs of exceptions of (potentially recurring) events
173-
* Note: This repair step needs to be applied after all exceptions have been found
171+
* Removes all recurrence information of exceptions of (potentially recurring) events. This is:
172+
* `RRULE`, `RDATE` and `EXDATE`.
173+
* Note: This repair step needs to be applied after all exceptions have been found.
174174
*
175175
* @param exceptions exceptions of an event
176176
*/
177177
@VisibleForTesting
178-
internal fun removeRRulesOfExceptions(exceptions: List<Event>) {
179-
for (exception in exceptions)
180-
exception.rRules.clear() // Drop all RRULEs for the exception
178+
internal fun removeRecurrenceOfExceptions(exceptions: List<Event>) {
179+
for (exception in exceptions) {
180+
// Drop all RRULEs, RDATEs, EXDATEs for the exception
181+
exception.rRules.clear()
182+
exception.rDates.clear()
183+
exception.exDates.clear()
184+
}
181185
}
182186

183187

0 commit comments

Comments
 (0)