Skip to content

Add JtxRecurringCollection for managing recurring jtx objects and exceptions#421

Open
sunkup wants to merge 3 commits into
mainfrom
395-add-support-for-exceptions-of-recurring-tasksjournal-entries
Open

Add JtxRecurringCollection for managing recurring jtx objects and exceptions#421
sunkup wants to merge 3 commits into
mainfrom
395-add-support-for-exceptions-of-recurring-tasksjournal-entries

Conversation

@sunkup
Copy link
Copy Markdown
Member

@sunkup sunkup commented May 21, 2026

Introduce JtxRecurringCollection (+tests) to handle recurrence and exception instances of jtx ical objects. See also #421 (review) .

Note

Copilot suggests safeguarding against misusing exception IDs. Would be a bit of a weird mistake to make and since its extra code I am not entirely sure whether we should add these checks. Added it, but could revert that commit if this seems overengineered.

@sunkup sunkup self-assigned this May 21, 2026
@sunkup sunkup added the refactoring Quality improvement of existing functions label May 21, 2026
@sunkup sunkup requested a review from Copilot May 21, 2026 12:57
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a new JtxRecurringCollection helper to manage recurring jtx objects together with their exception rows (linked by UID/RECURID) on top of the existing JtxCollection storage API, plus supporting assertion helpers and instrumentation tests.

Changes:

  • Add JtxRecurringCollection with CRUD methods for “main + exceptions” and utilities to process dirty/deleted exceptions.
  • Add android instrumentation tests covering CRUD, cleanup behavior, and dirty/deleted exception processing.
  • Extend test assertion helpers to compare JtxObjectAndExceptions and to apply a main jtx ID for comparisons.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
lib/src/main/kotlin/at/bitfire/synctools/test/AssertHelpers.kt Adds assertion + helper methods for JtxObjectAndExceptions comparisons in tests.
lib/src/main/kotlin/at/bitfire/synctools/storage/jtx/JtxRecurringCollection.kt Introduces the recurring “main + exceptions” storage abstraction and exception-processing helpers.
lib/src/androidTest/kotlin/at/bitfire/synctools/storage/jtx/JtxRecurringCollectionTest.kt Adds instrumentation coverage for the new recurring collection behavior.
Comments suppressed due to low confidence (2)

lib/src/main/kotlin/at/bitfire/synctools/storage/jtx/JtxRecurringCollection.kt:156

  • issue (blocking): updateJtxObjectAndExceptions assumes id refers to a main row, but it can silently corrupt data when an exception row ID is passed.

Because exceptions are associated by UID, passing an exception ID will cause findExceptionsByUid(existingUid) to include that same row, enqueueing a delete for id and then an update for the deleted row in the same batch (likely resulting in the row being removed and exceptions re-inserted without a main). Consider validating that the existing row has RECURID IS NULL (or at least RECURID is null in the fetched row) and throw IllegalArgumentException / return early if id is not a main object.

    fun updateJtxObjectAndExceptions(id: Long, objectAndExceptions: JtxObjectAndExceptions): Long {
        try {
            // validate / clean up input
            val cleaned = cleanUp(objectAndExceptions)

            // get UID of the existing main object to find and delete its old exceptions (because
            // they may be invalid for the updated event)
            val existingUid = collection.getJtxObjectRow(id, arrayOf(JtxContract.JtxICalObject.UID))
                ?.getAsString(JtxContract.JtxICalObject.UID)

            val batch = JtxBatchOperation(collection.client)

            // Delete old exceptions individually by item URI. The jtx Board provider blocks
            // bulk directory-level deletes of RECURID-bearing rows (adds "AND RECURID IS NULL"),
            // so a bulk delete would silently be a no-op here.
            // See: https://github.com/TechbeeAT/jtxBoard/blob/45a5f75693b2a50e55f2812bb5681016e50500d8/app/src/main/java/at/techbee/jtx/SyncContentProvider.kt#L197
            if (existingUid != null)
                for (oldException in findExceptionsByUid(existingUid))
                    collection.deleteJtxObject(
                        oldException.entityValues.getAsLong(JtxContract.JtxICalObject.ID),
                        batch
                    )

            // update main object
            collection.updateJtxObject(id, cleaned.main, batch)

lib/src/main/kotlin/at/bitfire/synctools/storage/jtx/JtxRecurringCollection.kt:186

  • issue (blocking): deleteJtxObjectAndExceptions() will delete whatever row id points to, even if it's an exception row.

Given this API is documented as taking a main ID, it should validate that the row has RECURID IS NULL before deleting; otherwise callers can accidentally delete a single exception while leaving the main + other exceptions behind, or trigger provider-side orphan cleanup in unexpected ways.

    /**
     * Deletes a jtx object and all its exceptions.
     *
     * @param id    ID of the main jtx object
     */
    fun deleteJtxObjectAndExceptions(id: Long) {
        try {
            val batch = JtxBatchOperation(collection.client)

            // delete main object; the jtx Board provider's removeOrphans() will clean up
            // associated exceptions and auto-generated instances when the main is removed
            collection.deleteJtxObject(id, batch)

            batch.commit()
        } catch (e: RemoteException) {
            throw LocalStorageException("Couldn't delete jtx object $id", e)
        }
    }

@sunkup sunkup marked this pull request as ready for review May 21, 2026 13:39
@sunkup sunkup requested a review from ArnyminerZ May 21, 2026 13:39
@sunkup sunkup force-pushed the 395-add-support-for-exceptions-of-recurring-tasksjournal-entries branch from 683d413 to ea06f65 Compare May 22, 2026 09:54
@sunkup sunkup force-pushed the 395-add-support-for-exceptions-of-recurring-tasksjournal-entries branch from ea06f65 to 89648f2 Compare May 22, 2026 12:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

refactoring Quality improvement of existing functions

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[jtx rewrite] Add support for exceptions of recurring tasks/journal entries

2 participants