From cf82cbc484b0256f100ff4c72274798909927432 Mon Sep 17 00:00:00 2001 From: Sprite Date: Tue, 15 Sep 2026 18:28:37 +0000 Subject: [PATCH] Wave 10 (L-search): search own library + Open Library + Google Books, deduplicated Committed by tasks/wave-chain.sh after its mechanical gate passed (assembleDebug, testDebugUnitTest = 308 tests, verifyPaparazziDebug, no build files touched, no "always 'false'"). ORCHESTRATOR REVIEW STILL PENDING. Prompt: tasks/L-search.txt. Worker: $10.452535, 157 turns. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01RWoivrRUEmJLFFwbsrGkqQ --- .../java/org/modg/bookshelf/AppContainer.kt | 4 + .../data/metadata/BookSearchRepository.kt | 33 ++ .../data/metadata/GoogleBooksClient.kt | 6 +- .../data/metadata/GoogleBooksDtos.kt | 40 +- .../data/metadata/GoogleBooksSearchClient.kt | 88 +++++ .../bookshelf/data/metadata/OnlineBook.kt | 23 ++ .../data/metadata/OpenLibrarySearchClient.kt | 87 +++++ .../data/metadata/OpenLibrarySearchDtos.kt | 46 +++ .../bookshelf/data/metadata/RetryPolicy.kt | 78 +++- .../modg/bookshelf/data/metadata/SearchHit.kt | 21 + .../data/metadata/SearchResultMerger.kt | 176 +++++++++ .../data/metadata/SearchSourceResult.kt | 35 ++ .../data/metadata/TextNormalization.kt | 48 +++ .../modg/bookshelf/ui/add/AddBookModels.kt | 36 +- .../modg/bookshelf/ui/add/AddBookScreen.kt | 19 +- .../modg/bookshelf/ui/add/AddBookViewModel.kt | 1 + .../bookshelf/ui/library/LibraryModels.kt | 86 ++++ .../bookshelf/ui/library/LibraryScreen.kt | 369 +++++++++++++++++- .../bookshelf/ui/library/LibraryViewModel.kt | 215 +++++++++- .../bookshelf/ui/library/LocalBookMatcher.kt | 41 ++ .../modg/bookshelf/ui/nav/BookshelfNavHost.kt | 56 ++- .../java/org/modg/bookshelf/ui/nav/Routes.kt | 53 ++- .../metadata/GoogleBooksSearchClientTest.kt | 143 +++++++ .../metadata/OpenLibrarySearchClientTest.kt | 139 +++++++ .../data/metadata/SearchResultMergerTest.kt | 240 ++++++++++++ .../ui/library/LibraryViewModelTest.kt | 348 +++++++++++++++++ .../ui/library/LocalBookMatcherTest.kt | 74 ++++ .../org/modg/bookshelf/ui/nav/RoutesTest.kt | 35 ++ .../ui/screens/LibraryScreenPaparazziTest.kt | 122 ++++++ .../fixtures/openlibrary_search_no_docs.json | 5 + .../fixtures/openlibrary_search_success.json | 22 ++ ...etLight_library-online-save-sheet-dark.png | Bin 0 -> 19277 bytes ...tLight_library-online-save-sheet-light.png | Bin 0 -> 20460 bytes ...ight_library-search-both-sections-dark.png | Bin 0 -> 39472 bytes ...ght_library-search-both-sections-light.png | Bin 0 -> 42680 bytes ...adingLight_library-search-loading-dark.png | Bin 0 -> 29863 bytes ...dingLight_library-search-loading-light.png | Bin 0 -> 33129 bytes ...edLight_library-search-one-failed-dark.png | Bin 0 -> 43617 bytes ...dLight_library-search-one-failed-light.png | Bin 0 -> 46747 bytes logs/L-search.sid | 1 + logs/L-search.summary | 12 + logs/WAVE9-DONE | 4 + 42 files changed, 2662 insertions(+), 44 deletions(-) create mode 100644 app/app/src/main/java/org/modg/bookshelf/data/metadata/BookSearchRepository.kt create mode 100644 app/app/src/main/java/org/modg/bookshelf/data/metadata/GoogleBooksSearchClient.kt create mode 100644 app/app/src/main/java/org/modg/bookshelf/data/metadata/OnlineBook.kt create mode 100644 app/app/src/main/java/org/modg/bookshelf/data/metadata/OpenLibrarySearchClient.kt create mode 100644 app/app/src/main/java/org/modg/bookshelf/data/metadata/OpenLibrarySearchDtos.kt create mode 100644 app/app/src/main/java/org/modg/bookshelf/data/metadata/SearchHit.kt create mode 100644 app/app/src/main/java/org/modg/bookshelf/data/metadata/SearchResultMerger.kt create mode 100644 app/app/src/main/java/org/modg/bookshelf/data/metadata/SearchSourceResult.kt create mode 100644 app/app/src/main/java/org/modg/bookshelf/data/metadata/TextNormalization.kt create mode 100644 app/app/src/main/java/org/modg/bookshelf/ui/library/LocalBookMatcher.kt create mode 100644 app/app/src/test/java/org/modg/bookshelf/data/metadata/GoogleBooksSearchClientTest.kt create mode 100644 app/app/src/test/java/org/modg/bookshelf/data/metadata/OpenLibrarySearchClientTest.kt create mode 100644 app/app/src/test/java/org/modg/bookshelf/data/metadata/SearchResultMergerTest.kt create mode 100644 app/app/src/test/java/org/modg/bookshelf/ui/library/LibraryViewModelTest.kt create mode 100644 app/app/src/test/java/org/modg/bookshelf/ui/library/LocalBookMatcherTest.kt create mode 100644 app/app/src/test/resources/fixtures/openlibrary_search_no_docs.json create mode 100644 app/app/src/test/resources/fixtures/openlibrary_search_success.json create mode 100644 app/app/src/test/snapshots/images/org.modg.bookshelf.ui.screens_LibraryScreenPaparazziTest_libraryOnlineSaveSheetLight_library-online-save-sheet-dark.png create mode 100644 app/app/src/test/snapshots/images/org.modg.bookshelf.ui.screens_LibraryScreenPaparazziTest_libraryOnlineSaveSheetLight_library-online-save-sheet-light.png create mode 100644 app/app/src/test/snapshots/images/org.modg.bookshelf.ui.screens_LibraryScreenPaparazziTest_librarySearchBothSectionsLight_library-search-both-sections-dark.png create mode 100644 app/app/src/test/snapshots/images/org.modg.bookshelf.ui.screens_LibraryScreenPaparazziTest_librarySearchBothSectionsLight_library-search-both-sections-light.png create mode 100644 app/app/src/test/snapshots/images/org.modg.bookshelf.ui.screens_LibraryScreenPaparazziTest_librarySearchLoadingLight_library-search-loading-dark.png create mode 100644 app/app/src/test/snapshots/images/org.modg.bookshelf.ui.screens_LibraryScreenPaparazziTest_librarySearchLoadingLight_library-search-loading-light.png create mode 100644 app/app/src/test/snapshots/images/org.modg.bookshelf.ui.screens_LibraryScreenPaparazziTest_librarySearchOneSourceFailedLight_library-search-one-failed-dark.png create mode 100644 app/app/src/test/snapshots/images/org.modg.bookshelf.ui.screens_LibraryScreenPaparazziTest_librarySearchOneSourceFailedLight_library-search-one-failed-light.png create mode 100644 logs/L-search.sid create mode 100644 logs/L-search.summary create mode 100644 logs/WAVE9-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 7186524..7040ceb 100644 --- a/app/app/src/main/java/org/modg/bookshelf/AppContainer.kt +++ b/app/app/src/main/java/org/modg/bookshelf/AppContainer.kt @@ -11,6 +11,7 @@ import okhttp3.MediaType.Companion.toMediaType 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.metadata.MetadataRepository import org.modg.bookshelf.diagnostics.CrashReporter import org.modg.bookshelf.data.prefs.SettingsStore @@ -120,6 +121,9 @@ class AppContainer(private val context: Context) { val metadataRepository by lazy { MetadataRepository(metadataHttpClient, json, BuildConfig.GOOGLE_BOOKS_API_KEY) } + /** 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) } + val syncEngine by lazy { SyncEngine( apiProvider = apiProvider, diff --git a/app/app/src/main/java/org/modg/bookshelf/data/metadata/BookSearchRepository.kt b/app/app/src/main/java/org/modg/bookshelf/data/metadata/BookSearchRepository.kt new file mode 100644 index 0000000..a45aba0 --- /dev/null +++ b/app/app/src/main/java/org/modg/bookshelf/data/metadata/BookSearchRepository.kt @@ -0,0 +1,33 @@ +package org.modg.bookshelf.data.metadata + +import kotlinx.coroutines.async +import kotlinx.coroutines.coroutineScope +import kotlinx.serialization.json.Json +import okhttp3.OkHttpClient + +/** + * Entry point for the library screen's online title/author search — [MetadataRepository]'s + * counterpart for search rather than by-ISBN lookup. Unlike [MetadataRepository.lookup], + * this does NOT combine the two sources into one outcome: the library screen needs each + * source's own [SearchSourceResult] so it can show one source's results alongside the + * other's failure-with-retry (task: "per-source failure ... while still showing the + * OTHER source's results"). [org.modg.bookshelf.data.metadata.SearchResultMerger] is + * where the actual merge/dedupe happens, once local books are available to merge in too. + */ +class BookSearchRepository( + private val openLibraryClient: OpenLibrarySearchClient, + private val googleBooksClient: GoogleBooksSearchClient, +) { + constructor(httpClient: OkHttpClient, json: Json, googleBooksApiKey: String = "") : this( + OpenLibrarySearchClient(httpClient, json), + GoogleBooksSearchClient(httpClient, json, googleBooksApiKey), + ) + + data class Results(val openLibrary: SearchSourceResult, val googleBooks: SearchSourceResult) + + suspend fun search(query: String): Results = coroutineScope { + val openLibrary = async { openLibraryClient.search(query) } + val googleBooks = async { googleBooksClient.search(query) } + Results(openLibrary.await(), googleBooks.await()) + } +} diff --git a/app/app/src/main/java/org/modg/bookshelf/data/metadata/GoogleBooksClient.kt b/app/app/src/main/java/org/modg/bookshelf/data/metadata/GoogleBooksClient.kt index 8ce5877..3869b5d 100644 --- a/app/app/src/main/java/org/modg/bookshelf/data/metadata/GoogleBooksClient.kt +++ b/app/app/src/main/java/org/modg/bookshelf/data/metadata/GoogleBooksClient.kt @@ -130,9 +130,5 @@ class GoogleBooksClient( * will. Package-visible so the scrub itself is unit-testable without a socket. */ internal fun redact(result: SourceResult): SourceResult = - if (result is SourceResult.Failed && apiKey.isNotBlank() && result.reason.contains(apiKey)) { - result.copy(reason = result.reason.replace(apiKey, "[REDACTED]")) - } else { - result - } + if (result is SourceResult.Failed) result.copy(reason = redactApiKey(result.reason, apiKey)) else result } diff --git a/app/app/src/main/java/org/modg/bookshelf/data/metadata/GoogleBooksDtos.kt b/app/app/src/main/java/org/modg/bookshelf/data/metadata/GoogleBooksDtos.kt index f5361a8..3f12b9a 100644 --- a/app/app/src/main/java/org/modg/bookshelf/data/metadata/GoogleBooksDtos.kt +++ b/app/app/src/main/java/org/modg/bookshelf/data/metadata/GoogleBooksDtos.kt @@ -52,8 +52,46 @@ fun GoogleBooksVolumeInfoDto.toBookMetadata(): BookMetadata = BookMetadata( coverUrl = normalizeCoverUrl(imageLinks?.thumbnail ?: imageLinks?.smallThumbnail), ) -private fun normalizeCoverUrl(raw: String?): String? { +/** Forces https + zoom=2, per SPEC's cover chain. Internal (not private) so [GoogleBooksSearchClient] reuses it too. */ +internal fun normalizeCoverUrl(raw: String?): String? { if (raw.isNullOrBlank()) return null val https = raw.replaceFirst("http://", "https://") return if ("zoom=" in https) https.replace(Regex("zoom=\\d+"), "zoom=2") else "$https&zoom=2" } + +/** + * Maps one volume to a [SearchHit] for the library search screen. [isbn13s] collects + * every industry identifier this volume reports, normalized to ISBN-13 regardless of + * its declared type (ISBN-10 converted) — a volume usually carries 0 or 1, unlike an + * Open Library work's dozens, which is exactly what [SearchResultMerger]'s ISBN rule + * depends on to tell the two sources apart. + */ +fun GoogleBooksVolumeInfoDto.toSearchHit(): SearchHit = SearchHit( + title = title, + subtitle = subtitle, + authors = authors, + year = plausibleYear(publishedDate), + publisher = publisher, + pageCount = pageCount, + coverUrl = normalizeCoverUrl(imageLinks?.thumbnail ?: imageLinks?.smallThumbnail), + isbn13s = industryIdentifiers.mapNotNull { it.identifier }.mapNotNull(IsbnUtils::toIsbn13).distinct(), +) + +/** + * A [publishedDate] prefix is only trustworthy as a year if it's exactly four digits — + * real GB data has been observed to return garbage like "101-01-01" (METADATA-SOURCES.md), + * which this rejects because its first four characters ("101-") aren't all digits. + */ +internal fun plausibleYear(publishedDate: String?): Int? { + val prefix = publishedDate?.take(4) ?: return null + if (prefix.length != 4 || !prefix.all(Char::isDigit)) return null + return prefix.toIntOrNull()?.takeIf { it in 1000..2999 } +} + +/** + * [SourceResult.Failed.reason]/[SearchSourceResult.Failed.reason] must never carry the + * API key — shared by [GoogleBooksClient.redact] and [GoogleBooksSearchClient.redact] + * so there is one scrub implementation, not two that could drift apart. + */ +internal fun redactApiKey(reason: String, apiKey: String): String = + if (apiKey.isNotBlank() && reason.contains(apiKey)) reason.replace(apiKey, "[REDACTED]") else reason diff --git a/app/app/src/main/java/org/modg/bookshelf/data/metadata/GoogleBooksSearchClient.kt b/app/app/src/main/java/org/modg/bookshelf/data/metadata/GoogleBooksSearchClient.kt new file mode 100644 index 0000000..96410a9 --- /dev/null +++ b/app/app/src/main/java/org/modg/bookshelf/data/metadata/GoogleBooksSearchClient.kt @@ -0,0 +1,88 @@ +package org.modg.bookshelf.data.metadata + +import java.io.IOException +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import kotlinx.serialization.SerializationException +import kotlinx.serialization.json.Json +import okhttp3.HttpUrl +import okhttp3.OkHttpClient +import okhttp3.Request + +/** + * Google Books title/author search — `volumes?q=`, distinct from [GoogleBooksClient]'s + * by-ISBN lookup but sharing its DTOs ([GoogleBooksResponseDto]/[GoogleBooksVolumeInfoDto]): + * a search result and an ISBN lookup result are both a list of volumes, just a different + * query. Same key handling, same redaction, same never-throws contract — see + * [GoogleBooksClient]'s KDoc. + */ +class GoogleBooksSearchClient( + private val httpClient: OkHttpClient, + json: Json, + private val apiKey: String = "", +) { + private val json = Json(from = json) { ignoreUnknownKeys = true } + + suspend fun search(query: String): SearchSourceResult = + withContext(Dispatchers.IO) { + withSearchRetry(retryRateLimitedOnce = apiKey.isNotBlank()) { fetch(query) } + } + + /** Single un-retried attempt, for tests that need to count calls. */ + internal suspend fun searchOnce(query: String): SearchSourceResult = + withContext(Dispatchers.IO) { fetch(query) } + + private fun fetch(query: String): SearchSourceResult = redact( + try { + val request = Request.Builder().url(requestUrl(query)).build() + httpClient.newCall(request).execute().use { response -> + classify(response.code, response.body.string(), response.header("Retry-After")) + } + } catch (e: CancellationException) { + throw e + } catch (e: IOException) { + SearchSourceResult.fromException(e) + } catch (e: Throwable) { + SearchSourceResult.Failed("unexpected: ${e.javaClass.simpleName}", FailureKind.UNEXPECTED) + }, + ) + + /** Package-visible pure function — no socket — the key is appended only when non-blank, same as the ISBN lookup. */ + internal fun requestUrl(query: String): String { + val builder = HttpUrl.Builder() + .scheme("https") + .host("www.googleapis.com") + .addPathSegments("books/v1/volumes") + .addQueryParameter("q", query) + .addQueryParameter("maxResults", "20") + .addQueryParameter("printType", "books") + if (apiKey.isNotBlank()) { + builder.addQueryParameter("key", apiKey) + } + return builder.build().toString() + } + + /** Package-visible pure function — no socket — so it's exhaustively unit-testable offline. */ + internal fun classify(httpCode: Int, body: String?, retryAfterHeader: String? = null): SearchSourceResult { + if (httpCode !in 200..299) return SearchSourceResult.fromHttpCode(httpCode, retryAfterHeader) + if (body.isNullOrBlank()) return SearchSourceResult.Failed("empty body", FailureKind.MALFORMED) + return try { + val dto = json.decodeFromString(GoogleBooksResponseDto.serializer(), body) + val hits = dto.items.mapNotNull { it.volumeInfo?.toSearchHit() } + if (hits.isEmpty()) SearchSourceResult.NotFound else SearchSourceResult.Found(hits) + } catch (e: SerializationException) { + SearchSourceResult.Failed("malformed json", FailureKind.MALFORMED) + } catch (e: IllegalArgumentException) { + SearchSourceResult.Failed("malformed json", FailureKind.MALFORMED) + } + } + + /** Package-visible for offline fixture tests — parses a raw response body with no network involved. */ + internal fun parseResponse(body: String): List = + (classify(200, body) as? SearchSourceResult.Found)?.hits.orEmpty() + + /** Same reasoning as [GoogleBooksClient.redact] — the key must never survive into a shown/logged reason. */ + internal fun redact(result: SearchSourceResult): SearchSourceResult = + if (result is SearchSourceResult.Failed) result.copy(reason = redactApiKey(result.reason, apiKey)) else result +} diff --git a/app/app/src/main/java/org/modg/bookshelf/data/metadata/OnlineBook.kt b/app/app/src/main/java/org/modg/bookshelf/data/metadata/OnlineBook.kt new file mode 100644 index 0000000..3038383 --- /dev/null +++ b/app/app/src/main/java/org/modg/bookshelf/data/metadata/OnlineBook.kt @@ -0,0 +1,23 @@ +package org.modg.bookshelf.data.metadata + +/** Which source(s) contributed to a merged [OnlineBook] — surfaced so the UI/enrichment can tell. */ +enum class SearchSource { OPEN_LIBRARY, GOOGLE_BOOKS } + +/** + * One deduplicated online search result, after [SearchResultMerger] has grouped + * together every [SearchHit] (and any matching local [org.modg.bookshelf.data.local.BookEntity]) + * that identify the same book. [isbn13] is set only when [SearchResultMerger]'s ISBN + * rule could resolve a single unambiguous edition — see its KDoc; otherwise null, + * never guessed. + */ +data class OnlineBook( + val title: String?, + val subtitle: String? = null, + val authors: List = emptyList(), + val year: Int? = null, + val publisher: String? = null, + val pageCount: Int? = null, + val coverUrl: String? = null, + val isbn13: String? = null, + val sources: Set = emptySet(), +) diff --git a/app/app/src/main/java/org/modg/bookshelf/data/metadata/OpenLibrarySearchClient.kt b/app/app/src/main/java/org/modg/bookshelf/data/metadata/OpenLibrarySearchClient.kt new file mode 100644 index 0000000..b976513 --- /dev/null +++ b/app/app/src/main/java/org/modg/bookshelf/data/metadata/OpenLibrarySearchClient.kt @@ -0,0 +1,87 @@ +package org.modg.bookshelf.data.metadata + +import java.io.IOException +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import kotlinx.serialization.SerializationException +import kotlinx.serialization.json.Json +import okhttp3.HttpUrl +import okhttp3.OkHttpClient +import okhttp3.Request + +/** + * Open Library title/author search — `search.json`, distinct from [OpenLibraryClient]'s + * by-ISBN `api/books` lookup. Same never-throws contract as [OpenLibraryClient] (see its + * KDoc): every outcome is a [SearchSourceResult], [CancellationException] propagates, + * anything else becomes [FailureKind.UNEXPECTED]. + */ +class OpenLibrarySearchClient( + private val httpClient: OkHttpClient, + json: Json, +) { + private val json = Json(from = json) { ignoreUnknownKeys = true } + + /** Retries transient failures per [RetryPolicy] — same 13% TLS-reset story as the ISBN lookup. */ + suspend fun search(query: String): SearchSourceResult = + withContext(Dispatchers.IO) { withSearchRetry { fetch(query) } } + + /** Single un-retried attempt, for tests that need to count calls. */ + internal suspend fun searchOnce(query: String): SearchSourceResult = + withContext(Dispatchers.IO) { fetch(query) } + + private fun fetch(query: String): SearchSourceResult = try { + val request = Request.Builder().url(requestUrl(query)).build() + httpClient.newCall(request).execute().use { response -> + classify(response.code, response.body.string()) + } + } catch (e: CancellationException) { + throw e + } catch (e: IOException) { + SearchSourceResult.fromException(e) + } catch (e: Throwable) { + SearchSourceResult.Failed("unexpected: ${e.javaClass.simpleName}", FailureKind.UNEXPECTED) + } + + /** + * Package-visible pure function — no socket — for offline URL tests. `fields=` + * is load-bearing: without it `isbn` is absent from every result (verified live, + * see docs/METADATA-SOURCES.md). Built with [HttpUrl.Builder] so [query] is + * correctly percent-encoded regardless of what punctuation it contains. + */ + internal fun requestUrl(query: String): String = + HttpUrl.Builder() + .scheme("https") + .host("openlibrary.org") + .addPathSegment("search.json") + .addQueryParameter("q", query) + .addQueryParameter("limit", "20") + .addQueryParameter( + "fields", + "key,title,subtitle,author_name,first_publish_year,edition_count,cover_i,isbn,publisher,number_of_pages_median", + ) + .build() + .toString() + + /** + * Package-visible pure function — no socket — so it's exhaustively unit-testable + * offline. An empty `docs` list is [SearchSourceResult.NotFound] (OL answered + * authoritatively), never conflated with a transport [SearchSourceResult.Failed]. + */ + internal fun classify(httpCode: Int, body: String?): SearchSourceResult { + if (httpCode !in 200..299) return SearchSourceResult.fromHttpCode(httpCode) + if (body.isNullOrBlank()) return SearchSourceResult.Failed("empty body", FailureKind.MALFORMED) + return try { + val dto = json.decodeFromString(OpenLibrarySearchResponseDto.serializer(), body) + if (dto.docs.isEmpty()) SearchSourceResult.NotFound else SearchSourceResult.Found(dto.docs.map { it.toSearchHit() }) + } catch (e: SerializationException) { + SearchSourceResult.Failed("malformed json", FailureKind.MALFORMED) + } catch (e: IllegalArgumentException) { + SearchSourceResult.Failed("malformed json", FailureKind.MALFORMED) + } + } + + /** Package-visible for offline fixture tests — parses a raw response body with no network involved. */ + internal fun parseResponse(body: String): List = + (classify(200, body) as? SearchSourceResult.Found)?.hits.orEmpty() +} diff --git a/app/app/src/main/java/org/modg/bookshelf/data/metadata/OpenLibrarySearchDtos.kt b/app/app/src/main/java/org/modg/bookshelf/data/metadata/OpenLibrarySearchDtos.kt new file mode 100644 index 0000000..cac8e5b --- /dev/null +++ b/app/app/src/main/java/org/modg/bookshelf/data/metadata/OpenLibrarySearchDtos.kt @@ -0,0 +1,46 @@ +package org.modg.bookshelf.data.metadata + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +/** + * https://openlibrary.org/search.json?q=&limit=20&fields=... — a WORKS index + * search, distinct from [OpenLibraryBookDto]'s by-ISBN edition lookup. `isbn` is + * NOT in OL's default response fields; the caller must request it explicitly (see + * [OpenLibrarySearchClient.requestUrl]) or every hit comes back with no ISBNs at all. + */ +@Serializable +data class OpenLibrarySearchResponseDto( + val docs: List = emptyList(), +) + +@Serializable +data class OpenLibrarySearchDocDto( + val key: String? = null, + val title: String? = null, + val subtitle: String? = null, + @SerialName("author_name") val authorName: List = emptyList(), + @SerialName("first_publish_year") val firstPublishYear: Int? = null, + @SerialName("cover_i") val coverI: Int? = null, + /** Flat list mixing ISBN-10 and ISBN-13 of ALL editions of this work — may be absent, may be dozens. */ + val isbn: List = emptyList(), + val publisher: List = emptyList(), + @SerialName("number_of_pages_median") val numberOfPagesMedian: Int? = null, +) + +/** + * Maps one work to a [SearchHit]. [isbn13s] normalizes every reported ISBN to + * ISBN-13 (ISBN-10 converted) and drops anything that doesn't pass a checksum — + * [SearchResultMerger]'s ISBN rule counts DISTINCT values here to decide whether + * this work resolves to a single unambiguous edition. + */ +fun OpenLibrarySearchDocDto.toSearchHit(): SearchHit = SearchHit( + title = title, + subtitle = subtitle, + authors = authorName, + year = firstPublishYear, + publisher = publisher.firstOrNull(), + pageCount = numberOfPagesMedian, + coverUrl = coverI?.let { "https://covers.openlibrary.org/b/id/$it-M.jpg" }, + isbn13s = isbn.mapNotNull(IsbnUtils::toIsbn13).distinct(), +) diff --git a/app/app/src/main/java/org/modg/bookshelf/data/metadata/RetryPolicy.kt b/app/app/src/main/java/org/modg/bookshelf/data/metadata/RetryPolicy.kt index c2a3ad4..449b4f7 100644 --- a/app/app/src/main/java/org/modg/bookshelf/data/metadata/RetryPolicy.kt +++ b/app/app/src/main/java/org/modg/bookshelf/data/metadata/RetryPolicy.kt @@ -130,22 +130,82 @@ suspend fun withRetry( nowMillis: () -> Long = { System.currentTimeMillis() }, sleep: suspend (Long) -> Unit = { delay(it) }, attempt: suspend () -> SourceResult, -): SourceResult { +): SourceResult = withRetryLoop( + maxAttempts = maxAttempts, + budgetMillis = budgetMillis, + retryRateLimitedOnce = retryRateLimitedOnce, + random = random, + nowMillis = nowMillis, + sleep = sleep, + failureOf = { result -> (result as? SourceResult.Failed)?.let { it.kind to it.retryAfterMillis } }, + appendAttempts = { result, attempts -> + (result as SourceResult.Failed).copy(reason = "${result.reason}, $attempts attempts") + }, + attempt = attempt, +) + +/** + * [OpenLibrarySearchClient]/[GoogleBooksSearchClient]'s counterpart to [withRetry] — + * same policy, same loop, applied to [SearchSourceResult] instead of [SourceResult] + * (search returns a list of hits, not one [BookMetadata], so it needs its own Found + * shape, but the retry decision is identical). Both delegate to [withRetryLoop] so + * there is exactly one loop implementation to keep correct. + */ +suspend fun withSearchRetry( + maxAttempts: Int = RetryPolicy.MAX_ATTEMPTS, + budgetMillis: Long = RetryPolicy.TOTAL_BUDGET_MILLIS, + retryRateLimitedOnce: Boolean = false, + random: Random = Random.Default, + nowMillis: () -> Long = { System.currentTimeMillis() }, + sleep: suspend (Long) -> Unit = { delay(it) }, + attempt: suspend () -> SearchSourceResult, +): SearchSourceResult = withRetryLoop( + maxAttempts = maxAttempts, + budgetMillis = budgetMillis, + retryRateLimitedOnce = retryRateLimitedOnce, + random = random, + nowMillis = nowMillis, + sleep = sleep, + failureOf = { result -> (result as? SearchSourceResult.Failed)?.let { it.kind to it.retryAfterMillis } }, + appendAttempts = { result, attempts -> + (result as SearchSourceResult.Failed).copy(reason = "${result.reason}, $attempts attempts") + }, + attempt = attempt, +) + +/** + * The actual retry loop, shared by [withRetry] and [withSearchRetry] so the policy + * (attempt/budget caps, keyed-429 one-shot retry, backoff) can never drift between + * ISBN lookup and title/author search. [failureOf] extracts (kind, retryAfterMillis) + * from a failed [T], or null if [T] isn't a failure; [appendAttempts] stamps the final + * attempt count into a surviving failure's reason, same diagnostic value as before. + */ +private suspend fun withRetryLoop( + maxAttempts: Int, + budgetMillis: Long, + retryRateLimitedOnce: Boolean, + random: Random, + nowMillis: () -> Long, + sleep: suspend (Long) -> Unit, + failureOf: (T) -> Pair?, + appendAttempts: (T, Int) -> T, + attempt: suspend () -> T, +): T { val started = nowMillis() - var last: SourceResult = attempt() + var last: T = attempt() var attemptsMade = 1 var rateLimitedRetryUsed = false while (attemptsMade < maxAttempts) { - val failure = last as? SourceResult.Failed ?: return last - val rateLimitedRetry = failure.kind == FailureKind.RATE_LIMITED && - retryRateLimitedOnce && !rateLimitedRetryUsed - if (!RetryPolicy.isRetryable(failure.kind) && !rateLimitedRetry) break + val failure = failureOf(last) ?: return last + val (kind, retryAfterMillis) = failure + val rateLimitedRetry = kind == FailureKind.RATE_LIMITED && retryRateLimitedOnce && !rateLimitedRetryUsed + if (!RetryPolicy.isRetryable(kind) && !rateLimitedRetry) break if (nowMillis() - started >= budgetMillis) break if (rateLimitedRetry) { rateLimitedRetryUsed = true - sleep(failure.retryAfterMillis ?: RetryPolicy.RATE_LIMIT_RETRY_CAP_MILLIS) + sleep(retryAfterMillis ?: RetryPolicy.RATE_LIMIT_RETRY_CAP_MILLIS) } else { sleep(RetryPolicy.backoffMillis(attemptsMade + 1, random)) } @@ -153,6 +213,6 @@ suspend fun withRetry( attemptsMade++ } - val failure = last as? SourceResult.Failed ?: return last - return if (attemptsMade > 1) failure.copy(reason = "${failure.reason}, $attemptsMade attempts") else failure + if (failureOf(last) == null) return last + return if (attemptsMade > 1) appendAttempts(last, attemptsMade) else last } diff --git a/app/app/src/main/java/org/modg/bookshelf/data/metadata/SearchHit.kt b/app/app/src/main/java/org/modg/bookshelf/data/metadata/SearchHit.kt new file mode 100644 index 0000000..3e9558f --- /dev/null +++ b/app/app/src/main/java/org/modg/bookshelf/data/metadata/SearchHit.kt @@ -0,0 +1,21 @@ +package org.modg.bookshelf.data.metadata + +/** + * One hit from a title/author search against Open Library or Google Books, before + * [SearchResultMerger] groups and dedupes across sources. [isbn13s] carries EVERY + * ISBN-13 this hit reports, already normalized (via [IsbnUtils.toIsbn13], ISBN-10 + * converted): for an Open Library work that can be dozens across all its editions; + * for a Google Books volume it's the 0 or 1 ISBN-13 that volume itself carries. The + * merger's ISBN rule (SPEC-adjacent, see its KDoc) reads this list directly rather + * than a single [isbn13], so it can tell "one edition" from "many" per source. + */ +data class SearchHit( + val title: String?, + val subtitle: String? = null, + val authors: List = emptyList(), + val year: Int? = null, + val publisher: String? = null, + val pageCount: Int? = null, + val coverUrl: String? = null, + val isbn13s: List = emptyList(), +) diff --git a/app/app/src/main/java/org/modg/bookshelf/data/metadata/SearchResultMerger.kt b/app/app/src/main/java/org/modg/bookshelf/data/metadata/SearchResultMerger.kt new file mode 100644 index 0000000..6519598 --- /dev/null +++ b/app/app/src/main/java/org/modg/bookshelf/data/metadata/SearchResultMerger.kt @@ -0,0 +1,176 @@ +package org.modg.bookshelf.data.metadata + +import org.modg.bookshelf.data.local.BookEntity +import org.modg.bookshelf.data.repo.decodeAuthors + +/** + * Combines local books with the two search sources' hits into one deduplicated result + * (task: library search screen, "Results are DEDUPLICATED across the three sources"). + * Pure, no Android/coroutines — a plain function over already-fetched data so it's + * exhaustively unit-testable. + * + * [local] is whatever candidate set of owned books the caller wants checked for a tie + * to an online hit — the library ViewModel passes the locally text-matched books plus + * any owned book sharing an ISBN with one of the online hits, so a book reached ONLY + * via an online hit (its own text didn't match the query, but an OL/GB record it + * shares an ISBN or title+author with did) still surfaces "In your library" rather + * than being duplicated as a new online result. See the class-level dedupe writeup in + * the wave report for where a caller that passes too narrow a [local] set would miss + * that case, and where too broad a one would falsely merge unrelated namesakes. + */ +object SearchResultMerger { + + data class SearchResults(val inLibrary: List, val online: List) + + /** + * Identity: two records are the same book if they share any ISBN-13 (after + * normalizing everything, ISBN-10 included) OR their normalized title AND first + * author's surname match — grouped transitively via union-find, not pairwise, so + * A~B by ISBN and B~C by title puts all three in one group. Any group containing + * a local book becomes an "In your library" entry (shown as the local book(s) in + * it, since two owned copies/editions of the same work both belong there); every + * other group becomes one [OnlineBook], ordered by the best (lowest) position + * either source returned any of its members at, so relevance from both sources + * is preserved and neither is buried under the other. + */ + fun merge(local: List, openLibrary: List, googleBooks: List): SearchResults { + val localCount = local.size + val olCount = openLibrary.size + val total = localCount + olCount + googleBooks.size + if (total == 0) return SearchResults(emptyList(), emptyList()) + + val keys = buildList { + local.forEach { book -> add(identityKeysOf(book)) } + openLibrary.forEach { hit -> add(identityKeysOf(hit)) } + googleBooks.forEach { hit -> add(identityKeysOf(hit)) } + } + + val unionFind = UnionFind(total) + unionByIsbn(unionFind, keys) + unionByTitleAuthor(unionFind, keys) + + val groups = LinkedHashMap>() + for (index in 0 until total) groups.getOrPut(unionFind.find(index)) { mutableListOf() }.add(index) + + val inLibrary = mutableListOf() + val rankedOnline = mutableListOf>() + + for (members in groups.values) { + val localMembers = members.filter { it < localCount } + if (localMembers.isNotEmpty()) { + inLibrary += localMembers.map { local[it] } + continue + } + val olMembers = members.filter { it in localCount until localCount + olCount }.map { it - localCount } + val gbMembers = members.filter { it >= localCount + olCount }.map { it - localCount - olCount } + if (olMembers.isEmpty() && gbMembers.isEmpty()) continue + + val olHits = olMembers.map { openLibrary[it] } + val gbHits = gbMembers.map { googleBooks[it] } + val bestRank = minOf( + olMembers.minOrNull() ?: Int.MAX_VALUE, + gbMembers.minOrNull() ?: Int.MAX_VALUE, + ) + rankedOnline += bestRank to buildOnlineBook(olHits, gbHits) + } + + return SearchResults(inLibrary, rankedOnline.sortedBy { it.first }.map { it.second }) + } + + private data class IdentityKeys(val isbns: List, val titleKey: String, val surname: String) + + private fun identityKeysOf(book: BookEntity): IdentityKeys { + val isbns = listOfNotNull( + book.isbn13?.let(IsbnUtils::toIsbn13), + book.isbn10?.let(IsbnUtils::toIsbn13), + ).distinct() + return IdentityKeys(isbns, TextNormalization.normalizedTitleKey(book.title), TextNormalization.authorSurnameKey(decodeAuthors(book.authorsJson))) + } + + private fun identityKeysOf(hit: SearchHit): IdentityKeys = + IdentityKeys(hit.isbn13s, TextNormalization.normalizedTitleKey(hit.title), TextNormalization.authorSurnameKey(hit.authors)) + + private fun unionByIsbn(unionFind: UnionFind, keys: List) { + val firstSeenAt = HashMap() + keys.forEachIndexed { index, key -> + for (isbn in key.isbns) { + val existing = firstSeenAt.putIfAbsent(isbn, index) + if (existing != null) unionFind.union(existing, index) + } + } + } + + private fun unionByTitleAuthor(unionFind: UnionFind, keys: List) { + val firstSeenAt = HashMap() + keys.forEachIndexed { index, key -> + if (key.titleKey.isBlank() || key.surname.isBlank()) return@forEachIndexed + val compositeKey = "${key.titleKey}|${key.surname}" + val existing = firstSeenAt.putIfAbsent(compositeKey, index) + if (existing != null) unionFind.union(existing, index) + } + } + + /** + * SPEC-adjacent field fill, mirroring [MetadataMerger]'s "prefer whichever source + * has a title, fill blanks from the other" — here per-field per the task's rule: + * title/subtitle/authors from the OL hit with a title if there is one (work-level + * data, cleaner than a single edition's), else the first GB hit with one; year, + * publisher, page count and cover each independently fall through OL -> GB. + */ + private fun buildOnlineBook(olHits: List, gbHits: List): OnlineBook { + val olPrimary = olHits.firstOrNull { !it.title.isNullOrBlank() } + val gbPrimary = gbHits.firstOrNull { !it.title.isNullOrBlank() } + val primary = olPrimary ?: gbPrimary + val secondary = if (primary === olPrimary) gbPrimary else olPrimary + + val sources = buildSet { + if (olHits.isNotEmpty()) add(SearchSource.OPEN_LIBRARY) + if (gbHits.isNotEmpty()) add(SearchSource.GOOGLE_BOOKS) + } + return OnlineBook( + title = primary?.title, + subtitle = primary?.subtitle ?: secondary?.subtitle, + authors = primary?.authors?.ifEmpty { null } ?: secondary?.authors ?: emptyList(), + year = primary?.year ?: secondary?.year, + publisher = primary?.publisher ?: secondary?.publisher, + pageCount = primary?.pageCount ?: secondary?.pageCount, + coverUrl = primary?.coverUrl ?: secondary?.coverUrl, + isbn13 = resolveIsbn(olHits, gbHits), + sources = sources, + ) + } + + /** + * The ISBN rule (decided by the user, implemented exactly): an OL work whose + * ISBNs normalize to exactly one distinct ISBN-13 wins; else a GB volume set that + * carries exactly one distinct ISBN-13 wins; otherwise no ISBN is recorded at all + * rather than guessing among several editions. + */ + private fun resolveIsbn(olHits: List, gbHits: List): String? { + for (hit in olHits) { + val distinct = hit.isbn13s.distinct() + if (distinct.size == 1) return distinct.single() + } + return gbHits.flatMap { it.isbn13s }.distinct().singleOrNull() + } + + /** Plain union-find over node indices `0 until n` — path-halved finds, union by attaching root to root. */ + private class UnionFind(n: Int) { + private val parent = IntArray(n) { it } + + fun find(x: Int): Int { + var current = x + while (parent[current] != current) { + parent[current] = parent[parent[current]] + current = parent[current] + } + return current + } + + fun union(a: Int, b: Int) { + val rootA = find(a) + val rootB = find(b) + if (rootA != rootB) parent[rootA] = rootB + } + } +} diff --git a/app/app/src/main/java/org/modg/bookshelf/data/metadata/SearchSourceResult.kt b/app/app/src/main/java/org/modg/bookshelf/data/metadata/SearchSourceResult.kt new file mode 100644 index 0000000..5039521 --- /dev/null +++ b/app/app/src/main/java/org/modg/bookshelf/data/metadata/SearchSourceResult.kt @@ -0,0 +1,35 @@ +package org.modg.bookshelf.data.metadata + +import java.io.IOException + +/** + * Per-source title/author search outcome — [SearchSourceResult]'s counterpart to + * [SourceResult] for the library screen's online search (task: "Extend it to also + * search Open Library and Google Books"). Kept as its own sealed type rather than + * reusing [SourceResult] because a search's [Found] carries a list of [SearchHit], + * not one [BookMetadata] — but it follows the exact same three-way rule (SPEC.md + * "Book metadata lookup": a source that could not be reached must never be reported + * as "no results") and the exact same [FailureKind] classification, via + * [fromException]/[fromHttpCode] delegating straight to [SourceResult]'s so the two + * can never disagree about what a given exception or HTTP code means. + */ +sealed interface SearchSourceResult { + data class Found(val hits: List) : SearchSourceResult + + /** The source answered (2xx) and, in good faith, found nothing for this query. */ + data object NotFound : SearchSourceResult + + data class Failed( + val reason: String, + val kind: FailureKind = FailureKind.TRANSPORT, + val retryAfterMillis: Long? = null, + ) : SearchSourceResult + + companion object { + fun fromException(e: IOException): Failed = + SourceResult.fromException(e).let { Failed(it.reason, it.kind, it.retryAfterMillis) } + + fun fromHttpCode(code: Int, retryAfterHeader: String? = null): Failed = + SourceResult.fromHttpCode(code, retryAfterHeader).let { Failed(it.reason, it.kind, it.retryAfterMillis) } + } +} 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 new file mode 100644 index 0000000..603d06f --- /dev/null +++ b/app/app/src/main/java/org/modg/bookshelf/data/metadata/TextNormalization.kt @@ -0,0 +1,48 @@ +package org.modg.bookshelf.data.metadata + +import java.text.Normalizer + +/** + * Shared text normalization for the two places that need to match books by loose + * text rather than exact fields: [org.modg.bookshelf.ui.library.LocalBookMatcher] + * (query tokens against an owned book) and [SearchResultMerger] (title+author + * identity across sources). One implementation so "hobbit" ~ "Hobbit" and + * "Brontë" ~ "bronte" mean the same thing in both places. + */ +object TextNormalization { + private val leadingArticles = setOf("the", "a", "an") + + /** + * Lowercase, diacritics stripped (NFD decompose + drop combining marks), every + * non-alphanumeric character turned into a space, whitespace collapsed. "J.R.R." + * becomes "j r r" — a space-separated token run, not a single run-together word. + */ + fun normalizeForMatching(raw: String): String { + val decomposed = Normalizer.normalize(raw, Normalizer.Form.NFD) + val noDiacritics = decomposed.replace(Regex("\\p{Mn}+"), "") + val spaced = noDiacritics.map { c -> if (c.isLetterOrDigit()) c else ' ' }.joinToString("") + return spaced.lowercase().trim().replace(Regex("\\s+"), " ") + } + + fun tokens(raw: String): List { + val normalized = normalizeForMatching(raw) + return if (normalized.isBlank()) emptyList() else normalized.split(' ') + } + + /** + * Identity title key for [SearchResultMerger]: subtitle (everything from the + * first ':' on) dropped BEFORE normalizing (the colon itself would otherwise + * become a space and the split point would be lost), then a leading "the/a/an" + * dropped so "The Hobbit" and "Hobbit" key the same. + */ + fun normalizedTitleKey(title: String?): String { + if (title.isNullOrBlank()) return "" + val words = tokens(title.substringBefore(':')) + val withoutArticle = if (words.firstOrNull() in leadingArticles) words.drop(1) else words + return withoutArticle.joinToString(" ") + } + + /** The last alphabetic token of the first author — "Joanne S. Williamson" -> "williamson". */ + fun authorSurnameKey(authors: List): String = + authors.firstOrNull()?.let { tokens(it).lastOrNull() }.orEmpty() +} diff --git a/app/app/src/main/java/org/modg/bookshelf/ui/add/AddBookModels.kt b/app/app/src/main/java/org/modg/bookshelf/ui/add/AddBookModels.kt index c0080cb..eb1756c 100644 --- a/app/app/src/main/java/org/modg/bookshelf/ui/add/AddBookModels.kt +++ b/app/app/src/main/java/org/modg/bookshelf/ui/add/AddBookModels.kt @@ -4,11 +4,13 @@ import org.modg.bookshelf.data.metadata.IsbnUtils /** * The add-by-hand form's fields, independent of any particular entry point. - * [org.modg.bookshelf.ui.nav.Routes.addBook] pre-fills [title]/[isbn] today — - * from the scan screen's manual-ISBN dialog, or the library FAB with neither. - * A follow-up wave (online search) is expected to pre-fill more fields from a - * search hit; [fromRouteArgs] is the one place the route-args-to-draft mapping - * lives, so extending it later is a small, localized change. + * [org.modg.bookshelf.ui.nav.Routes.addBook] pre-fills whatever the entry point + * knows — the scan screen's manual-ISBN dialog and library FAB pass just + * title/isbn; wave 10's online-search save sheet's "Edit details" passes every + * field. [fromRouteArgs] is the one place the route-args-to-draft mapping lives. + * [coverSourceUrl] isn't shown anywhere in the form (no cover picker in this + * screen) but rides along so a cover carried in from a search hit still gets + * saved — see [AddBookViewModel.performSave]. */ data class BookDraft( val title: String = "", @@ -19,10 +21,30 @@ data class BookDraft( val pages: String = "", val isbn: String = "", val description: String = "", + val coverSourceUrl: String = "", ) { companion object { - fun fromRouteArgs(title: String?, isbn: String?): BookDraft = - BookDraft(title = title.orEmpty(), isbn = isbn.orEmpty()) + fun fromRouteArgs( + title: String? = null, + isbn: String? = null, + subtitle: String? = null, + authors: String? = null, + publisher: String? = null, + publishedDate: String? = null, + pages: String? = null, + description: String? = null, + coverSourceUrl: String? = null, + ): BookDraft = BookDraft( + title = title.orEmpty(), + subtitle = subtitle.orEmpty(), + authors = authors.orEmpty(), + publisher = publisher.orEmpty(), + publishedDate = publishedDate.orEmpty(), + pages = pages.orEmpty(), + isbn = isbn.orEmpty(), + description = description.orEmpty(), + coverSourceUrl = coverSourceUrl.orEmpty(), + ) } } 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 3a95d79..992d947 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 @@ -56,6 +56,13 @@ fun AddBookScreen( onBack: () -> Unit, onSaved: (bookId: String) -> Unit, container: AppContainer, + initialSubtitle: String? = null, + initialAuthors: String? = null, + initialPublisher: String? = null, + initialPublishedDate: String? = null, + initialPages: String? = null, + initialDescription: String? = null, + initialCoverSourceUrl: String? = null, ) { val viewModel: AddBookViewModel = viewModel( factory = viewModelFactory { @@ -64,7 +71,17 @@ fun AddBookScreen( bookRepository = container.bookRepository, locationRepository = container.locationRepository, settingsStore = container.settingsStore, - initialDraft = BookDraft.fromRouteArgs(initialTitle, initialIsbn), + initialDraft = BookDraft.fromRouteArgs( + title = initialTitle, + isbn = initialIsbn, + subtitle = initialSubtitle, + authors = initialAuthors, + publisher = initialPublisher, + publishedDate = initialPublishedDate, + pages = initialPages, + description = initialDescription, + coverSourceUrl = initialCoverSourceUrl, + ), ) } }, 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 cf39f57..96f8dbc 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 @@ -197,6 +197,7 @@ class AddBookViewModel( publishedDate = draft.publishedDate.trim().ifEmpty { null }, pageCount = pageCount, description = draft.description.trim().ifEmpty { null }, + coverSourceUrl = draft.coverSourceUrl.trim().ifEmpty { null }, shelfId = form.selectedShelfId, ) rememberShelf(form.selectedShelfId) 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 fc379ae..a819e97 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 @@ -1,6 +1,8 @@ package org.modg.bookshelf.ui.library import org.modg.bookshelf.data.local.BookEntity +import org.modg.bookshelf.data.metadata.BookMetadata +import org.modg.bookshelf.data.metadata.OnlineBook import org.modg.bookshelf.data.repo.decodeAuthors /** SPEC.md library screen: "sort title/author/added". */ @@ -52,3 +54,87 @@ object LibraryFilterLogic { } } } + +/** + * Online search state (task: library search "Extend it to also search Open Library + * and Google Books"). Lives in [LibraryViewModel] so it survives rotation. Search + * runs ONLY on submit (IME action / the explicit row), never per keystroke — decided + * by the user, see the task brief on Google Books' 1,000/day quota. + */ +sealed interface OnlineSearchState { + /** No search has run for the current query yet — the prompt row is what's shown. */ + data object NotSearched : 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 + * 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") + * from an authoritative miss (both answered, nothing found anywhere). + */ + data class Done( + val query: String, + val inLibrary: List, + val online: List, + val openLibraryFailure: String?, + val googleBooksFailure: String?, + ) : OnlineSearchState +} + +/** The bottom sheet shown when an online result is tapped (task: "same idea as the scan screen's Found sheet"). */ +sealed interface OnlineSaveSheetState { + data object Hidden : OnlineSaveSheetState + + /** + * [enrichment] fills in once the background by-ISBN lookup (started the moment + * the sheet opens, only when [book] has an isbn13) completes — Save reads + * whatever's here at the moment it's tapped and never waits on it, per the task's + * "Save must NEVER wait for this". + */ + data class Shown( + val book: OnlineBook, + val selectedShelfId: String?, + val isSaving: Boolean = false, + val saveError: String? = null, + val enrichment: BookMetadata? = null, + ) : OnlineSaveSheetState +} + +/** + * The same OL-then-GB-then-enrichment field-fill precedence, used by BOTH + * [LibraryViewModel.performSaveOnlineResult] (what gets persisted) and + * [org.modg.bookshelf.ui.nav.BookshelfNavHost]'s "Edit details" route builder + * (what pre-fills the add-by-hand form) — one place so the two can't drift apart. + */ +data class OnlineBookDraftFields( + val title: String, + val subtitle: String?, + val authors: List, + val publisher: String?, + val publishedDate: String?, + val pageCount: Int?, + val isbn13: String?, + val isbn10: String?, + val description: String?, + val coverUrl: String?, +) + +fun resolveOnlineBookFields(book: OnlineBook, enrichment: BookMetadata?): OnlineBookDraftFields = OnlineBookDraftFields( + title = book.title?.ifBlank { null } ?: enrichment?.title?.ifBlank { null } ?: "Untitled", + subtitle = book.subtitle ?: enrichment?.subtitle, + authors = book.authors.ifEmpty { enrichment?.authors.orEmpty() }, + publisher = book.publisher ?: enrichment?.publisher, + publishedDate = enrichment?.publishedDate ?: book.year?.toString(), + pageCount = book.pageCount ?: enrichment?.pageCount, + isbn13 = book.isbn13 ?: enrichment?.isbn13, + isbn10 = enrichment?.isbn10, + description = enrichment?.description, + coverUrl = book.coverUrl ?: enrichment?.coverUrl, +) 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 cf1d5b5..8ced445 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 @@ -1,18 +1,26 @@ package org.modg.bookshelf.ui.library import androidx.compose.foundation.clickable +import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.items import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Clear import androidx.compose.material.icons.outlined.EditNote @@ -21,6 +29,7 @@ import androidx.compose.material.icons.outlined.QrCodeScanner import androidx.compose.material.icons.outlined.Search import androidx.compose.material.icons.outlined.Settings import androidx.compose.material.icons.outlined.Sort +import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ExperimentalMaterial3Api @@ -28,10 +37,13 @@ import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.SmallFloatingActionButton import androidx.compose.material3.Text +import androidx.compose.material3.TextButton import androidx.compose.material3.pulltorefresh.PullToRefreshBox +import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue @@ -41,6 +53,7 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp @@ -52,20 +65,30 @@ import org.modg.bookshelf.R 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.OnlineBook import org.modg.bookshelf.data.repo.decodeAuthors import org.modg.bookshelf.ui.components.BookCover 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.SyncStatus import org.modg.bookshelf.ui.components.SyncStatusBar +import org.modg.bookshelf.ui.scan.ShelfPicker /** * SPEC.md "library" screen: adaptive cover grid, search, filter, sort, empty * state, FAB to scan, sync status line. Covers carry the color; card chrome * stays minimal (cover + two lines of text) on purpose. + * + * Wave 10 (online search) extends the search bar: a blank query keeps the + * plain cover grid, but any query switches this screen into a sectioned + * search-results view — "In your library" (instant, local, as-you-type) above + * "Online" (Open Library + Google Books, submit-only) — see + * [LibrarySearchResultsContent]. */ @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -77,6 +100,8 @@ fun LibraryScreen( onLocationsClick: () -> Unit, onSettingsClick: () -> Unit, container: AppContainer, + onEnterByHandWithQuery: (String) -> Unit = { onAddByHandClick() }, + onEditOnlineResult: (OnlineBook, BookMetadata?) -> Unit = { _, _ -> }, ) { val viewModel: LibraryViewModel = viewModel( // A fresh VM per distinct shelf filter, so tapping a different shelf @@ -89,6 +114,8 @@ fun LibraryScreen( locationRepository = container.locationRepository, syncEngine = container.syncEngine, settingsStore = container.settingsStore, + bookSearchRepository = container.bookSearchRepository, + metadataRepository = container.metadataRepository, initialShelfId = shelfIdFilter, ) } @@ -99,6 +126,9 @@ fun LibraryScreen( val query by viewModel.query.collectAsState() val sortOption by viewModel.sortOption.collectAsState() val filter by viewModel.filter.collectAsState() + val onlineSearch by viewModel.onlineSearch.collectAsState() + val saveSheet by viewModel.saveSheet.collectAsState() + val recentShelfId by viewModel.recentShelfId.collectAsState() BookshelfScaffold( title = "Bookshelf", @@ -141,6 +171,7 @@ fun LibraryScreen( LibraryToolbar( query = query, onQueryChange = viewModel::onQueryChange, + onSearchSubmit = viewModel::submitOnlineSearch, sortOption = sortOption, onSortOptionChange = viewModel::onSortOptionChange, filter = filter, @@ -150,6 +181,16 @@ fun LibraryScreen( ) when { + query.isNotBlank() -> LibrarySearchResultsContent( + query = query, + localMatches = uiState.books, + onlineSearch = onlineSearch, + onBookClick = onBookClick, + onSubmitOnlineSearch = viewModel::submitOnlineSearch, + onRetryOnlineSearch = viewModel::retryOnlineSearch, + onOnlineResultClick = viewModel::onOnlineResultTapped, + onEnterByHand = { onEnterByHandWithQuery(query.trim()) }, + ) !uiState.hasAnyBooks -> EmptyState( title = "Your shelves are empty", message = "Scan a barcode to add your first book.", @@ -180,6 +221,25 @@ fun LibraryScreen( } } } + + val sheetState = saveSheet + if (sheetState is OnlineSaveSheetState.Shown) { + ModalBottomSheet( + onDismissRequest = viewModel::dismissSaveSheet, + sheetState = rememberModalBottomSheetState(), + ) { + OnlineResultSaveSheet( + state = sheetState, + bookcases = uiState.bookcases, + shelves = uiState.shelves, + recentShelfId = recentShelfId, + onShelfSelected = viewModel::onSaveSheetShelfSelected, + onSave = viewModel::saveOnlineResult, + onEditDetails = { onEditOnlineResult(sheetState.book, sheetState.enrichment) }, + onSkip = viewModel::dismissSaveSheet, + ) + } + } } @Composable @@ -192,6 +252,7 @@ internal fun LibraryToolbar( onFilterChange: (LibraryFilter) -> Unit, bookcases: List, shelves: List, + onSearchSubmit: () -> Unit = {}, ) { var filterMenuExpanded by remember { mutableStateOf(false) } var sortMenuExpanded by remember { mutableStateOf(false) } @@ -217,6 +278,10 @@ internal fun LibraryToolbar( } } }, + // Wave 10: IME Search triggers the online search (task: "ONLY ON SUBMIT + // (IME Search action, or an explicit button) — never per keystroke"). + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search), + keyboardActions = KeyboardActions(onSearch = { onSearchSubmit() }), ) Column { @@ -288,8 +353,8 @@ internal fun LibraryToolbar( } @Composable -internal fun LibraryBookCard(book: BookEntity, onClick: () -> Unit) { - Column(modifier = Modifier.fillMaxWidth().clickable(onClick = onClick)) { +internal fun LibraryBookCard(book: BookEntity, onClick: () -> Unit, modifier: Modifier = Modifier) { + Column(modifier = modifier.fillMaxWidth().clickable(onClick = onClick)) { BookCover( coverUrl = book.coverUrl ?: book.coverSourceUrl, contentDescription = book.title, @@ -321,3 +386,303 @@ internal fun LibraryBookCard(book: BookEntity, onClick: () -> Unit) { } } } + +// --- Wave 10: online search results --- + +/** + * The sectioned view shown whenever the search bar carries a non-blank query — + * "In your library" (instant local matches, existing card style, horizontally + * scrollable so it never fights the outer vertical scroll) above "Online" + * (Open Library + Google Books, submit-only, as rows). Split out as its own + * stateless composable — fed real state, not hand-rolled — so Paparazzi + * renders the actual screen (see HANDOFF.md wave-6 "soft spots"). + */ +@Composable +internal fun LibrarySearchResultsContent( + query: String, + localMatches: List, + onlineSearch: OnlineSearchState, + onBookClick: (String) -> Unit, + onSubmitOnlineSearch: () -> Unit, + onRetryOnlineSearch: () -> Unit, + onOnlineResultClick: (OnlineBook) -> Unit, + onEnterByHand: () -> Unit, + modifier: Modifier = Modifier, +) { + Column( + modifier = modifier + .fillMaxSize() + .verticalScroll(rememberScrollState()) + .padding(bottom = 24.dp), + ) { + SearchSectionHeader(text = "In your library") + if (localMatches.isEmpty()) { + // The header stays up even with nothing to show — task: the user must be + // able to tell "not owned" from "didn't look". + Text( + text = "No matches in your library", + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.padding(horizontal = 16.dp, vertical = 4.dp), + ) + } else { + Row( + modifier = Modifier + .fillMaxWidth() + .horizontalScroll(rememberScrollState()) + .padding(horizontal = 16.dp), + horizontalArrangement = Arrangement.spacedBy(12.dp), + ) { + localMatches.forEach { book -> + LibraryBookCard(book = book, onClick = { onBookClick(book.id) }, modifier = Modifier.width(112.dp)) + } + } + } + + GoldDivider(modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp)) + SearchSectionHeader(text = "Online") + OnlineSearchSection( + query = query, + state = onlineSearch, + onSubmit = onSubmitOnlineSearch, + onRetry = onRetryOnlineSearch, + onResultClick = onOnlineResultClick, + ) + + TextButton(onClick = onEnterByHand, modifier = Modifier.padding(horizontal = 12.dp, vertical = 8.dp)) { + Text("Can't find it? Enter it by hand") + } + } +} + +@Composable +private fun SearchSectionHeader(text: String) { + Text( + text = text, + style = MaterialTheme.typography.titleMedium, + color = MaterialTheme.colorScheme.onSurface, + modifier = Modifier.padding(start = 16.dp, end = 16.dp, top = 16.dp, bottom = 8.dp), + ) +} + +/** Minimum characters before the online-search prompt/submit is offered at all. */ +internal const val OnlineSearchMinQueryLength = 2 + +@Composable +private fun OnlineSearchSection( + query: String, + state: OnlineSearchState, + onSubmit: () -> Unit, + onRetry: () -> Unit, + onResultClick: (OnlineBook) -> Unit, +) { + val trimmed = query.trim() + when (state) { + is OnlineSearchState.NotSearched -> { + if (trimmed.length >= OnlineSearchMinQueryLength) { + SearchPromptRow(query = trimmed, onClick = onSubmit) + } else { + Text( + text = "Keep typing to search Open Library and Google Books.", + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.padding(horizontal = 16.dp, vertical = 4.dp), + ) + } + } + is OnlineSearchState.Searching -> { + Row( + modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp, vertical = 16.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + CircularProgressIndicator(modifier = Modifier.size(20.dp), strokeWidth = 2.dp) + Text( + text = "Searching Open Library and Google Books…", + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.padding(start = 12.dp), + ) + } + } + is OnlineSearchState.Done -> OnlineSearchDoneSection(state = state, onRetry = onRetry, onResultClick = onResultClick) + } +} + +@Composable +private fun OnlineSearchDoneSection(state: OnlineSearchState.Done, onRetry: () -> Unit, onResultClick: (OnlineBook) -> Unit) { + val bothFailed = state.openLibraryFailure != null && state.googleBooksFailure != null + if (bothFailed) { + // Task: "both failed -> a retry affordance that does NOT say 'no results'". + Column(modifier = Modifier.padding(horizontal = 16.dp, vertical = 4.dp)) { + Text( + text = "Couldn't search online", + style = MaterialTheme.typography.titleSmall, + color = MaterialTheme.colorScheme.onSurface, + ) + Text( + text = "This doesn't mean there are no results — neither source could be reached.", + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.padding(top = 2.dp), + ) + PrimaryButton(text = "Retry", onClick = onRetry, modifier = Modifier.padding(top = 8.dp)) + } + return + } + + state.openLibraryFailure?.let { FailureLine(message = "Open Library couldn't be reached — $it", onRetry = onRetry) } + state.googleBooksFailure?.let { FailureLine(message = "Google Books couldn't be reached — $it", onRetry = onRetry) } + + if (state.online.isEmpty()) { + Text( + text = "No online results for \"${state.query}\".", + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.padding(horizontal = 16.dp, vertical = 4.dp), + ) + } else { + Column { + state.online.forEach { book -> OnlineResultRow(book = book, onClick = { onResultClick(book) }) } + } + } +} + +@Composable +private fun SearchPromptRow(query: String, onClick: () -> Unit) { + Row( + modifier = Modifier + .fillMaxWidth() + .clickable(onClick = onClick) + .padding(horizontal = 16.dp, vertical = 14.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Icon(Icons.Outlined.Search, contentDescription = null, tint = MaterialTheme.colorScheme.primary) + Text( + text = "Search Open Library & Google Books for \"$query\"", + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.primary, + modifier = Modifier.padding(start = 12.dp), + ) + } +} + +@Composable +private fun FailureLine(message: String, onRetry: () -> Unit) { + Row( + modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp, vertical = 4.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + text = message, + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.weight(1f), + ) + TextButton(onClick = onRetry) { Text("Retry") } + } +} + +@Composable +internal fun OnlineResultRow(book: OnlineBook, onClick: () -> Unit) { + Row( + modifier = Modifier + .fillMaxWidth() + .clickable(onClick = onClick) + .padding(horizontal = 16.dp, vertical = 8.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + BookCover(coverUrl = book.coverUrl, contentDescription = book.title, modifier = Modifier.width(44.dp)) + Column(modifier = Modifier.padding(start = 12.dp).weight(1f)) { + Text( + text = book.title ?: "Untitled", + style = MaterialTheme.typography.titleSmall, + color = MaterialTheme.colorScheme.onSurface, + maxLines = 2, + overflow = TextOverflow.Ellipsis, + ) + val subtitleParts = listOfNotNull( + book.authors.takeIf { it.isNotEmpty() }?.joinToString(", "), + book.year?.toString(), + ) + if (subtitleParts.isNotEmpty()) { + Text( + text = subtitleParts.joinToString(" • "), + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.padding(top = 2.dp), + ) + } + } + } +} + +/** + * The save sheet for an online result — task: "same idea as the scan screen's + * Found sheet: book summary + shelf picker + Save / Skip", plus "Edit details". + */ +@Composable +internal fun OnlineResultSaveSheet( + state: OnlineSaveSheetState.Shown, + bookcases: List, + shelves: List, + recentShelfId: String?, + onShelfSelected: (String?) -> Unit, + onSave: () -> Unit, + onEditDetails: () -> Unit, + onSkip: () -> Unit, +) { + val book = state.book + Column(modifier = Modifier.fillMaxWidth().padding(16.dp)) { + Box( + modifier = Modifier + .align(Alignment.CenterHorizontally) + .size(width = 100.dp, height = 150.dp), + ) { + BookCover(coverUrl = book.coverUrl, contentDescription = book.title, modifier = Modifier.fillMaxSize()) + } + Text( + text = book.title ?: "Untitled", + style = MaterialTheme.typography.titleLarge, + modifier = Modifier.padding(top = 12.dp), + ) + if (book.authors.isNotEmpty()) { + Text(text = book.authors.joinToString(", "), style = MaterialTheme.typography.bodyLarge) + } + val meta = listOfNotNull(book.year?.toString(), book.isbn13) + if (meta.isNotEmpty()) { + Text( + text = meta.joinToString(" • "), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.padding(top = 2.dp), + ) + } + if (state.saveError != null) { + Text( + text = "Couldn't save: ${state.saveError}", + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.error, + modifier = Modifier.padding(top = 8.dp), + ) + } + ShelfPicker( + bookcases = bookcases, + shelves = shelves, + selectedShelfId = state.selectedShelfId, + recentShelfId = recentShelfId, + onShelfSelected = onShelfSelected, + ) + 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", + 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 bc43bd6..22b7e20 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 @@ -2,19 +2,29 @@ package org.modg.bookshelf.ui.library import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.CancellationException import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.flow.update 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.BookSearchRepository +import org.modg.bookshelf.data.metadata.IsbnUtils +import org.modg.bookshelf.data.metadata.LookupResult +import org.modg.bookshelf.data.metadata.MetadataRepository +import org.modg.bookshelf.data.metadata.OnlineBook +import org.modg.bookshelf.data.metadata.SearchHit +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.LocationRepository @@ -37,6 +47,8 @@ class LibraryViewModel( private val locationRepository: LocationRepository, private val syncEngine: SyncEngine, private val settingsStore: SettingsStore, + private val bookSearchRepository: BookSearchRepository, + private val metadataRepository: MetadataRepository, initialShelfId: String?, ) : ViewModel() { @@ -65,12 +77,26 @@ class LibraryViewModel( .map { shelves -> shelves.groupBy({ it.bookcaseId }, { it.id }).mapValues { it.value.toSet() } } .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), emptyMap()) - private val hasAnyBooks: StateFlow = bookRepository.observeAll() + /** Every non-deleted owned book — the base [allBooks] both the grid and the search/merge logic read from. */ + private val allBooks: StateFlow> = bookRepository.observeAll() + .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), emptyList()) + + private val hasAnyBooks: StateFlow = allBooks .map { it.isNotEmpty() } .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), true) + /** + * Instant, offline, as-you-type local match (task: "Own library filters AS THEY + * TYPE"). [LocalBookMatcher] replaces [org.modg.bookshelf.data.local.BookDao.search]'s + * 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 filteredSortedBooks: StateFlow> = combine( - _query.flatMapLatest { bookRepository.search(it) }, + localMatches, _filter, _sortOption, shelfIdsByBookcase, @@ -78,6 +104,62 @@ class LibraryViewModel( LibrarySort.sort(LibraryFilterLogic.apply(books, filterValue, shelfMap), sort) }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), emptyList()) + // --- online search (task: extend the search bar to also search Open Library/Google Books) --- + + private sealed interface OnlineSearchPhase { + data object NotSearched : OnlineSearchPhase + data class Searching(val query: String) : OnlineSearchPhase + data class Done(val hits: RawHits) : OnlineSearchPhase + } + + private data class RawHits( + val query: String, + val openLibrary: List, + val googleBooks: List, + val openLibraryFailure: String?, + val googleBooksFailure: String?, + ) + + private val _onlineSearchPhase = MutableStateFlow(OnlineSearchPhase.NotSearched) + private var onlineSearchJob: Job? = null + + /** + * Recomputed from [allBooks] on every change, not frozen at search time — so a + * book saved from the online section's save sheet (or from anywhere else, e.g. + * 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 -> + when (phase) { + is OnlineSearchPhase.NotSearched -> OnlineSearchState.NotSearched + is OnlineSearchPhase.Searching -> OnlineSearchState.Searching(phase.query) + is OnlineSearchPhase.Done -> buildDoneState(phase.hits, books) + } + }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), OnlineSearchState.NotSearched) + + private fun buildDoneState(raw: RawHits, books: List): 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 merged = SearchResultMerger.merge(candidates, raw.openLibrary, raw.googleBooks) + return OnlineSearchState.Done(raw.query, merged.inLibrary, merged.online, raw.openLibraryFailure, raw.googleBooksFailure) + } + + private fun bookIsbn13s(book: BookEntity): List = + listOfNotNull(book.isbn13?.let(IsbnUtils::toIsbn13), book.isbn10?.let(IsbnUtils::toIsbn13)) + + // --- online result save sheet --- + + private val _saveSheet = MutableStateFlow(OnlineSaveSheetState.Hidden) + val saveSheet: StateFlow = _saveSheet.asStateFlow() + private var enrichmentJob: Job? = null + + /** 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) + private val syncBar: StateFlow = combine( settingsStore.lastSyncTime, _isSyncing, @@ -104,8 +186,16 @@ class LibraryViewModel( refreshSync() } + /** + * Editing the query after a submit clears the online section and cancels any + * in-flight search (task: stale results for a different query are misleading). + */ fun onQueryChange(value: String) { _query.value = value + if (_onlineSearchPhase.value !is OnlineSearchPhase.NotSearched) { + onlineSearchJob?.cancel() + _onlineSearchPhase.value = OnlineSearchPhase.NotSearched + } } fun onSortOptionChange(option: LibrarySortOption) { @@ -116,6 +206,125 @@ class LibraryViewModel( _filter.value = filter } + /** + * Runs on submit (IME Search action / the explicit prompt row) ONLY — never per + * keystroke, per the task's decision on Google Books' 1,000/day quota and Open + * Library's latency. [org.modg.bookshelf.ui.library.LibraryScreen]'s minimum + * query length (2) is enforced here too, so a stray programmatic call can't + * burn quota on a near-empty query either. + */ + fun submitOnlineSearch() { + val trimmed = _query.value.trim() + if (trimmed.length < 2) return + onlineSearchJob?.cancel() + onlineSearchJob = viewModelScope.launch { performOnlineSearch(trimmed) } + } + + /** Retries both sources — used by the "both failed" retry affordance and each per-source failure line. */ + fun retryOnlineSearch() = submitOnlineSearch() + + /** + * Internal (not private) so tests can await it directly, same reasoning as + * [org.modg.bookshelf.ui.scan.ScanViewModel.runLookup]. [BookSearchRepository]'s + * clients already never throw (see their KDoc), but this guard is the same + * belt-and-braces backstop that class documents. + */ + internal suspend fun performOnlineSearch(query: String) { + _onlineSearchPhase.value = OnlineSearchPhase.Searching(query) + val raw = try { + val results = bookSearchRepository.search(query) + RawHits( + query = query, + openLibrary = (results.openLibrary as? SearchSourceResult.Found)?.hits.orEmpty(), + googleBooks = (results.googleBooks as? SearchSourceResult.Found)?.hits.orEmpty(), + openLibraryFailure = (results.openLibrary as? SearchSourceResult.Failed)?.reason, + googleBooksFailure = (results.googleBooks as? SearchSourceResult.Failed)?.reason, + ) + } catch (e: CancellationException) { + throw e + } catch (e: Throwable) { + val reason = "unexpected: ${e.javaClass.simpleName}" + RawHits(query, emptyList(), emptyList(), reason, reason) + } + _onlineSearchPhase.value = OnlineSearchPhase.Done(raw) + } + + /** + * Opens the save sheet and, when [book] carries an ISBN, kicks off the same + * by-ISBN enrichment lookup the scan flow uses in the background — Save never + * waits on it (see [performSaveOnlineResult]); it's cancelled when the sheet closes. + */ + fun onOnlineResultTapped(book: OnlineBook) { + enrichmentJob?.cancel() + _saveSheet.value = OnlineSaveSheetState.Shown(book = book, selectedShelfId = recentShelfId.value) + val isbn13 = book.isbn13 ?: return + enrichmentJob = viewModelScope.launch { + val result = metadataRepository.lookup(isbn13) + if (result is LookupResult.Found) { + _saveSheet.update { state -> + if (state is OnlineSaveSheetState.Shown && state.book == book) state.copy(enrichment = result.metadata) else state + } + } + } + } + + fun onSaveSheetShelfSelected(shelfId: String?) { + _saveSheet.update { if (it is OnlineSaveSheetState.Shown) it.copy(selectedShelfId = shelfId) else it } + } + + fun dismissSaveSheet() { + enrichmentJob?.cancel() + _saveSheet.value = OnlineSaveSheetState.Hidden + } + + fun saveOnlineResult() { + viewModelScope.launch { performSaveOnlineResult() } + } + + /** + * Split out so tests can await it directly (same pattern as + * [org.modg.bookshelf.ui.add.AddBookViewModel.performSave]). Re-entrancy guarded + * here so a genuine double-tap can't create two books. Reads whatever + * [OnlineSaveSheetState.Shown.enrichment] happens to hold at this instant — it + * never awaits the enrichment lookup, per the task's "Save must NEVER wait for this". + */ + internal suspend fun performSaveOnlineResult(): String? { + val state = _saveSheet.value as? OnlineSaveSheetState.Shown ?: return null + if (state.isSaving) return null + _saveSheet.update { if (it is OnlineSaveSheetState.Shown) it.copy(isSaving = true, saveError = null) else it } + + val fields = resolveOnlineBookFields(state.book, state.enrichment) + return try { + val id = bookRepository.createBook( + title = fields.title, + subtitle = fields.subtitle, + authors = fields.authors, + isbn13 = fields.isbn13, + isbn10 = fields.isbn10, + publisher = fields.publisher, + publishedDate = fields.publishedDate, + pageCount = fields.pageCount, + description = fields.description, + coverSourceUrl = fields.coverUrl, + shelfId = state.selectedShelfId, + ) + rememberShelf(state.selectedShelfId) + enrichmentJob?.cancel() + _saveSheet.value = OnlineSaveSheetState.Hidden + id + } catch (e: CancellationException) { + throw e + } catch (e: Throwable) { + _saveSheet.update { if (it is OnlineSaveSheetState.Shown) it.copy(isSaving = false, saveError = e.javaClass.simpleName) else it } + null + } + } + + /** "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) + } + /** Manual trigger — app start (see init) and pull-to-refresh, per SPEC "Sync design". */ fun refreshSync() { if (_isSyncing.value) return 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 new file mode 100644 index 0000000..c491bf6 --- /dev/null +++ b/app/app/src/main/java/org/modg/bookshelf/ui/library/LocalBookMatcher.kt @@ -0,0 +1,41 @@ +package org.modg.bookshelf.ui.library + +import org.modg.bookshelf.data.local.BookEntity +import org.modg.bookshelf.data.metadata.TextNormalization +import org.modg.bookshelf.data.repo.decodeAuthors + +/** + * The library search bar's instant, offline, as-you-type local filter (task: "Own + * library filters AS THEY TYPE"). Replaces [org.modg.bookshelf.data.local.BookDao.search]'s + * single whole-string LIKE for this screen — that matches nothing for "hobbit tolkien" + * because neither column contains that exact substring. A home library is hundreds to + * low thousands of books, so a pure Kotlin scan is fine and far more testable than SQL. + */ +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". + */ + fun match(books: List, query: String): List { + val queryTokens = TextNormalization.tokens(query) + if (queryTokens.isEmpty()) return emptyList() + return books.filter { book -> matches(book, queryTokens) } + } + + 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) } } + } +} diff --git a/app/app/src/main/java/org/modg/bookshelf/ui/nav/BookshelfNavHost.kt b/app/app/src/main/java/org/modg/bookshelf/ui/nav/BookshelfNavHost.kt index fd7f6a5..6b64b56 100644 --- a/app/app/src/main/java/org/modg/bookshelf/ui/nav/BookshelfNavHost.kt +++ b/app/app/src/main/java/org/modg/bookshelf/ui/nav/BookshelfNavHost.kt @@ -11,6 +11,7 @@ import org.modg.bookshelf.AppContainer import org.modg.bookshelf.ui.add.AddBookScreen import org.modg.bookshelf.ui.detail.DetailScreen import org.modg.bookshelf.ui.library.LibraryScreen +import org.modg.bookshelf.ui.library.resolveOnlineBookFields import org.modg.bookshelf.ui.locations.LocationsScreen import org.modg.bookshelf.ui.scan.ScanScreen import org.modg.bookshelf.ui.settings.SettingsScreen @@ -57,6 +58,28 @@ fun BookshelfNavHost( onLocationsClick = { navController.navigate(Routes.LOCATIONS) }, onSettingsClick = { navController.navigate(Routes.SETTINGS) }, container = container, + // Wave 10: "Can't find it? Enter it by hand" under the online results — + // pre-fills the add-by-hand form's title with the search query. + onEnterByHandWithQuery = { query -> navController.navigate(Routes.addBook(title = query)) }, + // Wave 10: the online-result save sheet's "Edit details" — carries the + // whole resolved draft (see resolveOnlineBookFields) through to the + // add-by-hand form via its per-field route args. + onEditOnlineResult = { book, enrichment -> + val fields = resolveOnlineBookFields(book, enrichment) + navController.navigate( + Routes.addBook( + title = fields.title, + subtitle = fields.subtitle, + authors = fields.authors.joinToString(", "), + publisher = fields.publisher, + publishedDate = fields.publishedDate, + pages = fields.pageCount?.toString(), + isbn = fields.isbn13 ?: fields.isbn10, + description = fields.description, + coverSourceUrl = fields.coverUrl, + ), + ) + }, ) } composable( @@ -79,21 +102,34 @@ fun BookshelfNavHost( composable( route = Routes.ADD_BOOK, arguments = listOf( - navArgument(Routes.ADD_BOOK_TITLE_ARG) { + Routes.ADD_BOOK_TITLE_ARG, + Routes.ADD_BOOK_ISBN_ARG, + Routes.ADD_BOOK_SUBTITLE_ARG, + Routes.ADD_BOOK_AUTHORS_ARG, + Routes.ADD_BOOK_PUBLISHER_ARG, + Routes.ADD_BOOK_PUBLISHED_DATE_ARG, + Routes.ADD_BOOK_PAGES_ARG, + Routes.ADD_BOOK_DESCRIPTION_ARG, + Routes.ADD_BOOK_COVER_URL_ARG, + ).map { argName -> + navArgument(argName) { type = NavType.StringType nullable = true defaultValue = null - }, - navArgument(Routes.ADD_BOOK_ISBN_ARG) { - type = NavType.StringType - nullable = true - defaultValue = null - }, - ), + } + }, ) { backStackEntry -> + fun arg(name: String) = backStackEntry.arguments?.getString(name) AddBookScreen( - initialTitle = backStackEntry.arguments?.getString(Routes.ADD_BOOK_TITLE_ARG), - initialIsbn = backStackEntry.arguments?.getString(Routes.ADD_BOOK_ISBN_ARG), + initialTitle = arg(Routes.ADD_BOOK_TITLE_ARG), + initialIsbn = arg(Routes.ADD_BOOK_ISBN_ARG), + initialSubtitle = arg(Routes.ADD_BOOK_SUBTITLE_ARG), + initialAuthors = arg(Routes.ADD_BOOK_AUTHORS_ARG), + initialPublisher = arg(Routes.ADD_BOOK_PUBLISHER_ARG), + initialPublishedDate = arg(Routes.ADD_BOOK_PUBLISHED_DATE_ARG), + initialPages = arg(Routes.ADD_BOOK_PAGES_ARG), + initialDescription = arg(Routes.ADD_BOOK_DESCRIPTION_ARG), + initialCoverSourceUrl = arg(Routes.ADD_BOOK_COVER_URL_ARG), onBack = { navController.popBackStack() }, onSaved = { bookId -> // Removes the add screen from the back stack, per the task's "Save" diff --git a/app/app/src/main/java/org/modg/bookshelf/ui/nav/Routes.kt b/app/app/src/main/java/org/modg/bookshelf/ui/nav/Routes.kt index 12b6f41..eb8cb27 100644 --- a/app/app/src/main/java/org/modg/bookshelf/ui/nav/Routes.kt +++ b/app/app/src/main/java/org/modg/bookshelf/ui/nav/Routes.kt @@ -20,21 +20,45 @@ object Routes { const val ADD_BOOK_TITLE_ARG = "title" const val ADD_BOOK_ISBN_ARG = "isbn" + + /** + * Wave 10 (online search): "Edit details" on an online search hit needs to carry + * the whole draft, not just title/isbn, to the add-by-hand screen. Extends the + * wave-9 route with the same per-field nullable-query-arg pattern rather than a + * new serialized-blob argument, so every arg keeps the identical encode/decode + * story [ADD_BOOK_TITLE_ARG]/[ADD_BOOK_ISBN_ARG] already have (and RoutesTest + * already covers). + */ + const val ADD_BOOK_SUBTITLE_ARG = "subtitle" + const val ADD_BOOK_AUTHORS_ARG = "authors" + const val ADD_BOOK_PUBLISHER_ARG = "publisher" + const val ADD_BOOK_PUBLISHED_DATE_ARG = "publishedDate" + const val ADD_BOOK_PAGES_ARG = "pages" + const val ADD_BOOK_DESCRIPTION_ARG = "description" + const val ADD_BOOK_COVER_URL_ARG = "coverUrl" private const val ADD_BOOK_BASE = "add" /** - * Both args are optional (wave 9: "add a book by hand" with nothing pre-filled) + * Every arg is optional (wave 9: "add a book by hand" with nothing pre-filled) * — nullable/defaultValue-null args, same pattern [LIBRARY_WITH_SHELF] uses so - * this one route also matches the bare "add" entry with neither supplied. + * this one route also matches the bare "add" entry with none supplied. */ - const val ADD_BOOK = "$ADD_BOOK_BASE?$ADD_BOOK_TITLE_ARG={$ADD_BOOK_TITLE_ARG}&$ADD_BOOK_ISBN_ARG={$ADD_BOOK_ISBN_ARG}" + const val ADD_BOOK = "$ADD_BOOK_BASE?$ADD_BOOK_TITLE_ARG={$ADD_BOOK_TITLE_ARG}" + + "&$ADD_BOOK_ISBN_ARG={$ADD_BOOK_ISBN_ARG}" + + "&$ADD_BOOK_SUBTITLE_ARG={$ADD_BOOK_SUBTITLE_ARG}" + + "&$ADD_BOOK_AUTHORS_ARG={$ADD_BOOK_AUTHORS_ARG}" + + "&$ADD_BOOK_PUBLISHER_ARG={$ADD_BOOK_PUBLISHER_ARG}" + + "&$ADD_BOOK_PUBLISHED_DATE_ARG={$ADD_BOOK_PUBLISHED_DATE_ARG}" + + "&$ADD_BOOK_PAGES_ARG={$ADD_BOOK_PAGES_ARG}" + + "&$ADD_BOOK_DESCRIPTION_ARG={$ADD_BOOK_DESCRIPTION_ARG}" + + "&$ADD_BOOK_COVER_URL_ARG={$ADD_BOOK_COVER_URL_ARG}" fun libraryFilteredByShelf(shelfId: String) = "library?shelfId=$shelfId" fun detail(bookId: String) = "detail/$bookId" /** - * Builds the add-by-hand route, percent-encoding [title]/[isbn] so values - * containing `&`, `?`, `/`, `#` etc. (titles routinely do) survive as a single + * Builds the add-by-hand route, percent-encoding every arg so values containing + * `&`, `?`, `/`, `#` etc. (titles/descriptions routinely do) survive as a single * query value instead of corrupting the route string. [Uri.encode] rather than * [java.net.URLEncoder] because the latter encodes space as `+`, which * Navigation Compose's Uri-pattern argument matching does NOT decode back to @@ -44,10 +68,27 @@ object Routes { * nothing to pre-fill (e.g. the plain library FAB) gets exactly the bare * "add" route wave 9's [org.modg.bookshelf.ui.add.AddBookScreen] expects. */ - fun addBook(title: String? = null, isbn: String? = null): String { + fun addBook( + title: String? = null, + isbn: String? = null, + subtitle: String? = null, + authors: String? = null, + publisher: String? = null, + publishedDate: String? = null, + pages: String? = null, + description: String? = null, + coverSourceUrl: String? = null, + ): String { val query = buildList { title?.let { add("$ADD_BOOK_TITLE_ARG=${Uri.encode(it)}") } isbn?.let { add("$ADD_BOOK_ISBN_ARG=${Uri.encode(it)}") } + subtitle?.let { add("$ADD_BOOK_SUBTITLE_ARG=${Uri.encode(it)}") } + authors?.let { add("$ADD_BOOK_AUTHORS_ARG=${Uri.encode(it)}") } + publisher?.let { add("$ADD_BOOK_PUBLISHER_ARG=${Uri.encode(it)}") } + publishedDate?.let { add("$ADD_BOOK_PUBLISHED_DATE_ARG=${Uri.encode(it)}") } + pages?.let { add("$ADD_BOOK_PAGES_ARG=${Uri.encode(it)}") } + description?.let { add("$ADD_BOOK_DESCRIPTION_ARG=${Uri.encode(it)}") } + coverSourceUrl?.let { add("$ADD_BOOK_COVER_URL_ARG=${Uri.encode(it)}") } } return if (query.isEmpty()) ADD_BOOK_BASE else "$ADD_BOOK_BASE?" + query.joinToString("&") } diff --git a/app/app/src/test/java/org/modg/bookshelf/data/metadata/GoogleBooksSearchClientTest.kt b/app/app/src/test/java/org/modg/bookshelf/data/metadata/GoogleBooksSearchClientTest.kt new file mode 100644 index 0000000..6e29acc --- /dev/null +++ b/app/app/src/test/java/org/modg/bookshelf/data/metadata/GoogleBooksSearchClientTest.kt @@ -0,0 +1,143 @@ +package org.modg.bookshelf.data.metadata + +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.test.runTest +import kotlinx.serialization.json.Json +import okhttp3.Interceptor +import okhttp3.OkHttpClient +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Assert.fail +import org.junit.Test + +/** + * Parses checked-in sample JSON fixtures — no network. Reuses the existing + * googlebooks_*.json fixtures ([GoogleBooksClientTest] already established they're + * shaped like a real `volumes?q=` response, which search and ISBN lookup share). + */ +class GoogleBooksSearchClientTest { + + private val client = GoogleBooksSearchClient(OkHttpClient(), Json) + + @Test + fun `parses a successful response into SearchHits with null industryIdentifiers and no imageLinks handled`() { + val body = """ + {"items": [{"volumeInfo": {"title": "Some Title", "authors": ["Some Author"]}}]} + """.trimIndent() + val hits = client.parseResponse(body) + + assertEquals(1, hits.size) + assertEquals("Some Title", hits[0].title) + assertEquals(listOf("Some Author"), hits[0].authors) + assertTrue(hits[0].isbn13s.isEmpty()) + assertEquals(null, hits[0].coverUrl) + } + + @Test + fun `parses the real success fixture and forces https zoom=2 on the cover`() { + val hits = client.parseResponse(fixture("googlebooks_success.json")) + assertEquals(1, hits.size) + assertEquals("Effective Java", hits[0].title) + assertEquals(listOf("9780134685991"), hits[0].isbn13s) + assertEquals( + "https://books.google.com/books/content?id=ABC123XYZ&printsec=frontcover&img=1&zoom=2", + hits[0].coverUrl, + ) + } + + @Test + fun `a garbage publishedDate does not become a year`() { + val body = """ + {"items": [{"volumeInfo": {"title": "Old Book", "publishedDate": "101-01-01"}}]} + """.trimIndent() + val hits = client.parseResponse(body) + assertEquals(null, hits[0].year) + } + + @Test + fun `returns NotFound for no items, not an empty Found`() { + assertEquals(SearchSourceResult.NotFound, client.classify(200, fixture("googlebooks_no_items.json"))) + } + + @Test + fun `classify reports Failed for a non-2xx status`() { + assertEquals(SearchSourceResult.Failed("http 404", FailureKind.CLIENT_ERROR), client.classify(404, null)) + } + + @Test + fun `classify reports Failed for malformed json`() { + assertEquals(SearchSourceResult.Failed("malformed json", FailureKind.MALFORMED), client.classify(200, fixture("malformed.json"))) + } + + // --- requestUrl(): key only when configured, query url-encoded --- + + @Test + fun `requestUrl omits the key when blank`() { + val url = client.requestUrl("dune") + assertTrue(url.contains("q=dune")) + assertFalse(url.contains("key=")) + } + + @Test + fun `requestUrl carries the key only when configured`() { + val keyed = GoogleBooksSearchClient(OkHttpClient(), Json, apiKey = "test-key-123") + assertTrue(keyed.requestUrl("dune").contains("key=test-key-123")) + } + + @Test + fun `requestUrl percent-encodes a query with url-breaking characters`() { + val url = client.requestUrl("a&b c") + assertFalse(url.contains("q=a&b")) + assertTrue(url.contains("q=a%26b")) + } + + // --- redact(): the key must never survive into a shown reason --- + + @Test + fun `redact scrubs the api key from a leaked reason`() { + val key = "test-key-123" + val keyed = GoogleBooksSearchClient(OkHttpClient(), Json, apiKey = key) + val leakedUrl = keyed.requestUrl("dune") + check(leakedUrl.contains(key)) + + val leaking = SearchSourceResult.Failed("network error: connect to $leakedUrl failed", FailureKind.TRANSPORT) + val redacted = keyed.redact(leaking) as SearchSourceResult.Failed + + assertFalse(redacted.reason.contains(key)) + assertTrue(redacted.reason.contains("[REDACTED]")) + } + + // --- fetch(): non-IOException -> Failed(UNEXPECTED); CancellationException propagates --- + + @Test + fun `searchOnce reports Failed UNEXPECTED, not a crash, when the http call throws a non-IOException`() = runTest { + val client = clientThrowing(IllegalStateException("boom")) + val result = client.searchOnce("dune") + val failed = result as? SearchSourceResult.Failed + checkNotNull(failed) { "expected Failed, got $result" } + assertEquals(FailureKind.UNEXPECTED, failed.kind) + assertEquals("unexpected: IllegalStateException", failed.reason) + } + + @Test + fun `CancellationException propagates out of the client rather than becoming a Failed`() = runTest { + val client = clientThrowing(CancellationException("scope cancelled")) + try { + client.searchOnce("dune") + fail("expected CancellationException to propagate") + } catch (e: CancellationException) { + // expected + } + } + + private fun clientThrowing(t: Throwable): GoogleBooksSearchClient { + val httpClient = OkHttpClient.Builder().addInterceptor(Interceptor { throw t }).build() + return GoogleBooksSearchClient(httpClient, Json) + } + + private fun fixture(name: String): String = + checkNotNull(javaClass.classLoader.getResourceAsStream("fixtures/$name")) { "missing fixture $name" } + .bufferedReader() + .readText() +} diff --git a/app/app/src/test/java/org/modg/bookshelf/data/metadata/OpenLibrarySearchClientTest.kt b/app/app/src/test/java/org/modg/bookshelf/data/metadata/OpenLibrarySearchClientTest.kt new file mode 100644 index 0000000..9fe54fe --- /dev/null +++ b/app/app/src/test/java/org/modg/bookshelf/data/metadata/OpenLibrarySearchClientTest.kt @@ -0,0 +1,139 @@ +package org.modg.bookshelf.data.metadata + +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.test.runTest +import kotlinx.serialization.json.Json +import okhttp3.Interceptor +import okhttp3.OkHttpClient +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertNull +import org.junit.Assert.assertTrue +import org.junit.Assert.fail +import org.junit.Test + +/** + * Parses checked-in sample JSON fixtures (src/test/resources/fixtures) — no network + * involved, offline-safe. Mirrors [OpenLibraryClientTest]'s structure for the sibling + * search endpoint. + */ +class OpenLibrarySearchClientTest { + + private val client = OpenLibrarySearchClient(OkHttpClient(), Json) + + @Test + fun `parses a successful response, normalizing mixed isbn10-13 to one distinct isbn13`() { + val hits = client.parseResponse(fixture("openlibrary_search_success.json")) + + assertEquals(2, hits.size) + val hittite = hits[0] + assertEquals("Hittite Warrior", hittite.title) + assertEquals(listOf("Joanne S. Williamson"), hittite.authors) + assertEquals(1960, hittite.year) + assertEquals("Bethlehem Books", hittite.publisher) + assertEquals(237, hittite.pageCount) + assertEquals("https://covers.openlibrary.org/b/id/930599-M.jpg", hittite.coverUrl) + // "1883937388" (isbn-10) and "9781883937386" (isbn-13) are the SAME edition -- + // must normalize to one distinct value, not two. + assertEquals(listOf("9781883937386"), hittite.isbn13s) + } + + @Test + fun `a doc with no isbn field parses with an empty isbn13s list, not a crash`() { + val hits = client.parseResponse(fixture("openlibrary_search_success.json")) + val noIsbn = hits[1] + assertEquals("A Book With No ISBN On File", noIsbn.title) + assertTrue(noIsbn.isbn13s.isEmpty()) + } + + @Test + fun `a doc with no cover_i has a null coverUrl, never a synthesized one`() { + val hits = client.parseResponse(fixture("openlibrary_search_success.json")) + assertNull(hits[1].coverUrl) + } + + @Test + fun `fails soft on malformed json instead of throwing`() { + assertTrue(client.parseResponse(fixture("malformed.json")).isEmpty()) + } + + // --- classify(): the three-way per-source outcome --- + + @Test + fun `classify reports Found for docs with results`() { + val result = client.classify(200, fixture("openlibrary_search_success.json")) + val found = result as? SearchSourceResult.Found + checkNotNull(found) { "expected Found, got $result" } + assertEquals(2, found.hits.size) + } + + @Test + fun `classify reports NotFound for an empty docs list, distinctly from a failure`() { + val result = client.classify(200, fixture("openlibrary_search_no_docs.json")) + assertEquals(SearchSourceResult.NotFound, result) + } + + @Test + fun `classify reports Failed for a non-2xx status, not NotFound`() { + assertEquals(SearchSourceResult.Failed("http 500 (server error)", FailureKind.SERVER_ERROR), client.classify(500, null)) + assertEquals(SearchSourceResult.Failed("http 429 (rate limited)", FailureKind.RATE_LIMITED), client.classify(429, null)) + } + + @Test + fun `classify reports Failed for malformed json even on a 2xx status`() { + assertEquals( + SearchSourceResult.Failed("malformed json", FailureKind.MALFORMED), + client.classify(200, fixture("malformed.json")), + ) + } + + // --- requestUrl(): fields= (with isbn) must be present, and the query URL-encoded --- + + @Test + fun `requestUrl includes fields= with isbn -- without it OL omits isbns entirely`() { + val url = client.requestUrl("dune") + assertTrue(url.contains("fields=")) + assertTrue(url.contains("isbn")) + } + + @Test + fun `requestUrl percent-encodes a query with url-breaking characters`() { + val url = client.requestUrl("a&b c") + // A literal, un-encoded "&" would split the query into two bogus parameters. + assertFalse(url.contains("q=a&b")) + assertTrue(url.contains("q=a%26b")) + } + + // --- fetch(): non-IOException -> Failed(UNEXPECTED); CancellationException propagates --- + + @Test + fun `searchOnce reports Failed UNEXPECTED, not a crash, when the http call throws a non-IOException`() = runTest { + val client = clientThrowing(IllegalStateException("boom")) + val result = client.searchOnce("dune") + val failed = result as? SearchSourceResult.Failed + checkNotNull(failed) { "expected Failed, got $result" } + assertEquals(FailureKind.UNEXPECTED, failed.kind) + assertEquals("unexpected: IllegalStateException", failed.reason) + } + + @Test + fun `CancellationException propagates out of the client rather than becoming a Failed`() = runTest { + val client = clientThrowing(CancellationException("scope cancelled")) + try { + client.searchOnce("dune") + fail("expected CancellationException to propagate") + } catch (e: CancellationException) { + // expected + } + } + + private fun clientThrowing(t: Throwable): OpenLibrarySearchClient { + val httpClient = OkHttpClient.Builder().addInterceptor(Interceptor { throw t }).build() + return OpenLibrarySearchClient(httpClient, Json) + } + + private fun fixture(name: String): String = + checkNotNull(javaClass.classLoader.getResourceAsStream("fixtures/$name")) { "missing fixture $name" } + .bufferedReader() + .readText() +} diff --git a/app/app/src/test/java/org/modg/bookshelf/data/metadata/SearchResultMergerTest.kt b/app/app/src/test/java/org/modg/bookshelf/data/metadata/SearchResultMergerTest.kt new file mode 100644 index 0000000..bbd8df3 --- /dev/null +++ b/app/app/src/test/java/org/modg/bookshelf/data/metadata/SearchResultMergerTest.kt @@ -0,0 +1,240 @@ +package org.modg.bookshelf.data.metadata + +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNull +import org.junit.Assert.assertTrue +import org.junit.Test +import org.modg.bookshelf.data.local.BookEntity +import org.modg.bookshelf.data.local.SyncState +import org.modg.bookshelf.data.repo.encodeAuthors + +class SearchResultMergerTest { + + private fun book( + id: String, + title: String, + authors: List, + isbn13: String? = null, + isbn10: String? = null, + ) = BookEntity( + id = id, + title = title, + authorsJson = encodeAuthors(authors), + isbn13 = isbn13, + isbn10 = isbn10, + createdAt = 1, + updatedAt = 1, + syncState = SyncState.SYNCED, + ) + + private fun hit( + title: String?, + authors: List = emptyList(), + isbn13s: List = emptyList(), + year: Int? = null, + coverUrl: String? = null, + publisher: String? = null, + pageCount: Int? = null, + ) = SearchHit(title, authors = authors, isbn13s = isbn13s, year = year, coverUrl = coverUrl, publisher = publisher, pageCount = pageCount) + + // --- identity: ISBN --- + + @Test + fun `a GB edition joins an OL work by a shared isbn13`() { + val ol = hit("Dune", listOf("Frank Herbert"), isbn13s = listOf("9780441013593", "9780451128596")) + val gb = hit("Dune", listOf("Frank Herbert"), isbn13s = listOf("9780441013593")) + + val result = SearchResultMerger.merge(emptyList(), listOf(ol), listOf(gb)) + + assertEquals(1, result.online.size) + assertEquals(setOf(SearchSource.OPEN_LIBRARY, SearchSource.GOOGLE_BOOKS), result.online[0].sources) + } + + // --- identity: title + author surname, near-duplicate author spellings --- + + @Test + fun `near-duplicate author spellings for the same work collapse into one entry`() { + // Real example from the task brief: the same book returned as two OL works. + val a = hit("Hittite Warrior", listOf("Joanne S. Williamson")) + val b = hit("Hittite Warrior", listOf("Joanne Small Williamson")) + + val result = SearchResultMerger.merge(emptyList(), listOf(a, b), emptyList()) + + assertEquals(1, result.online.size) + } + + @Test + fun `initials vs full name for the same author collapse via surname matching`() { + val a = hit("The Hobbit", listOf("J.R.R. Tolkien")) + val b = hit("The Hobbit", listOf("John Ronald Reuel Tolkien")) + + val result = SearchResultMerger.merge(emptyList(), listOf(a), listOf(b)) + + assertEquals(1, result.online.size) + } + + @Test + fun `five GB editions of the same title plus one OL work collapse to ONE online entry`() { + val ol = hit("The Hobbit", listOf("J.R.R. Tolkien")) + val gbEditions = List(5) { hit("The Hobbit", listOf("J. R. R. Tolkien")) } + + val result = SearchResultMerger.merge(emptyList(), listOf(ol), gbEditions) + + assertEquals(1, result.online.size) + } + + @Test + fun `distinct books with the same title but different authors stay separate`() { + val a = hit("Circe", listOf("Madeline Miller")) + val b = hit("Circe", listOf("Someone Else")) + + val result = SearchResultMerger.merge(emptyList(), listOf(a, b), emptyList()) + + assertEquals(2, result.online.size) + } + + // --- transitivity --- + + @Test + fun `transitivity -- A tilde B by isbn, B tilde C by title, all three land in one group`() { + // A and C share NEITHER an isbn NOR a title+author -- only B bridges them, so + // this only passes if grouping is transitive (union-find) rather than pairwise. + val a = hit("Title A", listOf("Author A"), isbn13s = listOf("9781111111111")) + val b = hit("Title B", listOf("Author B"), isbn13s = listOf("9781111111111")) // shares A's isbn + val c = hit("Title B", listOf("Author B")) // shares B's title+author, nothing in common with A + + val result = SearchResultMerger.merge(emptyList(), listOf(a), listOf(b, c)) + + assertEquals(1, result.online.size) + } + + // --- local books: placement, and the "reached only via an online hit" case --- + + @Test + fun `an online hit of a different edition of an owned book lands in inLibrary, not online`() { + val owned = book("b1", "Dune", listOf("Frank Herbert"), isbn13 = "9780451128596") + val differentEditionOnline = hit("Dune", listOf("Frank Herbert"), isbn13s = listOf("9780441013593")) + + val result = SearchResultMerger.merge(listOf(owned), listOf(differentEditionOnline), emptyList()) + + assertEquals(listOf(owned), result.inLibrary) + assertTrue("the online hit must not ALSO surface as a new online result", result.online.isEmpty()) + } + + @Test + fun `an online hit unrelated to any local book stays in online`() { + val owned = book("b1", "Dune", listOf("Frank Herbert"), isbn13 = "9780451128596") + val unrelated = hit("Piranesi", listOf("Susanna Clarke")) + + val result = SearchResultMerger.merge(listOf(owned), listOf(unrelated), emptyList()) + + assertEquals(listOf(owned), result.inLibrary) + assertEquals(1, result.online.size) + assertEquals("Piranesi", result.online[0].title) + } + + @Test + fun `two local books sharing a group are both shown, not deduped away`() { + val copy1 = book("b1", "Dune", listOf("Frank Herbert"), isbn13 = "9780441013593") + val copy2 = book("b2", "Dune", listOf("Frank Herbert"), isbn13 = "9780441013593") + + val result = SearchResultMerger.merge(listOf(copy1, copy2), emptyList(), emptyList()) + + assertEquals(setOf("b1", "b2"), result.inLibrary.map { it.id }.toSet()) + } + + // --- ranking --- + + @Test + fun `online results are ordered by the best rank either source returned a member at`() { + // The real hit is OL position 0 AND GB position 0 (rank 0); the noise trails + // at OL position 1 with nothing in GB backing it (rank 1) -- it must sort after. + val olRealHit = hit("The Real Book", listOf("Real Author"), isbn13s = listOf("9781111111111")) + val olNoise = hit("Loosely Related Noise", listOf("Nobody")) + val gbRealHit = hit("The Real Book", listOf("Real Author"), isbn13s = listOf("9781111111111")) + + val result = SearchResultMerger.merge( + local = emptyList(), + openLibrary = listOf(olRealHit, olNoise), + googleBooks = listOf(gbRealHit), + ) + + assertEquals(2, result.online.size) + assertEquals("The Real Book", result.online[0].title) + assertEquals("Loosely Related Noise", result.online[1].title) + } + + // --- field fill --- + + @Test + fun `title and authors come from OL when OL has a title, year and cover fall through independently`() { + // Tied together by a shared isbn13 so they're one group despite OL and GB + // disagreeing on title/author string -- exactly the shape a real work-vs- + // edition pairing takes, and what makes the primary/secondary choice visible. + val ol = hit("Concrete Mathematics", listOf("Ronald L. Graham"), isbn13s = listOf("9780201558029"), year = null, coverUrl = null) + val gb = hit( + "A Different Title Entirely", + listOf("GB Author"), + isbn13s = listOf("9780201558029"), + year = 1994, + coverUrl = "https://example.com/cover.jpg", + ) + + val merged = SearchResultMerger.merge(emptyList(), listOf(ol), listOf(gb)).online.single() + + assertEquals("Concrete Mathematics", merged.title) // OL wins because it has a title + assertEquals(listOf("Ronald L. Graham"), merged.authors) + assertEquals(1994, merged.year) // OL's is null, falls through to GB + assertEquals("https://example.com/cover.jpg", merged.coverUrl) + } + + // --- ISBN rule: each branch --- + + @Test + fun `isbn rule -- OL isbn10 and isbn13 of the same edition normalize to one, and that one is used`() { + val ol = hit("Hittite Warrior", listOf("Joanne S. Williamson"), isbn13s = listOf("9781883937386")) // already normalized by the client + val merged = SearchResultMerger.merge(emptyList(), listOf(ol), emptyList()).online.single() + assertEquals("9781883937386", merged.isbn13) + } + + @Test + fun `isbn rule -- OL has many isbns, GB has exactly one -- GB's isbn wins`() { + val ol = hit("Popular Book", listOf("Author"), isbn13s = listOf("9781111111111", "9782222222222", "9783333333333")) + val gb = hit("Popular Book", listOf("Author"), isbn13s = listOf("9781111111111")) + val merged = SearchResultMerger.merge(emptyList(), listOf(ol), listOf(gb)).online.single() + assertEquals("9781111111111", merged.isbn13) + } + + @Test + fun `isbn rule -- several GB isbns and no single-edition OL work -- no isbn is recorded`() { + val ol = hit("Popular Book", listOf("Author"), isbn13s = listOf("9781111111111", "9782222222222")) + val gbA = hit("Popular Book", listOf("Author"), isbn13s = listOf("9784444444444")) + val gbB = hit("Popular Book", listOf("Author"), isbn13s = listOf("9785555555555")) + val merged = SearchResultMerger.merge(emptyList(), listOf(ol), listOf(gbA, gbB)).online.single() + assertNull(merged.isbn13) + } + + @Test + fun `isbn rule -- OL has no isbn field -- falls through to GB`() { + val ol = hit("Popular Book", listOf("Author"), isbn13s = emptyList()) + val gb = hit("Popular Book", listOf("Author"), isbn13s = listOf("9781111111111")) + val merged = SearchResultMerger.merge(emptyList(), listOf(ol), listOf(gb)).online.single() + assertEquals("9781111111111", merged.isbn13) + } + + @Test + fun `isbn rule -- neither source resolves to a single isbn -- null, never a guess`() { + val ol = hit("Popular Book", listOf("Author"), isbn13s = emptyList()) + val gbA = hit("Popular Book", listOf("Author"), isbn13s = listOf("9781111111111")) + val gbB = hit("Popular Book", listOf("Author"), isbn13s = listOf("9782222222222")) + val merged = SearchResultMerger.merge(emptyList(), listOf(ol), listOf(gbA, gbB)).online.single() + assertNull(merged.isbn13) + } + + @Test + fun `empty inputs produce empty results without throwing`() { + val result = SearchResultMerger.merge(emptyList(), emptyList(), emptyList()) + assertTrue(result.inLibrary.isEmpty()) + assertTrue(result.online.isEmpty()) + } +} 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 new file mode 100644 index 0000000..f6ac234 --- /dev/null +++ b/app/app/src/test/java/org/modg/bookshelf/ui/library/LibraryViewModelTest.kt @@ -0,0 +1,348 @@ +package org.modg.bookshelf.ui.library + +import androidx.room.Room +import androidx.test.core.app.ApplicationProvider +import java.util.concurrent.CountDownLatch +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.resetMain +import kotlinx.coroutines.test.runTest +import kotlinx.coroutines.test.setMain +import kotlinx.serialization.json.Json +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.OkHttpClient +import okhttp3.Protocol +import okhttp3.Request +import okhttp3.Response +import okhttp3.ResponseBody.Companion.toResponseBody +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNull +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +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.modg.bookshelf.data.metadata.BookSearchRepository +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.LocationRepository +import org.modg.bookshelf.data.repo.SyncEngine +import org.modg.bookshelf.data.repo.decodeAuthors +import org.modg.bookshelf.data.remote.ApiProvider +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config + +/** + * Wave 10 (online search). Covers the ViewModel-owned parts of the task's contract: + * typing never hits the network, submit does, editing after a submit clears/cancels, + * partial and total source failure, and the online-result save sheet's field mapping + * and error handling. Parsing/merge logic itself is covered by + * [org.modg.bookshelf.data.metadata.SearchResultMergerTest] and the client tests — + * this file only exercises what's specific to owning that state in a ViewModel. + */ +@OptIn(ExperimentalCoroutinesApi::class) +@RunWith(RobolectricTestRunner::class) +@Config(sdk = [34]) +class LibraryViewModelTest { + + private lateinit var db: BookshelfDatabase + private lateinit var settingsStore: SettingsStore + private lateinit var locationRepository: LocationRepository + private lateinit var bookRepository: BookRepository + private lateinit var context: android.content.Context + + @Before + fun setUp() { + Dispatchers.setMain(UnconfinedTestDispatcher()) + context = ApplicationProvider.getApplicationContext() + db = Room.inMemoryDatabaseBuilder(context, BookshelfDatabase::class.java) + .allowMainThreadQueries() + .build() + settingsStore = SettingsStore(context) + locationRepository = LocationRepository(db.bookcaseDao(), db.shelfDao(), db.bookDao()) + bookRepository = BookRepository(db.bookDao(), context) + } + + @After + fun tearDown() { + db.close() + Dispatchers.resetMain() + } + + private fun viewModel(searchHttpClient: OkHttpClient = OkHttpClient()): LibraryViewModel = LibraryViewModel( + bookRepository = bookRepository, + locationRepository = locationRepository, + syncEngine = SyncEngine(ApiProvider { null }, db.bookDao(), db.bookcaseDao(), db.shelfDao(), settingsStore), + settingsStore = settingsStore, + bookSearchRepository = BookSearchRepository(searchHttpClient, Json { ignoreUnknownKeys = true }), + metadataRepository = MetadataRepository(OkHttpClient(), Json { ignoreUnknownKeys = true }), + initialShelfId = null, + ) + + // --- typing vs submit --- + + @Test + fun `typing does NOT trigger an online search`() = runTest { + val vm = viewModel() + vm.onQueryChange("d") + vm.onQueryChange("du") + vm.onQueryChange("dune") + + assertEquals(OnlineSearchState.NotSearched, vm.onlineSearch.first()) + } + + @Test + fun `submit with fewer than 2 characters does nothing`() = runTest { + val vm = viewModel() + vm.onQueryChange("d") + vm.submitOnlineSearch() + + assertEquals(OnlineSearchState.NotSearched, vm.onlineSearch.first()) + } + + @Test + fun `submit runs the online search and populates online results`() = runTest { + val vm = viewModel(fakeSearchClient(olBody = olHit("Dune", "Frank Herbert"), gbBody = gbEmpty())) + vm.onQueryChange("dune") + vm.submitOnlineSearch() + + val done = vm.onlineSearch.first { it is OnlineSearchState.Done } as OnlineSearchState.Done + assertEquals(1, done.online.size) + assertEquals("Dune", done.online[0].title) + assertNull(done.openLibraryFailure) + assertNull(done.googleBooksFailure) + } + + // --- editing after a submit --- + + @Test + fun `editing the query after a submit clears the online section and cancels the in-flight search`() = runTest { + val gate = CountDownLatch(1) + val vm = viewModel(gatedFakeSearchClient(gate, olBody = olHit("Dune", "Frank Herbert"), gbBody = gbEmpty())) + vm.onQueryChange("dune") + vm.submitOnlineSearch() + + // .first(), not .value -- see the KDoc convention this codebase already uses + // (e.g. AddBookViewModel.uiState): a bare .value read with no live collector + // only ever sees the seed passed to stateIn. The fake call is blocked on the + // gate, so the phase genuinely stays Searching until collection observes it. + val searching = vm.onlineSearch.first { it !is OnlineSearchState.NotSearched } + assertTrue("expected Searching, got $searching", searching is OnlineSearchState.Searching) + + vm.onQueryChange("something else") + + assertEquals(OnlineSearchState.NotSearched, vm.onlineSearch.first()) + gate.countDown() // release the blocked background call so its thread isn't leaked + } + + // --- partial and total source failure --- + + @Test + fun `one source failing still shows the other source's results, with a failure line naming it`() = runTest { + val vm = viewModel(fakeSearchClient(olCode = 404, olBody = "", gbBody = gbHit("Dune", "Frank Herbert"))) + vm.onQueryChange("dune") + vm.submitOnlineSearch() + + val done = vm.onlineSearch.first { it is OnlineSearchState.Done } as OnlineSearchState.Done + assertEquals(1, done.online.size) + assertEquals("Dune", done.online[0].title) + checkNotNull(done.openLibraryFailure) + assertNull(done.googleBooksFailure) + } + + @Test + fun `both sources failing is reported as failures, never as an authoritative no-results`() = runTest { + val vm = viewModel(fakeSearchClient(olCode = 404, olBody = "", gbCode = 404, gbBody = "")) + vm.onQueryChange("dune") + vm.submitOnlineSearch() + + val done = vm.onlineSearch.first { it is OnlineSearchState.Done } as OnlineSearchState.Done + assertTrue(done.online.isEmpty()) + checkNotNull(done.openLibraryFailure) + checkNotNull(done.googleBooksFailure) + } + + @Test + fun `both sources answering with nothing is an authoritative empty result, no failures`() = runTest { + val vm = viewModel(fakeSearchClient(olBody = olEmpty(), gbBody = gbEmpty())) + vm.onQueryChange("zzzznonexistentzzzz") + vm.submitOnlineSearch() + + val done = vm.onlineSearch.first { it is OnlineSearchState.Done } as OnlineSearchState.Done + assertTrue(done.online.isEmpty()) + assertNull(done.openLibraryFailure) + assertNull(done.googleBooksFailure) + } + + // --- save sheet --- + + @Test + fun `save maps every online book field to createBook`() = runTest { + val bookcaseId = locationRepository.createBookcase(name = "Living Room") + val shelfId = locationRepository.createShelf(bookcaseId, label = "Top shelf") + val vm = viewModel() + val onlineBook = OnlineBook( + title = "Dune", + subtitle = "A novel", + authors = listOf("Frank Herbert"), + year = 1965, + publisher = "Ace", + pageCount = 412, + coverUrl = "https://example.com/cover.jpg", + isbn13 = "9780441013593", + ) + vm.onOnlineResultTapped(onlineBook) + vm.onSaveSheetShelfSelected(shelfId) + + val bookId = vm.performSaveOnlineResult() + checkNotNull(bookId) + + val saved = bookRepository.getById(bookId) + checkNotNull(saved) + assertEquals("Dune", saved.title) + assertEquals("A novel", saved.subtitle) + assertEquals(listOf("Frank Herbert"), decodeAuthors(saved.authorsJson)) + assertEquals("Ace", saved.publisher) + assertEquals("1965", saved.publishedDate) + assertEquals(412, saved.pageCount) + assertEquals("9780441013593", saved.isbn13) + assertEquals("https://example.com/cover.jpg", saved.coverSourceUrl) + assertEquals(shelfId, saved.shelfId) + } + + @Test + fun `after a save the sheet closes and the book appears in inLibrary via the Room flow`() = runTest { + val vm = viewModel(fakeSearchClient(olBody = olHit("Dune", "Frank Herbert", isbn = "9780441013593"), gbBody = gbEmpty())) + vm.onQueryChange("dune") + vm.submitOnlineSearch() + val beforeSave = vm.onlineSearch.first { it is OnlineSearchState.Done } as OnlineSearchState.Done + assertEquals(1, beforeSave.online.size) + assertTrue(beforeSave.inLibrary.isEmpty()) + + vm.onOnlineResultTapped(beforeSave.online[0]) + vm.performSaveOnlineResult() + + assertEquals(OnlineSaveSheetState.Hidden, vm.saveSheet.first()) + val afterSave = vm.onlineSearch.first { (it as? OnlineSearchState.Done)?.online?.isEmpty() == true } as OnlineSearchState.Done + assertEquals(1, afterSave.inLibrary.size) + assertEquals("Dune", afterSave.inLibrary[0].title) + } + + @Test + 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( + bookRepository = throwingBookRepository, + locationRepository = locationRepository, + syncEngine = SyncEngine(ApiProvider { null }, db.bookDao(), db.bookcaseDao(), db.shelfDao(), settingsStore), + settingsStore = settingsStore, + bookSearchRepository = BookSearchRepository(OkHttpClient(), Json { ignoreUnknownKeys = true }), + metadataRepository = MetadataRepository(OkHttpClient(), Json { ignoreUnknownKeys = true }), + initialShelfId = null, + ) + vm.onOnlineResultTapped(OnlineBook(title = "Dune")) + + val result = vm.performSaveOnlineResult() // must not throw + + assertNull(result) + val shown = vm.saveSheet.first() as? OnlineSaveSheetState.Shown + checkNotNull(shown) + assertEquals("IllegalStateException", shown.saveError) + assertTrue(!shown.isSaving) + } + + @Test + fun `a CancellationException during save propagates rather than being swallowed`() = runTest { + val vm = LibraryViewModel( + bookRepository = BookRepository(ThrowingUpsertBookDao(db.bookDao(), CancellationException("scope cancelled")), context), + locationRepository = locationRepository, + syncEngine = SyncEngine(ApiProvider { null }, db.bookDao(), db.bookcaseDao(), db.shelfDao(), settingsStore), + settingsStore = settingsStore, + bookSearchRepository = BookSearchRepository(OkHttpClient(), Json { ignoreUnknownKeys = true }), + metadataRepository = MetadataRepository(OkHttpClient(), Json { ignoreUnknownKeys = true }), + initialShelfId = null, + ) + vm.onOnlineResultTapped(OnlineBook(title = "Dune")) + + try { + vm.performSaveOnlineResult() + org.junit.Assert.fail("expected CancellationException to propagate") + } catch (e: CancellationException) { + // expected + } + } + + @Test + fun `a second save while one is in flight does not create a second book`() = runTest { + val vm = viewModel() + vm.onOnlineResultTapped(OnlineBook(title = "Dune")) + + val first = kotlinx.coroutines.CoroutineScope(Dispatchers.Unconfined).launch { vm.performSaveOnlineResult() } + val second = kotlinx.coroutines.CoroutineScope(Dispatchers.Unconfined).launch { vm.performSaveOnlineResult() } + first.join() + second.join() + + assertEquals(1, bookRepository.observeAll().first().size) + } + + private class ThrowingUpsertBookDao( + private val delegate: BookDao, + private val toThrow: Throwable, + ) : BookDao by delegate { + override suspend fun upsert(book: BookEntity) = throw toThrow + } + + // --- fake HTTP for BookSearchRepository --- + + private fun fakeSearchClient( + olCode: Int = 200, + olBody: String, + gbCode: Int = 200, + gbBody: String, + ): OkHttpClient = OkHttpClient.Builder() + .addInterceptor { chain -> respond(chain.request(), olCode, olBody, gbCode, gbBody) } + .build() + + private fun gatedFakeSearchClient( + gate: CountDownLatch, + olBody: String, + gbBody: String, + ): OkHttpClient = OkHttpClient.Builder() + .addInterceptor { chain -> gate.await(); respond(chain.request(), 200, olBody, 200, gbBody) } + .build() + + private fun respond(request: Request, olCode: Int, olBody: String, gbCode: Int, gbBody: String): Response = when { + request.url.host.contains("openlibrary") -> jsonResponse(request, olCode, olBody) + request.url.host.contains("googleapis") -> jsonResponse(request, gbCode, gbBody) + else -> error("unexpected host: ${request.url.host}") + } + + private fun jsonResponse(request: Request, code: Int, body: String): Response = Response.Builder() + .request(request) + .protocol(Protocol.HTTP_1_1) + .code(code) + .message("") + .body(body.toResponseBody("application/json".toMediaType())) + .build() + + private fun olHit(title: String, author: String, isbn: String? = null) = """ + {"docs": [{"title": "$title", "author_name": ["$author"]${isbn?.let { ", \"isbn\": [\"$it\"]" }.orEmpty()}}]} + """.trimIndent() + + private fun olEmpty() = """{"docs": []}""" + + private fun gbHit(title: String, author: String) = """ + {"items": [{"volumeInfo": {"title": "$title", "authors": ["$author"]}}]} + """.trimIndent() + + private fun gbEmpty() = """{"items": []}""" +} diff --git a/app/app/src/test/java/org/modg/bookshelf/ui/library/LocalBookMatcherTest.kt b/app/app/src/test/java/org/modg/bookshelf/ui/library/LocalBookMatcherTest.kt new file mode 100644 index 0000000..9618114 --- /dev/null +++ b/app/app/src/test/java/org/modg/bookshelf/ui/library/LocalBookMatcherTest.kt @@ -0,0 +1,74 @@ +package org.modg.bookshelf.ui.library + +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Test +import org.modg.bookshelf.data.local.BookEntity +import org.modg.bookshelf.data.local.SyncState +import org.modg.bookshelf.data.repo.encodeAuthors + +class LocalBookMatcherTest { + + private fun book(id: String, title: String, authors: List, subtitle: String? = null, isbn13: String? = null) = BookEntity( + id = id, + title = title, + subtitle = subtitle, + authorsJson = encodeAuthors(authors), + isbn13 = isbn13, + createdAt = 1, + updatedAt = 1, + syncState = SyncState.SYNCED, + ) + + private val hobbit = book("b1", "The Hobbit", listOf("J.R.R. Tolkien"), isbn13 = "9780547928227") + private val bronte = book("b2", "Jane Eyre", listOf("Charlotte Brontë")) + private val dune = book("b3", "Dune", listOf("Frank Herbert")) + + @Test + fun `title plus author across two fields matches`() { + val result = LocalBookMatcher.match(listOf(hobbit, dune), "hobbit tolkien") + assertEquals(listOf(hobbit), result) + } + + @Test + fun `squashed initials match a token with no spaces`() { + val result = LocalBookMatcher.match(listOf(hobbit, dune), "jrr tolkien") + assertEquals(listOf(hobbit), result) + } + + @Test + fun `token order does not matter`() { + val result = LocalBookMatcher.match(listOf(hobbit, dune), "tolkien hobbit") + assertEquals(listOf(hobbit), result) + } + + @Test + fun `diacritics are stripped for matching`() { + val result = LocalBookMatcher.match(listOf(bronte, dune), "bronte") + assertEquals(listOf(bronte), result) + } + + @Test + fun `a query token matching nothing excludes the book`() { + val result = LocalBookMatcher.match(listOf(hobbit), "hobbit nonexistentword") + assertTrue(result.isEmpty()) + } + + @Test + fun `a token can match the isbn`() { + val result = LocalBookMatcher.match(listOf(hobbit, dune), "9780547928227") + assertEquals(listOf(hobbit), result) + } + + @Test + fun `blank query matches nothing`() { + assertTrue(LocalBookMatcher.match(listOf(hobbit, dune), " ").isEmpty()) + } + + @Test + fun `a subtitle token matches`() { + val withSubtitle = book("b4", "The Way of Kings", listOf("Brandon Sanderson"), subtitle = "The Stormlight Archive") + val result = LocalBookMatcher.match(listOf(withSubtitle, dune), "stormlight") + assertEquals(listOf(withSubtitle), result) + } +} diff --git a/app/app/src/test/java/org/modg/bookshelf/ui/nav/RoutesTest.kt b/app/app/src/test/java/org/modg/bookshelf/ui/nav/RoutesTest.kt index faf6a64..53dd849 100644 --- a/app/app/src/test/java/org/modg/bookshelf/ui/nav/RoutesTest.kt +++ b/app/app/src/test/java/org/modg/bookshelf/ui/nav/RoutesTest.kt @@ -74,4 +74,39 @@ class RoutesTest { assertEquals("add", Routes.addBook().substringBefore("?")) assertEquals("add", Routes.ADD_BOOK.substringBefore("?")) } + + // --- wave 10: the full draft (online search's "Edit details") round-trips too --- + + @Test + fun `round-trips every draft field an online search result can carry`() { + val route = Routes.addBook( + title = "The Hobbit", + subtitle = "or There and Back Again", + authors = "J.R.R. Tolkien, Someone Else", + publisher = "Houghton Mifflin", + publishedDate = "1937", + pages = "310", + isbn = "9780547928227", + description = "A hobbit goes on an adventure & returns home.", + coverSourceUrl = "https://covers.openlibrary.org/b/id/1-M.jpg?x=1&y=2", + ) + + assertEquals("The Hobbit", decode(route, Routes.ADD_BOOK_TITLE_ARG)) + assertEquals("or There and Back Again", decode(route, Routes.ADD_BOOK_SUBTITLE_ARG)) + assertEquals("J.R.R. Tolkien, Someone Else", decode(route, Routes.ADD_BOOK_AUTHORS_ARG)) + assertEquals("Houghton Mifflin", decode(route, Routes.ADD_BOOK_PUBLISHER_ARG)) + assertEquals("1937", decode(route, Routes.ADD_BOOK_PUBLISHED_DATE_ARG)) + assertEquals("310", decode(route, Routes.ADD_BOOK_PAGES_ARG)) + assertEquals("9780547928227", decode(route, Routes.ADD_BOOK_ISBN_ARG)) + assertEquals("A hobbit goes on an adventure & returns home.", decode(route, Routes.ADD_BOOK_DESCRIPTION_ARG)) + assertEquals("https://covers.openlibrary.org/b/id/1-M.jpg?x=1&y=2", decode(route, Routes.ADD_BOOK_COVER_URL_ARG)) + } + + @Test + fun `omitting the new draft fields leaves them out entirely, not as literal nulls`() { + val route = Routes.addBook(title = "Dune") + assertNull(decode(route, Routes.ADD_BOOK_SUBTITLE_ARG)) + assertNull(decode(route, Routes.ADD_BOOK_DESCRIPTION_ARG)) + assertNull(decode(route, Routes.ADD_BOOK_COVER_URL_ARG)) + } } 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 c907751..9d6b5e6 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 @@ -21,6 +21,7 @@ import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.SmallFloatingActionButton +import androidx.compose.material3.Surface import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -30,6 +31,8 @@ import app.cash.paparazzi.Paparazzi 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.metadata.SearchSource import org.modg.bookshelf.ui.components.BookshelfScaffold import org.modg.bookshelf.ui.components.EmptyState import org.modg.bookshelf.ui.components.PaperSurface @@ -40,8 +43,12 @@ import org.modg.bookshelf.ui.components.SyncStatusBar import org.modg.bookshelf.ui.library.LibraryBookCard import org.modg.bookshelf.ui.library.LibraryFilter import org.modg.bookshelf.ui.library.LibraryScreen +import org.modg.bookshelf.ui.library.LibrarySearchResultsContent import org.modg.bookshelf.ui.library.LibrarySortOption import org.modg.bookshelf.ui.library.LibraryToolbar +import org.modg.bookshelf.ui.library.OnlineResultSaveSheet +import org.modg.bookshelf.ui.library.OnlineSaveSheetState +import org.modg.bookshelf.ui.library.OnlineSearchState import org.modg.bookshelf.ui.theme.BookshelfTheme /** @@ -63,12 +70,127 @@ class LibraryScreenPaparazziTest { @Test fun libraryEmptyLight() = snapshotBoth("library-empty") { Empty() } + @Test + fun librarySearchBothSectionsLight() = snapshotBoth("library-search-both-sections") { + SearchShell( + localMatches = listOf(ScreenFixtures.books[1]), // "The Hobbit" -- already owned + onlineSearch = OnlineSearchState.Done( + query = "hobbit", + inLibrary = listOf(ScreenFixtures.books[1]), + online = onlineResults, + openLibraryFailure = null, + googleBooksFailure = null, + ), + ) + } + + @Test + fun librarySearchLoadingLight() = snapshotBoth("library-search-loading") { + SearchShell(localMatches = emptyList(), onlineSearch = OnlineSearchState.Searching(query = "hobbit")) + } + + @Test + fun librarySearchOneSourceFailedLight() = snapshotBoth("library-search-one-failed") { + SearchShell( + localMatches = emptyList(), + onlineSearch = OnlineSearchState.Done( + query = "hobbit", + inLibrary = emptyList(), + online = onlineResults, + openLibraryFailure = "tls connection reset, 3 attempts", + googleBooksFailure = null, + ), + ) + } + + @Test + fun libraryOnlineSaveSheetLight() = snapshotBoth("library-online-save-sheet") { + 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 = {}, + ) + } + } + + /** Two online hits: one merged from both sources, one Open-Library-only -- the shape [SearchResultMerger] actually produces. */ + private val onlineResults = listOf( + OnlineBook( + title = "The Hobbit", + authors = listOf("J.R.R. Tolkien"), + year = 1937, + publisher = "Houghton Mifflin", + isbn13 = "9780547928227", + sources = setOf(SearchSource.OPEN_LIBRARY, SearchSource.GOOGLE_BOOKS), + ), + OnlineBook( + title = "The Annotated Hobbit", + authors = listOf("J.R.R. Tolkien", "Douglas A. Anderson"), + year = 2002, + sources = setOf(SearchSource.OPEN_LIBRARY), + ), + ) + @Composable private fun Populated() = Shell(books = ScreenFixtures.books, syncStatus = SyncStatus.Synced, syncLabel = "Synced • 2m ago") @Composable private fun Empty() = Shell(books = emptyList(), syncStatus = SyncStatus.Offline, syncLabel = "Not synced yet") + /** Same chrome as [Shell] but with a non-blank query, so [LibraryScreen] shows the sectioned search-results view instead of the grid. */ + @Composable + private fun SearchShell(localMatches: List, onlineSearch: OnlineSearchState) { + BookshelfScaffold( + title = "Bookshelf", + actions = { + IconButton(onClick = {}) { Icon(Icons.Outlined.Warehouse, contentDescription = "Bookcases & shelves") } + IconButton(onClick = {}) { Icon(Icons.Outlined.Settings, contentDescription = "Settings") } + }, + floatingActionButton = { + Column(horizontalAlignment = Alignment.End) { + SmallFloatingActionButton(onClick = {}) { Icon(Icons.Outlined.EditNote, contentDescription = "Add a book by hand") } + Spacer(modifier = Modifier.height(12.dp)) + FloatingActionButton(onClick = {}) { Icon(Icons.Outlined.QrCodeScanner, contentDescription = "Scan a book") } + } + }, + syncStatusBar = { SyncStatusBar(status = SyncStatus.Synced, label = "Synced • 2m ago") }, + ) { innerPadding -> + PaperSurface(modifier = Modifier.fillMaxSize()) { + Column(modifier = Modifier.fillMaxSize()) { + LibraryToolbar( + query = "hobbit", + onQueryChange = {}, + sortOption = LibrarySortOption.TITLE, + onSortOptionChange = {}, + filter = LibraryFilter.All, + onFilterChange = {}, + bookcases = ScreenFixtures.bookcases, + shelves = ScreenFixtures.shelves, + ) + LibrarySearchResultsContent( + query = "hobbit", + localMatches = localMatches, + onlineSearch = onlineSearch, + onBookClick = {}, + onSubmitOnlineSearch = {}, + onRetryOnlineSearch = {}, + onOnlineResultClick = {}, + onEnterByHand = {}, + ) + } + } + } + } + @Composable private fun Shell(books: List, syncStatus: SyncStatus, syncLabel: String) { BookshelfScaffold( diff --git a/app/app/src/test/resources/fixtures/openlibrary_search_no_docs.json b/app/app/src/test/resources/fixtures/openlibrary_search_no_docs.json new file mode 100644 index 0000000..c29ff25 --- /dev/null +++ b/app/app/src/test/resources/fixtures/openlibrary_search_no_docs.json @@ -0,0 +1,5 @@ +{ + "numFound": 0, + "start": 0, + "docs": [] +} diff --git a/app/app/src/test/resources/fixtures/openlibrary_search_success.json b/app/app/src/test/resources/fixtures/openlibrary_search_success.json new file mode 100644 index 0000000..9abcf01 --- /dev/null +++ b/app/app/src/test/resources/fixtures/openlibrary_search_success.json @@ -0,0 +1,22 @@ +{ + "numFound": 2, + "start": 0, + "docs": [ + { + "key": "/works/OL2028038W", + "title": "Hittite Warrior", + "author_name": ["Joanne S. Williamson"], + "first_publish_year": 1960, + "cover_i": 930599, + "isbn": ["1883937388", "9781883937386"], + "publisher": ["Bethlehem Books"], + "number_of_pages_median": 237 + }, + { + "key": "/works/OL9999999W", + "title": "A Book With No ISBN On File", + "author_name": ["Some Author"], + "first_publish_year": 1901 + } + ] +} diff --git a/app/app/src/test/snapshots/images/org.modg.bookshelf.ui.screens_LibraryScreenPaparazziTest_libraryOnlineSaveSheetLight_library-online-save-sheet-dark.png b/app/app/src/test/snapshots/images/org.modg.bookshelf.ui.screens_LibraryScreenPaparazziTest_libraryOnlineSaveSheetLight_library-online-save-sheet-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..38470b67872e438bbc28831b1c5e7c6377b7beab GIT binary patch literal 19277 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 N=5_OH6$bYo|2GK9HiiHI literal 0 HcmV?d00001 diff --git a/app/app/src/test/snapshots/images/org.modg.bookshelf.ui.screens_LibraryScreenPaparazziTest_libraryOnlineSaveSheetLight_library-online-save-sheet-light.png b/app/app/src/test/snapshots/images/org.modg.bookshelf.ui.screens_LibraryScreenPaparazziTest_libraryOnlineSaveSheetLight_library-online-save-sheet-light.png new file mode 100644 index 0000000000000000000000000000000000000000..4b591c2cb07aaeaf11887c45d921dbaed2a9f0e8 GIT binary patch literal 20460 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 Hl+XkKkF2m| literal 0 HcmV?d00001 diff --git a/app/app/src/test/snapshots/images/org.modg.bookshelf.ui.screens_LibraryScreenPaparazziTest_librarySearchBothSectionsLight_library-search-both-sections-dark.png b/app/app/src/test/snapshots/images/org.modg.bookshelf.ui.screens_LibraryScreenPaparazziTest_librarySearchBothSectionsLight_library-search-both-sections-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..f3b71dd1dbec28d7721ab3877859bdf069465821 GIT binary patch literal 39472 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

x&S-1SHWFq>&iepBI=nl)B6K8^>pfQ~Z?_}}$1 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 POTbSV^$=MuXCC-p00<@( literal 0 HcmV?d00001 diff --git a/app/app/src/test/snapshots/images/org.modg.bookshelf.ui.screens_LibraryScreenPaparazziTest_librarySearchLoadingLight_library-search-loading-dark.png b/app/app/src/test/snapshots/images/org.modg.bookshelf.ui.screens_LibraryScreenPaparazziTest_librarySearchLoadingLight_library-search-loading-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..0266268cd21c0231c7d527d15eaf6e72f8f2b644 GIT binary patch literal 29863 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 sq9F|R!MC5 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 Lnh#49EQ0?Vsn_B* literal 0 HcmV?d00001 diff --git a/app/app/src/test/snapshots/images/org.modg.bookshelf.ui.screens_LibraryScreenPaparazziTest_librarySearchOneSourceFailedLight_library-search-one-failed-dark.png b/app/app/src/test/snapshots/images/org.modg.bookshelf.ui.screens_LibraryScreenPaparazziTest_librarySearchOneSourceFailedLight_library-search-one-failed-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..0453fa433437024e623e70de1d1cb3de88fa07c5 GIT binary patch literal 43617 zcmc$mRal$B+o#*omf!)3TZ6k6w-Vf;w79#w2TFk;#jQxO0xeS9f_od>ogk&SYjD~0 zzq^Nf?ZJ0_yBy3x@^a;6=AFsReg7UlYpBWNV3K1#dGZ8DQ9xg+Azx%hhF*>1e2t|ZqaKVM!OQobpWXx^ z*qSACmg399p~Yax%I}Z%2L;YZVG()LaAfZ#T{e7ocel;{Xkq&P@lNC+4G0E+XjmWP zzq|#2pkM`5qXEDGL4lYcR09UlqZ(!?ssV&0pqe}&0qXi!U{v$pa`A*Hn5V zV1>RG1#o&t43)rJI8J3%3G48OHDt}`hpdck@0{xJ#r1R@|G=GUGv}@6%<{^&Q+PXE z@;>FMdo~w+O+A@b_encRxdek36Kc;yd=hF}-|KbQCM}A0Ac$kwdIbkV`s9c_xNy~akpa9UUU6-MiJxiqF-5Z z_7EApID+Vdoli?nB+d*@(DJC6o zvroWY1w-*36Yn=>>3pG8PBW{-iM6}3G=}^=YXpolmCKnD*3jZ_9zB$; zK{x*ypOx07hksf-)pnlRaM?ztLJE$%$JH>hUo|M!$gbXCAXYwN>}`Ij)Fu&02c@1( zPa{wT(bEu1K5nY<;r*g`d_b0nHT%6&s!d*ra_7nwfB1rxx4|RU;&WJ!kw=301-Q(2`7UI+4N{veRIa^9|*S+Q7 zfbi+cN|1HsZ4ZggRC?=<`)G#HX*~ec7=+w*AOgEu_WmRe1BM^DNCs5wJ>J|)L-uQ2 zoPREgbeP2L8~&iCMfjfaSS$BY8_!qHCx7@cf53Z}Coa-Imv_HDmGcFb=6iA+E=an3 z&!s2imBQg)P*Now=vuk^i2CLlHaTMj(>Ug1$*fCY}NGfKmS zlpa{`FQ+?QztG%C%T+>kdyd=HwP1r&Pm3>HS1{i8B*x8o83f;9hPr4265m^tKkoV9 zO&gdQIJW@1|5%6}$R?S*&LQ>D0*Re&Lb*8@WqNrv~ks6D!;^3w9n!Q?cpga@|UsLb-fsgfb(rQNc0-r1M{TSv;}*z zS_EQR&KV&ujrB3Zsr?=gCz}gV2$z;^^MWa$4Rm= zmVE{cOK7bHlg`xEF1A;7uspR`3bD=^t*e$k;DL8MUMUE0W|l1f`I0PpaG&s^$yGm) zuX2B3XX!#9*GQ5xx-fp6eVQg`sV*((Z2jjty=PcGL4Ti>OvbXrS#XrD>iupu>d8F7^>O6mP8Z_7@Js1mkU>B*Q zzHA$PJu_8kL{+p$GMP=5BrE0aNa(16%%d_HcT>8E%up$6&3OdJdw*L5=4~S|VIQh+ zYi7tFRMU1?jbB>~uf|Nsq%6<*elSioH^&Hq&Nlhp=o&pV&`3X%=|=Lyo&A7D_q?2Phs+~CtGoZW)K_C_~_ zAQFF>57zwHlN%7hArHSe52Kv5q@4m5C=|SnLfmw zdLH3ua^QdLn&m=mQlBR6D?V7?%|rBOrQ;)uUM%gdGQe$!gUzP&_2cohYWD56pNg-= z%*XT?>H}b5O0lVMwM}-Mi^%Uu7HGQC>MO^{d#$xFy*>O*ubiXhFFh9`P^ZApWVQT8@ ztQC@mx5pd9C$hzS!KUB>Tj`uIBzo)668=>w7aYmGC#7Yaaw3Sw^|NT#tGoGRENhwS zxkvB9^#qsX(pDX?2uQu$UNi5|bi7I8a9_AlyYEHjw(4wA3e@#vWY3}ob^lac4Sv-v z2!gT|w#1g!LmAkI#k&QC1OL)-^xEj)%}s(?m1{hWtP~3wI|BT+8lz~y-+Z|CVZCUH zDg`|hazcrU(HnsK_LvhbOpsq?lGV+6;lSmn^P97%Kf!^Qbvrm*6ls3B`mQH)-o5QM zUS!=#-ueCUxenpQTOR@OBE4&yDrHDksQO{fjO!01WITq4W8`UzpSKHNB}oW!|3O%F zw@aWMI(9}5f~Lj0T(Ux^1J4lWHP5nhudkvInGj}Z-t0$tIaY+(td20cukUo_m+dDAxE?HiMOPg_i0Ag_u7lGB89}d81Sc7a^|#> z#y9~O?^QVpYqw)a3oM`^T)MD zwE!RL+8TO;o|>FNo}375J>|Ltp|gB2DaGR8!L(O|PuY~lmr7&tu;T;#?l+SXJc@yr z%}vf0z3r%nS~9TvD}mYPg`Kw2gO$snqlta`CFk=$?cM?`%Yy!nTkent&?fN=9w&rh zqX3xHKn{rUsA{q=HoNfWkAn*+F}7mncbZNrCp zxr?s^K|`uh)&|Z+K;j?0C0iYV(<%mlan5RNC;Us|KZ2e2HBewbgKjwY8iF^FmWB4M z^mo)FJ!L17t`>Y5p`Wvyo_Fur!hdG8!qjRhCf};{YBr0Us>}lby35H}c?CXQ)=Eud z+#^=?P*!U4IQew)gsFd%sCIhI!=InKxo03c)A-2p`N=AhF|Sj`w^al?SDAc@Nj+y- z+#xt?keG1JG1u(lcFfX)L^RtkZm=j6v<7i_lI$P#4jzBqT>OFE59(m zK`s7RyRx`+@>X!0!lg?5`S4j!kGAmaS6nRcK+wZQ`z6G3mtwxwah6LDP+2bv5NxfV z6$^5ATJv2s3p{}#Ps{@16Tu3RW^z5Q*`u3SQ1aDPVJ65e`Px?v11qsUj?>=<$*0qy zXCmnMN1H=xj(&HTa4M6Jy@(I(Z*qXGeWgr315-DkVa8({Cb?bZnzk=|e4L9;i-aKg z*5MaBV_si1H-U}qMOlhlnhF)%ZA*V6Z{9kP><3KHh+laRCXjvDos+GG)VB2?K> zWBIEIVtss(Iz(A&re;XfYOAtMqiwvN`bF1TW@T2GEvgntzNtpO;Xy&qQ*CtnGMh_< zv^R~6oq#+z0&lgaW)99{83+m}b`V`;f>O2}P*5zMmA7psqCxh~F__XlA}{0K{eu&& zmr&$3dy}H3vnBb_g{*{>?0<<~$BL*TF&o6a-&PmRFk?x6POR6>bEy~&|KK(DNml&8}5$kZWzYqoE z0QG+5ZIRsH^G3CJsMDflpk~|O@1K#DJNX}6Em3(83RuE z37U~gaa8K(P(ziUahi;HafvLZ3S}x(yXIfbZmNVbcou&TkpsDE<$eu1_GIakc*ryM zEox%$j&gT!YU*)|XcC9q(CfW{|2FSQs_w6`G^}CgXLoqQQWFpmRrZANoh3thI%_H8 zj{*Chul;rd3oM*7s;X&7Q~hQ~ZpW+~5QU9pA0s!Pw7}?43P&xk6?Bk>@_QaB>+URF?f$VK8Y@$2{jW7i z8tgoE(A2@D$-J={CuN}ifV1Lgn@$j0(5JccQP;c8O&s=HLWlgax7t-jyj^%SOwQG~ zl#-e7?X`w>3Yk?e{~vu<)n1B9yxAxRcs&gd(zOwPD)xhVaV?s%A+iW*H`mhlp|{v! zs_Da{D8&)_Asz=t zXf!I6<2CDXRP*%7MQ06O710xK3*FCysh>dbicWavOy}l%l4eJsqvnV9L4hv0r*rIs zAG=bY&hgiKg!4+T#5}1FstT+6{KJlu0>c0!69b`_wO>{8d@cfv`5ZIFc) zV-y}3UOp0YSOG)StX1t)9oXS>isEJBA@Fdj&R_HV`hh=&X)D({POu%ZtN51bGsZUa zcxOj}%(YjFg6SLP{50kGZ>nR(RdQ`1A7A)**pa>AC{{+F7MgBRqba#)@`LLBsbpL1 zf~P8oV0fD2Qj(2H|NQyWa`8&>9bdk7I1a@j+?Bgkm|xBQZ7gqhKfuR6drE-k9oZ_p zII+EiVk1_c%t>=L%RhQtHO9^2d$d1^MH`Rx_n0&IT;c?`cZv=?{%quV^h+i$-tqn` zMn*r2km!CwxfZ_h->+y?ve#q%GhQ+Z+BE-8*&Nb<_r;WK9!>EG@QG(^nrrjH2g-IV zEMdGeY#!EcY&g?*EVG8iqV*1ke0UMrFKQ{irmvaj32qAX(V|}+IJLY`+qY;LS@U+5 z&k!4QFzPiFB(L-dzx1y@{*YF@r)PZFqrwz-yV+XgpG$Swst2)kWLjPTF3h~Qs#`m< z&EP5TqJ(6nY%r10A%>?*a9!TOa^85iC%2iBS-N~fTanpfEI8m^&iN&Z-7ITZ;MRcQ zNIQeY^K^N8o8U#zk&NCIe=h+Z6MoJc5s}s9?NuNpm#$he6NpuXn94DP%6vmHLAo>R zt;eG66s?Os(I44aLw_4zqCCwdDaA!1P2qRbW@2ulpT83-#ttUo@S zeo4Y7ajsO4#nr6tLG9xW6HpkP6-p?NlY}6_ox6SR`4EXz1b|$UfvMI^gBSc~>;I@? z_>D=2SYj=@pkhaer*<8Q5^r)VrkU>Oh`!6?)k$KR(VOYvGYW}szn0Pe_W9F86B@YR z`0reK^G~554II-sIAltw5fyJdfz{KQ8UB zWSro?OQ)^`b?W%#3Aq@%$Goy(R8r9x^**AR+ptgsAO(AO=5}EyivKQwcs76b0(F9z z`@O+p4VP8W^bdhv?oRAJNEt~c-lgdFt3^%$%We7uI2fmVe|}DSg+J%m#s!9W@@~ba zp{Rj`21<39DT5pBuGz0QPd;4l`Q-Lr(;2*vi90mp_(x)|Q7qVYqVF%6MIgB;yJWRb zb~d*;kMq?Nqes;7wqb)%UVA=I4oVtyy;O86DJePQaeojoy8gG?iB)&z#K%R~^5}N< zh+4tWmCK;&vaHcrV^A@eEA=4z`= z8WJAaxvoB19}mslqs9mKp@X1n;X1aPbTeIVSDIYkwl{jc!#&bvL7jBIJl>!2(Y-h6 zL!pdZu#ms2r0VM~o~nbPaB>7;hI!2Kkwgdm(-{+d#Fz{^$VfKpjNh zOuZktD=FA$$Tg}PG&_(Ccw24oOc~{CvRiBXemfGOvzwS+Gz`2?&1FPc8SUWvSuXVt zhPg|kO!D3p6Yif-I>LzQZUY`j85Gu-`R&Iv=~%yzi51;(RIoS6TsZ{S90Yc=pa~Dd^!`MeWeYCE^T) z#McehKA0^159Xi!akf?$FoPmioyzhlMfF4lDU-g zgP@&4(mC34J4I=DJ1*k#U-u}B(H7IjsIxybR%27McYjs3j+`KSLWD2!Wxk@uNbRApJD>!} znG#$r zgX06$K`XpddXa1X@#-e5VR3{^TdFh-H4QhBx)zYG3!B;Aajn>bwhF6Q_Ys&cD zVUxQiN;9?w&@nC3UTltrN+yY^jtn5JBHV{oQ=ZH`uFZX!9;Y@(yiT#_>l_|P+XW1e zIx8#KYWklaoOkBLdO!pwHHBW7o)^Va6`G%Kxf~AG=&dcmdJN?#|1t65I39?(Q-RId z94Yg9U9^(Hques8Ir{Ll_YS4$s$;yt7Srv`;AaCgg;dY2q?Y$s-wpmBpZ`|0;LIJp;7#Oxz*f^|1csNU}o^|4`~(r zNAsiC8Rhp7@53mGd=8R|?)pQ{U$fc{Nd(resl( z`6W9t=KUyAVY5n%!ctcIayTvPyX`#8KL?Y#JNxtMQ|IKv$K&5bOVs_;C@7aQJZO_) zv6z?}TNJk8dr!t#W$_aYCND(ffvj>qDr88)r*N()g+y<_NvUfj%A3;-b;^^GiHaDS zViqI88|k@oy}l*dR3c%=-HGMzj8jVVGSC}1XZN}cd!NQWi)*GbnS8}bWg@DvH&2dN zpDt-tSJB(fPt^8|6N~9T?!}0(G4z zmO34U^9W;+Do5t>n=PHf*n1)|&H-yuwpq7Qt=u?y^&YqfM+D&%HkVh6FXu~+ygeZDu32=p)q^EC4M*PgGbLNaToTV5hVdfHaP$Y z{mqB%Jlgb!1F2dGeWA-24$1(WWr7&DgU$)sb6NaXK9$uRX1&GSQ zd2sCM)ddSbyfvfs-|RKU0=4F$Abb;n{S4<=L)vTX&;5(7iT`VTL_qxAoD`^DXpISY_!zz(k)l%XNf;^|$m7@7MVn3nV-8{jxPkB?^$xectH(<;(~lyj8D>A~r{Lz-6H} zqYCO3cKnY<2Cu-Rttox4$@sqKs!-~&$ie|HeSMSE%%4FNI_})Mp_uA*Gyz?+j63gXqW32T z^V(~as0hqzrO&uiSu)6M3$ufUBC(QDn7J)(6)lQ<6o2glb2auQGgJT4nQAmf;%UC; ztzg^-i?}d{3if_`;$6;urG&C8*GsEHmH9%)isnPxL#?02Iw<)PLp4*W3f^uNPzDrD z;YWDW;NTwniUmHZ7&^8JXjN(g*}3 z6Us-B??F29*AhwXO)N>&6iRebfUv52u&!id<)TD`vi2~A3&m^HH`hsHkX>cgV(mLz zXFdA(3YrwFTKKkae#4q{6-vzMj?n0H+2ECZ(Sqpg93i8b20t&ct)I6~yW7V*%ty^z zI7xlRIb>n-E>ECkH)D6>%@c}Nd$3G0Rz?W4tA~&&bdow0}T^dHhGv^LvGRs zNbXJ{uDF(?&XV@G|Su-6dfK(`6^7-(KC1zWWDgV&zQEmlv(o~a(lCW`^lSkrNj5$e%`_E zqlS}VJrwIXO&!bzs2zZxQ!3C{sMVO0YN_~=1nGIb&Din~t(xiYe_L!Q0aHB8TKO>> zzW0^StkF*Awr+8IF~A%738uJDXt_YVyB%UDts4jXbQ|*s`l_v0wU3HD6rLq3|F5wC zti>-2$vmSD76hqaU+NS{x=a9&o2I1b-`r? z3ed^W7BtgTY4~&{+N$IsH*t;ZHbov}et2Jtu4I+!uYLc%(Rwc=%a$ zopa{Ru*jgmt%}ma0%Oof{D~&1b6NbGA;CJH>l@+JQ10RtJtNb+c z?eB4&j2t0D8E&hEbXC8Jn=SyWt4_-Fzo#-9PKIEZpaC>|x!xz#OuIolG2<{`{)5hF z?zc;Fr)S7;0jLaPq*zjI)x$vbuzx%|sMYd0QE&A2wCrGw(}s#irOupH26sN#IeV!j zo0%K~l`MozMNcww_4s z^2NY7ND=#N7Dy{Ykf*&RfpT}6C$Q(`M$P>!okQ9t03f$l(KE zv?+Qygr$D>>8%J{NsHuIe-+e4AuIkieE@3``IU~!qD4gYJG;q?C2}rEHh4RO9nIyj z)&ZS60hPJK@SiN2npnfewc(8Z2hwi14{xSrJxr&96 zM`Z5rzE>R1X=dp`5e78tcYwe3^OqR3jvHTYY~ZQcBI>S6+SJ0S4G6)$5mzRJiUf2^Rp9t1%Y46FhQ>aM5PJ^-Y?62uDwP1B*Tub0 z<*vpwrF35n-mcT{@iX~nQ7E!D;?j2%0}mk{V8BypCU}~>ZVGj!l~XZ`3${)W?V7{O zI#wt-risW0=>cTn;w9(dH14baj0p)bn3OIiREitr*IQ%cXYooD<`CXT-;EKc^J@Wd z_!9BJ?zM#$v-UP>IMB+S5KI@6V*w?l$P}%Hmt&JoW{EU6tAZo_-_F0h7;#6uToueh zUu}tnUx`R30;2PC0vPOCu|dxjghIYay)UbN4|o9`&v^5!E9C9#VE9*xsH#&qgBB%* zAk~|l^$!3L?emL3zHGY1!2(3u%`0$>>e^y@)GGD?Oct<;--Gy7UzQ;m3=c;2_aHua zSVkpA!_5^F1kR0sacCDn>wEy@P(VhoHu=LKL3fyzT#2b_ae2be3Cj6)`_u}_Dl5%e zS5T57yNjwg8XY!w1(1z&DnbX>R(Jsw@w2~l%Br0I=>^{di+A$L0XX?MRl=S>3mhN~ zQW<~Sq%-U=X0gdA+x89wEegA>Mrlkz7oV<_#IJ64MrQ$ZcK|}%gESYHh>@qy2^Cd= zewGIkEgpAY0H9rSR4{0=@SWw!`MCdMDS4Zx3s^TR&Z^h)Amp<@m!>u}Thq8MGZhNV z5k4f^{f%g(L*-59Skt|q@B{~+8b$zFCKeOJY&k-!d*+s`YVo?iACFZj%uWj_3_Y{|y zS@TusJNbZK+l!ZWP}l>U68inz<37Q0jm4dbk}P4l)0al(H{UrHPr9~A`P!#qHcATG z?emnx^IDGsO2|GTvdM1#y@4T68!oh8MO=Z_Yv6DZdhFjKFhxBm~#Rz?mDUusZehHbkV)Wvk^XO6Uc?PE8R{j`Sodz=pwEe`< z!qfMUrPzJG@!RX##~ez>1H~Efyxe_QimSX z@@n$)uzsf%=WvFWN^m7Yo_Vndx zmZ0gXjB;+r@Ush86j_o?9lQJljD{JWDgRtaJzOod7aX0AGufVZ6Ndy3U`g~I)KOGU z#nqci-kYR*+jh1vLsNbA%)iBcuMXD7L#H07qUP*|e5Az>?=!xly=EfVnAOa`H~RJ5 zGimNEiJMe5{c-|uQBM(jhg~txC}q8hGwE4(i;}&Jjw0W8d2cP={ANY#H`|oPbpR} z6(pq}%bu`|;=X^m{#@w=%HIvNz9s{l{>R_FBzmsw1p*{?$%1&Fzl!J^OVS{F=^ruR zDy?dl9<#lc4|8dZd2Y4J`_rUx3K^Zy{D9B9pkYD{vUn-OYQaWpWhk2xQyP;L9<0er z{a$wPIXAODtP-fBsHFOwF3qdgfPu*;n5Tep+MeS@Mbm<%{$LS~i?g z#=?Q0iX}A*Ro3obYrK`dmSuir+$$;^ys_J<>8f(`4|mK;1Z84p+Jvag^Rj8dMn+Pc zOx02y1^3e(($hFZaauN8=GtD%U$74)wwY?i?Hi=9-I7qGt`9uz>H6U}kZ0xR?u&AG zPuWg4idW}Q77zCGZ5)MK*~>6@ch$Ts-YjDS`l4B)ezKl<z4JawA8Y(B0~3 z(tMfn!U{aEmcg4}#(buz<()!<0@hZY{D1Y`mY#UI7FSDX2a6PQ>JG?a4=A0e4`@cara;x3 z8Qg!axl#V^%jeCer@rBEyqKA-pLr9=m{q;noVJ@&g+SsHmEAKt(hr;d)+hUa$>6ny zXhXH)tv=}zd%l||`#mo^i8~oP1+ZB?=c8EB9MK&Cju?(gvK50Un|F>5zc?RspQoLp zpJN$2|h{_@0PFiOi1h9iIr8>xX*1y_YNop2%m|4Zj#P+%HhW?~|~67SU1S2b0& zRdrSUPBjXTh3$I#bemkoK;;{-H$!7efp+rB4X@Nm5Q$=dGbdIq3P#c zgB^T<%bSHi<@M7o_LcHe#z((Pdsml~ZR3kfmvl{_h(_`dE?Tjjjcf&%D?QKm`T9K_ zCDky6xAIPNoFdosGqhD!f5k9P!}nI*=^BMGIPfQA^3|J}6+xPxX-dmCl&}xXP2wx}yk-=Yy|&6L#E;|+3r-xud+(jf#dQzhJ_z^vHrr_J1&0(_ zwgbrtc<aM|>se6IqsqODcmr|eQX7nX*t_BWcTZ1;mIQ;+C zT8sbAQjT`c8xm^;E5*18CPCRd(ZF?>1edTJDrc&oF{|!d`!B-r5fp>F&G}=#pH=rH z?;{s-&P=?eZ{_2a;lmM^3CoID6XR-I^V}XJ^XviH5^*{zDUtZ2mi&MIE;Z_iplzJa zvW4rC8uWPc{(3aGJ>{R5@Iwg!lw(kGZAe?o`ym(6w@StK0nh>QUjmrvK$g<;Hed?W zOHE$|GlAf~J^XIHHHY<%jz^V`55rCwqIM}KFZrxY?cy8_#MK`z*0xw4a~^Z<=TCEjI|C9Z6@6bx9^ zuXWuW#P6=|LGf}_EzPNZ7;w#F^e|gXbD`DTyY?3maI7gbYoSA`;rhD4haQFf)0m(y zwsp!U`!18ln(8E3o3F=ch{^W>jukKNG4lZ$A9T{uY0Ox z@xw$aeCk7*VnAeWD%M&$R?&26gdVEo1>#sEZ?lt`$vQdgx20Z^Y_{>+*&-Qr-x34jCEXP-@QMCciIyy`0y;x7-Sk;cbU$3oKdadVm{)LB76(q{-AAO}7g8NpvQhQ%s z+9w0GQ!Ej;A=!Jx3y7lM(1F|s*U^+SWg|gr-KKE$$A?Ke2!wtWWw#LfhA2ble)(65 zgRO$d^zLkVqergse(oEJuSpYkqr&CyWq`!PN|EPHMwtRCHDg*5iZ9M+&%1lFvYUzfR7CQ`zS0HFW zAGq;jzb6qCrBy4aPS54E1E6=;nYYacsb4(yH-r^6Ve&AudF%++Kz6~=!|4rFO3{9n zbQL+-l8KS``*Yi#CsnJojUPI4SjPq5*i1^_P>Wy;ir;)s(x9%WaH)22n)o5m^SVL3 z!Y=YlY}9}HezDH65VQMViO)p7bRbsV3GphwIpV_^)BIp9VJ5W7?=0dsGh{;)R6!G@T!tubtdbhSr2DT&aH9L~*@q>`n@zU&PW^YM)qCkBAdy`3CU%Jno@y;wfA z2q{v3;IJu}X(DKKZkp}>={VoU+RoYCGZ|Reh_#BZ48U`8_Eu}{hZBG!t39&6Sk~HD zoW+bJ%RTu3xO6#Yfd*27g_=#l;O3cc-bS5;=HlV=iO1p}@V!cZaV zIv#2MeT?zWDd>kY#vK`?!PceS_IZSj;Ex)5gp*|JYsBf;69NtHE~-7oDeZn2>SnQF zl(e&gx5!3+C$mjHabPJ2(8`%_)sNk8S@$i zy%V4BJDNCZnPzsI7r)yQxhfCE9~fJtq0PdakcPE~8RE0RwQ!-4j*ot{5wObdI;fxm zO*2qG#gg$Sb08`r8&2?ed=(|u=>1-MG?$%4vuhX@;k(@A+8G;$iVZKJY9KPpdw94n zfs=G%vr_BfsU(61&Ib{S*CcVyUIua89AaXv#Rv}?fc>O_okXId*R(rwiu3xuVDR){ z(!u?<`(uWXJ5Ux?%f}2ImYM^06B16H{RnjX>+~d_glbN@GIm7-Rr80rs$4Y7^ho1^ ziz>Ur(?LB^|DP%X^MEx3|JRyCSngGVF>ij2?(WZ91v$exil$-|t}J2iKW*kL9_-~U zej`tN*Svcq73jX+xhYF9g-pJ@k-HpJPi~{wR7PbOjAys#NBVOHxyRJ_wNQ4XG8)utp%{Z{NUS@R6rxP%UJ_i`5oP#DKF!L z&X3Mbci!LgXd?$%lHulH`%Q{j>3bj&}y+A{1F$uSuc{J9)#_Ye#?zp)hbyFjo#TxMV z;GimH^vd3lRojvXlEa?#23dgAw-e0r$87N2gkSaIWM=mT2ZrtUpoXo(=crVsW88s} zDR}7gf?UT0O#1V3)go6;w0N)AUt-Va%Sj>0C~5iQ)(@FM6dTUm+h6zz4EMY=($QVk zuI#UODiUZ&OFhItjNKA<#{^wKa(ElGi?{~9SY%oajn!y}0Y??zZs9tKFv7efNDlm{ zPbeEaACDKW{Bf&BbjYel&RVs0e_H;FjQ2`YAQwq`9f#HGVC_F_$Q}(e8Cbrkt<_28H2r78Tt}d*?!Jg5Ye8QWe5i3*dv@RPQNcvGIk03O#lH`Y7U^cy zou|2f9F)KCOmix=9w@S2D(4fe8#jnOTI}-T4f;oUQJ1lysQBqTmZKuTBE-LM!yLv10X2AdscNR5ao7L~m#GRkj*e^1II@YbqM!Qv0_ zA6n~&*GoycA$N1V|+$wrmRTP>})FfI9? zno%gyzr&NaX`E`;yEVVGrcW$`){3!Ouoqz&d-B>%Y-$>cPjn$Jo6Rw~H~(X+v;LYO z)}fGc&?f#1!p3LdoF~-4iHl>2PI-*$>urcbV}NIJ%Epfc@YQvFD6hw&#w0OpuYGc_ zbXeTBZHQxfXV4c;(1W%P-#PeC6{`c~N+xi#=vQBLHYyu9SK=i1=C+=+Jq*7+$SxP< zG$zY&PXh8jjuUI*VHL&Ih}wK!eOOqQ!#i|XxV09a-MwBraQF=?-Q@3^A>uoK_;vs) zbIvAl+MXM+@^|!<;mI=3CXuGN737$Lg!<h#R}hq_vg_wE=hcTt2j z*F=)JCC2D@<8^}1TYw-(HY*n6tbosT#HAo!S0Vi6f(eB}JK29-?^ zFgI4?JrY&zlS>!M0<#H< zZOofp0*l}gNDYc&Rf2^)7J+SCKjuH{uL!}c>dX|}aJVh?PFC$ZsmXf~Ej(F{ei-*y z6B}5yMskL>d|bl>V6i`djURF^2LIHE5nZ;!pP1ff!T(v{(kOJ}dOz9^|)7#C`H2xHF>)<+n3+`T_|A5+Yny4`tLSjJxybcVvk zAN6T;gL)$wc4c!rq6mnj46!QgMy^)vvCKma!JqxR&*FiS1NB{iwPl>MR{tLWoxkKZ zF;rzv04k|ED$gfB{kLrAw=zbAf4$mc9rAi>2pX|HLXF;p#hR`ne()x_&rgM-bT6+^ zrTVt~VJTDfLp?hY2d|sV?(;ZI5H&Wb)p@@7Z0t|g50$i2q$q+;-b554HHt1?e!`Cy z<90S{?A&hWI@7Lf9its;jgeGqgN64({8t(Xx}B((M`bveS00VLy6}rgX<~Ywk;B_9 zz)jzeFJ;ed>dOxDq#pfJob+>9+FbXOA0T9^c&+Q0onpq1oP+-U8}4d<_t-g?ErCcR zXqUaz?e?ES3RjbD+Zi@M*pnip0R`kZ95ICcH9hFBn!YrY=V`Kik7<_8_#^yU44P9r znOs)FJaLB7Xrx?wJ`VpLiMn6D5s~k7sR7^)Zf_tL+Z+3^;rwUKJNHnby7!wiyHjWW zuGn?usc&2Sa>keku-v4th+k443>YN6iVIph7ic(@a=pn!$tppBUQI!^_Sf6b7CpKK z;B?E@_cp#R6UD(Otwr}&jAR$rSWLv%_hoio{{j3gi^n@Mic>_nv13Lgl0}3b6}wRb zdHTg-EsAIXm!swOBlws3rjq%p0hgAQBw{;2Z>!huWD(T;eo0U# ztZHo0r?fWNT!ik5CJ~s`OJF#4Ell@qrV3xW;3!ePaqOH}JWYDt^b?v&&&ezBAX>=w zpVh0=5uPR||M2an`a)r@o?gj`QtJEP@XOL@q164n+b;^LA);Jw^Ktxp8#&*YzInKg zU9Q5+>peLh`y+I$z({_Cww%{M%?xG$y!b76;k+cnB&>w<#y@X_=am?A<~7713ca`c{|vQHZ!Re1C=-_<4&Y4WNYH<_9SZFAu=asX8S6L8 zbB8;SOT)_<2Hz@-r@9`d4lL?tw~z32O9(5v!&5MF2644$0#%IsC<;pXF9~xL64I)> z29i{8$V7X)%0|$CjS_%;t!MnPV^w&TI~bs)o3TC)2tjq1Sxf%NgJc8wx_}XX=w)~u zO*nBtY)3t6ugd4!GlH8ut(d!@%Q-bk8<9=!_zUaSmjWvs=)b@w^F#xE#GOthdatL6 zSe44E!lB5JJ0G3GD$Fr$q*!ot0Mu|lHOYSw0=}X~X}qj$d`KL=GjE7F8Iub*64mW{im7M50z*lVS9p&JaPFDCSKukO82lL4 zR!dTjt$SE^U^#}F^1+XK$aD1<7A83sy^S^;s8_6<%hgQMfPala*pZMlaw=Mj2;{8V z+oLiWE{ylW3yfJ#M6>wIwb8FKX8;(!=qilZe!g|i2<0w*VaKB%9y78Id>)WoK+^zpmPt^iFgP)uYn&r=hS^?5i?Mbx8PO!;8ZAS$SUyvzu6I1f4x(Cnxs z54X?O3)jBn$K$F%!=s-VKnYn9HlQLsCCC$c1D~^ngaIuq6~cqx`kws{=H5CgsxOWg zMUjvkMWmz*K#)#JNtKis8b(CAV;E{cVJImTY3Uxidxl238-|h`Qo7&ackf&8{c+z~ zZ@v4^`-4A*bI#0J`|SPQdw=SPFfl|rFsPdb)U~)0Bf3m}-4(dkDA#$$WH`xa_b2|t z&`;29N3MVSZcv8q3vZ{#1O34LKGk4bbwUGn7od#tG3q1ES;n;|)$wvbO9_KMg~y~O zvPqG-Y}Rg1!YXCt&V@Yr*;vB_r^T+{UM=ahcnq!zB}P2y$aJ#feu}7JB&<(YS(+3v z_K;49u+m>^*L~KF@Km^W$3*jq#V5P0hzI&<$^*PI9BKXC3IRb8Wa)yld+9R4gTl!U z`P-=tas1)`QsEm{4zK8BvV!*(d`YZQjVQA^v2l|9L78;H4W0?vM$CNXbDH7?{balz zK`f&g!eI)~Zuh|T#W!-l_dN-jWt4aZ*Y(own7)~+J0pZO$31(1A5K5@Q9C!UQHBuL zd;br<@W;Om_nFH^1{cWV324Zf#dSFh|8fct-SM;dLU7Z2EvT=`@eA-|@6#!C{EL9A zt_yWMfqwb^xWQ~x{?oDuNf=jyeSX0^*OD?wVbG&Ms(9OVnhgF04m}orE<@jEUZf{P zo$_uFF9b+pnmA~}=5<436VB2d(~#wiA|?d_VQvj>D(uiklg9vqA-21h|45fVHCIJu zQs2vJgQ5A0iTHyRjEU#APA@Mg#)ea1S|6DCM`F)@a|)VuicI@;P|>*uDlDEojiQJj zWOXb4u)KwpCj!|Zj;o3jvKGZF)OuQjnleb~XUirHd)O(d zdS&?>vUr_wm4SP2-@mE#c=7eyxs07nx&vZIpUo8duqO-%k-WbgH$2o;U|f3wia$4d^Vek44(q$=7TmZ zoX>5|2Nfjuis(bgwFC2Be9l@GsnN;h9}Qgz`Q(eG6;K)kG!q^=d;dQ2n9`C)uU+y# zCvFls>Q@~t&{Xq(g2^v;Q~ZD*7Ji=KfRDSS^1-_j*xdb~TBs z%kus;(zQ1;ErJ<41!QS4Q%`WABx)dNXn(UKV^T}3e6}xD{LhEx{8K#4FSYRBo+-Ij zN6s+kGQN+qB4|#MN(WWpIhZ0C@4D~tF!&o=Tb`(r$b)3Oe}Xo--&S!}icuDSn{6uM z<9?>GN#U0z-VjT|6Bb7Nv(}vE9&b%Jw`uV1kos!oTSe$Eselqr62=d?i+NS3?Irk~ zE*entx1Y+A>xl0{?pMXAR@?g~H+#wUn7O?;9LiRbx6gag>U(@)R5T-vUASbt$xPx( zgv`gGJI5jR#N@o)myDJXv6JOdJ9K6Zy=;-evNcZMO4J7Fj#(i2pgR0Hsp|z=K^az* zajXL11i9-Vle_Ceip}de@Hb?5DOV^dIT>t(AY!;~x$w7qOaP0WS{1HJu^I2Raug@{dBYHmx!x(=IG#^n$SwXsF=j6{y zuxtO$^~*O#Iqqh6H_+>?So%SEyn-e|7P*q~U}NWE)dU1?+V2VKPuq0Ut?sc&eUR9u z$3jG}#H9%>>FLVIVVtHzx}* z)zc9EV97VwRIxZ4kvh4PvR=CILgpiLu!xTK8=e0`ri8f>rWF21Ff+LkaSQhV-N1rj z(eiLS@eZdDW=LWZD>{-rGluaZ^-}qzlYvBf2p zhiaBAwCf#k%sfM+DcJsy!!2$1_#UwN_SWF~4_GwtN%E=&Z>ffDNMGU$v2>E~E#WWv z=?L?_lxq})cfFm66x5pjPW^PMLkaQj9FUul(`1TZMt||#hby5PPc;HS4t&jtEBiYV zSUP`z2f!q4P@IJ?_H1^Pyl8X!@GHeLP}e>{pp{OZWQHVoJl;HpmejgD0Q_hh3 zgt9E41(;tchK>~1FH^|64=_kpY+#qm3mG_`8Fx(ls#j^~w9qOz@kfon{$ix;U~$O< zhd*Lukogm&_;ERZ)LX?T=wo1Ib8C;)qlHo9qgQ2-k{T&ZwOh zi%P9Hic~3keP*qc%weq4;=|s!M?)S1B&8{Fj`M3970cc@%-iEGBz>~(Qy}VNjjwc_ zk1r|Xmv-mh)VApgibQ9OadToLK0M9A#U;N$=^EmD_1hiC0QgnguE#yJUgPOtlfV2 zk7BLs=M2;e<9t->nk&;6k@;^^%ZI1qI@IHw37L?fWb1px4xybvV*B(GzbHHFU{;Yn z$M^2$K!l`9w52Njg!fIoyx)RS)=Dv`NE-veYUy@HNKjB5I;4aXpzJu9cv&D-dLEgPk%}?X@vE zJ|_ISL=9?*cIlkDSep4b(N~*J*IL0g-^xL7IPxwABr0`Iv`fYe_8#d=1TD2zp1yl2 zQ$UDrG4P(6|NRUYXJq%8e=0NKzmFhKX<3#%eZbgwILiR>>6mCPj0{ z)!lth9ba@XahA*yg`!yJ#7hTP&UCl zukPJX!EHVa?Ecxi_PAxk6j`}NSZHtXT2m@4Wcvfl^Ij$^0`#(9Fu(l;a{i?0Lg?p%Z`3< zd)}8zV2OaOB}{qRy&G)iL%{dx{5&QJS-NSqUx>v3Oe=wq0yV{?tfvJFa<4+@4t)s6 zAFhB?`BP{#0MmO(G9id)ev7C&E>ndsdN7}{a7Z@1B<`|>#68Yu+UQ7V0a1))$0@P8{k;QuR+{lFCK@Tw+%3 z{CPl6Q)&deWh%~+|% z46EVOFkRI9mOA*W>!eAX+YraohvYG{`59_=yk`)D4`qe!-iAD)e&Ncy-DJwE?b$t& z0V(qS#|RMz$%(qN8Mj?azK1cDg^OfY(+oD9N1_M-D@ zlY+!5AU}W;y}C7a&1*1=mzc%hI(jR+=Y1~?uK}kbWe}bw7IGf`#88{b_q-Ke?Ser& zw;7{h%MSq`D)~0l_j(o8Yny9zu+Oa9SRMr?0V2A%;hqj4KKHKx;WuJqBxxbj!{A&U z;e0YIEwK|#AvT&(F&<52-VLzV>o0HBfDYv#pR0w2%Y(?svBY?Or_3i)rHXh98*}`_ zJn0&@A(>x!8rhlUYM^h_W4sSek;%({`oRGIVDq0xc$ehq@d+bi7oeT6BQo&(2jp~B zvLPM=22;BzkYCuOBaL9LvPQb!T+A+}z{U8p;aB$644_c$?HJSg1CXLC<=D>810C`SAD28X4<~s@ zNhNDMdlM+-peAVs!!3i)i2ll^WQ>m z&{uo4z?uo7W7H7@lsabbnJ6Gi)T{=&Zk{CIV zmQ&l(vuJbUVbljAT=(E0nF8YNp{``nFKv{;hHjrS>7B1uyTC_*-%~%mNHct0s(BI= zG?oC8*-K#E5+8=DGPg}GWhAm5UOvlDh!9H^=}`>>h*dWVnJ1>#zlK)N4Hp+AYx6u& zz@sDXQi^b*Xqgvp_(&{XbT;l0yXT-Qn$gAaM862QKhUGhb{b}%mO(%+AhRBxYn=3a zx)`7}5It*QFoWy*0q%`|WtRe1Mw;sBcMoRIAP6C5*3+dNhTZUkS<(50D$v-kbCgN7y;NLcEX&7>u91%X=GM_igGAQqo?g#%|28 z+LY_JkU}g(6g9^HV6K_~q5Y`EEqI|R-Ss^g_NbpI&7zFt9(fP%00<2TWy;qUJxsOe z#9OKY6>JD%ggou368jcs%AHD!#9SOT^Sk(j`*+6ds8sVEc=hn7kh)}x6~?dL0EOxU zA${8^O#O#aD9QJ>?+g#=2HQ)E%C%z6yK0!>^ES+YEkn*%3I$h`@v6Au6c~7hAPo9+ z!H7n^0MtISRd3VD#_b^dXnAH5V5=r>d&s~V$uJ9u;uE3aAq+p8xETMtbs5%nt0BI! zvUjwB(Oq%P@JtiS_)*eVKZbinLbj&V*+YjvU+(!-fsXrwqkH30PgDtk>>X$?S=1hH z-fS(WTyuQVSvpXFA9MTiuQdw*dT{aTZBD21mLvenjV}OFQzK%g>hK{lHNFRYVy-rv z2P}v<+>r0v2po?5a3E#{~2fs{PqhGZVjW>MSePp1zhz&W@ikk zp;Y7Ble1BGs}Pern*pl4$@Fa-J!%7!Ark!s>c8%!sT7nKpG~V9TyiqY9b0I~rP@yj zbF27VwMrZvc8Bt9JF8e7evxW){qv+eG|+M%Ddc*OX;_}-zGsnS?MrO)iI7c_iM!M$TwL%8NN~LC&_&yGu>ymEpO-KwU_9Gt8;a>| z*78elwmO_H=#%$G9}iGtJ!cdRYx;-Ek4xp80Ie1KPLo+=7k%ZI?H|E?{Lc_IF3v~eb^u z@a)@@V7J>wc#++oPw2#Q`%Fa>ckyAt$k) zkoO_g3WR5l0fUPVp4xufZ>@e$Xp-vkFeBl|sK_0Lmes0R_xF1pe9O;fW5im&fW#5B zosrbJ3?>S;y5)}f2b{vmYpRj=T@c*335Br!w}_Ltl!y5gvsnC-D8RRSq%ASv&+}4z z;P0Q0G}GvJ5}iO8MW1m?IIkq)(<0?bpURj`+y{d!W<3iHGUKaNJ+2emY*vYJ6%uNRD%lS$chIzHIbWf)M+b z%K;0iu<*c^oBeN$nd-KI!>a0&$CtR(X)L51g(a8qxO9C>8yPCjuASD^rlVh47Hnjb z#A>=ev$kp1M|kOwCjtTieA)e(9ARYz!G6-$}+--a#sq7&{=qI48AxO_b3C_PY?@1 zTOvK2Pb#L$ozyF5exxKX7_-)obMgR6Q4{lU#wDL*@pMQV`s4mn>0E;}&UoJOInTmU zbW=u;XJU2n6M+GYqz&q3FNWsB_bc&KyGf}!c*_>1WDsxV4&ASlVn*J0p_-%1DzHm$ z{qY*F8~w9$$~Xgyleos?Np{^HqH_Pv0hjH+HbS*yw5q~jPsA5x{RAN~ zveb7tWVXYQk1Svz-L)vANYRNuC#E$epq%;g#5C{-j>$@heQ1##w(ii%CQ7|`JuwzD zE>Y&2R(M&~YeCKhCT!_ChBKg@_)am%V+M?Az;w}91KOD z)m$M}=S9%mlV*9)4K>|>8MlCq{>lRtHM&XgwyBA>Mi!Hyt?cP>Z6jsf2eoa8!}zPX zEg$VSp29d2=yUI)C-V%)&rOdm-xpm7|KQd5!t|UF`xrrPPwx_58Ah*b+qz`gwj+eM zI8xnzFP3qLd%nSA|I2HXgT38r3YQ#D(QNPhq{PfC$880nnKSt+8t|A8qb5+0eq!??v#Om&*rf?pECd@RG zslokoey!KWn*cj9(iYhnowyVGCxRTSs*dAazn74hm(K`lm`1(aqcjAJbC59a1@fcw zyv?!G6hEF}5o@q?oca86M4|Y6-DMped4rb|+G{u3EwRB&E2MsZqFuA^Sv~z`H>uId zPnry)G1?*<8XW0Kq(4uWP*|F8d9gU*+GwuLArPOsIv7Doc`XS68)YPvx-b5T(T9dA z5Xs;LGI0FsE7Pihn=op=pnOR#P34&L0avoo;ghdNKdcxkkDP-3#pB!dj5QO1L-zjMS~)VCd!U8)+KAoU2J@`MK> zfwER~)_jJaRwpdjkzLxJUXWQLU1QoOE{r=OwX=bzXXjs%{S+^4es9?O099Dv^0TgX zP^!vHrR4mmLs_btdDkwn9%1I5ccFY0dFQsmLR!y`2qyZFXA~-E<9MNU^Ew4gy|zmD zL7luNkX~;ILOv94aLKB5u`ebj3XiplFK9Zv9%KsJEqHlT-X~U+Sky>&F3zeSqp<7W4v_f4quywoEzF2R92m zzMbc^T`VZ@-qn;!7B?5?{fbkqG?1*)Op=l+%#a+XP;AaWLjJVtM&A=%{lPU4%#sfg zI5>XaneN@T2{X=Z@wjt4)e!z)6X)IA@d?5i?Dj8lP>?AnYxx>*SqcV@V^f)GZGg`{ zfIEx^xYXOJOP#*A0ws}P2RbfT%^m~3xkRxp_HXJ|ra28FU}NsgKOgwXaU*?K0)Dm^ z*;9BX&RSk5!rT8vG6?A=Xo9jkKz=yZ+(o8z5xCOSdaAZ>`9-oe2uu`Ry(k8#aBv7) z0(GqtNaNF{Ld-SPv*U-w^8JzyE)pm;LzU-ef5kFQJu+)c{6*by?+y+rHR9)BR?@}~ z&@xvQ<2c7$82caB)krQL&7hTdl!OMI3L~8#5apz-`l;akHx}TEa1hWM|GR$z`Ao{+ ztR21qYWD|c8|I(h7sZdGW6P~neameX8Tm6|su?u8ab}7K!^%FM4eznj@#G9RgO1YF z4l8ckmI`ZGaUpmc(Y7fDpJG(Fc-r+sxaD_y??qt>qh?f5I5=N!H0POhK@(FXs^NmN zPj#hUO!U(Hu;;VYMJZeH(kpvg04D(ry4+2)Bq~qRl>HcCO#Sc0?Xxfjk`ZT0yA?}b z;-_Uk47@$7R;j{8j9;8PYZ_$xOCn`VvA18G?|4f8}Hq*UOB*3ry zzwTrIt5a>By{L+Mj~x%-hEV8ykN>RbW%gQP0)t9vjS1E-6>4Cge?k0Enz!D=7&w64)*%>%I&*IP~IxKpR@=L4wHIT zft)YRwZ5L9ImQ(Gt?kK5llP8v*Yhdp@ESvPw{K@?^aWqdR`!Yu(&*&;1%DsZ4z`%Dd<&XG0<1Pvn{I~F9SyY%mSziIAs++M%as760I z{1+W;N^r!>dg9>5O2Yl`yrtl?%f=~}rZq>#`DF584V%mP5$DcuSnRk|gzF9|!{fHTMC5ubl?Pw->m&gybB65&Q>NOyCe5c)1s48L_NQY(VQJs z@7A+l$)ijKNn7$O30vMB8nF=m*H+mf_Zg*X?C~E-`odNG98Ws3%)1KqSBDbUdSe-9 zg{UI8UB_hC&jsV9=K@mDz9Zi5H{V{D*kx@*C+lXsf9!iPmQ#dP4Nu1$T=zvqv*=oq zY;L95tzs&dvvu_S0KZa!hx=MszTp_tdG-4lXUk4nmzBQ^_4N75#)`0V*n#swS1We4 zYlf;X+pJtge}XF-W2&WB>J_QRIOo{2QF$H0*m}M?b5*}mrA}GE-WqiwTuY=jfSqKh z*u5C4*Xd&=FRFze^$d`qB&D8p&7vF!!AAO>a~yByu$6Y#B?%T2CnzxtZ0mlD3BO%0 zlknqpMylUd{#$d2IlaOI7y&ZIL4qx%Slnqbx^|l@Tf>{-w zmvAFpFP)?74KF7!G(Bx!HsD%EP6vVya_%AXcd~t@BhIR&yth02i1?%6%W6rsW_3-M zOIU)Pt9g`XulL?K-i&3DAh|6yQd&!Jqt6lSp7&sU8GX9>btVFMtx;{~s6+wuwDoOs zsp+=N0i*AE$K_%9#cCT$MJO{O?xx~mFBal$3&b*S#3Y*g%cLF5`(6xP0+5u@Nl^-q zgQ2+Xi3yUl;nhKBiUj|_4n*j!TOt31>aNS&peReDi^d%Gg6eak7!`TE@8zmXHEF`% z^obO)3JK~C>xCPj^6)ztOFo>$z~($hk2W}6dL@gR&NnycR!=dc_DMJA7uBYc15$yDiXU+?=kgCM!{nsPf$h+cL*>5-4Ng-OgPjm(Km}5D9@_w@b`(av6!( z>B@31Ws4hHDUCnT$K51CKIrQmxDGQZ2PYwZPlj+eU2$y`km<;Y+{9Md@iaD#40z6+ zXYq4z`kuAfpUhfR{TRF0EM@3&S|yi)Z~CPc4FNNlLT>?aVb4v@9b_4o`E- zFgq6Ef*ZCF6W;c~UPRi}6DZWGmQ=|?-rE=Il~?wUoy#{Pzv#WMPC~B!uuHK61ia`3 z2HhM@F!shtzo2B8c=e7G5_i;4r&XEDq_gf3nRlVnlO)xU(Si;7b?6#ieC*(kZejQT z(44k2%~!96G4&GWbism!ouJaW%e&BeYmZ%#{uI7xiR^C%tj}}EY~7^tmkN5Q7egB9 zIFutE7JmcwP7t31w!L^N9o-aFp2Jgal&58Np%M>aWkdmDl4x$8R_AH}?Y+xVbL!7? zSbliC+ub!s$)KZlN19Nel}U?G^@*V^@FEXj)fcE5qrR}=WMq+>QeBwL*Z{n7!vm~^ zz*L;hd*4hJ>Rj)YTYTlM;Z~XdPF&u|M3VGzf9|bAZ3!)Zu38q;=<>C_As&FY$Nd#KoY+M1l5kGYS?^`h#qPcVm0^3{$)FASj;@g%$b8D$fZzNXeP?kB zYSG?t53@be%wDY;WoA7oh&0AAQq356Z&ZK10?5Ia#*cDReE-(*JyVGpI?g55>alL9 zMRxHSXvaiSNHxN`z<&3b2iyF4SS7my41qP*2uD_amrC$IiHUY)8qCD2c%d?Rg0#A( z!}7W=jr#FRE&=`H9#+^abxM)W!AMQ!c;0urFs23{b6=0xzXATtRQc7yH>Z6TT=x{n z*pPgLW8eZ_4k)*EnB#Epoz>aJZ9y`bfZsYER-fKy7X5`eBsuqNO=*>CKq^04``=4r z7lYDa1!&kY|xkB16w1QS@H@`ihD0D+@IOpoRof9t&`CKKRh2?6Zj#&oA$*fjA^#4>{8@%Cy1PVR>R&g)#eZw!;eWC z%hC<2=g>D+?M$Pe>4c5lHqQ{GDm=(12>L!4BYK4)MZo(_!1mZZCSjWO=~BgVIl*PE z8!0bKuaDV(Lx_sUo@WInygGn?^nxa(`Fix79-7-}L(kg2`2B8bKZn%Zyd{#ctDeZZ zK0h8Ca|^eAToN+|%@z30Lf-#!w&V;s4k)WbM2g=Uy&pF+kR z{)X*0n#(d^YRAK)H=GFq0wyR|9kwbMRJ_$=6FMRc{meln@zLMHZX3uMZqMXi03!)hPpsHmj zraf1$QmubfzFyw<7j@jaSp9xIWqS5T$>`Q zY%n-#-X4kW&~98#NJ(C2*W;{q#yqN)^nHW9I>c-yk1OjG+c;Mi3LTxIFOQX|FrI2g zkJFp9)~#NRFe3D|O*^f=I<@BKbjzxZp(SyBL7UVz9VO#=p{HQ@rdMY7%#1xf-FCT* z{&FMcM62AaaZ|Tb$F%=ii5u8!r1?AA#?xrU=`EHHGvt}~pGp@Q2v8$NuB3GbpZk5V zxVFw5D{=pt!L-+ZDws4^-=09rWJI2WbIQkKW*nJMH%OPGkx~3|3uDvpLEOtRv7Y1I z`gXg(Hl)YWXcHg&<*YMU>+9LBL7ue|zkK7#xR8}X4lUY2(t9Kzl|iSb&_KL3V2;uE z+9beE{aOB0C=q1W31%hhX6s4>l6K3BhfU|qqbzG_0@&z z9#+@dt^u_B#CB*zhI#8|`NYw;Yq8bS>Wkgu8&QQF=NMl9S(j;h`nU8qc^Tc7ucSdj zAxwv0#HP^Lsfi|$43u*7SU>F_E>#o9B)ho%KpFILk-hxQ@^z6kUUHq%*q{8TukLG^ zEY_`^2NrbBL-U$Q=eUem23bERx|X8Y3s4dqMM`7-$k5^M&~*uc{b#OoNJR3$<(58* z+=ZpC=hdiV5Y}9|Gj7u1pvU~TMjxdbXC*j%m_U>GOjx{9!q2xX;i9IJD)&9en`4B zMxHeI@gI8J-2YOg*C`9mJvK1SPGcpUMo&=$)-M;>X@GN60d8ykbf3`c(v_rwtnc|4 z3VGf~et++YLA=hjq$~Nc?RtHMWryyGhessty~Q4~7N-G@mUf|gN?w^YYY7@40QP9X zzu)46DjM3zIImy$m;4NXMmYHh1!c7+h&LgC6KFsp%5)BmaRGpnRC&C`US6|t9Dc8@ zH~hZCANajy-{{~Wm0aHxyTV8pfA9Oxx$ti-AFz-y-+m1%AuHKSUd^|^63Q+~VDQvy z@L2Wdb2ecik!;_u8}s~qzdo}jq_w{;(ilbm_x1Ci3ZIL09V+ikzp}3*1<)6RBS^|E zSn)qmn?!V3%kPExwGq6XRwb{1##FT6AJs5HT(7mNZ&wrZkxxd&V&`vB?8VS2v{{~> zfSuOl+NuhzJ&=sIJuA}K^Lx4))0-sn%Dwo{hp514ZQbkMui@rE+7!#{UO2aGIPC{& zZ@XL<0^#_r?_zNXm)%VhAg$rgM>d~}!@#cKP-MmL>)D}zR)bkS(j)n?B)f=heG2i% zIpnsEGcj8Jrs|9=k?Rf&vp^Er4YmjA5!HL$r^G({q(R74sTP}KOnm)yg(_=zGDD12 zUk+EeMr7c(SjAL>%XUdEfb-G}aid%25HSM`7C`)+DF)>{xMe7YZXsO?bR89u=KLl3sg6CbFx=^KDHANz?WpEm*wk=gjv zd+N^+%h(F{#pf4fFFj zKGvJyPc?;GUJzYeIQ;>@#9|1@czPb>p~^pqx33iHlpUB>d5*V*2xozf79#-hlS-u7bp94tVXc733#1% zzq`vpZ}P`h-D&<-da5fdt;~#N{-+ve?;tUT$a(P%nHPp2GVVUu*k#@owyz70?aMia z0<<9kogq{mIV92O>+f-XOhfhI$8~lZgOq7x6!OP?*^9|-Ny^fC@rOwttm>i8sc`BK z-zmLS7f;$VIEskXC=YQjY8#wPAbS!WBwKcp@ZFy?s{r7iXmQf7Z8-{+Iq3C40bcg9 z=CX+fn-e8p4W)q%YG22b3Sy^S#OJYwke!QExK4D|zyz@|z0HyJEzdQF+1`2nfmIIU zWd#?tuim~e6mJ*{82Y<-)w1If`B&tmKAR%{lxG0@xo!R+&F*@kZ^Ya(C3#0|9|h;W z@rtEfiF4S38TxkEpspGZQ92Cp*y`ehWl3&$$92>Y5I<6D`ZgCBRVJi~}?E>{gXcN`;Zy)>@(Bte5fpNbxpIGvzi>(unB-QoM|`BQ|p2zM$8(^kkK} zMJlZQ_6!V2H)4%YUTz}*PhHZ21GfZO(>Qp5{g4%9);vR5bxd1HQA-RX+-P*7R%D(5 zy9Nl^qG7f;zdFb?dfMeB9FagN^yTw*%gdrou;oj_BL+-OGVL3|ie-Wmk2EGN+12et zj1I;3%K_}Er^O~1&xDcgVag0Luevx?Gm075n~`jf^njg;3k^Q*{GM53$1gMh$Fo0I zVM>rNvE%_g)8jL(D0~CMZ#79FjVZ56FWgwmA5e;p5^jYxNOVNnrx0FGvEij5m3J+Y zyuJVJ;PDq!#J=oSbn9%A8tRf#$lVQMcZcUR4rtD>8#{V1Zw%f48WGLjI=CCl{suhK z9HM<(Utk;(@^yjSD5Z|8@q{QV>H%KbvEbo=bwC1dRl`iV$RMQw927xJhwzg1`+yz# zoJeo8S~X2GcF%-^yyf}K>G{pmvje8zni)mvf^I=>1;LYMkEaDaN0aFxB4t_HbLl_) zK2T;e9Nb)q2h30%^FjmUiCSQGP4MFdXUBM;Cv)-QEfyUSb5QI4qI*pk)SwHH%tsMF z>RvV?dwqKylI9HT06WjL?O zDK1c>XTIKZP#dvkSjkO<3myz$Hm&-Rt9W+qk8PgFuRjZlv?q|tcqg6v=3m6u4jh0z zW%09|6D_(P(4f==CXEx@#ZmR019wYVMam>ei>o_UaG2H)d{+H-H@--_Hy+DwCjNuk zKNdL_$%+gnZsG&@%Y46*HjbFrICSJj$XiaVH>LmEt*P6fQl>G%tpWyh{Dd$*hlvBr zK}e&`oCRcN{yhl6$wCNyjp#^k*q69+&MQ5vTS5}c1pKHAcxqHmNsHM0r-_@*lV=0k zR5k=7(8Ts1oX8Kxmh+qMlPm&-Bcj94^)cjE^dlicSV8<+|Ie%CWXzN^96yag6N;Sr za;`V`b%jD+^q#Cj%D|5#4&3MoYGIF-87?r6F=#V#4B0adDO^7wqC$wC)!N{8iV0!7 zyrgsW0*M%RUl(fbQWQ0hyGPe?f;l-{#W$;tVT}q6s3D&SqR*UI-^RLU19-@8q~C{F zRPMuZk(mP`s4{^5^ld7P!JZwKlTaC+8^3(+g6m7IH%|)IKIgJw$&Q(5`?>HuX;$Fm z+E`vck*VN5yyJY9C2+%fJWGU0%P(SO+XH|O4>+p+3a>U;Wtd`pk{rY%2DW97SA8v_Nm4cVk@fCYM<8>&> z_!Ljc1_=i%W&zWR14C zMMNK>bQ=`3je?Qy1foiNlM!sMD%3EaX_EHyowQbv`v&0KCzmo->z_DRCK^9v_A((X z-89`a=VWNWQ$@kBLXXd-Am6j4F9ICdByX@1)E{POQhjw?E3u=?@k)?}W!NGg8e~ic z^tLpyep=rGcEU!}BGcCunHFbRILBg}DX%hYHg*gr*HF84&le|m1e>?-lX4|JZ(yWe z8~OVvJkq*h@x3oNI3b?!&-}B!Ol!6LeCHCASiLi=B+Ien=yWc1J8lfw%>KQG1-_Jmt&N?mq@r%i#}@ zKPK?%&tGWBuA|qrVV{P!WOoZRETRaiIg17CjLO#pOjSs$XpMJ8SqlxKG-L=?xhDf< zcQXRz%5N0mMkFY#OIAG+(*cpwh_216s2r6&ptAVmU2bu!-M$4m?nwRu`91uU-Fg zS2UZ5SLM$iqWCWw(;TgZ5LRM+`&@^I5yuc#7KfQD@QXYJwa#{|-)I*7^y5Zt=3-t+ zaFuZ|1KbYkcymPC7#WdNB@fP%G}!j1g@%(_&rSQ%3quD2>fe7c7IV^_FA1-akzu7d z*g9buMm%UNrrl7SJ07C%)uY=Iwfb<9#-i&n2SfUdaqdFZH=TngsySU)B}L5>-au_n zHrH60g)X6Y)8Rw=ljSDrdk=}i69ZJYhAdJ`x2m}s?J*4{^GV;`=OyUr0&*j6#E!4D zz-OH&G7~vmis8+uKfBqgMThYF(5F08$}{O0(upM76VAi*QTo&e&_K0b*HCkt z0)NMkCXXTbz|I$9DD6C9PLH_yH~c^8=ARj&aaM3I0f3X%>ooAZgQM>O+BE*@Ulx^M z26~(gTa)y=CAXFxujE41GD&@U;c4LDy;f!Z$s9JqDv8uC?)kRyk!5Crb0^;Rv7Tnxl8UEmkf>eg zIIo}>X4@ufyW)J>t~Ym8;-P3YzlV|7l<8P|rnbxPLrE*Qc*log)n0iJf$f}Q;k3sy zX#-9PVm?O)SA%PG8wZx}z;JS$iWVm2GSbDk3VU-+r@$%wpBuJYNhX2WZFQi0?D^Or z*GBXw{1@%`xJ5eEs$e0RmsM#-F)5#fX6*E>A^A+BXc{O<3*}j!IapeMGId~gd}{lm zV(Z>XxgRoTuK)LBM6=nWFC4@g{+;hT3y{kK{H(k+wz*|(akO?WMQ~rcw=xn0Yp(Sv z23u}ygU_}d*x(WQ#3M+FJGpsgZtx$Hk%3xcCpd37h-JXi%frilcT$msYBIm#e?%=X z(EWU3;2=H8%`2)4&b_0(C+8W?uGxr=Ig(Kzg?i%(eGz4*j(fD+QY2m?mH@0W`hGKn zGdh+{;sp7WNQBPQ&UM}|^B_Zs$VXPr((56+j z-UT-4J7UMd^xmSgAE*gHuMXFbi%cMvbgsUCa?=b<>BPZO@4qTOPjm4Y6POIlK*lRW zS_?|BW+^>Y;a14P)$^G_j`HvVS_g@oZ$&j;R60(*y)TP_m*x?v_XOETG0??NkKqN& z=wru3welWK-q?sm*)A8_sYBx*k2TvxV)!e*YE%8ixu~JNbgegxmbRSG15E}Acp09* zkgM<3ohj<1q@GSrLq*}@rB`V-YlA`N3WU4?Y2$^avpx7xNF}I>jc=nVQqjGUlr1Uq zbRvTS7wqDx5(4My(&1(Q>)Gb!d{I2W%+cc1Prjv7b45A<#p0z!)W=M~q;De0we~%Y zRR4HwNQ~lwAo8G1uk@>t{n528i1tjHY5K{q#fB7f?&fLZ`u})Rf@H?+56^$oCO;7R zKH8MWn~+Sud)GFA^evdpT0n^+-G5mHyKSoE`J#SLE7-bGUzM~dS;sU>VGipz_Yoq+ z{H*Ao$S=4u_PEaS$cIRbTdP9C`fnuz__K9f7P`Srx$fJh&Vp&^QqW@wuOJfd0N9r?bPaLf)Wy$)V3ccz%F^1P71FyL+nMPy+h zksGI*2YaeokN>{FJ5lL*RUKY`A_-*Sh&mcJST`(*=)uZgmuw48LAVKpr~+p?duYh&)41P@w^Goc`fi zHO3%yv!7%IXI|mkhw4BkjQH0R^7?W^~Ywt(QZ8R zL1Rz^mWaxS#*J>KmI4QCNyGw0d(Op0ZS%U``8<&U$J%gc;KfB0QXz_LUT4(j(~h)k z6-yE3&p>=Z)+q#26s+JM$UWg2CM9UfN<~wbhW*FiPPz+XsYn5hiCn5oj78?v2Uv+G zQ{W`g05*LBh2M7`=_(>!sz87Mh8jR=N+$+XAff__6and>1P}s-B1DQJRXQOQk=~VF?+*Xx!~OKm zGxNMN?>l!cGiQdJv-jG2ueMimerxS8#BTG?HPMw~^wacDkKHQ20W{2K4NlS!vAn2p zZJlVUM5l1JS7!Q2=yRp?k``%3@=QasXJr%Ycb%|( zkS`@q*se4R)4`8bs8CcoC2#3_SwuXHTmE)-lNlwWNt3nW<+G_tPSIBbpM#9FjR80V z4&K+M;(z6bEE%kq`H<67?qxes&KcFE+G2fiMC5zw-uYoWd`$M_@bxQ%k?u(|>aySK ze*~L`ewuMRjid=Xd00}@i9R`w?hnBWOF=-8Hhc>?+5da{MoBVL zL<5AZAF}vLSoe&HST;xC?4ZwCiC^cl+@l}#923@}Pt__k%3pIskDOQKoDQ$4HuBlA z{Ow1V5=)O9Rvw9D@f6x}+|jY`by|_K%pPw@ucwDSk`C9FhO7?x270^NfOny!fDF?8 zJn1v%^0c8S28{bd>w6JagaYuTh*uKQ$grlu`DC*+WSmMI(&z~mABzS6o)aM&2J$^a z>#i9A1(_-vXNQl_`5GX8b%gkW(a8O&zn5ka#>r7Y5@gcf?S(? zbPl0hU2KKOG)AVOzF5@Y^ga`>Se}%eDlS6wQ~Uj`(-hMgd+7$pQWjOWlQ>dy(^*4i zFILsv8&cvOsv9D?Oe(bsuXoAo!K8*se~W*X5|CoLo)TnAQSLNg~znlr8;C$37HzcV}wOQXqRh{xl@tJz8IiZyNDI-%`WdCn_gGYw@#GJNNeD z*2npdxLBV=Pw7asVcDrM3u}Bai=PdafY<%30;zx zXkQnW7MJ`?8Z)8LOv<02zo@#3=hc-K)>w7VC2Z6LTw~`Q4NMpqBDs7Lj%r`1+6Zpg za`8NSa^Z3PF+M`u_S-PZ@PMdnCqv?4>5LB;!Bh|GdQkyQ8^K%qt#Wh%UaV#AG}i=* z5yYq;ZyA)O3x0PvEgHKl3xY8-m{+Hh%eB{{cm9dYXwlifcLl?L!vS6vB)|Mmxm3QX z{O9Q0{}kQ(f3yGpN5TJp`bFfweR=rz)brm}z3?;tqC0fgPl9*{?c-_EZ^(6qyr_Jijc5L~2Ioe;H-(sKJp4$Bd+*ZvGwi+41 z3HM1qP$&un=ysx zJJ23o+6vmiV{gvUUnh23sGZx*RLMhY`)>4mtm}OrG9+Q=Ar&V8(@9||htr8F+1A^j zj6z8RpM~PReHC!b;5eR^_#t&0+RATXQ&3%<(LiiVGTgVthQRaMXwQO;xiL(yno-y8 zXF5NVLE;bcKp-mDL%IV_b6Jit$a4rtq_2BcasU7Pl`?98hm35_(VRNPpSf z3VClBrw!&AuM0KyQQ3X-`Uv?Y;|D)9?f_Q*c5~M!3PE>VD-szAl&cb>7i}Sh6nt~y z{R_@Ujxkc4=C+J6K%&zrKV?9ovrkr521Qpyl9_A2?Ei^=PR*jt zzy2sv$(&L6=x*#ta?o0HGoM~xO796E%3#40*kirTY%dvo1+uqeWLD@`Y3((Rrn;8T zhmI%(uI9Wq=&8_OxU5EFDks)a#+3IgBr4C!E^uwxd?KN-Y$oI6%6TvUSRmCdE2yBX z9&~2SkyKM1v@W_zY_$qgW27K>_b1?$_7f|i`8`sO3h>r6D7`|vsZ zkBjo9=s(@ok*Wu3brULV3Jf~l2pQE1&YPxb0`GYUf_uRF#iv6C?RBI(oUP*m+*7A6 zfC+%8o9V47Oq^G&+3jpBvP~4NZ34q|&=??r(L0nTJ~yyn6ixY@U*Wp=03jCFor8Pf z2!p7|YJ|~LSQQ%H@T=<#&+sTG>^YkE3UpPmUxsdMV7KmOY3X5sgR!L{oYy`sd~-05 ztsl_4TO5^px3iAZE6M3{Q9;RvN-!W1k=4UbT}#$7O!xzhbX||OmgZ(JcOh4JVrUFi zgN9*|ad1P*ZH zR+hl?IKo&nZ6LSv7_N6&m0hISyMJP#&ALN3(o_OEYo(O|b=g=}0VbZ!^wl^tmMnfA z3Nyq)=Xi;{E(X;~G&U&W$#;<4Fi2J$!vtGRxImwHHyjW$42{K;zX38FvnJ!~IUJZ# zJdKVXzlte>+Z(;_)K};P;T)1Ze{{A2u>Vl7c-|9knc{Z3yv3q}hF;vpkUAM0e=rUT z;IF{pcRx!NgTih#3Lh@~phlDAO_s0EQ~U1x;uSbbQ*9hseBr?{>qa8$Nm_@LaOKF; z;kYz(C|#s;N`N2`ruJnhOo)!wc3!pUwRF!OFmC0VZqqUbj`cSnuL~@HWuUfB2d!l` zNfhjCTKL(s;vOVj{qVQM3;P$KuacuVER_ z1BZ!G(=)PuD9K#YF4`HRTqa7>UO5=%Gh#rRLbn1Sl=rmNLTL$bR|-!y7uR;2RBh)E z%V9O-p?O=fs(5~AXiC;ae8AJlgF6_}*Lr#@t|0>4iOTHVt)U8_C&Yl2C@puz&Z1Bl z|9I06XvW=>E*XY{WVNLyMzqPPl5Tx6pX#%|MoRw(109xpB4ipPCahvqznCjf!@#0~ zPhHi>ERV&eaAfY8{h=D>lW{tDEQzlp5fAJH!e+GJ4k+`GD&6(Pr7hhegjw zL)0G#B@FxTw(qQR@tYI|?EU5?j4e!8#aixnr@>bFv1foUc39>5+3N#+`CVIje_b{? zVSl$#SETQgCkofQJT$S;BC_{9nNYdbO=< z=Oo#kD_?%UC0E;s#drGc^0$lp zz4ZV&6V&%WMu*>)*!sRbSY-o0-6PmT)uwQm3^xb>1_qy%k+u@a7p7!o%zWyBh@zj0NNisJmRC0!*5-5yD|_xJQVso%3yT2af)y>xR(ran z3z8{5a3LgVDrI&P8o0XT8>%7Br%*8YVoK1*{C`3_0}`)`^iIPLO>i2{qJ-iAJ2=QMTLv< z$c=1{vtN&sW})fz?|etI?VCk|$$xobX9>p>o!7o3A%IkUml_IMx2l-1XsfKGU zcF{HM=oAn0~qlPaI`Uag93+qGDAWI2cR^c3->(qswYP~mzi9?MgU>3GW6{+ zWnjeG4@H=2cSPs4F4Ov?_AZIAFqw5UcW7zHI1>KpX$UoYpKq5~*azc3ptQmp$aPqT zBmARTAau=>`td-41OWQs+gulsv-wm&ecizTVc_jVXy}#4dE#Xr=IosdLWZQ2c&ElJV5-dUQI&v4~{ZA%~`^p|;{Sy50}#yPyl$(|2)(WS|OTL8id5k2j4 z$FB6%vZ_&;hIjHu%3G7CVC0l7c!|wGe^a|+PX*7jl!og@jJHc!4G$h=s${3=xu5rZ zT#qA2mqD9n2YWo7*V$nhr6+utYWUn;8f{YqG@SVaOz={R-R+!(SJ5fUH8T4gEVbd? zBO=vKOWl8HOi1BmKxPrE!;~ID8;%OTFPD&kUH-l%hX`wrQPm%PNBNwjQMAp0E|b9H zOc;StnTQ-is)IF|IT~?`Le#z%-cYtV9CGr4jGYZ-H~PgVm1u`#%~C=OaWt+o1CiFA zjJsP#A&h9wbv1ahH9y83cS&GYu>xa>B|zp+1xRa2_X$4!NYfo^S}nevIrj(%N= zoviI#yWq@)(kcvxvM8CCXy?=7rC?(yR+?GO3SKQm=sJ6JHtV0wIuaU=6Z7=Lv)0P% zyaAC~O2#o{ERs7;nO%Ux3U5tk%#ZQPXP`OvapaWI^P)XH{}(%>CKXWsBBz6$fD2Il zx)jn^f^~I^71+QW1P%E&SgO7lcElVh6rk_Y$=FD z463K2Yc~S{!@u#1gjj+3GZSr)1Is@A`Ap*x;MKNd%gdhu*ds}<-1c*SiA&gY!n>h# zB0>Fb;^wao&N8&ueO(P#e1EacTev;+q4({C&+;oza-G+wb<9d@!FDL3kw;ZIcy1D}Z znH%it8_SdJU&8DHH>Qa1Z&W64%XKOaNtRYn0_W9V2k6eHk!=0!v#``MJ$di(?dXNI z>no<~#f|u&z_)${>W^f7F7LKY3VKN%iFh>l$k#3KI|vs~9GT_?=3q}_QhWwudc-FyVrq$j!mlWt9VM31u^wakb;UZeT?S+ayubgZL)+@mhV;LslLlo z#$}cYAENuZ2P=HPStt~B=KH+K|H*NWDaz1X;mQYzfhyf}_+Ytbn4cB>OwGqZr}g`w zM&E`vKyDRYf^Y^hbw)1ePmo_<0fqNW+O71vKcV4O6H=N4ymI;sESX{Exx) z3ek2v#(lQ3l9xa*F{^aOa#}DYIt%%bMBDx}At_CUqok5_*2m3s3Ul_l z0P(gh7WP()nPD_#$mm!q{u2P}m#^ZAw|^@Xz;~vS{t}r)2&i|>++#giMYnb>jN|5s zZXG51qq(y+b|67xJC0}VT7Y#_#>+I)W0Tf|5uz@l$;USIwDVdVAt~iDkvG$vmHtYe6K&FhN!NJIBZ@f__zdb-0L&ZJyG37+ zyR=Y$q&B?h5p<#)ZgI^komnSzqNL+N>`+z|>rbG86GQ5=YX-8D9f;iKcTt4+g`|P5 zix8vyf_T}EWXdP>=rC4Q*vKfRoqsM694_TUdA!*t1J204AuFMd z_%iS;U&Q`1e1#;h_EfLYsFD0d0bi%&J9+v*ChjC3|L(y)Nw^JUZzk!5bi9IDOp*&9 zRSqd5mV95}@=kw0Iz$195D zjj+j;ejSL05$QdA*RLZ2M30smy|LNjmCHBjZy^MKOa&O`I8}*EXl1YXBQSc@R6L#T z9D>V#4nV-}ElBSesfS48r5F@CKEh%28r2(D zW?q~r@OzxE@V=b{@~=syXgrHL+r_IO6~V(6`$>ppl;#UeW4rpiQIllLiH;oP#4OCV zITW3N(hKb64YhVZgScKHA*}sY-*yL}LJQ1wsJJ~%xMI2AfYGJB0(CES;Tz!|_b|OP zjtT%vDY8h$MLlGa;&Y?2H>$9^7~dqu|BgTRf@EWPG~_%P^_k&?jv>G2pmh#P1N+u z^za95wUb1(X*mfz142=3RmVAeNuf!kaA})d&%{j)HzxN$qHPqw@a4B#xFfa%d6=dFGU_5HYz`)!B zV4=^bmfnkEU{sqb%1CK@&hDk2`{_(oUo0V47hQ(x#P$mse3Y5lW8YGJYxwUE>oHgk z^5l!mSJvYv9t8{OT_Wjam->2hubyvSub>n!Muj$$T|{len0FuUmQqh405C$d zdB(Y6AbRDG>apy4Q#^d&Q zxsevsC9=HbBv}>#$MH9ARv&}$z@Pdo!{f$DOuYMxls9>0zWG*xz4-0y*j$U?HDD8WceQt8;lS zwhKh+Tp~h)7Hj-$RYL}%wvTsfNj;PB=4v!w{pSsNfs$ZO(Va-L7!Hx#kO~o!UdsgGuE3~g} zsgYJYA;F_*Oo+9w>z&sS9En<0aqm@zxzDXJ-Ado0$I8Iv&MmIjd#Sqn@FL%{ZQf4xhdMnZ+2h3Zc;Up z#n?glwHzoP$=q{cY<+U7H~c-QTUH|O@lLu4sN~r>5?rk;aO(W|$gy)f3q5~6`5&pT z4lULY9A3C7i(VYg*Q%gW3-7EM>6j5jMgaLK7DPh0X2pfy^*!ZVqaQ8x^B7=6nM4Zk zU<&mcxd|<}bBHtmjMM}>GL8C|a%4X3CO!Ffw^^>%(})w0;}`rf83v$%=C2e z+0trvnhKYMC^%7{vAzZLxOD6*cbwd9`Ui3`G7M&=v%PkO{qZIBq;F~9-|FsINO`9) zTab00T!QHI+?h6+v5@g-JE;@4dO}Bod5(PY4079Kak3hIxr}wI7D6})+&xh3tAY`1 z8#b87M-HP%AY{a6Ux5GyCCET7n zD+aMUkYS8h=$QE&5#5Ltxoc@gWv-ao1;0O7s6~fcCIl$;h*;`mGW4Mog%KRb|V&0T|B&hbX(XWX3f~TW@Awt z5heSc?2D(UX*viylDI3w?r+52cOtDWx~{$w zBltosm4>VNcp71(24vj0JXR4O*Cz{)0rcNAqw``j$gA^L6{O;AY4_KPjL zii?$5F_GI>T@iauc#0Xk_j_UsQ#o#L6oo;rE!E|FZmrGsqLUN90~G*1ce8J?^R7sK z9*Vq$rw3O;YlmT9&pxy>Ri7VT#oVh! zNc`1g6sX;NS)BX^T2o}irRN7T5(*w;1Ox)ZD5j-5?8kbqZ5&P7Dd8u@_ZJ7DZBI^1 z#68>iy8+z7D2)vjkTo84(0Mv%0Wq%Um&xYAq7-k0#)=#N-Hn-uYK$CVegZHvn(l1t zWE2_=h#|6+&=_Aay6{R2IcVH~TsF24-!(ja4#_6fQtmzZ?pEJANYE#`!3Pqn99uO+ z7_+tKq4qLCFg8cVWPyu1+T&VQB{+e`O?R9*A`pnAjQ-y+B+QZXtnnU{);sl3&Wx$_4K|-4O00#D9+n2i}tO!|h9G-j^Jk_BL>fdgZH;;~e&gXG1L_dUq zs3m?@IMm!cE>gVE3ceh@LkF^XJTN3EF(3$hd)H81A?<&JUsf`1FFCv^@W+0urSNOp z9h7GCeb?FUs9cRnY497nT+C-;GMx!N@aA5T@)9>fha;F1LN0 zS0TiKN8AWGcChI3f$nbt<@agyU>Rx|VecAiQ+2$SFBr2PuzPp=9TfsmNGoXJkxj~5lb25{zZ(hnEH|&JFf+aD=7<9h>R~JyB^>?T0{UfW3{X7sv1^np@v@*tmc)yUV zyL-q-4HVg={V4%^XG2z*Cn96xAD#U^Vt_d}FY@V}3mbQB(7#9r?V?(&=%G_d@_ns; zV8nV!)>P}TZHJuddTT$)P3?ORtueus8wlys&8DC7c(N4NYZ&RzOjcx#x6+(^_Mvj; z$^ZWQ>iOnx|DNGKS6=Wbrmbn_qiWdV^ddie9*CTxnXIXR z_eU+Jx*GI;1rV8ZbgR;s!5Z5yjf?bLPPO}2K4)kaMvYzM9q!pn_+8c+vh)@6EdlmT z9f@uvkPm+`=ak#aT`r5`@9e>BylItH)L;V@X)g_u9dwSJqHCvjHG&Ly>JLg8qHq7p zZKbDA%aJBrcsh;dUGogmD=A2_$QK4+IH~boltBq1)YysJon0pBeQ?1UK;b|9d5#uj zkw605{$(f#taa$iA3~5TF%IYv^$3O+ceRctjvv5>NHnqciIa}-VZzOLo3K)RNOU^# zQh#7M^@3d#>*wc+yi8ZMtO|)i+&2A8!V$>B;?fHUetTK2!h3kW|2v-JG&4o;r$$J~ zgg%!dyk*SZof3gVx(Mwz zKaii?KJqWynDoK}<9kTmDEb&F+cs(1Rx;$`y?0q#)|9gPF*T?ZQKg&Ha)yHt+4tv< zxc-`Iz<0iv(g25kRHrgIOy{2*n-i_{kq!q|pZ?Hz*a+pj^V5++tTi!Xrm3t*^kHqsieYz37gDk0mJj;v?&H5;eT}tyypC^L<1U|O~f$?dfK(6|+Izm=RgHXY7Jq_ODwRADCeKKG9CDHzv z9?!GLnT(0QKV+^qvT8&(z?mwgri6d!KiM5F6vS~v2Ib6t|J#XMo&w=24N$92_}4El2P+vO(cO}K6tlL;RgThT_t!&61okh8?{qLrWes6VYJ1CQ$&gY)h17hK*!4) zS$9A1%f-&*tgAaNiB@GWz50vy2Y7h$*pFjo!QwTK*$DUdR=*L>m^Bvq*3awqy*5@X ziD0*wXx$d?i@1&;92hGg1xT5CKfT$JBizdUJ?E)D+{KGmsIZ!<=2;adYYhT-$-e2@ zWzz911e)S|f3BIxJSJr^y_(lHSRGgq*ie2&kf;qKl2lL!yyx)fuPWW6JlN%AU(8VQ zEGHxX#>H|H%xl6dkli)|3z3rcKfpPB4&{7Tf#Vu>*~{NvlV;i~_+*3<(WWVX^uXsq zQ|8;z0vFwR1&O#Hbh}`duwD*mN)%st;zC$b-@yDIR7kU?JxCPS{0^VRGFe2P;;{T* zQ3oaj+mruWIuT_l2Y~6a`YjamX!jb|NCsvY-Z$d(8qPEE#WMNLGsR03r)&8(=rd_* zMn9u?$&%3fW_>YE^!m=Gx#gDc;lZ6@DvAzigf-LKq^uXDLQ483wt;kP{UER+h*zM% zc~ew@o4zmACMtWrSc&(Clcv|KqmqNp=NN})Ywxac>hyf)W%U|U1nV>@c3*eO_Hfrh zpY3M~_6}&)`ErZ`!Bdi&wJBV6@5oqm0yMb)FCx4{set7(mb%-)S zIx}0Qr!+Y;`$Rf(`_vfr&a#tMxc7AQr#iTkRrdwrmkyNbaAXItJA-~K*iEl}%+k*D zCkQ%!n~FQBsI#MAp!CSrkiI0ocTFcK?rqYOcT6w7_7U_M{?Oa`_%${iy7w_UR0}U> zZ~SrFCuR|x4dxsi7XlZ3!?pQc0+OTg|Ocdeu=D6X#MEW7w|3 ztD^BOBHZJR+PBOumcS9SY%ZJ16wSAuuG!C-=(1^_;S1o$+yc19xnxe-T0RH_&~w`T zH2EAle6QqSCD9``I;Bd}L$FOJ1a4y3bmSBei`$bH!(Sj+pcf%?jbn2x4b7-XQTy(N zaci^1lqB^tAfw5A4}0Hm&vw_;&)~B9!aHntny}bqfUms2>N)DhXb)vW!7H|4WGyn^ zmijJcBMMq9O2(`H?mkM_quFSkYj}W`okmxPz$q>@eno>_OLwNeZzfi#7n<&WF4|vF zmKG!a#-i~~)M&^(Q`~a5&X(G7D)^a}K%sZ0n_N=Cc~4-00Zzq&n|-m-!#DN*xbf10 zXVv!E=JG?M-aEf4if=;e_e=|nhFDcobt?%9$N4oaX0c`!bb4((QuJ(Rqpc-!*)znf z=SKOfrREG&QFD7XFsvha#NU^uOG%{;@QMGMp^k^zB!w(C~x>DHCUUQT^7)P6Y(mP-sIjJ7x<*W&)@wo#@Pq z5zW!s^cA3BW-q#EL9azDjawdK&9rDcXo@f%;Ocfi| zb}y)bC*@2=0vI0T_L8#=H8sS@#RnB+iU`B!o>6<(DmdApd6s>;M@DY&(~7& zQ`)LjsN~R+I()>tatd?krds{W#bNx&$p1m`t1fsuiu*sqk3YRm2^c8ck{5BW$M?-KHP76NzAVJGG>m3ee$jIU8Qb?l=%N`fC+TDz1>3r)9-!!HvbA7A$P*pL z`iluqZ z{wLdWCEf88c%q`nioC2nmo4=8ccW)4wLo?a#g4DhT={3)M``I2;KK02wfFzBpXfG3 z&`HLRZ#=-;{iuNk{^7{Ga4CV+c|ye5!G~#r4TQ=Pu;_{+Z6~hnG|+h8#2!-X z)FEnp-KI-%(#9>onHDn>Bi9`aYh*e^-hBCVeZO1o(f3PVEET5>HG?TsYHDO~Nk#EN*=Q^i)Exx^e?La!54sNcL7 zCNoJCu&8W2P4{CdV5Fm@N2I(POo&%)9^FsX=ObW3G>KHjyD3{L8_GoWr}N7#!mo{$ z4BLIhE)dMWSKtjhW$3D$r>o|>l$db-ACtMubYuyp)98>)pS)b`jfZ{Sbpd}X(shuY zy&{d;Z-f+Q_Z~O}MOM2mA>(du`iOW&0LJJlb7B43C?K7f5)59e!wrvLd%Yu0*2UN=`TH#qF4pYHH#9QcYVze;Onx(u zZALVNJ*CI(_p9qO4GdfzskvZMN3f{NVei5KT53v$0n%A1y6pK{ z0O?EEF+g3PP6_2H%3R3xhr;JS2C#IfF`j-~F2Nu7r&&|M#-vfblP~r-K>=H^aJ`A1%!8x_i>OEntEe zB4qOiXM?z?8>kBSyQpCvBkIsw(9K$sjuU|lh8%HdC1A%`Z4?ROI zXPC?}tZw&{NsNcwlz+xT@`LBA-8EAo0N9`x;b>0UA}P%@Cs_ik%l<06l$sE+!-=@0 z8ns9^^3afD#1U2?edJ&y$`~%;W4w5K>6fxfh*F7ZIR_GU;61IT7Aco+TKN(dL*Ofi z0G`a_f*U0_o6%VBnru+==MYYp@9FWJO=X$=`j_p|Tx+^WDZ0r|jcDR~V~7clkFPQR zI)L7Od+HLIj$6SI!Mn|L?h!uGkiWq{gZ)T^*-^g`|3@)t8dh}Asl zG2onHMpT727ahKNg^o)ra6m@HhaYcx_>9Yx4PNBI#J&40V^=ws{0h2gRkq>xy-&wn zmr5d<%iab97actdx3Jq4dKmQpHN{S;r)|14GbUp$9XDANsxefodXEDUJ+#8eOZ&HFu9Y?1G++&7nJ`~H-`=8)z6YLiaar+rbTj*3&&65H;8 zzd0v+-r+1%i#4G>u74lUQEE&a)fawbnv4;#J-flEDB`6pFJ*d!e}iQpa5rhzVjH60 zN7FpHDsn$6L%6^Av&(dx`Oig97S_&BqHH3UC*128P_hg5L~gZB>PZG5fYOx3U*~0_ zpT|Tt=2RhvT7sfO2?d??=@`kLP`DbHN}{mjo`c`IA-FSq_Qi$p!q^ZkUH9uQdHGlGQNxUZILlloiOC(VF`cI}+gS8T)jUFJE5%T!Gu7#Y+qjv2G2yS~fA+QAFpRoj+uI zT}~1L_Yk#?@vx)$DwCu}`}Qs`A~yY~^_NO{Oa_aB5nbGr@k7x@De-d_K~JCl`Zl-& zPaVR!e7I?=S+imP-8;Vd--Ce>N4yS3!kYnA<+!4qk|+lwmWvr(nn++8c^LOQxdK*u zfP`te??=-xZ@DUVzo19-NA1pUSNEf%k;WyC*(K92`TV?!ZMEs!mrzumj@=axyE@a? zOascJs~Tx9>x*PXY`zg#RC-Y|E1=6ZRLYLnfeflrtAmqxROwHrXE~`97iaFxxGOzwS z-w*g7^$4s<0l-L3d91WbBRlMv1&6OlejsioLQsV=u|g1)+@~a{w#Jrw3*T3MBp(J#kk?+ zT@B2kHEDD6!;5FtFgi+R@Y*&>D~KH#6T51n9tSK=6jO^IAiHbFTUkG0W9G5AX7fZW zLbm!T(cXy@ZM>C`H2Z&Ak@KdrQ!2!>fKAyna_{@mT}rkf%C#s<6K2Hry)jm>$w41d zi=d=SN_qBsaT8rgGMszn*A2?`C`KIPw3zzc6e!>O>9HWcIYOjCIMiU*uTp>399$R7=PVdx9=8Wd^D6;zNE8yxrLZ#a}JIhYejq6m#Q~ z&M7Uv*n;hnoNn^tQ!kZOs}zG>4A3%r0h>xyAHLh8>s~Gk5ZubA2{D``po~u44pAqK z^zcbfhnfL*k<`~iLWSmzMA21-@Lug3yjd?ZIp!rd zJAuU!+;gM|=^utK0jV3?!ysv@dIUT3kjn*U0PrC2#0~1RvE^HCJ)?5>pd=g7Gd9Fy z@ple*^C6z3i9V}kbyze`1yDGHm2go~J&-Uq)yZItb zCj|&BZ2^JrL2sZKkZi8Adjt^E@`gMM>37OQZHU?CcVUi5a}mDzAPH_iUlD6j_SbB? zu60N}rds~`!64*#@xveX0P;q79L31*gC&75u2xToqYYkoW(f)ORYVsIz}S8^95I0= z=!-`eQJ{I6Ab2D9V!S0^p^xoMlS2lD#~Qcmq4)VKCw8hgT_H!kLaXP7A#Kd7=V!c- zk6)eO2%u>c6=~+wXOH?}09kwQ(zPc@@cxI$6P%J?#;A|(NrNB(4P$(VTCt5^nc6R8 zHRS*4qN(Wy5DA*MfhqI+eUcT{n1zqg>e?;cIzlUg1V*23 z4$g8u(S01&STsQA3aWsuK1;VBEZq!A+c)1#zOL3?M7F8xzXk?5uu~|S+~7pI5K=_jD@TNoGJu8N{PRb!FZx zQ|n*1fGK1G|jO`Z3#6juDw{RCq zd^&vkcG=*+lv%X{WO|yJ2} z-Q~WV4kCTTo|Va~_u)x~F)Ej0cERCE?&wIKn*;>FNe*(fpOV!3Bdssy@r5mozg0At zrAH}jQdFevC&pc%7}fecQ{b>qDgF(+PmOivIb9Smlan}ONKv|e7mo;`Mfdn%TLe9q zp%SgX0S0tz-?-q;p8y0;z|UKejEHw3I8ZEnq!xfUp2|U{W$tvQ+9rZR#}96P$%;UM zU#1!;C=OcdDol{+pZQF@MsS5Gz;?cJ(O^1!^)P~j__Hc-VtxNZ;_5ucO^fXnNZX0n z`;HDrRtkisC#8s^ABsf1_^%?HD_0##NQa2Lg5-BZL8xdVR@F%KPi`F8LMa}5A((8l zUtqcdUk6!63cpVPesRNN+*KySf@KU%8!IHBt$TK*XxU_dVobd)Pz@|I4TLR_)ooJE zeD$YVl)E^@1!MhXOA;S7bC$rb(YhK|uNnW1u3Fmti&#agpV}K0-+e;I4}^U*{~6pn zGPbJsW887~kM*a-QbH!>_ifK07?IYqA&avTo=vk8MD8>5l&zlq+`XrqbfZAl*uZn= zFA0FNbYWS70DVvp&~&uvZayr8J&{*i`oJHdAv+Y3cq;pMcyQwU8N{>QGYzn5rbZWz zi5_30x;;z~!YV5!z94Ib6&MZTG*3lZPV-Et~-0*XxnjqMf2<`MT=pfOH%IoOV z1WudkhVEAyrUlj|Bci%!5nI@ZO$@WOCZbmeaVcetLNEWLd%4T~e7Gf<;NL^T(AKjj z2bf+aBu&0?dxg6^z#{|TZ>n4FFo>Bl8CQe`Xa#awa zoW$_w0oquJ6d3B0JJPZe^tk}fP^ts1xr#4LBJ$_L9ki}P-l^XnJ?g3=F(bNWJHqgeqV(EjzKT%-tP&(m;}P0fx*E@Cdv?P^Wl%Eq?JG%PEOCo zLEA3s8HN}Yp}$l=LT|U6ge=$8UG{Uv&)! zKgaNFwlAe9K776lf8Q~i;CD-~8iImEVGaWA6r)bLQC)G|1T7a#3X?RkUS$lkv%EjYOyE_S#ahyh}2&ChwX;rI+4 zs@BLc=w6kb;O9ntNTcp+KX?AzqD98+?YT)2kAsGlCxt%Z^|O6dJn_MpD%D;2qd`G$ z|DFFIFu~ekOb`XnRcW4UT>pN@LPJ%vqugPZecR$(As#z(d)>`k#2CqM#Aoq#i09Mr z(0efu#NqR^>s9Zvl^-duu2=P= zZ+(k2e3AMex;>m2)O{Pi@BVd;02|bKF!x1m|1}K)8y!GJSv@#E^OA0Atq)#5moA?# zz*6kp|MiB0MZH0Yp@v1RUNOpGopCMK+0Y@rqv2}|WlmWT87V^-{)jX6b9wTJ-g6h) zx5cTUQMR%x4y|kYCW(gZ_GL$*A%arWV<>W$Qflv%;H+NxZjfT z&324v21UPhbVWRu`LYsZ@)pRgazYnG5v1TNNM=Vy?`84 z+wU)QxjCk;iqlM$mxQ9LNekA6dF!nVTKi(&3+KGL$c>h`gy!N#YjNz_lR__=Y>O3^ z7;rJqXgS3OiwL{4R(!4@UYsW1x_$4WH{p%S{7~0<PPjYMe>}dpgR6{f{xf336bQ7l`T{dzHClYnP%e)qedP_iW`t z^T?!X3i3PYUil*%yf9U+>J|&S2(&0Q8=;ZFu9@;IgCDC}Za?B#PW4uhb77|%dCJo{ zi#hITUbDFmtK*Y;?nBd(Re|yLg-Wp^lJ>cCz7z(yDZlc=`Kl+U7krfD!o~QjXUfNH zDi$s%tLH7I@C?Zeh*C=OvB<<~H4A5dPICqflIA2Qgiy5j{t|7y@iad_Pup3&6UB&Z z?+d6sthuASXDVRq0R7CYh0^}`@taZhzHUP`!`S!o=bwb%_uViIFFwQLj)_EPTWHwh z@2h?5lIBwZg%lihwVMh3-cc((vYd^VQ@hE{)+4S^_uHl)Izx>gQJjqFJOf3f_i0&O zF4YAv>SL|0+X>l{n;oQ@&jdjCJYru^~d*4k5m0#ym8Ch zyYWHycb*5y&jjG8hnSRRu9`}5;0@+9_iv~G;={3KuO3|GH zqTb*}k0KQoT&;D(3-UU6-IYHSbxHZ+1Z}g47Jy!H=4=b3C)&qd+<%{^zrJ0VS__!E zXKxTFT5fxdW_MPIqmsFa?6gDL_bf7I7;bioehDu<_NB-@QN8e4QHFw4#W% zy0fKA=+q!lCrIr?kajK4-XDG>cuG`Nf7^v1h# zv}bQ*6PnOlkz+hneX_F4IlC+l7pb{O;Ya7np=co1;o{F%CllDP-oVlR&s`?Gn|X3p zj(dXy%5G?~(&5|uDo&zoTXsREn%oA^$$q>w?z)@ej6m&fs#b#?XG@%uqx%XIE zSngA+;Eh7G{B=Twx6=8-MF&Vh4#UeBl-%0J@kuMvsL3j*>CRa;Vn2FCe^6rs z?Xfq^l3)zC)1X<&_ZRzw7&b+lQj5=gPl^qH_tw;yeIRP=7+Unw={>Qz5h7{`tasVX z1zk#6J)bl9^zfARIeO9$LH?N+Um#!-zFh~XQ|v}r&u~F^RDA~@@41{a&u<<(9}Px}C)8T}mtB06+h|6n z;hBhBO8!LqjHPG!ZP(g7C9LWGjR_7IE58x-&DN%UL|OzBi%ONtF8upgVC??B-%mTp#kEM-z-LgL zj~Cg|_3+zLaq&yha=*|6R107Q28GT5R+~&{-X$E1!)|K9^)R%CkOoOiD1?L*5MNuW zPtV0f1ka2K<&qsYnj$FY$_(dwwm#@_!S^r?gd@;5s>Om}ac>b>tMD+M09tpwTmuE6`EY7!jC9JC^l7c2ZRCY8q3gb-qD*b*j~X4`3u;)mVira z;ypBtU%>Y7cpik-ft|~Em8%P(OvcR*^|mbS0_ok0?5yF8x|v`VHq2nx{LjAE8ERoj zk1Caa5cvnBF#HBBCKg$KU}RCVzrJizCEr{eG?5R&>Q0$6z2d^b0l zf4R{_t(~eo6<9BSw!2YAeOYT5%{l^ZfDg{pyGM6v6Q21IZZ9b6-yKzL6d6ysKZBbK z$f8w#CPXu>HlPKPK3j37DjBkv%F2)|;Jf?ZCq%MVSB~gZfmVb5zwP__KXsG^Mg>Zu z@8SP=2c;{9C4&j*HVb+V*P0)k;;8pLZe_e$JZd!O4TtFD@ZJ5D;ejFZ$P#mxw8dbp zfBl`F?=aeJTJCtlRp(LEe|Ud#Yn~*YS##^&)Ht}ir(aVFz3f*Gk3qA6J~WF?OLp#6 z5B2+CT?|XQHzGVVq-&Vu@Z=yMn{{%YkG5>S`<|x&ATcBCS>`}?7joZOv}9-8_-!qo zpG@uV$R{(09M#^Z-X1h~WoYC^?$K@XsP7y;p_`@iu7+<=j?Tmo>%aCWiN9I!==F~9x@dN_3-QK;2ZZx40{v$hh3zM%JB@AaqGY|Mz58K9! zK&ZOf-J~jSsYG{NKf!j6bW4^T)p$tR49h~3ozYb)KkK&p2Dj<2^?MN^GY7?6gV zPy9IMS)SV9sq|qjI(rwG&Rd(Zx74WeF>Hqwv%l_?kr3zV<@*GA!Rn=wx5+71cq_|; z?B@Gldc2oR77Uaj=237_^oyNQQQcMZxY|Z%c0GQVFnpd(0&eIbX!V6&_;G*P2l9oy zfI?$loy*myCSi5PkTqXaaFe#>s_bq1_6wdVEqm0-tO>|WyQ&ZY}{{Yj+)VieWHzPbucYX$^vMvzeL`P_MShQ&;k1JmQp6^$rZCYW;c2hWX}Mmx&Ek^< zfx*}+ozCUkx(b_HCwtL3Elun_c)oFHF8-8dfH0>2RbS7qBycm! z$l#T0zo#yGg^itVhEZmFRTei5R=SL5F?3>Oe0oi^5AK-ByNg%-G!07P&#Ylf}pHEtk(fxqg zoN;|0!-_Ba@Vp+QyPL4$mV89;X%JGZTPtWTO6zMip5Y^)uymoCGi#(bnb?W1QHHCU zLvGDtU|QJs=Pw1o|L5NiYyoVMqDBO7+0Pm25sss0LF5rwQ zy(!+J+8nxTTh(zcodtiSpAUD-omtUYntpMkp>QP{)3~(P|26g5$q@Dg4^|crmR%JF zl}*8JUaFD5-hHVW{i{`bfK^8125QgY3JV;wrn9}c>t z1A&?T!AYB8<*HvLG9N$2{CtN}C0C_fRA%Y#gUCvK^NOa(4r({9dk^+79P@tktEhSW zB239;DBnrWnExh*E`a}c2GVW&2d;@g%tx|2ypHYJ;Q6kQ2#Y-%W%NAT8Djm@WZQ*nP<}~|6ZmFNBfW9Vac<4y_RrwWGoQsZzE#)kc#>4kjX+vusPJq?!7P7(oq}+a; zO6$gYJutg$PIu0#Xjy3<8-!Lvm&Vs@EqiZ zh0iC>$tLU-@jew#!Dv2y9ou8TQ=^R$8p4DX%g6%yOWxNMp zBWz~_syTGIJDS7Z$m^^P4~4-!HI4o{TrzRalfDXnh?L)p(R^*xO_H`977gbe{bC6* z@m2~1#sPxFJy9&_aL?toaN;ilm|z*q(nZfB(jYo?BLPKCbFN8?g5IX7Mkl(N%U-JW zdQuz^zHWVQ1eR$lgM8@jL$_9fo#vYcZDios_)TP0xlpMdo%H!cnkz6YxSx zVOeZ>Iy6U*1uH?t>`0B00uc5RKXkt?5z9mLQ^`E%h>A;l|1H!GCo zsMGe+l3aR&9T8viRW714qO|5s(>YFftBVtbE7>4>r9 z7{k*ce+c-*FGF|@XEEgTdRdl>m0N#EDm75bLtH)fX;=*dVzLkX*~7f45-l=6;ls=` zJ%w>0S5?hGic&a~l`@Ve0qQ4IA9UC_=LZ7gP>6OifLm!3BIPY=1tmbSBC zDE{|CQhGXs2o>kBXMD)b72cdQATK!qWJ&*MB)kgm^~_}#T$5-eFw2*4YkK~I>#)|? z{pdk+6}T+!CzL62gbU8c4;MEBWgtfoo^zmunoc@bT(D#gn#;n@jQsB9Sot6vFVR@L zj8dSHe^*qSBJ9g24aipE9%a0WZovLwGX|`S%Nvl;51KP>3T>h!or7$_jtGhaN|}4< zhAEkf@uC|Ke0|Oj0s>c@aGCJgsx2tmCV*fB_ZQ7Cn}1;+f*BC;{@R?AWKXh@8IUcp zBYLYoXSlz=e_+?=Tj6vMdq{}rw1tlRzrbIVCZgFoIj2%AJwvd!)ChK?n(Soc2pR5c+$t=912peB~t=kntkE zCJlS`Y3oeh0Gyba%tn;nQV7y`-n_xEtoB)0Ck6P|!u$(y)0q}Ah;#d>7Z_KH7`D!% zg_2;qp-pa!7r0QKlYJF|)(MJru1g60qdA}~?reGgh0MzSIj?<(7pN4{`n(lwxgp+= z$vIvml+rsmAUb7400Izu`mF0-J+tbX@hSOfA%W+wQA~mVdTuFRo_QGKNBub_Ten!p zgloFtfN!4a4yF7IViceEd;c8P1}J(Di=O=aC>Aq}i{fVma@9ts*qc-KfVfm}O6Y_w z3i$YJhF1GJKM)8T_q6`f6@-%V{4pdF_2}$nE4sEZ8UkQZSUW;#uqc8m`N4(k18#BK zSZZ(}2X4mMUBAq#aKKNf_wm56O-WFQis4o$%Qo}fOsU!ZTR3*ffUvTvjUbsvcLLg2wcW~56n>v$LGhV_Eo1|)T%B@woQPQ66?lBmjkrd?Sn9?GW%NdR+t-g6X^`=#l{A|k5-Dk@4QOleVq`wNs#K^k1NQK5cD{w{5 zyyY2Y9uUfHfxp7qt5OPreRk?rJ;J|8r7X)Ivvr%Xcj;L^uC+vyeC!)1V(7G`ru|5D6S%P>F;z z7m3R>(&{e|_~`<*`2`>*xRoN->L&r)jl{ zQDcpzPylEiN0+Q0h!@Vjc9m}DE`PZ$I2Jn*_stMxf%ye-(l*D?DoG}|+An~Qf3Y$^ zW=%wohzl&pEfldvK8@&di_QxItguttjmRoWydW=)1Ww7%{Pk+pcs~_*^z#i-9E;$N8eDl0>MrQfN?Y{;z@Q(nL96EJ zB$YfY5RNi{1Yh_cv0?RpNT~s4szLUm{HC!RpS1r!%)Mt&Q&IOfiV6~nf+B*_MM3GJ z6sb}K1*sA`L_~`8-a`~ZM~X-hqzTd?)F3qi(tGcOBGL&xkdWjap65UB{qW8$Gxy8= zkbyZlY5T0R_gd>$cF1l8#XkV$oT-SpC$SNHElsi@ZGyl0##gEys^c40u|iz-1KhhD zjh2Zq9KU11}DqkUdP<(c8N> zWI~p=-^fAD^lgtK_dyorYI8Y%8uKNxD!Ko9w%uS?xZ zjf{RQBD4$7<@GWiaoe2zQeo6D_P#<#5zMw&XtXe6=3US?EfH}^@}a%QZVszUOmk`K zxk1{28+(-K0TU8V8H7JOblXQ3udZo6iO$AmU6Wx@zRJ1&(mv~%)u*cgA42QEqOLKs)B_`HlN;-=^z+9^tng z>(+N^R6HE~@*%k-CQK2hoWwyBH~&Wa z&)|B0+Hlr`9o5P|P5lrKK$EhZ!pLF#1S@g))$i-x%^Pgd(>3}SSYA?%%cBeEggt^@ zkv!kawhUF| z`>@b46?Vd?TtmGp>c}CQ3tku{z5FfLTHsjx^l7~(&Xxczl`(1i_N1ZuDCLS@XLw$X zil9n@A_y2H-F*6m-|x;cn;!%>6Mu_|ioHW0NTI~81!m5C z#`S0Op5Z&%Ug|Rwvk;&Knjj0k=QZdz-yC@UmQm<+N#{!)pU|o680Vd-Pj2l^o7ZEe zpV|f^Etw3Vhiz zm1HVDHi1-~c*``yW%D4a1mnxfnD6*BKYJ$4VJ${a`~A{D&Ai2JD#D(X!66GobgF!6 zwL|GXor6(X=Cc;D3E@<0G)HmoN-@o;6qy|KJ=FIyM?Y(HY*DKFqJ{B?mSif|vdcaoQTY6U)BcAv!Csp!xxW@!H`LX;0FdtVj*@O7 zZ(+wthWG5ejWHDbV9oiq=4o*rfm-$^Rl_k+E&jn%&-eEBHQ*ej!qSru;HUr6B1>;P3^1Cz z%%vTEAMVl{IW1Pq&8*Nt>22Ry4R|1(#+5&S55zVvn^^x6Z?0L>0;xwcQ6q-#vclDC@da-0c>$c8D8jHITy6iw8IGC@cX=3*e2e7= z$;8lN6vYn|67R-ZJ-i2I1Y(}8RA{19NId732QmHcU z*KMq=d|79JGl86MSg85L3dIhI;SQDm2JYm$2mZOekYTU(q9#bLi6S9%^YQ6t#JEy4 z#sH+`g&6fviZ6bn@0ZE4Mss|6G#c-f;t|+_(?+LdppIXVAwc z|58>iiQ>YHE>m6PZP+Ns|BeyFR zC0`pyZ`?#Vr~lb8+e(h!d#0OS(HpKqv;=hN_s)95#rZE?j10$AJN3=jtG9mhYJdbE zpQEIYXaOmsFS{)6H+&DUo?@k&+0&!SR;L}T2m;`3*;W)O$wl8-DoDX{r)h;OB5P z!rZh}<;E4meMA4}fODm0s4Q^c752XeaxXjuv+4gcXxgCvEZ`5}{~5|#ivK~-skwmE z8`0R-qjF(A=y$Qg|8FN;XjDVu{sRmtp2|=yaY*p5F4P?4m;IP)Hoe#WEJDr5-0gRK z`II@}&BfurqNtNT*$hzJ`TCxDBMutkE|f=>jICkM`j=|AR=>c+6;!UR3uWVLWjq-l z@OU5^kC){y-2Bup^iC?wQCC3L6;JX17-X+InAc=YY+YP4p3mEe$?syWk}MAZjOw)7 z7w*mz`!yCFP^ftUvHN0IVTF?0v-O*wg9+ICx3Skxh!3iGQyqn8 z@qp_Km};S>Ss}yWmD86Z3m=0s|hP8&d9GoOjT&MM}C*bP{*$-;ab7`N($CB$DEwX{+J;L;*jq(^UWx z2H>n*&XwHrvGpfSozeSBKQ~S5n@D!xjP0rNvG6H*FufL=w19WnQ<+<58`20sjQRHX z#~pyO=~5wJ@~#cro!BE2yF-&7n~>Q|cQv`sJ}Qu z`x5g=LxNU;=S)fqod&3M$f*|Df6U&}AaS}a+#~aIVG*!8LtJ|X!?VU%8|vr+}?5=*h`0G2T zJ)j|f#5v;YYW^?9vPu5GYaI|!4>h+?}+8)TZ%pz^yL>OV);Ijh{;-YmZsad z=KyXwtoU$z+JJhHZeby!Aedb-e7GoMwQ3~rPC2q&m3*V_V!{b2&$yT3cV2GF_sZYy zKlE^A@>zF4hlU}{>dzkTo$GMKmN#eXfS6)PXAOhUm`n+)ue)hd*4pX8RszFw4OTzH z=*zHo1>BE@G#PdOP9H@6Cv>xeS;nW`D(-`eqQ3lfn>MO5Pqe}`_{yn%d3RlH?30EH z=uM^(mrSEg&OFLG;ziRhEnxY8lFsmQ%SfMp$yhqME{YL>Kwu6lgj6N zxsf~T64gCnz@(Q=U;jvwh1)e-`o5@b4nUPp%ApijbLjHw)`flmO?5S|A3?1y8vji+}DhkP&kr$$rJhrS#9QA>$DG zNjtzhvz{$`Vg~L)tbSY%Z8@r_2~`ZQ?sCxaDKF50_gl9#k#ml`>la=0=Wgo6(UtH?zxK?v&>)TYV{jkpL;yHc{nB zNs5Aj|JV1JYr_i901igz^*)8WE(=3)=||rIeSJ^OW&qpVO;4ZX8~LOphzW6h*c-?W zY!wvG5jL;&H$Zx;qTSlKeHRu(+#f%!;)>Ul_*SG z{CT(=CZ=A&_Jbgj+`@gaq&Cp=Fmnm?gda}nXZZIRfavOFT7llclZ1D<<8+W-Y0_E# zeDdVqUOKClp0j$6V5O1=6P)D5O#NeV5K`F=P?JkeEu22xQuIUvMKo;s1L(S6>P$R} z#Rm{~?MHU=F}cIM80LlXp$7*5Y_@pzg*JABVNYiEjzvd}@!MM^XHo7kl6eY!>7vG% zsHaO(>%}Eri16ScfrI_@?h8|)jjs$swJIRrlMs<6Audyd#VEg(P3A)`IOUt~?j2zO zr@bEaL!{qQI*Bn@_3B_>-F#gibF*%|4f*v2rabZIwwHaC0RqF>T*Ky^QEWG90iVvL z>J$6Zx7!Wiiyh1DQ&9QXFJJ+0#=H#PgTz$&!Z#ecH}3^Bvo7@*{BY`gx8k~l_q(r`(ZV2?s7UBUvy!${y05uz~cuJ;} zlmFwS(8UOEs>rN)ar|JLHVL3&aVtMIybA06jHTrIgDGC!IC<~4ouhiu;`nw5;tJ)x z=cVq@CC>PJw1Y;59r-5mNvyhV-z5SEevz$%ZCdDC{>DYJ>8k=4c|eT&9I`1+=aQr+r=zdG$dvXt!5OmWQ|1YXfqTw#3=^MToslMd@Y`^#tlQE>hA{Qy4Vp zeJ=cKtNUl3aI~9vvs)wL`0Sl57kr32m)@@&I&K<4Gr#J6K@<4) z#3@S@kfa$sasJ^%^K|KGyBid{VlZ$=_%W9>q3m>7L({DUS|)K}pgWs9Fz)-gu+2{m zK(z^|`}pc3hw&*JiaQ?@t&=o41g+{KV!_q5rQcVo+%$~LzW`2JDm>SAd5ZsO#vQhA zuorKz&NQoC5L8A(v0s8`^=H(7z2KPHt=$$EOjBqBWM6TLhAdB1juWA209nP`w$tv; zctaCFF!(Y$Ou70yX;t)BukcMk*o9tMQS|N|aw;|L=C9XGS{LlkZc*C>! z{P&%<^9Ut9hYynfNj6EL^qOo7y_O~(d*XM->Ovq0dPT`H7;mg2fFX}GXe3+>d6?j$ zIm>aL5j@1(s0&*7Sy|#ovozjg%6K@NJXlze_lHAgsbU~xGS!zyO}03ws&nUjDG(4e zf}=H6+2Y=WrZHhi{=p%4sJ?j4h+8+u8612qH^-~{O3+gyu5 z;&&H3C1!MmJtZ1ny4EfW{S@5Y-DL;G(ZA$$y3f_p6w*UDJvZx2?0aWb@h*<+l^opy zBI!YuwYA2-l*2DZ!$BgCy>i5S*V@&@V)bvqr^75ul~%A0FJr~Ue`xtb(s|mQN<=`y z8C^#jpQ~W3)~LE;?FR*`>tYI|{jbAl<3aIE{J*Y&e)*%>vbF1&`MF+qgyykfHoc<bA7S zdIwHjnjy3}S=Xbk-=Fy&0y|1|F4Cjld=iwYhuiRo@pL?^c+lxs=hOr~E_$a&Z^x+) z4%WT`x$5P#RO|xftq-@|kn>-u{Pt4_G)AhmTTtGbICwK#?>6AsyMVMSL)o5|QiCPQ zg38Uwvo*)!gEgOCuBg<=vCB)7b9(zNM}<30B|mxt6TCjjml2mkVWHsHNEfmfs6e4g zgRSSr*8*SmuEUStIyGQ@dQIkuUOL5L&U6<)Ib(Xe=jwak;%^Of@76^I_B=4AkHp=p zLE8k%|GD1oRQ<`!1ov;XQe~s)G977NiJ}>IZ9KV~>Ix@_sue^X;d-SZ^s-97gH#~` zBHE;@Q1DL^Ens+)laV4@^W?4}1G}QcU3jWoc60Zhc9-1EEeO-=#}A7?=z-1g9hjN! zxi+0(m!+}C;<+Zpx|vw~YO6Mf1vsd@vWL3WWrNb&c=9#}{uumQgLRl!kD#VM!j6Ws zyv~k;r+5;jK`>e}g_rc9{bu&K1$Csw2Ca0`w?%x0#~1ndw7;1KH8p-M@8yjj8`Br{ ze3L?=u6bqeH_%)=E-a)HNm*fXEbser2W5SV(-+q~0&#&wj(Wt!7$Izrr z#G8)=M&8R^&5YNRhTS@@t-s5`dmeqd4P0tnib&*YHH{|N*zSkk`urxRUVE7p!sKZ` z#Djb`2P*IDA-^@*9I=|;eui=O!FZjl3}yT*(xUJa`E~W;E33G=VEs6#q4QK=()h=u z#b+8ULvvMc{hs%AJP@}$U)042_et5d`9oKflo^*7%Ssjd6RG36K0Pk#QmG?T`;4~k ztfD*1n+iI*vRali@hV3VxLZi(RW6YbZ!wZ!hIpCY8w3t_Q~hfAtvbNC?CPbwOAjsQ zPx#*7nnTm=?n2xhl{eX04jp+>bHlr+)F>vIviNT;YvhSWMqpQ~LMm{kx9B_S@Co;? zc$xmDv49rc!8*)~HxE55vUSSuR^O6KT!GVgG#PO9c3-}T1nRG13QeL6lg_JKy~Zj& zC__)3M7;Vnf!g}LwqOedN3kxF8i@Af0EGUkX zoNVk9=j&Nioj#?7{<7xvtH7zwrVH^?#%yrkZhuK;;9VMBZcX}#wtNWyzwCfjtS?>P&)6=ft{qtF$jKE2!+$NV0=|eHzI@TfH$i)w)2w*;>(;>M+rP_RqyFMJSY(8B z>M%5shI%}D9ECIP8R*uJyX|knIv82YZ}Z_g1=-kJ`W^%m^|rEp(1RUgM^}LBv2aY& z_(yP5@QG91YW_}x74`SlzAIkM4{P+#wFStsu9I_-0VDGN?NP%ALVTCCGoq};Lw=&` zZ6ixF51VT3yS3Hlq{c|N%uKMi17dTnVdb+5P+un-aj;z0X?vM1Q zx&FT`d>ydri91#;o+ZqDKAN z+y4nFdU%Z>NI3H~HxPh&aA3gy-^C*Re@|`vtZ=&3Zt1mZDo2TC+g0AR=zTSfN$=6B z6WnrHQr5R+I~bw+dL~71FmSRnU9U;j1fN@uqRjZJTep>^4ZgNuuK}UmD5l_Z9MJre zW5&_USOQt2Ztb==-*W5bP8!S_OI$@Y?(U?bla39a9;)IeA*5METMm}k)Hj(fpYcI! z0j6aOJ#LOnDz)6(hoP-7jpro<51wQU8*o4QtSCrUJKOkgLtuy@;6?rHzqYi9$~8U{ zv0}t_bGdX&Oyj|K()NBEetw^FIsSA<+;@)NKyYn-2(R^ztpaE);7IzNxbuLM0<<%( zxxV@X>#-Q#yR0Uk;BeUX!>Z2W)#Fh%Q+K^r*=%w9v$X;s%i7Y)eD+_R zyQEHzoqnc_#-)#SV4&w`;VeVSHIs?=RKlR^ED8}BBMikD)g+0s?*>l5%x~~f9@~XF z-)iRAFHM*96AT~Em9jW`&LMe?y=m|!)(OdPQ+y&@VC+8@V(Y8G%siB1_A^+UtHEYV ztyikCc5H>(e5W(mU)&Y&qJBxQzZ*$df)U#TnWxc^#I1^uC(T&i9It+P8rj?H=$$l^ z+i8hEnZg2xiJI1*`Ezd*IWaSHUt=1`tm(Zeq$TeW9qaqmi- zaa{)^KsEyA0j+7_2au&E4)ZhOHTsai#k8YV{)nNEpL4?IBR*u+d^oT0wB4`k|1L5z zz?f(7%lxju{xSm{uE?dZSoipwtaSB5=w(u27hqj)&rRu33v|3@e@YYg!}PbIZe_!?%M8rH%g13>z*H7b5eTbH(<*C4nJq+yEH(Q(nLlp8QBRW$Fw(nAbfI2L3O+O7 zl!T6JC-_Q(&Lkw1u&g^vPrkJMD#x*RlVTzV>W&&5`x}515j?8@{uLU?5z)*7b(XUB zU|qmsN&CI)GkJwDbM!M_wTt*o)Z+o6VLQICcbNzC^c+$}QyvG`oSXHma(MbxgynY3 zCB{O3k?C^eD6Sk7ey8U9c6Ku39$Q!5Q+|GQ<>U|4`z%h!7ET8;5_hg_8sGbX(p18G zAY%kk)pz7$_ILx+=~3s48j$17<4$+F*)dG$+3&N(V7OgvL*cR0i&_&nnI@!VuW*U|EKjqM(GtGr zN$fA0Pq7X&*fvK)^g!8VQn8rZR)DB*o#aEahhEN;eHp&%<|sC5sV3OiZ}#l05h`Ll ziI7H@S}3GfL#kl~2ec!e?V@OQ(83G;^E{u~mWwyulBsEsXSb>byBwL?cz+VIg9&2y966zNTMThWHU?Do8aGTQ9PtOa)A=B`2I?uO5P!ul4wx zEcrv9lPmp&J%cQ2ooLO$YUMvr@S1`jNfqwQHs{TG+U^w5h%sIr*RIl9*vFXExZpey zY|EELB=;#8(>0@oWsl1-mVJW{WgSSIdgn)fI0Axk@zQPET=PIJ9tZj2!Ga@VWZ z_u<%Fq2sBmp6lqWKSB&dp`@evMb={wQf7Y_j4G1b3ezDVwkY4M-ZI~Z0(+D4Uhg=CVdjxD1}~lK z67q?V9EKMfQGWCFKdXAq&n=B83EKF*&G!SjkBQKt#@4iv_dw5?_zaxB6kBYk8}jv+ zvwC4?CTu$prYq|%Aa;~;*%Sd@50P^}FhrSN3!;Ld_{JzPXH^Jm(Jx-fEO03WXG6Kp zm{2j<;>+W1U@o9|oguZ5Jpc!_1kTmDeuHh#8)O`Qx)FW-TI-q$9yH#^EGMZp1}otc z76qZ9HJ@NwYOMp|d^lCoFFsE*0ia5QvM;>LqA5tu4E(cGD?c0m%kgKB$U%c1#YS?g1eiQ2Xt=_W+r zcTWb}J4=KmK&vIiUU`A?eGqb6xRZ$D8-h9GMdEoDDF3yFW1PGYd$k{)s_XJZ!EicH zCj=p+eHNZ3LXl`_4bC5LO)10!%kk?t?x_ zKLrWjdNA8PN6z09SvBe#wjHn(Kjh97n{POWTt)g$doaPNJjFrd-}7Nx`&m>qZV4we z0rCkvJI-cR?B41Kamo9Gy^a9213I%l^s9C_8@ftQoH+!f%qjP16e{P?#e=`};E6i$SRQLrr z{-Ne1%zY}yaQ}XG;`4?LGNEGw$pF{X{3aaHFjVhy?@0;Ajx>t4e>mFVUlgsPW3sck z5Y^=Kuq4Q7r&1*BzUpP_oP%c5xcjauO*%Rxc`gyg^)w1-a?F{8b3RBAk!aA7RhFMOY@o8&h zF;OQ7ia8~;mtN0M9F6+q1jP0Y&PTjDAq z$*104OU}2JpeU7tV5pqPvfiHUEZ2DIR;A${i9KvXjIJ&x9IRmHSBvR2N*@L`c2&mw z!MOzN#>2v{ez27azv{MLvpM)CGijpOIaxfA?Urr)8}g@WZ!|)RQamty0JIq@#8+)9 z_=I`2h$sX8-6B=ZU-uyOD)~kYH6_%1Zotz?nz%Tr1kGj9@2g1Z?lILIo)i;M{`sFlH~AtdHm(%+h;5W zeaEr()%IT+riKVM;|}xd?fKg+4W#$oyVBHX3BR37wN5hHDp~wUYut>!a)$E7 zbVRmE9L1-Zp^%WGSN+aobM0MtlbAbW#Xh%kXDRDRKl74pdsJNFvm({F9^3R7%Cjls z>-JSYD{?!EIcqB94f$Zl*S0MUtcn#+u4r3pfi!67OJn)EvSz@bzEsH>%FvTqmF?yY zIr}{S*NE|Svs={)6nE|lvNQ}kjzY6n`3!O1B%Y)5UtCbd9F<^c%2dy0=aM7AO~Fo`w#56fBrX)%{m@q9oSP)r&hd{H7qy!BIM*7?%uf8 z-G!$mXJNRxw@n$a^FWx)dfk=BECG`CO*O1+>Jk36E|ug8KI*|D6+bw?FQ;h?{$;Dr z3DV{j(9nzV;3Lfh)b4c#Z$9Te0RCj}wL@Q>C3(3dqo66N^G|QcR@R9X$avInG=JZN zaB6F-lqIu|j)m)P{pAx9g!Csb#oCJ^5hLdamajzG2vaW~u*Zg8%uk-}qf1$l*w+en zV}yK1ZBj;&8HJ&?(dKc31fslOXuZ0*s`K~Cr4lbZoG$<;9X!W{##VvIs? z^iv%OV?|L>m*;tBwl z$Cqq4HQeK!x5s-_WFL=*eBztXDE`;L=eFf3A<`woy2byiH{YEgH>tr=J-1i>bs17K z#k9|sA2#s{rFLWGZAMxd(Ju>LRr*n5!&4IYMM- zDHqKRD0Yn{2Hmb>Dt|flo|Cqofy1OH`YPJ|3xR6c^FD}f+w=n-DX^bykWw*7G5}}( zV{kH(YYSPBl1{Chl~2wnAAdK|k0m|0I30J}KJ_V#N4`RT&VvQ0{3o^`nPfnb{y#Oh zJ?cAmg;&|%>kK!Ej=+1 zi<{g1tnOeNE51;k;pn)$w?M40^^S^H@)!T=N1#*kpFxfvmNm4!dirR8RtfX@X`qXD z1FP3sVr9T=cHxd`ZTabS{%-%$jfQJE3-K?!UGGR_6|er>MO0h?dn3Pz;%0}MPhAa# zYcrYj>>zrSP$pSp=xtAYac}6)QAeb7pwm+a&ur1gNePLj7!!m3<5^=SO_%B2<<$m- z`4WRPVFw4&KvJNKYT=p$BE>Pdp{7!9=kDx}Ld$Knsb=Z;wD&;CQ?cNPs*Z=wG!XoE zWU?E0OTSCEH1ACHkz@(oQ$}7j?9U2M#fTkJDXQXi@NZy{PEm%r$4}agVF#i3 z*@w+LOG%0D=VXS7)X;0(&hqc+aB`lGR`)R+2yjAvw=kYu_A2+W?#ifbj3_6isY79 z6e`)Y7HsgNSydymNR81a{~$(B3N#*&CxQx)nho3hly1MRI|Y3TE@WG%oz8R+y}m>3 zm?!@6k!Rj5(Uo z#`%lrdV}@mQhxP$GE?x*1lV2R1BM57Kx@t+2pYVVJC&iK?$}vR(d-9;K%h<|3!mlm zbeC(vr_c@Q&6xv+G|9%;(hT{+p3d}Lsv?FOp<8-OaM(h3PT0=k?lDp9G0SayRCeZI zQuBsy{H%nX<4YH&S1d^bzjl9(HosBtl=HrEGf-Zy$$YRKW4crOBtYKDyLZ&)1yXKZ za=4;up5kOboFj7Lpt>1oljDw``7xBz7Bjs*!QVT7r_|X?{9-g;>q zW6q$*YuB+o{m&f@pRPL#3!FWkvtSY_(yYlGQ=T1!syNq{6t5i?vBE38hV@83dv$L* zukU@@BgC=q=AKl1K0_uDjCBc49=twKC;A?#v)a?Wacv4D46g=x9PNb}CTbtEe}4(I zym{?*gdNfF8%?Fh`Od~_bI2hoZ$kt!uz`mbvomQk0AI2a|Fq@0Ljyj?v6gxpAq0D^ z4dP?h=LcXC#>snR!|kESDpavgHN+1|oVyC&3HwVhIrl#b+!2N*FLln}@P3@tSsgRO zL7l+KSzqr7Ud^RPmwQ;-%GZ)M*-$$(Xgg!t6BC7wOX#AJ7a2ex}9}MO$Baye^ML{Y@PRVcOLXd%IS3;2|odWH;Ud-O<4XrZ4vJeDM$U5ytyT; zAEai0|JfL5M7aC{FN{8BtSc(j+?CdfEW>vNvs)jOx>9?3s>s>?LE@xx8Ud-qob}MBPsUl-0q*;cuY}AX$`vZ(hzZUhUpy*RwgDJn)yrq>vF54dm zCeYxT|GAKU&AbbO~Jv0n3|nMvTb<~v zo6ddJ*U9{TwT>JetBGu>-+xm9FMzyXeyM{SDS9H_Lh$@RVAAX||P( zW$8+KD;^YU#lDBeD;iKyp!2Wt7y8i#+50PJ<_b|C5s#zt&!|PtPjzjrLO;J8Z~r7@ z9+VW`wfbmV<#>nQvtj(4(4IDUX7}^}E680qGx@@5=$3z*0<-II(ds(0(Kg>GdTS%x zE~q?%GFwvsfG@}Xz5$A0Fx94qaPdKm!`<}dvKU5XFx#U$f*>$swFejdf^Y$kZ2GO( zZM3d6{fQm^r?VU_#vbU3P?cTQKKyod13r2<9Hh;ty7vuyO)=fU^WIUWbYrbNm8$+V zg_J}lc+s!f0y|FwY$8?bj{Uzgmtp&NwE~A=Egzpp4!5;9wwe(_C!eLyoK3~6`XF_a zSsWpsTJ@@BXFB7d$koq!6I3!jsODN>7BP^Ssq!_&TFumZI_pKsJ6Fs=$~1~0&?>C? z0bcC9ftIh?R-s^LtfCmBgf3N)h5+bTZGPfxS!)U0zCU?aa;Sc1zVb(pLcyR8>iwy= z{)DZum{##K>7&{$q7&G+I94*CJ}$y zx&jwStHOVf)-N20&$NnQI;DG89nJ&mM8;GN%@hi-c5IUZ-?vFk9uud!S6VC54Y3ba%Mk7TdnJ76b$``Mgh*t-jupH@f@Mw6lU zESlr-seCsL23 z%ZT-85`>Gcdwiiwo%En6iMMarBX)5Lu+Qnt(fzb$OwURhByx=<-*POECc5B}_S;(j zq^LH@1DYSMgkJ5n&H98B7>DF*Yu7w=eUGLF%C={ACoJ=b9J=Z*PQADyE$vyi8H#*O z#Vck%OBRbBg2o4L3*HLm96Q-me9nE-^WELC8Y^28pY2Rtj~Y7ovI+0|QdOs(X8Y_F zzMG$2#^ZeYSM=Q7MGRGfxt?n2^uwiYcnMmTM>k8PlZj<1_3Er|0I{UCo=ia047HZ>DNiz-X4;6E`z_ulT05wDB2-_GSIDF5&>hx7l0 zy3;{SS}X=dg$7V)!~=s7sSh7ygBi*+K~kSK$nzKVt8{dxw0+X`#4hXff;r4Yq_kpV zJ$R9e?jB;L45gYW#+hC50ZaWzsZ0DRx1JaI=+!A-f3B0Yb@-J2CDcd(X5+F{(P5Ti z>+h-XChc(=i<#28e6NVx!W4`g;+vFTndP#X2G;L%w1H5@Bg zc1F_?6>~{rBYciWP`y7?D)dMCpX2n6dzRSH2UgdahxMX7qw-%kA`J}fr*Sby&lKp3 zaN`wsX)}hqO$fzMwUcj}2MKLL;bWw{=R*`Jspe}MtammOM%$JD;`sG=>Y(EY=o`qV zVw7NGyUF;37HaAQ^S6c|`9<4OadR-xYvn80-?J&QW2eOom} zD-|;Rr;)m|Zzkqs9i6KQd3!pmcN)FV7oFq+dgq%DPEgXrcKBi`#(T!{?=;W~s+l?q zax|%w#iB5<83iio&|8P0)6|oLlsGM%I9&_BrqI=-qSH zUa~B9%IxpLFDp`hzOgr>HX$}X^62XV?vKxI2#Dc6JEfC^fzz2|=!)g%~m0eV+G z4ZBQcCjxpQ-{pIczvYWpDe~9nC`SyA!hAVtBk4}<+`;I{yeNgH#lK6^kgvu+IU*^5 zkG&|W|Nr%vJ5%u4@X33CF}8X1cx&gKj$e%+wl=EMVd7)i?OG?D#07(tyEdbEuWDju z2Y`1WF}B=iN~dzDj~U(@f#^AGhTF-m`#Sx$?G{ zZBu(D>d_u_KCkGGX`q^(!2o+xDP=0s9i4i1vK11fhO)6H35i$O+cyM=78_4k7-wF$ z3gi$gY+NCGE=${*Irw*GS%*-1mP4|KEMO%Ns|=*dY`iAM%E#+Bk+oWUfcPb*f z^T|yCOUyqvEk)|M49wH&JUX;x6B^b|X_1MA)Qmohkf;%wM+YoOp>WYOxah46^{Dyv zTBu^O{s}BOYJ0OzrZ;-ljzaaF5~BOn)DoIwJKs9?d~`yC%^n~HMB{u{q@U6>iH{7t z80D)O@O{-F$nTU!&<*)r6wM)3Q{6##4*Xcd6%w1HKld?V5xjLelE7$Arl%iUYc6nz zFL<@eMoG45L{Hq+JUt9MMk01e4Rjw*kD~+!z%N>;0@|YY$8W>^lLk*n8M5VRV!rD_ z>gd|KK5T*L_*(I0s#ZhBF^m;?406R<>!ZOOMGNvQK|tEV#`SG}r%7=>rdFp^js}cu zOM|}e=V+J=!?pDH?##w6>{+Dv+Zxm<{PI1CMZ%8Hbv57f=-H)*1srEUM;9%m(=ts< z(M?4&J2X6DMGaj$O^gathdZIugJEklx+U{8u|~<0 znN^uuYw*j0^I$irLX4s0xTZCmz_@9_TFlSwTAI!g0qc#|q*Q=GLWC0Jp}p4~jV$iq zYiGQ7QVmxqNlVE&ld5H-6jbAC2>pASt)klD;|k>-Qm(VoANF{@kQf zE#4}}mU6k~%d`tsp1aDZ47+y%!k{)R_}9uE%Ja(O=@|)@JlI9VpUcsle86DMk@{Nm zn6xCV_vR)se3D{=W$uaot$9D4?$l+kRW8>W4YZgA;%kLdma(J98iRO2&p{Q)H`Q5l zK~xE+fLr#R2Dca#=R-HVIxnYK=@yit+h4s&bvCX7ral&x!U^*HzJBj@?*T_P{0tSIhJNkaG7`ab7-e)+g{$MZP+ZTpfYPd z(fBaGVEfiF!*|H|S3;gmyywXjy))-{+eR$g2au#6^(;iAi&W!ALD6bek`;yyvFXj<&7X==UQavUi92R34>+rWsT(zKbEG7 z%%QZHSb|Xjlg)h7;-!{YmBFweU0$3fLdJ^iuG~^GQ-D@H!QBs%M-$iM;7kt}oC};J zPBf2uPannm3s`r*W`f@#)-7O~-k~ytK@^E{u(8QZRYy5}dZbsUF1FTuuXoJG-k-6j zoiK4{w<+@sTH=VCF{58Uj+oU4{~`qq7HqnbWo1w-E$g6h3}JkcZdcs*J@6yx_mg8e zmV;k{(}GGcl!)oXC#p&c1w9Wu2d}~?nyp73qP9c0@n#L8gIdT(vM5)^B{Q$LZST-b z%%w1w*~VAICVQUP6OACL2o;*v7-9L1Vh%y?!0uPB1qd7cpct;;iB$FWG?69x47-J@bnZ^Wd%o^P=q8pEd>(lxfQ1104t zD)pv|Wu>uA*Gyo5y$#EM>|CQ$4Ee$*0qy+eI(zQ#_lvV3;9f6}hvRD7GgNM-Ybl;s z&0aLsYj=0tJJ-&T?|jwm}Y2+7Af1MZ^|OMGLgavrgGC;5{tg9Rc@YC!RS zBU#efRv32g)m(k$@?--;iKbA|tufY#w)8(i#2=g>)td7Xz0ov2+Rvi25KSu_2wQ5t zIH|5-6rX*Q9lq47Io-4XTM(!{r+M*#ct*^8>c$2BKdSo0N?|i1g5f07~z@Cb@6?=gge>@A>b& zXYRdc<}mXPL)h8x-fOMB_F8MNXKmfW*2^<+6FpSQ{s-+6ezPF`^~GMB3`B%?&{5%= z6mbq967k|zHS&21|7Qn>7(y4;xM zsAJ2DqI{)&9hq!TVBBXv-rMpk7wcR{6)P)@11O@#hsd>vftgi}#kY(s4UJDubH`WAx z#gRiN*$mCF2i!|Wf7dApRNmi&SZmme<&S3S=_bK8yTlqb;H|ZS0x3`(M)## zEk~u>8>Wj?@*XhRBKEBuw>)NEo-Zu{1|15N-(` zZ4tNN?pwra{it0uMK4b;cLrX&-C;|rHJ9=?+@cM~dJgTHj`23WrEL6wrylK5?Ij>9 zeVaDkML8uEeH^@751SG6WOarU;*a0scc1aYa# z8CI4KED79jud<_nBvbhCz2IbL3}Zg>nw$9?eg5J}|6jX1ORHQ=xN#u5vT5jK^K|clxZFDv&w;8%xZemOZSOf zzWVww1R_k=_`qywG5rQ~rF$eU?6(V{1 zCSiv>9Jni=I2MYy%|wOoFfXcOS(rlXoF+EWJw*?24IZDjW{W(yAbwe@50#}*H-hdo ztbq-d`7%c9abOCy1x8$IHWhxOP>LUo3wsAGTHcDm_E35Dcg+PD)$~F?4vXMFDxlLs z9|-)N@%xtCT>)Gl%~m*fNZCs|=fiWGAHZ(?-$I%L^zUW8fExrKj>ds z6?E_kjAECKN3SJ5@Dhv>kK}7%)_T=n?^6F7Q{%sW*#Dk8I7-2awZzOL__6)IH6k8R z1&3C-3bulezBeQqOQaCV?%PaE^`#Dd3tl4bSJ{=v8Zr3Bd>=;W<`$4G4?dyCCgfi{ z+_;g0J1PGCq}a|U*r;;tHK{&#-{5jb7_NK=*wBS5%&H0835ib8C7qNd0aBTMT*zkI zy4l>^iRi|zvH&^A%WTpJm+9E!T|N(AddlBQp=}YXU(5%7Gd>5>vj00hknmL+ASWF3 zU-s1cw^)lmg#Dj7WMOYIsDQk3>VN-C@6Qy3-vRRB|9?Cnm1k%ETXFDTNSix~aFe|p zN(CSW{*w<{{wIX%pGf|10kWk-&*`%{MPFLf~_x!|HagA%%!C66aOPz0f*<= ze~&nL?tbMzPfb8bKe7Bro&Rq?`{^Ij0^D`{&xQByn;35*J&n20?H6jb!&ap|Qy+TawIp#dkKvqMR z;oboN4p2@ONA*+{eVLSTGqH9#>A~)gRm6{Tk&mOA0(!08$J5BoUCN>Bf4PuJD>8vU zc!AUjLWn}b3d|T4DuTTWya)n@L!TEsbv+orH$2lOyB5)M`r-CL2if?S)#*y8dCkqK z8e*F9;hjb^{i{>a5&)LwIfNl%dg=7h%E+vj%i#gJ?m9Kc(p&?{QFXkgOsYdaIH3^J zmdd0GbPj?;!=iv(_nVwbA?9Z!^s%yBX7ky}x*X2jl#G!ogU{&8k|Iv=P2|Ovjh7IH zpl!Tr66%tNF;2KG70m>+L3a#g3AZNrs~2s>=uGNe<>=$h7^D>fKoLt4r$kAB^0gY^ zBvda?)j47V;q6sHM$#^{dOGSt`&mZ|dk%)I!XC<)voej4#=Yz&>u;O(hlvM2%i)4h zrDv#&Z?|1xs4fxds+@WpkkUIxFL^@!`Lz);^@p;XUyGsw%~=dsy0qMH^MP$uBn3jW z=lMMLm@ZVo3!mD2>RJrK$#}H5vgdG zMf@2g7|tkOH;a_$c>KD=3=+3GkbfW5;J+0MeC4{q*YUzrR>e@IL11#_sGcOl2#W0D zgjj50_1I0_H$RFVLaLqdeV0fQIRkW6Zu-UWTEA@*#gq@O3(=B6JBxEtb$KUX56?aZ zfcQT0G;Q-tba3<%eQLV0*Y0z(A@y3n0S?6{yDu|@9vtMVBZiJKn>W(bDUu`Gozr1q z!>W@}yBPLp9*v=$35-AOQV78>rZRISl6=3|StE9Cnu(^%jYmIao>;PT8c9{RM3@X` zU8byy>!9;NY(Lr?ws(RZ4ezi4ekqBAJTPBjW3 zYtNx{_YliVw3aCwK5Wy9%*0a0jl(JcWi?VdG3}8`VfZ*pcyMa4^JV&H?+e>qdO!HL zxgx@73!ji2P?0{4w*EpVYZVUA>md0%N4Az!RRSQNg5+&wEb*jNy~%X{!GSyw4pks?wg z2QA9kY3?k*m(##F&4N#BuD11B+*74Mo?L7{a@8>)jC`8{FZ48k`^{N2Gr#e=my#4B z@pUTra0*Z5SP*91VyJ=@qEP$2Lbx@D_@jeEzDUHb65BKPXheK*?r)xBOO@#;b};vQJhjQg^#gJVs{@xswt&9cWD{>#II& zb5##|4g8+Pzw?_^wI%*D3S+L5KpL9IMmu=3G0m$l6o8A(Z%9iI$lMQ5rtuOY9Z>OkdoqRmExc#tL6Gi0yn+hUuyq2n0dq+308FDSVTH zA9`s(oPVQl47+efq^h&Ys1Gt9E`gn1i8HV~J6aZPr8PXw)G9HXa6IvK6D)5I5TrVA ztwsjc96}5e(3sZUk0vcVoPxQJ&1v^EXlKeR(eOhNiYOXFGL0egb&$?2+ZU#&BH2Q(Q~Q@9ZU`@AG?nIiLSgs@_d@Q z&v~itkwHPNh&MRh#1!FM(#AY-1SgFrikOUfFwZ%a%|3USeNHWBEHeT*LquX8-K^s^ zW+@f6pbdhA!w)Iq4i*>^c>s_eqrxuf%)^LLk$(HQVAa>Qb|fnMjPTsFyQcQb!jS9K zXmF(*&A}057x~CS8vWcz7xg3&8#W;uli9hKSiAw1*n8;x@hH%3 zDL96{`%15mZSopUf?tW@;OO~%uNUYt%sH@mguCuJVH!lnrMW9ijPK4NkOfbJ-e65{ zHFBO7M_>KNMx?rzLA>P5Ber>U7K<|gwlodv?Bid!HVFagDcMAf{!aGd8EpNdfRi99 z=WsM#$9pi`IL~>YCG6NjmoZbkgswEbrS7pFAOXw7#r{TJgdgc{J`~P*IkJAtCzJUL zjdh_AD>f~OizX}8KG z9W%r{eRo<3MmU0n5U-@+FR)aG(7aT<&Ut|dLoInz zede3-3O>`%W<0l`FOx!O!>JXH-^tZ(5WO_MGuZ3 z4n())qYwM zc{qm>ETLlilKKnnqnl@TCG2R3Ck)U%_+OZ^pAw>lOrc=ryX@NF_SBEPThx9xl&DG> zV++?I6#G#k7S!hQ!mn6`L8V-d!(~{{`mONGVA{ozT;5v>mG!C=JT`8#UV;JBrW?Uw zbbYu|#Fm<)NVQD6Rl&#sL@}`9iI-#&`6Q_{jU(F6IvHLbPJY+9l}*J^Y+#G0Dk`D8 zCX2`DMSH?>{Lu@68C&1(s^|5?*UT!qbF!T-*mh@SQ#D#BP+_HLD3*dTwcna@E+TiSSySPN*$H=`H(IwU1Th3qavKC!K~UlIi@Y1plMIkm!~ zb#CK%sps!d_^3e@NuIrHs}`mOB^3t~)l>u5P@z=|q(icM16}AbbKnXouxLlXV`AIs z*BWl?hyK9@VsYddV8YMqr|3-vXfX3Vzs6q4xa{5u+#!%_`EjL6C?#T9aT=_>hez{# z#LR{?2XL}Qg`a`odEdK<%#RA_-1hFEx2atqpQ486*FFF+kThtOS;8L+k{j4X>biNP zzHyR>%vHiPZB0Jp$!f2?Gq7YeVx)Bc13)nDa5J=??~ro!YIq57<& zY@Bzi#5hWUD~uY^2%|+hxNocbQ`o{kly%(wG=8h0WD0tsM80X#>%>Rc-#U*s-gE_8@HMCQ`om=4KnXq^(GR;Wyyy=o#&5KsTM z&HpzNy4&=&Rf@v3SzbdbLC{IfF=mQ(=2IbI?C95JQVkoiSBAF56>7vr5$z~dU4cN_ znYlsv;jLV?J#kP0(~65j%)r_};p(h)ZA&YLA}(fE!c%3kd0hdQ4`|XtuEu64Cpoig zKd!?ZI(Eh%MeecDwYcg4?49Jg#=-#Z@3cOvhLHkEL+;7eg&dW@61Q>IBJ5%}!-&ilv6;pMy3RgMrF~^FAmTMl4@QM+6-- z*Ao}*ncjsk%%({mmSR{rJ4u@;`OKnGD!MH2L##Pzx>~Ea+x({aN{!VgU!GHkyboJP zUhziham&Jjb@Tjn~mp-Wju^SA*5Mh z>z?60F*N{Uf?`X<}81tFQhEU~JY-WfTbtbZWDIJPyvf z@o+7Eg|6Fz570X9blE6{w=N5D4tPgMe!JTaDsYTe>ke5vhOll;{yrI6jhYNVgU@G@ zI}r|M3zzwui(7eD9V5nGg?x_Pf8oKihkP6tnW`R&{gVD?u6huX(Zz6uPI@npw8=*W zHD8nyZea3*|C?r43b9q$hzDUgeE6Kgmv z8264^svVweaGc}<2rqy+y8%L6h@%uU@R0(Q)_Tg<+P%K+YW<6h4OOR`=}f;yZoJlB zMAW@}|}$SOY@p?D;LA9^C!DwF!`=J8MihMK(YnfFc4EI{SF;KV<25d3DItBQp6lm-0QJ)8+l$SA7{)0o4a9VCen!q6>$`D;xoI`=cg&oWxVO|glCEiLQBa}!2D11k4&HDCaLYMAI|z(<#U)vQ3W21#w?)9Vn3R&hrgmIF9NS<8LH>X9uZvCY-CF=F*>b7 z)^kuTYekUHhWzn zSGVu)@29cIq$Ql^?3=0mZOc$h)z!oOv*b!zgXRk4NwwKioVccDG(eZFP1_IF6O#3INeLO?Y0hW9TrQmKUZ#UAzj<1&$PbZox>^Ub?f$3K9JNp zknpi_mYQk^8V*FRPJ~#&Dq6XHS(m#j4EEkXb*$kYt>|oq_O(p~OlK|+k!_}mt)NOD!rTI*3%-A9>u!4KMrMvh*n;@HPH6V&{8>n{T@iN*_+{WMHblzR^mRA<};3wbcZ978%eVNrm zJM&k=tT2?I6i{F5k2j7G*VUd+G>k}zI{o}^^*ymUXWMoWr`G)%%tiYUK%JGGnV3E+ zITA6ymPKmuGp5FH;sYFstmEnyAT=XoeQs=O$Pi7@V?6|NnIA#UO1)P~LquMOA1VS$ z6@bp=hG!q23r6_eC=&K&dxydO%Em~v8b>(uA+iKb4DFq$zGnwdtWHlAE3>jZl%04n zaTireWyqGy;TsHkd}BkTgiiYJ8(j#b^Ch35tkf_zk~61~bzv!;X6n7rCW`BJhv>-D zebAi?2mBVE+e`G_tOsluVNtfoSM9dw{J$gO<`)aWnud_cOA|x|w2!ijjm77 z^%mq_Yc5rNMkSLOdvU_BnC8`F&f7GItxS;=$?VOWC3G?Lou?NW-qjfz7A^%*1gD-? zDP%G$1$!O&IAj}PTh09PQ=3F$xaI4Kn z^ad9L>aKOfKb%K>#A4f~qXvtUzislOy45B6S!x*QUhaLiJI*pqVm}>3-g^bna%_xf zt)QoezZ^-bZhDuLh6hXgHOtYoZQR%iyuN>(VlF9zN++Gvv_8&7WY4oZo3&TnQTnZ( zh}qT`oN_n&FiZ7dcLnWpTdxqppcZXZ%t`Til4Rr6j3U?~ZQ&SZwur8+cXtdca)ob3 z%&vXBkln#|R^sMZsaN||4w)CtA3Z2`XYlox=xySl z7f!sSqbMHq9xns(+(GsoLtrHLYObG;n0@s_QAL;NG+7zfds(>mjVd?G0Q``lt^ z^Jv?5(eMP$G$4jA^Hlp4z3An*jEITh)=y~t#Hf%H1qdH+iov=k&%l3X>jN*OO=YX+ zO{f@^=CG8>hD}b8ADdsr)jvT8P}}DGz$cZhI4{dR$wX*>^ibIONECoJY99J zt&o2<=ejTL@M(2HaWqk?eZ_xBq)BYNdU;%jOt{&41~8VAnBVE5S$E*S&Mq*ql{f3N zoB)yd=K<`|V^#OHHkbi30ex}fPK`U|r1yEiYhZ`@pO1XeB!5MqBEz^&f#fI@I4Cn} z!5?;4yUxp{-TG7`mhso{)3`4`^;UfO0Qe_qQQBP29~;JEE>e#@tl(39vDQub?% zUhWNbmJI#OkksWQcE#`Sj6>{S#Sm&@wKCRJrb>jx>=wrThj%9I*VTN_6qII%sMryOiSq15x__>1hs*0l#+$_EyI25WL zA#|-5oqub$baJ`^9@sbSnMv+@dWQYy_NR-!CfWny4JPmP1Gu#9O-ir>pNL+Cd6|RT zI|g=>lDuKgh(lzpi<;uduhu54YtNHCDLcz|(VnL~%($JC#CbycF z!u6#GU7BNdb?POA>c7X0n#2{I3drPd!IsJ81WHM>^X{;8&!NS_pQ?FQUXlk{c>=CJ zr7=6GZs=ve_K<=2{U#KQ0M$bl;$0ov6F~VE5xXO7v3ZwH5rB3)jIMWB=?y~Dp7?L={skoEm$(ghasO0Kmuoa$ zJYuW(+HlZp+Su)m(%~5^z)jOW58w)ip_E+Dd0C4|f#$x<93`3=&8|x;e=&R_#K6>1 za)8j?Jgue9AZnNop>Wu|z{xNT{!=fo3Ic(hCrt}yv6?G*r3aI~Ry?!mxDS1wkrrY@ zRFv6CjKdV|18`v&)7p&ADgQ~OZ&&b$@mq{!U=0x$dEmVAp}}KCpB=;K+PPcatTI{Y zT~AI2Kygd4Pxb-Gn5iAR5qLAY+LT%f7o4OF1If4rCiq%chG&QntK-%uT9tjfEF}EH zvD5F|ehoNX1W#k%36=fvws)xx_GKQRK0vudDcAWvlh=#2Mkr_ zmDute2*Xa73@N{7e{gw8L<3O>o*e?NFlT0@f^&4|g#}l8e7BpUvz=O>Nwk{UD)RHi zL6?x4FQkebxI})4mi#od7kcz=zo_Ci7XWtgieF`-wcTO1+DGa}(@(l?3G8-vIUFj> zBVKTQc)%FkLn`hPeLNXN5Ji7?`ccCjy!oAm9SoL?*i45;9Z$;{48)r1L1&)U@PG@y ziYoz8S8U0{^qpT2>nf+Tunx9x@NyQ|yvFk#b8akhg4lwXbRyzv1jcQyAd(woMJvU- zm3P@nm36uXj@j(LMx}-~RGrEJMSk`(#gt?>e_Lz`c!D?OcR(nH_<@QiO1`nk&PQ!_ocKCF=1yyb zDm=lql(ZQG_0@>q#|TkY##RvL=Ni9!t;!HPCDI$*V>PW8eM|RR^GOn~^%+a?Vbf_a zr*MyarMC$XjEQ;3;j)$vYP9}oQfVWr+1uX92ax46Dns6Vtir@`*>kirU$3`!W5xyG zkY`RLTL8{=is@C*lgv=<#d2QhE0hm)1TZ6?EPfE|=oeZp*y9)ZpNYDw%n57y;<)=y zxL}Dk-C?hqOP!C3wwlcb1-qtw;Ls%D!lEx3yPjANx zOr28l?0@AUv*PJP8wZND7pqsU|-2z#gaDU!PLq35g z#EdJZ9DMbssTBi1qssS~GcKR2oZPmeZt#I;pjuKZP6u-~W*K66HB}{*O`h_>{v#mc zCEE^OHd8KWtYiG{m4{DSB>OPNYzuFyzl_z5BzB&K7T8}{+Pu^8pdy*S^l#G;i`P-G zGxwbnNKK_JW`d(abPm_fP$%0DTMe0uf3Lfuv7e;fesH-s$>0dLB+-7lW^tRdnaysk z;BRV!f{6*W0YAwLk?dx$Xyv;7JuOAxsv~FZ#ZpyEhtie0R9>AmK*+VHG>XxorRQ}m=sT;|tc zXih-iz9Y!4!G#oCDZ{tsr$4_Ln5p`u#qxYd!Po`BE^P?_YlO?a6O?uG)=-i)B(e QRKSnMJ?*