Add library categories and favorites

This commit is contained in:
sergeych committed 2026-05-23 13:14:16 +03:00
1 parent d749352333
commit 02bde7e644
9 files changed
+263 -21

No files matched your search

@@ -17,6 +17,7 @@ enum class BookImportPolicy {
enum class BookReadingStatus {
NEW,
TO_READ,
READING,
READ,
NOT_INTERESTED,
@@ -109,6 +110,7 @@ data class BookFileRecord(
val lastModifiedMillis: Long? = null,
val lastSeenAt: Long? = null,
val readingStatus: BookReadingStatus = BookReadingStatus.NEW,
val favorite: Boolean = false,
val lastReadAt: Long? = null,
val createdAt: Long,
val updatedAt: Long,
@@ -128,6 +130,7 @@ data class LibraryFileRecord(
val storageUri: String? = null,
val lastSeenAt: Long? = null,
val readingStatus: BookReadingStatus = BookReadingStatus.NEW,
val favorite: Boolean = false,
val lastReadAt: Long? = null,
val importedAt: Long,
)
@@ -210,6 +213,7 @@ interface BookFileRepository {
fun list(limit: Int = 500, offset: Int = 0): List<BookFileRecord>
fun listForBook(bookId: String): List<BookFileRecord>
fun updateReadingStatus(id: String, status: BookReadingStatus): Boolean
fun updateFavorite(id: String, favorite: Boolean): Boolean
fun touchLastReadAt(id: String, lastReadAt: Long): Boolean
fun delete(id: String): Boolean
}
@@ -213,6 +213,7 @@ private fun migrate(connection: Connection) {
last_modified_millis BIGINT,
last_seen_at BIGINT,
reading_status VARCHAR NOT NULL DEFAULT 'NEW',
favorite BOOLEAN NOT NULL DEFAULT FALSE,
last_read_at BIGINT,
imported_at BIGINT NOT NULL,
created_at BIGINT NOT NULL,
@@ -225,6 +226,7 @@ private fun migrate(connection: Connection) {
)
statement.execute("ALTER TABLE book_files ADD COLUMN IF NOT EXISTS duplicate_of_file_id VARCHAR")
statement.execute("ALTER TABLE book_files ADD COLUMN IF NOT EXISTS reading_status VARCHAR NOT NULL DEFAULT 'NEW'")
statement.execute("ALTER TABLE book_files ADD COLUMN IF NOT EXISTS favorite BOOLEAN NOT NULL DEFAULT FALSE")
statement.execute("ALTER TABLE book_files ADD COLUMN IF NOT EXISTS last_read_at BIGINT")
statement.execute("ALTER TABLE book_files ADD COLUMN IF NOT EXISTS imported_at BIGINT")
connection.prepareStatement("UPDATE book_files SET imported_at = ? WHERE imported_at IS NULL").use { update ->
@@ -487,9 +489,9 @@ private class JdbcBookFileRepository(private val connection: Connection) : BookF
MERGE INTO book_files(
id, book_id, body_id, body_cluster_id, duplicate_of_file_id, raw_sha256, format,
mime_type, size_bytes, original_filename, storage_kind, storage_uri, content_object_id,
last_modified_millis, last_seen_at, reading_status, last_read_at, imported_at, created_at, updated_at
last_modified_millis, last_seen_at, reading_status, favorite, last_read_at, imported_at, created_at, updated_at
)
KEY(id) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
KEY(id) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""".trimIndent()
).use { statement ->
statement.setString(1, file.id)
@@ -508,10 +510,11 @@ private class JdbcBookFileRepository(private val connection: Connection) : BookF
statement.setLongOrNull(14, file.lastModifiedMillis)
statement.setLongOrNull(15, file.lastSeenAt)
statement.setString(16, file.readingStatus.name)
statement.setLongOrNull(17, file.lastReadAt)
statement.setLong(18, file.importedAt)
statement.setLong(19, file.createdAt)
statement.setLong(20, file.updatedAt)
statement.setBoolean(17, file.favorite)
statement.setLongOrNull(18, file.lastReadAt)
statement.setLong(19, file.importedAt)
statement.setLong(20, file.createdAt)
statement.setLong(21, file.updatedAt)
statement.executeUpdate()
}
}
@@ -536,6 +539,7 @@ private class JdbcBookFileRepository(private val connection: Connection) : BookF
f.storage_uri AS storage_uri,
f.last_seen_at AS last_seen_at,
f.reading_status AS reading_status,
f.favorite AS favorite,
f.last_read_at AS last_read_at,
f.imported_at AS imported_at
FROM book_files f
@@ -692,6 +696,7 @@ private class JdbcBookFileRepository(private val connection: Connection) : BookF
f.storage_uri AS storage_uri,
f.last_seen_at AS last_seen_at,
f.reading_status AS reading_status,
f.favorite AS favorite,
f.last_read_at AS last_read_at,
f.imported_at AS imported_at
FROM book_files f
@@ -732,6 +737,7 @@ private class JdbcBookFileRepository(private val connection: Connection) : BookF
f.storage_uri AS storage_uri,
f.last_seen_at AS last_seen_at,
f.reading_status AS reading_status,
f.favorite AS favorite,
f.last_read_at AS last_read_at,
f.imported_at AS imported_at
FROM book_files f
@@ -771,6 +777,7 @@ private class JdbcBookFileRepository(private val connection: Connection) : BookF
f.storage_uri AS storage_uri,
f.last_seen_at AS last_seen_at,
f.reading_status AS reading_status,
f.favorite AS favorite,
f.last_read_at AS last_read_at,
f.imported_at AS imported_at
FROM book_files f
@@ -847,6 +854,24 @@ private class JdbcBookFileRepository(private val connection: Connection) : BookF
}
}
override fun updateFavorite(id: String, favorite: Boolean): Boolean {
val now = System.currentTimeMillis()
return connection.prepareStatement(
"""
UPDATE book_files
SET favorite = ?, updated_at = ?
WHERE id = ?
OR body_cluster_id = (SELECT body_cluster_id FROM book_files WHERE id = ? AND body_cluster_id IS NOT NULL)
""".trimIndent()
).use { statement ->
statement.setBoolean(1, favorite)
statement.setLong(2, now)
statement.setString(3, id)
statement.setString(4, id)
statement.executeUpdate() > 0
}
}
override fun touchLastReadAt(id: String, lastReadAt: Long): Boolean {
return connection.prepareStatement(
"""
@@ -1085,6 +1110,7 @@ private fun ResultSet.toLibraryFileRecord() = LibraryFileRecord(
storageUri = getString("storage_uri"),
lastSeenAt = getLongOrNull("last_seen_at"),
readingStatus = getReadingStatus("reading_status"),
favorite = getBoolean("favorite"),
lastReadAt = getLongOrNull("last_read_at"),
importedAt = getLong("imported_at"),
)
@@ -1122,6 +1148,7 @@ private fun ResultSet.toBookFileRecord() = BookFileRecord(
lastModifiedMillis = getLongOrNull("last_modified_millis"),
lastSeenAt = getLongOrNull("last_seen_at"),
readingStatus = getReadingStatus("reading_status"),
favorite = getBoolean("favorite"),
lastReadAt = getLongOrNull("last_read_at"),
importedAt = getLong("imported_at"),
createdAt = getLong("created_at"),
@@ -122,6 +122,9 @@ class H2LibraryDatabaseTest {
assertEquals("2024", db.files.getLibraryFile("file-1")?.date)
assertEquals("image/jpeg", db.books.getCover("book-1")?.mimeType)
assertEquals(BookReadingStatus.NEW, db.files.getLibraryFile("file-1")?.readingStatus)
assertEquals(false, db.files.getLibraryFile("file-1")?.favorite)
assertEquals(true, db.files.updateFavorite("file-1", true))
assertEquals(true, db.files.getLibraryFile("file-1")?.favorite)
assertEquals(true, db.files.updateReadingStatus("file-1", BookReadingStatus.READING))
assertEquals(BookReadingStatus.READING, db.files.getLibraryFile("file-1")?.readingStatus)
assertEquals("body-1", db.bodies.findByExactTextHash("text-sha", 1)?.id)
@@ -299,6 +302,9 @@ class H2LibraryDatabaseTest {
assertEquals(true, db.files.updateReadingStatus("file-zip", BookReadingStatus.READING))
assertEquals(BookReadingStatus.READING, db.files.get("file-fb2")?.readingStatus)
assertEquals(BookReadingStatus.READING, db.files.get("file-zip")?.readingStatus)
assertEquals(true, db.files.updateFavorite("file-zip", true))
assertEquals(true, db.files.get("file-fb2")?.favorite)
assertEquals(true, db.files.get("file-zip")?.favorite)
db.close()
}