[messages] add processing order option#260
Conversation
🤖 Pull request artifacts
|
WalkthroughAdds configurable processing order (LIFO/FIFO) for message retrieval. Replaces a single DAO pending query with FIFO/LIFO variants, surfaces the setting in MessagesSettings and UI, plumbs it through repository, service loop, and Gateway API, and adds related resources and imports. Changes
Sequence Diagram(s)sequenceDiagram
actor Worker as MessagesService
participant Settings as MessagesSettings
participant Repo as MessagesRepository
participant DAO as MessagesDao
Worker->>Settings: get processingOrder
Worker->>Repo: getPending(order)
alt order == LIFO
Repo->>DAO: getPendingLifo()
else order == FIFO
Repo->>DAO: getPendingFifo()
end
DAO-->>Repo: MessageWithRecipients?
Repo-->>Worker: StoredSendRequest? / null
Worker->>Worker: process/send if not null
sequenceDiagram
actor Bg as GatewayService
participant Settings as MessagesSettings
participant API as GatewayApi
participant Server as HTTP Endpoint
Bg->>Settings: get processingOrder
Bg->>API: getMessages(token, order)
API->>Server: GET /messages?order=lifo|fifo
Server-->>API: List<Message>
API-->>Bg: List<Message>
Bg->>Bg: process messages
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Tip 🔌 Remote MCP (Model Context Protocol) integration is now available!Pro plan users can now connect to remote MCP servers from the "Integrations" page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats. ✨ Finishing Touches
🧪 Generate unit tests
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 1
🔭 Outside diff range comments (1)
app/src/main/java/me/capcom/smsgateway/modules/messages/repositories/MessagesRepository.kt (1)
59-72: Replace recursion with a loop to avoid potential stack growthIf many head-of-queue items are non-Pending, the recursive re-fetch can recurse deeply and risk stack overflow. Use a loop.
Apply this diff:
- fun getPending(order: MessagesSettings.ProcessingOrder): StoredSendRequest? { - val message = when (order) { - MessagesSettings.ProcessingOrder.LIFO -> dao.getPendingLifo() - MessagesSettings.ProcessingOrder.FIFO -> dao.getPendingFifo() - } ?: return null - - if (message.state != ProcessingState.Pending) { - // if for some reason stored state is not in sync with recipients state - dao.updateMessageState(message.message.id, message.state) - return getPending(order) - } - - return message.toRequest() - } + fun getPending(order: MessagesSettings.ProcessingOrder): StoredSendRequest? { + while (true) { + val message = when (order) { + MessagesSettings.ProcessingOrder.LIFO -> dao.getPendingLifo() + MessagesSettings.ProcessingOrder.FIFO -> dao.getPendingFifo() + } ?: return null + + if (message.state != ProcessingState.Pending) { + // if for some reason stored state is not in sync with recipients state + dao.updateMessageState(message.message.id, message.state) + continue + } + + return message.toRequest() + } + }
🧹 Nitpick comments (8)
app/src/main/res/values/arrays.xml (2)
24-31: Mark internal values as non-translatable.These are persisted/internal values and should not be localized. Mark the values array as non-translatable to prevent accidental translation.
- <string-array name="processing_order_values"> + <string-array name="processing_order_values" translatable="false"> <item>LIFO</item> <item>FIFO</item> </string-array>
24-31: Align resource naming with existing pattern (“titles”/“values”).Elsewhere in this file you use “…_titles” and “…_values”. For consistency, prefer
processing_order_titlesinstead ofprocessing_order_entriesand update usage in preferences.- <string-array name="processing_order_entries"> + <string-array name="processing_order_titles"> <item>LIFO (newer messages first)</item> <item>FIFO (older messages first)</item> </string-array>Apply in preferences:
- android:entries="@array/processing_order_entries" + android:entries="@array/processing_order_titles"app/src/main/java/me/capcom/smsgateway/data/dao/MessagesDao.kt (2)
31-36: Consider adding an index to support the new ORDER BY path.Frequent queries filtering by state and ordering by (priority DESC, createdAt ASC/DESC) will benefit from a composite index.
- Suggested Room entity annotation on Message:
- indices = [Index(value = ["state", "priority", "createdAt"])]
- If you already have indices, ensure they cover (state, priority, createdAt) to avoid full scans.
31-36: Add brief KDoc to avoid future LIFO/FIFO mix-ups.A short comment above each method clarifying the intended order will prevent regressions like this.
Example:
/** * FIFO: oldest pending first (priority DESC, createdAt ASC) */app/src/main/res/xml/messages_preferences.xml (1)
21-28: Use app:icon for consistency with other preferences.This file mixes android:icon and app:icon. Prefer one convention (app:icon is used elsewhere here).
- <DropDownPreference - android:defaultValue="LIFO" - android:entries="@array/processing_order_entries" - android:entryValues="@array/processing_order_values" - android:icon="@drawable/ic_processing_order" + <DropDownPreference + android:defaultValue="LIFO" + android:entries="@array/processing_order_entries" + android:entryValues="@array/processing_order_values" + app:icon="@drawable/ic_processing_order" android:key="messages.processing_order" app:title="@string/processing_order_title" app:useSimpleSummaryProvider="true" />app/src/main/java/me/capcom/smsgateway/ui/settings/MessagesSettingsFragment.kt (1)
42-46: Avoid potential ClassCastException by using a safe castIf a wrong preference type gets associated with one of these keys,
(preference as EditTextPreference)will crash. Use a safe cast.Apply this diff:
- (preference as EditTextPreference).setOnBindEditTextListener { + (preference as? EditTextPreference)?.setOnBindEditTextListener { it.inputType = InputType.TYPE_CLASS_NUMBER it.setSelectAllOnFocus(true) it.selectAll() }app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesSettings.kt (1)
136-139: Make import of ProcessingOrder more robust
valueOfwill throw on unexpected input, potentially breaking imports from older or malformed configs. Consider a tolerant parse that ignores bad values and keeps the default.Apply this diff:
- PROCESSING_ORDER -> storage.set( - key, - value?.let { ProcessingOrder.valueOf(it.toString()) }) + PROCESSING_ORDER -> { + val parsed = value + ?.toString() + ?.uppercase() + ?.let { runCatching { ProcessingOrder.valueOf(it) }.getOrNull() } + if (parsed != null) { + storage.set(key, parsed) + } + // else: keep current/default + }app/src/main/java/me/capcom/smsgateway/modules/messages/repositories/MessagesRepository.kt (1)
61-63: Confirm DAO retrieval is atomic to prevent duplicate picks under concurrencyEnsure
getPendingLifo/Fifo()either:
- marks/locks the selected row atomically in the same transaction, or
- is called within a transaction that updates state before releasing it.
Otherwise, multiple workers could pick the same message. If not already covered in DAO, consider adding an atomic “claim” step (e.g., update where state=Pending and id IN (…) limit 1 returning …).
I can help propose a Room/SQL pattern for “select-and-claim” in one transaction if needed.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (9)
app/src/main/java/me/capcom/smsgateway/data/dao/MessagesDao.kt(1 hunks)app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesService.kt(1 hunks)app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesSettings.kt(5 hunks)app/src/main/java/me/capcom/smsgateway/modules/messages/repositories/MessagesRepository.kt(2 hunks)app/src/main/java/me/capcom/smsgateway/ui/settings/MessagesSettingsFragment.kt(1 hunks)app/src/main/res/drawable/ic_processing_order.xml(1 hunks)app/src/main/res/values/arrays.xml(1 hunks)app/src/main/res/values/strings.xml(1 hunks)app/src/main/res/xml/messages_preferences.xml(1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Build release / build-apk
🔇 Additional comments (7)
app/src/main/res/values/strings.xml (2)
134-135: LGTM: No semantic change to the long string.The reflow keeps the content intact. Good to go.
136-137: LGTM: New strings are appropriate and scoped.Names and values look good and match the new preference UI.
app/src/main/res/drawable/ic_processing_order.xml (1)
1-9: LGTM: Theme-aware vector drawable.Uses colorControlNormal and proper viewport/size. No issues.
app/src/main/res/xml/messages_preferences.xml (1)
20-29: Defaults are consistent: XML and code both use LIFO
Confirmed thatandroid:defaultValue="LIFO"in messages_preferences.xml matches the?: ProcessingOrder.LIFOfallback in MessagesSettings.processingOrder. No changes required.app/src/main/java/me/capcom/smsgateway/ui/settings/MessagesSettingsFragment.kt (1)
37-41: Switch to when-expression is a clean winThe new when-based dispatch is clearer and scales better than chained ifs. Nice.
app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesService.kt (1)
198-201: Verified: order-awaregetPendingintegration is correctAll checks passed—no further changes needed.
MessagesRepository.getPending(order: ProcessingOrder)signature is present.- Calls to
messages.getPending(settings.processingOrder)are updated inMessagesService.kt.- DAO provides
getPendingLifo()andgetPendingFifo().- Preference key
messages.processing_orderexists inres/xml/messages_preferences.xml.- Storage constant
PROCESSING_ORDERis defined and used inMessagesSettings.kt.Approving these code changes.
app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesSettings.kt (1)
24-27: Processing order setting is well-integrated
- Enum, storage key, public property with default (LIFO), and export inclusion are consistent and align with the UI default. Good cohesion.
Also applies to: 60-62, 95-96, 106-107
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
app/src/main/java/me/capcom/smsgateway/data/dao/MessagesDao.kt (1)
37-43: LIFO query ordering is correct (newest first within priority). Resolved prior concern.This addresses the earlier review noting swapped directions for FIFO/LIFO.
🧹 Nitpick comments (6)
app/src/main/res/xml/messages_preferences.xml (1)
20-29: Unify icon attribute namespace for consistency.This file mixes android:icon (Line 6) and app:icon (Line 26). Prefer one convention across the file (typically android:icon for Preference icon), unless there’s a project-wide guideline to use app:icon.
Proposed change:
- app:icon="@drawable/ic_processing_order" + android:icon="@drawable/ic_processing_order"app/src/main/java/me/capcom/smsgateway/modules/messages/repositories/MessagesRepository.kt (4)
12-12: Avoid repository dependency on UI/settings layer types.Taking MessagesSettings.ProcessingOrder couples the repository to the settings module. Consider introducing a domain-level enum (e.g., ProcessingOrder in the messages domain) and map the settings value at the boundary (e.g., in the service). This keeps repository free of settings concerns.
Additional change outside this hunk (illustrative):
// domain-level enum class ProcessingOrder { LIFO, FIFO } // in repository fun getPending(order: ProcessingOrder): StoredSendRequest? { /* ... */ } // in service (map from settings enum to domain enum) val order = when (settings.processingOrder) { MessagesSettings.ProcessingOrder.LIFO -> ProcessingOrder.LIFO MessagesSettings.ProcessingOrder.FIFO -> ProcessingOrder.FIFO } repo.getPending(order)
59-74: Convert recursion to loop: good; consider a bounded guard to prevent edge-case spins.The while(true) + resync logic is sound. For robustness against unexpected data conditions (e.g., update not taking effect, stale caches), add a small retry cap.
Apply:
- fun getPending(order: MessagesSettings.ProcessingOrder): StoredSendRequest? { - while (true) { + fun getPending(order: MessagesSettings.ProcessingOrder): StoredSendRequest? { + var attempts = 0 + val maxAttempts = 100 + while (attempts++ < maxAttempts) { val message = when (order) { MessagesSettings.ProcessingOrder.LIFO -> dao.getPendingLifo() MessagesSettings.ProcessingOrder.FIFO -> dao.getPendingFifo() } ?: return null if (message.state != ProcessingState.Pending) { // if for some reason stored state is not in sync with recipients state dao.updateMessageState(message.message.id, message.state) continue } return message.toRequest() } + return null }
59-74: Add unit tests for both FIFO and LIFO paths.Given the new branching on order, add tests to ensure:
- For equal priority, FIFO returns the oldest createdAt; LIFO returns the newest.
- Priority DESC is honored before createdAt.
I can draft DAO-level tests inserting messages with varying priority/createdAt, then asserting the expected id is returned by each method. Want me to open an issue and provide a test skeleton?
59-74: Double-check concurrent retrieval semantics.If multiple workers call getPending concurrently, consider whether you need to atomically transition the selected message from Pending to, say, Processing to avoid duplicate processing. SQLite doesn’t support SKIP LOCKED; a common pattern is to update the row’s state in the same transaction as selection. Current code reads, then updates only on mismatch. If concurrency is possible, you may want a DAO method that atomically claims a pending message.
I can propose a transactional “claim next” DAO method (SELECT … FOR UPDATE emulation via an UPDATE with matching ORDER BY using a subquery) tailored to SQLite/Room if needed.
app/src/main/java/me/capcom/smsgateway/data/dao/MessagesDao.kt (1)
30-43: Consider adding a composite index to support these queries.To keep getPendingFifo/Lifo fast as data grows, add an index on (state, priority DESC, createdAt ASC/DESC). In Room, define it on the Message entity:
Example (outside this file):
// In Message entity @Entity( tableName = "message", indices = [ Index(value = ["state", "priority", "createdAt"]) ] ) data class Message( /* ... */ )If priority is low cardinality, the state + createdAt index may suffice; measure with EXPLAIN QUERY PLAN.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
app/src/main/java/me/capcom/smsgateway/data/dao/MessagesDao.kt(1 hunks)app/src/main/java/me/capcom/smsgateway/modules/messages/repositories/MessagesRepository.kt(2 hunks)app/src/main/res/values/arrays.xml(1 hunks)app/src/main/res/xml/messages_preferences.xml(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- app/src/main/res/values/arrays.xml
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Build release / build-apk
- GitHub Check: Build insecure / build-apk
🔇 Additional comments (2)
app/src/main/res/xml/messages_preferences.xml (1)
20-29: Addition looks correct and consistent with the new setting.The new "Processing" category and DropDownPreference are wired with key, entries, entryValues, and a sensible default. This matches the PR objective to expose FIFO/LIFO selection.
app/src/main/java/me/capcom/smsgateway/data/dao/MessagesDao.kt (1)
30-36: FIFO query ordering is correct (oldest first within priority).createdAt ASC under priority DESC matches FIFO semantics. Good.
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
app/src/main/java/me/capcom/smsgateway/modules/gateway/GatewayApi.kt (1)
222-232: Avoid overloading toString() for wire values.Overriding toString() for transport can surprise logs/telemetry. Prefer an explicit wireValue property or function.
Option A (keep as-is): Acceptable if toString is only used for query serialization.
Option B (preferred): Add an explicit property and use it in getMessages:
enum class ProcessingOrder(val wireValue: String) { @SerializedName("lifo") LIFO("lifo"), @SerializedName("fifo") FIFO("fifo"), }Then pass parameter("order", processingOrder.wireValue).
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
app/src/main/java/me/capcom/smsgateway/modules/gateway/GatewayApi.kt(2 hunks)app/src/main/java/me/capcom/smsgateway/modules/gateway/GatewayService.kt(2 hunks)app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesRepository.kt(2 hunks)app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesService.kt(2 hunks)app/src/main/java/me/capcom/smsgateway/modules/messages/Module.kt(0 hunks)app/src/main/java/me/capcom/smsgateway/modules/messages/vm/MessageDetailsViewModel.kt(1 hunks)app/src/main/java/me/capcom/smsgateway/modules/messages/vm/MessagesListViewModel.kt(1 hunks)
💤 Files with no reviewable changes (1)
- app/src/main/java/me/capcom/smsgateway/modules/messages/Module.kt
✅ Files skipped from review due to trivial changes (2)
- app/src/main/java/me/capcom/smsgateway/modules/messages/vm/MessageDetailsViewModel.kt
- app/src/main/java/me/capcom/smsgateway/modules/messages/vm/MessagesListViewModel.kt
🚧 Files skipped from review as they are similar to previous changes (1)
- app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesService.kt
🔇 Additional comments (3)
app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesRepository.kt (2)
1-1: No stale imports/usages of the old package path detectedI ran:
rg -n --no-heading 'me\.capcom\.smsgateway\.modules\.messages\.repositories'and found no remaining references. All imports, usages, and DI wiring now point to
me.capcom.smsgateway.modules.messages.
58-73: DAO Queries Guarantee Pending State – Infinite Loop Risk Not ApplicableBoth
getPendingFifo()andgetPendingLifo()are annotated withWHERE state = 'Pending'so any returned
MessageWithRecipientsalways hasmessage.state == Pending. Thewhile(true)loop will exit on the first iteration, making the suggested attempts cap and bail-out unnecessary. No changes required here.Likely an incorrect or invalid review comment.
app/src/main/java/me/capcom/smsgateway/modules/gateway/GatewayService.kt (1)
20-20: LGTM: Import aligns with new ProcessingOrder plumbing.
5b0c496 to
9628614
Compare
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (1)
app/src/main/java/me/capcom/smsgateway/modules/gateway/GatewayService.kt (1)
197-202: Add backward-compatible fallback for servers that don’t support the ‘order’ queryOlder servers will 400/404 when
?order=is present. Wrap the two-arg call and fall back to the legacy endpoint. This mirrors the earlier review guidance and is still missing here.Apply this diff to introduce the fallback:
- val messages = api.getMessages(settings.token, processingOrder) + val messages = try { + api.getMessages(settings.token, processingOrder) + } catch (e: ClientRequestException) { + when (e.response.status) { + HttpStatusCode.BadRequest, HttpStatusCode.NotFound -> { + // Older servers don’t support 'order' yet; fall back to legacy endpoint + api.getMessages(settings.token) + } + else -> throw e + } + }Note: This assumes a single-argument overload
getMessages(token: String)exists in GatewayApi. If not present, please add it.To verify the overload exists and is used only via this fallback pattern, run:
#!/usr/bin/env bash set -euo pipefail echo "Search GatewayApi for getMessages overloads" rg -n --no-heading $'interface\\s+GatewayApi' -A200 | rg -n --no-heading $'getMessages\\(' -A1 echo echo "Search call sites of getMessages" rg -n --no-heading $'getMessages\\(' -A1 -B1
🧹 Nitpick comments (2)
app/src/main/java/me/capcom/smsgateway/modules/gateway/GatewayService.kt (1)
195-201: Nit: avoid shadowing the class property ‘settings’ with a local ‘settings’The local
val settings = settings.registrationInfo ?: returnshadows the class property and reduces readability. Prefer a distinct name likeregistrationorregInfoand update the use sites (Line 201).Example (outside selected lines):
val registration = settings.registrationInfo ?: return ... val messages = api.getMessages(registration.token, processingOrder)app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesRepository.kt (1)
58-63: Decouple repository from settings by using a domain-level ProcessingOrderThe repository shouldn’t depend on a UI/settings type. Introduce a domain-level
ProcessingOrder(e.g., inme.capcom.smsgateway.domain) and map UI settings to it at the boundary. This prevents cross-layer coupling and avoids drift with other enums (e.g., GatewayApi.ProcessingOrder).
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (14)
app/src/main/java/me/capcom/smsgateway/data/dao/MessagesDao.kt(1 hunks)app/src/main/java/me/capcom/smsgateway/modules/gateway/GatewayApi.kt(3 hunks)app/src/main/java/me/capcom/smsgateway/modules/gateway/GatewayService.kt(2 hunks)app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesRepository.kt(2 hunks)app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesService.kt(2 hunks)app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesSettings.kt(5 hunks)app/src/main/java/me/capcom/smsgateway/modules/messages/Module.kt(0 hunks)app/src/main/java/me/capcom/smsgateway/modules/messages/vm/MessageDetailsViewModel.kt(1 hunks)app/src/main/java/me/capcom/smsgateway/modules/messages/vm/MessagesListViewModel.kt(1 hunks)app/src/main/java/me/capcom/smsgateway/ui/settings/MessagesSettingsFragment.kt(1 hunks)app/src/main/res/drawable/ic_processing_order.xml(1 hunks)app/src/main/res/values/arrays.xml(1 hunks)app/src/main/res/values/strings.xml(1 hunks)app/src/main/res/xml/messages_preferences.xml(1 hunks)
💤 Files with no reviewable changes (1)
- app/src/main/java/me/capcom/smsgateway/modules/messages/Module.kt
🚧 Files skipped from review as they are similar to previous changes (10)
- app/src/main/res/drawable/ic_processing_order.xml
- app/src/main/java/me/capcom/smsgateway/modules/messages/vm/MessageDetailsViewModel.kt
- app/src/main/res/xml/messages_preferences.xml
- app/src/main/java/me/capcom/smsgateway/modules/messages/vm/MessagesListViewModel.kt
- app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesSettings.kt
- app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesService.kt
- app/src/main/java/me/capcom/smsgateway/modules/gateway/GatewayApi.kt
- app/src/main/res/values/strings.xml
- app/src/main/java/me/capcom/smsgateway/ui/settings/MessagesSettingsFragment.kt
- app/src/main/java/me/capcom/smsgateway/data/dao/MessagesDao.kt
🔇 Additional comments (2)
app/src/main/res/values/arrays.xml (1)
24-31: LGTM: titles/values are consistent with the new enums
- Titles are human-readable and translatable (default), values are stable identifiers and marked non-translatable, matching enum constants LIFO/FIFO.
app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesRepository.kt (1)
58-73: No change needed — DAO already filters pending rows and getPending syncs state
- MessagesDao.getPendingFifo / getPendingLifo (app/src/main/java/me/capcom/smsgateway/data/dao/MessagesDao.kt) use "WHERE state = 'Pending'" in their queries.
- MessageWithRecipients.state (app/src/main/java/me/capcom/smsgateway/data/entities/MessageWithRecipients.kt) is computed from recipient states.
- MessagesRepository.getPending (app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesRepository.kt) updates the message row via dao.updateMessageState(...) when the computed state is non-Pending; after that update the message no longer matches the DAO WHERE clause and won't be returned again.
Conclusion: the proposed while-loop guard is unnecessary — the DAO-level filter + update step prevent the loop from repeatedly returning the same non-pending record under normal DB behavior. Ignore the suggested diff.
Summary by CodeRabbit