Page encrypted logs with cursors
This commit is contained in:
@@ -19,6 +19,7 @@ import android.widget.TextView
|
|||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import se.ajpanton.notificationlog.data.EncryptedImageStore
|
import se.ajpanton.notificationlog.data.EncryptedImageStore
|
||||||
import se.ajpanton.notificationlog.data.EncryptedNotificationLogStore
|
import se.ajpanton.notificationlog.data.EncryptedNotificationLogStore
|
||||||
|
import se.ajpanton.notificationlog.data.NewestLogCursor
|
||||||
import se.ajpanton.notificationlog.databinding.FragmentViewLogsBinding
|
import se.ajpanton.notificationlog.databinding.FragmentViewLogsBinding
|
||||||
import se.ajpanton.notificationlog.model.NotificationAction
|
import se.ajpanton.notificationlog.model.NotificationAction
|
||||||
import se.ajpanton.notificationlog.model.NotificationLogEntry
|
import se.ajpanton.notificationlog.model.NotificationLogEntry
|
||||||
@@ -37,7 +38,7 @@ class ViewLogsFragment : Fragment(R.layout.fragment_view_logs) {
|
|||||||
private var binding: FragmentViewLogsBinding? = null
|
private var binding: FragmentViewLogsBinding? = null
|
||||||
private val expandedIds = mutableSetOf<String>()
|
private val expandedIds = mutableSetOf<String>()
|
||||||
private val rows = mutableListOf<LogRow>()
|
private val rows = mutableListOf<LogRow>()
|
||||||
private var loadedEntries = 0
|
private var nextCursor: NewestLogCursor? = null
|
||||||
private var loading = false
|
private var loading = false
|
||||||
private var noMoreRows = false
|
private var noMoreRows = false
|
||||||
private var loadGeneration = 0
|
private var loadGeneration = 0
|
||||||
@@ -75,7 +76,7 @@ class ViewLogsFragment : Fragment(R.layout.fragment_view_logs) {
|
|||||||
val context = requireContext().applicationContext
|
val context = requireContext().applicationContext
|
||||||
if (reset) {
|
if (reset) {
|
||||||
rows.clear()
|
rows.clear()
|
||||||
loadedEntries = 0
|
nextCursor = null
|
||||||
noMoreRows = false
|
noMoreRows = false
|
||||||
loadGeneration++
|
loadGeneration++
|
||||||
}
|
}
|
||||||
@@ -83,12 +84,12 @@ class ViewLogsFragment : Fragment(R.layout.fragment_view_logs) {
|
|||||||
loading = true
|
loading = true
|
||||||
binding?.logsRefresh?.isRefreshing = true
|
binding?.logsRefresh?.isRefreshing = true
|
||||||
Thread {
|
Thread {
|
||||||
val entries = EncryptedNotificationLogStore(context).readNewest(loadedEntries, PAGE_SIZE)
|
val page = EncryptedNotificationLogStore(context).readNewest(nextCursor, PAGE_SIZE)
|
||||||
activity?.runOnUiThread {
|
activity?.runOnUiThread {
|
||||||
if (generation == loadGeneration) {
|
if (generation == loadGeneration) {
|
||||||
loadedEntries += entries.size
|
nextCursor = page.nextCursor
|
||||||
noMoreRows = entries.size < PAGE_SIZE
|
noMoreRows = nextCursor == null
|
||||||
val addedRows = entries.map(::LogRow)
|
val addedRows = page.entries.map(::LogRow)
|
||||||
rows += addedRows
|
rows += addedRows
|
||||||
binding?.let(::render)
|
binding?.let(::render)
|
||||||
}
|
}
|
||||||
|
|||||||
+33
-12
@@ -11,6 +11,16 @@ import java.io.DataOutputStream
|
|||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileNotFoundException
|
import java.io.FileNotFoundException
|
||||||
|
|
||||||
|
data class NewestLogCursor(
|
||||||
|
val chunkName: String,
|
||||||
|
val entryIndex: Int,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class NotificationLogPage(
|
||||||
|
val entries: List<NotificationLogEntry>,
|
||||||
|
val nextCursor: NewestLogCursor?,
|
||||||
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Independently encrypted log chunks. Reading or appending a record needs only
|
* Independently encrypted log chunks. Reading or appending a record needs only
|
||||||
* one bounded chunk; older chunks stay on disk until requested by the viewer.
|
* one bounded chunk; older chunks stay on disk until requested by the viewer.
|
||||||
@@ -30,22 +40,33 @@ class EncryptedNotificationLogStore(context: Context) {
|
|||||||
chunkFiles().flatMap(::readChunk)
|
chunkFiles().flatMap(::readChunk)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun readNewest(offset: Int, limit: Int): List<NotificationLogEntry> = synchronized(lock) {
|
fun readNewest(cursor: NewestLogCursor?, limit: Int): NotificationLogPage = synchronized(lock) {
|
||||||
require(offset >= 0)
|
|
||||||
require(limit > 0)
|
require(limit > 0)
|
||||||
val result = ArrayList<NotificationLogEntry>(limit)
|
val result = ArrayList<NotificationLogEntry>(limit)
|
||||||
var skipped = 0
|
val chunks = chunkFiles().asReversed()
|
||||||
for (chunk in chunkFiles().asReversed()) {
|
var chunkIndex = cursor?.let { saved -> chunks.indexOfFirst { it.name == saved.chunkName } } ?: 0
|
||||||
readChunk(chunk).asReversed().forEach { entry ->
|
var entryIndex = cursor?.entryIndex ?: Int.MAX_VALUE
|
||||||
if (skipped < offset) {
|
if (chunkIndex < 0) return@synchronized NotificationLogPage(emptyList(), null)
|
||||||
skipped++
|
|
||||||
} else if (result.size < limit) {
|
while (chunkIndex < chunks.size) {
|
||||||
result += entry
|
val chunk = chunks[chunkIndex]
|
||||||
}
|
val entries = readChunk(chunk)
|
||||||
|
var index = minOf(entryIndex, entries.lastIndex)
|
||||||
|
while (index >= 0 && result.size < limit) {
|
||||||
|
result += entries[index--]
|
||||||
}
|
}
|
||||||
if (result.size == limit) break
|
if (result.size == limit) {
|
||||||
|
val next = if (index >= 0) {
|
||||||
|
NewestLogCursor(chunk.name, index)
|
||||||
|
} else {
|
||||||
|
chunks.getOrNull(chunkIndex + 1)?.let { NewestLogCursor(it.name, Int.MAX_VALUE) }
|
||||||
|
}
|
||||||
|
return@synchronized NotificationLogPage(result, next)
|
||||||
|
}
|
||||||
|
chunkIndex++
|
||||||
|
entryIndex = Int.MAX_VALUE
|
||||||
}
|
}
|
||||||
result.sortedByDescending { it.recordedAtEpochMillis }
|
NotificationLogPage(result, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user