Stream notification log exports

This commit is contained in:
ajp_anton
2026-07-27 17:14:33 +00:00
parent 8f21874492
commit a72c6b9437
5 changed files with 154 additions and 43 deletions
@@ -99,34 +99,80 @@ class SettingsFragment : Fragment(R.layout.fragment_settings) {
val format = pendingExport ?: return
val context = requireContext().applicationContext
Thread {
val entries = EncryptedNotificationLogStore(context).readAll()
val logStore = EncryptedNotificationLogStore(context)
val settings = LogViewSettingsStore(context).load()
context.contentResolver.openOutputStream(uri)?.use { output ->
when (format) {
ExportFormat.CSV -> output.write(LogExporter.csv(entries, settings).encodeToByteArray())
ExportFormat.FORMATTED -> output.write(LogExporter.formatted(entries, settings).encodeToByteArray())
ExportFormat.HTML_ZIP -> ZipOutputStream(output).use { zip ->
zip.putNextEntry(java.util.zip.ZipEntry("notification-log.html"))
val imageStore = EncryptedImageStore(context)
val imageEntries = entries.filter { it.imageId != null && LogField.CONTENTS in settings.visibleFields }
val exportedImages = imageEntries.mapNotNull { entry ->
imageStore.read(entry.imageId!!)?.let { entry.imageId to it }
}.toMap()
zip.write(LogExporter.html(entries, settings) { entry ->
entry.imageId?.takeIf(exportedImages::containsKey)?.let { "images/$it.png" }
}.encodeToByteArray())
zip.closeEntry()
exportedImages.forEach { (imageId, image) ->
zip.putNextEntry(java.util.zip.ZipEntry("images/$imageId.png"))
zip.write(image)
zip.closeEntry()
}
}
ExportFormat.CSV -> writeCsv(output.bufferedWriter(Charsets.UTF_8), logStore, settings)
ExportFormat.FORMATTED -> writeFormatted(output.bufferedWriter(Charsets.UTF_8), logStore, settings)
ExportFormat.HTML_ZIP -> writeHtmlZip(ZipOutputStream(output), logStore, settings, context)
}
}
}.start()
}
private fun writeCsv(
writer: java.io.BufferedWriter,
logStore: EncryptedNotificationLogStore,
settings: se.ajpanton.notificationlog.settings.LogViewSettings,
) = writer.use {
it.write(LogExporter.csvHeader(settings))
logStore.forEachNewest { entry ->
it.newLine()
it.write(LogExporter.csvRow(entry, settings))
}
}
private fun writeFormatted(
writer: java.io.BufferedWriter,
logStore: EncryptedNotificationLogStore,
settings: se.ajpanton.notificationlog.settings.LogViewSettings,
) = writer.use {
val headers = LogExporter.headers(settings)
val widths = headers.map { header -> header.length }.toMutableList()
logStore.forEachNewest { entry ->
LogExporter.values(entry, settings).forEachIndexed { index, value ->
widths[index] = maxOf(widths[index], value.length)
}
}
it.write(LogExporter.formattedRow(headers, widths))
logStore.forEachNewest { entry ->
it.newLine()
it.write(LogExporter.formattedRow(LogExporter.values(entry, settings), widths))
}
}
private fun writeHtmlZip(
zip: ZipOutputStream,
logStore: EncryptedNotificationLogStore,
settings: se.ajpanton.notificationlog.settings.LogViewSettings,
context: android.content.Context,
) = zip.use { zipOutput ->
val imageStore = EncryptedImageStore(context)
zipOutput.putNextEntry(java.util.zip.ZipEntry("notification-log.html"))
zipOutput.write(LogExporter.htmlStart(settings).encodeToByteArray())
logStore.forEachNewest { entry ->
val imagePath = entry.imageId
?.takeIf { LogField.CONTENTS in settings.visibleFields && imageStore.exists(it) }
?.let { imageId -> "images/$imageId.png" }
zipOutput.write(LogExporter.htmlRow(entry, settings, imagePath).encodeToByteArray())
}
zipOutput.write(LogExporter.htmlEnd().encodeToByteArray())
zipOutput.closeEntry()
if (LogField.CONTENTS in settings.visibleFields) {
logStore.forEachNewest { entry ->
entry.imageId?.let { imageId ->
imageStore.read(imageId)?.let { image ->
zipOutput.putNextEntry(java.util.zip.ZipEntry("images/$imageId.png"))
zipOutput.write(image)
zipOutput.closeEntry()
}
}
}
}
}
private enum class ExportFormat(val extension: String) { CSV("csv"), FORMATTED("txt"), HTML_ZIP("zip") }
private companion object {