+image viewer, better dupes protection and background processing
This commit is contained in:
1 parent
14c9863a83
commit
ade4fb1896
18 files changed
+817
-127
No files matched your search
@@ -10,15 +10,26 @@ import net.sergeych.toread.storage.ReadingStateRecord
|
||||
import net.sergeych.toread.storage.jdbc.H2LibraryDatabase
|
||||
import net.sergeych.toread.storage.jdbc.LibraryScanner
|
||||
import org.jetbrains.skia.Image
|
||||
import java.awt.Toolkit
|
||||
import java.awt.datatransfer.DataFlavor
|
||||
import java.awt.datatransfer.Transferable
|
||||
import java.awt.datatransfer.UnsupportedFlavorException
|
||||
import java.io.File
|
||||
import java.io.ByteArrayInputStream
|
||||
import javax.imageio.ImageIO
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.swing.JFileChooser
|
||||
import kotlin.concurrent.thread
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
actual fun loadDefaultBookBytes(): ByteArray? {
|
||||
@@ -37,6 +48,22 @@ actual fun decodeBookImage(binary: Fb2Binary): ImageBitmap? =
|
||||
actual fun decodeImageBytes(bytes: ByteArray): ImageBitmap? =
|
||||
runCatching { Image.makeFromEncoded(bytes).toComposeImageBitmap() }.getOrNull()
|
||||
|
||||
actual suspend fun copyImageToClipboard(bytes: ByteArray, mimeType: String, label: String): Boolean = withContext(Dispatchers.IO) {
|
||||
runCatching {
|
||||
val image = ImageIO.read(ByteArrayInputStream(bytes)) ?: return@withContext false
|
||||
val transferable = object : Transferable {
|
||||
override fun getTransferDataFlavors(): Array<DataFlavor> = arrayOf(DataFlavor.imageFlavor)
|
||||
override fun isDataFlavorSupported(flavor: DataFlavor): Boolean = flavor == DataFlavor.imageFlavor
|
||||
override fun getTransferData(flavor: DataFlavor): Any {
|
||||
if (!isDataFlavorSupported(flavor)) throw UnsupportedFlavorException(flavor)
|
||||
return image
|
||||
}
|
||||
}
|
||||
Toolkit.getDefaultToolkit().systemClipboard.setContents(transferable, null)
|
||||
true
|
||||
}.getOrDefault(false)
|
||||
}
|
||||
|
||||
actual fun defaultLibraryScanPath(): String? = findProjectRoot()?.let { File(it, "test_books").absolutePath }
|
||||
|
||||
actual suspend fun loadPlatformOpenBookRequest(): PlatformOpenBookRequest? = null
|
||||
@@ -79,27 +106,53 @@ actual suspend fun loadLibraryItemCover(fileId: String): LibraryCover? = withCon
|
||||
actual suspend fun scanLibrarySubtree(
|
||||
path: String,
|
||||
onProgress: (LibraryScanProgress) -> Unit,
|
||||
): LibraryScanReport = withContext(Dispatchers.IO) {
|
||||
appendLibraryLog("scan requested path=$path")
|
||||
openLibraryDatabase().useLibrary { db ->
|
||||
val summary = LibraryScanner(db, ::appendLibraryLog).scanSubtree(File(path)) {
|
||||
onProgress(
|
||||
LibraryScanProgress(
|
||||
scannedFiles = it.scannedFiles,
|
||||
importedFiles = it.importedFiles,
|
||||
skippedFiles = it.skippedFiles,
|
||||
failedFiles = it.failedFiles,
|
||||
currentFile = it.currentFile,
|
||||
): LibraryScanReport = coroutineScope {
|
||||
withContext(Dispatchers.IO) {
|
||||
appendLibraryLog("scan requested path=$path")
|
||||
}
|
||||
val root = File(path)
|
||||
val totalFiles = AtomicInteger(-1)
|
||||
val latestProgress = AtomicReference<LibraryScanProgress?>(null)
|
||||
|
||||
fun emitProgress(progress: LibraryScanProgress) {
|
||||
val enriched = totalFiles.get().takeIf { it >= 0 }?.let { progress.copy(totalFiles = it) } ?: progress
|
||||
latestProgress.set(enriched)
|
||||
onProgress(enriched)
|
||||
}
|
||||
|
||||
val totalJob = async(Dispatchers.IO) {
|
||||
runCatching { LibraryScanner.countSupportedBookFiles(root) }.getOrNull()
|
||||
}
|
||||
val totalPublisher = launch(Dispatchers.IO) {
|
||||
val total = totalJob.await() ?: return@launch
|
||||
totalFiles.set(total)
|
||||
emitProgress(latestProgress.get()?.copy(totalFiles = total) ?: LibraryScanProgress(0, 0, 0, 0, totalFiles = total))
|
||||
}
|
||||
|
||||
val report = withContext(Dispatchers.IO) {
|
||||
openLibraryDatabase().useLibrary { db ->
|
||||
val summary = LibraryScanner(db, ::appendLibraryLog).scanSubtree(root) {
|
||||
emitProgress(
|
||||
LibraryScanProgress(
|
||||
scannedFiles = it.scannedFiles,
|
||||
importedFiles = it.importedFiles,
|
||||
skippedFiles = it.skippedFiles,
|
||||
failedFiles = it.failedFiles,
|
||||
currentFile = it.currentFile,
|
||||
)
|
||||
)
|
||||
}
|
||||
LibraryScanReport(
|
||||
scannedFiles = summary.scannedFiles,
|
||||
importedFiles = summary.importedFiles,
|
||||
skippedFiles = summary.skippedFiles,
|
||||
failedFiles = summary.failedFiles,
|
||||
totalFiles = totalFiles.get().takeIf { it >= 0 },
|
||||
)
|
||||
}
|
||||
LibraryScanReport(
|
||||
scannedFiles = summary.scannedFiles,
|
||||
importedFiles = summary.importedFiles,
|
||||
skippedFiles = summary.skippedFiles,
|
||||
failedFiles = summary.failedFiles,
|
||||
)
|
||||
}
|
||||
totalPublisher.join()
|
||||
report.copy(totalFiles = totalFiles.get().takeIf { it >= 0 } ?: report.totalFiles)
|
||||
}
|
||||
|
||||
actual suspend fun openLibraryBook(fileId: String): ByteArray? = withContext(Dispatchers.IO) {
|
||||
|
||||
Reference in new issue
Block a user