Skip to content

Adfa facility to disable webserver's pebble cache#1357

Open
jimturner-adfa wants to merge 5 commits into
stagefrom
ADFA-Facility-to-disable-webserver's-Pebble-cache
Open

Adfa facility to disable webserver's pebble cache#1357
jimturner-adfa wants to merge 5 commits into
stagefrom
ADFA-Facility-to-disable-webserver's-Pebble-cache

Conversation

@jimturner-adfa
Copy link
Copy Markdown
Collaborator

Clear the Pebble Cache (Code On the Go's cache) if the file
CodeOnTheGo.webserver.cs0
is in /sdcard/Download

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 1, 2026

Review Change Stack

📝 Walkthrough

Release Notes: Pebble Cache Disable Facility

Features

  • Cache invalidation mechanism: Added ability to clear the Pebble template cache by placing a marker file (CodeOnTheGo.webserver.cs0) in /sdcard/Download/
  • Configuration field: New clearCacheEnablePath field in ServerConfig data class enables centralized control of cache behavior
  • Bookshelf endpoint optimization: /pr/bs endpoint now clears the template cache when the marker file is present, improving cache refresh capabilities

Implementation Details

  • Cache clearing is triggered on each /pr/bs endpoint request when the marker file exists
  • Response state tracking added to prevent sending multiple HTTP response headers if cache clearing triggers an error
  • Enhanced error handling with context-aware logging for the bookshelf endpoint

Risks & Best Practices Violations

⚠️ Frozen Flag at Startup: The clearCacheEnabled flag is evaluated once at server startup and not re-checked at runtime. Users who create or delete the marker file while the server is running will not see the effect until the server restarts. This could lead to unexpected behavior and confusion.

⚠️ File-Based Feature Flag (Anti-pattern): Using file existence as a feature flag is unconventional and not centralized. The code itself contains a TODO comment acknowledging this: "TODO: Use the centralized experiments flag instead of this ad-hoc check." This approach differs from the codebase's other feature flag patterns and complicates testing and deployment.

⚠️ Hard-coded External Storage Path: The marker file path is fixed to /sdcard/Download/, which may not be compatible with:

  • Android 11+ scoped storage restrictions
  • Devices with different storage configurations
  • Non-Android or emulated environments

⚠️ Marker File Name Inconsistency: The commit message references both CodeOnTheGo.webserver.cs0 and CodeOnTheCode.webserver.cs0 - the naming inconsistency suggests possible confusion about the intended file name.

⚠️ Limited Cache Scope: Cache clearing only applies to the /pr/bs bookshelf endpoint. Other Pebble templates served by the application are not affected, creating inconsistent cache behavior across different endpoints.

⚠️ Incomplete Error Response Handling: The code includes an inline comment acknowledging that sending HTTP 500 errors after a partial response can corrupt the stream. While a responseStarted flag was added to mitigate this, the underlying design fragility remains and could lead to subtle bugs.

Migration/Testing Considerations

  • Cache behavior changes require server restart to take effect
  • No dynamic cache invalidation - create/delete of marker file requires server restart for changes to be recognized
  • Testing should verify behavior across the full server lifecycle, not just after marker file changes

Walkthrough

WebServer.kt is refactored to implement template-based bookshelf rendering. A new clearCacheEnablePath config field enables conditional template cache clearing. The /pr/bs endpoint now queries JSON from the database, caches and instantiates a Pebble template, and writes the rendered output. Debug logging is added to trace template compilation and context parsing.

Changes

Bookshelf Template Rendering Pipeline

Layer / File(s) Summary
Config & Template Engine Initialization
app/src/main/java/com/itsaky/androidide/localWebServer/WebServer.kt
New clearCacheEnablePath field added to ServerConfig. Imports for serialization and Okio utilities added. Server initialization creates pebbleEngine, templateCache, and bookshelfTemplateId properties alongside clearCacheEnabled toggle sourced from the new config path.
/pr/bs Endpoint & Template-Based Rendering
app/src/main/java/com/itsaky/androidide/localWebServer/WebServer.kt
handleBsEndpoint conditionally clears cache and tracks response state via boolean from realHandleBsEndpoint. realHandleBsEndpoint replaces static HTML generation with database query for bookshelf JSON, template lookup/caching by name, template instantiation with JSON context, and rendered output writing to client.
Documentation & Debug Logging
app/src/main/java/com/itsaky/androidide/localWebServer/WebServer.kt
KDocs expanded for handleClient, instantiatePebbleTemplate, and handlePrEndpoint. Debug logs added for template blob content and parsed context map during template compilation and rendering within instantiatePebbleTemplate.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • appdevforall/CodeOnTheGo#1318: Both PRs modify Pebble templating pipeline and per-template cache usage; this PR builds on the template support added there.
  • appdevforall/CodeOnTheGo#1003: Both PRs refactor error handling with boolean "output started" state tracking to avoid incorrect 500 responses after output begins.

Suggested reviewers

  • jatezzz
  • davidschachterADFA
  • hal-eisen-adfa

Poem

🐰 Templates dance in Pebble's embrace,
Bookshelf JSON finds its rightful place,
Cache clears when debug whispers call,
Template logs illuminate it all!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title mentions 'disable webserver's pebble cache' which directly relates to the main change of introducing a cache-clearing toggle, but contains a typo ('Adfa' instead of 'Add a') that affects clarity.
Description check ✅ Passed The description accurately describes the cache-clearing functionality when a specific marker file exists, which matches the main implementation change of the clearCacheEnabled toggle.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ADFA-Facility-to-disable-webserver's-Pebble-cache

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
app/src/main/java/com/itsaky/androidide/localWebServer/WebServer.kt (1)

72-72: 💤 Low value

Non-atomic read-modify-write on bookshelfTemplateId in concurrent context.

bookshelfTemplateId is a mutable var accessed from realHandleBsEndpoint which can be called by multiple request-handling threads. The check-then-set pattern at lines 759-771 is racy. While functionally benign (same value is always written), consider using AtomicInteger or synchronization for clarity and correctness.

♻️ Suggested fix using AtomicInteger
-    private          var bookshelfTemplateId : Int = -1;
+    private          val bookshelfTemplateId = java.util.concurrent.atomic.AtomicInteger(-1)

Then update the usage in realHandleBsEndpoint:

// Check if template ID is cached
if (bookshelfTemplateId.get() == -1) {
    // ... query database ...
    bookshelfTemplateId.compareAndSet(-1, cursor.getInt(0))
}
val templateId = bookshelfTemplateId.get()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/main/java/com/itsaky/androidide/localWebServer/WebServer.kt` at line
72, The field bookshelfTemplateId is mutated non-atomically and read from
realHandleBsEndpoint by concurrent request threads; replace the Int var with an
AtomicInteger (or guard accesses with a synchronized block) and update all
reads/writes in realHandleBsEndpoint to use AtomicInteger.get(),
compareAndSet(-1, newValue) when caching the DB result, and AtomicInteger.get()
when reading the templateId to use—this eliminates the racy check-then-set on
bookshelfTemplateId.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/src/main/java/com/itsaky/androidide/localWebServer/WebServer.kt`:
- Around line 776-784: The catch in WebServer.kt currently logs and sends an
error response but does not stop further processing, allowing code after the
try/catch (which uses jsonText) to run with jsonText uninitialized; modify the
catch block in the request-handling method so that after calling
sendError(writer, output, 500, "Internal Server Error", e.message ?: "") it
immediately returns false (or otherwise exits the handler) to prevent reaching
instantiatePebbleTemplate(...) and using jsonText, ensuring cursor.close() still
runs in finally.
- Around line 490-492: The unconditional debug call "log.debug(\"context =
${context}\")" in WebServer.kt should be gated like other debug logs to avoid
interpolation overhead; wrap it with the existing debug check (e.g., use
log.isDebugEnabled or the file's debugEnabled pattern) or use a lazy/logging API
that defers message construction, referencing the "log.debug" call and the
"context" value so the message is only constructed and emitted when debugging is
enabled.
- Around line 759-771: The code reassigns the variable cursor when fetching the
'bookshelf' template ID, leaking the earlier cursor resource; before calling
database.rawQuery("SELECT id FROM Templates WHERE name = 'bookshelf'") reclose
the existing cursor (or use a new variable like templateCursor) so the original
cursor is closed, then proceed to check cursor.count, moveToFirst(), and set
bookshelfTemplateId; ensure the finally block still closes whichever cursor
variable(s) are used and that sendError and the rest of the logic remain
unchanged.

---

Nitpick comments:
In `@app/src/main/java/com/itsaky/androidide/localWebServer/WebServer.kt`:
- Line 72: The field bookshelfTemplateId is mutated non-atomically and read from
realHandleBsEndpoint by concurrent request threads; replace the Int var with an
AtomicInteger (or guard accesses with a synchronized block) and update all
reads/writes in realHandleBsEndpoint to use AtomicInteger.get(),
compareAndSet(-1, newValue) when caching the DB result, and AtomicInteger.get()
when reading the templateId to use—this eliminates the racy check-then-set on
bookshelfTemplateId.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9380d000-c893-4a8f-a865-2aa7f2e0c81a

📥 Commits

Reviewing files that changed from the base of the PR and between 4f206c6 and ed5ab9c.

📒 Files selected for processing (1)
  • app/src/main/java/com/itsaky/androidide/localWebServer/WebServer.kt

Comment on lines +490 to +492
/*******************DEBUGGING ONLY*******************/
log.debug("context = ${context}")
/*******************DEBUGGING ONLY*******************/
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Unconditional debug logging should be gated.

This log.debug call is not guarded by debugEnabled, unlike other debug logging throughout the file. This will execute the string interpolation and logging call on every template instantiation even in production, causing unnecessary overhead.

🔧 Proposed fix
         /*******************DEBUGGING ONLY*******************/
-        log.debug("context = ${context}")
+        if (debugEnabled) log.debug("context = {}", context)
         /*******************DEBUGGING ONLY*******************/
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/main/java/com/itsaky/androidide/localWebServer/WebServer.kt` around
lines 490 - 492, The unconditional debug call "log.debug(\"context =
${context}\")" in WebServer.kt should be gated like other debug logs to avoid
interpolation overhead; wrap it with the existing debug check (e.g., use
log.isDebugEnabled or the file's debugEnabled pattern) or use a lazy/logging API
that defers message construction, referencing the "log.debug" call and the
"context" value so the message is only constructed and emitted when debugging is
enabled.

Comment on lines +759 to +771
if (bookshelfTemplateId == -1) {
cursor = database.rawQuery("SELECT id FROM Templates WHERE name = 'bookshelf'", arrayOf())

if (cursor.count != 1) {
if (cursor.count == 0)
sendError(writer, output, 404, "Not Found")
else
sendError(writer, output, 500, "Corrupt database - ${cursor.count} Bookshelf templates found when 1 was expected.")
return false
}

try {
if (debugEnabled) log.debug("Retrieved {} rows.", cursor.count)
cursor.moveToFirst()
bookshelfTemplateId = cursor.getInt(0);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Cursor resource leak when template ID is fetched.

When bookshelfTemplateId == -1, the cursor variable is reassigned at line 760, losing the reference to the first cursor (from line 739). The finally block at line 781 only closes the second cursor, leaking the first.

🐛 Proposed fix - close first cursor before reassigning
             //Have we already fetched the template
             if (bookshelfTemplateId == -1) {
+                cursor.close()  // Close the bookshelf JSON cursor before reusing variable
                 cursor = database.rawQuery("SELECT id FROM Templates WHERE name = 'bookshelf'", arrayOf())

                 if (cursor.count != 1) {

Alternatively, use a separate variable for the template cursor to make the resource management clearer.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (bookshelfTemplateId == -1) {
cursor = database.rawQuery("SELECT id FROM Templates WHERE name = 'bookshelf'", arrayOf())
if (cursor.count != 1) {
if (cursor.count == 0)
sendError(writer, output, 404, "Not Found")
else
sendError(writer, output, 500, "Corrupt database - ${cursor.count} Bookshelf templates found when 1 was expected.")
return false
}
try {
if (debugEnabled) log.debug("Retrieved {} rows.", cursor.count)
cursor.moveToFirst()
bookshelfTemplateId = cursor.getInt(0);
if (bookshelfTemplateId == -1) {
cursor.close() // Close the bookshelf JSON cursor before reusing variable
cursor = database.rawQuery("SELECT id FROM Templates WHERE name = 'bookshelf'", arrayOf())
if (cursor.count != 1) {
if (cursor.count == 0)
sendError(writer, output, 404, "Not Found")
else
sendError(writer, output, 500, "Corrupt database - ${cursor.count} Bookshelf templates found when 1 was expected.")
return false
}
cursor.moveToFirst()
bookshelfTemplateId = cursor.getInt(0);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/main/java/com/itsaky/androidide/localWebServer/WebServer.kt` around
lines 759 - 771, The code reassigns the variable cursor when fetching the
'bookshelf' template ID, leaking the earlier cursor resource; before calling
database.rawQuery("SELECT id FROM Templates WHERE name = 'bookshelf'") reclose
the existing cursor (or use a new variable like templateCursor) so the original
cursor is closed, then proceed to check cursor.count, moveToFirst(), and set
bookshelfTemplateId; ensure the finally block still closes whichever cursor
variable(s) are used and that sendError and the rest of the logic remain
unchanged.

Comment on lines +776 to +784
} catch (e: Exception) {
log.error("Error processing request: {}", e.message)
sendError(writer, output, 500, "Internal Server Error", e.message ?: "")

} finally {
cursor.close()
}

if (debugEnabled) log.debug("html is '{}'.", html) // May output a lot of stuff but better too much than too little. --DS, 23-Feb-2026
val result = instantiatePebbleTemplate(bookshelfTemplateId, jsonText, "/bookshelf", "application/json", "none")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Missing return false after error in catch block causes use of uninitialized variable.

The catch block at lines 776-778 sends an error response but doesn't return. Execution continues to line 784 where jsonText may be uninitialized (if the exception occurred before line 754), causing UninitializedPropertyAccessException.

🐛 Proposed fix
         } catch (e: Exception) {
             log.error("Error processing request: {}", e.message)
             sendError(writer, output, 500, "Internal Server Error", e.message ?: "")
+            return false

         } finally {
             cursor.close()
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
} catch (e: Exception) {
log.error("Error processing request: {}", e.message)
sendError(writer, output, 500, "Internal Server Error", e.message ?: "")
} finally {
cursor.close()
}
if (debugEnabled) log.debug("html is '{}'.", html) // May output a lot of stuff but better too much than too little. --DS, 23-Feb-2026
val result = instantiatePebbleTemplate(bookshelfTemplateId, jsonText, "/bookshelf", "application/json", "none")
} catch (e: Exception) {
log.error("Error processing request: {}", e.message)
sendError(writer, output, 500, "Internal Server Error", e.message ?: "")
return false
} finally {
cursor.close()
}
val result = instantiatePebbleTemplate(bookshelfTemplateId, jsonText, "/bookshelf", "application/json", "none")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/main/java/com/itsaky/androidide/localWebServer/WebServer.kt` around
lines 776 - 784, The catch in WebServer.kt currently logs and sends an error
response but does not stop further processing, allowing code after the try/catch
(which uses jsonText) to run with jsonText uninitialized; modify the catch block
in the request-handling method so that after calling sendError(writer, output,
500, "Internal Server Error", e.message ?: "") it immediately returns false (or
otherwise exits the handler) to prevent reaching instantiatePebbleTemplate(...)
and using jsonText, ensuring cursor.close() still runs in finally.

return String(bytes, 0, len, Charsets.ISO_8859_1)
}

/**
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This comment/documentation and some other new ones in this file seem to have been added despite the functions not being new.
I think they can be removed or at the very least be more concise.

val mapper = ObjectMapper()
val context: Map<String, Any> = mapper.readValue(dbContent.toString(Charsets.UTF_8), object : TypeReference<Map<String, Any>>() {})

/*******************DEBUGGING ONLY*******************/
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This log should be safe to remove


//Have we already fetched the template
if (bookshelfTemplateId == -1) {
cursor = database.rawQuery("SELECT id FROM Templates WHERE name = 'bookshelf'", arrayOf())
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The cursor wasn't previously closed before its reassignment here. Let's ensure safe closure.

WHERE C.id = B.contentID
AND B.bookCategoryID = BC.id
GROUP BY BC.category
ORDER BY BC.category,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Adding ORDER BY after GROUP BY will sort the resulting category group, and not the JSON_GROUP_ARRAY itself. Is that the intention?

If that's not the intention, then this subquery will sort the items in JSON_GROUP_ARRAY:

FROM (
      SELECT BC.category, BC.description as category_desc, IFNULL(B.title, C.path) as title, B.description as book_desc, C.path
      FROM Content C
      JOIN Bookshelf B ON C.id = B.contentID
      JOIN BookCategories BC ON B.bookCategoryID = BC.id
      ORDER BY BC.category, title
  )
  GROUP BY category
);

val debugDatabaseTimestamp = getDatabaseTimestamp(config.debugDatabasePath, true)
if (debugDatabaseTimestamp > databaseTimestamp) {
database.close()
database = SQLiteDatabase.openDatabase(config.debugDatabasePath, null, SQLiteDatabase.OPEN_READONLY)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

You might want to reset bookshelfTemplateId to -1 here because of the database swap happening, as well as when clearCacheEnabled is triggered.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants