From 7cd3dfd99008330e749647fd402664cff31dbb1f Mon Sep 17 00:00:00 2001 From: Pierre-Yves Nicolas <6371790+pynicolas@users.noreply.github.com> Date: Sun, 29 Mar 2026 10:10:25 +0200 Subject: [PATCH] ImageRepository: make jpegBytes and getThumbnail suspend --- .../java/org/fairscan/app/data/FileManager.kt | 5 ++- .../org/fairscan/app/data/ImageRepository.kt | 7 ++-- .../fairscan/app/domain/ExportPreparation.kt | 42 ++++++++++++------- .../fairscan/app/platform/AndroidPdfWriter.kt | 5 ++- .../app/ui/screens/export/ExportViewModel.kt | 4 +- .../org/fairscan/app/data/FileManagerTest.kt | 10 +++-- 6 files changed, 43 insertions(+), 30 deletions(-) diff --git a/app/src/main/java/org/fairscan/app/data/FileManager.kt b/app/src/main/java/org/fairscan/app/data/FileManager.kt index 9f200d6..264c3fe 100644 --- a/app/src/main/java/org/fairscan/app/data/FileManager.kt +++ b/app/src/main/java/org/fairscan/app/data/FileManager.kt @@ -14,6 +14,7 @@ */ package org.fairscan.app.data +import org.fairscan.app.domain.JpegProvider import java.io.File import java.io.FileOutputStream import java.io.OutputStream @@ -25,7 +26,7 @@ data class GeneratedPdf( ) fun interface PdfWriter { - fun writePdfFromJpegs(jpegs: Sequence, outputStream: OutputStream): Int + suspend fun writePdfFromJpegs(jpegs: List, outputStream: OutputStream): Int } class FileManager( @@ -42,7 +43,7 @@ class FileManager( } } - fun generatePdf(jpegs: Sequence): GeneratedPdf { + suspend fun generatePdf(jpegs: List): GeneratedPdf { pdfDir.mkdirs() require(pdfDir.exists() && pdfDir.isDirectory) { "Invalid pdfDir: $pdfDir" } val file = File(pdfDir, "${System.currentTimeMillis()}.pdf") diff --git a/app/src/main/java/org/fairscan/app/data/ImageRepository.kt b/app/src/main/java/org/fairscan/app/data/ImageRepository.kt index f8d9e1d..e3f7286 100644 --- a/app/src/main/java/org/fairscan/app/data/ImageRepository.kt +++ b/app/src/main/java/org/fairscan/app/data/ImageRepository.kt @@ -170,13 +170,12 @@ class ImageRepository( saveMetadata() } - fun jpegBytes(key: PageViewKey): ByteArray? = runBlocking(Dispatchers.IO) { + suspend fun jpegBytes(key: PageViewKey): ByteArray? = getOrCompute(imageCache, key, ::computeProcessedImage) - } - fun getThumbnail(key: PageViewKey): ByteArray? = runBlocking(Dispatchers.IO) { + + suspend fun getThumbnail(key: PageViewKey): ByteArray? = getOrCompute(thumbnailCache, key, ::computeThumbnail) - } // --- Cache compute functions --- diff --git a/app/src/main/java/org/fairscan/app/domain/ExportPreparation.kt b/app/src/main/java/org/fairscan/app/domain/ExportPreparation.kt index 59d663a..8b6cdd8 100644 --- a/app/src/main/java/org/fairscan/app/domain/ExportPreparation.kt +++ b/app/src/main/java/org/fairscan/app/domain/ExportPreparation.kt @@ -22,36 +22,46 @@ import org.fairscan.imageprocessing.resizeForMaxPixels import org.fairscan.imageprocessing.scaledTo import org.opencv.core.Mat +fun interface JpegProvider { + suspend fun get(): ByteArray +} + suspend fun jpegsForExport( imageRepository: ImageRepository, exportQuality: ExportQuality -): Sequence { +): List { - val pages = imageRepository.pages().asSequence() + val pages = imageRepository.pages() return when (exportQuality) { - ExportQuality.BALANCED -> pages.map { jpegBytes(it, imageRepository) } + ExportQuality.BALANCED -> pages.map { + JpegProvider { jpegBytes(it, imageRepository) } + } ExportQuality.LOW -> pages.map { page -> - resizeJpegBytesForMaxPixels( - jpegBytes = jpegBytes(page, imageRepository), - maxPixels = exportQuality.maxPixels.toDouble(), - jpegQuality = exportQuality.jpegQuality - ) + JpegProvider { + resizeJpegBytesForMaxPixels( + jpegBytes = jpegBytes(page, imageRepository), + maxPixels = exportQuality.maxPixels.toDouble(), + jpegQuality = exportQuality.jpegQuality + ) + } } ExportQuality.HIGH -> pages.map { page -> - val sourceJpegBytes = imageRepository.sourceJpegBytes(page.id) - val pageMetadata = page.metadata - val manualRotation = page.manualRotation - if (sourceJpegBytes != null && pageMetadata != null) - prepareJpegForHigh(sourceJpegBytes, pageMetadata, manualRotation, exportQuality) - else - jpegBytes(page, imageRepository) + JpegProvider { + val sourceJpegBytes = imageRepository.sourceJpegBytes(page.id) + val pageMetadata = page.metadata + val manualRotation = page.manualRotation + if (sourceJpegBytes != null && pageMetadata != null) + prepareJpegForHigh(sourceJpegBytes, pageMetadata, manualRotation, exportQuality) + else + jpegBytes(page, imageRepository) + } } } } -private fun jpegBytes(page: ScanPage, imageRepository: ImageRepository): ByteArray { +private suspend fun jpegBytes(page: ScanPage, imageRepository: ImageRepository): ByteArray { val key = page.key() return imageRepository.jpegBytes(key) ?: throw IllegalArgumentException("JPEG not found for $key") diff --git a/app/src/main/java/org/fairscan/app/platform/AndroidPdfWriter.kt b/app/src/main/java/org/fairscan/app/platform/AndroidPdfWriter.kt index d38922a..3fff6e4 100644 --- a/app/src/main/java/org/fairscan/app/platform/AndroidPdfWriter.kt +++ b/app/src/main/java/org/fairscan/app/platform/AndroidPdfWriter.kt @@ -22,17 +22,18 @@ import com.tom_roush.pdfbox.pdmodel.common.PDRectangle import com.tom_roush.pdfbox.pdmodel.graphics.image.JPEGFactory import org.fairscan.app.BuildConfig import org.fairscan.app.data.PdfWriter +import org.fairscan.app.domain.JpegProvider import java.io.OutputStream import java.util.Calendar class AndroidPdfWriter : PdfWriter { - override fun writePdfFromJpegs(jpegs: Sequence, outputStream: OutputStream): Int { + override suspend fun writePdfFromJpegs(jpegs: List, outputStream: OutputStream): Int { val doc = PDDocument() doc.documentInformation.creationDate = Calendar.getInstance() doc.documentInformation.creator = "FairScan ${BuildConfig.VERSION_NAME}" doc.use { document -> for (jpegBytes in jpegs) { - val image = JPEGFactory.createFromByteArray(document, jpegBytes) + val image = JPEGFactory.createFromByteArray(document, jpegBytes.get()) val page = PDPage(PDRectangle(image.width.toFloat(), image.height.toFloat())) document.addPage(page) val contentStream = PDPageContentStream(document, page, AppendMode.OVERWRITE, false) diff --git a/app/src/main/java/org/fairscan/app/ui/screens/export/ExportViewModel.kt b/app/src/main/java/org/fairscan/app/ui/screens/export/ExportViewModel.kt index 381c7a4..c48adde 100644 --- a/app/src/main/java/org/fairscan/app/ui/screens/export/ExportViewModel.kt +++ b/app/src/main/java/org/fairscan/app/ui/screens/export/ExportViewModel.kt @@ -174,9 +174,9 @@ class ExportViewModel(container: AppContainer, val imageRepository: ImageReposit val jpegs = jpegsForExport(imageRepository, exportQuality) val timestamp = System.currentTimeMillis() preparationDir.mkdirs() - val files = jpegs.mapIndexed { index, bytes -> + val files = jpegs.mapIndexed { index, jpeg -> val file = File(preparationDir, "$timestamp-${index + 1}.jpg") - file.writeBytes(bytes) + file.writeBytes(jpeg.get()) file }.toList() val sizeInBytes = files.sumOf { it.length() } diff --git a/app/src/test/java/org/fairscan/app/data/FileManagerTest.kt b/app/src/test/java/org/fairscan/app/data/FileManagerTest.kt index 1df2be8..e4b2122 100644 --- a/app/src/test/java/org/fairscan/app/data/FileManagerTest.kt +++ b/app/src/test/java/org/fairscan/app/data/FileManagerTest.kt @@ -14,7 +14,9 @@ */ package org.fairscan.app.data +import kotlinx.coroutines.test.runTest import org.assertj.core.api.Assertions.assertThat +import org.fairscan.app.domain.JpegProvider import org.junit.Test import java.io.File import java.io.OutputStream @@ -68,16 +70,16 @@ class FileManagerTest { } @Test - fun generatePdf() { + fun generatePdf() = runTest { val fakePdfWriter = object : PdfWriter { - override fun writePdfFromJpegs(jpegs: Sequence, outputStream: OutputStream): Int { + override suspend fun writePdfFromJpegs(jpegs: List, outputStream: OutputStream): Int { val list = jpegs.toList() - list.forEach { bytes -> outputStream.write(bytes) } + list.forEach { bytes -> outputStream.write(bytes.get()) } return list.size } } val manager = FileManager(pdfDir, externalDir, fakePdfWriter) - val jpegs = listOf(byteArrayOf(0x01, 0x02), byteArrayOf(0x11)).asSequence() + val jpegs = listOf(byteArrayOf(0x01, 0x02), byteArrayOf(0x11)).map { JpegProvider { it } } val pdf = manager.generatePdf(jpegs) assertThat(pdf.pageCount).isEqualTo(2) assertThat(pdf.sizeInBytes).isEqualTo(3)