From 3c30a203241e92a97d43fc5d33c58a09d6063893 Mon Sep 17 00:00:00 2001 From: Sprite Date: Wed, 16 Sep 2026 19:01:54 +0000 Subject: [PATCH] Review fixes for waves 9-10; preload covers so Save stops waiting on them Orchestrator review of b600df6 and cf82cbc (the chain's gate only proved they built). Fixed: - Search: "In your library" now shows the merged set (owned editions reached only via an online ISBN were removed from "Online" and shown nowhere); the shelf filter applies to it and hidden owned matches are counted. - "Edit details" closes the online save sheet (left open, it invited a duplicate save). - A failed remembered-shelf write after a successful insert no longer reports "Couldn't save" (a retry would duplicate the book). - Library search no longer normalizes every book on every keystroke on the main thread. - Add-by-hand duplicate check and the scan sheet's save are guarded (no crash, no double-tap duplicate, CancellationException rethrown). Covers (design decided with the user): createBook used to download the cover before writing the row, so every save waited on the image host, and a failed download silently saved no cover file. The cover now downloads as soon as a sheet has its URL; Save waits for it if needed and copies the file into place. A failed download shows on the sheet with Retry, and Save becomes "Save without cover". Unsaved preloads are discarded, and leftovers from a killed process are swept at startup. 337 tests (was 308), 2 skipped, 0 failures; assembleDebug and verifyPaparazziDebug green. Co-Authored-By: Claude Opus 5 --- .../java/org/modg/bookshelf/AppContainer.kt | 8 + .../modg/bookshelf/BookshelfApplication.kt | 4 + .../data/metadata/TextNormalization.kt | 9 +- .../bookshelf/data/repo/BookRepository.kt | 85 ++++---- .../modg/bookshelf/data/repo/CoverPreload.kt | 164 ++++++++++++++ .../modg/bookshelf/ui/add/AddBookScreen.kt | 9 +- .../modg/bookshelf/ui/add/AddBookViewModel.kt | 94 ++++++-- .../ui/components/CoverPreloadNotice.kt | 42 ++++ .../bookshelf/ui/library/LibraryModels.kt | 16 +- .../bookshelf/ui/library/LibraryScreen.kt | 35 ++- .../bookshelf/ui/library/LibraryViewModel.kt | 135 ++++++++++-- .../bookshelf/ui/library/LocalBookMatcher.kt | 56 +++-- .../org/modg/bookshelf/ui/scan/ScanModels.kt | 3 + .../org/modg/bookshelf/ui/scan/ScanScreen.kt | 30 ++- .../modg/bookshelf/ui/scan/ScanViewModel.kt | 95 ++++++-- .../data/repo/BookRepositoryCoverTest.kt | 97 +++++++++ .../bookshelf/data/repo/CoverPreloadTest.kt | 206 ++++++++++++++++++ .../modg/bookshelf/data/repo/FakeCoverHost.kt | 68 ++++++ .../bookshelf/ui/add/AddBookViewModelTest.kt | 93 ++++++++ .../ui/library/LibraryViewModelTest.kt | 104 ++++++++- .../bookshelf/ui/scan/ScanViewModelTest.kt | 135 ++++++++++++ .../ui/screens/LibraryScreenPaparazziTest.kt | 27 ++- ...ry-online-save-sheet-cover-failed-dark.png | Bin 0 -> 25108 bytes ...y-online-save-sheet-cover-failed-light.png | Bin 0 -> 26333 bytes ...ight_library-search-both-sections-dark.png | Bin 39472 -> 38163 bytes ...ght_library-search-both-sections-light.png | Bin 42680 -> 41349 bytes docs/HANDOFF.md | 69 ++++++ logs/CHAIN-DONE | 1 + logs/WAVE10-DONE | 4 + 29 files changed, 1447 insertions(+), 142 deletions(-) create mode 100644 app/app/src/main/java/org/modg/bookshelf/data/repo/CoverPreload.kt create mode 100644 app/app/src/main/java/org/modg/bookshelf/ui/components/CoverPreloadNotice.kt create mode 100644 app/app/src/test/java/org/modg/bookshelf/data/repo/BookRepositoryCoverTest.kt create mode 100644 app/app/src/test/java/org/modg/bookshelf/data/repo/CoverPreloadTest.kt create mode 100644 app/app/src/test/java/org/modg/bookshelf/data/repo/FakeCoverHost.kt create mode 100644 app/app/src/test/snapshots/images/org.modg.bookshelf.ui.screens_LibraryScreenPaparazziTest_libraryOnlineSaveSheetCoverFailedLight_library-online-save-sheet-cover-failed-dark.png create mode 100644 app/app/src/test/snapshots/images/org.modg.bookshelf.ui.screens_LibraryScreenPaparazziTest_libraryOnlineSaveSheetCoverFailedLight_library-online-save-sheet-cover-failed-light.png create mode 100644 logs/CHAIN-DONE create mode 100644 logs/WAVE10-DONE diff --git a/app/app/src/main/java/org/modg/bookshelf/AppContainer.kt b/app/app/src/main/java/org/modg/bookshelf/AppContainer.kt index 7040ceb..25baf8b 100644 --- a/app/app/src/main/java/org/modg/bookshelf/AppContainer.kt +++ b/app/app/src/main/java/org/modg/bookshelf/AppContainer.kt @@ -1,6 +1,7 @@ package org.modg.bookshelf import android.content.Context +import java.io.File import java.util.concurrent.TimeUnit import kotlinx.coroutines.flow.first import kotlinx.coroutines.runBlocking @@ -12,6 +13,7 @@ import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor import org.modg.bookshelf.data.local.BookshelfDatabase import org.modg.bookshelf.data.metadata.BookSearchRepository +import org.modg.bookshelf.data.repo.CoverDownloader import org.modg.bookshelf.data.metadata.MetadataRepository import org.modg.bookshelf.diagnostics.CrashReporter import org.modg.bookshelf.data.prefs.SettingsStore @@ -121,6 +123,12 @@ class AppContainer(private val context: Context) { val metadataRepository by lazy { MetadataRepository(metadataHttpClient, json, BuildConfig.GOOGLE_BOOKS_API_KEY) } + /** + * Cover preloads for the save sheets. [metadataHttpClient], not [okHttpClient]: covers + * come from third-party image hosts, which must never see our PocketBase token. + */ + val coverDownloader by lazy { CoverDownloader(metadataHttpClient, File(context.cacheDir, "cover-preload")) } + /** Library screen's online title/author search — shares the same client/timeouts/key as [metadataRepository]. */ val bookSearchRepository by lazy { BookSearchRepository(metadataHttpClient, json, BuildConfig.GOOGLE_BOOKS_API_KEY) } diff --git a/app/app/src/main/java/org/modg/bookshelf/BookshelfApplication.kt b/app/app/src/main/java/org/modg/bookshelf/BookshelfApplication.kt index b7e220b..a4e71e9 100644 --- a/app/app/src/main/java/org/modg/bookshelf/BookshelfApplication.kt +++ b/app/app/src/main/java/org/modg/bookshelf/BookshelfApplication.kt @@ -16,5 +16,9 @@ class BookshelfApplication : Application() { // See CrashReporter's KDoc: born from an on-device crash right after // the previous commit that could never be reproduced off-device. appContainer.crashReporter.install() + // Preloaded covers from a process that died with a save sheet open. Off the main + // thread (file listing); files this process creates are newer and never swept. + val processStart = System.currentTimeMillis() + Thread { appContainer.coverDownloader.sweepStale(olderThanMillis = processStart) }.start() } } diff --git a/app/app/src/main/java/org/modg/bookshelf/data/metadata/TextNormalization.kt b/app/app/src/main/java/org/modg/bookshelf/data/metadata/TextNormalization.kt index 603d06f..fe0c8b6 100644 --- a/app/app/src/main/java/org/modg/bookshelf/data/metadata/TextNormalization.kt +++ b/app/app/src/main/java/org/modg/bookshelf/data/metadata/TextNormalization.kt @@ -12,6 +12,11 @@ import java.text.Normalizer object TextNormalization { private val leadingArticles = setOf("the", "a", "an") + // Compiled once: the library search normalizes every owned book's fields, so an inline + // Regex(...) here was being recompiled thousands of times per keystroke. + private val combiningMarks = Regex("\\p{Mn}+") + private val whitespaceRun = Regex("\\s+") + /** * Lowercase, diacritics stripped (NFD decompose + drop combining marks), every * non-alphanumeric character turned into a space, whitespace collapsed. "J.R.R." @@ -19,9 +24,9 @@ object TextNormalization { */ fun normalizeForMatching(raw: String): String { val decomposed = Normalizer.normalize(raw, Normalizer.Form.NFD) - val noDiacritics = decomposed.replace(Regex("\\p{Mn}+"), "") + val noDiacritics = decomposed.replace(combiningMarks, "") val spaced = noDiacritics.map { c -> if (c.isLetterOrDigit()) c else ' ' }.joinToString("") - return spaced.lowercase().trim().replace(Regex("\\s+"), " ") + return spaced.lowercase().trim().replace(whitespaceRun, " ") } fun tokens(raw: String): List { diff --git a/app/app/src/main/java/org/modg/bookshelf/data/repo/BookRepository.kt b/app/app/src/main/java/org/modg/bookshelf/data/repo/BookRepository.kt index 6843731..de8d2a6 100644 --- a/app/app/src/main/java/org/modg/bookshelf/data/repo/BookRepository.kt +++ b/app/app/src/main/java/org/modg/bookshelf/data/repo/BookRepository.kt @@ -7,14 +7,11 @@ import kotlinx.coroutines.withContext import kotlinx.serialization.json.Json import kotlinx.serialization.encodeToString import kotlinx.serialization.decodeFromString -import okhttp3.OkHttpClient -import okhttp3.Request import org.modg.bookshelf.data.local.BookDao import org.modg.bookshelf.data.local.BookEntity import org.modg.bookshelf.data.local.IdGenerator import org.modg.bookshelf.data.local.SyncState import java.io.File -import java.io.IOException private val authorsJson = Json { ignoreUnknownKeys = true } @@ -31,7 +28,6 @@ fun decodeAuthors(json: String): List = class BookRepository( private val bookDao: BookDao, private val context: Context, - private val downloadClient: OkHttpClient = OkHttpClient(), ) { fun observeAll(): Flow> = bookDao.observeAll() fun observeAllByRecentlyAdded(): Flow> = bookDao.observeAllByRecentlyAdded() @@ -44,10 +40,13 @@ class BookRepository( suspend fun countByShelf(shelfId: String): Int = bookDao.countByShelf(shelfId) /** - * Creates a new book. If [coverSourceUrl] is set, attempts to download the - * cover to app-private storage now (best-effort; failures are swallowed — - * the SyncEngine will simply have nothing to upload, and the UI still has - * [coverSourceUrl] to fall back on for display). + * Creates a new book. [coverFile] is a cover the caller already downloaded (see + * [CoverPreload]); it is COPIED into app-private storage as [BookEntity.localCoverPath] + * for SyncEngine to upload. This used to download [coverSourceUrl] itself before + * writing the row, which made every save wait on the image host and silently saved + * the book with no cover file when that download failed. The caller keeps ownership + * of [coverFile] (a copy, not a move, so a failed write leaves the preload intact for + * a retry). [coverSourceUrl] is still recorded — the UI falls back to it for display. */ suspend fun createBook( title: String, @@ -60,35 +59,42 @@ class BookRepository( pageCount: Int? = null, description: String? = null, coverSourceUrl: String? = null, + coverFile: File? = null, shelfId: String? = null, notes: String? = null, addedBy: String? = null, ): String { val id = IdGenerator.newId() val now = System.currentTimeMillis() - val localCoverPath = coverSourceUrl?.let { downloadCoverBestEffort(id, it) } - bookDao.upsert( - BookEntity( - id = id, - title = title, - subtitle = subtitle, - authorsJson = encodeAuthors(authors), - isbn13 = isbn13, - isbn10 = isbn10, - publisher = publisher, - publishedDate = publishedDate, - pageCount = pageCount, - description = description, - coverSourceUrl = coverSourceUrl, - shelfId = shelfId, - notes = notes, - addedBy = addedBy, - createdAt = now, - updatedAt = now, - syncState = SyncState.PENDING_CREATE, - localCoverPath = localCoverPath, - ), - ) + val localCover = coverFile?.let { copyCover(id, it) } + try { + bookDao.upsert( + BookEntity( + id = id, + title = title, + subtitle = subtitle, + authorsJson = encodeAuthors(authors), + isbn13 = isbn13, + isbn10 = isbn10, + publisher = publisher, + publishedDate = publishedDate, + pageCount = pageCount, + description = description, + coverSourceUrl = coverSourceUrl, + shelfId = shelfId, + notes = notes, + addedBy = addedBy, + createdAt = now, + updatedAt = now, + syncState = SyncState.PENDING_CREATE, + localCoverPath = localCover?.absolutePath, + ), + ) + } catch (e: Throwable) { + // No row points at the copy, so nothing would ever upload or delete it. + localCover?.delete() + throw e + } return id } @@ -131,19 +137,8 @@ class BookRepository( ) } - private suspend fun downloadCoverBestEffort(id: String, url: String): String? = withContext(Dispatchers.IO) { - try { - val request = Request.Builder().url(url).build() - downloadClient.newCall(request).execute().use { response -> - if (!response.isSuccessful) return@withContext null - val bytes = response.body.bytes() - val dir = File(context.filesDir, "covers").apply { mkdirs() } - val file = File(dir, "$id.jpg") - file.writeBytes(bytes) - file.absolutePath - } - } catch (e: IOException) { - null - } + private suspend fun copyCover(id: String, source: File): File = withContext(Dispatchers.IO) { + val dir = File(context.filesDir, "covers").apply { mkdirs() } + source.copyTo(File(dir, "$id.jpg"), overwrite = true) } } diff --git a/app/app/src/main/java/org/modg/bookshelf/data/repo/CoverPreload.kt b/app/app/src/main/java/org/modg/bookshelf/data/repo/CoverPreload.kt new file mode 100644 index 0000000..e078b88 --- /dev/null +++ b/app/app/src/main/java/org/modg/bookshelf/data/repo/CoverPreload.kt @@ -0,0 +1,164 @@ +package org.modg.bookshelf.data.repo + +import java.io.File +import java.io.IOException +import java.util.UUID +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import okhttp3.OkHttpClient +import okhttp3.Request +import org.modg.bookshelf.data.metadata.SourceResult + +/** Outcome of one cover download. [Failed.reason] is safe to show the user. */ +sealed interface CoverFetch { + data class Ready(val file: File) : CoverFetch + data class Failed(val reason: String) : CoverFetch +} + +/** + * Downloads a cover into [preloadDir] so it can be handed to [BookRepository.createBook] + * at save time. Save used to download the cover itself, BEFORE writing the row, so every + * save sat waiting on the image host (covers.openlibrary.org redirects to archive.org and + * is often slow) even though the sheet was already showing that very cover. Downloading + * as soon as the cover URL is known means the file is usually there by the time Save is + * tapped, and a failure can be shown while the user is still on the sheet. + * + * Not Coil's disk cache: that's Coil's private performance detail (eviction, cache keys, + * format can all change), and we need a file we own to upload to the server. + */ +class CoverDownloader( + private val httpClient: OkHttpClient, + private val preloadDir: File, + private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO, +) { + /** Never throws except [CancellationException]; no file is left behind on any non-success path. */ + suspend fun download(url: String): CoverFetch { + preloadDir.mkdirs() + val file = File(preloadDir, UUID.randomUUID().toString()) + return try { + withContext(ioDispatcher) { fetchInto(file, url) } + } catch (e: CancellationException) { + // Also covers a cancel that lands AFTER the bytes were written: withContext then + // throws on its way out, past any catch inside the block. + file.delete() + throw e + } + } + + private fun fetchInto(file: File, url: String): CoverFetch = try { + val request = Request.Builder().url(url).build() + httpClient.newCall(request).execute().use { response -> + val bytes = if (response.isSuccessful) response.body.bytes() else null + when { + bytes == null -> CoverFetch.Failed(SourceResult.fromHttpCode(response.code).reason) + bytes.isEmpty() -> CoverFetch.Failed("empty image") + else -> { + file.writeBytes(bytes) + CoverFetch.Ready(file) + } + } + } + } catch (e: IOException) { + file.delete() + CoverFetch.Failed(SourceResult.fromException(e).reason) + } catch (e: Throwable) { + file.delete() + CoverFetch.Failed("unexpected: ${e.javaClass.simpleName}") + } + + /** + * Deletes preloads left behind by a previous process (killed or crashed with a sheet + * open). Only files older than [olderThanMillis] — the current process's start — so a + * preload started by this process can never be swept out from under its sheet. + */ + fun sweepStale(olderThanMillis: Long) { + preloadDir.listFiles()?.forEach { if (it.lastModified() < olderThanMillis) it.delete() } + } +} + +/** What a sheet/screen shows about its cover download. Every non-[None] state carries the URL it is for. */ +sealed interface CoverPreloadState { + data object None : CoverPreloadState + data class Loading(val url: String) : CoverPreloadState + data class Ready(val url: String, val file: File) : CoverPreloadState + data class Failed(val url: String, val reason: String) : CoverPreloadState +} + +/** + * At most ONE cover download for one owner (a sheet or screen) — it starts when a result is + * opened, never for every row in a result list. Owns the downloaded file until [discard]: + * call it on Skip/dismiss/leaving the screen AND after a successful save (createBook copies + * the file), so nothing is left behind for books that weren't saved. + */ +class CoverPreload( + private val downloader: CoverDownloader, + private val scope: CoroutineScope, +) { + private val _state = MutableStateFlow(CoverPreloadState.None) + val state: StateFlow = _state.asStateFlow() + private var job: Job? = null + + /** Starts downloading [url]; a no-op if that URL is already loading or ready. Null/blank clears. */ + fun start(url: String?) { + if (url.isNullOrBlank()) { + discard() + return + } + when (val current = _state.value) { + is CoverPreloadState.Loading -> if (current.url == url) return + is CoverPreloadState.Ready -> if (current.url == url) return + else -> Unit + } + discard() + val loading = CoverPreloadState.Loading(url) + _state.value = loading + job = scope.launch { + val result = downloader.download(url) + if (_state.value != loading) { + // Superseded or discarded while downloading — the file is nobody's. + (result as? CoverFetch.Ready)?.file?.delete() + return@launch + } + _state.value = when (result) { + is CoverFetch.Ready -> CoverPreloadState.Ready(url, result.file) + is CoverFetch.Failed -> CoverPreloadState.Failed(url, result.reason) + } + } + } + + fun retry() { + val failed = _state.value as? CoverPreloadState.Failed ?: return + discard() + start(failed.url) + } + + /** + * Waits for any in-flight download to finish. A Ready file that has since vanished + * (the preload lives in the cache dir, which Android may clear) is downloaded again. + */ + suspend fun awaitSettled(): CoverPreloadState { + val settled = _state.first { it !is CoverPreloadState.Loading } + if (settled is CoverPreloadState.Ready && !settled.file.exists()) { + discard() + start(settled.url) + return _state.first { it !is CoverPreloadState.Loading } + } + return settled + } + + fun discard() { + job?.cancel() + job = null + (_state.value as? CoverPreloadState.Ready)?.file?.delete() + _state.value = CoverPreloadState.None + } +} diff --git a/app/app/src/main/java/org/modg/bookshelf/ui/add/AddBookScreen.kt b/app/app/src/main/java/org/modg/bookshelf/ui/add/AddBookScreen.kt index 992d947..00997a8 100644 --- a/app/app/src/main/java/org/modg/bookshelf/ui/add/AddBookScreen.kt +++ b/app/app/src/main/java/org/modg/bookshelf/ui/add/AddBookScreen.kt @@ -37,9 +37,11 @@ import androidx.lifecycle.viewmodel.initializer import androidx.lifecycle.viewmodel.viewModelFactory import org.modg.bookshelf.AppContainer import org.modg.bookshelf.ui.components.BookshelfScaffold +import org.modg.bookshelf.ui.components.CoverPreloadNotice import org.modg.bookshelf.ui.components.PaperSurface import org.modg.bookshelf.ui.components.PrimaryButton import org.modg.bookshelf.ui.components.SecondaryButton +import org.modg.bookshelf.ui.components.saveButtonLabel import org.modg.bookshelf.ui.scan.DuplicateStatus import org.modg.bookshelf.ui.scan.ShelfPicker @@ -71,6 +73,7 @@ fun AddBookScreen( bookRepository = container.bookRepository, locationRepository = container.locationRepository, settingsStore = container.settingsStore, + coverDownloader = container.coverDownloader, initialDraft = BookDraft.fromRouteArgs( title = initialTitle, isbn = initialIsbn, @@ -103,6 +106,7 @@ fun AddBookScreen( onSave = { viewModel.save(onSaved) }, onSaveAndAddAnother = viewModel::saveAndAddAnother, onBack = onBack, + onRetryCover = viewModel::retryCover, ), ) } @@ -127,6 +131,7 @@ data class AddBookCallbacks( val onSave: () -> Unit, val onSaveAndAddAnother: () -> Unit, val onBack: () -> Unit, + val onRetryCover: () -> Unit = {}, ) @Composable @@ -304,6 +309,8 @@ fun AddBookContent(state: AddBookUiState, callbacks: AddBookCallbacks, modifier: onShelfSelected = callbacks.onShelfSelected, ) + CoverPreloadNotice(state = state.cover, onRetry = callbacks.onRetryCover) + if (state.saveError != null) { Text( text = "Couldn't save: ${state.saveError}", @@ -324,7 +331,7 @@ fun AddBookContent(state: AddBookUiState, callbacks: AddBookCallbacks, modifier: modifier = Modifier.weight(1f), ) PrimaryButton( - text = if (state.isSaving) "Saving…" else "Save", + text = saveButtonLabel(state.isSaving, state.cover), enabled = state.canSave, onClick = callbacks.onSave, modifier = Modifier.weight(1f), diff --git a/app/app/src/main/java/org/modg/bookshelf/ui/add/AddBookViewModel.kt b/app/app/src/main/java/org/modg/bookshelf/ui/add/AddBookViewModel.kt index 96f8dbc..ea8527c 100644 --- a/app/app/src/main/java/org/modg/bookshelf/ui/add/AddBookViewModel.kt +++ b/app/app/src/main/java/org/modg/bookshelf/ui/add/AddBookViewModel.kt @@ -16,6 +16,9 @@ import org.modg.bookshelf.data.local.BookcaseEntity import org.modg.bookshelf.data.local.ShelfEntity import org.modg.bookshelf.data.prefs.SettingsStore import org.modg.bookshelf.data.repo.BookRepository +import org.modg.bookshelf.data.repo.CoverDownloader +import org.modg.bookshelf.data.repo.CoverPreload +import org.modg.bookshelf.data.repo.CoverPreloadState import org.modg.bookshelf.data.repo.LocationRepository import org.modg.bookshelf.ui.scan.DuplicateCheck import org.modg.bookshelf.ui.scan.DuplicateStatus @@ -32,6 +35,7 @@ data class AddBookUiState( val saveError: String? = null, val savedCount: Int = 0, val lastSavedTitle: String? = null, + val cover: CoverPreloadState = CoverPreloadState.None, ) { val isbnError: String? get() = if (validateIsbn(draft.isbn) is IsbnFieldState.Invalid) { @@ -57,6 +61,7 @@ class AddBookViewModel( private val bookRepository: BookRepository, locationRepository: LocationRepository, private val settingsStore: SettingsStore, + coverDownloader: CoverDownloader, initialDraft: BookDraft = BookDraft(), ) : ViewModel() { @@ -71,6 +76,12 @@ class AddBookViewModel( ) private val _formState = MutableStateFlow(FormState(draft = initialDraft)) + + /** + * Only a draft carried in from an online search hit ("Edit details") has a cover URL — + * there's no cover field on this form — so this is usually [CoverPreloadState.None]. + */ + private val coverPreload = CoverPreload(coverDownloader, viewModelScope) private var duplicateCheckJob: Job? = null val bookcases: StateFlow> = locationRepository.observeBookcases() @@ -87,8 +98,8 @@ class AddBookViewModel( // collecting (e.g. Compose's collectAsState, or a test using .first()); a bare // .value read with no live collector sees only the seed passed to stateIn. val uiState: StateFlow = combine( - _formState, bookcases, shelves, recentShelfId, - ) { form, cases, shelfList, recent -> + _formState, bookcases, shelves, recentShelfId, coverPreload.state, + ) { form, cases, shelfList, recent, cover -> AddBookUiState( draft = form.draft, bookcases = cases, @@ -100,6 +111,7 @@ class AddBookViewModel( saveError = form.saveError, savedCount = form.savedCount, lastSavedTitle = form.lastSavedTitle, + cover = cover, ) }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), AddBookUiState(draft = initialDraft)) @@ -113,8 +125,11 @@ class AddBookViewModel( if (remembered != null) _formState.update { it.copy(selectedShelfId = remembered) } } if (initialDraft.isbn.isNotBlank()) scheduleDuplicateCheck(initialDraft.isbn) + coverPreload.start(initialDraft.coverSourceUrl) } + fun retryCover() = coverPreload.retry() + fun onTitleChanged(value: String) = updateDraft { it.copy(title = value) } fun onSubtitleChanged(value: String) = updateDraft { it.copy(subtitle = value) } fun onAuthorsChanged(value: String) = updateDraft { it.copy(authors = value) } @@ -145,8 +160,16 @@ class AddBookViewModel( return } duplicateCheckJob = viewModelScope.launch { - val existing = bookRepository.findByIsbn13(isbn13) - _formState.update { it.copy(duplicate = DuplicateCheck.check(existing)) } + // A Room failure here would otherwise crash the app from a keystroke. The check + // is only a warning, so on failure there is simply no warning. + val duplicate = try { + DuplicateCheck.check(bookRepository.findByIsbn13(isbn13)) + } catch (e: CancellationException) { + throw e + } catch (e: Throwable) { + DuplicateStatus.New + } + _formState.update { it.copy(duplicate = duplicate) } } } @@ -177,17 +200,26 @@ class AddBookViewModel( */ internal suspend fun performSave(clearAfterSave: Boolean): String? { if (_formState.value.isSaving) return null + val coverAtTap = coverPreload.state.value _formState.update { it.copy(isSaving = true, saveError = null) } val form = _formState.value val draft = form.draft - return try { + val savedTitle = draft.title.trim() + val bookId = try { + // Same cover rule as the scan sheet (see ScanViewModel.performSave): a download + // that fails while Save waits stops the save; one that had already failed when + // Save (labelled "Save without cover") was tapped does not. + val cover = coverPreload.awaitSettled() + if (cover is CoverPreloadState.Failed && coverAtTap !is CoverPreloadState.Failed) { + _formState.update { it.copy(isSaving = false) } + return null + } val isbnValidation = validateIsbn(draft.isbn) val isbn13 = (isbnValidation as? IsbnFieldState.Valid)?.isbn13 val isbn10 = (isbnValidation as? IsbnFieldState.Valid)?.isbn10 val pageCount = (validatePages(draft.pages) as? PagesFieldState.Valid)?.pages val authors = draft.authors.split(",").map(String::trim).filter(String::isNotEmpty) - val savedTitle = draft.title.trim() - val bookId = bookRepository.createBook( + bookRepository.createBook( title = savedTitle, subtitle = draft.subtitle.trim().ifEmpty { null }, authors = authors, @@ -198,30 +230,46 @@ class AddBookViewModel( pageCount = pageCount, description = draft.description.trim().ifEmpty { null }, coverSourceUrl = draft.coverSourceUrl.trim().ifEmpty { null }, + coverFile = (cover as? CoverPreloadState.Ready)?.file, shelfId = form.selectedShelfId, ) - rememberShelf(form.selectedShelfId) - _formState.update { - it.copy( - draft = if (clearAfterSave) BookDraft() else it.draft, - isSaving = false, - saveError = null, - savedCount = it.savedCount + 1, - lastSavedTitle = savedTitle, - ) - } - if (clearAfterSave) scheduleDuplicateCheck("") // the ISBN field just cleared; the warning must too - bookId } catch (e: CancellationException) { + _formState.update { it.copy(isSaving = false) } throw e } catch (e: Throwable) { _formState.update { it.copy(isSaving = false, saveError = e.javaClass.simpleName) } - null + return null + } + // Everything below runs only once the book is written. None of it may report a + // failure: "couldn't save" for a saved book invites a retry that creates a duplicate. + coverPreload.discard() // createBook copied it; "add another" starts a draft with no cover + rememberShelf(form.selectedShelfId) + _formState.update { + it.copy( + draft = if (clearAfterSave) BookDraft() else it.draft, + isSaving = false, + saveError = null, + savedCount = it.savedCount + 1, + lastSavedTitle = savedTitle, + ) + } + if (clearAfterSave) scheduleDuplicateCheck("") // the ISBN field just cleared; the warning must too + return bookId + } + + /** A remembered-shelf write that fails is swallowed — the book is already saved (see [performSave]). */ + private suspend fun rememberShelf(shelfId: String?) { + if (shelfId == null) return // "Not shelved" must never overwrite the memory — it isn't a shelf + try { + settingsStore.setLastShelfId(shelfId) + } catch (e: CancellationException) { + throw e + } catch (e: Throwable) { + // Losing the "recent shelf" shortcut is not worth an error on a saved book. } } - /** "Not shelved" (null) must never overwrite the memory — it isn't a shelf. */ - private suspend fun rememberShelf(shelfId: String?) { - if (shelfId != null) settingsStore.setLastShelfId(shelfId) + override fun onCleared() { + coverPreload.discard() } } diff --git a/app/app/src/main/java/org/modg/bookshelf/ui/components/CoverPreloadNotice.kt b/app/app/src/main/java/org/modg/bookshelf/ui/components/CoverPreloadNotice.kt new file mode 100644 index 0000000..9e126fd --- /dev/null +++ b/app/app/src/main/java/org/modg/bookshelf/ui/components/CoverPreloadNotice.kt @@ -0,0 +1,42 @@ +package org.modg.bookshelf.ui.components + +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import org.modg.bookshelf.data.repo.CoverPreloadState + +/** + * The one visible trace of a cover preload: nothing while it loads or once it's ready (the + * sheet already shows the cover via Coil), and a failure line with Retry when it failed — + * so the user finds out while still on the sheet, not after the book is saved without one. + */ +@Composable +fun CoverPreloadNotice(state: CoverPreloadState, onRetry: () -> Unit, modifier: Modifier = Modifier) { + if (state !is CoverPreloadState.Failed) return + Row(modifier = modifier.fillMaxWidth().padding(top = 8.dp), verticalAlignment = Alignment.CenterVertically) { + Text( + text = "Couldn't download the cover — ${state.reason}", + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.error, + modifier = Modifier.weight(1f), + ) + TextButton(onClick = onRetry) { Text("Retry") } + } +} + +/** + * Save's label. After a failed cover download, saving goes ahead without the cover file + * (the user decided: retry OR save without it, never stuck mid-box), so the button says so. + */ +fun saveButtonLabel(isSaving: Boolean, cover: CoverPreloadState, idle: String = "Save"): String = when { + isSaving -> "Saving…" + cover is CoverPreloadState.Failed -> "$idle without cover" + else -> idle +} diff --git a/app/app/src/main/java/org/modg/bookshelf/ui/library/LibraryModels.kt b/app/app/src/main/java/org/modg/bookshelf/ui/library/LibraryModels.kt index a819e97..b54d089 100644 --- a/app/app/src/main/java/org/modg/bookshelf/ui/library/LibraryModels.kt +++ b/app/app/src/main/java/org/modg/bookshelf/ui/library/LibraryModels.kt @@ -68,12 +68,15 @@ sealed interface OnlineSearchState { data class Searching(val query: String) : OnlineSearchState /** - * [inLibrary] is the FULL "In your library" set once a search has run — a superset - * of [LocalBookMatcher]'s plain text match, since [org.modg.bookshelf.data.metadata.SearchResultMerger] - * also pulls in an owned book reached only via matching one of the online hits (task: - * "an online hit that is a different edition of an owned book belongs in the library - * section"). [openLibraryFailure]/[googleBooksFailure] are that source's - * [org.modg.bookshelf.data.metadata.SourceResult.Failed.reason]-equivalent, null when + * [inLibrary] is what the "In your library" section shows once a search has run — a + * superset of [LocalBookMatcher]'s plain text match, since [org.modg.bookshelf.data.metadata.SearchResultMerger] + * also pulls in an owned book reached only via one of the online hits (task: "an + * online hit that is a different edition of an owned book belongs in the library + * section"). The shelf/bookcase filter and sort are applied to it; owned matches the + * filter hides are counted in [inLibraryOutsideFilter] instead of silently vanishing — + * the merge still (correctly) keeps them out of the online section, so without the count + * a book you own on another shelf would appear in neither. + * [openLibraryFailure]/[googleBooksFailure] are that source's failure reason, null when * that source answered (whether or not it had anything) — the UI keys off these to * show a per-source failure line with Retry while still showing the other source's * [online] results, and to tell "both failed" (retry affordance, never "no results") @@ -85,6 +88,7 @@ sealed interface OnlineSearchState { val online: List, val openLibraryFailure: String?, val googleBooksFailure: String?, + val inLibraryOutsideFilter: Int = 0, ) : OnlineSearchState } diff --git a/app/app/src/main/java/org/modg/bookshelf/ui/library/LibraryScreen.kt b/app/app/src/main/java/org/modg/bookshelf/ui/library/LibraryScreen.kt index 8ced445..2275236 100644 --- a/app/app/src/main/java/org/modg/bookshelf/ui/library/LibraryScreen.kt +++ b/app/app/src/main/java/org/modg/bookshelf/ui/library/LibraryScreen.kt @@ -68,13 +68,16 @@ import org.modg.bookshelf.data.local.ShelfEntity import org.modg.bookshelf.data.metadata.BookMetadata import org.modg.bookshelf.data.metadata.OnlineBook import org.modg.bookshelf.data.repo.decodeAuthors +import org.modg.bookshelf.data.repo.CoverPreloadState import org.modg.bookshelf.ui.components.BookCover +import org.modg.bookshelf.ui.components.CoverPreloadNotice import org.modg.bookshelf.ui.components.EmptyState import org.modg.bookshelf.ui.components.BookshelfScaffold import org.modg.bookshelf.ui.components.GoldDivider import org.modg.bookshelf.ui.components.PaperSurface import org.modg.bookshelf.ui.components.PrimaryButton import org.modg.bookshelf.ui.components.SecondaryButton +import org.modg.bookshelf.ui.components.saveButtonLabel import org.modg.bookshelf.ui.components.SyncStatus import org.modg.bookshelf.ui.components.SyncStatusBar import org.modg.bookshelf.ui.scan.ShelfPicker @@ -116,6 +119,7 @@ fun LibraryScreen( settingsStore = container.settingsStore, bookSearchRepository = container.bookSearchRepository, metadataRepository = container.metadataRepository, + coverDownloader = container.coverDownloader, initialShelfId = shelfIdFilter, ) } @@ -129,6 +133,7 @@ fun LibraryScreen( val onlineSearch by viewModel.onlineSearch.collectAsState() val saveSheet by viewModel.saveSheet.collectAsState() val recentShelfId by viewModel.recentShelfId.collectAsState() + val cover by viewModel.cover.collectAsState() BookshelfScaffold( title = "Bookshelf", @@ -183,7 +188,11 @@ fun LibraryScreen( when { query.isNotBlank() -> LibrarySearchResultsContent( query = query, - localMatches = uiState.books, + // Once an online search has run, the merged set — it includes owned + // editions reached only through an online hit, which the plain text + // match can't find and the merge has already removed from "Online". + localMatches = (onlineSearch as? OnlineSearchState.Done)?.inLibrary ?: uiState.books, + inLibraryOutsideFilter = (onlineSearch as? OnlineSearchState.Done)?.inLibraryOutsideFilter ?: 0, onlineSearch = onlineSearch, onBookClick = onBookClick, onSubmitOnlineSearch = viewModel::submitOnlineSearch, @@ -235,8 +244,10 @@ fun LibraryScreen( recentShelfId = recentShelfId, onShelfSelected = viewModel::onSaveSheetShelfSelected, onSave = viewModel::saveOnlineResult, - onEditDetails = { onEditOnlineResult(sheetState.book, sheetState.enrichment) }, + onEditDetails = { viewModel.takeForEditing()?.let { (book, enrichment) -> onEditOnlineResult(book, enrichment) } }, onSkip = viewModel::dismissSaveSheet, + cover = cover, + onRetryCover = viewModel::retryCover, ) } } @@ -408,6 +419,7 @@ internal fun LibrarySearchResultsContent( onOnlineResultClick: (OnlineBook) -> Unit, onEnterByHand: () -> Unit, modifier: Modifier = Modifier, + inLibraryOutsideFilter: Int = 0, ) { Column( modifier = modifier @@ -439,6 +451,20 @@ internal fun LibrarySearchResultsContent( } } + if (inLibraryOutsideFilter > 0) { + Text( + text = if (inLibraryOutsideFilter == 1) { + "1 more in your library, outside the current filter" + } else { + "$inLibraryOutsideFilter more in your library, outside the current filter" + }, + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + // Extra top space: at 4dp it sat under the last card's author and read as its byline. + modifier = Modifier.padding(start = 16.dp, end = 16.dp, top = 12.dp, bottom = 4.dp), + ) + } + GoldDivider(modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp)) SearchSectionHeader(text = "Online") OnlineSearchSection( @@ -632,6 +658,8 @@ internal fun OnlineResultSaveSheet( onSave: () -> Unit, onEditDetails: () -> Unit, onSkip: () -> Unit, + cover: CoverPreloadState = CoverPreloadState.None, + onRetryCover: () -> Unit = {}, ) { val book = state.book Column(modifier = Modifier.fillMaxWidth().padding(16.dp)) { @@ -674,12 +702,13 @@ internal fun OnlineResultSaveSheet( recentShelfId = recentShelfId, onShelfSelected = onShelfSelected, ) + CoverPreloadNotice(state = cover, onRetry = onRetryCover) Row(modifier = Modifier.fillMaxWidth().padding(top = 16.dp), horizontalArrangement = Arrangement.spacedBy(12.dp)) { SecondaryButton(text = "Skip", onClick = onSkip, modifier = Modifier.weight(1f)) SecondaryButton(text = "Edit details", onClick = onEditDetails, modifier = Modifier.weight(1f)) } PrimaryButton( - text = if (state.isSaving) "Saving…" else "Save", + text = saveButtonLabel(state.isSaving, cover), enabled = !state.isSaving, onClick = onSave, modifier = Modifier.fillMaxWidth().padding(top = 12.dp), diff --git a/app/app/src/main/java/org/modg/bookshelf/ui/library/LibraryViewModel.kt b/app/app/src/main/java/org/modg/bookshelf/ui/library/LibraryViewModel.kt index 22b7e20..91e1dad 100644 --- a/app/app/src/main/java/org/modg/bookshelf/ui/library/LibraryViewModel.kt +++ b/app/app/src/main/java/org/modg/bookshelf/ui/library/LibraryViewModel.kt @@ -3,6 +3,8 @@ package org.modg.bookshelf.ui.library import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow @@ -10,6 +12,7 @@ import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update @@ -17,6 +20,7 @@ import kotlinx.coroutines.launch import org.modg.bookshelf.data.local.BookEntity import org.modg.bookshelf.data.local.BookcaseEntity import org.modg.bookshelf.data.local.ShelfEntity +import org.modg.bookshelf.data.metadata.BookMetadata import org.modg.bookshelf.data.metadata.BookSearchRepository import org.modg.bookshelf.data.metadata.IsbnUtils import org.modg.bookshelf.data.metadata.LookupResult @@ -27,6 +31,9 @@ import org.modg.bookshelf.data.metadata.SearchResultMerger import org.modg.bookshelf.data.metadata.SearchSourceResult import org.modg.bookshelf.data.prefs.SettingsStore import org.modg.bookshelf.data.repo.BookRepository +import org.modg.bookshelf.data.repo.CoverDownloader +import org.modg.bookshelf.data.repo.CoverPreload +import org.modg.bookshelf.data.repo.CoverPreloadState import org.modg.bookshelf.data.repo.LocationRepository import org.modg.bookshelf.data.repo.SyncEngine import org.modg.bookshelf.data.repo.SyncResult @@ -49,7 +56,10 @@ class LibraryViewModel( private val settingsStore: SettingsStore, private val bookSearchRepository: BookSearchRepository, private val metadataRepository: MetadataRepository, + coverDownloader: CoverDownloader, initialShelfId: String?, + /** Where the per-keystroke match and the search merge run — off the main thread. Tests pass their own. */ + computeDispatcher: CoroutineDispatcher = Dispatchers.Default, ) : ViewModel() { private val _query = MutableStateFlow("") @@ -81,6 +91,12 @@ class LibraryViewModel( private val allBooks: StateFlow> = bookRepository.observeAll() .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), emptyList()) + /** [allBooks] normalized once per change, not once per keystroke — see [LocalBookMatcher.IndexedBook]. */ + private val allBooksIndex: StateFlow> = allBooks + .map { LocalBookMatcher.index(it) } + .flowOn(computeDispatcher) + .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), emptyList()) + private val hasAnyBooks: StateFlow = allBooks .map { it.isNotEmpty() } .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), true) @@ -91,9 +107,9 @@ class LibraryViewModel( * single whole-string LIKE for this screen, which couldn't match "hobbit tolkien" * at all. A blank query means "browse everything", not "match nothing". */ - private val localMatches: StateFlow> = combine(_query, allBooks) { queryValue, books -> - if (queryValue.isBlank()) books else LocalBookMatcher.match(books, queryValue) - }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), emptyList()) + private val localMatches: StateFlow> = combine(_query, allBooksIndex) { queryValue, index -> + if (queryValue.isBlank()) index.map { it.book } else LocalBookMatcher.matchIndexed(index, queryValue) + }.flowOn(computeDispatcher).stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), emptyList()) private val filteredSortedBooks: StateFlow> = combine( localMatches, @@ -129,22 +145,46 @@ class LibraryViewModel( * sync) immediately migrates itself from "Online" into "In your library" via the * normal Room flow, with no hand-written "move it over" step. */ - val onlineSearch: StateFlow = combine(_onlineSearchPhase, allBooks) { phase, books -> + val onlineSearch: StateFlow = combine( + _onlineSearchPhase, + allBooksIndex, + _filter, + _sortOption, + shelfIdsByBookcase, + ) { phase, index, filterValue, sort, shelfMap -> when (phase) { is OnlineSearchPhase.NotSearched -> OnlineSearchState.NotSearched is OnlineSearchPhase.Searching -> OnlineSearchState.Searching(phase.query) - is OnlineSearchPhase.Done -> buildDoneState(phase.hits, books) + is OnlineSearchPhase.Done -> buildDoneState(phase.hits, index, filterValue, sort, shelfMap) } - }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), OnlineSearchState.NotSearched) + }.flowOn(computeDispatcher).stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), OnlineSearchState.NotSearched) - private fun buildDoneState(raw: RawHits, books: List): OnlineSearchState.Done { + /** + * The merge runs against the WHOLE library (ownership doesn't depend on the filter), + * then the shelf/bookcase filter and sort are applied to what "In your library" shows. + */ + private fun buildDoneState( + raw: RawHits, + index: List, + filterValue: LibraryFilter, + sort: LibrarySortOption, + shelfMap: Map>, + ): OnlineSearchState.Done { val onlineIsbns = (raw.openLibrary.asSequence() + raw.googleBooks.asSequence()) .flatMap { it.isbn13s } .toSet() - val isbnLinked = books.filter { book -> bookIsbn13s(book).any { it in onlineIsbns } } - val candidates = (LocalBookMatcher.match(books, raw.query) + isbnLinked).distinctBy { it.id } + val isbnLinked = index.map { it.book }.filter { book -> bookIsbn13s(book).any { it in onlineIsbns } } + val candidates = (LocalBookMatcher.matchIndexed(index, raw.query) + isbnLinked).distinctBy { it.id } val merged = SearchResultMerger.merge(candidates, raw.openLibrary, raw.googleBooks) - return OnlineSearchState.Done(raw.query, merged.inLibrary, merged.online, raw.openLibraryFailure, raw.googleBooksFailure) + val visible = LibraryFilterLogic.apply(merged.inLibrary, filterValue, shelfMap) + return OnlineSearchState.Done( + query = raw.query, + inLibrary = LibrarySort.sort(visible, sort), + online = merged.online, + openLibraryFailure = raw.openLibraryFailure, + googleBooksFailure = raw.googleBooksFailure, + inLibraryOutsideFilter = merged.inLibrary.size - visible.size, + ) } private fun bookIsbn13s(book: BookEntity): List = @@ -156,6 +196,10 @@ class LibraryViewModel( val saveSheet: StateFlow = _saveSheet.asStateFlow() private var enrichmentJob: Job? = null + /** The save sheet's cover, downloading from the moment the sheet opens — one result at a time, never the whole list. */ + private val coverPreload = CoverPreload(coverDownloader, viewModelScope) + val cover: StateFlow = coverPreload.state + /** Surfaced as the save sheet's shelf picker default, same as the scan/add-by-hand sheets. */ val recentShelfId: StateFlow = settingsStore.lastShelfId .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), null) @@ -257,6 +301,7 @@ class LibraryViewModel( fun onOnlineResultTapped(book: OnlineBook) { enrichmentJob?.cancel() _saveSheet.value = OnlineSaveSheetState.Shown(book = book, selectedShelfId = recentShelfId.value) + coverPreload.start(resolveOnlineBookFields(book, enrichment = null).coverUrl) val isbn13 = book.isbn13 ?: return enrichmentJob = viewModelScope.launch { val result = metadataRepository.lookup(isbn13) @@ -264,16 +309,33 @@ class LibraryViewModel( _saveSheet.update { state -> if (state is OnlineSaveSheetState.Shown && state.book == book) state.copy(enrichment = result.metadata) else state } + // A no-op unless enrichment supplied the only cover (the hit's own wins, see resolveOnlineBookFields). + val shown = _saveSheet.value as? OnlineSaveSheetState.Shown + if (shown?.book == book) coverPreload.start(resolveOnlineBookFields(book, shown.enrichment).coverUrl) } } } + fun retryCover() = coverPreload.retry() + + /** + * "Edit details": closes the sheet and hands its book to the add-by-hand screen. It must + * close here — left open, the sheet was still up (Save enabled) on returning to the + * library after saving that same book from the add screen, one tap from a duplicate. + */ + fun takeForEditing(): Pair? { + val shown = _saveSheet.value as? OnlineSaveSheetState.Shown ?: return null + dismissSaveSheet() + return shown.book to shown.enrichment + } + fun onSaveSheetShelfSelected(shelfId: String?) { _saveSheet.update { if (it is OnlineSaveSheetState.Shown) it.copy(selectedShelfId = shelfId) else it } } fun dismissSaveSheet() { enrichmentJob?.cancel() + coverPreload.discard() _saveSheet.value = OnlineSaveSheetState.Hidden } @@ -291,11 +353,24 @@ class LibraryViewModel( internal suspend fun performSaveOnlineResult(): String? { val state = _saveSheet.value as? OnlineSaveSheetState.Shown ?: return null if (state.isSaving) return null + val coverAtTap = coverPreload.state.value _saveSheet.update { if (it is OnlineSaveSheetState.Shown) it.copy(isSaving = true, saveError = null) else it } + var shelfId = state.selectedShelfId - val fields = resolveOnlineBookFields(state.book, state.enrichment) - return try { - val id = bookRepository.createBook( + val id = try { + // Same cover rule as the scan sheet (see ScanViewModel.performSave): a download + // that fails while Save waits stops the save; one that had already failed when + // "Save without cover" was tapped does not. + val cover = coverPreload.awaitSettled() + if (cover is CoverPreloadState.Failed && coverAtTap !is CoverPreloadState.Failed) { + _saveSheet.update { if (it is OnlineSaveSheetState.Shown) it.copy(isSaving = false) else it } + return null + } + // Read the sheet again: enrichment may have landed during the wait. + val latest = _saveSheet.value as? OnlineSaveSheetState.Shown ?: state + val fields = resolveOnlineBookFields(latest.book, latest.enrichment) + shelfId = latest.selectedShelfId + bookRepository.createBook( title = fields.title, subtitle = fields.subtitle, authors = fields.authors, @@ -306,23 +381,39 @@ class LibraryViewModel( pageCount = fields.pageCount, description = fields.description, coverSourceUrl = fields.coverUrl, - shelfId = state.selectedShelfId, + coverFile = (cover as? CoverPreloadState.Ready)?.takeIf { it.url == fields.coverUrl }?.file, + shelfId = shelfId, ) - rememberShelf(state.selectedShelfId) - enrichmentJob?.cancel() - _saveSheet.value = OnlineSaveSheetState.Hidden - id } catch (e: CancellationException) { + _saveSheet.update { if (it is OnlineSaveSheetState.Shown) it.copy(isSaving = false) else it } throw e } catch (e: Throwable) { _saveSheet.update { if (it is OnlineSaveSheetState.Shown) it.copy(isSaving = false, saveError = e.javaClass.simpleName) else it } - null + return null + } + // The book is written: nothing below may report a failure (a retry would duplicate it). + dismissSaveSheet() // also discards the preload — createBook copied it + rememberShelf(shelfId) + return id + } + + /** + * "Not shelved" (null) must never overwrite the memory — it isn't a shelf. Runs after the + * book is written, so a failure is swallowed rather than reported (see [performSaveOnlineResult]). + */ + private suspend fun rememberShelf(shelfId: String?) { + if (shelfId == null) return + try { + settingsStore.setLastShelfId(shelfId) + } catch (e: CancellationException) { + throw e + } catch (e: Throwable) { + // Losing the "recent shelf" shortcut is not worth an error on a saved book. } } - /** "Not shelved" (null) must never overwrite the memory — it isn't a shelf. */ - private suspend fun rememberShelf(shelfId: String?) { - if (shelfId != null) settingsStore.setLastShelfId(shelfId) + override fun onCleared() { + coverPreload.discard() } /** Manual trigger — app start (see init) and pull-to-refresh, per SPEC "Sync design". */ diff --git a/app/app/src/main/java/org/modg/bookshelf/ui/library/LocalBookMatcher.kt b/app/app/src/main/java/org/modg/bookshelf/ui/library/LocalBookMatcher.kt index c491bf6..4e55770 100644 --- a/app/app/src/main/java/org/modg/bookshelf/ui/library/LocalBookMatcher.kt +++ b/app/app/src/main/java/org/modg/bookshelf/ui/library/LocalBookMatcher.kt @@ -14,28 +14,44 @@ import org.modg.bookshelf.data.repo.decodeAuthors object LocalBookMatcher { /** - * Normalizes [query] and every candidate field, splits the query on whitespace, - * and requires EVERY token to appear in at least one of title/subtitle/authors/ - * isbn13/isbn10 (different tokens may match different fields). Matching also - * checks a space-stripped form of each field so an initials-heavy name like - * "J.R.R. Tolkien" (normalized to "j r r tolkien", per [TextNormalization]) is - * still found by the squashed query token "jrr", not just "j" or "r". + * One book's searchable text, normalized once. Normalizing every field of every book + * on each keystroke (Unicode decomposition + regex, on the main thread) measured ~60ms + * per keystroke for 2,000 books on the dev server, so a phone would visibly lag; + * [index] is rebuilt only when the book list changes. */ - fun match(books: List, query: String): List { - val queryTokens = TextNormalization.tokens(query) - if (queryTokens.isEmpty()) return emptyList() - return books.filter { book -> matches(book, queryTokens) } + class IndexedBook internal constructor(val book: BookEntity, private val haystacks: List) { + private val squashed = haystacks.map { it.replace(" ", "") } + + internal fun matches(queryTokens: List): Boolean = + queryTokens.all { token -> haystacks.any { it.contains(token) } || squashed.any { it.contains(token) } } } - private fun matches(book: BookEntity, queryTokens: List): Boolean { - val haystacks = buildList { - add(TextNormalization.normalizeForMatching(book.title)) - book.subtitle?.let { add(TextNormalization.normalizeForMatching(it)) } - decodeAuthors(book.authorsJson).forEach { add(TextNormalization.normalizeForMatching(it)) } - book.isbn13?.let { add(TextNormalization.normalizeForMatching(it)) } - book.isbn10?.let { add(TextNormalization.normalizeForMatching(it)) } - } - val squashed = haystacks.map { it.replace(" ", "") } - return queryTokens.all { token -> haystacks.any { it.contains(token) } || squashed.any { it.contains(token) } } + fun index(books: List): List = books.map { book -> + IndexedBook( + book, + buildList { + add(TextNormalization.normalizeForMatching(book.title)) + book.subtitle?.let { add(TextNormalization.normalizeForMatching(it)) } + decodeAuthors(book.authorsJson).forEach { add(TextNormalization.normalizeForMatching(it)) } + book.isbn13?.let { add(TextNormalization.normalizeForMatching(it)) } + book.isbn10?.let { add(TextNormalization.normalizeForMatching(it)) } + }, + ) } + + /** + * Normalizes [query], splits it on whitespace, and requires EVERY token to appear in at + * least one of title/subtitle/authors/isbn13/isbn10 (different tokens may match different + * fields). Matching also checks a space-stripped form of each field so an initials-heavy + * name like "J.R.R. Tolkien" (normalized to "j r r tolkien", per [TextNormalization]) is + * still found by the squashed query token "jrr", not just "j" or "r". + */ + fun matchIndexed(index: List, query: String): List { + val queryTokens = TextNormalization.tokens(query) + if (queryTokens.isEmpty()) return emptyList() + return index.filter { it.matches(queryTokens) }.map { it.book } + } + + /** Convenience for callers without an index (tests, one-off matches). */ + fun match(books: List, query: String): List = matchIndexed(index(books), query) } diff --git a/app/app/src/main/java/org/modg/bookshelf/ui/scan/ScanModels.kt b/app/app/src/main/java/org/modg/bookshelf/ui/scan/ScanModels.kt index f6ce03d..5643091 100644 --- a/app/app/src/main/java/org/modg/bookshelf/ui/scan/ScanModels.kt +++ b/app/app/src/main/java/org/modg/bookshelf/ui/scan/ScanModels.kt @@ -55,6 +55,9 @@ object ScanMetadataOutcome { } } +/** The Found sheet's Save button: [isSaving] disables it; [error] names the exception class of a failed save. */ +data class ScanSaveStatus(val isSaving: Boolean = false, val error: String? = null) + /** SPEC.md "Continuous mode: ... a running 'added this session' count." */ data class ScanSessionState(val savedCount: Int = 0) { fun withSave(): ScanSessionState = copy(savedCount = savedCount + 1) diff --git a/app/app/src/main/java/org/modg/bookshelf/ui/scan/ScanScreen.kt b/app/app/src/main/java/org/modg/bookshelf/ui/scan/ScanScreen.kt index 10cf6da..2296671 100644 --- a/app/app/src/main/java/org/modg/bookshelf/ui/scan/ScanScreen.kt +++ b/app/app/src/main/java/org/modg/bookshelf/ui/scan/ScanScreen.kt @@ -71,6 +71,9 @@ import org.modg.bookshelf.ui.components.EmptyState import org.modg.bookshelf.ui.components.PrimaryButton import org.modg.bookshelf.ui.components.SecondaryButton import org.modg.bookshelf.ui.components.ShelfPickerSheet +import org.modg.bookshelf.ui.components.CoverPreloadNotice +import org.modg.bookshelf.ui.components.saveButtonLabel +import org.modg.bookshelf.data.repo.CoverPreloadState /** * SPEC.md "scan" screen: camera + reticle, on-hit bottom sheet, continuous @@ -92,12 +95,15 @@ fun ScanScreen( locationRepository = container.locationRepository, metadataRepository = container.metadataRepository, settingsStore = container.settingsStore, + coverDownloader = container.coverDownloader, ) } }, ) val sheetState by viewModel.sheetState.collectAsState() + val cover by viewModel.cover.collectAsState() + val saveStatus by viewModel.saveStatus.collectAsState() val sessionState by viewModel.sessionState.collectAsState() val rejectedMessage by viewModel.rejectedMessage.collectAsState() val torchEnabled by viewModel.scannerController.torchEnabled.collectAsState() @@ -171,6 +177,10 @@ fun ScanScreen( onShelfSelected = viewModel::selectShelf, onSave = { viewModel.save(state.isbn13, state.metadata) }, onSkip = { viewModel.skip() }, + cover = cover, + onRetryCover = viewModel::retryCover, + isSaving = saveStatus.isSaving, + saveError = saveStatus.error, ) } is ScanSheetState.NotFound -> ModalBottomSheet( @@ -330,6 +340,10 @@ internal fun FoundBookSheet( onShelfSelected: (String?) -> Unit, onSave: () -> Unit, onSkip: () -> Unit, + cover: CoverPreloadState = CoverPreloadState.None, + onRetryCover: () -> Unit = {}, + isSaving: Boolean = false, + saveError: String? = null, ) { Column(modifier = Modifier.fillMaxWidth().padding(16.dp)) { Box( @@ -362,9 +376,23 @@ internal fun FoundBookSheet( recentShelfId = recentShelfId, onShelfSelected = onShelfSelected, ) + CoverPreloadNotice(state = cover, onRetry = onRetryCover) + if (saveError != null) { + Text( + text = "Couldn't save: $saveError", + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.error, + modifier = Modifier.padding(top = 8.dp), + ) + } Row(modifier = Modifier.fillMaxWidth().padding(top = 16.dp), horizontalArrangement = Arrangement.spacedBy(12.dp)) { SecondaryButton(text = "Skip", onClick = onSkip, modifier = Modifier.weight(1f)) - PrimaryButton(text = "Save", onClick = onSave, modifier = Modifier.weight(1f)) + PrimaryButton( + text = saveButtonLabel(isSaving, cover), + enabled = !isSaving, + onClick = onSave, + modifier = Modifier.weight(1f), + ) } } } diff --git a/app/app/src/main/java/org/modg/bookshelf/ui/scan/ScanViewModel.kt b/app/app/src/main/java/org/modg/bookshelf/ui/scan/ScanViewModel.kt index fcce674..95a4aff 100644 --- a/app/app/src/main/java/org/modg/bookshelf/ui/scan/ScanViewModel.kt +++ b/app/app/src/main/java/org/modg/bookshelf/ui/scan/ScanViewModel.kt @@ -18,6 +18,9 @@ import org.modg.bookshelf.data.metadata.IsbnUtils import org.modg.bookshelf.data.metadata.MetadataRepository import org.modg.bookshelf.data.prefs.SettingsStore import org.modg.bookshelf.data.repo.BookRepository +import org.modg.bookshelf.data.repo.CoverDownloader +import org.modg.bookshelf.data.repo.CoverPreload +import org.modg.bookshelf.data.repo.CoverPreloadState import org.modg.bookshelf.data.repo.LocationRepository /** @@ -31,12 +34,21 @@ class ScanViewModel( locationRepository: LocationRepository, private val metadataRepository: MetadataRepository, private val settingsStore: SettingsStore, + coverDownloader: CoverDownloader, val scannerController: ScannerController = ScannerController(), ) : ViewModel() { private val _sheetState = MutableStateFlow(ScanSheetState.Hidden) val sheetState: StateFlow = _sheetState.asStateFlow() + /** The Found sheet's cover, downloading from the moment the lookup succeeds — see [CoverPreload]. */ + private val coverPreload = CoverPreload(coverDownloader, viewModelScope) + val cover: StateFlow = coverPreload.state + + private val _saveStatus = MutableStateFlow(ScanSaveStatus()) + /** The Found sheet's Save: in flight (disables the button) and the last failure, if any. */ + val saveStatus: StateFlow = _saveStatus.asStateFlow() + private val _sessionState = MutableStateFlow(ScanSessionState()) val sessionState: StateFlow = _sessionState.asStateFlow() @@ -86,7 +98,9 @@ class ScanViewModel( try { val duplicate = DuplicateCheck.check(bookRepository.findByIsbn13(isbn13)) val result = metadataRepository.lookup(isbn13) - _sheetState.value = ScanMetadataOutcome.from(isbn13, result, duplicate) + val outcome = ScanMetadataOutcome.from(isbn13, result, duplicate) + _sheetState.value = outcome + if (outcome is ScanSheetState.Found) coverPreload.start(outcome.metadata.coverUrl) } catch (e: CancellationException) { // Normal control flow (sheet dismissed / screen left mid-lookup) — // must propagate, never be reported as a failed lookup. @@ -131,27 +145,57 @@ class ScanViewModel( viewModelScope.launch { performSave(isbn13, metadata) } } + fun retryCover() = coverPreload.retry() + /** * The actual save-from-metadata logic, split out from [save] so tests can await it * directly (as a plain suspend call) instead of racing [viewModelScope]'s launch. + * Returns whether the book was saved. + * + * Waits for the cover preload if it's still running, so the cover file is written + * with the row. If the download fails DURING that wait the save stops and the sheet + * shows the failure (Retry, or Save again to go ahead without it); if it had already + * failed when Save was tapped, the user saw "Save without cover" and meant it. + * + * Guarded like wave 8's lookup path, which this save path wasn't: [Throwable] is + * caught so a Room failure can't crash the app, [CancellationException] rethrown + * first, and a save already in flight makes a second tap a no-op. */ - internal suspend fun performSave(isbn13: String, metadata: BookMetadata) { + internal suspend fun performSave(isbn13: String, metadata: BookMetadata): Boolean { + if (_saveStatus.value.isSaving) return false + val coverAtTap = coverPreload.state.value + _saveStatus.value = ScanSaveStatus(isSaving = true) val shelfId = _selectedShelfId.value - bookRepository.createBook( - title = metadata.title ?: "Untitled", - subtitle = metadata.subtitle, - authors = metadata.authors, - isbn13 = metadata.isbn13 ?: isbn13, - isbn10 = metadata.isbn10, - publisher = metadata.publisher, - publishedDate = metadata.publishedDate, - pageCount = metadata.pageCount, - description = metadata.description, - coverSourceUrl = metadata.coverUrl, - shelfId = shelfId, - ) + try { + val cover = coverPreload.awaitSettled() + if (cover is CoverPreloadState.Failed && coverAtTap !is CoverPreloadState.Failed) { + _saveStatus.value = ScanSaveStatus() + return false + } + bookRepository.createBook( + title = metadata.title ?: "Untitled", + subtitle = metadata.subtitle, + authors = metadata.authors, + isbn13 = metadata.isbn13 ?: isbn13, + isbn10 = metadata.isbn10, + publisher = metadata.publisher, + publishedDate = metadata.publishedDate, + pageCount = metadata.pageCount, + description = metadata.description, + coverSourceUrl = metadata.coverUrl, + coverFile = (cover as? CoverPreloadState.Ready)?.file, + shelfId = shelfId, + ) + } catch (e: CancellationException) { + _saveStatus.value = ScanSaveStatus() + throw e + } catch (e: Throwable) { + _saveStatus.value = ScanSaveStatus(error = e.javaClass.simpleName) + return false + } rememberShelf(shelfId) recordSave() + return true } /** Save from the manual-entry form shown when metadata lookup misses (SPEC: pre-filled with the scanned ISBN). */ @@ -172,9 +216,20 @@ class ScanViewModel( recordSave() } - /** "Not shelved" (null) must never overwrite the memory — it isn't a shelf. */ + /** + * "Not shelved" (null) must never overwrite the memory — it isn't a shelf. Called only + * AFTER the book is written, and a failure here is swallowed: reporting "couldn't save" + * for a book that WAS saved would invite a retry that creates a duplicate. + */ private suspend fun rememberShelf(shelfId: String?) { - if (shelfId != null) settingsStore.setLastShelfId(shelfId) + if (shelfId == null) return + try { + settingsStore.setLastShelfId(shelfId) + } catch (e: CancellationException) { + throw e + } catch (e: Throwable) { + // The book is saved; losing the "recent shelf" shortcut is not worth an error. + } } private fun recordSave() { @@ -188,9 +243,15 @@ class ScanViewModel( fun dismissSheet() { _sheetState.value = ScanSheetState.Hidden + coverPreload.discard() // saved (createBook copied it) or skipped — either way, not needed + _saveStatus.value = ScanSaveStatus() scannerController.resetDebounce() } + override fun onCleared() { + coverPreload.discard() + } + private companion object { /** How long a rejected-barcode message stays on screen before it auto-clears. */ const val REJECTED_MESSAGE_MILLIS = 3000L diff --git a/app/app/src/test/java/org/modg/bookshelf/data/repo/BookRepositoryCoverTest.kt b/app/app/src/test/java/org/modg/bookshelf/data/repo/BookRepositoryCoverTest.kt new file mode 100644 index 0000000..8777cd2 --- /dev/null +++ b/app/app/src/test/java/org/modg/bookshelf/data/repo/BookRepositoryCoverTest.kt @@ -0,0 +1,97 @@ +package org.modg.bookshelf.data.repo + +import androidx.room.Room +import androidx.test.core.app.ApplicationProvider +import java.io.File +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.test.runTest +import org.junit.After +import org.junit.Assert.assertArrayEquals +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNull +import org.junit.Assert.assertTrue +import org.junit.Assert.fail +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TemporaryFolder +import org.junit.runner.RunWith +import org.modg.bookshelf.data.local.BookDao +import org.modg.bookshelf.data.local.BookEntity +import org.modg.bookshelf.data.local.BookshelfDatabase +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config + +/** + * [BookRepository.createBook] no longer downloads anything: it takes an already-preloaded + * cover file and copies it into app storage for SyncEngine to upload. + */ +@RunWith(RobolectricTestRunner::class) +@Config(sdk = [34]) +class BookRepositoryCoverTest { + + // Not context.cacheDir: a file written there in @Before was observed missing by the test body. + @get:Rule + val tmp = TemporaryFolder() + + private lateinit var db: BookshelfDatabase + private lateinit var context: android.content.Context + private lateinit var preloaded: File + + @Before + fun setUp() { + context = ApplicationProvider.getApplicationContext() + db = Room.inMemoryDatabaseBuilder(context, BookshelfDatabase::class.java) + .allowMainThreadQueries() + .build() + preloaded = tmp.newFile("preloaded-cover").apply { writeBytes(byteArrayOf(1, 2, 3)) } + } + + @After + fun tearDown() { + db.close() + } + + @Test + fun `a cover file is copied into app storage and recorded as localCoverPath`() = runTest { + val repository = BookRepository(db.bookDao(), context) + + val id = repository.createBook(title = "Dune", coverSourceUrl = "https://x/c.jpg", coverFile = preloaded) + + val saved = checkNotNull(repository.getById(id)) + val local = File(checkNotNull(saved.localCoverPath)) + assertEquals(File(context.filesDir, "covers/$id.jpg"), local) + assertArrayEquals(byteArrayOf(1, 2, 3), local.readBytes()) + assertEquals("https://x/c.jpg", saved.coverSourceUrl) + assertTrue("a copy: the caller still owns the preload", preloaded.exists()) + } + + @Test + fun `no cover file means no localCoverPath and no download attempt`() = runTest { + val repository = BookRepository(db.bookDao(), context) + + val id = repository.createBook(title = "Dune", coverSourceUrl = "https://x/c.jpg") + + assertNull(checkNotNull(repository.getById(id)).localCoverPath) + } + + @Test + fun `a failed write deletes the copied cover, leaving nothing unowned in app storage`() = runTest { + val repository = BookRepository(ThrowingUpsertBookDao(db.bookDao()), context) + + try { + repository.createBook(title = "Dune", coverFile = preloaded) + fail("expected the write failure to propagate") + } catch (e: IllegalStateException) { + // expected + } + + assertEquals(0, File(context.filesDir, "covers").listFiles()?.size ?: 0) + assertTrue(preloaded.exists()) + assertTrue(db.bookDao().observeAll().first().isEmpty()) + } + + private class ThrowingUpsertBookDao(private val delegate: BookDao) : BookDao by delegate { + override suspend fun upsert(book: BookEntity) = throw IllegalStateException("disk full") + } +} diff --git a/app/app/src/test/java/org/modg/bookshelf/data/repo/CoverPreloadTest.kt b/app/app/src/test/java/org/modg/bookshelf/data/repo/CoverPreloadTest.kt new file mode 100644 index 0000000..9972a68 --- /dev/null +++ b/app/app/src/test/java/org/modg/bookshelf/data/repo/CoverPreloadTest.kt @@ -0,0 +1,206 @@ +package org.modg.bookshelf.data.repo + +import java.io.File +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancel +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withTimeout +import org.junit.After +import org.junit.Assert.assertArrayEquals +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Assert.fail +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TemporaryFolder + +/** + * The cover preload that replaced save-time downloading: the file must be ready (or the + * failure visible) before Save, and no file may outlive the sheet that asked for it. + */ +class CoverPreloadTest { + + @get:Rule + val tmp = TemporaryFolder() + + private val host = FakeCoverHost() + private lateinit var preloadDir: File + private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Unconfined) + + @Before + fun setUp() { + preloadDir = File(tmp.root, "cover-preload") + } + + @After + fun tearDown() { + host.release() + scope.cancel() + } + + private val url = "https://covers.example.org/b/id/42-M.jpg" + + // --- CoverDownloader --- + + @Test + fun `a successful download writes the bytes into the preload dir`() = runBlocking { + host.serve(url, byteArrayOf(9, 8, 7)) + + val result = host.downloader(preloadDir).download(url) + + val file = (result as CoverFetch.Ready).file + assertEquals(preloadDir, file.parentFile) + assertArrayEquals(byteArrayOf(9, 8, 7), file.readBytes()) + } + + @Test + fun `an http error is Failed with the status in the reason, and leaves no file`() = runBlocking { + host.fail(url, 503) + + val result = host.downloader(preloadDir).download(url) + + assertTrue(result is CoverFetch.Failed) + assertTrue((result as CoverFetch.Failed).reason.contains("503")) + assertEquals(0, preloadDir.listFiles()?.size ?: 0) + } + + @Test + fun `a transport failure is Failed, not a crash, and leaves no file`() = runBlocking { + host.reset(url) + + val result = host.downloader(preloadDir).download(url) + + assertTrue(result is CoverFetch.Failed) + assertEquals(0, preloadDir.listFiles()?.size ?: 0) + } + + @Test + fun `sweepStale deletes only files older than the cutoff`() { + preloadDir.mkdirs() + val old = File(preloadDir, "old").apply { writeBytes(byteArrayOf(1)); setLastModified(1_000) } + val fresh = File(preloadDir, "fresh").apply { writeBytes(byteArrayOf(1)); setLastModified(5_000) } + + host.downloader(preloadDir).sweepStale(olderThanMillis = 3_000) + + assertFalse(old.exists()) + assertTrue(fresh.exists()) + } + + // --- CoverPreload --- + + @Test + fun `start downloads, and starting the same url again does not download twice`() = runBlocking { + host.serve(url) + val preload = CoverPreload(host.downloader(preloadDir), scope) + + preload.start(url) + preload.start(url) + + val ready = preload.awaitSettled() as CoverPreloadState.Ready + assertTrue(ready.file.exists()) + assertEquals(1, host.requestCount(url)) + } + + @Test + fun `a different url replaces the preload and deletes the old file`() = runBlocking { + val other = "https://covers.example.org/b/id/43-M.jpg" + host.serve(url) + host.serve(other) + val preload = CoverPreload(host.downloader(preloadDir), scope) + + preload.start(url) + val first = (preload.awaitSettled() as CoverPreloadState.Ready).file + preload.start(other) + val second = preload.awaitSettled() as CoverPreloadState.Ready + + assertFalse(first.exists()) + assertEquals(other, second.url) + assertTrue(second.file.exists()) + } + + @Test + fun `discard deletes the downloaded file`() = runBlocking { + host.serve(url) + val preload = CoverPreload(host.downloader(preloadDir), scope) + preload.start(url) + val file = (preload.awaitSettled() as CoverPreloadState.Ready).file + + preload.discard() + + assertFalse(file.exists()) + assertEquals(CoverPreloadState.None, preload.state.value) + } + + @Test + fun `a failed preload can be retried`() = runBlocking { + host.reset(url) + val preload = CoverPreload(host.downloader(preloadDir), scope) + preload.start(url) + assertTrue(preload.awaitSettled() is CoverPreloadState.Failed) + + host.serve(url) + preload.retry() + + assertTrue(preload.awaitSettled() is CoverPreloadState.Ready) + } + + @Test + fun `awaitSettled waits for a download still in flight`() = runBlocking { + host.serve(url) + host.hold() + val preload = CoverPreload(host.downloader(preloadDir, Dispatchers.IO), scope) + preload.start(url) + assertTrue(preload.state.value is CoverPreloadState.Loading) + + host.release() + val settled = withTimeout(10_000) { preload.awaitSettled() } + + assertTrue(settled is CoverPreloadState.Ready) + } + + @Test + fun `a download discarded while in flight leaves no file behind`() = runBlocking { + host.serve(url) + host.hold() + val preload = CoverPreload(host.downloader(preloadDir, Dispatchers.IO), scope) + preload.start(url) + pollUntil { host.requestCount(url) == 1 } // blocked inside the HTTP call + + preload.discard() + host.release() + // The blocked call returns on its IO thread after cancellation and must clean up after itself. + pollUntil { (preloadDir.listFiles()?.size ?: 0) == 0 } + Thread.sleep(200) // and nothing lands afterwards + + assertEquals(CoverPreloadState.None, preload.state.value) + assertEquals(0, preloadDir.listFiles()?.size ?: 0) + } + + /** Bounded: a condition that never comes true fails the test instead of hanging the build. */ + private fun pollUntil(condition: () -> Boolean) { + repeat(500) { + if (condition()) return + Thread.sleep(10) + } + fail("condition not met within 5s") + } + + @Test + fun `a ready file that has vanished is downloaded again rather than handed to save`() = runBlocking { + host.serve(url) + val preload = CoverPreload(host.downloader(preloadDir), scope) + preload.start(url) + (preload.awaitSettled() as CoverPreloadState.Ready).file.delete() // e.g. Android cleared the cache dir + + val settled = preload.awaitSettled() as CoverPreloadState.Ready + + assertTrue(settled.file.exists()) + assertEquals(2, host.requestCount(url)) + assertEquals(url, preload.state.first().let { (it as CoverPreloadState.Ready).url }) + } +} diff --git a/app/app/src/test/java/org/modg/bookshelf/data/repo/FakeCoverHost.kt b/app/app/src/test/java/org/modg/bookshelf/data/repo/FakeCoverHost.kt new file mode 100644 index 0000000..9570c27 --- /dev/null +++ b/app/app/src/test/java/org/modg/bookshelf/data/repo/FakeCoverHost.kt @@ -0,0 +1,68 @@ +package org.modg.bookshelf.data.repo + +import java.io.File +import java.io.IOException +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.OkHttpClient +import okhttp3.Protocol +import okhttp3.Response +import okhttp3.ResponseBody.Companion.toResponseBody + +/** + * An image host for cover-preload tests, answered by an interceptor (no sockets). Each URL + * gets bytes, an HTTP error code, or a thrown IOException; anything unregistered is a 404. + * [hold] makes downloads block until [release], to test Save waiting on a preload. + */ +class FakeCoverHost { + private sealed interface Answer { + class Bytes(val bytes: ByteArray) : Answer + class Code(val code: Int) : Answer + class Throw(val e: IOException) : Answer + } + + private val answers = ConcurrentHashMap() + private val requests = ConcurrentHashMap() + @Volatile private var gate: CountDownLatch? = null + + fun serve(url: String, bytes: ByteArray = byteArrayOf(1, 2, 3, 4)) { answers[url] = Answer.Bytes(bytes) } + fun fail(url: String, code: Int) { answers[url] = Answer.Code(code) } + fun reset(url: String, message: String = "connection reset") { answers[url] = Answer.Throw(IOException(message)) } + fun requestCount(url: String): Int = requests[url] ?: 0 + + fun hold() { gate = CountDownLatch(1) } + fun release() { gate?.countDown() } + + val client: OkHttpClient = OkHttpClient.Builder().addInterceptor { chain -> + val request = chain.request() + val url = request.url.toString() + requests.merge(url, 1, Int::plus) + gate?.await(10, TimeUnit.SECONDS) + when (val answer = answers[url]) { + is Answer.Bytes -> response(request, 200, answer.bytes) + is Answer.Code -> response(request, answer.code, ByteArray(0)) + is Answer.Throw -> throw answer.e + null -> response(request, 404, ByteArray(0)) + } + }.build() + + /** + * Downloads run on the calling thread by default, so no IO-thread download can resume + * into a later test after this one's Dispatchers.resetMain. Tests that [hold] must pass + * a real dispatcher instead (a held download would block the test thread itself). + */ + fun downloader(preloadDir: File, dispatcher: CoroutineDispatcher = Dispatchers.Unconfined): CoverDownloader = + CoverDownloader(client, preloadDir, dispatcher) + + private fun response(request: okhttp3.Request, code: Int, bytes: ByteArray) = Response.Builder() + .request(request) + .protocol(Protocol.HTTP_1_1) + .code(code) + .message("fake") + .body(bytes.toResponseBody("image/jpeg".toMediaType())) + .build() +} diff --git a/app/app/src/test/java/org/modg/bookshelf/ui/add/AddBookViewModelTest.kt b/app/app/src/test/java/org/modg/bookshelf/ui/add/AddBookViewModelTest.kt index 32a5a4f..3458de8 100644 --- a/app/app/src/test/java/org/modg/bookshelf/ui/add/AddBookViewModelTest.kt +++ b/app/app/src/test/java/org/modg/bookshelf/ui/add/AddBookViewModelTest.kt @@ -24,6 +24,8 @@ import org.modg.bookshelf.data.local.BookEntity import org.modg.bookshelf.data.local.BookshelfDatabase import org.modg.bookshelf.data.prefs.SettingsStore import org.modg.bookshelf.data.repo.BookRepository +import org.modg.bookshelf.data.repo.CoverPreloadState +import org.modg.bookshelf.data.repo.FakeCoverHost import org.modg.bookshelf.data.repo.LocationRepository import org.modg.bookshelf.data.repo.decodeAuthors import org.modg.bookshelf.ui.scan.DuplicateStatus @@ -42,6 +44,7 @@ class AddBookViewModelTest { private lateinit var viewModel: AddBookViewModel private lateinit var shelfId: String private lateinit var context: android.content.Context + private val coverHost = FakeCoverHost() @Before fun setUp() = runTest { @@ -64,6 +67,7 @@ class AddBookViewModelTest { shelfId = locationRepository.createShelf(bookcaseId, label = "Top shelf") viewModel = AddBookViewModel( + coverDownloader = coverHost.downloader(java.io.File(context.cacheDir, "cover-preload")), bookRepository = bookRepository, locationRepository = locationRepository, settingsStore = settingsStore, @@ -267,6 +271,7 @@ class AddBookViewModelTest { settingsStore.setLastShelfId(shelfId) val vm = AddBookViewModel( + coverDownloader = coverHost.downloader(java.io.File(context.cacheDir, "cover-preload")), bookRepository = bookRepository, locationRepository = locationRepository, settingsStore = settingsStore, @@ -276,7 +281,95 @@ class AddBookViewModelTest { assertEquals(shelfId, preSelected.selectedShelfId) } + // --- review fixes (2026-09-16) --- + + private val coverUrl = "https://covers.example.org/b/id/42-M.jpg" + + private fun viewModelWithDraft(draft: BookDraft, dispatcher: kotlinx.coroutines.CoroutineDispatcher = Dispatchers.Unconfined) = + AddBookViewModel( + coverDownloader = coverHost.downloader(java.io.File(context.cacheDir, "cover-preload"), dispatcher), + bookRepository = bookRepository, + locationRepository = locationRepository, + settingsStore = settingsStore, + initialDraft = draft, + ) + + @Test + fun `a cover carried in from a search hit is preloaded and saved as the book's cover file`() = runTest { + coverHost.serve(coverUrl, byteArrayOf(7, 7)) + val vm = viewModelWithDraft(BookDraft(title = "Dune", coverSourceUrl = coverUrl)) + val preloaded = (vm.uiState.first { it.cover is CoverPreloadState.Ready }.cover as CoverPreloadState.Ready).file + + val bookId = checkNotNull(vm.performSave(clearAfterSave = false)) + + val saved = checkNotNull(bookRepository.getById(bookId)) + assertTrue(java.io.File(checkNotNull(saved.localCoverPath)).readBytes().contentEquals(byteArrayOf(7, 7))) + assertTrue("the preload is deleted once createBook has copied it", !preloaded.exists()) + } + + @Test + fun `save waits for a cover download that is still running`() = runTest { + coverHost.serve(coverUrl) + coverHost.hold() + val vm = viewModelWithDraft(BookDraft(title = "Dune", coverSourceUrl = coverUrl), Dispatchers.IO) + Thread { Thread.sleep(200); coverHost.release() }.start() + + val bookId = checkNotNull(vm.performSave(clearAfterSave = false)) + + assertTrue(checkNotNull(bookRepository.getById(bookId)).localCoverPath != null) + } + + @Test + fun `a cover that already failed when Save was tapped does not block the save`() = runTest { + coverHost.reset(coverUrl) + val vm = viewModelWithDraft(BookDraft(title = "Dune", coverSourceUrl = coverUrl)) + vm.uiState.first { it.cover is CoverPreloadState.Failed } + + val bookId = checkNotNull(vm.performSave(clearAfterSave = false)) + + val saved = checkNotNull(bookRepository.getById(bookId)) + assertNull(saved.localCoverPath) + assertEquals(coverUrl, saved.coverSourceUrl) + } + + @Test + fun `a cover that fails while Save waits stops the save and keeps the form`() = runTest { + coverHost.fail(coverUrl, 404) + coverHost.hold() + val vm = viewModelWithDraft(BookDraft(title = "Dune", coverSourceUrl = coverUrl), Dispatchers.IO) + Thread { Thread.sleep(200); coverHost.release() }.start() + + assertNull(vm.performSave(clearAfterSave = false)) + + val state = vm.uiState.first { it.cover is CoverPreloadState.Failed } + assertEquals("Dune", state.draft.title) + assertTrue(!state.isSaving) + assertTrue(bookRepository.observeAll().first().isEmpty()) + } + + @Test + fun `a failing duplicate lookup shows no warning instead of crashing`() = runTest { + val vm = AddBookViewModel( + coverDownloader = coverHost.downloader(java.io.File(context.cacheDir, "cover-preload")), + bookRepository = BookRepository(ThrowingFindByIsbnBookDao(db.bookDao(), IllegalStateException("disk full")), context), + locationRepository = locationRepository, + settingsStore = settingsStore, + ) + + vm.onIsbnChanged("9780441013593") // must not throw out of the launched check + + assertEquals(DuplicateStatus.New, vm.uiState.first().duplicate) + } + + private class ThrowingFindByIsbnBookDao( + private val delegate: BookDao, + private val toThrow: Throwable, + ) : BookDao by delegate { + override suspend fun findByIsbn13(isbn13: String): BookEntity? = throw toThrow + } + private fun viewModelWithThrowingCreateBook(toThrow: Throwable): AddBookViewModel = AddBookViewModel( + coverDownloader = coverHost.downloader(java.io.File(context.cacheDir, "cover-preload")), bookRepository = BookRepository(ThrowingUpsertBookDao(db.bookDao(), toThrow), context), locationRepository = locationRepository, settingsStore = settingsStore, diff --git a/app/app/src/test/java/org/modg/bookshelf/ui/library/LibraryViewModelTest.kt b/app/app/src/test/java/org/modg/bookshelf/ui/library/LibraryViewModelTest.kt index f6ac234..c41df80 100644 --- a/app/app/src/test/java/org/modg/bookshelf/ui/library/LibraryViewModelTest.kt +++ b/app/app/src/test/java/org/modg/bookshelf/ui/library/LibraryViewModelTest.kt @@ -34,6 +34,9 @@ import org.modg.bookshelf.data.metadata.MetadataRepository import org.modg.bookshelf.data.metadata.OnlineBook import org.modg.bookshelf.data.prefs.SettingsStore import org.modg.bookshelf.data.repo.BookRepository +import org.modg.bookshelf.data.repo.CoverDownloader +import org.modg.bookshelf.data.repo.CoverPreloadState +import org.modg.bookshelf.data.repo.FakeCoverHost import org.modg.bookshelf.data.repo.LocationRepository import org.modg.bookshelf.data.repo.SyncEngine import org.modg.bookshelf.data.repo.decodeAuthors @@ -59,6 +62,7 @@ class LibraryViewModelTest { private lateinit var locationRepository: LocationRepository private lateinit var bookRepository: BookRepository private lateinit var context: android.content.Context + private val coverHost = FakeCoverHost() @Before fun setUp() { @@ -78,7 +82,12 @@ class LibraryViewModelTest { Dispatchers.resetMain() } - private fun viewModel(searchHttpClient: OkHttpClient = OkHttpClient()): LibraryViewModel = LibraryViewModel( + private fun viewModel( + searchHttpClient: OkHttpClient = OkHttpClient(), + coverDownloader: CoverDownloader = coverHost.downloader(java.io.File(context.cacheDir, "cover-preload")), + ): LibraryViewModel = LibraryViewModel( + coverDownloader = coverDownloader, + computeDispatcher = Dispatchers.Main, bookRepository = bookRepository, locationRepository = locationRepository, syncEngine = SyncEngine(ApiProvider { null }, db.bookDao(), db.bookcaseDao(), db.shelfDao(), settingsStore), @@ -200,6 +209,7 @@ class LibraryViewModelTest { coverUrl = "https://example.com/cover.jpg", isbn13 = "9780441013593", ) + coverHost.serve("https://example.com/cover.jpg") vm.onOnlineResultTapped(onlineBook) vm.onSaveSheetShelfSelected(shelfId) @@ -241,6 +251,8 @@ class LibraryViewModelTest { fun `a throwing save keeps the sheet open with an error and does not throw`() = runTest { val throwingBookRepository = BookRepository(ThrowingUpsertBookDao(db.bookDao(), IllegalStateException("disk full")), context) val vm = LibraryViewModel( + coverDownloader = coverHost.downloader(java.io.File(context.cacheDir, "cover-preload")), + computeDispatcher = Dispatchers.Main, bookRepository = throwingBookRepository, locationRepository = locationRepository, syncEngine = SyncEngine(ApiProvider { null }, db.bookDao(), db.bookcaseDao(), db.shelfDao(), settingsStore), @@ -263,6 +275,8 @@ class LibraryViewModelTest { @Test fun `a CancellationException during save propagates rather than being swallowed`() = runTest { val vm = LibraryViewModel( + coverDownloader = coverHost.downloader(java.io.File(context.cacheDir, "cover-preload")), + computeDispatcher = Dispatchers.Main, bookRepository = BookRepository(ThrowingUpsertBookDao(db.bookDao(), CancellationException("scope cancelled")), context), locationRepository = locationRepository, syncEngine = SyncEngine(ApiProvider { null }, db.bookDao(), db.bookcaseDao(), db.shelfDao(), settingsStore), @@ -294,6 +308,94 @@ class LibraryViewModelTest { assertEquals(1, bookRepository.observeAll().first().size) } + // --- review fixes (2026-09-16) --- + + @Test + fun `an owned edition reached only through an online ISBN is shown in inLibrary, not dropped`() = runTest { + // Its own text doesn't match the query at all; only the OL work's ISBN list ties it in. + bookRepository.createBook(title = "Warrior of the Hittites", isbn13 = "9781883937386") + val vm = viewModel(fakeSearchClient(olBody = olHit("Hittite Warrior", "Joanne Williamson", isbn = "1883937388"), gbBody = gbEmpty())) + vm.uiState.first { it.books.isNotEmpty() } // the owned book has loaded from Room before the search merges against it + vm.onQueryChange("hittite williamson") + vm.submitOnlineSearch() + + val done = vm.onlineSearch.first { it is OnlineSearchState.Done } as OnlineSearchState.Done + + assertEquals(listOf("Warrior of the Hittites"), done.inLibrary.map { it.title }) + assertTrue(done.online.isEmpty()) + } + + @Test + fun `the shelf filter narrows inLibrary and counts what it hides instead of dropping it`() = runTest { + val bookcaseId = locationRepository.createBookcase(name = "Living Room") + val top = locationRepository.createShelf(bookcaseId, label = "Top") + val bottom = locationRepository.createShelf(bookcaseId, label = "Bottom") + bookRepository.createBook(title = "Dune", authors = listOf("Frank Herbert"), shelfId = top) + bookRepository.createBook(title = "Dune Messiah", authors = listOf("Frank Herbert"), shelfId = bottom) + val vm = viewModel(fakeSearchClient(olBody = olEmpty(), gbBody = gbEmpty())) + vm.onFilterChange(LibraryFilter.Shelf(top)) + vm.onQueryChange("dune herbert") + vm.submitOnlineSearch() + + val done = vm.onlineSearch.first { it is OnlineSearchState.Done && it.inLibraryOutsideFilter > 0 } as OnlineSearchState.Done + + assertEquals(listOf("Dune"), done.inLibrary.map { it.title }) + assertEquals(1, done.inLibraryOutsideFilter) + } + + @Test + fun `Edit details closes the sheet, so returning to the library can't save the same book twice`() = runTest { + val cover = "https://covers.example.org/dune.jpg" + coverHost.serve(cover) + val vm = viewModel() + val book = OnlineBook(title = "Dune", coverUrl = cover) + vm.onOnlineResultTapped(book) + val preloaded = (vm.cover.first { it is CoverPreloadState.Ready } as CoverPreloadState.Ready).file + + val taken = vm.takeForEditing() + + assertEquals(book, taken?.first) + assertEquals(OnlineSaveSheetState.Hidden, vm.saveSheet.value) + assertTrue("the sheet's preloaded cover must not be left behind", !preloaded.exists()) + } + + @Test + fun `save waits for a cover still downloading and stores the cover file with the book`() = runTest { + val cover = "https://covers.example.org/dune.jpg" + coverHost.serve(cover, byteArrayOf(4, 2)) + coverHost.hold() + val vm = viewModel(coverDownloader = coverHost.downloader(java.io.File(context.cacheDir, "cover-preload"), Dispatchers.IO)) + vm.onOnlineResultTapped(OnlineBook(title = "Dune", coverUrl = cover)) + assertTrue(vm.cover.value is CoverPreloadState.Loading) + Thread { Thread.sleep(200); coverHost.release() }.start() + + val bookId = checkNotNull(vm.performSaveOnlineResult()) + + val saved = checkNotNull(bookRepository.getById(bookId)) + val localCover = java.io.File(checkNotNull(saved.localCoverPath)) + assertTrue(localCover.readBytes().contentEquals(byteArrayOf(4, 2))) + assertEquals(CoverPreloadState.None, vm.cover.value) + } + + @Test + fun `a cover that fails while save waits stops the save, and saving again goes ahead without it`() = runTest { + val cover = "https://covers.example.org/dune.jpg" + coverHost.fail(cover, 503) + coverHost.hold() + val vm = viewModel(coverDownloader = coverHost.downloader(java.io.File(context.cacheDir, "cover-preload"), Dispatchers.IO)) + vm.onOnlineResultTapped(OnlineBook(title = "Dune", coverUrl = cover)) + Thread { Thread.sleep(200); coverHost.release() }.start() + + assertNull("the user hasn't seen the failure yet — don't save past it", vm.performSaveOnlineResult()) + assertTrue(vm.cover.value is CoverPreloadState.Failed) + assertTrue(bookRepository.observeAll().first().isEmpty()) + + val bookId = checkNotNull(vm.performSaveOnlineResult()) // tapped "Save without cover" + val saved = checkNotNull(bookRepository.getById(bookId)) + assertNull(saved.localCoverPath) + assertEquals(cover, saved.coverSourceUrl) + } + private class ThrowingUpsertBookDao( private val delegate: BookDao, private val toThrow: Throwable, diff --git a/app/app/src/test/java/org/modg/bookshelf/ui/scan/ScanViewModelTest.kt b/app/app/src/test/java/org/modg/bookshelf/ui/scan/ScanViewModelTest.kt index 786f8ad..a4f41cd 100644 --- a/app/app/src/test/java/org/modg/bookshelf/ui/scan/ScanViewModelTest.kt +++ b/app/app/src/test/java/org/modg/bookshelf/ui/scan/ScanViewModelTest.kt @@ -3,7 +3,12 @@ package org.modg.bookshelf.ui.scan import androidx.room.Room import androidx.test.core.app.ApplicationProvider import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.first +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.resetMain +import kotlinx.coroutines.test.setMain import kotlinx.coroutines.test.runTest import kotlinx.serialization.json.Json import okhttp3.OkHttpClient @@ -21,6 +26,10 @@ import org.modg.bookshelf.data.metadata.BookMetadata import org.modg.bookshelf.data.metadata.MetadataRepository import org.modg.bookshelf.data.prefs.SettingsStore import org.modg.bookshelf.data.repo.BookRepository +import org.modg.bookshelf.data.repo.CoverPreloadState +import org.modg.bookshelf.data.repo.FakeCoverHost +import kotlinx.coroutines.async +import okhttp3.ResponseBody.Companion.toResponseBody import org.modg.bookshelf.data.repo.LocationRepository import org.robolectric.RobolectricTestRunner import org.robolectric.annotation.Config @@ -31,6 +40,7 @@ import org.robolectric.annotation.Config * so it survives to the next scanning session — except "Not shelved" (null), * which must never overwrite what's already remembered. */ +@OptIn(ExperimentalCoroutinesApi::class) @RunWith(RobolectricTestRunner::class) @Config(sdk = [34]) class ScanViewModelTest { @@ -41,9 +51,13 @@ class ScanViewModelTest { private lateinit var locationRepository: LocationRepository private lateinit var shelfId: String private lateinit var context: android.content.Context + private val coverHost = FakeCoverHost() @Before fun setUp() = runTest { + // The cover preload launches on viewModelScope (Dispatchers.Main); unconfined so it + // runs without idling Robolectric's main looper. + Dispatchers.setMain(UnconfinedTestDispatcher()) context = ApplicationProvider.getApplicationContext() db = Room.inMemoryDatabaseBuilder(context, BookshelfDatabase::class.java) .allowMainThreadQueries() @@ -54,6 +68,7 @@ class ScanViewModelTest { shelfId = locationRepository.createShelf(bookcaseId, label = "Top shelf") viewModel = ScanViewModel( + coverDownloader = coverHost.downloader(java.io.File(context.cacheDir, "cover-preload")), bookRepository = BookRepository(db.bookDao(), context), locationRepository = locationRepository, metadataRepository = MetadataRepository(OkHttpClient(), Json { ignoreUnknownKeys = true }), @@ -64,6 +79,7 @@ class ScanViewModelTest { @After fun tearDown() { db.close() + Dispatchers.resetMain() } @Test @@ -131,7 +147,126 @@ class ScanViewModelTest { } } + // --- review fixes (2026-09-16): cover preload + a guarded save --- + + @Test + fun `a successful lookup preloads its cover, and save stores it as the book's cover file`() = runTest { + val olFixture = fixture("openlibrary_success.json") + val coverUrl = "https://covers.openlibrary.org/b/id/675832-L.jpg" // the fixture's reported cover.large + coverHost.serve(coverUrl, byteArrayOf(5, 5, 5)) + val vm = ScanViewModel( + bookRepository = BookRepository(db.bookDao(), context), + locationRepository = locationRepository, + metadataRepository = MetadataRepository(fakeMetadataClient(olFixture), Json { ignoreUnknownKeys = true }), + settingsStore = settingsStore, + coverDownloader = coverHost.downloader(java.io.File(context.cacheDir, "cover-preload")), + ) + + vm.runLookup("9780201558029") + val found = vm.sheetState.value as ScanSheetState.Found + val preloaded = (vm.cover.first { it is CoverPreloadState.Ready } as CoverPreloadState.Ready).file + assertTrue(vm.performSave(found.isbn13, found.metadata)) + + val saved = db.bookDao().findByIsbn13("9780201558029") + checkNotNull(saved) + assertTrue(java.io.File(checkNotNull(saved.localCoverPath)).readBytes().contentEquals(byteArrayOf(5, 5, 5))) + assertTrue("the preload is discarded once saved", !preloaded.exists()) + assertEquals(ScanSheetState.Hidden, vm.sheetState.value) + } + + @Test + fun `a failing save is reported on the sheet instead of crashing`() = runTest { + val vm = viewModelWithThrowingUpsert(IllegalStateException("disk full")) + + val saved = vm.performSave("9780765326355", BookMetadata(title = "The Way of Kings")) // must not throw + + assertTrue(!saved) + assertEquals(ScanSaveStatus(isSaving = false, error = "IllegalStateException"), vm.saveStatus.value) + } + + @Test + fun `a CancellationException during save propagates`() = runTest { + val vm = viewModelWithThrowingUpsert(CancellationException("scope cancelled")) + try { + vm.performSave("9780765326355", BookMetadata(title = "The Way of Kings")) + fail("expected CancellationException to propagate") + } catch (e: CancellationException) { + // expected + } + } + + @Test + fun `a second tap while a save is in flight does not create a second book`() = runTest { + // Hold the first save inside its database write, so the second tap deterministically + // lands while it is in flight (not after it finished and closed the sheet). + val gate = kotlinx.coroutines.CompletableDeferred() + val vm = ScanViewModel( + bookRepository = BookRepository(GatedUpsertBookDao(db.bookDao(), gate), context), + locationRepository = locationRepository, + metadataRepository = MetadataRepository(OkHttpClient(), Json { ignoreUnknownKeys = true }), + settingsStore = settingsStore, + coverDownloader = coverHost.downloader(java.io.File(context.cacheDir, "cover-preload")), + ) + val first = kotlinx.coroutines.CoroutineScope(Dispatchers.Unconfined).async { + vm.performSave("9780765326355", BookMetadata(title = "The Way of Kings")) + } + assertTrue(vm.saveStatus.value.isSaving) + + val secondSaved = vm.performSave("9780765326355", BookMetadata(title = "The Way of Kings")) + gate.complete(Unit) + + assertTrue(!secondSaved) + assertTrue(first.await()) + assertEquals(1, db.bookDao().observeAll().first().size) + } + + private class GatedUpsertBookDao( + private val delegate: BookDao, + private val gate: kotlinx.coroutines.CompletableDeferred, + ) : BookDao by delegate { + override suspend fun upsert(book: BookEntity) { + gate.await() + delegate.upsert(book) + } + } + + private fun viewModelWithThrowingUpsert(toThrow: Throwable): ScanViewModel = ScanViewModel( + bookRepository = BookRepository(ThrowingUpsertBookDao(db.bookDao(), toThrow), context), + locationRepository = locationRepository, + metadataRepository = MetadataRepository(OkHttpClient(), Json { ignoreUnknownKeys = true }), + settingsStore = settingsStore, + coverDownloader = coverHost.downloader(java.io.File(context.cacheDir, "cover-preload")), + ) + + private class ThrowingUpsertBookDao( + private val delegate: BookDao, + private val toThrow: Throwable, + ) : BookDao by delegate { + override suspend fun upsert(book: BookEntity) = throw toThrow + } + + /** Open Library answers with [olBody]; Google Books has nothing. No sockets. */ + private fun fakeMetadataClient(olBody: String): OkHttpClient = OkHttpClient.Builder() + .addInterceptor { chain -> + val request = chain.request() + val (code, body) = if (request.url.host.contains("openlibrary")) 200 to olBody else 200 to """{"totalItems": 0}""" + okhttp3.Response.Builder() + .request(request) + .protocol(okhttp3.Protocol.HTTP_1_1) + .code(code) + .message("") + .body(body.toResponseBody(null)) + .build() + } + .build() + + private fun fixture(name: String): String = + checkNotNull(javaClass.classLoader?.getResourceAsStream("fixtures/$name")) { "missing fixture $name" } + .bufferedReader() + .readText() + private fun viewModelWithThrowingBookDao(toThrow: Throwable): ScanViewModel = ScanViewModel( + coverDownloader = coverHost.downloader(java.io.File(context.cacheDir, "cover-preload")), bookRepository = BookRepository(ThrowingFindByIsbnBookDao(db.bookDao(), toThrow), context), locationRepository = locationRepository, metadataRepository = MetadataRepository(OkHttpClient(), Json { ignoreUnknownKeys = true }), diff --git a/app/app/src/test/java/org/modg/bookshelf/ui/screens/LibraryScreenPaparazziTest.kt b/app/app/src/test/java/org/modg/bookshelf/ui/screens/LibraryScreenPaparazziTest.kt index 9d6b5e6..e52d144 100644 --- a/app/app/src/test/java/org/modg/bookshelf/ui/screens/LibraryScreenPaparazziTest.kt +++ b/app/app/src/test/java/org/modg/bookshelf/ui/screens/LibraryScreenPaparazziTest.kt @@ -32,6 +32,7 @@ import org.junit.Rule import org.junit.Test import org.modg.bookshelf.data.local.BookEntity import org.modg.bookshelf.data.metadata.OnlineBook +import org.modg.bookshelf.data.repo.CoverPreloadState import org.modg.bookshelf.data.metadata.SearchSource import org.modg.bookshelf.ui.components.BookshelfScaffold import org.modg.bookshelf.ui.components.EmptyState @@ -74,12 +75,15 @@ class LibraryScreenPaparazziTest { fun librarySearchBothSectionsLight() = snapshotBoth("library-search-both-sections") { SearchShell( localMatches = listOf(ScreenFixtures.books[1]), // "The Hobbit" -- already owned + // Owned "The Hobbit" is NOT repeated online — the merge puts it in exactly one + // section. The count line is an owned match hidden by the shelf filter. onlineSearch = OnlineSearchState.Done( query = "hobbit", inLibrary = listOf(ScreenFixtures.books[1]), - online = onlineResults, + online = onlineResults.drop(1), openLibraryFailure = null, googleBooksFailure = null, + inLibraryOutsideFilter = 1, ), ) } @@ -122,6 +126,26 @@ class LibraryScreenPaparazziTest { } } + @Test + fun libraryOnlineSaveSheetCoverFailedLight() = snapshotBoth("library-online-save-sheet-cover-failed") { + Surface { + OnlineResultSaveSheet( + state = OnlineSaveSheetState.Shown( + book = onlineResults[0], + selectedShelfId = ScreenFixtures.topShelf.id, + ), + bookcases = ScreenFixtures.bookcases, + shelves = ScreenFixtures.shelves, + recentShelfId = ScreenFixtures.topShelf.id, + onShelfSelected = {}, + onSave = {}, + onEditDetails = {}, + onSkip = {}, + cover = CoverPreloadState.Failed("https://covers.openlibrary.org/b/id/1-M.jpg", "tls connection reset"), + ) + } + } + /** Two online hits: one merged from both sources, one Open-Library-only -- the shape [SearchResultMerger] actually produces. */ private val onlineResults = listOf( OnlineBook( @@ -179,6 +203,7 @@ class LibraryScreenPaparazziTest { LibrarySearchResultsContent( query = "hobbit", localMatches = localMatches, + inLibraryOutsideFilter = (onlineSearch as? OnlineSearchState.Done)?.inLibraryOutsideFilter ?: 0, onlineSearch = onlineSearch, onBookClick = {}, onSubmitOnlineSearch = {}, diff --git a/app/app/src/test/snapshots/images/org.modg.bookshelf.ui.screens_LibraryScreenPaparazziTest_libraryOnlineSaveSheetCoverFailedLight_library-online-save-sheet-cover-failed-dark.png b/app/app/src/test/snapshots/images/org.modg.bookshelf.ui.screens_LibraryScreenPaparazziTest_libraryOnlineSaveSheetCoverFailedLight_library-online-save-sheet-cover-failed-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..de8e353a4ed7a63e4e0b20b84f25cbe659542f8a GIT binary patch literal 25108 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 literal 39472 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

x&S-1SHWFq>&iepBI=nl)B6K8^>pfQ~Z?_}}$1 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≫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

