View/logic improvements

This commit is contained in:
sergeych committed 2026-05-18 00:39:29 +03:00
1 parent da88a6e221
commit 26343cb8a1
10 files changed
+386 -25

No files matched your search

@@ -3,6 +3,9 @@ package net.sergeych.toread
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.toComposeImageBitmap
import net.sergeych.toread.fb2.Fb2Binary
import net.sergeych.toread.fb2.Fb2Book
import net.sergeych.toread.fb2.Fb2Format
import net.sergeych.toread.storage.BookRecord
import net.sergeych.toread.storage.BookReadingStatus
import net.sergeych.toread.storage.ContentAnchor
import net.sergeych.toread.storage.LibraryFileRecord
@@ -175,6 +178,45 @@ actual suspend fun openLibraryBook(fileId: String): ByteArray? = withContext(Dis
}
}
actual suspend fun refreshLibraryItemFromParsedBook(fileId: String, book: Fb2Book): LibraryItem? = withContext(Dispatchers.IO) {
openLibraryDatabase().useLibrary { db ->
db.refreshBookCardFromParsedBook(fileId, book).item
}
}
actual suspend fun rescanAllLibraryBooks(): LibraryRescanReport = withContext(Dispatchers.IO) {
appendLibraryLog("rescan all library requested")
openLibraryDatabase().useLibrary { db ->
var scanned = 0
var updated = 0
var failed = 0
db.files.list(Int.MAX_VALUE, 0).forEach { file ->
val bytes = file.storageUri?.let { File(it) }?.takeIf { it.isFile }?.readBytes()
if (bytes == null) {
failed += 1
appendLibraryLog("rescan missing fileId=${file.id} uri=${file.storageUri}")
return@forEach
}
scanned += 1
val parsed = runCatching {
Fb2Format.parse(bytes, file.originalFilename ?: file.storageUri ?: file.id)
}
parsed
.onSuccess {
if (db.refreshBookCardFromParsedBook(file.id, it).updated) {
updated += 1
}
}
.onFailure {
failed += 1
appendLibraryLog("rescan failed fileId=${file.id} error=${it.message}")
}
}
appendLibraryLog("rescan all library finished scanned=$scanned updated=$updated failed=$failed")
LibraryRescanReport(scannedFiles = scanned, updatedFiles = updated, failedFiles = failed)
}
}
actual suspend fun deleteLibraryItem(fileId: String): Boolean = withContext(Dispatchers.IO) {
appendLibraryLog("delete fileId=$fileId")
openLibraryDatabase().useLibrary { db ->
@@ -433,6 +475,58 @@ private fun LibraryFileRecord.toLibraryItem(): LibraryItem =
importedAt = importedAt,
)
private data class ParsedBookCover(
val bytes: ByteArray,
val mimeType: String?,
)
private data class BookCardRefresh(
val updated: Boolean,
val item: LibraryItem?,
)
private fun H2LibraryDatabase.refreshBookCardFromParsedBook(fileId: String, book: Fb2Book): BookCardRefresh {
val file = files.get(fileId) ?: return BookCardRefresh(updated = false, item = null)
val bookId = file.bookId ?: return BookCardRefresh(updated = false, item = null)
val stored = books.get(bookId) ?: return BookCardRefresh(updated = false, item = null)
val cover = book.libraryCardCover()
val next = stored.copy(
title = book.title.ifBlank { file.originalFilename?.substringBeforeLast('.') ?: stored.title.orEmpty() },
authors = book.authors.mapNotNull { it.displayName.takeIf(String::isNotBlank) },
language = book.language,
date = book.date,
description = book.annotation,
keywords = book.keywords,
coverImage = cover?.bytes,
coverImageMimeType = cover?.mimeType,
updatedAt = System.currentTimeMillis(),
)
val updated = !stored.hasSameCardMetadata(next)
if (updated) {
appendLibraryLog("refresh book card fileId=$fileId bookId=$bookId title=${next.title}")
books.upsert(next)
}
return BookCardRefresh(updated = updated, item = files.getLibraryFile(fileId)?.toLibraryItem())
}
private fun Fb2Book.libraryCardCover(): ParsedBookCover? {
val image = coverImages.firstOrNull() ?: bodyImages.firstOrNull()
val binary = image?.let(::binaryFor) ?: return null
return runCatching {
ParsedBookCover(bytes = binary.imageBytes(), mimeType = binary.contentType)
}.getOrNull()
}
private fun BookRecord.hasSameCardMetadata(other: BookRecord): Boolean =
title == other.title &&
authors == other.authors &&
language == other.language &&
date == other.date &&
description == other.description &&
keywords == other.keywords &&
coverImage.contentEquals(other.coverImage) &&
coverImageMimeType == other.coverImageMimeType
private fun libraryLogFile(): File =
File(System.getProperty("user.home"), ".toread/toread.log")
@@ -0,0 +1,10 @@
package net.sergeych.toread
import androidx.compose.runtime.Composable
@Composable
internal actual fun PlatformBackHandler(
enabled: Boolean,
navigationDepth: Int,
onBack: () -> Unit,
) = Unit