Added URL detection in reader, improved platform URL handling, library item auto-rescan, and file association support.
This commit is contained in:
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) {
|
||||
|
||||
Reference in new issue
Block a user