Added URL detection in reader, improved platform URL handling, library item auto-rescan, and file association support.

This commit is contained in:
sergeych committed 2026-05-25 20:14:33 +03:00
1 parent 2e9a52f4af
commit cde4d89eed
15 files changed
+367 -48

No files matched your search

@@ -26,6 +26,11 @@ data class LibraryScanSummary(
val currentFile: String? = null,
)
data class LibraryImportResult(
val fileId: String,
val imported: Boolean,
)
class LibraryScanner(
private val database: H2LibraryDatabase,
private val log: (String) -> Unit = {},
@@ -85,17 +90,29 @@ class LibraryScanner(
storageUri: String,
sizeBytes: Long?,
lastModifiedMillis: Long?,
): Boolean {
): Boolean =
importExternalFileAndGetResult(bytes, displayName, storageUri, sizeBytes, lastModifiedMillis).imported
fun importExternalFileAndGetResult(
bytes: ByteArray,
displayName: String,
storageUri: String,
sizeBytes: Long?,
lastModifiedMillis: Long?,
): LibraryImportResult {
val rawSha256 = bytes.sha256Hex()
if (sizeBytes != null && database.files.findByOriginalFilenameSizeAndRawSha256(displayName, sizeBytes, rawSha256).isNotEmpty()) {
return false
if (sizeBytes != null) {
database.files.findByOriginalFilenameSizeAndRawSha256(displayName, sizeBytes, rawSha256).firstOrNull()?.let {
return LibraryImportResult(fileId = it.id, imported = false)
}
}
val now = System.currentTimeMillis()
database.files.findPrimaryDuplicateTarget(bodyClusterId = null, bodyId = null, rawSha256 = rawSha256)?.let { duplicateTarget ->
val fileId = "file-${UUID.randomUUID()}"
database.files.upsert(
BookFileRecord(
id = "file-${UUID.randomUUID()}",
id = fileId,
bookId = duplicateTarget.bookId,
bodyId = duplicateTarget.bodyId,
bodyClusterId = duplicateTarget.bodyClusterId,
@@ -114,7 +131,7 @@ class LibraryScanner(
updatedAt = now,
)
)
return true
return LibraryImportResult(fileId = fileId, imported = true)
}
val book = Fb2Format.parse(bytes, displayName)
@@ -129,6 +146,7 @@ class LibraryScanner(
database.files.findPrimaryDuplicateTarget(bodyClusterId = clusterId, bodyId = bodyId, rawSha256 = rawSha256)
}
val bookId = duplicateTarget?.bookId ?: "book-${UUID.randomUUID()}"
val fileId = "file-${UUID.randomUUID()}"
database.transaction {
if (duplicateTarget == null || duplicateTarget.bookId == null) {
@@ -177,7 +195,7 @@ class LibraryScanner(
}
files.upsert(
BookFileRecord(
id = "file-${UUID.randomUUID()}",
id = fileId,
bookId = bookId,
bodyId = bodyId,
bodyClusterId = clusterId,
@@ -197,7 +215,7 @@ class LibraryScanner(
)
)
}
return true
return LibraryImportResult(fileId = fileId, imported = true)
}
private fun importLinkedFile(file: File): Boolean {
@@ -15,13 +15,14 @@ class LibraryScannerTest {
val db = H2LibraryDatabase.openMemory("scansFb2ZipSubtreeIntoLinkedLibraryRecords")
try {
val summary = LibraryScanner(db).scanSubtree(File(root, "test_books"))
val expectedFiles = LibraryScanner.countSupportedBookFiles(File(root, "test_books"))
assertEquals(1, summary.scannedFiles)
assertEquals(1, summary.importedFiles)
assertEquals(expectedFiles, summary.scannedFiles)
assertEquals(expectedFiles, summary.importedFiles)
assertEquals(0, summary.skippedFiles)
assertEquals(0, summary.failedFiles)
val file = db.files.list().single()
val file = db.files.list().first { it.storageUri?.endsWith(".fb2.zip") == true }
assertEquals("fb2.zip", file.format)
assertTrue(file.storageUri?.endsWith(".fb2.zip") == true)
val book = assertNotNull(file.bookId?.let(db.books::get))
@@ -31,7 +32,7 @@ class LibraryScannerTest {
val second = LibraryScanner(db).scanSubtree(File(root, "test_books"))
assertEquals(0, second.importedFiles)
assertEquals(1, second.skippedFiles)
assertEquals(expectedFiles, second.skippedFiles)
} finally {
db.close()
}
@@ -92,6 +93,36 @@ class LibraryScannerTest {
}
}
@Test
fun importExternalFileResultReturnsExistingFileForExactRepeat() {
val root = findProjectRoot()
val bytes = File(root, "test_books/Maraini_Zapiski-Terezy-Numy.G7vc8A.872381.fb2.zip").readBytes()
val db = H2LibraryDatabase.openMemory("importExternalFileResultReturnsExistingFileForExactRepeat")
try {
val scanner = LibraryScanner(db)
val first = scanner.importExternalFileAndGetResult(
bytes = bytes,
displayName = "opened.fb2.zip",
storageUri = "/tmp/opened.fb2.zip",
sizeBytes = bytes.size.toLong(),
lastModifiedMillis = 1L,
)
val second = scanner.importExternalFileAndGetResult(
bytes = bytes,
displayName = "opened.fb2.zip",
storageUri = "/tmp/opened.fb2.zip",
sizeBytes = bytes.size.toLong(),
lastModifiedMillis = 1L,
)
assertTrue(first.imported)
assertEquals(false, second.imported)
assertEquals(first.fileId, second.fileId)
} finally {
db.close()
}
}
private fun findProjectRoot(): File {
var current = File(System.getProperty("user.dir")).absoluteFile
while (true) {