Ask the server only for the conversations that changed - #6744
AndyScherzinger wants to merge 5 commits into
Conversation
📱 QA build
The QA build installs alongside a released Nextcloud app, so you can keep Downloading the file requires a GitHub account, so open this link on the |
0b93e1e to
465e035
Compare
cdd211e to
efe3157
Compare
efe3157 to
103dc8b
Compare
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (7)
Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 3 remain after this review. 📝 WalkthroughWalkthroughRoom-list fetching now supports server delta responses with modification timestamps. The repository persists sync state, preserves omitted conversations for delta responses, and reconciles full responses. Pull-to-refresh, successful conversation leave, and successful conversation deletion request a full sync. The network layer parses the modification header and throws for unsuccessful HTTP responses. Tests cover delta preservation, full-sync reconciliation, and timestamp handling. Priority: ➖ Normal Merge Risk: ⚪ Minimal · up to No new merge-blocking issue is established. The planned real-server check, including federated conversations, remains useful before release. Security Architecture ReviewSecurity architecture risk: 🟡 Moderate · up to Delta responses are correctly prevented from deleting conversations they omit, and synchronization remains account-scoped. However, overlapping requests can undermine leave/delete cleanup: an older response can restore stale conversation data and reset synchronization state after a newer refresh. Recovery then depends on another full refresh. Retained concerns
Security review detailsSecurity Blast Radius
Trust Boundaries and Controls
Resilience and Maintainability Implications
Hardening Proposals
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Advanced
Run ID: b7669712-a578-4858-a89e-9076348f8034
📒 Files selected for processing (15)
app/src/main/java/com/nextcloud/talk/api/NcApi.javaapp/src/main/java/com/nextcloud/talk/conversationlist/ConversationsListActivity.ktapp/src/main/java/com/nextcloud/talk/conversationlist/data/OfflineConversationsRepository.ktapp/src/main/java/com/nextcloud/talk/conversationlist/data/network/ConversationsNetworkDataSource.ktapp/src/main/java/com/nextcloud/talk/conversationlist/data/network/OfflineFirstConversationsRepository.ktapp/src/main/java/com/nextcloud/talk/conversationlist/data/network/RetrofitConversationsNetwork.ktapp/src/main/java/com/nextcloud/talk/conversationlist/viewmodels/ConversationsListViewModel.ktapp/src/main/java/com/nextcloud/talk/dagger/modules/RepositoryModule.ktapp/src/main/java/com/nextcloud/talk/data/database/dao/ConversationsDao.ktapp/src/main/java/com/nextcloud/talk/utils/preview/ComposePreviewUtils.ktapp/src/main/java/com/nextcloud/talk/utils/preview/ComposePreviewUtilsDaos.ktapp/src/test/java/com/nextcloud/talk/conversationlist/data/network/ConversationListDeltaSyncIntegrationTest.ktapp/src/test/java/com/nextcloud/talk/conversationlist/data/network/ConversationListFreshnessIntegrationTest.ktapp/src/test/java/com/nextcloud/talk/conversationlist/data/network/OfflineFirstConversationsRepositoryTest.ktapp/src/test/java/com/nextcloud/talk/conversationlist/data/network/RoomListMessagePrefetchIntegrationTest.kt
Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 3 remain after this review.
103dc8b to
cbca482
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Advanced
Run ID: c76b06e8-90ba-4bf0-ad1c-eb7f39d266fe
📒 Files selected for processing (9)
app/src/main/java/com/nextcloud/talk/api/NcApi.javaapp/src/main/java/com/nextcloud/talk/conversationlist/ConversationsListActivity.ktapp/src/main/java/com/nextcloud/talk/conversationlist/data/OfflineConversationsRepository.ktapp/src/main/java/com/nextcloud/talk/conversationlist/data/network/ConversationsNetworkDataSource.ktapp/src/main/java/com/nextcloud/talk/conversationlist/data/network/OfflineFirstConversationsRepository.ktapp/src/main/java/com/nextcloud/talk/conversationlist/viewmodels/ConversationsListViewModel.ktapp/src/main/java/com/nextcloud/talk/utils/preview/ComposePreviewUtils.ktapp/src/main/java/com/nextcloud/talk/utils/preview/ComposePreviewUtilsDaos.ktapp/src/test/java/com/nextcloud/talk/conversationlist/data/network/ConversationListFreshnessIntegrationTest.kt
Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 3 remain after this review.
cbca482 to
372c35b
Compare
Add the modifiedSince query parameter to GET /room and return the full response so the X-Nextcloud-Talk-Modified-Before header can be read. The network data source now reports the conversations, the timestamp to use for the next delta request, and whether the response it got back was a delta at all - a server below conversation API v4 answers in full whatever it was asked, and a caller that assumed otherwise would reconcile deletions against the wrong kind of response. Assisted-by: Claude Code:claude-opus-5 Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
Send the timestamp the server handed back on the last conversation list sync as modifiedSince, so a sync carries what changed rather than every room with its last message, and ask without includeStatus while doing so - with it the server returns every one-to-one room whatever modifiedSince says, which is most of the list on a typical account. The response to such a request cannot express a conversation being deleted or the user being removed from one, so the left-conversation reconcile is skipped for it: every conversation the response leaves out would otherwise look like one that was left, and deleting those cascades through the foreign key to their cached messages and chat blocks. The network layer reports whether the response really was a delta, because a server below conversation API v4 answers in full whatever it was asked. The timestamp is stored per account in arbitrary storage, and only once the response is in the database - one kept ahead of a write that then failed would permanently skip the conversations that write was carrying. A failed sync drops it, so the next one asks for everything again. Assisted-by: Claude Code:claude-opus-5 Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
372c35b to
ebf3e28
Compare
in these cases, trigger a full sync |
ebf3e28 to
96fd5ad
Compare
|
Both confirmed and fixed in Same root cause for both: the app removes the conversation on the server and leaves the local row to the list sync's reconciliation. A Rather than fixing the two call sites in the conversation list, the invalidation sits in the two workers, so leaving or deleting from the chat screen and from conversation info are covered as well: /** Makes the next conversation list sync of the account with the internal id [accountId] fetch the whole list. */
fun requireFullSync(accountId: Long)
On top of that, the list now refreshes immediately in both cases instead of waiting: leaving already called Covered by Still worth a retest on device, since I could only reproduce the mechanism in tests, not the UI flow. |
96fd5ad to
19a8ef6
Compare
A modifiedSince response cannot say that a conversation was deleted or that the user was removed from one, so the server asks clients to fetch the whole list regularly anyway. Follow that: at least every five minutes, and always when the internal signaling backend is in use, where no signaling server exists to announce the change out of band. A caller can also demand one. Pull to refresh does, so a conversation left on another device is gone by the time the indicator stops spinning rather than within the next five minutes. A device clock that moved backwards makes the last full sync read as being in the future; that is not a young full sync but an unusable one, and it asks for a full one too. Assisted-by: Claude Code:claude-opus-5 Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
The case worth a test here is the one that destroys data rather than the one that annoys: a delta response lists only what changed, so every conversation it leaves out looks like one the user left, and reconciling those away takes their cached messages and chat blocks with them through the foreign key cascade. The first test seeds ten conversations with cached chat, runs a delta sync that mentions one of them, and asserts the other nine still have theirs. It was checked against the mistake it guards: with the delta branch removed from the sync, it fails. The rest cover what decides the mode - the stored timestamp is sent and includeStatus is not, the internal signaling backend never gets a delta, a full response still reconciles a conversation away, and a failed sync drops the timestamp so the next one asks for everything. Assisted-by: Claude Code:claude-opus-5 Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
The per-room message catch-up wraps each room in runCatching, which also catches the CancellationException raised when the sync is stopped. The failure was logged and the loop moved on to the next room, so a sync that had been cancelled kept issuing requests for every remaining room. Rethrow the cancellation so the loop ends with the scope that owns it. Assisted-by: Claude Code:claude-opus-5 Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
19a8ef6 to
0a1d95f
Compare
Every conversation list sync is a full
GET /roomtoday: every room, with its last message. This adds the server'smodifiedSincedelta so a sync carries only what changed. It is the prerequisite for the two periodic triggers stacked on top of it, which would otherwise multiply the most expensive request the client makes.What it does
modifiedSinceonGET /roomand echoes back theX-Nextcloud-Talk-Modified-Beforeresponse header on the next request — the server's own timestamp, never a client clock.includeStatuson delta syncs, which otherwise makes the server return every one-to-one room regardless and eats most of the saving.docs/conversation.md.The one thing to check in review
A delta response must never reach
determineLeftConversationIds. That function lists every previously known conversation absent from the response — on a filtered response, nearly all of them — and the delete cascades through the foreign key to cached messages and chat blocks.RoomListResult.wasDeltais reported by the network layer rather than derived from the caller's intent, because an old server silently answers a delta request in full, and the reconcile is skipped on it.Not in this PR
delete/disinvitesignaling messages, which the server also recommends. Android handles neither event today.🚧 TODO
🏁 Checklist
/backport to stable-xx.x🤖 AI (if applicable)