Skip to content

Commit

Permalink
Don't use ContactsStorageException to wrap RemoteException / as all-c…
Browse files Browse the repository at this point in the history
…atch (#38)
  • Loading branch information
rfc2822 authored May 7, 2024
1 parent f09d9aa commit 03a37a8
Showing 1 changed file with 43 additions and 24 deletions.
67 changes: 43 additions & 24 deletions lib/src/main/java/at/bitfire/vcard4android/BatchOperation.kt
Original file line number Diff line number Diff line change
Expand Up @@ -44,30 +44,35 @@ class BatchOperation(
* Commits all operations from [queue] and then empties the queue.
*
* @return number of affected rows
*
* @throws RemoteException on calendar provider errors. In case of [android.os.DeadObjectException],
* the provider has probably been killed/crashed or the calling process is cached and thus IPC is frozen (Android 14+).
*
* @throws ContactsStorageException if
*
* - the transaction is too large and can't be split (wrapped [TransactionTooLargeException])
* - the batch can't be processed (wrapped [OperationApplicationException])
* - the content provider throws a [RuntimeException] (will be wrapped)
*/
fun commit(): Int {
var affected = 0
if (!queue.isEmpty())
try {
if (Constants.log.isLoggable(Level.FINE)) {
Constants.log.log(Level.FINE, "Committing ${queue.size} operations:")
for ((idx, op) in queue.withIndex())
Constants.log.log(Level.FINE, "#$idx: ${op.build()}")
}

results = arrayOfNulls(queue.size)
runBatch(0, queue.size)
if (!queue.isEmpty()) {
if (Constants.log.isLoggable(Level.FINE)) {
Constants.log.log(Level.FINE, "Committing ${queue.size} operations:")
for ((idx, op) in queue.withIndex())
Constants.log.log(Level.FINE, "#$idx: ${op.build()}")
}

for (result in results.filterNotNull())
when {
result.count != null -> affected += result.count ?: 0
result.uri != null -> affected += 1
}
Constants.log.fine("$affected record(s) affected")
results = arrayOfNulls(queue.size)
runBatch(0, queue.size)

} catch(e: Exception) {
throw ContactsStorageException("Couldn't apply batch operation", e)
}
for (result in results.filterNotNull())
when {
result.count != null -> affected += result.count ?: 0
result.uri != null -> affected += 1
}
Constants.log.fine("$affected record(s) affected")
}

queue.clear()
return affected
Expand All @@ -79,30 +84,44 @@ class BatchOperation(
/**
* Runs a subset of the operations in [queue] using [providerClient] in a transaction.
* Catches [TransactionTooLargeException] and splits the operations accordingly.
*
* @param start index of first operation which will be run (inclusive)
* @param end index of last operation which will be run (exclusive!)
* @throws RemoteException on calendar provider errors
* @throws OperationApplicationException when the batch can't be processed
* @throws ContactsStorageException if the transaction is too large
*
* @throws RemoteException on calendar provider errors. In case of [android.os.DeadObjectException],
* the provider has probably been killed/crashed or the calling process is cached and thus IPC is frozen (Android 14+).
*
* @throws ContactsStorageException if
*
* - the transaction is too large and can't be split (wrapped [TransactionTooLargeException])
* - the batch can't be processed (wrapped [OperationApplicationException])
* - the content provider throws a [RuntimeException] (will be wrapped)
*/
private fun runBatch(start: Int, end: Int) {
if (end == start)
return // nothing to do

try {
val ops = toCPO(start, end)
Constants.log.fine("Running ${ops.size} operations ($start .. ${end-1})")
Constants.log.fine("Running ${ops.size} operations ($start .. ${end - 1})")
val partResults = providerClient.applyBatch(ops)

val n = end - start
if (partResults.size != n)
Constants.log.warning("Batch operation returned only ${partResults.size} instead of $n results")

System.arraycopy(partResults, 0, results, start, partResults.size)

} catch (e: OperationApplicationException) {
throw ContactsStorageException("Couldn't apply batch operation", e)

} catch (e: RuntimeException) {
throw ContactsStorageException("Content provider threw a runtime exception", e)

} catch(e: TransactionTooLargeException) {
if (end <= start + 1)
// only one operation, can't be split
throw ContactsStorageException("Can't transfer data to content provider (data row too large)")
throw ContactsStorageException("Can't transfer data to content provider (too large data row can't be split)", e)

Constants.log.warning("Transaction too large, splitting (losing atomicity)")
val mid = start + (end - start)/2
Expand Down

0 comments on commit 03a37a8

Please sign in to comment.