Tl18|I;=*YzCeCr$BRD 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 literal 42680 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 POTbSV^$=MuXCC-p00<@( diff --git a/docs/HANDOFF.md b/docs/HANDOFF.md index 7cfe00e..5ebe193 100644 --- a/docs/HANDOFF.md +++ b/docs/HANDOFF.md @@ -766,3 +766,72 @@ Logs: `logs/wave-chain.log`, `logs/gate-.log`, `logs/WAVE9-DONE`, `logs/WA **The gate is not acceptance.** Still owed by the orchestrator for BOTH waves: read each `.summary`, read the diff, eyeball the new Paparazzi PNGs, check CancellationException is rethrown in every new catch, and check wave 10's dedupe judgement call in its report. + +## Waves 9 + 10 — REVIEWED 2026-09-16; accepted after a review-fix round +Chain finished 2026-09-15 18:28Z: `b600df6` (wave 9, 241 tests), `cf82cbc` (wave 10, 308). +Orchestrator independently re-ran assembleDebug / testDebugUnitTest / verifyPaparazziDebug on +both — green — then read the diffs and PNGs. The gate was right that they built; it could not +see these, all fixed directly by the orchestrator in the review-fix commit that follows: + +1. **"In your library" never showed the merged set.** The screen passed the plain text match + (`uiState.books`) instead of `OnlineSearchState.Done.inLibrary`, so an owned edition reached + only via an online ISBN was removed from "Online" and shown NOWHERE; wave 10's VM test + asserted `inLibrary` but nothing rendered it. Now rendered; the shelf filter applies to it + and hidden owned matches are counted ("1 more in your library, outside the current filter"). +2. **"Edit details" left the save sheet open** -> save from the add screen, Back, sheet still up + with Save enabled -> duplicate book. `LibraryViewModel.takeForEditing()` closes it. +3. **`rememberShelf` inside the save `try`** (add + online save): a failed prefs write after a + successful insert showed "Couldn't save" -> retry -> duplicate. Now after, and swallowed. +4. **Per-keystroke search on the main thread**: two regexes recompiled per call, every book + re-normalized per keystroke (~60ms/keystroke at 2,000 books on this server, measured with + jshell). Precompiled regexes, `LocalBookMatcher.index` rebuilt only when books change, + `flowOn(Dispatchers.Default)`. +5. **Add-by-hand duplicate check unguarded** (a Room throw from a keystroke would crash). Guarded. +6. **Scan screen save was unguarded** (wave 8's known gap): now catch/rethrow-cancellation, + in-flight guard, error line. `performSaveManualEntry` (post-lookup NotFound sheet) is STILL + unguarded — its sheet has no error UI; left as is. + +### Cover preload — design decided WITH the user, 2026-09-16 +User-reported: saving had a distinct delay even with the cover already visible on the sheet. +Cause: `createBook` downloaded the cover (fresh OkHttpClient) BEFORE writing the row, and on +failure silently saved with no `localCoverPath`. User rejected "save first, fetch cover in the +background" (a kill/crash loses the upload; a failure has nowhere to be shown). Their design: +- **Preload** (`data/repo/CoverPreload.kt`): the download starts when a sheet/screen gets a + cover URL — scan Found sheet, online save sheet on open (ONE result, never the whole list — + the user asked), add screen when "Edit details" carries a cover. Into `cacheDir/cover-preload/`. +- **Save waits** for it if still running; `createBook(coverFile=)` COPIES it to + `filesDir/covers/.jpg` (copy, so a failed insert leaves the preload for a retry; the copy + is deleted on insert failure). +- **Failure = Option A (user's choice)**: failure line + Retry on the sheet; Save becomes + "Save without cover". A download that fails WHILE Save waits stops the save (the user hasn't + seen it yet); one that had already failed when tapped proceeds without the file. +- **Cleanup**: preload discarded on skip/dismiss/save/onCleared; `BookshelfApplication` sweeps + files older than process start. NOT Coil's disk cache — public API, but Coil's eviction and + keys aren't a contract; the user agreed it wasn't worth depending on. +- Downloads use `metadataHttpClient` (never the PocketBase-token client). + +### Still open after review (not fixed — judgement calls or out of scope) +- Wave 9 has NO worker report (worker hit its wall clock mid-verification, see hazard #11). Its + undocumented calls: small EditNote FAB above Scan; no "More fields…" on the in-scan sheet; + `imePadding` rather than the prompt's `safeDrawingPadding` — needs an on-device look. +- Dedupe (wave 10's own analysis, agreed): false merges for same-author colon series + ("Star Wars: X"/"Star Wars: Y") and same-title same-surname authors; one bad edge chains via + union-find; misses translations, surname spelling variants, author-less books. +- Retry re-runs BOTH sources (an OL failure spends a Google Books request). +- Search toolbar still renders as a blank bar in Paparazzi (pre-existing), so no snapshot shows + a query. Online result rows are a non-lazy Column (≤ ~40 thumbnails per search; accepted). + +### HAZARD #11 — the lease did NOT stop a freeze; the wall clock counts frozen time +Chain took the lease 2026-09-13 17:30 (expire 3600s). The sprite froze ~17:45 — BEFORE the +first renewal, lease still valid — and stayed frozen ~26.5h until the user connected 09-14 +20:21. No reboot (uptime continuous); processes paused (`ps` start times shifted by the gap). +It froze again during K-manual-fix (09-14 20:37 -> 23:43 -> 09-15 11:27 log gaps). +Consequences: (a) an unattended chain only progresses while someone is connected — do not +promise the user overnight progress; (b) `run-task.sh`'s 24h `wall=` counts the freeze, so +K-manual was killed as "WALL CLOCK EXCEEDED" seconds after waking, mid-test-run, with no report. +The 8 red tests the gate then saw were its own unfinished tests. If this recurs, measure elapsed +time with the monotonic clock (`/proc/uptime`), which does not advance while frozen. + +Also seen 2026-09-16: Claude Code's background-task runner killed Gradle runs "because the system +is running low on memory" even with ~6GB available (8GB box, no swap; Kotlin daemon ~2.1GB, +Gradle daemon ~1.2GB). Foreground runs of the same command succeeded. diff --git a/logs/CHAIN-DONE b/logs/CHAIN-DONE new file mode 100644 index 0000000..0ce91e4 --- /dev/null +++ b/logs/CHAIN-DONE @@ -0,0 +1 @@ +chain complete 2026-09-15T18:28:39+00:00 diff --git a/logs/WAVE10-DONE b/logs/WAVE10-DONE new file mode 100644 index 0000000..6e30d50 --- /dev/null +++ b/logs/WAVE10-DONE @@ -0,0 +1,4 @@ +=== WAVE 10 (L-search) passed the chain gate 2026-09-15T18:28:39+00:00 === +commit cf82cbc (pushed); tests 308; worker $10.452535, 157 turns +Mechanical gate only. Orchestrator must still review: read logs/L-search.summary, +eyeball new Paparazzi PNGs, and read the diff (git show --stat cf82cbc).