-
-
Notifications
You must be signed in to change notification settings - Fork 25
Adfa 3802 make documentation bookshelf dynamic #1353
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: stage
Are you sure you want to change the base?
Changes from all commits
399e28d
ccfea34
b32a684
e6745a1
e865057
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -26,6 +26,8 @@ import java.util.concurrent.ConcurrentHashMap | |||||||||||||||||||||||||||||||||||||||
| import io.pebbletemplates.pebble.template.PebbleTemplate | ||||||||||||||||||||||||||||||||||||||||
| import com.fasterxml.jackson.core.type.TypeReference; | ||||||||||||||||||||||||||||||||||||||||
| import com.fasterxml.jackson.databind.ObjectMapper; | ||||||||||||||||||||||||||||||||||||||||
| import kotlinx.serialization.builtins.UByteArraySerializer | ||||||||||||||||||||||||||||||||||||||||
| import okio.ByteString.Companion.toByteString | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| data class ServerConfig( | ||||||||||||||||||||||||||||||||||||||||
|
|
@@ -62,8 +64,9 @@ class WebServer(private val config: ServerConfig) { | |||||||||||||||||||||||||||||||||||||||
| private val experimentsEnabled : Boolean = File(config.experimentsEnablePath).exists() // Frozen at startup. Restart server if needed. | ||||||||||||||||||||||||||||||||||||||||
| private val encodingHeader : String = "Accept-Encoding" | ||||||||||||||||||||||||||||||||||||||||
| private val brotliCompression : String = "br" | ||||||||||||||||||||||||||||||||||||||||
| private val pebbleEngine = PebbleEngine.Builder().loader(StringLoader()).build() | ||||||||||||||||||||||||||||||||||||||||
| private val templateCache = ConcurrentHashMap<Int, PebbleTemplate>() | ||||||||||||||||||||||||||||||||||||||||
| private val pebbleEngine = PebbleEngine.Builder().loader(StringLoader()).build() | ||||||||||||||||||||||||||||||||||||||||
| private val templateCache = ConcurrentHashMap<Int, PebbleTemplate>() | ||||||||||||||||||||||||||||||||||||||||
| private var bookshelfTemplateId : Int = -1; | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| private val contentChunkSize = 1024 * 1024 | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
|
|
@@ -243,6 +246,19 @@ clientSocket and the catch block logic are updated accordingly. | |||||||||||||||||||||||||||||||||||||||
| return String(bytes, 0, len, Charsets.ISO_8859_1) | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||
| * Handles a single HTTP request on the given client socket and writes the corresponding HTTP response. | ||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||
| * Parses the request line and headers, enforces GET-only for normal routes, and routes high-priority | ||||||||||||||||||||||||||||||||||||||||
| * "pr/" endpoints (including bookshelf, database table views, projects, and experiments). For other | ||||||||||||||||||||||||||||||||||||||||
| * paths it reads content and metadata from the server's SQLite database, reassembles multi-fragment | ||||||||||||||||||||||||||||||||||||||||
| * content, handles Brotli compression negotiation and decompression, optionally renders Pebble | ||||||||||||||||||||||||||||||||||||||||
| * templates, and writes appropriate HTTP response headers and body. If a newer debug database is | ||||||||||||||||||||||||||||||||||||||||
| * present on disk, swaps the server database to that file before serving the request. On errors | ||||||||||||||||||||||||||||||||||||||||
| * sends an HTTP error response and logs the failure. | ||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||
| * @param clientSocket Connected client socket used for reading the request and writing the response. | ||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||
| private fun handleClient(clientSocket: Socket) { | ||||||||||||||||||||||||||||||||||||||||
| if (debugEnabled) log.debug("In handleClient(), socket is {}.", clientSocket) | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
|
|
@@ -397,6 +413,17 @@ clientSocket and the catch block logic are updated accordingly. | |||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||
| * Renders a Pebble template identified by `templateId` using the provided JSON data and returns the rendered output as bytes. | ||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||
| * @param templateId The database ID of the Pebble template to load and compile. | ||||||||||||||||||||||||||||||||||||||||
| * @param dbContent JSON bytes that will be parsed and supplied as the template context. | ||||||||||||||||||||||||||||||||||||||||
| * @param path The request/content path associated with this template (used for diagnostic/logging purposes). | ||||||||||||||||||||||||||||||||||||||||
| * @param dbMimeType The MIME type of the stored content (used for diagnostic/logging purposes). | ||||||||||||||||||||||||||||||||||||||||
| * @param compression The compression label of the stored content (e.g., "br", "none") (used for diagnostic/logging purposes). | ||||||||||||||||||||||||||||||||||||||||
| * @return The rendered template encoded as UTF-8 bytes. | ||||||||||||||||||||||||||||||||||||||||
| * @throws Exception If the template ID is not found, is duplicated in the database, or if template lookup/instantiation fails. | ||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||
| private fun instantiatePebbleTemplate(templateId: Int, dbContent: ByteArray, path: String, dbMimeType: String, compression: String): ByteArray { | ||||||||||||||||||||||||||||||||||||||||
| if (debugEnabled) log.debug("Processing template for templateId={}", templateId) | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
|
|
@@ -446,6 +473,7 @@ clientSocket and the catch block logic are updated accordingly. | |||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
| else -> { | ||||||||||||||||||||||||||||||||||||||||
| val templateBlob = cursor.getBlob(0) | ||||||||||||||||||||||||||||||||||||||||
| if (debugEnabled) log.debug("templateBlob = '${String(templateBlob)}'") | ||||||||||||||||||||||||||||||||||||||||
| pebbleEngine.getTemplate(templateBlob.toString(Charsets.UTF_8)) | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
@@ -456,13 +484,25 @@ clientSocket and the catch block logic are updated accordingly. | |||||||||||||||||||||||||||||||||||||||
| val mapper = ObjectMapper() | ||||||||||||||||||||||||||||||||||||||||
| val context: Map<String, Any> = mapper.readValue(dbContent.toString(Charsets.UTF_8), object : TypeReference<Map<String, Any>>() {}) | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| /*******************DEBUGGING ONLY*******************/ | ||||||||||||||||||||||||||||||||||||||||
| log.debug("context = ${context}") | ||||||||||||||||||||||||||||||||||||||||
| /*******************DEBUGGING ONLY*******************/ | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| // Evaluate template with loaded data and return the output | ||||||||||||||||||||||||||||||||||||||||
| val sw = StringWriter() | ||||||||||||||||||||||||||||||||||||||||
| compiledTemplate.evaluate(sw, context) | ||||||||||||||||||||||||||||||||||||||||
| return sw.toString().toByteArray() | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||
| * Serve an HTML page showing the 20 most recent rows of the `LastChange` table. | ||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||
| * Queries the table schema to determine column names, selects the latest 20 rows | ||||||||||||||||||||||||||||||||||||||||
| * ordered by `changeTime`, escapes cell values for HTML, assembles an HTML table, | ||||||||||||||||||||||||||||||||||||||||
| * and writes a normal 200 HTML response to the client. On database or rendering | ||||||||||||||||||||||||||||||||||||||||
| * errors a 500 error response is sent. All database cursors are closed before returning. | ||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||
| private fun handleDbEndpoint(writer: PrintWriter, output: java.io.OutputStream) { | ||||||||||||||||||||||||||||||||||||||||
| if (debugEnabled) log.debug("Entering handleDbEndpoint().") | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
|
|
@@ -556,31 +596,38 @@ clientSocket and the catch block logic are updated accordingly. | |||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||
| * Handles the /pr/bs endpoint by invoking the bookshelf generator and sending a 500 error if generation fails. | ||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||
| * Calls realHandleBsEndpoint to produce and write the response body; if an exception occurs, sends an HTTP 500 | ||||||||||||||||||||||||||||||||||||||||
| * error using the reported output-start state so no additional headers/body are written after output has begun. | ||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||
| * @param writer PrintWriter used for writing textual HTTP response headers. | ||||||||||||||||||||||||||||||||||||||||
| * @param output Raw OutputStream used for writing the response body bytes. | ||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||
| private fun handleBsEndpoint(writer: PrintWriter, output: java.io.OutputStream) { | ||||||||||||||||||||||||||||||||||||||||
| if (debugEnabled) log.debug("Entering handleBsEndpoint().") | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| var projectDatabase : SQLiteDatabase? = null | ||||||||||||||||||||||||||||||||||||||||
| var outputStarted = false | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||
| projectDatabase = SQLiteDatabase.openDatabase(config.projectDatabasePath, | ||||||||||||||||||||||||||||||||||||||||
| null, | ||||||||||||||||||||||||||||||||||||||||
| SQLiteDatabase.OPEN_READONLY) | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| outputStarted = realHandleBsEndpoint(writer, output, projectDatabase) | ||||||||||||||||||||||||||||||||||||||||
| outputStarted = realHandleBsEndpoint(writer, output) | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| } catch (e: Exception) { | ||||||||||||||||||||||||||||||||||||||||
| log.error("Error handling /pr/bs endpoint: {}", e.message) | ||||||||||||||||||||||||||||||||||||||||
| sendError(writer, output, 500, "Internal Server Error 6", "Error generating database table.", outputStarted) | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| } finally { | ||||||||||||||||||||||||||||||||||||||||
| projectDatabase?.close() | ||||||||||||||||||||||||||||||||||||||||
| sendError(writer, output, 500, "Internal Server Error 6", "Error generating bookshelf HTML.", outputStarted) | ||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+608
to
+619
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Set
🤖 Prompt for AI Agents
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You could create a constant to centralize the |
||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| if (debugEnabled) log.debug("Leaving handleBsEndpoint().") | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||
| * Writes a small CSS response that shows or hides elements with the | ||||||||||||||||||||||||||||||||||||||||
| * `.code_on_the_go_experiment` class depending on the server's | ||||||||||||||||||||||||||||||||||||||||
| * `experimentsEnabled` flag. | ||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||
| private fun handleExEndpoint(writer: PrintWriter, output: java.io.OutputStream) { | ||||||||||||||||||||||||||||||||||||||||
| val flag = if (experimentsEnabled) "{}" else "{display: none;}" | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
|
|
@@ -589,6 +636,12 @@ clientSocket and the catch block logic are updated accordingly. | |||||||||||||||||||||||||||||||||||||||
| sendCSS(writer, output, ".code_on_the_go_experiment $flag") | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||
| * Handle the /pr/pr endpoint by opening the project database, delegating page generation to realHandlePrEndpoint, and sending an HTTP 500 error if generation fails. | ||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||
| * @param writer PrintWriter used to write response headers. | ||||||||||||||||||||||||||||||||||||||||
| * @param output OutputStream used to write response body bytes. | ||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||
| private fun handlePrEndpoint(writer: PrintWriter, output: java.io.OutputStream) { | ||||||||||||||||||||||||||||||||||||||||
| if (debugEnabled) log.debug("Entering handlePrEndpoint().") | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
|
|
@@ -644,63 +697,105 @@ second response. | |||||||||||||||||||||||||||||||||||||||
| if (debugEnabled) log.debug("Leaving handlePrEndpoint().") | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| private fun realHandleBsEndpoint(writer: PrintWriter, output: java.io.OutputStream, projectDatabase: SQLiteDatabase) : Boolean { | ||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||
| * Builds the Bookshelf content, renders it with the `bookshelf` template, and sends the resulting response to the client. | ||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||
| * @param writer PrintWriter for sending HTTP headers and control output. | ||||||||||||||||||||||||||||||||||||||||
| * @param output OutputStream for writing the response body bytes. | ||||||||||||||||||||||||||||||||||||||||
| * @return `true` if the templated response was written to the client, `false` if an error response was sent or no output was produced. | ||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||
| private fun realHandleBsEndpoint(writer: PrintWriter, output: java.io.OutputStream) : Boolean { | ||||||||||||||||||||||||||||||||||||||||
| if (debugEnabled) log.debug("Entering realHandleBsEndpoint().") | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| val query = """ | ||||||||||||||||||||||||||||||||||||||||
| SELECT id, | ||||||||||||||||||||||||||||||||||||||||
| name, | ||||||||||||||||||||||||||||||||||||||||
| DATETIME(create_at / 1000, 'unixepoch'), | ||||||||||||||||||||||||||||||||||||||||
| DATETIME(last_modified / 1000, 'unixepoch'), | ||||||||||||||||||||||||||||||||||||||||
| location, | ||||||||||||||||||||||||||||||||||||||||
| template_name, | ||||||||||||||||||||||||||||||||||||||||
| language | ||||||||||||||||||||||||||||||||||||||||
| FROM recent_project_table | ||||||||||||||||||||||||||||||||||||||||
| ORDER BY last_modified DESC""" | ||||||||||||||||||||||||||||||||||||||||
| // Database fetch | ||||||||||||||||||||||||||||||||||||||||
| val sql_query = | ||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||
| SELECT '{"result" : [' || group_concat(Item) || ']}' FROM ( | ||||||||||||||||||||||||||||||||||||||||
| SELECT | ||||||||||||||||||||||||||||||||||||||||
| JSON_OBJECT( | ||||||||||||||||||||||||||||||||||||||||
| 'category', IFNULL(BC.category, 'General'), | ||||||||||||||||||||||||||||||||||||||||
| 'description', BC.description, | ||||||||||||||||||||||||||||||||||||||||
| 'books', JSON_GROUP_ARRAY(JSON_OBJECT( | ||||||||||||||||||||||||||||||||||||||||
| 'title', IFNULL(B.title, C.path), | ||||||||||||||||||||||||||||||||||||||||
| 'description', B.description, | ||||||||||||||||||||||||||||||||||||||||
| 'link', C.path) | ||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||
| ) AS Item | ||||||||||||||||||||||||||||||||||||||||
| FROM Content AS C, | ||||||||||||||||||||||||||||||||||||||||
| Bookshelf AS B, | ||||||||||||||||||||||||||||||||||||||||
| BookCategories AS BC | ||||||||||||||||||||||||||||||||||||||||
| WHERE C.id = B.contentID | ||||||||||||||||||||||||||||||||||||||||
| AND B.bookCategoryID = BC.id | ||||||||||||||||||||||||||||||||||||||||
| GROUP BY BC.category | ||||||||||||||||||||||||||||||||||||||||
| ORDER BY BC.category, | ||||||||||||||||||||||||||||||||||||||||
| B.title | ||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||
| """.trimIndent() | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| var cursor = database.rawQuery(sql_query, arrayOf()) | ||||||||||||||||||||||||||||||||||||||||
| lateinit var jsonText : ByteArray | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| var html = getTableHtml("Projects", "Projects") + """ | ||||||||||||||||||||||||||||||||||||||||
| <tr> | ||||||||||||||||||||||||||||||||||||||||
| <th>Id</th> | ||||||||||||||||||||||||||||||||||||||||
| <th>Name</th> | ||||||||||||||||||||||||||||||||||||||||
| <th>Created</th> | ||||||||||||||||||||||||||||||||||||||||
| <th>Modified <span style="font-family: sans-serif">V</span></th> | ||||||||||||||||||||||||||||||||||||||||
| <th>Directory</th> | ||||||||||||||||||||||||||||||||||||||||
| <th>Template</th> | ||||||||||||||||||||||||||||||||||||||||
| <th>Language</th> | ||||||||||||||||||||||||||||||||||||||||
| </tr>""" | ||||||||||||||||||||||||||||||||||||||||
| // Process database fetch | ||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||
| if (cursor.count != 1) { | ||||||||||||||||||||||||||||||||||||||||
| if (cursor.count == 0) | ||||||||||||||||||||||||||||||||||||||||
| sendError(writer, output, 404, "Not Found") | ||||||||||||||||||||||||||||||||||||||||
| else | ||||||||||||||||||||||||||||||||||||||||
| sendError(writer, output, 500, "Corrupt database - ${cursor.count} bookshelf results found when one was expected.") | ||||||||||||||||||||||||||||||||||||||||
| return false | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| val cursor = projectDatabase.rawQuery(query, arrayOf()) | ||||||||||||||||||||||||||||||||||||||||
| //get the JSON from the bookshelf table | ||||||||||||||||||||||||||||||||||||||||
| cursor.moveToFirst() | ||||||||||||||||||||||||||||||||||||||||
| jsonText = cursor.getBlob(0) | ||||||||||||||||||||||||||||||||||||||||
| if (debugEnabled) log.debug("json content = '${String(jsonText)}'.") | ||||||||||||||||||||||||||||||||||||||||
| if (debugEnabled) log.debug("before fetch bookshelf template ID = '${bookshelfTemplateId}'") | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| //Have we already fetched the template | ||||||||||||||||||||||||||||||||||||||||
| if (bookshelfTemplateId == -1) { | ||||||||||||||||||||||||||||||||||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you avoid the nested structure? if () {
if () {
if () {
}
}
} |
||||||||||||||||||||||||||||||||||||||||
| 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); | ||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+755
to
+767
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't overwrite the first cursor before closing it. The cursor from 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||
| if (debugEnabled) log.debug("after the fetch bookshelf template ID = '${bookshelfTemplateId}'") | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| while (cursor.moveToNext()) { | ||||||||||||||||||||||||||||||||||||||||
| html += """<tr> | ||||||||||||||||||||||||||||||||||||||||
| <td>${escapeHtml(cursor.getString(0) ?: "")}</td> | ||||||||||||||||||||||||||||||||||||||||
| <td>${escapeHtml(cursor.getString(1) ?: "")}</td> | ||||||||||||||||||||||||||||||||||||||||
| <td>${escapeHtml(cursor.getString(2) ?: "")}</td> | ||||||||||||||||||||||||||||||||||||||||
| <td>${escapeHtml(cursor.getString(3) ?: "")}</td> | ||||||||||||||||||||||||||||||||||||||||
| <td>${escapeHtml(cursor.getString(4) ?: "")}</td> | ||||||||||||||||||||||||||||||||||||||||
| <td>${escapeHtml(cursor.getString(5) ?: "")}</td> | ||||||||||||||||||||||||||||||||||||||||
| <td>${escapeHtml(cursor.getString(6) ?: "")}</td> | ||||||||||||||||||||||||||||||||||||||||
| </tr>""" | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| html += "</table></body></html>" | ||||||||||||||||||||||||||||||||||||||||
| } 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") | ||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+772
to
+780
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Return immediately after sending the fetch-time error. If anything in the fetch block fails before Suggested 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
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| writeNormalToClient(writer, output, html) | ||||||||||||||||||||||||||||||||||||||||
| if (debugEnabled) log.debug("Bookshelf result is '{}'.", String(result)) | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| if (debugEnabled) log.debug("Leaving realHandlePrEndpoint().") | ||||||||||||||||||||||||||||||||||||||||
| writeNormalToClient(writer, output, String(result)) | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| if (debugEnabled) log.debug("Leaving realHandleBsEndpoint().") | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| return true | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||
| * Builds an HTML table of recent projects from the provided project database and writes it to the client. | ||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||
| * @param writer PrintWriter used for writing HTTP response headers. | ||||||||||||||||||||||||||||||||||||||||
| * @param output OutputStream used for writing the HTTP response body. | ||||||||||||||||||||||||||||||||||||||||
| * @param projectDatabase Read-only SQLiteDatabase containing the `recent_project_table`. | ||||||||||||||||||||||||||||||||||||||||
| * @return `true` if an HTML response was written to the client. | ||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||
| private fun realHandlePrEndpoint(writer: PrintWriter, output: java.io.OutputStream, projectDatabase: SQLiteDatabase) : Boolean { | ||||||||||||||||||||||||||||||||||||||||
| if (debugEnabled) log.debug("Entering realHandlePrEndpoint().") | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Invalidate these caches when the database handle changes.
Lines 308-313 can reopen
database, buttemplateCacheandbookshelfTemplateIdsurvive that swap. After a reload,/pr/bscan keep using a template ID or compiled template from the previous database.🤖 Prompt for AI Agents