From 40c77d140dba1563b956ffb09956f43c67240c09 Mon Sep 17 00:00:00 2001 From: Chai Date: Sun, 16 Aug 2026 20:00:44 -0400 Subject: [PATCH 01/10] fix(rag): enable MOBI-family vectorization --- .../src/lib/rag/auto-vectorize-book.ts | 19 +++-------- .../rag/mobile-vectorize-capability.test.ts | 26 +++++++++++++++ .../lib/rag/mobile-vectorize-capability.ts | 17 ++++++++++ packages/app-expo/src/stores/library-store.ts | 33 ++++++------------- 4 files changed, 57 insertions(+), 38 deletions(-) create mode 100644 packages/app-expo/src/lib/rag/mobile-vectorize-capability.test.ts create mode 100644 packages/app-expo/src/lib/rag/mobile-vectorize-capability.ts diff --git a/packages/app-expo/src/lib/rag/auto-vectorize-book.ts b/packages/app-expo/src/lib/rag/auto-vectorize-book.ts index f798a8abc..f19265504 100644 --- a/packages/app-expo/src/lib/rag/auto-vectorize-book.ts +++ b/packages/app-expo/src/lib/rag/auto-vectorize-book.ts @@ -2,19 +2,7 @@ import { getPlatformService } from "@readany/core/services"; import type { Book } from "@readany/core/types"; import * as FileSystem from "expo-file-system/legacy"; import { queueBook as queueAutoVectorize } from "./auto-vectorize-service"; - -const MIME_TYPES: Record = { - epub: "application/epub+zip", - pdf: "application/pdf", - txt: "text/plain", - // Mobile UMD imports are converted and stored as EPUB before vectorization. - umd: "application/epub+zip", -}; - -export function getMobileVectorizeMimeType(format: string | undefined): string | null { - const normalized = String(format || "").toLowerCase(); - return MIME_TYPES[normalized] ?? null; -} +import { getMobileVectorizeCapability } from "./mobile-vectorize-capability"; function bytesToBase64(bytes: Uint8Array): string { const chunkSize = 0x8000; @@ -57,8 +45,9 @@ export async function inspectMobileBookForVectorize(book: Book): Promise<{ reason?: "unsupported-format" | "missing-file"; }> { const absPath = await resolveMobileBookPath(book.filePath); - const mimeType = getMobileVectorizeMimeType(book.format); - if (!mimeType) { + const capability = getMobileVectorizeCapability(book.format); + const { mimeType } = capability; + if (!capability.supported) { return { absPath, mimeType, size: null, canVectorize: false, reason: "unsupported-format" }; } diff --git a/packages/app-expo/src/lib/rag/mobile-vectorize-capability.test.ts b/packages/app-expo/src/lib/rag/mobile-vectorize-capability.test.ts new file mode 100644 index 000000000..1c123551c --- /dev/null +++ b/packages/app-expo/src/lib/rag/mobile-vectorize-capability.test.ts @@ -0,0 +1,26 @@ +import { describe, expect, it } from "vitest"; +import { getMobileVectorizeCapability } from "./mobile-vectorize-capability"; + +describe("getMobileVectorizeCapability", () => { + it.each([ + ["epub", "application/epub+zip"], + ["pdf", "application/pdf"], + ["txt", "text/plain"], + ["umd", "application/epub+zip"], + ["mobi", "application/x-mobipocket-ebook"], + ["azw", "application/vnd.amazon.ebook"], + ["azw3", "application/vnd.amazon.ebook"], + ])("supports %s", (format, mimeType) => { + expect(getMobileVectorizeCapability(format)).toEqual({ supported: true, mimeType }); + }); + + it("normalizes case and rejects other formats", () => { + expect(getMobileVectorizeCapability("MOBI")).toEqual({ + supported: true, + mimeType: "application/x-mobipocket-ebook", + }); + expect(getMobileVectorizeCapability("kfx")).toEqual({ supported: false, mimeType: null }); + expect(getMobileVectorizeCapability("unknown")).toEqual({ supported: false, mimeType: null }); + expect(getMobileVectorizeCapability(undefined)).toEqual({ supported: false, mimeType: null }); + }); +}); diff --git a/packages/app-expo/src/lib/rag/mobile-vectorize-capability.ts b/packages/app-expo/src/lib/rag/mobile-vectorize-capability.ts new file mode 100644 index 000000000..c5e7b1bb7 --- /dev/null +++ b/packages/app-expo/src/lib/rag/mobile-vectorize-capability.ts @@ -0,0 +1,17 @@ +const MOBILE_VECTORIZE_MIME = { + epub: "application/epub+zip", + pdf: "application/pdf", + txt: "text/plain", + umd: "application/epub+zip", + mobi: "application/x-mobipocket-ebook", + azw: "application/vnd.amazon.ebook", + azw3: "application/vnd.amazon.ebook", +} as const; + +export function getMobileVectorizeCapability(format: string | undefined) { + const normalized = String(format || "").toLowerCase() as keyof typeof MOBILE_VECTORIZE_MIME; + const mimeType = MOBILE_VECTORIZE_MIME[normalized]; + return mimeType + ? { supported: true as const, mimeType } + : { supported: false as const, mimeType: null }; +} diff --git a/packages/app-expo/src/stores/library-store.ts b/packages/app-expo/src/stores/library-store.ts index d9014c14c..0a59340a6 100644 --- a/packages/app-expo/src/stores/library-store.ts +++ b/packages/app-expo/src/stores/library-store.ts @@ -4,6 +4,7 @@ import { extractBookMetadataFromFile, } from "@/lib/book/metadata-extractor"; import { queueBook as queueAutoVectorize } from "@/lib/rag/auto-vectorize-service"; +import { getMobileVectorizeCapability } from "@/lib/rag/mobile-vectorize-capability"; import { type ImportBooksResult, createEmptyImportBooksResult, @@ -200,10 +201,6 @@ async function extractMobileImportMetadata(params: { }; } -function shouldAutoVectorizeMobile(format: Book["format"]): boolean { - return format === "epub" || format === "txt" || format === "umd"; -} - /** * Ensure raw bytes are UTF-8 encoded. Hermes (React Native) only supports * UTF-8 in TextDecoder — GBK/GB18030/Shift-JIS etc. are NOT supported. @@ -995,14 +992,15 @@ export const useLibraryStore = create((set, get) => ({ // successful import doesn't get reported as a failed import. try { const vmState = useVectorModelStore.getState(); + const vectorizeCapability = getMobileVectorizeCapability(book.format); if ( vmState.autoVectorizeOnImport && vmState.vectorModelEnabled && vmState.hasVectorCapability() && - shouldAutoVectorizeMobile("txt") + vectorizeCapability.supported ) { const base64 = bytesToBase64(conversion.epubBytes); - queueAutoVectorize(book, base64, "application/epub+zip"); + queueAutoVectorize(book, base64, vectorizeCapability.mimeType); } } catch (autoVectorizeErr) { console.warn( @@ -1116,14 +1114,15 @@ export const useLibraryStore = create((set, get) => ({ try { const vmState = useVectorModelStore.getState(); + const vectorizeCapability = getMobileVectorizeCapability(book.format); if ( vmState.autoVectorizeOnImport && vmState.vectorModelEnabled && vmState.hasVectorCapability() && - shouldAutoVectorizeMobile("umd") + vectorizeCapability.supported ) { const base64 = bytesToBase64(conversion.epubBytes); - queueAutoVectorize(book, base64, "application/epub+zip"); + queueAutoVectorize(book, base64, vectorizeCapability.mimeType); } } catch (autoVectorizeErr) { console.warn( @@ -1235,28 +1234,16 @@ export const useLibraryStore = create((set, get) => ({ // successful import doesn't get reported as a failed import. try { const vmState = useVectorModelStore.getState(); + const vectorizeCapability = getMobileVectorizeCapability(format); if ( vmState.autoVectorizeOnImport && vmState.vectorModelEnabled && vmState.hasVectorCapability() && - shouldAutoVectorizeMobile(format) + vectorizeCapability.supported ) { const sourceBytes = await platform.readFile(filePath); const base64 = bytesToBase64(sourceBytes); - const mimeTypes: Record = { - epub: "application/epub+zip", - pdf: "application/pdf", - mobi: "application/x-mobipocket-ebook", - azw: "application/vnd.amazon.ebook", - azw3: "application/vnd.amazon.ebook", - cbz: "application/vnd.comicbook+zip", - cbr: "application/vnd.comicbook+zip", - fb2: "application/x-fictionbook+xml", - fbz: "application/x-zip-compressed-fb2", - txt: "text/plain", - }; - const mimeType = mimeTypes[format] || "application/epub+zip"; - queueAutoVectorize(book, base64, mimeType); + queueAutoVectorize(book, base64, vectorizeCapability.mimeType); } else if (vmState.autoVectorizeOnImport && vmState.vectorModelEnabled) { console.warn( `[importBooks] Skip auto-vectorize for unsupported mobile import: ${fileName} (${fileSize} bytes, format=${format})`, From 6d89a54f515206988d61c263c082dc0e969c8191 Mon Sep 17 00:00:00 2001 From: Chai Date: Sun, 16 Aug 2026 20:09:14 -0400 Subject: [PATCH 02/10] fix(rag): retain MOBI extraction context --- packages/app-expo/assets/reader/reader.html | 74 +++++++++++++++---- .../assets/reader/reader.template.html | 74 +++++++++++++++---- .../src/components/rag/ExtractorWebView.tsx | 38 ++++++++-- .../src/lib/rag/auto-vectorize-service.ts | 21 +++++- .../src/lib/rag/extractor-format.test.ts | 54 ++++++++++++++ .../app-expo/src/lib/rag/extractor-format.ts | 41 ++++++++++ .../app-expo/src/screens/LibraryScreen.tsx | 2 + .../screens/library/useVectorizationQueue.ts | 7 +- 8 files changed, 271 insertions(+), 40 deletions(-) create mode 100644 packages/app-expo/src/lib/rag/extractor-format.test.ts create mode 100644 packages/app-expo/src/lib/rag/extractor-format.ts diff --git a/packages/app-expo/assets/reader/reader.html b/packages/app-expo/assets/reader/reader.html index 2463249aa..50ddcf9c3 100644 --- a/packages/app-expo/assets/reader/reader.html +++ b/packages/app-expo/assets/reader/reader.html @@ -1385,9 +1385,54 @@ } // ─── Book loading ─── + const SUPPORTED_BOOK_FORMATS = new Set(['epub', 'pdf', 'txt', 'umd', 'mobi', 'azw', 'azw3']); + const BOOK_MIME_TYPES = { + epub: 'application/epub+zip', + pdf: 'application/pdf', + txt: 'text/plain', + umd: 'application/epub+zip', + mobi: 'application/x-mobipocket-ebook', + azw: 'application/vnd.amazon.ebook', + azw3: 'application/vnd.amazon.ebook', + }; + const BOOK_FORMATS_BY_MIME = { + 'application/epub+zip': 'epub', + 'application/pdf': 'pdf', + 'text/plain': 'txt', + 'application/x-mobipocket-ebook': 'mobi', + 'application/vnd.amazon.ebook': 'azw3', + }; + + function resolveBookFormat(msg) { + const storedFormat = String(msg.bookFormat || '').trim().toLowerCase(); + if (SUPPORTED_BOOK_FORMATS.has(storedFormat)) return storedFormat; + + const cleanFileName = String(msg.fileName || '').split(/[?#]/, 1)[0]; + const extension = cleanFileName.split('.').pop().toLowerCase(); + if (SUPPORTED_BOOK_FORMATS.has(extension)) return extension; + + const mimeType = String(msg.mimeType || '').split(';', 1)[0].trim().toLowerCase(); + return BOOK_FORMATS_BY_MIME[mimeType] || null; + } + + function getBookFileName(msg) { + const format = resolveBookFormat(msg); + const cleanFileName = String(msg.fileName || '').split(/[?#]/, 1)[0].split(/[\\/]/).pop(); + if (!format) return cleanFileName || 'book.epub'; + const baseName = cleanFileName?.replace(/\.[^.]*$/, '') || 'book'; + return `${baseName}.${format}`; + } + + function getBookMimeType(msg, fallback) { + const format = resolveBookFormat(msg); + return BOOK_MIME_TYPES[format] || msg.mimeType || fallback || 'application/octet-stream'; + } + async function openBook(msg) { const container = document.getElementById('reader-container'); const loading = document.getElementById('loading'); + const fileName = getBookFileName(msg); + const mimeType = getBookMimeType(msg); currentBookIsPdf = isPDFBookMessage(msg); pdfPageLightCache = {}; pdfDocIndexMap = new WeakMap(); @@ -1412,22 +1457,22 @@ const binary = atob(msg.base64); const bytes = new Uint8Array(binary.length); for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i); - file = new File([bytes], msg.fileName || 'book.epub', { - type: msg.mimeType || 'application/epub+zip' + file = new File([bytes], fileName, { + type: mimeType }); } else if (msg.uri) { postToRN('debug', { message: `[ReaderFetch] open ${JSON.stringify({ uri: msg.uri, - fileName: msg.fileName || '', - mimeType: msg.mimeType || '' + fileName, + mimeType })}` }); // Try Range-based lazy loading for ZIP-based formats (EPUB, CBZ, FBZ) // This avoids loading the entire file into memory — only reads // the ZIP central directory (~few KB) then fetches entries on demand. - const isZipFormat = /\.(epub|cbz|fb2\.zip|fbz)$/i.test(msg.fileName || ''); + const isZipFormat = /\.(epub|cbz|fb2\.zip|fbz)$/i.test(fileName); let lazyBook = null; if (isZipFormat) { @@ -1480,14 +1525,14 @@ // Try Range-based lazy loading for PDF // pdf.js natively supports Range requests via url + disableAutoFetch - if (!lazyBook && /\.pdf$/i.test(msg.fileName || '')) { + if (!lazyBook && /\.pdf$/i.test(fileName)) { try { const headRes = await fetch(msg.uri, { method: 'HEAD' }); const acceptRanges = headRes.headers.get('Accept-Ranges'); const contentLength = parseInt(headRes.headers.get('Content-Length'), 10); if (acceptRanges === 'bytes' && contentLength > 0 && window._makePDFFromURL) { - lazyBook = await window._makePDFFromURL(msg.uri, msg.fileName || 'book.pdf'); + lazyBook = await window._makePDFFromURL(msg.uri, fileName); } } catch (pdfLazyErr) { console.warn('[Reader] PDF lazy loading failed, falling back to full fetch:', pdfLazyErr); @@ -1500,8 +1545,8 @@ const isLocalFileStatus = res.status === 0 && /^file:\/\//i.test(msg.uri); if (!res.ok && !isLocalFileStatus) throw new Error(`Failed to fetch: ${res.status}`); const blob = await res.blob(); - file = new File([blob], msg.fileName || 'book.epub', { - type: msg.mimeType || blob.type || 'application/octet-stream' + file = new File([blob], fileName, { + type: getBookMimeType(msg, blob.type) }); } else { // Skip makeBook below — we already have the book object @@ -4199,8 +4244,7 @@ // ─── Chapter Extraction for Vectorization ─── function isPDFBookMessage(msg) { - const mimeType = (msg.mimeType || '').split(';')[0].trim().toLowerCase(); - return mimeType === 'application/pdf' || /\.pdf$/i.test(msg.fileName || ''); + return resolveBookFormat(msg) === 'pdf'; } async function createBookFileFromMessage(msg) { @@ -4208,8 +4252,8 @@ const binary = atob(msg.base64); const bytes = new Uint8Array(binary.length); for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i); - return new File([bytes], msg.fileName || 'book.epub', { - type: msg.mimeType || 'application/epub+zip' + return new File([bytes], getBookFileName(msg), { + type: getBookMimeType(msg) }); } @@ -4218,8 +4262,8 @@ const isLocalFileStatus = res.status === 0 && /^file:\/\//i.test(msg.uri); if (!res.ok && !isLocalFileStatus) throw new Error(`Failed to fetch: ${res.status}`); const blob = await res.blob(); - return new File([blob], msg.fileName || 'book.epub', { - type: msg.mimeType || blob.type || 'application/octet-stream' + return new File([blob], getBookFileName(msg), { + type: getBookMimeType(msg, blob.type) }); } diff --git a/packages/app-expo/assets/reader/reader.template.html b/packages/app-expo/assets/reader/reader.template.html index 63f3098ac..4ce4da314 100644 --- a/packages/app-expo/assets/reader/reader.template.html +++ b/packages/app-expo/assets/reader/reader.template.html @@ -1385,9 +1385,54 @@ } // ─── Book loading ─── + const SUPPORTED_BOOK_FORMATS = new Set(['epub', 'pdf', 'txt', 'umd', 'mobi', 'azw', 'azw3']); + const BOOK_MIME_TYPES = { + epub: 'application/epub+zip', + pdf: 'application/pdf', + txt: 'text/plain', + umd: 'application/epub+zip', + mobi: 'application/x-mobipocket-ebook', + azw: 'application/vnd.amazon.ebook', + azw3: 'application/vnd.amazon.ebook', + }; + const BOOK_FORMATS_BY_MIME = { + 'application/epub+zip': 'epub', + 'application/pdf': 'pdf', + 'text/plain': 'txt', + 'application/x-mobipocket-ebook': 'mobi', + 'application/vnd.amazon.ebook': 'azw3', + }; + + function resolveBookFormat(msg) { + const storedFormat = String(msg.bookFormat || '').trim().toLowerCase(); + if (SUPPORTED_BOOK_FORMATS.has(storedFormat)) return storedFormat; + + const cleanFileName = String(msg.fileName || '').split(/[?#]/, 1)[0]; + const extension = cleanFileName.split('.').pop().toLowerCase(); + if (SUPPORTED_BOOK_FORMATS.has(extension)) return extension; + + const mimeType = String(msg.mimeType || '').split(';', 1)[0].trim().toLowerCase(); + return BOOK_FORMATS_BY_MIME[mimeType] || null; + } + + function getBookFileName(msg) { + const format = resolveBookFormat(msg); + const cleanFileName = String(msg.fileName || '').split(/[?#]/, 1)[0].split(/[\\/]/).pop(); + if (!format) return cleanFileName || 'book.epub'; + const baseName = cleanFileName?.replace(/\.[^.]*$/, '') || 'book'; + return `${baseName}.${format}`; + } + + function getBookMimeType(msg, fallback) { + const format = resolveBookFormat(msg); + return BOOK_MIME_TYPES[format] || msg.mimeType || fallback || 'application/octet-stream'; + } + async function openBook(msg) { const container = document.getElementById('reader-container'); const loading = document.getElementById('loading'); + const fileName = getBookFileName(msg); + const mimeType = getBookMimeType(msg); currentBookIsPdf = isPDFBookMessage(msg); pdfPageLightCache = {}; pdfDocIndexMap = new WeakMap(); @@ -1412,22 +1457,22 @@ const binary = atob(msg.base64); const bytes = new Uint8Array(binary.length); for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i); - file = new File([bytes], msg.fileName || 'book.epub', { - type: msg.mimeType || 'application/epub+zip' + file = new File([bytes], fileName, { + type: mimeType }); } else if (msg.uri) { postToRN('debug', { message: `[ReaderFetch] open ${JSON.stringify({ uri: msg.uri, - fileName: msg.fileName || '', - mimeType: msg.mimeType || '' + fileName, + mimeType })}` }); // Try Range-based lazy loading for ZIP-based formats (EPUB, CBZ, FBZ) // This avoids loading the entire file into memory — only reads // the ZIP central directory (~few KB) then fetches entries on demand. - const isZipFormat = /\.(epub|cbz|fb2\.zip|fbz)$/i.test(msg.fileName || ''); + const isZipFormat = /\.(epub|cbz|fb2\.zip|fbz)$/i.test(fileName); let lazyBook = null; if (isZipFormat) { @@ -1480,14 +1525,14 @@ // Try Range-based lazy loading for PDF // pdf.js natively supports Range requests via url + disableAutoFetch - if (!lazyBook && /\.pdf$/i.test(msg.fileName || '')) { + if (!lazyBook && /\.pdf$/i.test(fileName)) { try { const headRes = await fetch(msg.uri, { method: 'HEAD' }); const acceptRanges = headRes.headers.get('Accept-Ranges'); const contentLength = parseInt(headRes.headers.get('Content-Length'), 10); if (acceptRanges === 'bytes' && contentLength > 0 && window._makePDFFromURL) { - lazyBook = await window._makePDFFromURL(msg.uri, msg.fileName || 'book.pdf'); + lazyBook = await window._makePDFFromURL(msg.uri, fileName); } } catch (pdfLazyErr) { console.warn('[Reader] PDF lazy loading failed, falling back to full fetch:', pdfLazyErr); @@ -1500,8 +1545,8 @@ const isLocalFileStatus = res.status === 0 && /^file:\/\//i.test(msg.uri); if (!res.ok && !isLocalFileStatus) throw new Error(`Failed to fetch: ${res.status}`); const blob = await res.blob(); - file = new File([blob], msg.fileName || 'book.epub', { - type: msg.mimeType || blob.type || 'application/octet-stream' + file = new File([blob], fileName, { + type: getBookMimeType(msg, blob.type) }); } else { // Skip makeBook below — we already have the book object @@ -4199,8 +4244,7 @@ // ─── Chapter Extraction for Vectorization ─── function isPDFBookMessage(msg) { - const mimeType = (msg.mimeType || '').split(';')[0].trim().toLowerCase(); - return mimeType === 'application/pdf' || /\.pdf$/i.test(msg.fileName || ''); + return resolveBookFormat(msg) === 'pdf'; } async function createBookFileFromMessage(msg) { @@ -4208,8 +4252,8 @@ const binary = atob(msg.base64); const bytes = new Uint8Array(binary.length); for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i); - return new File([bytes], msg.fileName || 'book.epub', { - type: msg.mimeType || 'application/epub+zip' + return new File([bytes], getBookFileName(msg), { + type: getBookMimeType(msg) }); } @@ -4218,8 +4262,8 @@ const isLocalFileStatus = res.status === 0 && /^file:\/\//i.test(msg.uri); if (!res.ok && !isLocalFileStatus) throw new Error(`Failed to fetch: ${res.status}`); const blob = await res.blob(); - return new File([blob], msg.fileName || 'book.epub', { - type: msg.mimeType || blob.type || 'application/octet-stream' + return new File([blob], getBookFileName(msg), { + type: getBookMimeType(msg, blob.type) }); } diff --git a/packages/app-expo/src/components/rag/ExtractorWebView.tsx b/packages/app-expo/src/components/rag/ExtractorWebView.tsx index 2609a7b01..fdabda99a 100644 --- a/packages/app-expo/src/components/rag/ExtractorWebView.tsx +++ b/packages/app-expo/src/components/rag/ExtractorWebView.tsx @@ -1,8 +1,10 @@ import type { ChapterData } from "@readany/core/rag"; +import type { Book } from "@readany/core/types"; import { Asset } from "expo-asset"; import { forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from "react"; import { StyleSheet, View } from "react-native"; import { WebView } from "react-native-webview"; +import { resolveExtractorFormat } from "../../lib/rag/extractor-format"; const READER_HTML_ASSET = Asset.fromModule(require("../../../assets/reader/reader.html")); const EXTRACTION_TIMEOUT_MS = 45_000; @@ -18,7 +20,18 @@ const EXTRACTOR_EXTENSIONS_BY_MIME: Record = { "text/plain": "txt", }; -function getExtractorFileName(mimeType: string) { +function getExtractorFileName( + mimeType: string, + bookFormat: Book["format"] | null, + fileName?: string, +) { + const cleanFileName = fileName?.split(/[?#]/, 1)[0]?.split(/[\\/]/).pop(); + if (bookFormat) { + const baseName = cleanFileName?.replace(/\.[^.]*$/, "") || "book"; + return `${baseName}.${bookFormat}`; + } + if (cleanFileName) return cleanFileName; + const normalized = mimeType.split(";")[0]?.trim().toLowerCase() || "application/epub+zip"; return `book.${EXTRACTOR_EXTENSIONS_BY_MIME[normalized] || "epub"}`; } @@ -28,7 +41,12 @@ function isPDFMimeType(mimeType: string) { } export interface ExtractorRef { - extractChapters: (base64BookData: string, mimeType?: string) => Promise; + extractChapters: ( + base64BookData: string, + mimeType?: string, + bookFormat?: Book["format"], + fileName?: string, + ) => Promise; } interface PendingExtraction { @@ -111,7 +129,12 @@ export const ExtractorWebView = forwardRef((_, ref) => { }, []); useImperativeHandle(ref, () => ({ - extractChapters: (base64BookData: string, mimeType = "application/epub+zip") => { + extractChapters: ( + base64BookData: string, + mimeType = "application/epub+zip", + bookFormat?: Book["format"], + fileName?: string, + ) => { return new Promise((resolve, reject) => { if (!ready || !webViewRef.current) { return reject(new Error("Extractor WebView not ready")); @@ -127,11 +150,16 @@ export const ExtractorWebView = forwardRef((_, ref) => { // Command the webview to open the book first. // It will reply with "loaded" when it finishes rendering. + const resolvedFormat = resolveExtractorFormat({ bookFormat, mimeType, fileName }); const cmd = { - type: isPDFMimeType(mimeType) ? "extractBookChapters" : "openBook", + type: + resolvedFormat === "pdf" || isPDFMimeType(mimeType) + ? "extractBookChapters" + : "openBook", base64: base64BookData, mimeType, - fileName: getExtractorFileName(mimeType), + bookFormat: resolvedFormat, + fileName: getExtractorFileName(mimeType, resolvedFormat, fileName), }; webViewRef.current.injectJavaScript(` diff --git a/packages/app-expo/src/lib/rag/auto-vectorize-service.ts b/packages/app-expo/src/lib/rag/auto-vectorize-service.ts index 4d27ec229..27ac0f74e 100644 --- a/packages/app-expo/src/lib/rag/auto-vectorize-service.ts +++ b/packages/app-expo/src/lib/rag/auto-vectorize-service.ts @@ -1,10 +1,18 @@ import type { ChapterData } from "@readany/core/rag"; import type { Book } from "@readany/core/types"; -export type AutoVectorizeCallback = (bookId: string, progress: { status: string; progress: number }) => void; +export type AutoVectorizeCallback = ( + bookId: string, + progress: { status: string; progress: number }, +) => void; interface ExtractorRef { - extractChapters: (base64BookData: string, mimeType?: string) => Promise; + extractChapters: ( + base64BookData: string, + mimeType?: string, + bookFormat?: Book["format"], + fileName?: string, + ) => Promise; } interface QueueItem { @@ -15,7 +23,7 @@ interface QueueItem { let extractorRef: ExtractorRef | null = null; let callback: AutoVectorizeCallback | null = null; -let queue: QueueItem[] = []; +const queue: QueueItem[] = []; let processing = false; export function setExtractorRef(ref: ExtractorRef | null) { @@ -61,7 +69,12 @@ async function processQueue() { continue; } - const chapters = await extractorRef.extractChapters(base64Data, mimeType); + const chapters = await extractorRef.extractChapters( + base64Data, + mimeType, + book.format, + book.filePath, + ); if (!chapters || chapters.length === 0) { console.warn(`[AutoVectorize] No chapters for ${book.meta.title}`); continue; diff --git a/packages/app-expo/src/lib/rag/extractor-format.test.ts b/packages/app-expo/src/lib/rag/extractor-format.test.ts new file mode 100644 index 000000000..dad14f8c5 --- /dev/null +++ b/packages/app-expo/src/lib/rag/extractor-format.test.ts @@ -0,0 +1,54 @@ +import { describe, expect, it } from "vitest"; +import { resolveExtractorFormat } from "./extractor-format"; + +describe("resolveExtractorFormat", () => { + it.each(["epub", "pdf", "txt", "umd", "mobi", "azw", "azw3"])( + "prefers the supported stored %s format", + (bookFormat) => { + expect( + resolveExtractorFormat({ + bookFormat, + mimeType: "application/octet-stream", + fileName: "misleading.epub", + }), + ).toBe(bookFormat); + }, + ); + + it("uses a supported filename extension when the stored format is unavailable", () => { + expect( + resolveExtractorFormat({ + bookFormat: undefined, + mimeType: "application/vnd.amazon.ebook", + fileName: "x.AZW3", + }), + ).toBe("azw3"); + }); + + it.each([ + ["application/epub+zip", "epub"], + ["application/pdf", "pdf"], + ["text/plain; charset=utf-8", "txt"], + ["application/x-mobipocket-ebook", "mobi"], + ["application/vnd.amazon.ebook", "azw3"], + ])("falls back from %s to %s", (mimeType, format) => { + expect(resolveExtractorFormat({ mimeType })).toBe(format); + }); + + it("normalizes stored format and ignores query text after the filename extension", () => { + expect(resolveExtractorFormat({ bookFormat: "MOBI" })).toBe("mobi"); + expect(resolveExtractorFormat({ fileName: "download.AZW?token=1" })).toBe("azw"); + }); + + it("rejects KFX and unknown signals", () => { + expect( + resolveExtractorFormat({ + bookFormat: "kfx", + mimeType: "application/octet-stream", + fileName: "x.kfx", + }), + ).toBeNull(); + expect(resolveExtractorFormat({ bookFormat: "unknown" })).toBeNull(); + expect(resolveExtractorFormat({})).toBeNull(); + }); +}); diff --git a/packages/app-expo/src/lib/rag/extractor-format.ts b/packages/app-expo/src/lib/rag/extractor-format.ts new file mode 100644 index 000000000..e82f0c435 --- /dev/null +++ b/packages/app-expo/src/lib/rag/extractor-format.ts @@ -0,0 +1,41 @@ +import type { Book } from "@readany/core/types"; + +const SUPPORTED_FORMATS = new Set([ + "epub", + "pdf", + "txt", + "umd", + "mobi", + "azw", + "azw3", +]); + +const FORMAT_BY_MIME_TYPE: Partial> = { + "application/epub+zip": "epub", + "application/pdf": "pdf", + "application/vnd.amazon.ebook": "azw3", + "application/x-mobipocket-ebook": "mobi", + "text/plain": "txt", +}; + +function asSupportedFormat(value: string | undefined): Book["format"] | null { + const normalized = value?.trim().toLowerCase() as Book["format"] | undefined; + return normalized && SUPPORTED_FORMATS.has(normalized) ? normalized : null; +} + +export function resolveExtractorFormat(input: { + bookFormat?: string; + mimeType?: string; + fileName?: string; +}): Book["format"] | null { + const storedFormat = asSupportedFormat(input.bookFormat); + if (storedFormat) return storedFormat; + + const cleanFileName = input.fileName?.split(/[?#]/, 1)[0]; + const extension = cleanFileName?.split(".").pop(); + const fileFormat = asSupportedFormat(extension); + if (fileFormat) return fileFormat; + + const normalizedMimeType = input.mimeType?.split(";", 1)[0]?.trim().toLowerCase(); + return normalizedMimeType ? FORMAT_BY_MIME_TYPE[normalizedMimeType] || null : null; +} diff --git a/packages/app-expo/src/screens/LibraryScreen.tsx b/packages/app-expo/src/screens/LibraryScreen.tsx index d5f3a1fb6..e4aa9c6a4 100644 --- a/packages/app-expo/src/screens/LibraryScreen.tsx +++ b/packages/app-expo/src/screens/LibraryScreen.tsx @@ -294,6 +294,8 @@ export function LibraryScreen() { return extractorRef.current.extractChapters( bytesToBase64(bytes), mimeTypes[String(book.format || "").toLowerCase()] || "application/epub+zip", + book.format, + filePath, ); }, }); diff --git a/packages/app-expo/src/screens/library/useVectorizationQueue.ts b/packages/app-expo/src/screens/library/useVectorizationQueue.ts index fa280e992..8f4dd14ff 100644 --- a/packages/app-expo/src/screens/library/useVectorizationQueue.ts +++ b/packages/app-expo/src/screens/library/useVectorizationQueue.ts @@ -51,7 +51,12 @@ export function useVectorizationQueue({ extractorRef, nav }: UseVectorizationQue encoding: FileSystem.EncodingType.Base64, }); - const chapters = await extractorRef.current.extractChapters(base64, info.mimeType); + const chapters = await extractorRef.current.extractChapters( + base64, + info.mimeType, + book.format, + info.absPath, + ); if (!chapters || chapters.length === 0) { throw new Error("No chapters extracted from book"); } From c9a89547b9c828d092a604c949aa9ac9c1afee21 Mon Sep 17 00:00:00 2001 From: Chai Date: Sun, 16 Aug 2026 20:17:28 -0400 Subject: [PATCH 03/10] fix(rag): honor extractor format precedence --- .../src/components/rag/ExtractorWebView.tsx | 48 +++---------------- .../src/lib/rag/extractor-format.test.ts | 29 ++++++++++- .../app-expo/src/lib/rag/extractor-format.ts | 43 +++++++++++++++++ .../rag/mobile-vectorize-capability.test.ts | 11 ++++- .../lib/rag/mobile-vectorize-capability.ts | 3 ++ .../screens/library/useVectorizationQueue.ts | 6 +-- 6 files changed, 92 insertions(+), 48 deletions(-) diff --git a/packages/app-expo/src/components/rag/ExtractorWebView.tsx b/packages/app-expo/src/components/rag/ExtractorWebView.tsx index fdabda99a..cf5c39890 100644 --- a/packages/app-expo/src/components/rag/ExtractorWebView.tsx +++ b/packages/app-expo/src/components/rag/ExtractorWebView.tsx @@ -4,42 +4,11 @@ import { Asset } from "expo-asset"; import { forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from "react"; import { StyleSheet, View } from "react-native"; import { WebView } from "react-native-webview"; -import { resolveExtractorFormat } from "../../lib/rag/extractor-format"; +import { createExtractorCommand } from "../../lib/rag/extractor-format"; const READER_HTML_ASSET = Asset.fromModule(require("../../../assets/reader/reader.html")); const EXTRACTION_TIMEOUT_MS = 45_000; -const EXTRACTOR_EXTENSIONS_BY_MIME: Record = { - "application/epub+zip": "epub", - "application/pdf": "pdf", - "application/x-mobipocket-ebook": "mobi", - "application/vnd.amazon.ebook": "azw3", - "application/vnd.comicbook+zip": "cbz", - "application/x-fictionbook+xml": "fb2", - "application/x-zip-compressed-fb2": "fbz", - "text/plain": "txt", -}; - -function getExtractorFileName( - mimeType: string, - bookFormat: Book["format"] | null, - fileName?: string, -) { - const cleanFileName = fileName?.split(/[?#]/, 1)[0]?.split(/[\\/]/).pop(); - if (bookFormat) { - const baseName = cleanFileName?.replace(/\.[^.]*$/, "") || "book"; - return `${baseName}.${bookFormat}`; - } - if (cleanFileName) return cleanFileName; - - const normalized = mimeType.split(";")[0]?.trim().toLowerCase() || "application/epub+zip"; - return `book.${EXTRACTOR_EXTENSIONS_BY_MIME[normalized] || "epub"}`; -} - -function isPDFMimeType(mimeType: string) { - return mimeType.split(";")[0]?.trim().toLowerCase() === "application/pdf"; -} - export interface ExtractorRef { extractChapters: ( base64BookData: string, @@ -150,17 +119,12 @@ export const ExtractorWebView = forwardRef((_, ref) => { // Command the webview to open the book first. // It will reply with "loaded" when it finishes rendering. - const resolvedFormat = resolveExtractorFormat({ bookFormat, mimeType, fileName }); - const cmd = { - type: - resolvedFormat === "pdf" || isPDFMimeType(mimeType) - ? "extractBookChapters" - : "openBook", - base64: base64BookData, + const cmd = createExtractorCommand({ + base64BookData, mimeType, - bookFormat: resolvedFormat, - fileName: getExtractorFileName(mimeType, resolvedFormat, fileName), - }; + bookFormat, + fileName, + }); webViewRef.current.injectJavaScript(` window.postMessage(${JSON.stringify(JSON.stringify(cmd))}, "*"); diff --git a/packages/app-expo/src/lib/rag/extractor-format.test.ts b/packages/app-expo/src/lib/rag/extractor-format.test.ts index dad14f8c5..f6934e5de 100644 --- a/packages/app-expo/src/lib/rag/extractor-format.test.ts +++ b/packages/app-expo/src/lib/rag/extractor-format.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; -import { resolveExtractorFormat } from "./extractor-format"; +import { createExtractorCommand, resolveExtractorFormat } from "./extractor-format"; describe("resolveExtractorFormat", () => { it.each(["epub", "pdf", "txt", "umd", "mobi", "azw", "azw3"])( @@ -52,3 +52,30 @@ describe("resolveExtractorFormat", () => { expect(resolveExtractorFormat({})).toBeNull(); }); }); + +describe("createExtractorCommand", () => { + it.each([ + [ + { bookFormat: "mobi", mimeType: "application/pdf", fileName: "stored.pdf" }, + { type: "openBook", bookFormat: "mobi", fileName: "stored.mobi" }, + ], + [ + { mimeType: "application/pdf", fileName: "filename.mobi" }, + { type: "openBook", bookFormat: "mobi", fileName: "filename.mobi" }, + ], + [ + { + bookFormat: "pdf", + mimeType: "application/x-mobipocket-ebook", + fileName: "stored.mobi", + }, + { type: "extractBookChapters", bookFormat: "pdf", fileName: "stored.pdf" }, + ], + [ + { mimeType: "application/x-mobipocket-ebook", fileName: "filename.pdf" }, + { type: "extractBookChapters", bookFormat: "pdf", fileName: "filename.pdf" }, + ], + ])("dispatches from resolved format for %#", (input, expected) => { + expect(createExtractorCommand({ base64BookData: "data", ...input })).toMatchObject(expected); + }); +}); diff --git a/packages/app-expo/src/lib/rag/extractor-format.ts b/packages/app-expo/src/lib/rag/extractor-format.ts index e82f0c435..19a882a11 100644 --- a/packages/app-expo/src/lib/rag/extractor-format.ts +++ b/packages/app-expo/src/lib/rag/extractor-format.ts @@ -1,5 +1,16 @@ import type { Book } from "@readany/core/types"; +const EXTRACTOR_EXTENSIONS_BY_MIME: Record = { + "application/epub+zip": "epub", + "application/pdf": "pdf", + "application/x-mobipocket-ebook": "mobi", + "application/vnd.amazon.ebook": "azw3", + "application/vnd.comicbook+zip": "cbz", + "application/x-fictionbook+xml": "fb2", + "application/x-zip-compressed-fb2": "fbz", + "text/plain": "txt", +}; + const SUPPORTED_FORMATS = new Set([ "epub", "pdf", @@ -39,3 +50,35 @@ export function resolveExtractorFormat(input: { const normalizedMimeType = input.mimeType?.split(";", 1)[0]?.trim().toLowerCase(); return normalizedMimeType ? FORMAT_BY_MIME_TYPE[normalizedMimeType] || null : null; } + +function getExtractorFileName( + mimeType: string, + bookFormat: Book["format"] | null, + fileName?: string, +) { + const cleanFileName = fileName?.split(/[?#]/, 1)[0]?.split(/[\\/]/).pop(); + if (bookFormat) { + const baseName = cleanFileName?.replace(/\.[^.]*$/, "") || "book"; + return `${baseName}.${bookFormat}`; + } + if (cleanFileName) return cleanFileName; + + const normalizedMimeType = mimeType.split(";")[0]?.trim().toLowerCase() || "application/epub+zip"; + return `book.${EXTRACTOR_EXTENSIONS_BY_MIME[normalizedMimeType] || "epub"}`; +} + +export function createExtractorCommand(input: { + base64BookData: string; + mimeType: string; + bookFormat?: string; + fileName?: string; +}) { + const resolvedFormat = resolveExtractorFormat(input); + return { + type: resolvedFormat === "pdf" ? "extractBookChapters" : "openBook", + base64: input.base64BookData, + mimeType: input.mimeType, + bookFormat: resolvedFormat, + fileName: getExtractorFileName(input.mimeType, resolvedFormat, input.fileName), + }; +} diff --git a/packages/app-expo/src/lib/rag/mobile-vectorize-capability.test.ts b/packages/app-expo/src/lib/rag/mobile-vectorize-capability.test.ts index 1c123551c..b9c209f2d 100644 --- a/packages/app-expo/src/lib/rag/mobile-vectorize-capability.test.ts +++ b/packages/app-expo/src/lib/rag/mobile-vectorize-capability.test.ts @@ -1,5 +1,8 @@ import { describe, expect, it } from "vitest"; -import { getMobileVectorizeCapability } from "./mobile-vectorize-capability"; +import { + MOBILE_VECTORIZE_UNSUPPORTED_FORMAT_DESCRIPTION, + getMobileVectorizeCapability, +} from "./mobile-vectorize-capability"; describe("getMobileVectorizeCapability", () => { it.each([ @@ -23,4 +26,10 @@ describe("getMobileVectorizeCapability", () => { expect(getMobileVectorizeCapability("unknown")).toEqual({ supported: false, mimeType: null }); expect(getMobileVectorizeCapability(undefined)).toEqual({ supported: false, mimeType: null }); }); + + it("describes MOBI-family support as DRM-free", () => { + expect(MOBILE_VECTORIZE_UNSUPPORTED_FORMAT_DESCRIPTION).toBe( + "Mobile vectorization supports EPUB, PDF, TXT, UMD, and DRM-free MOBI, AZW, and AZW3 books.", + ); + }); }); diff --git a/packages/app-expo/src/lib/rag/mobile-vectorize-capability.ts b/packages/app-expo/src/lib/rag/mobile-vectorize-capability.ts index c5e7b1bb7..958a405ad 100644 --- a/packages/app-expo/src/lib/rag/mobile-vectorize-capability.ts +++ b/packages/app-expo/src/lib/rag/mobile-vectorize-capability.ts @@ -8,6 +8,9 @@ const MOBILE_VECTORIZE_MIME = { azw3: "application/vnd.amazon.ebook", } as const; +export const MOBILE_VECTORIZE_UNSUPPORTED_FORMAT_DESCRIPTION = + "Mobile vectorization supports EPUB, PDF, TXT, UMD, and DRM-free MOBI, AZW, and AZW3 books."; + export function getMobileVectorizeCapability(format: string | undefined) { const normalized = String(format || "").toLowerCase() as keyof typeof MOBILE_VECTORIZE_MIME; const mimeType = MOBILE_VECTORIZE_MIME[normalized]; diff --git a/packages/app-expo/src/screens/library/useVectorizationQueue.ts b/packages/app-expo/src/screens/library/useVectorizationQueue.ts index 8f4dd14ff..b7361ca44 100644 --- a/packages/app-expo/src/screens/library/useVectorizationQueue.ts +++ b/packages/app-expo/src/screens/library/useVectorizationQueue.ts @@ -1,5 +1,6 @@ import type { ExtractorRef } from "@/components/rag/ExtractorWebView"; import { inspectMobileBookForVectorize } from "@/lib/rag/auto-vectorize-book"; +import { MOBILE_VECTORIZE_UNSUPPORTED_FORMAT_DESCRIPTION } from "@/lib/rag/mobile-vectorize-capability"; import { triggerVectorizeBook } from "@/lib/rag/vectorize-trigger"; import type { RootStackParamList } from "@/navigation/RootNavigator"; import { useVectorModelStore } from "@/stores/vector-model-store"; @@ -115,10 +116,7 @@ export function useVectorizationQueue({ extractorRef, nav }: UseVectorizationQue if (info.reason === "unsupported-format") { Alert.alert( t("vectorize.unsupportedFormatTitle", "Unsupported format"), - t( - "vectorize.unsupportedFormatDesc", - "Mobile vectorization currently supports EPUB, PDF, TXT, and UMD books.", - ), + t("vectorize.unsupportedFormatDesc", MOBILE_VECTORIZE_UNSUPPORTED_FORMAT_DESCRIPTION), ); return; } From 5d0444c34ed3e367cd463b58b572365cb94f3155 Mon Sep 17 00:00:00 2001 From: Chai Date: Sun, 16 Aug 2026 20:32:50 -0400 Subject: [PATCH 04/10] fix(rag): explain protected MOBI books --- .../src/components/rag/ExtractorWebView.tsx | 34 ++-- .../lib/rag/auto-vectorize-service.test.ts | 81 ++++++++++ .../src/lib/rag/auto-vectorize-service.ts | 95 ++++++----- .../src/lib/rag/extractor-error.test.ts | 79 ++++++++++ .../app-expo/src/lib/rag/extractor-error.ts | 68 ++++++++ .../app-expo/src/lib/rag/vectorize-trigger.ts | 11 +- .../app-expo/src/screens/LibraryScreen.tsx | 14 +- .../screens/library/useVectorizationQueue.ts | 31 +++- .../i18n/library-vectorize-locales.test.ts | 37 +++++ .../core/src/i18n/locales/en/library.json | 12 ++ .../core/src/i18n/locales/es/library.json | 12 ++ .../core/src/i18n/locales/fr/library.json | 12 ++ .../core/src/i18n/locales/ja/library.json | 12 ++ .../core/src/i18n/locales/ko/library.json | 12 ++ .../core/src/i18n/locales/zh-TW/library.json | 12 ++ .../core/src/i18n/locales/zh/library.json | 12 ++ packages/core/src/rag/index.ts | 2 +- .../core/src/rag/vectorize-trigger.test.ts | 147 +++++++++++++++++- packages/core/src/rag/vectorize-trigger.ts | 53 +++++-- 19 files changed, 667 insertions(+), 69 deletions(-) create mode 100644 packages/app-expo/src/lib/rag/auto-vectorize-service.test.ts create mode 100644 packages/app-expo/src/lib/rag/extractor-error.test.ts create mode 100644 packages/app-expo/src/lib/rag/extractor-error.ts create mode 100644 packages/core/src/i18n/library-vectorize-locales.test.ts diff --git a/packages/app-expo/src/components/rag/ExtractorWebView.tsx b/packages/app-expo/src/components/rag/ExtractorWebView.tsx index cf5c39890..d3fe7cb67 100644 --- a/packages/app-expo/src/components/rag/ExtractorWebView.tsx +++ b/packages/app-expo/src/components/rag/ExtractorWebView.tsx @@ -4,6 +4,7 @@ import { Asset } from "expo-asset"; import { forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from "react"; import { StyleSheet, View } from "react-native"; import { WebView } from "react-native-webview"; +import { toBookExtractionError } from "../../lib/rag/extractor-error"; import { createExtractorCommand } from "../../lib/rag/extractor-format"; const READER_HTML_ASSET = Asset.fromModule(require("../../../assets/reader/reader.html")); @@ -22,6 +23,7 @@ interface PendingExtraction { resolve: (chapters: ChapterData[]) => void; reject: (err: Error) => void; timeoutId: ReturnType; + bookFormat?: Book["format"]; } export const ExtractorWebView = forwardRef((_, ref) => { @@ -36,7 +38,9 @@ export const ExtractorWebView = forwardRef((_, ref) => { return () => { for (const pending of pendingRequests.current) { clearTimeout(pending.timeoutId); - pending.reject(new Error("Extractor WebView unmounted")); + pending.reject( + toBookExtractionError(new Error("Extractor WebView unmounted"), pending.bookFormat), + ); } pendingRequests.current = []; }; @@ -78,7 +82,7 @@ export const ExtractorWebView = forwardRef((_, ref) => { clearTimeout(pending.timeoutId); if (msg.error) { - pending.reject(new Error(msg.error)); + pending.reject(toBookExtractionError(new Error(String(msg.error)), pending.bookFormat)); } else if (msg.chapters) { pending.resolve(msg.chapters); } @@ -89,7 +93,7 @@ export const ExtractorWebView = forwardRef((_, ref) => { const pending = pendingRequests.current.shift(); if (pending) { clearTimeout(pending.timeoutId); - pending.reject(new Error(msg.message)); + pending.reject(toBookExtractionError(new Error(String(msg.message)), pending.bookFormat)); } } } catch (err) { @@ -106,16 +110,19 @@ export const ExtractorWebView = forwardRef((_, ref) => { ) => { return new Promise((resolve, reject) => { if (!ready || !webViewRef.current) { - return reject(new Error("Extractor WebView not ready")); + return reject( + toBookExtractionError(new Error("Extractor WebView not ready"), bookFormat), + ); } const timeoutId = setTimeout(() => { const index = pendingRequests.current.findIndex((pending) => pending.reject === reject); if (index >= 0) pendingRequests.current.splice(index, 1); - reject(new Error("Timed out extracting book content")); + reject(toBookExtractionError(new Error("Timed out extracting book content"), bookFormat)); }, EXTRACTION_TIMEOUT_MS); - pendingRequests.current.push({ resolve, reject, timeoutId }); + const pendingRequest = { resolve, reject, timeoutId, bookFormat }; + pendingRequests.current.push(pendingRequest); // Command the webview to open the book first. // It will reply with "loaded" when it finishes rendering. @@ -126,10 +133,17 @@ export const ExtractorWebView = forwardRef((_, ref) => { fileName, }); - webViewRef.current.injectJavaScript(` - window.postMessage(${JSON.stringify(JSON.stringify(cmd))}, "*"); - true; - `); + try { + webViewRef.current.injectJavaScript(` + window.postMessage(${JSON.stringify(JSON.stringify(cmd))}, "*"); + true; + `); + } catch (error) { + clearTimeout(timeoutId); + const index = pendingRequests.current.indexOf(pendingRequest); + if (index >= 0) pendingRequests.current.splice(index, 1); + reject(toBookExtractionError(error, bookFormat)); + } }); }, })); diff --git a/packages/app-expo/src/lib/rag/auto-vectorize-service.test.ts b/packages/app-expo/src/lib/rag/auto-vectorize-service.test.ts new file mode 100644 index 000000000..32b28834e --- /dev/null +++ b/packages/app-expo/src/lib/rag/auto-vectorize-service.test.ts @@ -0,0 +1,81 @@ +import type { Book } from "@readany/core/types"; +import { afterEach, describe, expect, it, vi } from "vitest"; +import { BookExtractionError } from "./extractor-error"; + +const vectorizeMocks = vi.hoisted(() => ({ + resetBookVectorization: vi.fn(), + triggerVectorizeBook: vi.fn(), +})); + +vi.mock("./vectorize-trigger", () => vectorizeMocks); + +import { isProcessing, queueBook, setCallback, setExtractorRef } from "./auto-vectorize-service"; + +const book: Book = { + id: "book-1", + filePath: "books/protected.mobi", + format: "mobi", + meta: { title: "Protected book", author: "Author" }, + addedAt: 1, + updatedAt: 1, + progress: 0, + isVectorized: true, + vectorizeProgress: 1, + tags: [], + syncStatus: "local", +}; + +afterEach(() => { + setExtractorRef(null); + setCallback(null); + vi.clearAllMocks(); +}); + +describe("automatic vectorization failure lifecycle", () => { + it("cleans the book before publishing a classified extraction error", async () => { + const events: string[] = []; + vectorizeMocks.resetBookVectorization.mockImplementation(async () => { + events.push("cleanup"); + }); + setExtractorRef({ + extractChapters: vi + .fn() + .mockRejectedValue(new BookExtractionError("Encrypted MOBI records", "mobi")), + }); + + const errorPublished = new Promise((resolve) => { + setCallback((_bookId, progress) => { + if (progress.status === "error") { + events.push(`error:${progress.errorCategory}`); + resolve(); + } + }); + }); + + await queueBook(book, "base64", "application/x-mobipocket-ebook"); + await errorPublished; + + expect(events).toEqual(["cleanup", "error:drm-protected"]); + expect(vectorizeMocks.triggerVectorizeBook).not.toHaveBeenCalled(); + }); + + it("publishes the failure and releases the queue if cleanup itself rejects", async () => { + vectorizeMocks.resetBookVectorization.mockRejectedValueOnce(new Error("cleanup failed")); + setExtractorRef({ + extractChapters: vi.fn().mockRejectedValue(new BookExtractionError("loader failed", "mobi")), + }); + const callback = vi.fn(); + setCallback(callback); + + await queueBook(book, "base64", "application/x-mobipocket-ebook"); + + await vi.waitFor(() => { + expect(callback).toHaveBeenCalledWith("book-1", { + status: "error", + progress: 0, + errorCategory: "unknown", + }); + expect(isProcessing()).toBe(false); + }); + }); +}); diff --git a/packages/app-expo/src/lib/rag/auto-vectorize-service.ts b/packages/app-expo/src/lib/rag/auto-vectorize-service.ts index 27ac0f74e..efac4cf7e 100644 --- a/packages/app-expo/src/lib/rag/auto-vectorize-service.ts +++ b/packages/app-expo/src/lib/rag/auto-vectorize-service.ts @@ -1,9 +1,14 @@ import type { ChapterData } from "@readany/core/rag"; import type { Book } from "@readany/core/types"; +import { + BookExtractionError, + type BookExtractionErrorCategory, + toBookExtractionError, +} from "./extractor-error"; export type AutoVectorizeCallback = ( bookId: string, - progress: { status: string; progress: number }, + progress: { status: string; progress: number; errorCategory?: BookExtractionErrorCategory }, ) => void; interface ExtractorRef { @@ -53,46 +58,56 @@ async function processQueue() { if (processing) return; processing = true; - const { triggerVectorizeBook } = await import("./vectorize-trigger"); - - while (queue.length > 0) { - const item = queue.shift(); - if (!item) break; - - const { book, base64Data, mimeType } = item; - - try { - callback?.(book.id, { status: "extracting", progress: 0 }); - - if (!extractorRef) { - console.warn("[AutoVectorize] Extractor not ready, skipping"); - continue; - } - - const chapters = await extractorRef.extractChapters( - base64Data, - mimeType, - book.format, - book.filePath, - ); - if (!chapters || chapters.length === 0) { - console.warn(`[AutoVectorize] No chapters for ${book.meta.title}`); - continue; + try { + const { resetBookVectorization, triggerVectorizeBook } = await import("./vectorize-trigger"); + + while (queue.length > 0) { + const item = queue.shift(); + if (!item) break; + + const { book, base64Data, mimeType } = item; + + try { + callback?.(book.id, { status: "extracting", progress: 0 }); + + if (!extractorRef) { + throw toBookExtractionError(new Error("Extractor WebView not ready"), book.format); + } + + const chapters = await extractorRef.extractChapters( + base64Data, + mimeType, + book.format, + book.filePath, + ); + if (!chapters || chapters.length === 0) { + throw toBookExtractionError(new Error("No chapters extracted from book"), book.format); + } + + callback?.(book.id, { status: "vectorizing", progress: 0 }); + + await triggerVectorizeBook(book.id, book.filePath, chapters, (progress) => { + const pct = + progress.totalChunks > 0 ? progress.processedChunks / progress.totalChunks : 0; + callback?.(book.id, { status: "vectorizing", progress: pct }); + }); + + callback?.(book.id, { status: "completed", progress: 1 }); + } catch (err) { + console.error(`[AutoVectorize] Failed for ${book.meta.title}:`, err); + try { + await resetBookVectorization(book.id); + } catch (cleanupError) { + console.error(`[AutoVectorize] Failed to clean up ${book.meta.title}:`, cleanupError); + } + callback?.(book.id, { + status: "error", + progress: 0, + errorCategory: err instanceof BookExtractionError ? err.category : undefined, + }); } - - callback?.(book.id, { status: "vectorizing", progress: 0 }); - - await triggerVectorizeBook(book.id, book.filePath, chapters, (progress) => { - const pct = progress.totalChunks > 0 ? progress.processedChunks / progress.totalChunks : 0; - callback?.(book.id, { status: "vectorizing", progress: pct }); - }); - - callback?.(book.id, { status: "completed", progress: 1 }); - } catch (err) { - console.error(`[AutoVectorize] Failed for ${book.meta.title}:`, err); - callback?.(book.id, { status: "error", progress: 0 }); } + } finally { + processing = false; } - - processing = false; } diff --git a/packages/app-expo/src/lib/rag/extractor-error.test.ts b/packages/app-expo/src/lib/rag/extractor-error.test.ts new file mode 100644 index 000000000..d7d996d3a --- /dev/null +++ b/packages/app-expo/src/lib/rag/extractor-error.test.ts @@ -0,0 +1,79 @@ +import { describe, expect, it } from "vitest"; +import { + BookExtractionError, + classifyBookExtractionError, + getBookExtractionErrorMessageKeys, + toBookExtractionError, +} from "./extractor-error"; + +describe("classifyBookExtractionError", () => { + it.each([ + ["mobi", "Encrypted MOBI records are not supported"], + ["azw", "DRM protected content"], + ["azw3", "encryption is not supported"], + ])("classifies narrow protection evidence for %s", (format, message) => { + expect(classifyBookExtractionError(new Error(message), format)).toBe("drm-protected"); + }); + + it.each(["epub", "pdf", "kfx", undefined])( + "does not classify protection wording for non-MOBI format %s", + (format) => { + expect(classifyBookExtractionError(new Error("encrypted content"), format)).toBe("unknown"); + }, + ); + + it.each([ + "invalid PDB record offset", + "truncated MOBI header", + "invalid record structure", + "record offset is outside the file", + ])("classifies malformed structure evidence: %s", (message) => { + expect(classifyBookExtractionError(new Error(message), "mobi")).toBe("malformed"); + }); + + it("does not guess DRM from a generic MOBI parser failure", () => { + expect(classifyBookExtractionError(new Error("loader failed"), "mobi")).toBe("unknown"); + }); + + it("classifies explicit unsupported-format failures", () => { + expect(classifyBookExtractionError(new Error("unsupported format: kfx"), "kfx")).toBe( + "unsupported-format", + ); + }); + + it("handles non-Error rejections without broadening classification", () => { + expect(classifyBookExtractionError("loader failed", "azw3")).toBe("unknown"); + }); +}); + +describe("BookExtractionError", () => { + it("carries bounded parser classification across the extraction boundary", () => { + const cause = new Error("Encrypted MOBI records are not supported"); + + const error = toBookExtractionError(cause, "mobi"); + + expect(error).toBeInstanceOf(BookExtractionError); + expect(error.message).toBe(cause.message); + expect(error.category).toBe("drm-protected"); + expect(error.cause).toBe(cause); + }); + + it("keeps extraction message keys distinct for every category", () => { + expect(getBookExtractionErrorMessageKeys("drm-protected")).toEqual({ + title: "vectorize.protectedBookTitle", + description: "vectorize.protectedBookDesc", + }); + expect(getBookExtractionErrorMessageKeys("malformed")).toEqual({ + title: "vectorize.malformedBookTitle", + description: "vectorize.malformedBookDesc", + }); + expect(getBookExtractionErrorMessageKeys("unsupported-format")).toEqual({ + title: "vectorize.unsupportedFormatTitle", + description: "vectorize.unsupportedFormatDesc", + }); + expect(getBookExtractionErrorMessageKeys("unknown")).toEqual({ + title: "vectorize.extractionFailedTitle", + description: "vectorize.extractionFailedDesc", + }); + }); +}); diff --git a/packages/app-expo/src/lib/rag/extractor-error.ts b/packages/app-expo/src/lib/rag/extractor-error.ts new file mode 100644 index 000000000..d1326d638 --- /dev/null +++ b/packages/app-expo/src/lib/rag/extractor-error.ts @@ -0,0 +1,68 @@ +export type BookExtractionErrorCategory = + | "drm-protected" + | "malformed" + | "unsupported-format" + | "unknown"; + +const MOBI_FAMILY = new Set(["mobi", "azw", "azw3"]); +const PROTECTION_EVIDENCE = /\b(?:encrypt(?:ed|ion)?|drm|protected)\b/i; +const MALFORMED_EVIDENCE = + /\b(?:truncat(?:ed|ion)|invalid\s+(?:(?:pdb|mobi)\s+)?record(?:\s+(?:offset|structure|header|index))?|record\s+(?:offset|structure|header|index))\b/i; +const UNSUPPORTED_FORMAT_EVIDENCE = /\bunsupported\s+(?:book\s+)?format\b/i; + +const MESSAGE_KEYS: Record = { + "drm-protected": { + title: "vectorize.protectedBookTitle", + description: "vectorize.protectedBookDesc", + }, + malformed: { + title: "vectorize.malformedBookTitle", + description: "vectorize.malformedBookDesc", + }, + "unsupported-format": { + title: "vectorize.unsupportedFormatTitle", + description: "vectorize.unsupportedFormatDesc", + }, + unknown: { + title: "vectorize.extractionFailedTitle", + description: "vectorize.extractionFailedDesc", + }, +}; + +export function classifyBookExtractionError( + error: unknown, + format: string | undefined, +): BookExtractionErrorCategory { + const message = error instanceof Error ? error.message : String(error); + const normalizedFormat = format?.trim().toLowerCase(); + + if (normalizedFormat && MOBI_FAMILY.has(normalizedFormat) && PROTECTION_EVIDENCE.test(message)) { + return "drm-protected"; + } + if (MALFORMED_EVIDENCE.test(message)) return "malformed"; + if (UNSUPPORTED_FORMAT_EVIDENCE.test(message)) return "unsupported-format"; + return "unknown"; +} + +export class BookExtractionError extends Error { + readonly category: BookExtractionErrorCategory; + override readonly cause: unknown; + + constructor(error: unknown, format: string | undefined) { + super(error instanceof Error ? error.message : String(error)); + this.name = "BookExtractionError"; + this.category = classifyBookExtractionError(error, format); + this.cause = error; + } +} + +export function toBookExtractionError( + error: unknown, + format: string | undefined, +): BookExtractionError { + return error instanceof BookExtractionError ? error : new BookExtractionError(error, format); +} + +export function getBookExtractionErrorMessageKeys(category: BookExtractionErrorCategory) { + return MESSAGE_KEYS[category]; +} diff --git a/packages/app-expo/src/lib/rag/vectorize-trigger.ts b/packages/app-expo/src/lib/rag/vectorize-trigger.ts index d9b4db8a0..ade73263b 100644 --- a/packages/app-expo/src/lib/rag/vectorize-trigger.ts +++ b/packages/app-expo/src/lib/rag/vectorize-trigger.ts @@ -1,6 +1,9 @@ import { useLibraryStore } from "@/stores/library-store"; import { useVectorModelStore } from "@/stores/vector-model-store"; -import { triggerVectorizeBook as coreTriggerVectorizeBook } from "@readany/core/rag"; +import { + resetBookVectorization as coreResetBookVectorization, + triggerVectorizeBook as coreTriggerVectorizeBook, +} from "@readany/core/rag"; import type { ChapterData, VectorizeStatusCallback, @@ -9,6 +12,12 @@ import type { export type { VectorizeStatusCallback }; +export async function resetBookVectorization(bookId: string): Promise { + await coreResetBookVectorization(bookId, { + onBookUpdate: useLibraryStore.getState().updateBook, + }); +} + export async function triggerVectorizeBook( bookId: string, _filePath: string, diff --git a/packages/app-expo/src/screens/LibraryScreen.tsx b/packages/app-expo/src/screens/LibraryScreen.tsx index e4aa9c6a4..26c8fd925 100644 --- a/packages/app-expo/src/screens/LibraryScreen.tsx +++ b/packages/app-expo/src/screens/LibraryScreen.tsx @@ -23,6 +23,7 @@ import { SyncButton } from "@/components/ui/SyncButton"; import { useResponsiveLayout } from "@/hooks/use-responsive-layout"; import { openMobileBook } from "@/lib/library/open-mobile-book"; import { setCallback, setExtractorRef } from "@/lib/rag/auto-vectorize-service"; +import { getBookExtractionErrorMessageKeys } from "@/lib/rag/extractor-error"; import type { RootStackParamList } from "@/navigation/RootNavigator"; import { WebDavConnectSheet } from "@/screens/library/WebDavConnectSheet"; import { WebDavImportSourceSheet } from "@/screens/library/WebDavImportSourceSheet"; @@ -303,13 +304,24 @@ export function LibraryScreen() { console.log( `[AutoVectorize] Book ${bookId}: ${progress.status} (${Math.round(progress.progress * 100)}%)`, ); + if (progress.status === "error") { + if (progress.errorCategory) { + const keys = getBookExtractionErrorMessageKeys(progress.errorCategory); + Alert.alert(t(keys.title), t(keys.description)); + } else { + Alert.alert( + t("vectorize.vectorizationFailedTitle"), + t("vectorize.vectorizationFailedDesc"), + ); + } + } }); return () => { setExtractorRef(null); setFallbackContentProvider(null); setCallback(null); }; - }, []); + }, [t]); useEffect(() => { return onLibraryChanged((deletedTags) => loadBooks(deletedTags)); diff --git a/packages/app-expo/src/screens/library/useVectorizationQueue.ts b/packages/app-expo/src/screens/library/useVectorizationQueue.ts index b7361ca44..583f19220 100644 --- a/packages/app-expo/src/screens/library/useVectorizationQueue.ts +++ b/packages/app-expo/src/screens/library/useVectorizationQueue.ts @@ -1,7 +1,12 @@ import type { ExtractorRef } from "@/components/rag/ExtractorWebView"; import { inspectMobileBookForVectorize } from "@/lib/rag/auto-vectorize-book"; +import { + BookExtractionError, + getBookExtractionErrorMessageKeys, + toBookExtractionError, +} from "@/lib/rag/extractor-error"; import { MOBILE_VECTORIZE_UNSUPPORTED_FORMAT_DESCRIPTION } from "@/lib/rag/mobile-vectorize-capability"; -import { triggerVectorizeBook } from "@/lib/rag/vectorize-trigger"; +import { resetBookVectorization, triggerVectorizeBook } from "@/lib/rag/vectorize-trigger"; import type { RootStackParamList } from "@/navigation/RootNavigator"; import { useVectorModelStore } from "@/stores/vector-model-store"; import type { NativeStackNavigationProp } from "@react-navigation/native-stack"; @@ -40,7 +45,7 @@ export function useVectorizationQueue({ extractorRef, nav }: UseVectorizationQue try { if (!extractorRef.current) { - throw new Error("Extractor WebView not ready"); + throw toBookExtractionError(new Error("Extractor WebView not ready"), book.format); } const info = await inspectMobileBookForVectorize(book); @@ -59,7 +64,7 @@ export function useVectorizationQueue({ extractorRef, nav }: UseVectorizationQue info.absPath, ); if (!chapters || chapters.length === 0) { - throw new Error("No chapters extracted from book"); + throw toBookExtractionError(new Error("No chapters extracted from book"), book.format); } await triggerVectorizeBook(book.id, book.filePath, chapters, (progress) => { @@ -78,16 +83,34 @@ export function useVectorizationQueue({ extractorRef, nav }: UseVectorizationQue `[useVectorizationQueue] Vectorization failed for "${book.meta.title}":`, err, ); + try { + await resetBookVectorization(book.id); + } catch (cleanupError) { + console.error( + `[useVectorizationQueue] Failed to clean up "${book.meta.title}":`, + cleanupError, + ); + } setVectorProgress({ bookId: book.id, status: "error", processedChunks: 0, totalChunks: 0, }); + + if (err instanceof BookExtractionError) { + const keys = getBookExtractionErrorMessageKeys(err.category); + Alert.alert(t(keys.title), t(keys.description)); + } else { + Alert.alert( + t("vectorize.vectorizationFailedTitle"), + t("vectorize.vectorizationFailedDesc"), + ); + } await new Promise((resolve) => setTimeout(resolve, 1500)); } }, - [extractorRef], + [extractorRef, t], ); const processQueue = useCallback(async () => { diff --git a/packages/core/src/i18n/library-vectorize-locales.test.ts b/packages/core/src/i18n/library-vectorize-locales.test.ts new file mode 100644 index 000000000..5c321a771 --- /dev/null +++ b/packages/core/src/i18n/library-vectorize-locales.test.ts @@ -0,0 +1,37 @@ +import { describe, expect, it } from "vitest"; +import en from "./locales/en/library.json"; +import es from "./locales/es/library.json"; +import fr from "./locales/fr/library.json"; +import ja from "./locales/ja/library.json"; +import ko from "./locales/ko/library.json"; +import zhTW from "./locales/zh-TW/library.json"; +import zh from "./locales/zh/library.json"; + +const locales = { en, es, fr, ja, ko, zh, "zh-TW": zhTW }; +const VECTORIZE_ERROR_KEYS = [ + "protectedBookTitle", + "protectedBookDesc", + "malformedBookTitle", + "malformedBookDesc", + "unsupportedFormatTitle", + "unsupportedFormatDesc", + "extractionFailedTitle", + "extractionFailedDesc", + "vectorizationFailedTitle", + "vectorizationFailedDesc", +] as const; + +describe("library vectorization error translations", () => { + it.each(Object.entries(locales))("defines every actionable message in %s", (_name, locale) => { + for (const key of VECTORIZE_ERROR_KEYS) { + expect(locale.vectorize[key]).toEqual(expect.any(String)); + expect(locale.vectorize[key].trim()).not.toBe(""); + } + }); + + it("states the DRM-free support boundary accurately in English", () => { + expect(en.vectorize.protectedBookDesc).toBe( + "ReadAny can vectorize DRM-free MOBI, AZW, and AZW3 books, but this file appears to be protected.", + ); + }); +}); diff --git a/packages/core/src/i18n/locales/en/library.json b/packages/core/src/i18n/locales/en/library.json index a6229cf6a..c25fa36a8 100644 --- a/packages/core/src/i18n/locales/en/library.json +++ b/packages/core/src/i18n/locales/en/library.json @@ -161,6 +161,18 @@ "detailsManagedMetadataNote": "ReadAny maintains this metadata for library display, search, and sync naming. The original book file is not modified.", "bookNotFound": "Book not found" }, + "vectorize": { + "protectedBookTitle": "Protected book", + "protectedBookDesc": "ReadAny can vectorize DRM-free MOBI, AZW, and AZW3 books, but this file appears to be protected.", + "malformedBookTitle": "Damaged or incomplete book", + "malformedBookDesc": "ReadAny couldn't read this book because its file structure appears damaged or incomplete. Try downloading or exporting a fresh DRM-free copy.", + "unsupportedFormatTitle": "Unsupported format", + "unsupportedFormatDesc": "ReadAny can vectorize EPUB, PDF, TXT, UMD, and DRM-free MOBI, AZW, and AZW3 books. This file format is not supported.", + "extractionFailedTitle": "Couldn't read book", + "extractionFailedDesc": "ReadAny couldn't extract readable text from this book. Try downloading or exporting a fresh DRM-free copy.", + "vectorizationFailedTitle": "Couldn't vectorize book", + "vectorizationFailedDesc": "ReadAny couldn't finish vectorizing this book. Check your vector model settings and try again." + }, "home": { "library": "Library", "addBook": "Add Book", diff --git a/packages/core/src/i18n/locales/es/library.json b/packages/core/src/i18n/locales/es/library.json index 5dc3c17f2..cda0b6dbf 100644 --- a/packages/core/src/i18n/locales/es/library.json +++ b/packages/core/src/i18n/locales/es/library.json @@ -148,6 +148,18 @@ "detailsManagedMetadataNote": "ReadAny mantiene estos metadatos para mostrar, buscar y nombrar en la sincronización. El archivo original del libro no se modifica.", "bookNotFound": "Libro no encontrado" }, + "vectorize": { + "protectedBookTitle": "Libro protegido", + "protectedBookDesc": "ReadAny puede vectorizar libros MOBI, AZW y AZW3 sin DRM, pero este archivo parece estar protegido.", + "malformedBookTitle": "Libro dañado o incompleto", + "malformedBookDesc": "ReadAny no pudo leer este libro porque la estructura del archivo parece dañada o incompleta. Prueba a descargar o exportar una copia nueva sin DRM.", + "unsupportedFormatTitle": "Formato no compatible", + "unsupportedFormatDesc": "ReadAny puede vectorizar EPUB, PDF, TXT, UMD y libros MOBI, AZW y AZW3 sin DRM. Este formato de archivo no es compatible.", + "extractionFailedTitle": "No se pudo leer el libro", + "extractionFailedDesc": "ReadAny no pudo extraer texto legible de este libro. Prueba a descargar o exportar una copia nueva sin DRM.", + "vectorizationFailedTitle": "No se pudo vectorizar el libro", + "vectorizationFailedDesc": "ReadAny no pudo terminar de vectorizar este libro. Comprueba la configuración del modelo vectorial e inténtalo de nuevo." + }, "home": { "library": "Biblioteca", "addBook": "Agregar libro", diff --git a/packages/core/src/i18n/locales/fr/library.json b/packages/core/src/i18n/locales/fr/library.json index fe3c20224..1b6c60f9e 100644 --- a/packages/core/src/i18n/locales/fr/library.json +++ b/packages/core/src/i18n/locales/fr/library.json @@ -148,6 +148,18 @@ "detailsManagedMetadataNote": "ReadAny conserve ces métadonnées pour l'affichage, la recherche et le nommage de synchronisation. Le fichier original du livre n'est pas modifié.", "bookNotFound": "Livre introuvable" }, + "vectorize": { + "protectedBookTitle": "Livre protégé", + "protectedBookDesc": "ReadAny peut vectoriser les livres MOBI, AZW et AZW3 sans DRM, mais ce fichier semble être protégé.", + "malformedBookTitle": "Livre endommagé ou incomplet", + "malformedBookDesc": "ReadAny n'a pas pu lire ce livre, car la structure du fichier semble endommagée ou incomplète. Essayez de télécharger ou d'exporter une nouvelle copie sans DRM.", + "unsupportedFormatTitle": "Format non pris en charge", + "unsupportedFormatDesc": "ReadAny peut vectoriser les fichiers EPUB, PDF, TXT et UMD, ainsi que les livres MOBI, AZW et AZW3 sans DRM. Ce format de fichier n'est pas pris en charge.", + "extractionFailedTitle": "Impossible de lire le livre", + "extractionFailedDesc": "ReadAny n'a pas pu extraire de texte lisible de ce livre. Essayez de télécharger ou d'exporter une nouvelle copie sans DRM.", + "vectorizationFailedTitle": "Impossible de vectoriser le livre", + "vectorizationFailedDesc": "ReadAny n'a pas pu terminer la vectorisation de ce livre. Vérifiez les paramètres du modèle vectoriel et réessayez." + }, "home": { "library": "Bibliothèque", "addBook": "Ajouter un livre", diff --git a/packages/core/src/i18n/locales/ja/library.json b/packages/core/src/i18n/locales/ja/library.json index efd242e63..f0eeefc02 100644 --- a/packages/core/src/i18n/locales/ja/library.json +++ b/packages/core/src/i18n/locales/ja/library.json @@ -148,6 +148,18 @@ "detailsManagedMetadataNote": "ReadAny はこのメタデータをライブラリ表示、検索、同期時の命名に使用します。元の書籍ファイルは変更しません。", "bookNotFound": "書籍が見つかりません" }, + "vectorize": { + "protectedBookTitle": "保護された書籍", + "protectedBookDesc": "ReadAny は DRM フリーの MOBI、AZW、AZW3 書籍をベクトル化できますが、このファイルは保護されているようです。", + "malformedBookTitle": "破損または不完全な書籍", + "malformedBookDesc": "ファイル構造が破損しているか不完全なため、この書籍を読み取れませんでした。DRM フリーの新しいコピーをダウンロードまたは書き出してください。", + "unsupportedFormatTitle": "未対応の形式", + "unsupportedFormatDesc": "ReadAny は EPUB、PDF、TXT、UMD、および DRM フリーの MOBI、AZW、AZW3 書籍をベクトル化できます。このファイル形式には対応していません。", + "extractionFailedTitle": "書籍を読み取れませんでした", + "extractionFailedDesc": "この書籍から読み取り可能なテキストを抽出できませんでした。DRM フリーの新しいコピーをダウンロードまたは書き出してください。", + "vectorizationFailedTitle": "書籍をベクトル化できませんでした", + "vectorizationFailedDesc": "この書籍のベクトル化を完了できませんでした。ベクトルモデルの設定を確認して、もう一度お試しください。" + }, "home": { "library": "ライブラリ", "addBook": "本を追加", diff --git a/packages/core/src/i18n/locales/ko/library.json b/packages/core/src/i18n/locales/ko/library.json index 5c216bddb..0f037a265 100644 --- a/packages/core/src/i18n/locales/ko/library.json +++ b/packages/core/src/i18n/locales/ko/library.json @@ -148,6 +148,18 @@ "detailsManagedMetadataNote": "ReadAny는 이 메타데이터를 서재 표시, 검색, 동기화 이름에 사용합니다. 원본 책 파일은 수정하지 않습니다.", "bookNotFound": "책을 찾을 수 없어요" }, + "vectorize": { + "protectedBookTitle": "보호된 책", + "protectedBookDesc": "ReadAny는 DRM이 없는 MOBI, AZW, AZW3 책을 벡터화할 수 있지만, 이 파일은 보호된 것으로 보입니다.", + "malformedBookTitle": "손상되었거나 불완전한 책", + "malformedBookDesc": "파일 구조가 손상되었거나 불완전하여 이 책을 읽지 못했습니다. DRM이 없는 새 사본을 다운로드하거나 내보낸 뒤 다시 시도하세요.", + "unsupportedFormatTitle": "지원하지 않는 형식", + "unsupportedFormatDesc": "ReadAny는 EPUB, PDF, TXT, UMD 및 DRM이 없는 MOBI, AZW, AZW3 책을 벡터화할 수 있습니다. 이 파일 형식은 지원하지 않습니다.", + "extractionFailedTitle": "책을 읽을 수 없음", + "extractionFailedDesc": "이 책에서 읽을 수 있는 텍스트를 추출하지 못했습니다. DRM이 없는 새 사본을 다운로드하거나 내보낸 뒤 다시 시도하세요.", + "vectorizationFailedTitle": "책을 벡터화할 수 없음", + "vectorizationFailedDesc": "이 책의 벡터화를 완료하지 못했습니다. 벡터 모델 설정을 확인하고 다시 시도하세요." + }, "home": { "library": "서재", "addBook": "책 추가", diff --git a/packages/core/src/i18n/locales/zh-TW/library.json b/packages/core/src/i18n/locales/zh-TW/library.json index 811eca7d6..644a14080 100644 --- a/packages/core/src/i18n/locales/zh-TW/library.json +++ b/packages/core/src/i18n/locales/zh-TW/library.json @@ -148,6 +148,18 @@ "detailsManagedMetadataNote": "ReadAny 會維護這份元資訊,用於書架顯示、搜尋和同步命名,不會修改原始書籍檔案。", "bookNotFound": "書籍不存在" }, + "vectorize": { + "protectedBookTitle": "受保護的書籍", + "protectedBookDesc": "ReadAny 可以向量化不含 DRM 的 MOBI、AZW 和 AZW3 書籍,但此檔案似乎受到保護。", + "malformedBookTitle": "書籍檔案損壞或不完整", + "malformedBookDesc": "ReadAny 無法讀取此書,因為檔案結構似乎已損壞或不完整。請重新下載或匯出一份不含 DRM 的副本。", + "unsupportedFormatTitle": "不支援的格式", + "unsupportedFormatDesc": "ReadAny 可以向量化 EPUB、PDF、TXT、UMD,以及不含 DRM 的 MOBI、AZW 和 AZW3 書籍。此檔案格式不受支援。", + "extractionFailedTitle": "無法讀取書籍", + "extractionFailedDesc": "ReadAny 無法從此書中擷取可讀文字。請重新下載或匯出一份不含 DRM 的副本。", + "vectorizationFailedTitle": "無法向量化書籍", + "vectorizationFailedDesc": "ReadAny 無法完成此書的向量化。請檢查向量模型設定後再試一次。" + }, "home": { "library": "書庫", "addBook": "新增書籍", diff --git a/packages/core/src/i18n/locales/zh/library.json b/packages/core/src/i18n/locales/zh/library.json index 440790e3c..ae50b24e3 100644 --- a/packages/core/src/i18n/locales/zh/library.json +++ b/packages/core/src/i18n/locales/zh/library.json @@ -161,6 +161,18 @@ "detailsManagedMetadataNote": "ReadAny 会维护这份元信息,用于书架展示、搜索和同步命名,不会修改原始书籍文件。", "bookNotFound": "书籍不存在" }, + "vectorize": { + "protectedBookTitle": "受保护的书籍", + "protectedBookDesc": "ReadAny 可以向量化不含 DRM 的 MOBI、AZW 和 AZW3 书籍,但此文件似乎受到保护。", + "malformedBookTitle": "书籍文件损坏或不完整", + "malformedBookDesc": "ReadAny 无法读取此书,因为文件结构似乎已损坏或不完整。请重新下载或导出一份不含 DRM 的副本。", + "unsupportedFormatTitle": "不支持的格式", + "unsupportedFormatDesc": "ReadAny 可以向量化 EPUB、PDF、TXT、UMD,以及不含 DRM 的 MOBI、AZW 和 AZW3 书籍。此文件格式不受支持。", + "extractionFailedTitle": "无法读取书籍", + "extractionFailedDesc": "ReadAny 无法从此书中提取可读文本。请重新下载或导出一份不含 DRM 的副本。", + "vectorizationFailedTitle": "无法向量化书籍", + "vectorizationFailedDesc": "ReadAny 无法完成此书的向量化。请检查向量模型设置后重试。" + }, "home": { "library": "书库", "addBook": "添加书籍", diff --git a/packages/core/src/rag/index.ts b/packages/core/src/rag/index.ts index bde1b66bc..a931def99 100644 --- a/packages/core/src/rag/index.ts +++ b/packages/core/src/rag/index.ts @@ -62,7 +62,7 @@ export type { export { vectorizeBook } from "./vectorize"; export type { VectorizeCallback } from "./vectorize"; -export { triggerVectorizeBook } from "./vectorize-trigger"; +export { resetBookVectorization, triggerVectorizeBook } from "./vectorize-trigger"; export type { VectorizeStatusCallback, VectorizeTriggerConfig, diff --git a/packages/core/src/rag/vectorize-trigger.test.ts b/packages/core/src/rag/vectorize-trigger.test.ts index ffa32b7d4..243a319d3 100644 --- a/packages/core/src/rag/vectorize-trigger.test.ts +++ b/packages/core/src/rag/vectorize-trigger.test.ts @@ -1,5 +1,70 @@ -import { describe, expect, it } from "vitest"; -import { canStoreInSharedVectorDB } from "./vectorize-trigger"; +import { beforeEach, describe, expect, it, vi } from "vitest"; + +const databaseMocks = vi.hoisted(() => ({ + deleteChunks: vi.fn(), + deleteVectorIndexProvenance: vi.fn(), + insertChunks: vi.fn(), + setVectorIndexProvenance: vi.fn(), +})); + +const vectorDatabaseMocks = vi.hoisted(() => ({ + deleteByBookId: vi.fn(), + getStats: vi.fn(), + insert: vi.fn(), + isReady: vi.fn(), +})); + +const remoteEmbeddingMocks = vi.hoisted(() => ({ + requestRemoteEmbeddingBatch: vi.fn(), +})); + +const chunkerMocks = vi.hoisted(() => ({ + chunkContent: vi.fn(), +})); + +vi.mock("../db/database", () => databaseMocks); +vi.mock("./chunker", () => chunkerMocks); +vi.mock("./remote-embedding", () => remoteEmbeddingMocks); +vi.mock("./vector-db", () => ({ + getVectorDB: () => vectorDatabaseMocks, + hasVectorDB: () => true, +})); + +import { + canStoreInSharedVectorDB, + resetBookVectorization, + triggerVectorizeBook, +} from "./vectorize-trigger"; + +beforeEach(() => { + vi.clearAllMocks(); + databaseMocks.deleteChunks.mockResolvedValue(undefined); + databaseMocks.deleteVectorIndexProvenance.mockResolvedValue(undefined); + databaseMocks.insertChunks.mockResolvedValue(undefined); + databaseMocks.setVectorIndexProvenance.mockResolvedValue(undefined); + chunkerMocks.chunkContent.mockReturnValue([ + { + id: "book-1-0-0", + bookId: "book-1", + chapterIndex: 0, + chapterTitle: "Chapter", + content: "content", + tokenCount: 1, + startCfi: "", + endCfi: "", + }, + ]); + vectorDatabaseMocks.deleteByBookId.mockResolvedValue(undefined); + vectorDatabaseMocks.getStats.mockResolvedValue({ totalVectors: 0, dimension: 2 }); + vectorDatabaseMocks.insert.mockResolvedValue(undefined); + vectorDatabaseMocks.isReady.mockResolvedValue(true); + remoteEmbeddingMocks.requestRemoteEmbeddingBatch.mockImplementation( + async (_model, texts: string[]) => ({ + ok: true, + embeddings: texts.map(() => [0.1, 0.2]), + }), + ); +}); describe("shared sqlite-vec dimension guard", () => { it("preserves a 384d book's acceleration index when a 1024d book is indexed", () => { @@ -12,3 +77,81 @@ describe("shared sqlite-vec dimension guard", () => { expect(canStoreInSharedVectorDB({ totalVectors: 120, dimension: 1024 }, 1024)).toBe(true); }); }); + +describe("failed vectorization cleanup", () => { + it("clears partial indexes and leaves the book unvectorized before rejecting", async () => { + const updates: Array<{ isVectorized: boolean; vectorizeProgress: number }> = []; + databaseMocks.setVectorIndexProvenance.mockRejectedValueOnce( + new Error("failed to save provenance"), + ); + + await expect( + triggerVectorizeBook( + "book-1", + [ + { + index: 0, + title: "Chapter", + content: "word ".repeat(400), + }, + ], + { + vectorModelEnabled: true, + vectorModelMode: "remote", + selectedBuiltinModelId: null, + remoteModel: { + url: "https://example.com/v1/embeddings", + apiKey: "test", + modelId: "test-model", + }, + }, + { + onBookUpdate: (_bookId, update) => updates.push(update), + }, + ), + ).rejects.toThrow("failed to save provenance"); + + expect(databaseMocks.deleteChunks).toHaveBeenCalledTimes(2); + expect(databaseMocks.deleteVectorIndexProvenance).toHaveBeenCalledTimes(2); + expect(vectorDatabaseMocks.deleteByBookId).toHaveBeenCalledTimes(2); + expect(updates.at(-1)).toEqual({ isVectorized: false, vectorizeProgress: 0 }); + expect(updates).not.toContainEqual({ isVectorized: true, vectorizeProgress: 1 }); + }); + + it("attempts every cleanup and resets the book flag when one store rejects", async () => { + const updates: Array<{ isVectorized: boolean; vectorizeProgress: number }> = []; + databaseMocks.deleteChunks.mockRejectedValueOnce(new Error("chunk cleanup failed")); + + await expect( + resetBookVectorization("book-1", { + onBookUpdate: (_bookId, update) => updates.push(update), + }), + ).rejects.toThrow("chunk cleanup failed"); + + expect(databaseMocks.deleteVectorIndexProvenance).toHaveBeenCalledWith("book-1"); + expect(vectorDatabaseMocks.deleteByBookId).toHaveBeenCalledWith("book-1"); + expect(updates).toEqual([{ isVectorized: false, vectorizeProgress: 0 }]); + }); + + it("waits for the unvectorized state write before cleanup resolves", async () => { + let releaseUpdate: (() => void) | undefined; + const updateReleased = new Promise((resolve) => { + releaseUpdate = resolve; + }); + + const cleanup = resetBookVectorization("book-1", { + onBookUpdate: async () => { + await updateReleased; + }, + }); + + const stateBeforeRelease = await Promise.race([ + cleanup.then(() => "resolved" as const), + new Promise<"pending">((resolve) => setTimeout(() => resolve("pending"), 10)), + ]); + expect(stateBeforeRelease).toBe("pending"); + + releaseUpdate?.(); + await cleanup; + }); +}); diff --git a/packages/core/src/rag/vectorize-trigger.ts b/packages/core/src/rag/vectorize-trigger.ts index c893abcd6..12e3ef700 100644 --- a/packages/core/src/rag/vectorize-trigger.ts +++ b/packages/core/src/rag/vectorize-trigger.ts @@ -46,7 +46,7 @@ export interface VectorizeTriggerCallbacks { onBookUpdate: ( bookId: string, update: { isVectorized: boolean; vectorizeProgress: number }, - ) => void; + ) => void | Promise; } /** Yield to the event loop so UI can repaint */ @@ -60,6 +60,35 @@ export function canStoreInSharedVectorDB( return stats.totalVectors === 0 || stats.dimension === embeddingDimension; } +/** Clear every persisted part of a book index and reset its library state. */ +export async function resetBookVectorization( + bookId: string, + callbacks: VectorizeTriggerCallbacks, +): Promise { + const results = await Promise.allSettled([ + deleteChunks(bookId), + deleteVectorIndexProvenance(bookId), + (async () => { + if (!hasVectorDB()) return; + const vectorDB = getVectorDB(); + if (vectorDB && (await vectorDB.isReady())) { + await vectorDB.deleteByBookId(bookId); + } + })(), + ]); + + invalidateChunkCache(bookId); + await callbacks.onBookUpdate(bookId, { + isVectorized: false, + vectorizeProgress: 0, + }); + + const failure = results.find( + (result): result is PromiseRejectedResult => result.status === "rejected", + ); + if (failure) throw failure.reason; +} + /** * Trigger full vectorization for a book. * 1. Chunks chapter text into manageable pieces @@ -97,7 +126,7 @@ export async function triggerVectorizeBook( try { // Update book state: vectorizing - callbacks.onBookUpdate(bookId, { + await callbacks.onBookUpdate(bookId, { isVectorized: false, vectorizeProgress: 0, }); @@ -241,7 +270,7 @@ export async function triggerVectorizeBook( progress.status = "completed"; onProgress?.(progress); - callbacks.onBookUpdate(bookId, { + await callbacks.onBookUpdate(bookId, { isVectorized: true, vectorizeProgress: 1, }); @@ -251,14 +280,15 @@ export async function triggerVectorizeBook( }); } catch (err) { const message = err instanceof Error ? err.message : String(err); + try { + await resetBookVectorization(bookId, callbacks); + } catch (cleanupError) { + console.error(`[Vectorize] Failed to clean up partial index for ${bookId}:`, cleanupError); + } + progress.status = "error"; progress.error = message; onProgress?.(progress); - - callbacks.onBookUpdate(bookId, { - isVectorized: false, - vectorizeProgress: 0, - }); eventBus.emit("vectorize:error", { bookId, error: message }); throw err; } @@ -269,12 +299,15 @@ function getEmbeddingProvenance( actualDimensions: number, ): EmbeddingProvenance { if (config.vectorModelMode === "builtin") { - const model = BUILTIN_EMBEDDING_MODELS.find((candidate) => candidate.id === config.selectedBuiltinModelId); + const model = BUILTIN_EMBEDDING_MODELS.find( + (candidate) => candidate.id === config.selectedBuiltinModelId, + ); if (!model) throw new Error("Cannot save provenance for an unknown built-in embedding model."); return { kind: "builtin", modelId: model.id, dimensions: actualDimensions || model.dimension }; } - if (!config.remoteModel) throw new Error("Cannot save provenance without a selected remote embedding model."); + if (!config.remoteModel) + throw new Error("Cannot save provenance without a selected remote embedding model."); return { kind: "remote", modelId: config.remoteModel.modelId, From 4f5e2776ca9b9d865b952a0ad814aeb52b0cedf9 Mon Sep 17 00:00:00 2001 From: Chai Date: Sun, 16 Aug 2026 20:52:33 -0400 Subject: [PATCH 05/10] fix(rag): harden MOBI extraction failures --- packages/app-expo/assets/reader/reader.html | 92 +++++------ .../src/components/rag/ExtractorWebView.tsx | 35 +++-- .../lib/rag/auto-vectorize-service.test.ts | 7 +- .../src/lib/rag/auto-vectorize-service.ts | 83 +++++----- .../src/lib/rag/extractor-error.test.ts | 9 ++ .../app-expo/src/lib/rag/extractor-error.ts | 19 ++- .../src/lib/rag/extractor-format.test.ts | 18 ++- .../app-expo/src/lib/rag/extractor-format.ts | 8 + .../src/lib/rag/mobi-parser-boundary.test.ts | 84 ++++++++++ .../src/lib/rag/vectorize-queue-job.test.ts | 141 +++++++++++++++++ .../src/lib/rag/vectorize-queue-job.ts | 81 ++++++++++ .../src/lib/rag/vectorize-trigger.test.ts | 64 ++++++++ .../app-expo/src/lib/rag/vectorize-trigger.ts | 4 +- .../screens/library/useVectorizationQueue.ts | 148 +++++++++--------- packages/app-expo/src/stores/library-store.ts | 13 ++ .../core/src/rag/vectorize-trigger.test.ts | 85 ++++++++++ packages/core/src/rag/vectorize-trigger.ts | 8 +- packages/foliate-js/mobi.js | 7 + 18 files changed, 711 insertions(+), 195 deletions(-) create mode 100644 packages/app-expo/src/lib/rag/mobi-parser-boundary.test.ts create mode 100644 packages/app-expo/src/lib/rag/vectorize-queue-job.test.ts create mode 100644 packages/app-expo/src/lib/rag/vectorize-queue-job.ts create mode 100644 packages/app-expo/src/lib/rag/vectorize-trigger.test.ts diff --git a/packages/app-expo/assets/reader/reader.html b/packages/app-expo/assets/reader/reader.html index 50ddcf9c3..ef5df0912 100644 --- a/packages/app-expo/assets/reader/reader.html +++ b/packages/app-expo/assets/reader/reader.html @@ -4972,8 +4972,8 @@ diff --git a/packages/app-expo/src/components/rag/ExtractorWebView.tsx b/packages/app-expo/src/components/rag/ExtractorWebView.tsx index d3fe7cb67..4d2fd9e39 100644 --- a/packages/app-expo/src/components/rag/ExtractorWebView.tsx +++ b/packages/app-expo/src/components/rag/ExtractorWebView.tsx @@ -5,7 +5,7 @@ import { forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useSta import { StyleSheet, View } from "react-native"; import { WebView } from "react-native-webview"; import { toBookExtractionError } from "../../lib/rag/extractor-error"; -import { createExtractorCommand } from "../../lib/rag/extractor-format"; +import { createExtractorRequest } from "../../lib/rag/extractor-format"; const READER_HTML_ASSET = Asset.fromModule(require("../../../assets/reader/reader.html")); const EXTRACTION_TIMEOUT_MS = 45_000; @@ -108,41 +108,50 @@ export const ExtractorWebView = forwardRef((_, ref) => { bookFormat?: Book["format"], fileName?: string, ) => { + const { command, classificationFormat } = createExtractorRequest({ + base64BookData, + mimeType, + bookFormat, + fileName, + }); return new Promise((resolve, reject) => { if (!ready || !webViewRef.current) { return reject( - toBookExtractionError(new Error("Extractor WebView not ready"), bookFormat), + toBookExtractionError(new Error("Extractor WebView not ready"), classificationFormat), ); } const timeoutId = setTimeout(() => { const index = pendingRequests.current.findIndex((pending) => pending.reject === reject); if (index >= 0) pendingRequests.current.splice(index, 1); - reject(toBookExtractionError(new Error("Timed out extracting book content"), bookFormat)); + reject( + toBookExtractionError( + new Error("Timed out extracting book content"), + classificationFormat, + ), + ); }, EXTRACTION_TIMEOUT_MS); - const pendingRequest = { resolve, reject, timeoutId, bookFormat }; + const pendingRequest = { + resolve, + reject, + timeoutId, + bookFormat: classificationFormat, + }; pendingRequests.current.push(pendingRequest); // Command the webview to open the book first. // It will reply with "loaded" when it finishes rendering. - const cmd = createExtractorCommand({ - base64BookData, - mimeType, - bookFormat, - fileName, - }); - try { webViewRef.current.injectJavaScript(` - window.postMessage(${JSON.stringify(JSON.stringify(cmd))}, "*"); + window.postMessage(${JSON.stringify(JSON.stringify(command))}, "*"); true; `); } catch (error) { clearTimeout(timeoutId); const index = pendingRequests.current.indexOf(pendingRequest); if (index >= 0) pendingRequests.current.splice(index, 1); - reject(toBookExtractionError(error, bookFormat)); + reject(toBookExtractionError(error, classificationFormat)); } }); }, diff --git a/packages/app-expo/src/lib/rag/auto-vectorize-service.test.ts b/packages/app-expo/src/lib/rag/auto-vectorize-service.test.ts index 32b28834e..e4b40d41f 100644 --- a/packages/app-expo/src/lib/rag/auto-vectorize-service.test.ts +++ b/packages/app-expo/src/lib/rag/auto-vectorize-service.test.ts @@ -1,6 +1,5 @@ import type { Book } from "@readany/core/types"; import { afterEach, describe, expect, it, vi } from "vitest"; -import { BookExtractionError } from "./extractor-error"; const vectorizeMocks = vi.hoisted(() => ({ resetBookVectorization: vi.fn(), @@ -38,9 +37,7 @@ describe("automatic vectorization failure lifecycle", () => { events.push("cleanup"); }); setExtractorRef({ - extractChapters: vi - .fn() - .mockRejectedValue(new BookExtractionError("Encrypted MOBI records", "mobi")), + extractChapters: vi.fn().mockRejectedValue(new Error("Encrypted MOBI records")), }); const errorPublished = new Promise((resolve) => { @@ -62,7 +59,7 @@ describe("automatic vectorization failure lifecycle", () => { it("publishes the failure and releases the queue if cleanup itself rejects", async () => { vectorizeMocks.resetBookVectorization.mockRejectedValueOnce(new Error("cleanup failed")); setExtractorRef({ - extractChapters: vi.fn().mockRejectedValue(new BookExtractionError("loader failed", "mobi")), + extractChapters: vi.fn().mockRejectedValue(new Error("loader failed")), }); const callback = vi.fn(); setCallback(callback); diff --git a/packages/app-expo/src/lib/rag/auto-vectorize-service.ts b/packages/app-expo/src/lib/rag/auto-vectorize-service.ts index efac4cf7e..152883012 100644 --- a/packages/app-expo/src/lib/rag/auto-vectorize-service.ts +++ b/packages/app-expo/src/lib/rag/auto-vectorize-service.ts @@ -1,10 +1,7 @@ import type { ChapterData } from "@readany/core/rag"; import type { Book } from "@readany/core/types"; -import { - BookExtractionError, - type BookExtractionErrorCategory, - toBookExtractionError, -} from "./extractor-error"; +import type { BookExtractionErrorCategory } from "./extractor-error"; +import { runVectorizeQueueJob } from "./vectorize-queue-job"; export type AutoVectorizeCallback = ( bookId: string, @@ -67,45 +64,43 @@ async function processQueue() { const { book, base64Data, mimeType } = item; - try { - callback?.(book.id, { status: "extracting", progress: 0 }); - - if (!extractorRef) { - throw toBookExtractionError(new Error("Extractor WebView not ready"), book.format); - } - - const chapters = await extractorRef.extractChapters( - base64Data, - mimeType, - book.format, - book.filePath, - ); - if (!chapters || chapters.length === 0) { - throw toBookExtractionError(new Error("No chapters extracted from book"), book.format); - } - - callback?.(book.id, { status: "vectorizing", progress: 0 }); - - await triggerVectorizeBook(book.id, book.filePath, chapters, (progress) => { - const pct = - progress.totalChunks > 0 ? progress.processedChunks / progress.totalChunks : 0; - callback?.(book.id, { status: "vectorizing", progress: pct }); - }); - - callback?.(book.id, { status: "completed", progress: 1 }); - } catch (err) { - console.error(`[AutoVectorize] Failed for ${book.meta.title}:`, err); - try { - await resetBookVectorization(book.id); - } catch (cleanupError) { - console.error(`[AutoVectorize] Failed to clean up ${book.meta.title}:`, cleanupError); - } - callback?.(book.id, { - status: "error", - progress: 0, - errorCategory: err instanceof BookExtractionError ? err.category : undefined, - }); - } + await runVectorizeQueueJob({ + format: book.format, + extract: async () => { + if (!extractorRef) throw new Error("Extractor WebView not ready"); + return extractorRef.extractChapters(base64Data, mimeType, book.format, book.filePath); + }, + vectorize: async (chapters, onProgress) => { + await triggerVectorizeBook(book.id, book.filePath, chapters, (progress) => { + const pct = + progress.totalChunks > 0 ? progress.processedChunks / progress.totalChunks : 0; + onProgress?.(pct); + }); + }, + cleanup: () => resetBookVectorization(book.id), + onEvent: (event) => { + if (event.status === "extracting") { + callback?.(book.id, { status: "extracting", progress: 0 }); + } else if (event.status === "vectorizing") { + callback?.(book.id, { status: "vectorizing", progress: event.progress ?? 0 }); + } else if (event.status === "completed") { + callback?.(book.id, { status: "completed", progress: 1 }); + } else { + console.error(`[AutoVectorize] Failed for ${book.meta.title}:`, event.error); + if (event.cleanupError) { + console.error( + `[AutoVectorize] Failed to clean up ${book.meta.title}:`, + event.cleanupError, + ); + } + callback?.(book.id, { + status: "error", + progress: 0, + errorCategory: event.errorCategory, + }); + } + }, + }); } } finally { processing = false; diff --git a/packages/app-expo/src/lib/rag/extractor-error.test.ts b/packages/app-expo/src/lib/rag/extractor-error.test.ts index d7d996d3a..155dca04c 100644 --- a/packages/app-expo/src/lib/rag/extractor-error.test.ts +++ b/packages/app-expo/src/lib/rag/extractor-error.test.ts @@ -27,6 +27,15 @@ describe("classifyBookExtractionError", () => { "truncated MOBI header", "invalid record structure", "record offset is outside the file", + "Invalid HUFF record", + "Invalid CDIC record", + "Invalid INDX record", + "Invalid TAGX section", + "Invalid EXTH header", + "Missing MOBI header", + "Missing FDST record", + "Record index out of bounds", + "Offset is outside the bounds of the DataView", ])("classifies malformed structure evidence: %s", (message) => { expect(classifyBookExtractionError(new Error(message), "mobi")).toBe("malformed"); }); diff --git a/packages/app-expo/src/lib/rag/extractor-error.ts b/packages/app-expo/src/lib/rag/extractor-error.ts index d1326d638..0d9e22ae7 100644 --- a/packages/app-expo/src/lib/rag/extractor-error.ts +++ b/packages/app-expo/src/lib/rag/extractor-error.ts @@ -6,8 +6,10 @@ export type BookExtractionErrorCategory = const MOBI_FAMILY = new Set(["mobi", "azw", "azw3"]); const PROTECTION_EVIDENCE = /\b(?:encrypt(?:ed|ion)?|drm|protected)\b/i; -const MALFORMED_EVIDENCE = +const GENERIC_MALFORMED_EVIDENCE = /\b(?:truncat(?:ed|ion)|invalid\s+(?:(?:pdb|mobi)\s+)?record(?:\s+(?:offset|structure|header|index))?|record\s+(?:offset|structure|header|index))\b/i; +const MOBI_PARSER_MALFORMED_EVIDENCE = + /^(?:Invalid (?:HUFF|CDIC|INDX) record|Invalid TAGX section|Invalid EXTH header|Missing MOBI header|Missing FDST record|Record index out of bounds|Offset is outside (?:the )?bounds of (?:the )?DataView)$/i; const UNSUPPORTED_FORMAT_EVIDENCE = /\bunsupported\s+(?:book\s+)?format\b/i; const MESSAGE_KEYS: Record = { @@ -34,13 +36,22 @@ export function classifyBookExtractionError( format: string | undefined, ): BookExtractionErrorCategory { const message = error instanceof Error ? error.message : String(error); + const parserMessage = message.replace(/^(?:Error|RangeError):\s*/i, ""); const normalizedFormat = format?.trim().toLowerCase(); - if (normalizedFormat && MOBI_FAMILY.has(normalizedFormat) && PROTECTION_EVIDENCE.test(message)) { + if ( + normalizedFormat && + MOBI_FAMILY.has(normalizedFormat) && + PROTECTION_EVIDENCE.test(parserMessage) + ) { return "drm-protected"; } - if (MALFORMED_EVIDENCE.test(message)) return "malformed"; - if (UNSUPPORTED_FORMAT_EVIDENCE.test(message)) return "unsupported-format"; + if ( + GENERIC_MALFORMED_EVIDENCE.test(parserMessage) || + MOBI_PARSER_MALFORMED_EVIDENCE.test(parserMessage) + ) + return "malformed"; + if (UNSUPPORTED_FORMAT_EVIDENCE.test(parserMessage)) return "unsupported-format"; return "unknown"; } diff --git a/packages/app-expo/src/lib/rag/extractor-format.test.ts b/packages/app-expo/src/lib/rag/extractor-format.test.ts index f6934e5de..463ff7e70 100644 --- a/packages/app-expo/src/lib/rag/extractor-format.test.ts +++ b/packages/app-expo/src/lib/rag/extractor-format.test.ts @@ -1,5 +1,9 @@ import { describe, expect, it } from "vitest"; -import { createExtractorCommand, resolveExtractorFormat } from "./extractor-format"; +import { + createExtractorCommand, + createExtractorRequest, + resolveExtractorFormat, +} from "./extractor-format"; describe("resolveExtractorFormat", () => { it.each(["epub", "pdf", "txt", "umd", "mobi", "azw", "azw3"])( @@ -79,3 +83,15 @@ describe("createExtractorCommand", () => { expect(createExtractorCommand({ base64BookData: "data", ...input })).toMatchObject(expected); }); }); + +describe("createExtractorRequest", () => { + it.each([ + [{ mimeType: "application/octet-stream", fileName: "inferred.mobi" }, "mobi"], + [{ mimeType: "application/vnd.amazon.ebook" }, "azw3"], + ])("preserves the resolved %s format for error classification", (input, expected) => { + const request = createExtractorRequest({ base64BookData: "data", ...input }); + + expect(request.command.bookFormat).toBe(expected); + expect(request.classificationFormat).toBe(expected); + }); +}); diff --git a/packages/app-expo/src/lib/rag/extractor-format.ts b/packages/app-expo/src/lib/rag/extractor-format.ts index 19a882a11..c09185908 100644 --- a/packages/app-expo/src/lib/rag/extractor-format.ts +++ b/packages/app-expo/src/lib/rag/extractor-format.ts @@ -82,3 +82,11 @@ export function createExtractorCommand(input: { fileName: getExtractorFileName(input.mimeType, resolvedFormat, input.fileName), }; } + +export function createExtractorRequest(input: Parameters[0]) { + const command = createExtractorCommand(input); + return { + command, + classificationFormat: command.bookFormat ?? undefined, + }; +} diff --git a/packages/app-expo/src/lib/rag/mobi-parser-boundary.test.ts b/packages/app-expo/src/lib/rag/mobi-parser-boundary.test.ts new file mode 100644 index 000000000..2ce301738 --- /dev/null +++ b/packages/app-expo/src/lib/rag/mobi-parser-boundary.test.ts @@ -0,0 +1,84 @@ +import { describe, expect, it } from "vitest"; +import { MOBI } from "../../../../foliate-js/mobi.js"; + +function writeAscii(bytes: Uint8Array, offset: number, value: string) { + for (let index = 0; index < value.length; index++) { + bytes[offset + index] = value.charCodeAt(index); + } +} + +function createProtectedMobiFile(): Blob { + const bytes = new Uint8Array(512); + const view = new DataView(bytes.buffer); + const recordOffset = 86; + + writeAscii(bytes, 60, "BOOKMOBI"); + view.setUint16(76, 1); + view.setUint32(78, recordOffset); + + view.setUint16(recordOffset, 1); + view.setUint16(recordOffset + 12, 1); + writeAscii(bytes, recordOffset + 16, "MOBI"); + view.setUint32(recordOffset + 20, 232); + view.setUint32(recordOffset + 28, 65001); + view.setUint32(recordOffset + 32, 1); + view.setUint32(recordOffset + 36, 6); + view.setUint32(recordOffset + 108, 1); + + return new Blob([bytes], { type: "application/x-mobipocket-ebook" }); +} + +function createProtectedComboMobiFile(): Blob { + const bytes = new Uint8Array(800); + const view = new DataView(bytes.buffer); + const mobi6Offset = 94; + const kf8Offset = 400; + + writeAscii(bytes, 60, "BOOKMOBI"); + view.setUint16(76, 2); + view.setUint32(78, mobi6Offset); + view.setUint32(86, kf8Offset); + + view.setUint16(mobi6Offset, 1); + writeAscii(bytes, mobi6Offset + 16, "MOBI"); + view.setUint32(mobi6Offset + 20, 232); + view.setUint32(mobi6Offset + 28, 65001); + view.setUint32(mobi6Offset + 32, 1); + view.setUint32(mobi6Offset + 36, 6); + view.setUint32(mobi6Offset + 108, 2); + view.setUint32(mobi6Offset + 128, 0b100_0000); + writeAscii(bytes, mobi6Offset + 248, "EXTH"); + view.setUint32(mobi6Offset + 252, 24); + view.setUint32(mobi6Offset + 256, 1); + view.setUint32(mobi6Offset + 260, 121); + view.setUint32(mobi6Offset + 264, 12); + view.setUint32(mobi6Offset + 268, 1); + + view.setUint16(kf8Offset, 1); + view.setUint16(kf8Offset + 12, 1); + writeAscii(bytes, kf8Offset + 16, "MOBI"); + view.setUint32(kf8Offset + 20, 232); + view.setUint32(kf8Offset + 28, 65001); + view.setUint32(kf8Offset + 32, 2); + view.setUint32(kf8Offset + 36, 8); + + return new Blob([bytes], { type: "application/x-mobipocket-ebook" }); +} + +describe("MOBI parser protection boundary", () => { + it("rejects encrypted PalmDOC records before text extraction", async () => { + const parser = new MOBI({ unzlib: (value: Uint8Array) => value }); + + await expect(parser.open(createProtectedMobiFile())).rejects.toThrow( + "Encrypted MOBI records are not supported", + ); + }); + + it("does not swallow encryption in a combo file's KF8 records", async () => { + const parser = new MOBI({ unzlib: (value: Uint8Array) => value }); + + await expect(parser.open(createProtectedComboMobiFile())).rejects.toThrow( + "Encrypted MOBI records are not supported", + ); + }); +}); diff --git a/packages/app-expo/src/lib/rag/vectorize-queue-job.test.ts b/packages/app-expo/src/lib/rag/vectorize-queue-job.test.ts new file mode 100644 index 000000000..6dfe24913 --- /dev/null +++ b/packages/app-expo/src/lib/rag/vectorize-queue-job.test.ts @@ -0,0 +1,141 @@ +import { describe, expect, it, vi } from "vitest"; +import { MOBI } from "../../../../foliate-js/mobi.js"; +import { BookExtractionError } from "./extractor-error"; +import { runVectorizeQueueJob } from "./vectorize-queue-job"; + +function writeAscii(bytes: Uint8Array, offset: number, value: string) { + for (let index = 0; index < value.length; index++) { + bytes[offset + index] = value.charCodeAt(index); + } +} + +function createProtectedMobiFile(): Blob { + const bytes = new Uint8Array(512); + const view = new DataView(bytes.buffer); + const recordOffset = 86; + + writeAscii(bytes, 60, "BOOKMOBI"); + view.setUint16(76, 1); + view.setUint32(78, recordOffset); + view.setUint16(recordOffset, 1); + view.setUint16(recordOffset + 12, 1); + writeAscii(bytes, recordOffset + 16, "MOBI"); + view.setUint32(recordOffset + 20, 232); + view.setUint32(recordOffset + 28, 65001); + view.setUint32(recordOffset + 36, 6); + + return new Blob([bytes], { type: "application/x-mobipocket-ebook" }); +} + +describe("runVectorizeQueueJob", () => { + it("routes a real protected MOBI parser failure through cleanup without vectorizing", async () => { + const order: string[] = []; + const vectorize = vi.fn(); + const parser = new MOBI({ unzlib: (value: Uint8Array) => value }); + + const result = await runVectorizeQueueJob({ + format: "mobi", + extract: async () => (await parser.open(createProtectedMobiFile())) as never, + vectorize, + cleanup: async () => { + order.push("cleanup"); + }, + onEvent: (event) => order.push(event.status), + }); + + expect(result.ok).toBe(false); + if (result.ok) throw new Error("Expected protected extraction to fail"); + expect(result.error).toBeInstanceOf(BookExtractionError); + expect(result.error).toMatchObject({ category: "drm-protected" }); + expect(order).toEqual(["extracting", "cleanup", "error"]); + expect(vectorize).not.toHaveBeenCalled(); + }); + + it.each(["manual", "automatic"])( + "classifies raw parser failures at the shared %s queue boundary", + async () => { + const order: string[] = []; + + const result = await runVectorizeQueueJob({ + format: "azw3", + extract: async () => { + throw new Error("Invalid HUFF record"); + }, + vectorize: vi.fn(), + cleanup: async () => { + order.push("cleanup"); + }, + onEvent: (event) => order.push(event.status), + }); + + expect(result.ok).toBe(false); + if (result.ok) throw new Error("Expected parser failure"); + expect(result.error).toMatchObject({ category: "malformed" }); + expect(order).toEqual(["extracting", "cleanup", "error"]); + }, + ); + + it("treats unmount cancellation as an extraction rejection and never completes", async () => { + const statuses: string[] = []; + const cleanup = vi.fn().mockResolvedValue(undefined); + + const result = await runVectorizeQueueJob({ + format: "mobi", + extract: async () => { + throw new Error("Extractor WebView unmounted"); + }, + vectorize: vi.fn(), + cleanup, + onEvent: (event) => statuses.push(event.status), + }); + + expect(result.ok).toBe(false); + expect(cleanup).toHaveBeenCalledOnce(); + expect(statuses).toEqual(["extracting", "error"]); + expect(statuses).not.toContain("completed"); + }); + + it("publishes error only after a rejected cleanup attempt", async () => { + const order: string[] = []; + const cleanupError = new Error("cleanup failed"); + + const result = await runVectorizeQueueJob({ + format: "mobi", + extract: async () => { + throw new Error("loader failed"); + }, + vectorize: vi.fn(), + cleanup: async () => { + order.push("cleanup"); + throw cleanupError; + }, + onEvent: (event) => order.push(event.status), + }); + + expect(result.ok).toBe(false); + if (result.ok) throw new Error("Expected extraction failure"); + expect(result.cleanupError).toBe(cleanupError); + expect(order).toEqual(["extracting", "cleanup", "error"]); + }); + + it("cleans up a final state-write rejection without publishing completion", async () => { + const statuses: string[] = []; + const cleanup = vi.fn().mockResolvedValue(undefined); + + const result = await runVectorizeQueueJob({ + format: "mobi", + extract: async () => [{ index: 0, title: "Chapter", content: "text", segments: [] }], + vectorize: async (_chapters, onProgress) => { + onProgress?.({ status: "completed" }); + throw new Error("failed to persist vectorized state"); + }, + cleanup, + onEvent: (event) => statuses.push(event.status), + }); + + expect(result.ok).toBe(false); + expect(cleanup).toHaveBeenCalledOnce(); + expect(statuses).toEqual(["extracting", "vectorizing", "error"]); + expect(statuses).not.toContain("completed"); + }); +}); diff --git a/packages/app-expo/src/lib/rag/vectorize-queue-job.ts b/packages/app-expo/src/lib/rag/vectorize-queue-job.ts new file mode 100644 index 000000000..1affc7ce9 --- /dev/null +++ b/packages/app-expo/src/lib/rag/vectorize-queue-job.ts @@ -0,0 +1,81 @@ +import type { ChapterData } from "@readany/core/rag"; +import { + BookExtractionError, + type BookExtractionErrorCategory, + toBookExtractionError, +} from "./extractor-error"; + +export type VectorizeQueueJobEvent = + | { status: "extracting" } + | { status: "vectorizing"; progress?: Progress } + | { status: "completed" } + | { + status: "error"; + error: unknown; + errorCategory?: BookExtractionErrorCategory; + cleanupError?: unknown; + }; + +export type VectorizeQueueJobResult = + | { ok: true } + | { ok: false; error: unknown; cleanupError?: unknown }; + +interface VectorizeQueueJobOptions { + format?: string; + extract: () => Promise; + vectorize: (chapters: ChapterData[], onProgress?: (progress: Progress) => void) => Promise; + cleanup: () => Promise; + onEvent: (event: VectorizeQueueJobEvent) => void; +} + +function isCompletedProgress(progress: unknown): boolean { + return ( + typeof progress === "object" && + progress !== null && + "status" in progress && + progress.status === "completed" + ); +} + +export async function runVectorizeQueueJob( + options: VectorizeQueueJobOptions, +): Promise { + let phase: "extracting" | "vectorizing" = "extracting"; + options.onEvent({ status: "extracting" }); + + try { + const chapters = await options.extract(); + if (!chapters.length) { + throw toBookExtractionError(new Error("No chapters extracted from book"), options.format); + } + + phase = "vectorizing"; + options.onEvent({ status: "vectorizing" }); + await options.vectorize(chapters, (progress) => { + if (!isCompletedProgress(progress)) { + options.onEvent({ status: "vectorizing", progress }); + } + }); + + options.onEvent({ status: "completed" }); + return { ok: true }; + } catch (error) { + const failure = phase === "extracting" ? toBookExtractionError(error, options.format) : error; + let cleanupError: unknown; + try { + await options.cleanup(); + } catch (errorDuringCleanup) { + cleanupError = errorDuringCleanup; + } + + options.onEvent({ + status: "error", + error: failure, + errorCategory: failure instanceof BookExtractionError ? failure.category : undefined, + cleanupError, + }); + return cleanupError + ? { ok: false, error: failure, cleanupError } + : { ok: false, error: failure }; + } +} diff --git a/packages/app-expo/src/lib/rag/vectorize-trigger.test.ts b/packages/app-expo/src/lib/rag/vectorize-trigger.test.ts new file mode 100644 index 000000000..0615e9e4d --- /dev/null +++ b/packages/app-expo/src/lib/rag/vectorize-trigger.test.ts @@ -0,0 +1,64 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; + +const mocks = vi.hoisted(() => ({ + coreTriggerVectorizeBook: vi.fn(), + updateBook: vi.fn(), + updateBookStrict: vi.fn(), +})); + +vi.mock("@readany/core/rag", () => ({ + resetBookVectorization: vi.fn(), + triggerVectorizeBook: mocks.coreTriggerVectorizeBook, +})); + +vi.mock("@/stores/library-store", () => ({ + useLibraryStore: { + getState: () => ({ + updateBook: mocks.updateBook, + updateBookStrict: mocks.updateBookStrict, + }), + }, +})); + +vi.mock("@/stores/vector-model-store", () => ({ + useVectorModelStore: { + getState: () => ({ + vectorModelEnabled: true, + vectorModelMode: "builtin", + selectedBuiltinModelId: "test-model", + getSelectedVectorModel: () => null, + }), + }, +})); + +import { triggerVectorizeBook } from "./vectorize-trigger"; + +describe("triggerVectorizeBook persistence boundary", () => { + beforeEach(() => { + vi.clearAllMocks(); + mocks.updateBook.mockResolvedValue(undefined); + mocks.updateBookStrict.mockRejectedValue(new Error("database write failed")); + mocks.coreTriggerVectorizeBook.mockImplementation( + async (_bookId, _chapters, _config, callbacks) => { + await callbacks.onBookUpdate("book-1", { + isVectorized: true, + vectorizeProgress: 1, + }); + }, + ); + }); + + it("propagates a failed durable vectorized-state write", async () => { + await expect( + triggerVectorizeBook("book-1", "book.mobi", [ + { index: 0, title: "Chapter", content: "text", segments: [] }, + ]), + ).rejects.toThrow("database write failed"); + + expect(mocks.updateBookStrict).toHaveBeenCalledWith("book-1", { + isVectorized: true, + vectorizeProgress: 1, + }); + expect(mocks.updateBook).not.toHaveBeenCalled(); + }); +}); diff --git a/packages/app-expo/src/lib/rag/vectorize-trigger.ts b/packages/app-expo/src/lib/rag/vectorize-trigger.ts index ade73263b..3481ec3a3 100644 --- a/packages/app-expo/src/lib/rag/vectorize-trigger.ts +++ b/packages/app-expo/src/lib/rag/vectorize-trigger.ts @@ -14,7 +14,7 @@ export type { VectorizeStatusCallback }; export async function resetBookVectorization(bookId: string): Promise { await coreResetBookVectorization(bookId, { - onBookUpdate: useLibraryStore.getState().updateBook, + onBookUpdate: useLibraryStore.getState().updateBookStrict, }); } @@ -44,7 +44,7 @@ export async function triggerVectorizeBook( // 2. Build callbacks for state updates const callbacks = { - onBookUpdate: useLibraryStore.getState().updateBook, + onBookUpdate: useLibraryStore.getState().updateBookStrict, }; // 3. Delegate to core vectorization pipeline which does the chunking & embedding diff --git a/packages/app-expo/src/screens/library/useVectorizationQueue.ts b/packages/app-expo/src/screens/library/useVectorizationQueue.ts index 583f19220..c05e0d21c 100644 --- a/packages/app-expo/src/screens/library/useVectorizationQueue.ts +++ b/packages/app-expo/src/screens/library/useVectorizationQueue.ts @@ -1,11 +1,8 @@ import type { ExtractorRef } from "@/components/rag/ExtractorWebView"; import { inspectMobileBookForVectorize } from "@/lib/rag/auto-vectorize-book"; -import { - BookExtractionError, - getBookExtractionErrorMessageKeys, - toBookExtractionError, -} from "@/lib/rag/extractor-error"; +import { getBookExtractionErrorMessageKeys } from "@/lib/rag/extractor-error"; import { MOBILE_VECTORIZE_UNSUPPORTED_FORMAT_DESCRIPTION } from "@/lib/rag/mobile-vectorize-capability"; +import { runVectorizeQueueJob } from "@/lib/rag/vectorize-queue-job"; import { resetBookVectorization, triggerVectorizeBook } from "@/lib/rag/vectorize-trigger"; import type { RootStackParamList } from "@/navigation/RootNavigator"; import { useVectorModelStore } from "@/stores/vector-model-store"; @@ -36,79 +33,78 @@ export function useVectorizationQueue({ extractorRef, nav }: UseVectorizationQue async (book: Book) => { setVectorizingBookId(book.id); setVectorizingBookTitle(book.meta.title); - setVectorProgress({ - bookId: book.id, - status: "chunking", - processedChunks: 0, - totalChunks: 0, + const result = await runVectorizeQueueJob({ + format: book.format, + extract: async () => { + if (!extractorRef.current) throw new Error("Extractor WebView not ready"); + + const info = await inspectMobileBookForVectorize(book); + if (!info.canVectorize || !info.mimeType) { + throw new Error(`Book cannot be vectorized on mobile: ${info.reason ?? "unknown"}`); + } + + const base64 = await FileSystem.readAsStringAsync(info.absPath, { + encoding: FileSystem.EncodingType.Base64, + }); + return extractorRef.current.extractChapters( + base64, + info.mimeType, + book.format, + info.absPath, + ); + }, + vectorize: (chapters, onProgress) => + triggerVectorizeBook(book.id, book.filePath, chapters, onProgress), + cleanup: () => resetBookVectorization(book.id), + onEvent: (event) => { + if (event.status === "extracting") { + setVectorProgress({ + bookId: book.id, + status: "chunking", + processedChunks: 0, + totalChunks: 0, + }); + } else if (event.status === "vectorizing") { + if (event.progress) setVectorProgress({ ...event.progress }); + } else if (event.status === "completed") { + setVectorProgress({ + bookId: book.id, + status: "completed", + processedChunks: 1, + totalChunks: 1, + }); + } else { + console.error( + `[useVectorizationQueue] Vectorization failed for "${book.meta.title}":`, + event.error, + ); + if (event.cleanupError) { + console.error( + `[useVectorizationQueue] Failed to clean up "${book.meta.title}":`, + event.cleanupError, + ); + } + setVectorProgress({ + bookId: book.id, + status: "error", + processedChunks: 0, + totalChunks: 0, + }); + + if (event.errorCategory) { + const keys = getBookExtractionErrorMessageKeys(event.errorCategory); + Alert.alert(t(keys.title), t(keys.description)); + } else { + Alert.alert( + t("vectorize.vectorizationFailedTitle"), + t("vectorize.vectorizationFailedDesc"), + ); + } + } + }, }); - try { - if (!extractorRef.current) { - throw toBookExtractionError(new Error("Extractor WebView not ready"), book.format); - } - - const info = await inspectMobileBookForVectorize(book); - if (!info.canVectorize || !info.mimeType) { - throw new Error(`Book cannot be vectorized on mobile: ${info.reason ?? "unknown"}`); - } - - const base64 = await FileSystem.readAsStringAsync(info.absPath, { - encoding: FileSystem.EncodingType.Base64, - }); - - const chapters = await extractorRef.current.extractChapters( - base64, - info.mimeType, - book.format, - info.absPath, - ); - if (!chapters || chapters.length === 0) { - throw toBookExtractionError(new Error("No chapters extracted from book"), book.format); - } - - await triggerVectorizeBook(book.id, book.filePath, chapters, (progress) => { - setVectorProgress({ ...progress }); - }); - - setVectorProgress({ - bookId: book.id, - status: "completed", - processedChunks: 1, - totalChunks: 1, - }); - await new Promise((resolve) => setTimeout(resolve, 800)); - } catch (err) { - console.error( - `[useVectorizationQueue] Vectorization failed for "${book.meta.title}":`, - err, - ); - try { - await resetBookVectorization(book.id); - } catch (cleanupError) { - console.error( - `[useVectorizationQueue] Failed to clean up "${book.meta.title}":`, - cleanupError, - ); - } - setVectorProgress({ - bookId: book.id, - status: "error", - processedChunks: 0, - totalChunks: 0, - }); - - if (err instanceof BookExtractionError) { - const keys = getBookExtractionErrorMessageKeys(err.category); - Alert.alert(t(keys.title), t(keys.description)); - } else { - Alert.alert( - t("vectorize.vectorizationFailedTitle"), - t("vectorize.vectorizationFailedDesc"), - ); - } - await new Promise((resolve) => setTimeout(resolve, 1500)); - } + await new Promise((resolve) => setTimeout(resolve, result.ok ? 800 : 1500)); }, [extractorRef, t], ); diff --git a/packages/app-expo/src/stores/library-store.ts b/packages/app-expo/src/stores/library-store.ts index 0a59340a6..1dc11b02b 100644 --- a/packages/app-expo/src/stores/library-store.ts +++ b/packages/app-expo/src/stores/library-store.ts @@ -76,6 +76,7 @@ export interface LibraryState { addBook: (book: Book) => Promise; removeBook: (bookId: string, options?: RemoveBookOptions) => Promise; updateBook: (bookId: string, updates: Partial) => Promise; + updateBookStrict: (bookId: string, updates: Partial) => Promise; setFilter: (filter: Partial) => void; setViewMode: (mode: LibraryViewMode) => void; setSortField: (field: SortField) => void; @@ -827,6 +828,18 @@ export const useLibraryStore = create((set, get) => ({ ); }, + updateBookStrict: async (bookId, updates) => { + set((state) => ({ + books: state.books.map((b) => (b.id === bookId ? { ...b, ...updates } : b)), + allTags: + updates.tags !== undefined + ? Array.from(new Set([...state.allTags, ...updates.tags])).sort() + : state.allTags, + })); + debouncedSave("library-books", get().books); + await persistBookUpdate(bookId, updates); + }, + setFilter: (filter) => set((state) => ({ filter: { ...state.filter, ...filter } })), setViewMode: (mode) => set({ viewMode: mode }), setSortField: (field) => set((state) => ({ filter: { ...state.filter, sortField: field } })), diff --git a/packages/core/src/rag/vectorize-trigger.test.ts b/packages/core/src/rag/vectorize-trigger.test.ts index 243a319d3..94740facc 100644 --- a/packages/core/src/rag/vectorize-trigger.test.ts +++ b/packages/core/src/rag/vectorize-trigger.test.ts @@ -22,6 +22,10 @@ const chunkerMocks = vi.hoisted(() => ({ chunkContent: vi.fn(), })); +const eventBusMocks = vi.hoisted(() => ({ + emit: vi.fn(), +})); + vi.mock("../db/database", () => databaseMocks); vi.mock("./chunker", () => chunkerMocks); vi.mock("./remote-embedding", () => remoteEmbeddingMocks); @@ -29,6 +33,7 @@ vi.mock("./vector-db", () => ({ getVectorDB: () => vectorDatabaseMocks, hasVectorDB: () => true, })); +vi.mock("../utils/event-bus", () => ({ eventBus: eventBusMocks })); import { canStoreInSharedVectorDB, @@ -154,4 +159,84 @@ describe("failed vectorization cleanup", () => { releaseUpdate?.(); await cleanup; }); + + it("does not publish completion when the final vectorized-state write rejects", async () => { + const progressStatuses: string[] = []; + + await expect( + triggerVectorizeBook( + "book-1", + [{ index: 0, title: "Chapter", content: "content" }], + { + vectorModelEnabled: true, + vectorModelMode: "remote", + selectedBuiltinModelId: null, + remoteModel: { + url: "https://example.com/v1/embeddings", + apiKey: "test", + modelId: "test-model", + }, + }, + { + onBookUpdate: async (_bookId, update) => { + if (update.isVectorized) throw new Error("final state write failed"); + }, + }, + (progress) => progressStatuses.push(progress.status), + ), + ).rejects.toThrow("final state write failed"); + + expect(progressStatuses).not.toContain("completed"); + expect(progressStatuses.at(-1)).toBe("error"); + expect(eventBusMocks.emit).not.toHaveBeenCalledWith("vectorize:completed", expect.anything()); + expect(eventBusMocks.emit).toHaveBeenCalledWith("vectorize:error", { + bookId: "book-1", + error: "final state write failed", + }); + expect(databaseMocks.deleteChunks).toHaveBeenCalledTimes(2); + }); + + it("waits for the final vectorized-state write before publishing completion", async () => { + let releaseFinalWrite: (() => void) | undefined; + const finalWriteReleased = new Promise((resolve) => { + releaseFinalWrite = resolve; + }); + const progressStatuses: string[] = []; + + const vectorization = triggerVectorizeBook( + "book-1", + [{ index: 0, title: "Chapter", content: "content" }], + { + vectorModelEnabled: true, + vectorModelMode: "remote", + selectedBuiltinModelId: null, + remoteModel: { + url: "https://example.com/v1/embeddings", + apiKey: "test", + modelId: "test-model", + }, + }, + { + onBookUpdate: async (_bookId, update) => { + if (update.isVectorized) await finalWriteReleased; + }, + }, + (progress) => progressStatuses.push(progress.status), + ); + + await vi.waitFor(() => { + expect(databaseMocks.setVectorIndexProvenance).toHaveBeenCalledOnce(); + }); + expect(progressStatuses).not.toContain("completed"); + expect(eventBusMocks.emit).not.toHaveBeenCalledWith("vectorize:completed", expect.anything()); + + releaseFinalWrite?.(); + await vectorization; + + expect(progressStatuses.at(-1)).toBe("completed"); + expect(eventBusMocks.emit).toHaveBeenCalledWith("vectorize:completed", { + bookId: "book-1", + chunksCount: 1, + }); + }); }); diff --git a/packages/core/src/rag/vectorize-trigger.ts b/packages/core/src/rag/vectorize-trigger.ts index 12e3ef700..201e18be1 100644 --- a/packages/core/src/rag/vectorize-trigger.ts +++ b/packages/core/src/rag/vectorize-trigger.ts @@ -266,14 +266,14 @@ export async function triggerVectorizeBook( // Invalidate search cache so next query picks up new embeddings invalidateChunkCache(bookId); - // Phase 4: Update state - progress.status = "completed"; - onProgress?.(progress); - + // Phase 4: Persist the final state before publishing completion. await callbacks.onBookUpdate(bookId, { isVectorized: true, vectorizeProgress: 1, }); + + progress.status = "completed"; + onProgress?.(progress); eventBus.emit("vectorize:completed", { bookId, chunksCount: allChunks.length, diff --git a/packages/foliate-js/mobi.js b/packages/foliate-js/mobi.js index 388a848f7..ac2529d7e 100644 --- a/packages/foliate-js/mobi.js +++ b/packages/foliate-js/mobi.js @@ -27,6 +27,8 @@ const PALMDOC_HEADER = { encryption: [12, 2, "uint"], }; +const ENCRYPTED_MOBI_ERROR = "Encrypted MOBI records are not supported"; + const MOBI_HEADER = { magic: [16, 4, "string"], length: [20, 4, "uint"], @@ -641,6 +643,8 @@ export class MOBI extends PDB { await super.open(file); // TODO: if (this.pdb.type === 'TEXt') this.headers = this.#getHeaders(await super.loadRecord(0)); + if (this.headers.palmdoc.encryption !== 0) + throw new Error(ENCRYPTED_MOBI_ERROR); this.#resourceStart = this.headers.mobi.resourceStart; let isKF8 = this.headers.mobi.version >= 8; if (!isKF8) { @@ -649,9 +653,12 @@ export class MOBI extends PDB { try { // it's a "combo" MOBI/KF8 file; try to open the KF8 part this.headers = this.#getHeaders(await super.loadRecord(boundary)); + if (this.headers.palmdoc.encryption !== 0) + throw new Error(ENCRYPTED_MOBI_ERROR); this.#start = boundary; isKF8 = true; } catch (e) { + if (e instanceof Error && e.message === ENCRYPTED_MOBI_ERROR) throw e; console.warn(e); console.warn("Failed to open KF8; falling back to MOBI"); } From 467fe02aec6e9272484898afaef5a91b6885c079 Mon Sep 17 00:00:00 2001 From: Chai Date: Sun, 16 Aug 2026 20:59:04 -0400 Subject: [PATCH 06/10] fix(rag): publish durable vector state --- packages/app-expo/src/stores/library-store.ts | 2 +- .../library-store.vectorization.test.ts | 113 ++++++++++++++++++ 2 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 packages/app-expo/src/stores/library-store.vectorization.test.ts diff --git a/packages/app-expo/src/stores/library-store.ts b/packages/app-expo/src/stores/library-store.ts index 1dc11b02b..531c13914 100644 --- a/packages/app-expo/src/stores/library-store.ts +++ b/packages/app-expo/src/stores/library-store.ts @@ -829,6 +829,7 @@ export const useLibraryStore = create((set, get) => ({ }, updateBookStrict: async (bookId, updates) => { + await persistBookUpdate(bookId, updates); set((state) => ({ books: state.books.map((b) => (b.id === bookId ? { ...b, ...updates } : b)), allTags: @@ -837,7 +838,6 @@ export const useLibraryStore = create((set, get) => ({ : state.allTags, })); debouncedSave("library-books", get().books); - await persistBookUpdate(bookId, updates); }, setFilter: (filter) => set((state) => ({ filter: { ...state.filter, ...filter } })), diff --git a/packages/app-expo/src/stores/library-store.vectorization.test.ts b/packages/app-expo/src/stores/library-store.vectorization.test.ts new file mode 100644 index 000000000..88c94c648 --- /dev/null +++ b/packages/app-expo/src/stores/library-store.vectorization.test.ts @@ -0,0 +1,113 @@ +import type { Book } from "@readany/core/types"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +const mocks = vi.hoisted(() => ({ + updateBook: vi.fn(), + debouncedSave: vi.fn(), +})); + +vi.mock("@/lib/book/metadata-extractor", () => ({ + createRangeReadableFile: vi.fn(), + extractBookMetadata: vi.fn(), + extractBookMetadataFromFile: vi.fn(), +})); + +vi.mock("@/lib/rag/auto-vectorize-service", () => ({ queueBook: vi.fn() })); +vi.mock("@/lib/rag/mobile-vectorize-capability", () => ({ + getMobileVectorizeCapability: vi.fn(), +})); +vi.mock("./vector-model-store", () => ({ + useVectorModelStore: { getState: vi.fn() }, +})); + +vi.mock("@readany/core/db/database", () => ({ + updateBook: mocks.updateBook, +})); + +vi.mock("@readany/core/db/write-retry", () => ({ + runWithDbRetry: (operation: () => Promise) => operation(), +})); + +vi.mock("./persist", () => ({ + debouncedSave: mocks.debouncedSave, + loadFromFS: vi.fn(), +})); + +import { useLibraryStore } from "./library-store"; + +const book: Book = { + id: "book-1", + filePath: "books/book.mobi", + format: "mobi", + meta: { title: "Book", author: "Author" }, + addedAt: 1, + updatedAt: 1, + progress: 0, + isVectorized: false, + vectorizeProgress: 0, + tags: [], + syncStatus: "local", +}; + +describe("library store strict vectorization updates", () => { + beforeEach(() => { + vi.clearAllMocks(); + useLibraryStore.getState().setBooks([{ ...book }]); + }); + + afterEach(() => { + useLibraryStore.getState().setBooks([]); + }); + + it("keeps completion invisible until the database write resolves", async () => { + let resolveWrite: (() => void) | undefined; + mocks.updateBook.mockImplementation( + () => + new Promise((resolve) => { + resolveWrite = resolve; + }), + ); + const visibleVectorizedStates: boolean[] = []; + const unsubscribe = useLibraryStore.subscribe((state) => { + if (state.books[0]?.isVectorized) visibleVectorizedStates.push(true); + }); + + const update = useLibraryStore.getState().updateBookStrict("book-1", { + isVectorized: true, + vectorizeProgress: 1, + }); + + expect(useLibraryStore.getState().books[0]?.isVectorized).toBe(false); + expect(mocks.debouncedSave).not.toHaveBeenCalled(); + expect(visibleVectorizedStates).toEqual([]); + + resolveWrite?.(); + await update; + + expect(useLibraryStore.getState().books[0]?.isVectorized).toBe(true); + expect(visibleVectorizedStates).toEqual([true]); + expect(mocks.debouncedSave).toHaveBeenCalledOnce(); + unsubscribe(); + }); + + it("does not expose or cache completion when the database write rejects", async () => { + mocks.updateBook.mockRejectedValue(new Error("database write failed")); + const visibleVectorizedStates: boolean[] = []; + const unsubscribe = useLibraryStore.subscribe((state) => { + if (state.books[0]?.isVectorized) visibleVectorizedStates.push(true); + }); + + await expect( + useLibraryStore.getState().updateBookStrict("book-1", { + isVectorized: true, + vectorizeProgress: 1, + }), + ).rejects.toThrow("database write failed"); + + expect(useLibraryStore.getState().books[0]?.isVectorized).toBe(false); + expect(useLibraryStore.getState().books[0]?.vectorizeProgress).toBe(0); + expect(visibleVectorizedStates).toEqual([]); + expect(mocks.debouncedSave).not.toHaveBeenCalled(); + unsubscribe(); + }); +}); From 660f31792dda93b143c83a328aac79b80c640698 Mon Sep 17 00:00:00 2001 From: Chai Date: Sun, 16 Aug 2026 21:09:29 -0400 Subject: [PATCH 07/10] test(rag): prove MOBI-family extraction --- .../src/lib/rag/__fixtures__/README.md | 18 ++ .../lib/rag/__fixtures__/gutenberg-11.azw3 | Bin 0 -> 256060 bytes .../lib/rag/__fixtures__/gutenberg-11.mobi | Bin 0 -> 240898 bytes .../rag/mobi-extraction.integration.test.ts | 166 ++++++++++++++++++ 4 files changed, 184 insertions(+) create mode 100644 packages/app-expo/src/lib/rag/__fixtures__/README.md create mode 100644 packages/app-expo/src/lib/rag/__fixtures__/gutenberg-11.azw3 create mode 100644 packages/app-expo/src/lib/rag/__fixtures__/gutenberg-11.mobi create mode 100644 packages/app-expo/src/lib/rag/mobi-extraction.integration.test.ts diff --git a/packages/app-expo/src/lib/rag/__fixtures__/README.md b/packages/app-expo/src/lib/rag/__fixtures__/README.md new file mode 100644 index 000000000..92f46bd7f --- /dev/null +++ b/packages/app-expo/src/lib/rag/__fixtures__/README.md @@ -0,0 +1,18 @@ +# MOBI-family integration fixtures + +These are byte-for-byte Project Gutenberg downloads of ebook 11, *Alice's Adventures in Wonderland* by Lewis Carroll. They were retrieved on 2026-08-16 and are used only to prove ReadAny's real foliate MOBI-family extraction path. + +| Fixture | Official acquisition URL | Resolved Project Gutenberg file | Bytes | SHA-256 | +| --- | --- | --- | ---: | --- | +| `gutenberg-11.mobi` | | | 240,898 | `4cc3901c405178935a0d4b25ac03bdafc776e0ec3ce81b24482844f2a47ecd13` | +| `gutenberg-11.azw3` | | | 256,060 | `fffee390f393ecf004f65c7fcd2cbefb3ee2652ff6f3fa8daa09c8a9a5644df0` | + +## Byte identity + +Both files have the Palm Database type/creator bytes `BOOKMOBI` and PalmDOC encryption value `0` (unencrypted). The older-Kindle download declares MOBI version 6. The KF8 download declares MOBI version 8 and is stored here with the `.azw3` extension so the integration test covers ReadAny's AZW3 input path without converting or modifying the source bytes. + +Project Gutenberg currently publishes the KF8 acquisition with a `.mobi` filename. The Library of Congress format description records that Amazon registered `application/vnd.amazon.mobi8-ebook` for the MOBI version that uses the `.azw3` extension: . That byte-level version evidence, rather than a fabricated conversion, is why the untouched KF8 download is the AZW3 fixture. + +## Rights and repository suitability + +Project Gutenberg's ebook page identifies this title as public domain in the USA: . Each fixture includes the Project Gutenberg License and its distribution terms in the ebook text. The two fixtures total 496,958 bytes (about 485 KiB), small enough for deterministic upstream integration coverage while retaining real MOBI v6 and KF8 containers, metadata, sections, compression, and images. diff --git a/packages/app-expo/src/lib/rag/__fixtures__/gutenberg-11.azw3 b/packages/app-expo/src/lib/rag/__fixtures__/gutenberg-11.azw3 new file mode 100644 index 0000000000000000000000000000000000000000..36f5e4e81258b50de8b88ea7390f539bd371824f GIT binary patch literal 256060 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 literal 0 HcmV?d00001 diff --git a/packages/app-expo/src/lib/rag/mobi-extraction.integration.test.ts b/packages/app-expo/src/lib/rag/mobi-extraction.integration.test.ts new file mode 100644 index 000000000..811cacf04 --- /dev/null +++ b/packages/app-expo/src/lib/rag/mobi-extraction.integration.test.ts @@ -0,0 +1,166 @@ +import { readFileSync } from "node:fs"; +import { fileURLToPath } from "node:url"; +import { afterAll, beforeAll, describe, expect, it, vi } from "vitest"; +import { MOBI } from "../../../../foliate-js/mobi.js"; +import { unzlibSync } from "../../../../foliate-js/vendor/fflate.js"; +import { BookExtractionError } from "./extractor-error"; +import { runVectorizeQueueJob } from "./vectorize-queue-job"; + +const FIXTURE_URL = new URL("./__fixtures__/", import.meta.url); + +function decodeHtmlEntities(value: string): string { + return value + .replace(/&#(x?[\da-f]+);/gi, (_match, digits: string) => { + const hexadecimal = digits[0]?.toLowerCase() === "x"; + return String.fromCodePoint( + Number.parseInt(hexadecimal ? digits.slice(1) : digits, hexadecimal ? 16 : 10), + ); + }) + .replace(/&(amp|apos|gt|lt|nbsp|quot);/gi, (_match, entity: string) => { + const values: Record = { + amp: "&", + apos: "'", + gt: ">", + lt: "<", + nbsp: " ", + quot: '"', + }; + return values[entity.toLowerCase()] ?? _match; + }); +} + +function htmlToText(value: string): string { + return decodeHtmlEntities( + value + .replace(//gi, " ") + .replace(//gi, " ") + .replace(/<[^>]+>/g, " "), + ); +} + +class TextOnlyDocument { + readonly documentElement: { textContent: string }; + + constructor(source: string) { + this.documentElement = { textContent: htmlToText(source) }; + } + + getElementsByTagName(): never[] { + return []; + } + + querySelectorAll(): never[] { + return []; + } +} + +class TextOnlyDomParser { + parseFromString(source: string): TextOnlyDocument { + return new TextOnlyDocument(source); + } +} + +function installParserTestDom() { + vi.stubGlobal("DOMParser", TextOnlyDomParser); + vi.stubGlobal("XMLSerializer", class {}); + vi.stubGlobal("CSS", { escape: (value: string) => value }); + vi.stubGlobal("document", { + createElement: () => { + let html = ""; + return { + set innerHTML(value: string) { + html = value; + }, + get value() { + return decodeHtmlEntities(html); + }, + }; + }, + }); +} + +async function extractFixtureSections(fixture: string): Promise { + const fixturePath = fileURLToPath(new URL(fixture, FIXTURE_URL).href); + const bytes = new Uint8Array(readFileSync(fixturePath)); + const book = await new MOBI({ unzlib: unzlibSync }).open( + new Blob([bytes], { type: "application/x-mobipocket-ebook" }), + ); + + const sections: string[] = []; + for (const section of book.sections) { + if (!section.createDocument) continue; + const document = await section.createDocument(); + const text = document.documentElement?.textContent?.replace(/\s+/g, " ").trim(); + if (text) sections.push(text); + } + return sections; +} + +function writeAscii(bytes: Uint8Array, offset: number, value: string) { + for (let index = 0; index < value.length; index++) { + bytes[offset + index] = value.charCodeAt(index); + } +} + +function createProtectedMobiFile(): Blob { + const bytes = new Uint8Array(512); + const view = new DataView(bytes.buffer); + const recordOffset = 86; + + writeAscii(bytes, 60, "BOOKMOBI"); + view.setUint16(76, 1); + view.setUint32(78, recordOffset); + view.setUint16(recordOffset, 1); + view.setUint16(recordOffset + 12, 1); + writeAscii(bytes, recordOffset + 16, "MOBI"); + view.setUint32(recordOffset + 20, 232); + view.setUint32(recordOffset + 28, 65001); + view.setUint32(recordOffset + 36, 6); + + return new Blob([bytes], { type: "application/x-mobipocket-ebook" }); +} + +describe("MOBI-family extraction fixtures", () => { + beforeAll(installParserTestDom); + afterAll(() => vi.unstubAllGlobals()); + + it.each(["gutenberg-11.mobi", "gutenberg-11.azw3"])( + "extracts ordered Alice text from %s with the foliate MOBI loader", + async (fixture) => { + const sections = await extractFixtureSections(fixture); + + expect(sections.length).toBeGreaterThan(10); + const firstChapter = sections.findIndex((text) => + text.includes("Alice was beginning to get very tired"), + ); + const secondChapter = sections.findIndex((text) => text.includes("Curiouser and curiouser")); + const queenChapter = sections.findIndex((text) => + text.includes("The Queen turned crimson with fury"), + ); + expect(firstChapter).toBeGreaterThanOrEqual(0); + expect(secondChapter).toBeGreaterThan(firstChapter); + expect(queenChapter).toBeGreaterThan(secondChapter); + }, + ); + + it("rejects a bounded protected header, cleans up, and never vectorizes", async () => { + const parser = new MOBI({ unzlib: unzlibSync }); + const cleanup = vi.fn().mockResolvedValue(undefined); + const vectorize = vi.fn(); + + const result = await runVectorizeQueueJob({ + format: "mobi", + extract: async () => (await parser.open(createProtectedMobiFile())) as never, + vectorize, + cleanup, + onEvent: vi.fn(), + }); + + expect(result.ok).toBe(false); + if (result.ok) throw new Error("Expected protected extraction to fail"); + expect(result.error).toBeInstanceOf(BookExtractionError); + expect(result.error).toMatchObject({ category: "drm-protected" }); + expect(cleanup).toHaveBeenCalledOnce(); + expect(vectorize).not.toHaveBeenCalled(); + }); +}); From 1dbd13e359015be0d16a809e3e56fa93d1d90162 Mon Sep 17 00:00:00 2001 From: Chai Date: Sun, 16 Aug 2026 21:40:20 -0400 Subject: [PATCH 08/10] fix(rag): add cancellable durable vectorization --- packages/app-expo/assets/reader/reader.html | 43 ++++++++--- .../assets/reader/reader.template.html | 43 ++++++++--- .../src/components/library/BookCard.tsx | 62 +++++++++++---- .../src/components/rag/ExtractorWebView.tsx | 71 +++++++++++++++-- .../src/lib/rag/auto-vectorize-service.ts | 34 ++++++--- .../extractor-cancellation-boundary.test.ts | 16 ++++ .../src/lib/rag/extractor-format.test.ts | 14 ++-- .../app-expo/src/lib/rag/extractor-format.ts | 8 -- .../src/lib/rag/vectorize-queue-job.test.ts | 50 ++++++++++-- .../src/lib/rag/vectorize-queue-job.ts | 73 +++++++++++++----- .../app-expo/src/lib/rag/vectorize-trigger.ts | 5 +- .../app-expo/src/screens/LibraryScreen.tsx | 7 +- .../screens/library/useVectorizationQueue.ts | 71 ++++++++++++----- .../library/vectorization-queue.test.ts | 29 +++++++ .../screens/library/vectorization-queue.ts | 44 +++++++++++ packages/app-expo/src/stores/library-store.ts | 16 ++++ .../library-store.vectorization.test.ts | 20 +++++ .../app/src/lib/rag/vectorize-trigger.test.ts | 67 ++++++++++++++++ packages/app/src/lib/rag/vectorize-trigger.ts | 3 +- packages/app/src/stores/library-store.ts | 29 +++++++ .../library-store.vectorization.test.ts | 61 +++++++++++++++ .../core/src/i18n/locales/en/library.json | 4 + .../core/src/i18n/locales/es/library.json | 4 + .../core/src/i18n/locales/fr/library.json | 4 + .../core/src/i18n/locales/ja/library.json | 4 + .../core/src/i18n/locales/ko/library.json | 4 + .../core/src/i18n/locales/zh-TW/library.json | 4 + .../core/src/i18n/locales/zh/library.json | 4 + packages/core/src/rag/remote-embedding.ts | 2 + .../core/src/rag/vectorize-trigger.test.ts | 76 +++++++++++++++++++ packages/core/src/rag/vectorize-trigger.ts | 72 ++++++++++++++++-- packages/core/src/types/rag.ts | 10 ++- packages/core/src/utils/event-bus.ts | 1 + 33 files changed, 835 insertions(+), 120 deletions(-) create mode 100644 packages/app-expo/src/lib/rag/extractor-cancellation-boundary.test.ts create mode 100644 packages/app-expo/src/screens/library/vectorization-queue.test.ts create mode 100644 packages/app-expo/src/screens/library/vectorization-queue.ts create mode 100644 packages/app/src/lib/rag/vectorize-trigger.test.ts create mode 100644 packages/app/src/stores/library-store.vectorization.test.ts diff --git a/packages/app-expo/assets/reader/reader.html b/packages/app-expo/assets/reader/reader.html index ef5df0912..029dccb60 100644 --- a/packages/app-expo/assets/reader/reader.html +++ b/packages/app-expo/assets/reader/reader.html @@ -212,6 +212,7 @@ let bookTextMetricsTimer = null; let bookmarkPullGestureActive = false; let pullBookmarkResetTimer = null; + const cancelledExtractionRequests = new Set(); let refreshAnnotationsTimer = null; let activeFootnoteTipKey = null; let bookmarkPullStateMeta = { @@ -1368,6 +1369,9 @@ case 'extractBookChapters': await handleExtractBookChapters(msg); break; + case 'cancelExtraction': + if (msg.requestId) cancelledExtractionRequests.add(msg.requestId); + break; case 'getChapterParagraphs': handleGetChapterParagraphs(); break; @@ -1443,12 +1447,13 @@ } try { + throwIfExtractionCancelled(msg.requestId); let hasSignalledLoaded = false; const markLoaded = () => { if (loading) loading.classList.add('hidden'); if (!hasSignalledLoaded) { hasSignalledLoaded = true; - postToRN('loaded', {}); + postToRN('loaded', { requestId: msg.requestId }); } }; @@ -1557,6 +1562,7 @@ } const book = (file && file.sections) ? file : await makeBook(file); + throwIfExtractionCancelled(msg.requestId); currentBook = book; attachBookTransformHandler(book); @@ -1797,7 +1803,8 @@ console.error('[WebView] Error in openBook:', err); const message = `${String(err)}${msg?.uri ? ` (${msg.uri})` : ''}`; loading.innerHTML = '

'; - postToRN('error', { message }); + postToRN('error', { message, requestId: msg.requestId }); + cancelledExtractionRequests.delete(msg.requestId); } } @@ -4271,7 +4278,9 @@ } async function handleExtractBookChapters(msg) { + const requestId = msg.requestId; try { + throwIfExtractionCancelled(requestId); if (isPDFBookMessage(msg)) { if (typeof window._extractPDFChapters !== 'function') { throw new Error('PDF extraction is not available in this reader build'); @@ -4291,6 +4300,7 @@ postToRN('debug', { message: `[PDFExtract] page ${JSON.stringify(detail)}` }); } }); + throwIfExtractionCancelled(requestId); postToRN('debug', { message: `[PDFExtract] done ${JSON.stringify({ @@ -4300,25 +4310,36 @@ })}` }); - postToRN('chaptersExtracted', { chapters }); + postToRN('chaptersExtracted', { requestId, chapters }); return; } currentBook = await makeBook(await createBookFileFromMessage(msg)); - await handleExtractChapters(); + throwIfExtractionCancelled(requestId); + await handleExtractChapters(requestId); } catch (err) { console.error('[WebView] Error extracting book chapters:', err); - postToRN('chaptersExtracted', { error: String(err) }); + postToRN('chaptersExtracted', { requestId, error: String(err) }); + } finally { + cancelledExtractionRequests.delete(requestId); } } - async function handleExtractChapters() { + function throwIfExtractionCancelled(requestId) { + if (!requestId || !cancelledExtractionRequests.has(requestId)) return; + const error = new Error('Vectorization cancelled'); + error.name = 'AbortError'; + throw error; + } + + async function handleExtractChapters(requestId) { if (!currentBook) { - postToRN('chaptersExtracted', { error: 'No book loaded' }); + postToRN('chaptersExtracted', { requestId, error: 'No book loaded' }); return; } try { + throwIfExtractionCancelled(requestId); const sections = currentBook.sections || []; const toc = currentBook.toc || []; @@ -4345,6 +4366,7 @@ let skippedNoCreateDocument = 0; for (let i = 0; i < sections.length; i++) { + throwIfExtractionCancelled(requestId); const section = sections[i]; if (!section.createDocument) { skippedNoCreateDocument += 1; @@ -4353,6 +4375,7 @@ try { const doc = await section.createDocument(); + throwIfExtractionCancelled(requestId); if (!doc.body) continue; const title = tocMap.get(i) || tocMap.get(section.href || "") || `Section ${i + 1}`; @@ -4386,10 +4409,12 @@ skippedNoCreateDocument }); - postToRN('chaptersExtracted', { chapters }); + postToRN('chaptersExtracted', { requestId, chapters }); } catch (err) { console.error('[WebView] Error extracting chapters:', err); - postToRN('chaptersExtracted', { error: String(err) }); + postToRN('chaptersExtracted', { requestId, error: String(err) }); + } finally { + cancelledExtractionRequests.delete(requestId); } } diff --git a/packages/app-expo/assets/reader/reader.template.html b/packages/app-expo/assets/reader/reader.template.html index 4ce4da314..837a90a19 100644 --- a/packages/app-expo/assets/reader/reader.template.html +++ b/packages/app-expo/assets/reader/reader.template.html @@ -212,6 +212,7 @@ let bookTextMetricsTimer = null; let bookmarkPullGestureActive = false; let pullBookmarkResetTimer = null; + const cancelledExtractionRequests = new Set(); let refreshAnnotationsTimer = null; let activeFootnoteTipKey = null; let bookmarkPullStateMeta = { @@ -1368,6 +1369,9 @@ case 'extractBookChapters': await handleExtractBookChapters(msg); break; + case 'cancelExtraction': + if (msg.requestId) cancelledExtractionRequests.add(msg.requestId); + break; case 'getChapterParagraphs': handleGetChapterParagraphs(); break; @@ -1443,12 +1447,13 @@ } try { + throwIfExtractionCancelled(msg.requestId); let hasSignalledLoaded = false; const markLoaded = () => { if (loading) loading.classList.add('hidden'); if (!hasSignalledLoaded) { hasSignalledLoaded = true; - postToRN('loaded', {}); + postToRN('loaded', { requestId: msg.requestId }); } }; @@ -1557,6 +1562,7 @@ } const book = (file && file.sections) ? file : await makeBook(file); + throwIfExtractionCancelled(msg.requestId); currentBook = book; attachBookTransformHandler(book); @@ -1797,7 +1803,8 @@ console.error('[WebView] Error in openBook:', err); const message = `${String(err)}${msg?.uri ? ` (${msg.uri})` : ''}`; loading.innerHTML = '
' + escapeHtml(message) + '
'; - postToRN('error', { message }); + postToRN('error', { message, requestId: msg.requestId }); + cancelledExtractionRequests.delete(msg.requestId); } } @@ -4271,7 +4278,9 @@ } async function handleExtractBookChapters(msg) { + const requestId = msg.requestId; try { + throwIfExtractionCancelled(requestId); if (isPDFBookMessage(msg)) { if (typeof window._extractPDFChapters !== 'function') { throw new Error('PDF extraction is not available in this reader build'); @@ -4291,6 +4300,7 @@ postToRN('debug', { message: `[PDFExtract] page ${JSON.stringify(detail)}` }); } }); + throwIfExtractionCancelled(requestId); postToRN('debug', { message: `[PDFExtract] done ${JSON.stringify({ @@ -4300,25 +4310,36 @@ })}` }); - postToRN('chaptersExtracted', { chapters }); + postToRN('chaptersExtracted', { requestId, chapters }); return; } currentBook = await makeBook(await createBookFileFromMessage(msg)); - await handleExtractChapters(); + throwIfExtractionCancelled(requestId); + await handleExtractChapters(requestId); } catch (err) { console.error('[WebView] Error extracting book chapters:', err); - postToRN('chaptersExtracted', { error: String(err) }); + postToRN('chaptersExtracted', { requestId, error: String(err) }); + } finally { + cancelledExtractionRequests.delete(requestId); } } - async function handleExtractChapters() { + function throwIfExtractionCancelled(requestId) { + if (!requestId || !cancelledExtractionRequests.has(requestId)) return; + const error = new Error('Vectorization cancelled'); + error.name = 'AbortError'; + throw error; + } + + async function handleExtractChapters(requestId) { if (!currentBook) { - postToRN('chaptersExtracted', { error: 'No book loaded' }); + postToRN('chaptersExtracted', { requestId, error: 'No book loaded' }); return; } try { + throwIfExtractionCancelled(requestId); const sections = currentBook.sections || []; const toc = currentBook.toc || []; @@ -4345,6 +4366,7 @@ let skippedNoCreateDocument = 0; for (let i = 0; i < sections.length; i++) { + throwIfExtractionCancelled(requestId); const section = sections[i]; if (!section.createDocument) { skippedNoCreateDocument += 1; @@ -4353,6 +4375,7 @@ try { const doc = await section.createDocument(); + throwIfExtractionCancelled(requestId); if (!doc.body) continue; const title = tocMap.get(i) || tocMap.get(section.href || "") || `Section ${i + 1}`; @@ -4386,10 +4409,12 @@ skippedNoCreateDocument }); - postToRN('chaptersExtracted', { chapters }); + postToRN('chaptersExtracted', { requestId, chapters }); } catch (err) { console.error('[WebView] Error extracting chapters:', err); - postToRN('chaptersExtracted', { error: String(err) }); + postToRN('chaptersExtracted', { requestId, error: String(err) }); + } finally { + cancelledExtractionRequests.delete(requestId); } } diff --git a/packages/app-expo/src/components/library/BookCard.tsx b/packages/app-expo/src/components/library/BookCard.tsx index 746f0a141..b7ad23e1b 100644 --- a/packages/app-expo/src/components/library/BookCard.tsx +++ b/packages/app-expo/src/components/library/BookCard.tsx @@ -54,6 +54,7 @@ interface BookCardProps { onShowDetails?: (book: Book) => void; onManageTags?: (book: Book) => void; onVectorize?: (book: Book) => void; + onCancelVectorize?: (bookId: string) => void; isVectorizing?: boolean; isQueued?: boolean; vectorProgress?: { status: string; processedChunks: number; totalChunks: number } | null; @@ -72,6 +73,7 @@ export const BookCard = memo(function BookCard({ onShowDetails, onManageTags, onVectorize, + onCancelVectorize, isVectorizing, isQueued, vectorProgress, @@ -288,30 +290,58 @@ export const BookCard = memo(function BookCard({ {/* Vectorization progress overlay */} {isVectorizing && ( - - + { + event.stopPropagation(); + onCancelVectorize?.(book.id); + }} + > + {vectorProgress?.status !== "cancelled" && } - {vectorProgress?.status === "chunking" - ? `${vecPct}%` - : vectorProgress?.status === "embedding" - ? `${vecPct}%` - : vectorProgress?.status === "indexing" - ? t("home.vec_indexing") - : vectorProgress?.status === "completed" - ? "✓" - : vectorProgress?.status === "error" - ? "✗" - : t("home.vec_processing")} + {vectorProgress?.status === "cancelling" + ? t("home.vec_cancelling", "Cancelling…") + : vectorProgress?.status === "cancelled" + ? t("home.vec_cancelled", "Cancelled") + : vectorProgress?.status === "chunking" + ? `${vecPct}%` + : vectorProgress?.status === "embedding" + ? `${vecPct}%` + : vectorProgress?.status === "indexing" + ? t("home.vec_indexing") + : vectorProgress?.status === "completed" + ? "✓" + : vectorProgress?.status === "error" + ? "✗" + : t("home.vec_processing")} - + {vectorProgress?.status !== "cancelling" && + vectorProgress?.status !== "cancelled" && ( + {t("home.vec_cancel", "Cancel")} + )} + )} {/* Queued overlay */} {isQueued && !isVectorizing && ( - + { + event.stopPropagation(); + onCancelVectorize?.(book.id); + }} + > {t("home.vec_queued", "排队中")} - + {t("home.vec_cancel", "Cancel")} + )} {/* Remote status overlay (on-demand download) */} diff --git a/packages/app-expo/src/components/rag/ExtractorWebView.tsx b/packages/app-expo/src/components/rag/ExtractorWebView.tsx index 4d2fd9e39..affa694f6 100644 --- a/packages/app-expo/src/components/rag/ExtractorWebView.tsx +++ b/packages/app-expo/src/components/rag/ExtractorWebView.tsx @@ -5,7 +5,7 @@ import { forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useSta import { StyleSheet, View } from "react-native"; import { WebView } from "react-native-webview"; import { toBookExtractionError } from "../../lib/rag/extractor-error"; -import { createExtractorRequest } from "../../lib/rag/extractor-format"; +import { createExtractorCommand } from "../../lib/rag/extractor-format"; const READER_HTML_ASSET = Asset.fromModule(require("../../../assets/reader/reader.html")); const EXTRACTION_TIMEOUT_MS = 45_000; @@ -16,14 +16,29 @@ export interface ExtractorRef { mimeType?: string, bookFormat?: Book["format"], fileName?: string, + signal?: AbortSignal, ) => Promise; } interface PendingExtraction { + requestId: string; resolve: (chapters: ChapterData[]) => void; reject: (err: Error) => void; timeoutId: ReturnType; bookFormat?: Book["format"]; + signal?: AbortSignal; + abortHandler?: () => void; +} + +function getAbortError(signal: AbortSignal): Error { + const reason = signal.reason; + if (reason instanceof Error) { + if (reason.name !== "AbortError") reason.name = "AbortError"; + return reason; + } + const error = new Error("Vectorization cancelled"); + error.name = "AbortError"; + return error; } export const ExtractorWebView = forwardRef((_, ref) => { @@ -38,6 +53,9 @@ export const ExtractorWebView = forwardRef((_, ref) => { return () => { for (const pending of pendingRequests.current) { clearTimeout(pending.timeoutId); + if (pending.abortHandler) { + pending.signal?.removeEventListener("abort", pending.abortHandler); + } pending.reject( toBookExtractionError(new Error("Extractor WebView unmounted"), pending.bookFormat), ); @@ -67,20 +85,30 @@ export const ExtractorWebView = forwardRef((_, ref) => { if (msg.type === "ready") { setReady(true); } else if (msg.type === "loaded") { + const pending = pendingRequests.current.find( + (request) => request.requestId === msg.requestId, + ); + if (!pending) return; // Trigger extraction once the book is fully loaded webViewRef.current?.injectJavaScript(` if (window.handleExtractChapters) { - window.handleExtractChapters(); + window.handleExtractChapters(${JSON.stringify(msg.requestId)}); } else { - window.ReactNativeWebView.postMessage(JSON.stringify({ type: 'chaptersExtracted', error: 'Extraction not supported' })); + window.ReactNativeWebView.postMessage(JSON.stringify({ type: 'chaptersExtracted', requestId: ${JSON.stringify(msg.requestId)}, error: 'Extraction not supported' })); } true; `); } else if (msg.type === "chaptersExtracted") { - const pending = pendingRequests.current.shift(); + const index = pendingRequests.current.findIndex( + (request) => request.requestId === msg.requestId, + ); + const pending = index >= 0 ? pendingRequests.current.splice(index, 1)[0] : undefined; if (!pending) return; clearTimeout(pending.timeoutId); + if (pending.abortHandler) { + pending.signal?.removeEventListener("abort", pending.abortHandler); + } if (msg.error) { pending.reject(toBookExtractionError(new Error(String(msg.error)), pending.bookFormat)); } else if (msg.chapters) { @@ -90,9 +118,15 @@ export const ExtractorWebView = forwardRef((_, ref) => { console.log("[ExtractorWebView]", msg.message); } else if (msg.type === "error") { console.error("[ExtractorWebView] WebView error:", msg.message); - const pending = pendingRequests.current.shift(); + const index = pendingRequests.current.findIndex( + (request) => request.requestId === msg.requestId, + ); + const pending = index >= 0 ? pendingRequests.current.splice(index, 1)[0] : undefined; if (pending) { clearTimeout(pending.timeoutId); + if (pending.abortHandler) { + pending.signal?.removeEventListener("abort", pending.abortHandler); + } pending.reject(toBookExtractionError(new Error(String(msg.message)), pending.bookFormat)); } } @@ -107,14 +141,19 @@ export const ExtractorWebView = forwardRef((_, ref) => { mimeType = "application/epub+zip", bookFormat?: Book["format"], fileName?: string, + signal?: AbortSignal, ) => { - const { command, classificationFormat } = createExtractorRequest({ + const requestId = `extract-${Date.now()}-${Math.random().toString(36).slice(2)}`; + const baseCommand = createExtractorCommand({ base64BookData, mimeType, bookFormat, fileName, }); + const command = { ...baseCommand, requestId }; + const classificationFormat = command.bookFormat ?? undefined; return new Promise((resolve, reject) => { + if (signal?.aborted) return reject(getAbortError(signal)); if (!ready || !webViewRef.current) { return reject( toBookExtractionError(new Error("Extractor WebView not ready"), classificationFormat), @@ -124,6 +163,7 @@ export const ExtractorWebView = forwardRef((_, ref) => { const timeoutId = setTimeout(() => { const index = pendingRequests.current.findIndex((pending) => pending.reject === reject); if (index >= 0) pendingRequests.current.splice(index, 1); + signal?.removeEventListener("abort", abortHandler); reject( toBookExtractionError( new Error("Timed out extracting book content"), @@ -132,13 +172,29 @@ export const ExtractorWebView = forwardRef((_, ref) => { ); }, EXTRACTION_TIMEOUT_MS); - const pendingRequest = { + const abortHandler = () => { + clearTimeout(timeoutId); + const index = pendingRequests.current.indexOf(pendingRequest); + if (index >= 0) pendingRequests.current.splice(index, 1); + webViewRef.current?.injectJavaScript(` + window.postMessage(${JSON.stringify( + JSON.stringify({ type: "cancelExtraction", requestId }), + )}, "*"); + true; + `); + reject(getAbortError(signal as AbortSignal)); + }; + const pendingRequest: PendingExtraction = { + requestId, resolve, reject, timeoutId, bookFormat: classificationFormat, + signal, + abortHandler, }; pendingRequests.current.push(pendingRequest); + signal?.addEventListener("abort", abortHandler, { once: true }); // Command the webview to open the book first. // It will reply with "loaded" when it finishes rendering. @@ -151,6 +207,7 @@ export const ExtractorWebView = forwardRef((_, ref) => { clearTimeout(timeoutId); const index = pendingRequests.current.indexOf(pendingRequest); if (index >= 0) pendingRequests.current.splice(index, 1); + signal?.removeEventListener("abort", abortHandler); reject(toBookExtractionError(error, classificationFormat)); } }); diff --git a/packages/app-expo/src/lib/rag/auto-vectorize-service.ts b/packages/app-expo/src/lib/rag/auto-vectorize-service.ts index 152883012..15ced9d55 100644 --- a/packages/app-expo/src/lib/rag/auto-vectorize-service.ts +++ b/packages/app-expo/src/lib/rag/auto-vectorize-service.ts @@ -1,7 +1,7 @@ import type { ChapterData } from "@readany/core/rag"; import type { Book } from "@readany/core/types"; import type { BookExtractionErrorCategory } from "./extractor-error"; -import { runVectorizeQueueJob } from "./vectorize-queue-job"; +import { runVectorizeQueueJob, throwIfQueueJobAborted } from "./vectorize-queue-job"; export type AutoVectorizeCallback = ( bookId: string, @@ -14,6 +14,7 @@ interface ExtractorRef { mimeType?: string, bookFormat?: Book["format"], fileName?: string, + signal?: AbortSignal, ) => Promise; } @@ -66,16 +67,29 @@ async function processQueue() { await runVectorizeQueueJob({ format: book.format, - extract: async () => { + extract: async (signal) => { + throwIfQueueJobAborted(signal); if (!extractorRef) throw new Error("Extractor WebView not ready"); - return extractorRef.extractChapters(base64Data, mimeType, book.format, book.filePath); + return extractorRef.extractChapters( + base64Data, + mimeType, + book.format, + book.filePath, + signal, + ); }, - vectorize: async (chapters, onProgress) => { - await triggerVectorizeBook(book.id, book.filePath, chapters, (progress) => { - const pct = - progress.totalChunks > 0 ? progress.processedChunks / progress.totalChunks : 0; - onProgress?.(pct); - }); + vectorize: async (chapters, onProgress, signal) => { + await triggerVectorizeBook( + book.id, + book.filePath, + chapters, + (progress) => { + const pct = + progress.totalChunks > 0 ? progress.processedChunks / progress.totalChunks : 0; + onProgress?.(pct); + }, + signal, + ); }, cleanup: () => resetBookVectorization(book.id), onEvent: (event) => { @@ -85,6 +99,8 @@ async function processQueue() { callback?.(book.id, { status: "vectorizing", progress: event.progress ?? 0 }); } else if (event.status === "completed") { callback?.(book.id, { status: "completed", progress: 1 }); + } else if (event.status === "cancelled") { + callback?.(book.id, { status: "cancelled", progress: 0 }); } else { console.error(`[AutoVectorize] Failed for ${book.meta.title}:`, event.error); if (event.cleanupError) { diff --git a/packages/app-expo/src/lib/rag/extractor-cancellation-boundary.test.ts b/packages/app-expo/src/lib/rag/extractor-cancellation-boundary.test.ts new file mode 100644 index 000000000..fd8cc49f1 --- /dev/null +++ b/packages/app-expo/src/lib/rag/extractor-cancellation-boundary.test.ts @@ -0,0 +1,16 @@ +import { readFileSync } from "node:fs"; +import { resolve } from "node:path"; +import { describe, expect, it } from "vitest"; + +describe("extractor cancellation boundary", () => { + it("routes request ids and checks cancellation inside reader extraction", () => { + const template = readFileSync( + resolve(process.cwd(), "assets/reader/reader.template.html"), + "utf8", + ); + + expect(template).toContain("case 'cancelExtraction'"); + expect(template).toContain("throwIfExtractionCancelled(requestId)"); + expect(template).toMatch(/postToRN\('chaptersExtracted', \{[^}]*requestId/s); + }); +}); diff --git a/packages/app-expo/src/lib/rag/extractor-format.test.ts b/packages/app-expo/src/lib/rag/extractor-format.test.ts index 463ff7e70..7f67a1552 100644 --- a/packages/app-expo/src/lib/rag/extractor-format.test.ts +++ b/packages/app-expo/src/lib/rag/extractor-format.test.ts @@ -1,9 +1,5 @@ import { describe, expect, it } from "vitest"; -import { - createExtractorCommand, - createExtractorRequest, - resolveExtractorFormat, -} from "./extractor-format"; +import { createExtractorCommand, resolveExtractorFormat } from "./extractor-format"; describe("resolveExtractorFormat", () => { it.each(["epub", "pdf", "txt", "umd", "mobi", "azw", "azw3"])( @@ -84,14 +80,14 @@ describe("createExtractorCommand", () => { }); }); -describe("createExtractorRequest", () => { +describe("extractor pending-request classification", () => { it.each([ [{ mimeType: "application/octet-stream", fileName: "inferred.mobi" }, "mobi"], [{ mimeType: "application/vnd.amazon.ebook" }, "azw3"], ])("preserves the resolved %s format for error classification", (input, expected) => { - const request = createExtractorRequest({ base64BookData: "data", ...input }); + const command = createExtractorCommand({ base64BookData: "data", ...input }); - expect(request.command.bookFormat).toBe(expected); - expect(request.classificationFormat).toBe(expected); + expect(command.bookFormat).toBe(expected); + expect(command.bookFormat ?? undefined).toBe(expected); }); }); diff --git a/packages/app-expo/src/lib/rag/extractor-format.ts b/packages/app-expo/src/lib/rag/extractor-format.ts index c09185908..19a882a11 100644 --- a/packages/app-expo/src/lib/rag/extractor-format.ts +++ b/packages/app-expo/src/lib/rag/extractor-format.ts @@ -82,11 +82,3 @@ export function createExtractorCommand(input: { fileName: getExtractorFileName(input.mimeType, resolvedFormat, input.fileName), }; } - -export function createExtractorRequest(input: Parameters[0]) { - const command = createExtractorCommand(input); - return { - command, - classificationFormat: command.bookFormat ?? undefined, - }; -} diff --git a/packages/app-expo/src/lib/rag/vectorize-queue-job.test.ts b/packages/app-expo/src/lib/rag/vectorize-queue-job.test.ts index 6dfe24913..c622ed15e 100644 --- a/packages/app-expo/src/lib/rag/vectorize-queue-job.test.ts +++ b/packages/app-expo/src/lib/rag/vectorize-queue-job.test.ts @@ -75,23 +75,61 @@ describe("runVectorizeQueueJob", () => { }, ); - it("treats unmount cancellation as an extraction rejection and never completes", async () => { + it("awaits cleanup before publishing cancellation during extraction", async () => { + const order: string[] = []; + const controller = new AbortController(); + let releaseCleanup: (() => void) | undefined; + const cleanupReleased = new Promise((resolve) => { + releaseCleanup = resolve; + }); + + const job = runVectorizeQueueJob({ + format: "mobi", + signal: controller.signal, + extract: (signal) => + new Promise((_, reject) => { + signal.addEventListener("abort", () => reject(signal.reason), { once: true }); + }), + vectorize: vi.fn(), + cleanup: async () => { + order.push("cleanup:start"); + await cleanupReleased; + order.push("cleanup:end"); + }, + onEvent: (event) => order.push(event.status), + }); + controller.abort(); + + await vi.waitFor(() => expect(order).toContain("cleanup:start")); + expect(order).not.toContain("cancelled"); + releaseCleanup?.(); + const result = await job; + + expect(result).toMatchObject({ ok: false, cancelled: true }); + expect(order).toEqual(["extracting", "cleanup:start", "cleanup:end", "cancelled"]); + expect(order).not.toContain("completed"); + }); + + it("cancels during vectorization without publishing completion", async () => { const statuses: string[] = []; + const controller = new AbortController(); const cleanup = vi.fn().mockResolvedValue(undefined); const result = await runVectorizeQueueJob({ format: "mobi", - extract: async () => { - throw new Error("Extractor WebView unmounted"); + signal: controller.signal, + extract: async () => [{ index: 0, title: "Chapter", content: "text", segments: [] }], + vectorize: async (_chapters, _onProgress, signal) => { + controller.abort(); + signal.throwIfAborted(); }, - vectorize: vi.fn(), cleanup, onEvent: (event) => statuses.push(event.status), }); - expect(result.ok).toBe(false); + expect(result).toMatchObject({ ok: false, cancelled: true }); expect(cleanup).toHaveBeenCalledOnce(); - expect(statuses).toEqual(["extracting", "error"]); + expect(statuses).toEqual(["extracting", "vectorizing", "cancelled"]); expect(statuses).not.toContain("completed"); }); diff --git a/packages/app-expo/src/lib/rag/vectorize-queue-job.ts b/packages/app-expo/src/lib/rag/vectorize-queue-job.ts index 1affc7ce9..adffb9a71 100644 --- a/packages/app-expo/src/lib/rag/vectorize-queue-job.ts +++ b/packages/app-expo/src/lib/rag/vectorize-queue-job.ts @@ -9,6 +9,7 @@ export type VectorizeQueueJobEvent = | { status: "extracting" } | { status: "vectorizing"; progress?: Progress } | { status: "completed" } + | { status: "cancelled" } | { status: "error"; error: unknown; @@ -18,16 +19,41 @@ export type VectorizeQueueJobEvent = export type VectorizeQueueJobResult = | { ok: true } - | { ok: false; error: unknown; cleanupError?: unknown }; + | { ok: false; error: unknown; cancelled?: boolean; cleanupError?: unknown }; interface VectorizeQueueJobOptions { format?: string; - extract: () => Promise; - vectorize: (chapters: ChapterData[], onProgress?: (progress: Progress) => void) => Promise; + signal?: AbortSignal; + extract: (signal: AbortSignal) => Promise; + vectorize: ( + chapters: ChapterData[], + onProgress: ((progress: Progress) => void) | undefined, + signal: AbortSignal, + ) => Promise; cleanup: () => Promise; onEvent: (event: VectorizeQueueJobEvent) => void; } +function createFallbackSignal(): AbortSignal { + return new AbortController().signal; +} + +export function throwIfQueueJobAborted(signal: AbortSignal): void { + if (!signal.aborted) return; + const reason = signal.reason; + if (reason instanceof Error) { + if (reason.name !== "AbortError") reason.name = "AbortError"; + throw reason; + } + const error = new Error("Vectorization cancelled"); + error.name = "AbortError"; + throw error; +} + +function isAbortFailure(error: unknown, signal: AbortSignal): boolean { + return signal.aborted || (error instanceof Error && error.name === "AbortError"); +} + function isCompletedProgress(progress: unknown): boolean { return ( typeof progress === "object" && @@ -41,25 +67,34 @@ export async function runVectorizeQueueJob( options: VectorizeQueueJobOptions, ): Promise { let phase: "extracting" | "vectorizing" = "extracting"; + const signal = options.signal ?? createFallbackSignal(); options.onEvent({ status: "extracting" }); try { - const chapters = await options.extract(); + throwIfQueueJobAborted(signal); + const chapters = await options.extract(signal); + throwIfQueueJobAborted(signal); if (!chapters.length) { throw toBookExtractionError(new Error("No chapters extracted from book"), options.format); } phase = "vectorizing"; options.onEvent({ status: "vectorizing" }); - await options.vectorize(chapters, (progress) => { - if (!isCompletedProgress(progress)) { - options.onEvent({ status: "vectorizing", progress }); - } - }); + await options.vectorize( + chapters, + (progress) => { + if (!isCompletedProgress(progress)) { + options.onEvent({ status: "vectorizing", progress }); + } + }, + signal, + ); + throwIfQueueJobAborted(signal); options.onEvent({ status: "completed" }); return { ok: true }; } catch (error) { + const cancelled = isAbortFailure(error, signal); const failure = phase === "extracting" ? toBookExtractionError(error, options.format) : error; let cleanupError: unknown; try { @@ -68,14 +103,18 @@ export async function runVectorizeQueueJob( cleanupError = errorDuringCleanup; } - options.onEvent({ - status: "error", - error: failure, - errorCategory: failure instanceof BookExtractionError ? failure.category : undefined, - cleanupError, - }); + if (cancelled) { + options.onEvent({ status: "cancelled" }); + } else { + options.onEvent({ + status: "error", + error: failure, + errorCategory: failure instanceof BookExtractionError ? failure.category : undefined, + cleanupError, + }); + } return cleanupError - ? { ok: false, error: failure, cleanupError } - : { ok: false, error: failure }; + ? { ok: false, error: failure, cancelled, cleanupError } + : { ok: false, error: failure, cancelled }; } } diff --git a/packages/app-expo/src/lib/rag/vectorize-trigger.ts b/packages/app-expo/src/lib/rag/vectorize-trigger.ts index 3481ec3a3..964ec6072 100644 --- a/packages/app-expo/src/lib/rag/vectorize-trigger.ts +++ b/packages/app-expo/src/lib/rag/vectorize-trigger.ts @@ -15,6 +15,7 @@ export type { VectorizeStatusCallback }; export async function resetBookVectorization(bookId: string): Promise { await coreResetBookVectorization(bookId, { onBookUpdate: useLibraryStore.getState().updateBookStrict, + onBookReset: useLibraryStore.getState().resetBookVectorizationState, }); } @@ -23,6 +24,7 @@ export async function triggerVectorizeBook( _filePath: string, chapters: ChapterData[], onProgress?: VectorizeStatusCallback, + signal?: AbortSignal, ): Promise { const vmState = useVectorModelStore.getState(); @@ -45,8 +47,9 @@ export async function triggerVectorizeBook( // 2. Build callbacks for state updates const callbacks = { onBookUpdate: useLibraryStore.getState().updateBookStrict, + onBookReset: useLibraryStore.getState().resetBookVectorizationState, }; // 3. Delegate to core vectorization pipeline which does the chunking & embedding - await coreTriggerVectorizeBook(bookId, chapters, config, callbacks, onProgress); + await coreTriggerVectorizeBook(bookId, chapters, config, callbacks, onProgress, signal); } diff --git a/packages/app-expo/src/screens/LibraryScreen.tsx b/packages/app-expo/src/screens/LibraryScreen.tsx index 26c8fd925..5e464e1b4 100644 --- a/packages/app-expo/src/screens/LibraryScreen.tsx +++ b/packages/app-expo/src/screens/LibraryScreen.tsx @@ -233,9 +233,8 @@ export function LibraryScreen() { onSuccess: () => {}, }); - const { vectorQueue, vectorizingBookId, vectorProgress, handleVectorize } = useVectorizationQueue( - { extractorRef, nav }, - ); + const { vectorQueue, vectorizingBookId, vectorProgress, handleVectorize, cancelVectorize } = + useVectorizationQueue({ extractorRef, nav }); const openSearch = useCallback(() => { setShowSearch(true); @@ -768,6 +767,7 @@ export function LibraryScreen() { onShowDetails={handleShowDetails} onManageTags={handleManageTags} onVectorize={handleVectorize} + onCancelVectorize={cancelVectorize} isVectorizing={vectorizingBookId === item.book.id} isQueued={vectorQueue.some((b) => b.id === item.book.id)} vectorProgress={vectorizingBookId === item.book.id ? vectorProgress : null} @@ -788,6 +788,7 @@ export function LibraryScreen() { handleShowDetails, handleOpen, handleVectorize, + cancelVectorize, removeBook, s.gridItem, selectedBookIds, diff --git a/packages/app-expo/src/screens/library/useVectorizationQueue.ts b/packages/app-expo/src/screens/library/useVectorizationQueue.ts index c05e0d21c..e9f6ace78 100644 --- a/packages/app-expo/src/screens/library/useVectorizationQueue.ts +++ b/packages/app-expo/src/screens/library/useVectorizationQueue.ts @@ -2,7 +2,7 @@ import type { ExtractorRef } from "@/components/rag/ExtractorWebView"; import { inspectMobileBookForVectorize } from "@/lib/rag/auto-vectorize-book"; import { getBookExtractionErrorMessageKeys } from "@/lib/rag/extractor-error"; import { MOBILE_VECTORIZE_UNSUPPORTED_FORMAT_DESCRIPTION } from "@/lib/rag/mobile-vectorize-capability"; -import { runVectorizeQueueJob } from "@/lib/rag/vectorize-queue-job"; +import { runVectorizeQueueJob, throwIfQueueJobAborted } from "@/lib/rag/vectorize-queue-job"; import { resetBookVectorization, triggerVectorizeBook } from "@/lib/rag/vectorize-trigger"; import type { RootStackParamList } from "@/navigation/RootNavigator"; import { useVectorModelStore } from "@/stores/vector-model-store"; @@ -12,6 +12,7 @@ import * as FileSystem from "expo-file-system/legacy"; import { useCallback, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import { Alert } from "react-native"; +import { VectorizationQueue } from "./vectorization-queue"; type Nav = NativeStackNavigationProp; @@ -23,19 +24,21 @@ interface UseVectorizationQueueOptions { export function useVectorizationQueue({ extractorRef, nav }: UseVectorizationQueueOptions) { const { t } = useTranslation(); const [vectorQueue, setVectorQueue] = useState([]); - const vectorQueueRef = useRef([]); + const queueRef = useRef(new VectorizationQueue()); const [vectorizingBookId, setVectorizingBookId] = useState(null); const [vectorizingBookTitle, setVectorizingBookTitle] = useState(""); const [vectorProgress, setVectorProgress] = useState(null); const isProcessingRef = useRef(false); const processOneBook = useCallback( - async (book: Book) => { + async (book: Book, signal: AbortSignal) => { setVectorizingBookId(book.id); setVectorizingBookTitle(book.meta.title); const result = await runVectorizeQueueJob({ format: book.format, - extract: async () => { + signal, + extract: async (jobSignal) => { + throwIfQueueJobAborted(jobSignal); if (!extractorRef.current) throw new Error("Extractor WebView not ready"); const info = await inspectMobileBookForVectorize(book); @@ -46,15 +49,17 @@ export function useVectorizationQueue({ extractorRef, nav }: UseVectorizationQue const base64 = await FileSystem.readAsStringAsync(info.absPath, { encoding: FileSystem.EncodingType.Base64, }); + throwIfQueueJobAborted(jobSignal); return extractorRef.current.extractChapters( base64, info.mimeType, book.format, info.absPath, + jobSignal, ); }, - vectorize: (chapters, onProgress) => - triggerVectorizeBook(book.id, book.filePath, chapters, onProgress), + vectorize: (chapters, onProgress, jobSignal) => + triggerVectorizeBook(book.id, book.filePath, chapters, onProgress, jobSignal), cleanup: () => resetBookVectorization(book.id), onEvent: (event) => { if (event.status === "extracting") { @@ -73,6 +78,13 @@ export function useVectorizationQueue({ extractorRef, nav }: UseVectorizationQue processedChunks: 1, totalChunks: 1, }); + } else if (event.status === "cancelled") { + setVectorProgress({ + bookId: book.id, + status: "cancelled", + processedChunks: 0, + totalChunks: 0, + }); } else { console.error( `[useVectorizationQueue] Vectorization failed for "${book.meta.title}":`, @@ -104,7 +116,9 @@ export function useVectorizationQueue({ extractorRef, nav }: UseVectorizationQue }, }); - await new Promise((resolve) => setTimeout(resolve, result.ok ? 800 : 1500)); + await new Promise((resolve) => + setTimeout(resolve, result.ok ? 800 : result.cancelled ? 500 : 1500), + ); }, [extractorRef, t], ); @@ -114,12 +128,15 @@ export function useVectorizationQueue({ extractorRef, nav }: UseVectorizationQue isProcessingRef.current = true; try { - while (vectorQueueRef.current.length > 0) { - const [nextBook, ...remainingBooks] = vectorQueueRef.current; - if (!nextBook) break; - vectorQueueRef.current = remainingBooks; - setVectorQueue([...vectorQueueRef.current]); - await processOneBook(nextBook); + while (true) { + const nextJob = queueRef.current.startNext(); + if (!nextJob) break; + setVectorQueue(queueRef.current.snapshot()); + try { + await processOneBook(nextJob.book, nextJob.signal); + } finally { + queueRef.current.finish(nextJob.book.id); + } } } finally { isProcessingRef.current = false; @@ -149,11 +166,8 @@ export function useVectorizationQueue({ extractorRef, nav }: UseVectorizationQue ); return; } - const alreadyQueued = vectorQueueRef.current.some((b) => b.id === book.id); - if (alreadyQueued || vectorizingBookId === book.id) return; - - vectorQueueRef.current = [...vectorQueueRef.current, book]; - setVectorQueue([...vectorQueueRef.current]); + if (!queueRef.current.enqueue(book)) return; + setVectorQueue(queueRef.current.snapshot()); if (!isProcessingRef.current) { processQueue(); @@ -180,14 +194,33 @@ export function useVectorizationQueue({ extractorRef, nav }: UseVectorizationQue ); }); }, - [nav, t, vectorizingBookId, processQueue], + [nav, t, processQueue], ); + const cancelVectorize = useCallback((bookId: string) => { + const outcome = queueRef.current.cancel(bookId); + if (outcome === "queued") { + setVectorQueue(queueRef.current.snapshot()); + } else if (outcome === "active") { + setVectorProgress((current) => + current + ? { ...current, status: "cancelling" } + : { + bookId, + status: "cancelling", + processedChunks: 0, + totalChunks: 0, + }, + ); + } + }, []); + return { vectorQueue, vectorizingBookId, vectorizingBookTitle, vectorProgress, handleVectorize, + cancelVectorize, }; } diff --git a/packages/app-expo/src/screens/library/vectorization-queue.test.ts b/packages/app-expo/src/screens/library/vectorization-queue.test.ts new file mode 100644 index 000000000..7a98d0e03 --- /dev/null +++ b/packages/app-expo/src/screens/library/vectorization-queue.test.ts @@ -0,0 +1,29 @@ +import { describe, expect, it } from "vitest"; +import { VectorizationQueue } from "./vectorization-queue"; + +describe("VectorizationQueue", () => { + it("removes a queued book without disturbing the active job", () => { + const queue = new VectorizationQueue<{ id: string }>(); + queue.enqueue({ id: "active" }); + queue.enqueue({ id: "queued" }); + const active = queue.startNext(); + + expect(queue.cancel("queued")).toBe("queued"); + expect(active?.book.id).toBe("active"); + expect(active?.signal.aborted).toBe(false); + expect(queue.snapshot()).toEqual([]); + }); + + it("aborts the active job and keeps it active until cleanup finishes", () => { + const queue = new VectorizationQueue<{ id: string }>(); + queue.enqueue({ id: "active" }); + const active = queue.startNext(); + + expect(queue.cancel("active")).toBe("active"); + expect(active?.signal.aborted).toBe(true); + expect(queue.activeBookId).toBe("active"); + + queue.finish("active"); + expect(queue.activeBookId).toBeNull(); + }); +}); diff --git a/packages/app-expo/src/screens/library/vectorization-queue.ts b/packages/app-expo/src/screens/library/vectorization-queue.ts new file mode 100644 index 000000000..8cd10a1fa --- /dev/null +++ b/packages/app-expo/src/screens/library/vectorization-queue.ts @@ -0,0 +1,44 @@ +export class VectorizationQueue { + private queuedBooks: Book[] = []; + private active: { book: Book; controller: AbortController } | null = null; + + get activeBookId(): string | null { + return this.active?.book.id ?? null; + } + + snapshot(): Book[] { + return [...this.queuedBooks]; + } + + enqueue(book: Book): boolean { + if (this.active?.book.id === book.id || this.queuedBooks.some((item) => item.id === book.id)) { + return false; + } + this.queuedBooks.push(book); + return true; + } + + startNext(): { book: Book; signal: AbortSignal } | null { + if (this.active) return null; + const book = this.queuedBooks.shift(); + if (!book) return null; + const controller = new AbortController(); + this.active = { book, controller }; + return { book, signal: controller.signal }; + } + + cancel(bookId: string): "active" | "queued" | "not-found" { + if (this.active?.book.id === bookId) { + this.active.controller.abort(); + return "active"; + } + const nextQueue = this.queuedBooks.filter((book) => book.id !== bookId); + if (nextQueue.length === this.queuedBooks.length) return "not-found"; + this.queuedBooks = nextQueue; + return "queued"; + } + + finish(bookId: string): void { + if (this.active?.book.id === bookId) this.active = null; + } +} diff --git a/packages/app-expo/src/stores/library-store.ts b/packages/app-expo/src/stores/library-store.ts index 531c13914..6c00832ca 100644 --- a/packages/app-expo/src/stores/library-store.ts +++ b/packages/app-expo/src/stores/library-store.ts @@ -77,6 +77,7 @@ export interface LibraryState { removeBook: (bookId: string, options?: RemoveBookOptions) => Promise; updateBook: (bookId: string, updates: Partial) => Promise; updateBookStrict: (bookId: string, updates: Partial) => Promise; + resetBookVectorizationState: (bookId: string) => Promise; setFilter: (filter: Partial) => void; setViewMode: (mode: LibraryViewMode) => void; setSortField: (field: SortField) => void; @@ -840,6 +841,21 @@ export const useLibraryStore = create((set, get) => ({ debouncedSave("library-books", get().books); }, + resetBookVectorizationState: async (bookId) => { + const reset = { isVectorized: false, vectorizeProgress: 0 } as const; + let persistenceError: unknown; + try { + await persistBookUpdate(bookId, reset); + } catch (error) { + persistenceError = error; + } + set((state) => ({ + books: state.books.map((book) => (book.id === bookId ? { ...book, ...reset } : book)), + })); + debouncedSave("library-books", get().books); + if (persistenceError) throw persistenceError; + }, + setFilter: (filter) => set((state) => ({ filter: { ...state.filter, ...filter } })), setViewMode: (mode) => set({ viewMode: mode }), setSortField: (field) => set((state) => ({ filter: { ...state.filter, sortField: field } })), diff --git a/packages/app-expo/src/stores/library-store.vectorization.test.ts b/packages/app-expo/src/stores/library-store.vectorization.test.ts index 88c94c648..e7cd66215 100644 --- a/packages/app-expo/src/stores/library-store.vectorization.test.ts +++ b/packages/app-expo/src/stores/library-store.vectorization.test.ts @@ -110,4 +110,24 @@ describe("library store strict vectorization updates", () => { expect(mocks.debouncedSave).not.toHaveBeenCalled(); unsubscribe(); }); + + it("clears visible and cached indexed state when the durable cleanup write rejects", async () => { + useLibraryStore.getState().setBooks([{ ...book, isVectorized: true, vectorizeProgress: 1 }]); + mocks.updateBook.mockRejectedValue(new Error("database write failed")); + + await expect(useLibraryStore.getState().resetBookVectorizationState("book-1")).rejects.toThrow( + "database write failed", + ); + + expect(useLibraryStore.getState().books[0]).toMatchObject({ + isVectorized: false, + vectorizeProgress: 0, + }); + expect(mocks.debouncedSave).toHaveBeenCalledWith( + "library-books", + expect.arrayContaining([ + expect.objectContaining({ id: "book-1", isVectorized: false, vectorizeProgress: 0 }), + ]), + ); + }); }); diff --git a/packages/app/src/lib/rag/vectorize-trigger.test.ts b/packages/app/src/lib/rag/vectorize-trigger.test.ts new file mode 100644 index 000000000..2d559d3ab --- /dev/null +++ b/packages/app/src/lib/rag/vectorize-trigger.test.ts @@ -0,0 +1,67 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; + +const mocks = vi.hoisted(() => ({ + coreTriggerVectorizeBook: vi.fn(), + updateBook: vi.fn(), + updateBookStrict: vi.fn(), +})); + +vi.mock("@readany/core/rag", () => ({ + triggerVectorizeBook: mocks.coreTriggerVectorizeBook, +})); +vi.mock("@/lib/storage/desktop-library-root", () => ({ + resolveDesktopDataPath: vi.fn(async (path: string) => path), +})); +vi.mock("@/lib/reader/document-loader", () => ({})); +vi.mock("./book-extractor", () => ({ + extractBookChapters: vi.fn(async () => [ + { index: 0, title: "Chapter", content: "text", segments: [] }, + ]), +})); +vi.mock("@/stores/library-store", () => ({ + useLibraryStore: { + getState: () => ({ + updateBook: mocks.updateBook, + updateBookStrict: mocks.updateBookStrict, + }), + }, +})); +vi.mock("@/stores/vector-model-store", () => ({ + useVectorModelStore: { + getState: () => ({ + vectorModelEnabled: true, + vectorModelMode: "builtin", + selectedBuiltinModelId: "test-model", + getSelectedVectorModel: () => null, + }), + }, +})); + +import { triggerVectorizeBook } from "./vectorize-trigger"; + +describe("desktop vectorization persistence adapter", () => { + beforeEach(() => { + vi.clearAllMocks(); + mocks.updateBookStrict.mockRejectedValue(new Error("database write failed")); + mocks.coreTriggerVectorizeBook.mockImplementation( + async (_bookId, _chapters, _config, callbacks) => { + await callbacks.onBookUpdate("book-1", { + isVectorized: true, + vectorizeProgress: 1, + }); + }, + ); + }); + + it("propagates a rejected strict state write from the core boundary", async () => { + await expect(triggerVectorizeBook("book-1", "book.epub")).rejects.toThrow( + "database write failed", + ); + + expect(mocks.updateBookStrict).toHaveBeenCalledWith("book-1", { + isVectorized: true, + vectorizeProgress: 1, + }); + expect(mocks.updateBook).not.toHaveBeenCalled(); + }); +}); diff --git a/packages/app/src/lib/rag/vectorize-trigger.ts b/packages/app/src/lib/rag/vectorize-trigger.ts index 331fa7c13..61d478118 100644 --- a/packages/app/src/lib/rag/vectorize-trigger.ts +++ b/packages/app/src/lib/rag/vectorize-trigger.ts @@ -49,7 +49,8 @@ export async function triggerVectorizeBook( // Build callbacks that write back to Zustand store const callbacks = { - onBookUpdate: useLibraryStore.getState().updateBook, + onBookUpdate: useLibraryStore.getState().updateBookStrict, + onBookReset: useLibraryStore.getState().resetBookVectorizationState, }; // Extract chapters from the book file (platform-specific: Tauri + foliate-js) diff --git a/packages/app/src/stores/library-store.ts b/packages/app/src/stores/library-store.ts index 73fb2b85f..f1df0a8b4 100644 --- a/packages/app/src/stores/library-store.ts +++ b/packages/app/src/stores/library-store.ts @@ -354,6 +354,8 @@ export interface LibraryState { addBook: (book: Book) => void; removeBook: (bookId: string, options?: RemoveBookOptions) => Promise; updateBook: (bookId: string, updates: Partial) => Promise; + updateBookStrict: (bookId: string, updates: Partial) => Promise; + resetBookVectorizationState: (bookId: string) => Promise; setFilter: (filter: Partial) => void; setViewMode: (mode: LibraryViewMode) => void; setSortField: (field: SortField) => void; @@ -854,6 +856,33 @@ export const useLibraryStore = create((set, get) => ({ .catch((err) => console.error("Failed to update book in database:", err)); }, + updateBookStrict: async (bookId, updates) => { + await db.updateBook(bookId, updates); + set((state) => ({ + books: state.books.map((book) => (book.id === bookId ? { ...book, ...updates } : book)), + allTags: + updates.tags !== undefined + ? Array.from(new Set([...state.allTags, ...updates.tags])).sort() + : state.allTags, + })); + debouncedSave("library-books", get().books); + }, + + resetBookVectorizationState: async (bookId) => { + const reset = { isVectorized: false, vectorizeProgress: 0 } as const; + let persistenceError: unknown; + try { + await db.updateBook(bookId, reset); + } catch (error) { + persistenceError = error; + } + set((state) => ({ + books: state.books.map((book) => (book.id === bookId ? { ...book, ...reset } : book)), + })); + debouncedSave("library-books", get().books); + if (persistenceError) throw persistenceError; + }, + setFilter: (filter) => set((state) => ({ filter: { ...state.filter, ...filter } })), setViewMode: (mode) => set({ viewMode: mode }), diff --git a/packages/app/src/stores/library-store.vectorization.test.ts b/packages/app/src/stores/library-store.vectorization.test.ts new file mode 100644 index 000000000..0866df7f0 --- /dev/null +++ b/packages/app/src/stores/library-store.vectorization.test.ts @@ -0,0 +1,61 @@ +import type { Book } from "@readany/core/types"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +const mocks = vi.hoisted(() => ({ + updateBook: vi.fn(), + debouncedSave: vi.fn(), +})); + +vi.mock("@/lib/db/database", () => ({ updateBook: mocks.updateBook })); +vi.mock("@/lib/rag/vectorize-trigger", () => ({ triggerVectorizeBook: vi.fn() })); +vi.mock("@/lib/storage/desktop-library-root", () => ({ + getDesktopLibraryRoot: vi.fn(), + isDesktopManagedRelativePath: vi.fn(), + resolveDesktopDataPath: vi.fn(), +})); +vi.mock("@readany/core/stores/persist", () => ({ + debouncedSave: mocks.debouncedSave, + loadFromFS: vi.fn(), +})); +vi.mock("@readany/core/stores/vector-model-store", () => ({ + useVectorModelStore: { getState: vi.fn() }, +})); + +import { useLibraryStore } from "./library-store"; + +const book: Book = { + id: "book-1", + filePath: "books/book.epub", + format: "epub", + meta: { title: "Book", author: "Author" }, + addedAt: 1, + updatedAt: 1, + progress: 0, + isVectorized: false, + vectorizeProgress: 0, + tags: [], + syncStatus: "local", +}; + +describe("desktop library strict vectorization updates", () => { + beforeEach(() => { + vi.clearAllMocks(); + useLibraryStore.getState().setBooks([{ ...book }]); + }); + + afterEach(() => useLibraryStore.getState().setBooks([])); + + it("does not expose or cache completion when the database rejects", async () => { + mocks.updateBook.mockRejectedValue(new Error("database write failed")); + + await expect( + useLibraryStore.getState().updateBookStrict("book-1", { + isVectorized: true, + vectorizeProgress: 1, + }), + ).rejects.toThrow("database write failed"); + + expect(useLibraryStore.getState().books[0]?.isVectorized).toBe(false); + expect(mocks.debouncedSave).not.toHaveBeenCalled(); + }); +}); diff --git a/packages/core/src/i18n/locales/en/library.json b/packages/core/src/i18n/locales/en/library.json index c25fa36a8..87ac00d40 100644 --- a/packages/core/src/i18n/locales/en/library.json +++ b/packages/core/src/i18n/locales/en/library.json @@ -196,6 +196,10 @@ "vec_indexing": "Indexing...", "vec_processing": "Processing...", "vec_queued": "Queued", + "vec_cancel": "Cancel", + "vec_cancelQueued": "Cancel queued vectorization", + "vec_cancelling": "Cancelling…", + "vec_cancelled": "Cancelled", "manageTags": "Manage Tags", "remote": "Download required", "downloading": "Downloading" diff --git a/packages/core/src/i18n/locales/es/library.json b/packages/core/src/i18n/locales/es/library.json index cda0b6dbf..7165fe41b 100644 --- a/packages/core/src/i18n/locales/es/library.json +++ b/packages/core/src/i18n/locales/es/library.json @@ -182,6 +182,10 @@ "vec_indexing": "Indexando...", "vec_processing": "Procesando...", "vec_queued": "En cola", + "vec_cancel": "Cancelar", + "vec_cancelQueued": "Cancelar vectorización en cola", + "vec_cancelling": "Cancelando…", + "vec_cancelled": "Cancelado", "manageTags": "Administrar etiquetas", "remote": "Requiere descarga", "downloading": "Descargando" diff --git a/packages/core/src/i18n/locales/fr/library.json b/packages/core/src/i18n/locales/fr/library.json index 1b6c60f9e..14d3d0741 100644 --- a/packages/core/src/i18n/locales/fr/library.json +++ b/packages/core/src/i18n/locales/fr/library.json @@ -182,6 +182,10 @@ "vec_indexing": "Indexation...", "vec_processing": "Traitement...", "vec_queued": "En file d'attente", + "vec_cancel": "Annuler", + "vec_cancelQueued": "Annuler la vectorisation en attente", + "vec_cancelling": "Annulation…", + "vec_cancelled": "Annulée", "manageTags": "Gérer les tags", "remote": "Téléchargement requis", "downloading": "Téléchargement" diff --git a/packages/core/src/i18n/locales/ja/library.json b/packages/core/src/i18n/locales/ja/library.json index f0eeefc02..9086192d1 100644 --- a/packages/core/src/i18n/locales/ja/library.json +++ b/packages/core/src/i18n/locales/ja/library.json @@ -182,6 +182,10 @@ "vec_indexing": "インデックス作成中...", "vec_processing": "処理中...", "vec_queued": "キュー待ち", + "vec_cancel": "キャンセル", + "vec_cancelQueued": "キュー内のベクトル化をキャンセル", + "vec_cancelling": "キャンセル中…", + "vec_cancelled": "キャンセル済み", "manageTags": "タグを管理", "remote": "ダウンロードが必要", "downloading": "ダウンロード中" diff --git a/packages/core/src/i18n/locales/ko/library.json b/packages/core/src/i18n/locales/ko/library.json index 0f037a265..2b43fcc12 100644 --- a/packages/core/src/i18n/locales/ko/library.json +++ b/packages/core/src/i18n/locales/ko/library.json @@ -182,6 +182,10 @@ "vec_indexing": "인덱싱 중...", "vec_processing": "처리 중...", "vec_queued": "대기 중", + "vec_cancel": "취소", + "vec_cancelQueued": "대기 중인 벡터화 취소", + "vec_cancelling": "취소 중…", + "vec_cancelled": "취소됨", "manageTags": "태그 관리", "remote": "다운로드 필요", "downloading": "다운로드 중" diff --git a/packages/core/src/i18n/locales/zh-TW/library.json b/packages/core/src/i18n/locales/zh-TW/library.json index 644a14080..c588e04fb 100644 --- a/packages/core/src/i18n/locales/zh-TW/library.json +++ b/packages/core/src/i18n/locales/zh-TW/library.json @@ -182,6 +182,10 @@ "vec_indexing": "入庫中...", "vec_processing": "處理中...", "vec_queued": "排隊中", + "vec_cancel": "取消", + "vec_cancelQueued": "取消佇列中的向量化", + "vec_cancelling": "正在取消…", + "vec_cancelled": "已取消", "manageTags": "管理標籤", "remote": "需下載", "downloading": "下載中" diff --git a/packages/core/src/i18n/locales/zh/library.json b/packages/core/src/i18n/locales/zh/library.json index ae50b24e3..160c1cf1d 100644 --- a/packages/core/src/i18n/locales/zh/library.json +++ b/packages/core/src/i18n/locales/zh/library.json @@ -196,6 +196,10 @@ "vec_indexing": "入库中...", "vec_processing": "处理中...", "vec_queued": "排队中", + "vec_cancel": "取消", + "vec_cancelQueued": "取消排队中的向量化", + "vec_cancelling": "正在取消…", + "vec_cancelled": "已取消", "manageTags": "管理标签", "remote": "需下载", "downloading": "下载中" diff --git a/packages/core/src/rag/remote-embedding.ts b/packages/core/src/rag/remote-embedding.ts index fb34029b0..8c309cae3 100644 --- a/packages/core/src/rag/remote-embedding.ts +++ b/packages/core/src/rag/remote-embedding.ts @@ -15,6 +15,7 @@ export type RemoteEmbeddingFetch = (url: string, init: RequestInit) => Promise { chunksCount: 1, }); }); + + it("cancels after a partial chunk write and removes every searchable residue", async () => { + const controller = new AbortController(); + const chapters = Array.from({ length: 51 }, (_, index) => ({ + index, + title: `Chapter ${index}`, + content: "content", + })); + databaseMocks.insertChunks.mockImplementationOnce(async () => { + controller.abort(); + }); + + await expect( + triggerVectorizeBook( + "book-1", + chapters, + { + vectorModelEnabled: true, + vectorModelMode: "remote", + selectedBuiltinModelId: null, + remoteModel: { + url: "https://example.com/v1/embeddings", + apiKey: "test", + modelId: "test-model", + }, + }, + { onBookUpdate: vi.fn() }, + undefined, + controller.signal, + ), + ).rejects.toMatchObject({ name: "AbortError" }); + + expect(databaseMocks.insertChunks).toHaveBeenCalledOnce(); + expect(databaseMocks.deleteChunks).toHaveBeenCalledTimes(2); + expect(databaseMocks.deleteVectorIndexProvenance).toHaveBeenCalledTimes(2); + expect(vectorDatabaseMocks.deleteByBookId).toHaveBeenCalledOnce(); + expect(databaseMocks.setVectorIndexProvenance).not.toHaveBeenCalled(); + expect(eventBusMocks.emit).not.toHaveBeenCalledWith("vectorize:completed", expect.anything()); + }); + + it("stops between embedding batches when cancelled", async () => { + const controller = new AbortController(); + remoteEmbeddingMocks.requestRemoteEmbeddingBatch.mockImplementationOnce(async () => { + controller.abort(); + return { ok: true, embeddings: Array.from({ length: 8 }, () => [0.1, 0.2]) }; + }); + + await expect( + triggerVectorizeBook( + "book-1", + Array.from({ length: 9 }, (_, index) => ({ + index, + title: `Chapter ${index}`, + content: "content", + })), + { + vectorModelEnabled: true, + vectorModelMode: "remote", + selectedBuiltinModelId: null, + remoteModel: { + url: "https://example.com/v1/embeddings", + apiKey: "test", + modelId: "test-model", + }, + }, + { onBookUpdate: vi.fn() }, + undefined, + controller.signal, + ), + ).rejects.toMatchObject({ name: "AbortError" }); + + expect(remoteEmbeddingMocks.requestRemoteEmbeddingBatch).toHaveBeenCalledOnce(); + expect(databaseMocks.insertChunks).not.toHaveBeenCalled(); + expect(databaseMocks.setVectorIndexProvenance).not.toHaveBeenCalled(); + expect(eventBusMocks.emit).not.toHaveBeenCalledWith("vectorize:completed", expect.anything()); + }); }); diff --git a/packages/core/src/rag/vectorize-trigger.ts b/packages/core/src/rag/vectorize-trigger.ts index 201e18be1..91dcbdf8f 100644 --- a/packages/core/src/rag/vectorize-trigger.ts +++ b/packages/core/src/rag/vectorize-trigger.ts @@ -47,11 +47,36 @@ export interface VectorizeTriggerCallbacks { bookId: string, update: { isVectorized: boolean; vectorizeProgress: number }, ) => void | Promise; + /** Reset visible/cache state after index deletion, even if durable persistence fails. */ + onBookReset?: ( + bookId: string, + update: { isVectorized: false; vectorizeProgress: 0 }, + ) => void | Promise; } /** Yield to the event loop so UI can repaint */ const yieldToUI = () => new Promise((r) => setTimeout(r, 0)); +export function createVectorizationAbortError(reason?: unknown): Error { + if (reason instanceof Error) return reason; + const error = new Error( + typeof reason === "string" && reason ? reason : "Vectorization cancelled", + ); + error.name = "AbortError"; + return error; +} + +export function throwIfVectorizationAborted(signal?: AbortSignal): void { + if (!signal?.aborted) return; + const error = createVectorizationAbortError(signal.reason); + if (error.name !== "AbortError") error.name = "AbortError"; + throw error; +} + +export function isVectorizationAbort(error: unknown): boolean { + return error instanceof Error && error.name === "AbortError"; +} + /** sqlite-vec currently owns one global vector dimension. */ export function canStoreInSharedVectorDB( stats: { totalVectors: number; dimension: number }, @@ -78,7 +103,7 @@ export async function resetBookVectorization( ]); invalidateChunkCache(bookId); - await callbacks.onBookUpdate(bookId, { + await (callbacks.onBookReset ?? callbacks.onBookUpdate)(bookId, { isVectorized: false, vectorizeProgress: 0, }); @@ -108,6 +133,7 @@ export async function triggerVectorizeBook( config: VectorizeTriggerConfig, callbacks: VectorizeTriggerCallbacks, onProgress?: VectorizeStatusCallback, + signal?: AbortSignal, ): Promise { if (!config.vectorModelEnabled) { throw new Error("Vector model is not enabled. Please enable it in Settings → Vector Model."); @@ -125,6 +151,7 @@ export async function triggerVectorizeBook( }; try { + throwIfVectorizationAborted(signal); // Update book state: vectorizing await callbacks.onBookUpdate(bookId, { isVectorized: false, @@ -133,6 +160,7 @@ export async function triggerVectorizeBook( eventBus.emit("vectorize:started", { bookId }); onProgress?.(progress); await yieldToUI(); + throwIfVectorizationAborted(signal); // Phase 1: Chunk chapters const allChunks: Array<{ @@ -150,6 +178,7 @@ export async function triggerVectorizeBook( const totalChapters = chapters.length; for (let i = 0; i < chapters.length; i++) { + throwIfVectorizationAborted(signal); const chapter = chapters[i]; const chunks = chunkContent( chapter.content, @@ -168,6 +197,7 @@ export async function triggerVectorizeBook( progress.totalChunks = totalChapters; onProgress?.(progress); await yieldToUI(); + throwIfVectorizationAborted(signal); } if (allChunks.length === 0) { @@ -187,22 +217,29 @@ export async function triggerVectorizeBook( config.selectedBuiltinModelId, progress, onProgress, + signal, ); } else { - await generateRemoteEmbeddings(allChunks, config, progress, onProgress); + await generateRemoteEmbeddings(allChunks, config, progress, onProgress, signal); } + throwIfVectorizationAborted(signal); // Phase 3: Store in database (batch insert for performance) progress.status = "indexing"; onProgress?.(progress); await yieldToUI(); + throwIfVectorizationAborted(signal); await deleteChunks(bookId); + throwIfVectorizationAborted(signal); await deleteVectorIndexProvenance(bookId); + throwIfVectorizationAborted(signal); // Insert in batches of 50 to avoid huge single transaction const insertBatchSize = 50; for (let i = 0; i < allChunks.length; i += insertBatchSize) { + throwIfVectorizationAborted(signal); await insertChunks(allChunks.slice(i, i + insertBatchSize)); + throwIfVectorizationAborted(signal); } // Store embeddings in vector database (sqlite-vec) @@ -211,6 +248,7 @@ export async function triggerVectorizeBook( const vectorDB = getVectorDB(); if (vectorDB && (await vectorDB.isReady())) { await vectorDB.deleteByBookId(bookId); + throwIfVectorizationAborted(signal); const vectorRecords: VectorRecord[] = allChunks.flatMap((c) => { const embedding = c.embedding; @@ -232,6 +270,7 @@ export async function triggerVectorizeBook( let canInsertIntoVectorDb = canStoreInSharedVectorDB(stats, detectedDimension); if (detectedDimension > 0 && vectorDB.reinit && stats.totalVectors === 0) { await vectorDB.reinit(detectedDimension); + throwIfVectorizationAborted(signal); canInsertIntoVectorDb = true; } else if (stats.dimension !== detectedDimension) { // sqlite-vec currently has one global vector dimension. Never drop @@ -243,6 +282,7 @@ export async function triggerVectorizeBook( } if (canInsertIntoVectorDb) { await vectorDB.insert(vectorRecords); + throwIfVectorizationAborted(signal); storedInVectorDb = true; } } @@ -256,12 +296,14 @@ export async function triggerVectorizeBook( console.warn("[Vectorize] Vector database not ready, skipping vector storage"); } } catch (err) { + throwIfVectorizationAborted(signal); console.error("[Vectorize] Failed to store vectors:", err); } } const provenance = getEmbeddingProvenance(config, allChunks[0]?.embedding?.length ?? 0); await setVectorIndexProvenance({ bookId, ...provenance, createdAt: Date.now() }); + throwIfVectorizationAborted(signal); // Invalidate search cache so next query picks up new embeddings invalidateChunkCache(bookId); @@ -271,6 +313,7 @@ export async function triggerVectorizeBook( isVectorized: true, vectorizeProgress: 1, }); + throwIfVectorizationAborted(signal); progress.status = "completed"; onProgress?.(progress); @@ -286,10 +329,15 @@ export async function triggerVectorizeBook( console.error(`[Vectorize] Failed to clean up partial index for ${bookId}:`, cleanupError); } - progress.status = "error"; - progress.error = message; + const cancelled = isVectorizationAbort(err); + progress.status = cancelled ? "cancelled" : "error"; + progress.error = cancelled ? undefined : message; onProgress?.(progress); - eventBus.emit("vectorize:error", { bookId, error: message }); + if (cancelled) { + eventBus.emit("vectorize:cancelled", { bookId }); + } else { + eventBus.emit("vectorize:error", { bookId, error: message }); + } throw err; } } @@ -322,6 +370,7 @@ async function generateBuiltinEmbeddings( builtinModelId: string | null, progress: VectorizeProgress, onProgress?: VectorizeStatusCallback, + signal?: AbortSignal, ) { if (!builtinModelId) { throw new Error("No built-in model selected. Please select one in Settings → Vector Model."); @@ -331,19 +380,23 @@ async function generateBuiltinEmbeddings( if (!model) throw new Error(`Unknown built-in model: ${builtinModelId}`); // Ensure the model is loaded in the Worker + throwIfVectorizationAborted(signal); await loadEmbeddingPipeline(builtinModelId); + throwIfVectorizationAborted(signal); // Process in batches — Worker handles the heavy lifting off main thread const batchSize = 16; let globalProcessed = 0; for (let i = 0; i < chunks.length; i += batchSize) { + throwIfVectorizationAborted(signal); const batch = chunks.slice(i, i + batchSize); const texts = batch.map((c) => c.content); const batchOffset = i; // generateLocalEmbeddings now runs in Worker with per-item progress const embeddings = await generateLocalEmbeddings(builtinModelId, texts, (done, _total) => { + if (signal?.aborted) return; globalProcessed = batchOffset + done; progress.processedChunks = globalProcessed; eventBus.emit("vectorize:progress", { @@ -353,12 +406,14 @@ async function generateBuiltinEmbeddings( }); onProgress?.(progress); }); + throwIfVectorizationAborted(signal); for (let j = 0; j < batch.length; j++) { batch[j].embedding = embeddings[j]; } await yieldToUI(); + throwIfVectorizationAborted(signal); } } @@ -368,6 +423,7 @@ async function generateRemoteEmbeddings( config: VectorizeTriggerConfig, progress: VectorizeProgress, onProgress?: VectorizeStatusCallback, + signal?: AbortSignal, ) { const selectedModel = config.remoteModel; if (!selectedModel) { @@ -390,14 +446,17 @@ async function generateRemoteEmbeddings( > => { return requestRemoteEmbeddingBatch(selectedModel, inputTexts, { maxCharsPerInput: MAX_CHARS_PER_CHUNK, + signal, }); }; for (let i = 0; i < chunks.length; i += batchSize) { + throwIfVectorizationAborted(signal); const batch = chunks.slice(i, i + batchSize); const texts = batch.map((c) => c.content); const batchResult = await callEmbeddingApi(texts); + throwIfVectorizationAborted(signal); if (batchResult.ok) { for (let j = 0; j < batch.length; j++) { @@ -415,7 +474,9 @@ async function generateRemoteEmbeddings( `[Embedding] Batch failed (${batchResult.status}): ${batchResult.errorText.slice(0, 200)}. Retrying per-chunk.`, ); for (let j = 0; j < batch.length; j++) { + throwIfVectorizationAborted(signal); const single = await callEmbeddingApi([batch[j].content]); + throwIfVectorizationAborted(signal); if (single.ok) { batch[j].embedding = single.embeddings[0] ?? []; } else { @@ -438,5 +499,6 @@ async function generateRemoteEmbeddings( }); onProgress?.(progress); await yieldToUI(); + throwIfVectorizationAborted(signal); } } diff --git a/packages/core/src/types/rag.ts b/packages/core/src/types/rag.ts index bf2df05d1..9760aab60 100644 --- a/packages/core/src/types/rag.ts +++ b/packages/core/src/types/rag.ts @@ -69,6 +69,14 @@ export interface VectorizeProgress { bookId: string; totalChunks: number; processedChunks: number; - status: "idle" | "chunking" | "embedding" | "indexing" | "completed" | "error"; + status: + | "idle" + | "chunking" + | "embedding" + | "indexing" + | "completed" + | "cancelling" + | "cancelled" + | "error"; error?: string; } diff --git a/packages/core/src/utils/event-bus.ts b/packages/core/src/utils/event-bus.ts index d575a87a2..b919666b6 100644 --- a/packages/core/src/utils/event-bus.ts +++ b/packages/core/src/utils/event-bus.ts @@ -23,6 +23,7 @@ type EventMap = { "vectorize:started": { bookId: string }; "vectorize:progress": { bookId: string; progress: number; status: string }; "vectorize:completed": { bookId: string; chunksCount: number }; + "vectorize:cancelled": { bookId: string }; "vectorize:error": { bookId: string; error: string }; "tts:jump-to-current": { bookId: string; cfi: string; respond?: () => void }; "tts:open-lyrics-page": { bookId: string; respond?: () => void }; From 3d19d2360a3eeb539ace6365e61b140a3e8269ad Mon Sep 17 00:00:00 2001 From: Chai Date: Sun, 16 Aug 2026 22:01:07 -0400 Subject: [PATCH 09/10] fix(rag): harden cancellation boundaries --- packages/app-expo/assets/reader/reader.html | 168 ++++++++++-------- .../assets/reader/reader.template.html | 66 ++++--- packages/app-expo/scripts/build-reader.js | 3 + .../src/components/library/BookCard.tsx | 24 ++- .../lib/rag/auto-vectorize-service.test.ts | 33 +++- .../src/lib/rag/auto-vectorize-service.ts | 10 +- .../extractor-cancellation-boundary.test.ts | 16 -- .../rag/reader-extraction-sessions.test.ts | 45 +++++ .../src/lib/rag/reader-extraction-sessions.ts | 50 ++++++ .../rag/vectorization-cancel-state.test.ts | 18 ++ .../src/lib/rag/vectorization-cancel-state.ts | 12 ++ .../src/lib/rag/vectorize-queue-job.test.ts | 34 +++- .../src/lib/rag/vectorize-queue-job.ts | 24 +-- .../screens/library/useVectorizationQueue.ts | 23 ++- .../library/vectorization-queue.test.ts | 23 +++ .../screens/library/vectorization-queue.ts | 12 +- .../i18n/library-vectorize-locales.test.ts | 2 + .../core/src/i18n/locales/en/library.json | 4 +- .../core/src/i18n/locales/es/library.json | 4 +- .../core/src/i18n/locales/fr/library.json | 4 +- .../core/src/i18n/locales/ja/library.json | 4 +- .../core/src/i18n/locales/ko/library.json | 4 +- .../core/src/i18n/locales/zh-TW/library.json | 4 +- .../core/src/i18n/locales/zh/library.json | 4 +- packages/core/src/rag/index.ts | 6 +- .../core/src/rag/vectorize-trigger.test.ts | 44 +++++ packages/core/src/rag/vectorize-trigger.ts | 28 ++- 27 files changed, 514 insertions(+), 155 deletions(-) delete mode 100644 packages/app-expo/src/lib/rag/extractor-cancellation-boundary.test.ts create mode 100644 packages/app-expo/src/lib/rag/reader-extraction-sessions.test.ts create mode 100644 packages/app-expo/src/lib/rag/reader-extraction-sessions.ts create mode 100644 packages/app-expo/src/lib/rag/vectorization-cancel-state.test.ts create mode 100644 packages/app-expo/src/lib/rag/vectorization-cancel-state.ts diff --git a/packages/app-expo/assets/reader/reader.html b/packages/app-expo/assets/reader/reader.html index 029dccb60..699824baa 100644 --- a/packages/app-expo/assets/reader/reader.html +++ b/packages/app-expo/assets/reader/reader.html @@ -212,7 +212,7 @@ let bookTextMetricsTimer = null; let bookmarkPullGestureActive = false; let pullBookmarkResetTimer = null; - const cancelledExtractionRequests = new Set(); + let extractionSessions = null; let refreshAnnotationsTimer = null; let activeFootnoteTipKey = null; let bookmarkPullStateMeta = { @@ -1370,7 +1370,7 @@ await handleExtractBookChapters(msg); break; case 'cancelExtraction': - if (msg.requestId) cancelledExtractionRequests.add(msg.requestId); + if (msg.requestId) getExtractionSessions().cancel(msg.requestId); break; case 'getChapterParagraphs': handleGetChapterParagraphs(); @@ -1419,6 +1419,13 @@ return BOOK_FORMATS_BY_MIME[mimeType] || null; } + function getExtractionSessions() { + if (!extractionSessions) { + extractionSessions = new window.ReaderExtractionSessions(); + } + return extractionSessions; + } + function getBookFileName(msg) { const format = resolveBookFormat(msg); const cleanFileName = String(msg.fileName || '').split(/[?#]/, 1)[0].split(/[\\/]/).pop(); @@ -1447,7 +1454,7 @@ } try { - throwIfExtractionCancelled(msg.requestId); + getExtractionSessions().throwIfCancelled(msg.requestId); let hasSignalledLoaded = false; const markLoaded = () => { if (loading) loading.classList.add('hidden'); @@ -1561,8 +1568,11 @@ throw new Error('No book data provided'); } - const book = (file && file.sections) ? file : await makeBook(file); - throwIfExtractionCancelled(msg.requestId); + const loadBook = async () => (file && file.sections) ? file : await makeBook(file); + const book = msg.requestId + ? await getExtractionSessions().open(msg.requestId, loadBook) + : await loadBook(); + getExtractionSessions().throwIfCancelled(msg.requestId); currentBook = book; attachBookTransformHandler(book); @@ -1804,7 +1814,7 @@ const message = `${String(err)}${msg?.uri ? ` (${msg.uri})` : ''}`; loading.innerHTML = '
' + escapeHtml(message) + '
'; postToRN('error', { message, requestId: msg.requestId }); - cancelledExtractionRequests.delete(msg.requestId); + getExtractionSessions().release(msg.requestId); } } @@ -4280,7 +4290,7 @@ async function handleExtractBookChapters(msg) { const requestId = msg.requestId; try { - throwIfExtractionCancelled(requestId); + getExtractionSessions().throwIfCancelled(requestId); if (isPDFBookMessage(msg)) { if (typeof window._extractPDFChapters !== 'function') { throw new Error('PDF extraction is not available in this reader build'); @@ -4300,7 +4310,7 @@ postToRN('debug', { message: `[PDFExtract] page ${JSON.stringify(detail)}` }); } }); - throwIfExtractionCancelled(requestId); + getExtractionSessions().throwIfCancelled(requestId); postToRN('debug', { message: `[PDFExtract] done ${JSON.stringify({ @@ -4314,34 +4324,38 @@ return; } - currentBook = await makeBook(await createBookFileFromMessage(msg)); - throwIfExtractionCancelled(requestId); + const requestBook = await getExtractionSessions().open( + requestId, + async () => makeBook(await createBookFileFromMessage(msg)) + ); + currentBook = requestBook; + getExtractionSessions().throwIfCancelled(requestId); await handleExtractChapters(requestId); } catch (err) { console.error('[WebView] Error extracting book chapters:', err); postToRN('chaptersExtracted', { requestId, error: String(err) }); } finally { - cancelledExtractionRequests.delete(requestId); + getExtractionSessions().release(requestId); } } - function throwIfExtractionCancelled(requestId) { - if (!requestId || !cancelledExtractionRequests.has(requestId)) return; - const error = new Error('Vectorization cancelled'); - error.name = 'AbortError'; - throw error; - } - async function handleExtractChapters(requestId) { - if (!currentBook) { + let extractionBook; + try { + extractionBook = requestId ? getExtractionSessions().getBook(requestId) : currentBook; + } catch (err) { + postToRN('chaptersExtracted', { requestId, error: String(err) }); + return; + } + if (!extractionBook) { postToRN('chaptersExtracted', { requestId, error: 'No book loaded' }); return; } try { - throwIfExtractionCancelled(requestId); - const sections = currentBook.sections || []; - const toc = currentBook.toc || []; + getExtractionSessions().throwIfCancelled(requestId); + const sections = extractionBook.sections || []; + const toc = extractionBook.toc || []; // Build map of href to title mapping const tocMap = new Map(); @@ -4366,7 +4380,7 @@ let skippedNoCreateDocument = 0; for (let i = 0; i < sections.length; i++) { - throwIfExtractionCancelled(requestId); + getExtractionSessions().throwIfCancelled(requestId); const section = sections[i]; if (!section.createDocument) { skippedNoCreateDocument += 1; @@ -4375,7 +4389,7 @@ try { const doc = await section.createDocument(); - throwIfExtractionCancelled(requestId); + getExtractionSessions().throwIfCancelled(requestId); if (!doc.body) continue; const title = tocMap.get(i) || tocMap.get(section.href || "") || `Section ${i + 1}`; @@ -4402,6 +4416,8 @@ } } + getExtractionSessions().throwIfCancelled(requestId); + console.log('[WebView] Chapter extraction summary:', { sections: sections.length, chapters: chapters.length, @@ -4414,7 +4430,7 @@ console.error('[WebView] Error extracting chapters:', err); postToRN('chaptersExtracted', { requestId, error: String(err) }); } finally { - cancelledExtractionRequests.delete(requestId); + getExtractionSessions().release(requestId); } } @@ -4997,8 +5013,8 @@ diff --git a/packages/app-expo/assets/reader/reader.template.html b/packages/app-expo/assets/reader/reader.template.html index 837a90a19..2e5291436 100644 --- a/packages/app-expo/assets/reader/reader.template.html +++ b/packages/app-expo/assets/reader/reader.template.html @@ -212,7 +212,7 @@ let bookTextMetricsTimer = null; let bookmarkPullGestureActive = false; let pullBookmarkResetTimer = null; - const cancelledExtractionRequests = new Set(); + let extractionSessions = null; let refreshAnnotationsTimer = null; let activeFootnoteTipKey = null; let bookmarkPullStateMeta = { @@ -1370,7 +1370,7 @@ await handleExtractBookChapters(msg); break; case 'cancelExtraction': - if (msg.requestId) cancelledExtractionRequests.add(msg.requestId); + if (msg.requestId) getExtractionSessions().cancel(msg.requestId); break; case 'getChapterParagraphs': handleGetChapterParagraphs(); @@ -1419,6 +1419,13 @@ return BOOK_FORMATS_BY_MIME[mimeType] || null; } + function getExtractionSessions() { + if (!extractionSessions) { + extractionSessions = new window.ReaderExtractionSessions(); + } + return extractionSessions; + } + function getBookFileName(msg) { const format = resolveBookFormat(msg); const cleanFileName = String(msg.fileName || '').split(/[?#]/, 1)[0].split(/[\\/]/).pop(); @@ -1447,7 +1454,7 @@ } try { - throwIfExtractionCancelled(msg.requestId); + getExtractionSessions().throwIfCancelled(msg.requestId); let hasSignalledLoaded = false; const markLoaded = () => { if (loading) loading.classList.add('hidden'); @@ -1561,8 +1568,11 @@ throw new Error('No book data provided'); } - const book = (file && file.sections) ? file : await makeBook(file); - throwIfExtractionCancelled(msg.requestId); + const loadBook = async () => (file && file.sections) ? file : await makeBook(file); + const book = msg.requestId + ? await getExtractionSessions().open(msg.requestId, loadBook) + : await loadBook(); + getExtractionSessions().throwIfCancelled(msg.requestId); currentBook = book; attachBookTransformHandler(book); @@ -1804,7 +1814,7 @@ const message = `${String(err)}${msg?.uri ? ` (${msg.uri})` : ''}`; loading.innerHTML = '
' + escapeHtml(message) + '
'; postToRN('error', { message, requestId: msg.requestId }); - cancelledExtractionRequests.delete(msg.requestId); + getExtractionSessions().release(msg.requestId); } } @@ -4280,7 +4290,7 @@ async function handleExtractBookChapters(msg) { const requestId = msg.requestId; try { - throwIfExtractionCancelled(requestId); + getExtractionSessions().throwIfCancelled(requestId); if (isPDFBookMessage(msg)) { if (typeof window._extractPDFChapters !== 'function') { throw new Error('PDF extraction is not available in this reader build'); @@ -4300,7 +4310,7 @@ postToRN('debug', { message: `[PDFExtract] page ${JSON.stringify(detail)}` }); } }); - throwIfExtractionCancelled(requestId); + getExtractionSessions().throwIfCancelled(requestId); postToRN('debug', { message: `[PDFExtract] done ${JSON.stringify({ @@ -4314,34 +4324,38 @@ return; } - currentBook = await makeBook(await createBookFileFromMessage(msg)); - throwIfExtractionCancelled(requestId); + const requestBook = await getExtractionSessions().open( + requestId, + async () => makeBook(await createBookFileFromMessage(msg)) + ); + currentBook = requestBook; + getExtractionSessions().throwIfCancelled(requestId); await handleExtractChapters(requestId); } catch (err) { console.error('[WebView] Error extracting book chapters:', err); postToRN('chaptersExtracted', { requestId, error: String(err) }); } finally { - cancelledExtractionRequests.delete(requestId); + getExtractionSessions().release(requestId); } } - function throwIfExtractionCancelled(requestId) { - if (!requestId || !cancelledExtractionRequests.has(requestId)) return; - const error = new Error('Vectorization cancelled'); - error.name = 'AbortError'; - throw error; - } - async function handleExtractChapters(requestId) { - if (!currentBook) { + let extractionBook; + try { + extractionBook = requestId ? getExtractionSessions().getBook(requestId) : currentBook; + } catch (err) { + postToRN('chaptersExtracted', { requestId, error: String(err) }); + return; + } + if (!extractionBook) { postToRN('chaptersExtracted', { requestId, error: 'No book loaded' }); return; } try { - throwIfExtractionCancelled(requestId); - const sections = currentBook.sections || []; - const toc = currentBook.toc || []; + getExtractionSessions().throwIfCancelled(requestId); + const sections = extractionBook.sections || []; + const toc = extractionBook.toc || []; // Build map of href to title mapping const tocMap = new Map(); @@ -4366,7 +4380,7 @@ let skippedNoCreateDocument = 0; for (let i = 0; i < sections.length; i++) { - throwIfExtractionCancelled(requestId); + getExtractionSessions().throwIfCancelled(requestId); const section = sections[i]; if (!section.createDocument) { skippedNoCreateDocument += 1; @@ -4375,7 +4389,7 @@ try { const doc = await section.createDocument(); - throwIfExtractionCancelled(requestId); + getExtractionSessions().throwIfCancelled(requestId); if (!doc.body) continue; const title = tocMap.get(i) || tocMap.get(section.href || "") || `Section ${i + 1}`; @@ -4402,6 +4416,8 @@ } } + getExtractionSessions().throwIfCancelled(requestId); + console.log('[WebView] Chapter extraction summary:', { sections: sections.length, chapters: chapters.length, @@ -4414,7 +4430,7 @@ console.error('[WebView] Error extracting chapters:', err); postToRN('chaptersExtracted', { requestId, error: String(err) }); } finally { - cancelledExtractionRequests.delete(requestId); + getExtractionSessions().release(requestId); } } diff --git a/packages/app-expo/scripts/build-reader.js b/packages/app-expo/scripts/build-reader.js index 5be4e1f41..fc0299ba0 100644 --- a/packages/app-expo/scripts/build-reader.js +++ b/packages/app-expo/scripts/build-reader.js @@ -12,6 +12,7 @@ const FOLIATE_DIR = path.resolve(__dirname, "../../foliate-js"); const ASSETS_DIR = path.resolve(__dirname, "../assets/reader"); const TEMPLATE = path.resolve(ASSETS_DIR, "reader.template.html"); const OUTPUT = path.resolve(ASSETS_DIR, "reader.html"); +const EXTRACTION_SESSIONS = path.resolve(__dirname, "../src/lib/rag/reader-extraction-sessions.ts"); async function buildReader() { // Create a temporary entry point @@ -22,6 +23,7 @@ async function buildReader() { import { configure, ZipReader, BlobReader, TextWriter, BlobWriter } from "${FOLIATE_DIR.replace(/\\/g, "/")}/vendor/zip.js"; import { EPUB } from "${FOLIATE_DIR.replace(/\\/g, "/")}/epub.js"; import { extractPDFChapters, makePDFFromURL } from "${FOLIATE_DIR.replace(/\\/g, "/")}/pdf.js"; + import { ReaderExtractionSessions } from "${EXTRACTION_SESSIONS.replace(/\\/g, "/")}"; window.makeBook = makeBook; window.Overlayer = Overlayer; @@ -32,6 +34,7 @@ async function buildReader() { window._EPUB = EPUB; window._makePDFFromURL = makePDFFromURL; window._extractPDFChapters = extractPDFChapters; + window.ReaderExtractionSessions = ReaderExtractionSessions; if (!customElements.get('foliate-view')) { customElements.define('foliate-view', View); diff --git a/packages/app-expo/src/components/library/BookCard.tsx b/packages/app-expo/src/components/library/BookCard.tsx index b7ad23e1b..e0a6a6ed4 100644 --- a/packages/app-expo/src/components/library/BookCard.tsx +++ b/packages/app-expo/src/components/library/BookCard.tsx @@ -1,4 +1,5 @@ import { CheckIcon, ClockIcon, Loader2Icon, MoreVerticalIcon } from "@/components/ui/Icon"; +import { isVectorizationCancellable } from "@/lib/rag/vectorization-cancel-state"; import { useColors } from "@/styles/theme"; import { getPlatformService } from "@readany/core/services"; /** @@ -126,6 +127,10 @@ export const BookCard = memo(function BookCard({ ? Math.round((vectorProgress.processedChunks / vectorProgress.totalChunks) * 100) : 0 : 0; + const canCancelVectorization = isVectorizationCancellable( + Boolean(isVectorizing), + vectorProgress?.status, + ); const measureAnchor = useCallback(async () => { const measureNode = (node: View | null, fallbackToBottomRight = false) => @@ -289,13 +294,12 @@ export const BookCard = memo(function BookCard({ )} {/* Vectorization progress overlay */} - {isVectorizing && ( + {isVectorizing && canCancelVectorization && ( { event.stopPropagation(); onCancelVectorize?.(book.id); @@ -325,6 +329,22 @@ export const BookCard = memo(function BookCard({ )} )} + {isVectorizing && !canCancelVectorization && ( + + {vectorProgress?.status !== "cancelled" && } + + {vectorProgress?.status === "cancelling" + ? t("home.vec_cancelling", "Cancelling…") + : vectorProgress?.status === "cancelled" + ? t("home.vec_cancelled", "Cancelled") + : vectorProgress?.status === "completed" + ? "✓" + : vectorProgress?.status === "error" + ? "✕" + : t("home.vec_processing")} + + + )} {/* Queued overlay */} {isQueued && !isVectorizing && ( diff --git a/packages/app-expo/src/lib/rag/auto-vectorize-service.test.ts b/packages/app-expo/src/lib/rag/auto-vectorize-service.test.ts index e4b40d41f..2ff709047 100644 --- a/packages/app-expo/src/lib/rag/auto-vectorize-service.test.ts +++ b/packages/app-expo/src/lib/rag/auto-vectorize-service.test.ts @@ -1,3 +1,4 @@ +import { VectorizationCleanupError } from "@readany/core/rag"; import type { Book } from "@readany/core/types"; import { afterEach, describe, expect, it, vi } from "vitest"; @@ -57,7 +58,8 @@ describe("automatic vectorization failure lifecycle", () => { }); it("publishes the failure and releases the queue if cleanup itself rejects", async () => { - vectorizeMocks.resetBookVectorization.mockRejectedValueOnce(new Error("cleanup failed")); + const cleanupError = new Error("cleanup failed"); + vectorizeMocks.resetBookVectorization.mockRejectedValueOnce(cleanupError); setExtractorRef({ extractChapters: vi.fn().mockRejectedValue(new Error("loader failed")), }); @@ -70,9 +72,38 @@ describe("automatic vectorization failure lifecycle", () => { expect(callback).toHaveBeenCalledWith("book-1", { status: "error", progress: 0, + error: expect.anything(), errorCategory: "unknown", + cleanupError, }); expect(isProcessing()).toBe(false); }); }); + + it("surfaces core cleanup failure instead of discarding it", async () => { + const cleanupError = new Error("partial vectors remain"); + setExtractorRef({ + extractChapters: vi.fn().mockResolvedValue([{ index: 0, title: "Chapter", content: "text" }]), + }); + vectorizeMocks.triggerVectorizeBook.mockRejectedValueOnce( + new VectorizationCleanupError(new Error("cancelled"), cleanupError), + ); + const callback = vi.fn(); + setCallback(callback); + + await queueBook(book, "base64", "application/x-mobipocket-ebook"); + + await vi.waitFor(() => { + expect(callback).toHaveBeenCalledWith( + "book-1", + expect.objectContaining({ + status: "error", + error: expect.any(VectorizationCleanupError), + cleanupError, + }), + ); + expect(vectorizeMocks.resetBookVectorization).not.toHaveBeenCalled(); + expect(isProcessing()).toBe(false); + }); + }); }); diff --git a/packages/app-expo/src/lib/rag/auto-vectorize-service.ts b/packages/app-expo/src/lib/rag/auto-vectorize-service.ts index 15ced9d55..220963ad8 100644 --- a/packages/app-expo/src/lib/rag/auto-vectorize-service.ts +++ b/packages/app-expo/src/lib/rag/auto-vectorize-service.ts @@ -5,7 +5,13 @@ import { runVectorizeQueueJob, throwIfQueueJobAborted } from "./vectorize-queue- export type AutoVectorizeCallback = ( bookId: string, - progress: { status: string; progress: number; errorCategory?: BookExtractionErrorCategory }, + progress: { + status: string; + progress: number; + error?: unknown; + errorCategory?: BookExtractionErrorCategory; + cleanupError?: unknown; + }, ) => void; interface ExtractorRef { @@ -112,7 +118,9 @@ async function processQueue() { callback?.(book.id, { status: "error", progress: 0, + error: event.error, errorCategory: event.errorCategory, + cleanupError: event.cleanupError, }); } }, diff --git a/packages/app-expo/src/lib/rag/extractor-cancellation-boundary.test.ts b/packages/app-expo/src/lib/rag/extractor-cancellation-boundary.test.ts deleted file mode 100644 index fd8cc49f1..000000000 --- a/packages/app-expo/src/lib/rag/extractor-cancellation-boundary.test.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { readFileSync } from "node:fs"; -import { resolve } from "node:path"; -import { describe, expect, it } from "vitest"; - -describe("extractor cancellation boundary", () => { - it("routes request ids and checks cancellation inside reader extraction", () => { - const template = readFileSync( - resolve(process.cwd(), "assets/reader/reader.template.html"), - "utf8", - ); - - expect(template).toContain("case 'cancelExtraction'"); - expect(template).toContain("throwIfExtractionCancelled(requestId)"); - expect(template).toMatch(/postToRN\('chaptersExtracted', \{[^}]*requestId/s); - }); -}); diff --git a/packages/app-expo/src/lib/rag/reader-extraction-sessions.test.ts b/packages/app-expo/src/lib/rag/reader-extraction-sessions.test.ts new file mode 100644 index 000000000..81355e8d1 --- /dev/null +++ b/packages/app-expo/src/lib/rag/reader-extraction-sessions.test.ts @@ -0,0 +1,45 @@ +import { describe, expect, it } from "vitest"; +import { ReaderExtractionSessions } from "./reader-extraction-sessions"; + +function deferred() { + let resolve: (value: T) => void = () => {}; + const promise = new Promise((promiseResolve) => { + resolve = promiseResolve; + }); + return { promise, resolve }; +} + +describe("ReaderExtractionSessions", () => { + it("keeps A and B paired with their own books when B opens first", async () => { + const sessions = new ReaderExtractionSessions<{ chapters: string[] }>(); + const a = deferred<{ chapters: string[] }>(); + const b = deferred<{ chapters: string[] }>(); + + const openA = sessions.open("A", () => a.promise); + const openB = sessions.open("B", () => b.promise); + b.resolve({ chapters: ["B chapter"] }); + await openB; + a.resolve({ chapters: ["A chapter"] }); + await openA; + + expect(sessions.getBook("A").chapters).toEqual(["A chapter"]); + expect(sessions.getBook("B").chapters).toEqual(["B chapter"]); + }); + + it("rejects cancelled A after its parser settles without contaminating B", async () => { + const sessions = new ReaderExtractionSessions<{ chapters: string[] }>(); + const a = deferred<{ chapters: string[] }>(); + const b = deferred<{ chapters: string[] }>(); + + const openA = sessions.open("A", () => a.promise); + sessions.cancel("A"); + const openB = sessions.open("B", () => b.promise); + b.resolve({ chapters: ["B chapter"] }); + await openB; + a.resolve({ chapters: ["A chapter"] }); + + await expect(openA).rejects.toMatchObject({ name: "AbortError" }); + expect(sessions.getBook("B").chapters).toEqual(["B chapter"]); + expect(() => sessions.getBook("A")).toThrow(/cancelled|not available/i); + }); +}); diff --git a/packages/app-expo/src/lib/rag/reader-extraction-sessions.ts b/packages/app-expo/src/lib/rag/reader-extraction-sessions.ts new file mode 100644 index 000000000..8e8ab312d --- /dev/null +++ b/packages/app-expo/src/lib/rag/reader-extraction-sessions.ts @@ -0,0 +1,50 @@ +function createAbortError(): Error { + const error = new Error("Vectorization cancelled"); + error.name = "AbortError"; + return error; +} + +/** Owns parser results by request so concurrent reader work cannot cross books. */ +export class ReaderExtractionSessions { + private readonly books = new Map(); + private readonly cancelled = new Set(); + + async open(requestId: string, load: () => Promise): Promise { + this.throwIfCancelled(requestId); + try { + const book = await load(); + this.throwIfCancelled(requestId); + this.books.set(requestId, book); + return book; + } catch (error) { + this.books.delete(requestId); + if (this.cancelled.has(requestId)) { + this.cancelled.delete(requestId); + throw createAbortError(); + } + throw error; + } + } + + cancel(requestId: string): void { + this.cancelled.add(requestId); + this.books.delete(requestId); + } + + getBook(requestId: string): Book { + this.throwIfCancelled(requestId); + const book = this.books.get(requestId); + if (!book) throw new Error(`Book for extraction request ${requestId} is not available`); + return book; + } + + throwIfCancelled(requestId?: string): void { + if (requestId && this.cancelled.has(requestId)) throw createAbortError(); + } + + release(requestId?: string): void { + if (!requestId) return; + this.books.delete(requestId); + this.cancelled.delete(requestId); + } +} diff --git a/packages/app-expo/src/lib/rag/vectorization-cancel-state.test.ts b/packages/app-expo/src/lib/rag/vectorization-cancel-state.test.ts new file mode 100644 index 000000000..d27d564ff --- /dev/null +++ b/packages/app-expo/src/lib/rag/vectorization-cancel-state.test.ts @@ -0,0 +1,18 @@ +import { describe, expect, it } from "vitest"; +import { isVectorizationCancellable } from "./vectorization-cancel-state"; + +describe("isVectorizationCancellable", () => { + it.each([undefined, "loading", "extracting", "chunking", "vectorizing", "embedding", "indexing"])( + "allows cancellation while active in %s", + (status) => expect(isVectorizationCancellable(true, status)).toBe(true), + ); + + it.each(["cancelling", "completed", "error", "cancelled"])( + "disables the terminal %s overlay", + (status) => expect(isVectorizationCancellable(true, status)).toBe(false), + ); + + it("does not expose a cancel action for an inactive card", () => { + expect(isVectorizationCancellable(false, "embedding")).toBe(false); + }); +}); diff --git a/packages/app-expo/src/lib/rag/vectorization-cancel-state.ts b/packages/app-expo/src/lib/rag/vectorization-cancel-state.ts new file mode 100644 index 000000000..4843881ae --- /dev/null +++ b/packages/app-expo/src/lib/rag/vectorization-cancel-state.ts @@ -0,0 +1,12 @@ +const CANCELLABLE_STATUSES = new Set([ + "loading", + "extracting", + "chunking", + "vectorizing", + "embedding", + "indexing", +]); + +export function isVectorizationCancellable(isActive: boolean, status?: string): boolean { + return isActive && (status === undefined || CANCELLABLE_STATUSES.has(status)); +} diff --git a/packages/app-expo/src/lib/rag/vectorize-queue-job.test.ts b/packages/app-expo/src/lib/rag/vectorize-queue-job.test.ts index c622ed15e..cf4ceb679 100644 --- a/packages/app-expo/src/lib/rag/vectorize-queue-job.test.ts +++ b/packages/app-expo/src/lib/rag/vectorize-queue-job.test.ts @@ -1,3 +1,4 @@ +import { VectorizationCleanupError } from "@readany/core/rag"; import { describe, expect, it, vi } from "vitest"; import { MOBI } from "../../../../foliate-js/mobi.js"; import { BookExtractionError } from "./extractor-error"; @@ -119,7 +120,8 @@ describe("runVectorizeQueueJob", () => { format: "mobi", signal: controller.signal, extract: async () => [{ index: 0, title: "Chapter", content: "text", segments: [] }], - vectorize: async (_chapters, _onProgress, signal) => { + vectorize: async (_chapters, onProgress, signal) => { + onProgress?.({ status: "cancelled" }); controller.abort(); signal.throwIfAborted(); }, @@ -128,11 +130,35 @@ describe("runVectorizeQueueJob", () => { }); expect(result).toMatchObject({ ok: false, cancelled: true }); - expect(cleanup).toHaveBeenCalledOnce(); + expect(cleanup).not.toHaveBeenCalled(); expect(statuses).toEqual(["extracting", "vectorizing", "cancelled"]); expect(statuses).not.toContain("completed"); }); + it("publishes an error when core cancellation cleanup rejects", async () => { + const statuses: string[] = []; + const controller = new AbortController(); + const cleanup = vi.fn(); + const cleanupError = new Error("failed to delete partial chunks"); + + const result = await runVectorizeQueueJob({ + signal: controller.signal, + extract: async () => [{ index: 0, title: "Chapter", content: "text", segments: [] }], + vectorize: async () => { + controller.abort(); + throw new VectorizationCleanupError(controller.signal.reason, cleanupError); + }, + cleanup, + onEvent: (event) => statuses.push(event.status), + }); + + expect(result).toMatchObject({ ok: false, cancelled: false, cleanupError }); + expect(cleanup).not.toHaveBeenCalled(); + expect(statuses).toEqual(["extracting", "vectorizing", "error"]); + expect(statuses).not.toContain("cancelled"); + expect(statuses).not.toContain("completed"); + }); + it("publishes error only after a rejected cleanup attempt", async () => { const order: string[] = []; const cleanupError = new Error("cleanup failed"); @@ -156,7 +182,7 @@ describe("runVectorizeQueueJob", () => { expect(order).toEqual(["extracting", "cleanup", "error"]); }); - it("cleans up a final state-write rejection without publishing completion", async () => { + it("does not double-clean a final state-write rejection or publish completion", async () => { const statuses: string[] = []; const cleanup = vi.fn().mockResolvedValue(undefined); @@ -172,7 +198,7 @@ describe("runVectorizeQueueJob", () => { }); expect(result.ok).toBe(false); - expect(cleanup).toHaveBeenCalledOnce(); + expect(cleanup).not.toHaveBeenCalled(); expect(statuses).toEqual(["extracting", "vectorizing", "error"]); expect(statuses).not.toContain("completed"); }); diff --git a/packages/app-expo/src/lib/rag/vectorize-queue-job.ts b/packages/app-expo/src/lib/rag/vectorize-queue-job.ts index adffb9a71..5b4cb9913 100644 --- a/packages/app-expo/src/lib/rag/vectorize-queue-job.ts +++ b/packages/app-expo/src/lib/rag/vectorize-queue-job.ts @@ -1,4 +1,4 @@ -import type { ChapterData } from "@readany/core/rag"; +import { type ChapterData, VectorizationCleanupError } from "@readany/core/rag"; import { BookExtractionError, type BookExtractionErrorCategory, @@ -54,12 +54,14 @@ function isAbortFailure(error: unknown, signal: AbortSignal): boolean { return signal.aborted || (error instanceof Error && error.name === "AbortError"); } -function isCompletedProgress(progress: unknown): boolean { +function isTerminalProgress(progress: unknown): boolean { return ( typeof progress === "object" && progress !== null && "status" in progress && - progress.status === "completed" + (progress.status === "completed" || + progress.status === "error" || + progress.status === "cancelled") ); } @@ -83,7 +85,7 @@ export async function runVectorizeQueueJob( await options.vectorize( chapters, (progress) => { - if (!isCompletedProgress(progress)) { + if (!isTerminalProgress(progress)) { options.onEvent({ status: "vectorizing", progress }); } }, @@ -94,14 +96,16 @@ export async function runVectorizeQueueJob( options.onEvent({ status: "completed" }); return { ok: true }; } catch (error) { - const cancelled = isAbortFailure(error, signal); const failure = phase === "extracting" ? toBookExtractionError(error, options.format) : error; - let cleanupError: unknown; - try { - await options.cleanup(); - } catch (errorDuringCleanup) { - cleanupError = errorDuringCleanup; + let cleanupError = error instanceof VectorizationCleanupError ? error.cleanupError : undefined; + if (phase === "extracting") { + try { + await options.cleanup(); + } catch (errorDuringCleanup) { + cleanupError = errorDuringCleanup; + } } + const cancelled = !cleanupError && isAbortFailure(error, signal); if (cancelled) { options.onEvent({ status: "cancelled" }); diff --git a/packages/app-expo/src/screens/library/useVectorizationQueue.ts b/packages/app-expo/src/screens/library/useVectorizationQueue.ts index e9f6ace78..5a21ab615 100644 --- a/packages/app-expo/src/screens/library/useVectorizationQueue.ts +++ b/packages/app-expo/src/screens/library/useVectorizationQueue.ts @@ -34,7 +34,7 @@ export function useVectorizationQueue({ extractorRef, nav }: UseVectorizationQue async (book: Book, signal: AbortSignal) => { setVectorizingBookId(book.id); setVectorizingBookTitle(book.meta.title); - const result = await runVectorizeQueueJob({ + return runVectorizeQueueJob({ format: book.format, signal, extract: async (jobSignal) => { @@ -103,7 +103,15 @@ export function useVectorizationQueue({ extractorRef, nav }: UseVectorizationQue totalChunks: 0, }); - if (event.errorCategory) { + if (event.cleanupError) { + Alert.alert( + t("vectorize.cleanupFailedTitle", "Couldn't fully clean up indexing"), + t( + "vectorize.cleanupFailedDesc", + "The book remains marked not indexed, but some partial search data could not be removed. Retry indexing, then restart the app if this book still appears in search.", + ), + ); + } else if (event.errorCategory) { const keys = getBookExtractionErrorMessageKeys(event.errorCategory); Alert.alert(t(keys.title), t(keys.description)); } else { @@ -115,10 +123,6 @@ export function useVectorizationQueue({ extractorRef, nav }: UseVectorizationQue } }, }); - - await new Promise((resolve) => - setTimeout(resolve, result.ok ? 800 : result.cancelled ? 500 : 1500), - ); }, [extractorRef, t], ); @@ -133,8 +137,13 @@ export function useVectorizationQueue({ extractorRef, nav }: UseVectorizationQue if (!nextJob) break; setVectorQueue(queueRef.current.snapshot()); try { - await processOneBook(nextJob.book, nextJob.signal); + const result = await processOneBook(nextJob.book, nextJob.signal); + queueRef.current.markTerminal(nextJob.book.id); + await new Promise((resolve) => + setTimeout(resolve, result.ok ? 800 : result.cancelled ? 500 : 1500), + ); } finally { + queueRef.current.markTerminal(nextJob.book.id); queueRef.current.finish(nextJob.book.id); } } diff --git a/packages/app-expo/src/screens/library/vectorization-queue.test.ts b/packages/app-expo/src/screens/library/vectorization-queue.test.ts index 7a98d0e03..da6a70b5d 100644 --- a/packages/app-expo/src/screens/library/vectorization-queue.test.ts +++ b/packages/app-expo/src/screens/library/vectorization-queue.test.ts @@ -26,4 +26,27 @@ describe("VectorizationQueue", () => { queue.finish("active"); expect(queue.activeBookId).toBeNull(); }); + + it.each(["completed", "error", "cancelled"])( + "ignores a late cancel tap while %s is being presented", + () => { + const queue = new VectorizationQueue<{ id: string }>(); + queue.enqueue({ id: "active" }); + const active = queue.startNext(); + queue.markTerminal("active"); + + expect(queue.cancel("active")).toBe("not-cancellable"); + expect(active?.signal.aborted).toBe(false); + expect(queue.activeBookId).toBe("active"); + }, + ); + + it("makes a second tap inert after cancellation begins", () => { + const queue = new VectorizationQueue<{ id: string }>(); + queue.enqueue({ id: "active" }); + queue.startNext(); + + expect(queue.cancel("active")).toBe("active"); + expect(queue.cancel("active")).toBe("not-cancellable"); + }); }); diff --git a/packages/app-expo/src/screens/library/vectorization-queue.ts b/packages/app-expo/src/screens/library/vectorization-queue.ts index 8cd10a1fa..7d0faf874 100644 --- a/packages/app-expo/src/screens/library/vectorization-queue.ts +++ b/packages/app-expo/src/screens/library/vectorization-queue.ts @@ -1,6 +1,6 @@ export class VectorizationQueue { private queuedBooks: Book[] = []; - private active: { book: Book; controller: AbortController } | null = null; + private active: { book: Book; controller: AbortController; cancellable: boolean } | null = null; get activeBookId(): string | null { return this.active?.book.id ?? null; @@ -23,12 +23,14 @@ export class VectorizationQueue { const book = this.queuedBooks.shift(); if (!book) return null; const controller = new AbortController(); - this.active = { book, controller }; + this.active = { book, controller, cancellable: true }; return { book, signal: controller.signal }; } - cancel(bookId: string): "active" | "queued" | "not-found" { + cancel(bookId: string): "active" | "queued" | "not-cancellable" | "not-found" { if (this.active?.book.id === bookId) { + if (!this.active.cancellable) return "not-cancellable"; + this.active.cancellable = false; this.active.controller.abort(); return "active"; } @@ -38,6 +40,10 @@ export class VectorizationQueue { return "queued"; } + markTerminal(bookId: string): void { + if (this.active?.book.id === bookId) this.active.cancellable = false; + } + finish(bookId: string): void { if (this.active?.book.id === bookId) this.active = null; } diff --git a/packages/core/src/i18n/library-vectorize-locales.test.ts b/packages/core/src/i18n/library-vectorize-locales.test.ts index 5c321a771..5477849ba 100644 --- a/packages/core/src/i18n/library-vectorize-locales.test.ts +++ b/packages/core/src/i18n/library-vectorize-locales.test.ts @@ -19,6 +19,8 @@ const VECTORIZE_ERROR_KEYS = [ "extractionFailedDesc", "vectorizationFailedTitle", "vectorizationFailedDesc", + "cleanupFailedTitle", + "cleanupFailedDesc", ] as const; describe("library vectorization error translations", () => { diff --git a/packages/core/src/i18n/locales/en/library.json b/packages/core/src/i18n/locales/en/library.json index 87ac00d40..97b4664ab 100644 --- a/packages/core/src/i18n/locales/en/library.json +++ b/packages/core/src/i18n/locales/en/library.json @@ -171,7 +171,9 @@ "extractionFailedTitle": "Couldn't read book", "extractionFailedDesc": "ReadAny couldn't extract readable text from this book. Try downloading or exporting a fresh DRM-free copy.", "vectorizationFailedTitle": "Couldn't vectorize book", - "vectorizationFailedDesc": "ReadAny couldn't finish vectorizing this book. Check your vector model settings and try again." + "vectorizationFailedDesc": "ReadAny couldn't finish vectorizing this book. Check your vector model settings and try again.", + "cleanupFailedTitle": "Couldn't fully clean up indexing", + "cleanupFailedDesc": "The book remains marked not indexed, but some partial search data could not be removed. Retry indexing, then restart the app if this book still appears in search." }, "home": { "library": "Library", diff --git a/packages/core/src/i18n/locales/es/library.json b/packages/core/src/i18n/locales/es/library.json index 7165fe41b..2f6a875b7 100644 --- a/packages/core/src/i18n/locales/es/library.json +++ b/packages/core/src/i18n/locales/es/library.json @@ -158,7 +158,9 @@ "extractionFailedTitle": "No se pudo leer el libro", "extractionFailedDesc": "ReadAny no pudo extraer texto legible de este libro. Prueba a descargar o exportar una copia nueva sin DRM.", "vectorizationFailedTitle": "No se pudo vectorizar el libro", - "vectorizationFailedDesc": "ReadAny no pudo terminar de vectorizar este libro. Comprueba la configuración del modelo vectorial e inténtalo de nuevo." + "vectorizationFailedDesc": "ReadAny no pudo terminar de vectorizar este libro. Comprueba la configuración del modelo vectorial e inténtalo de nuevo.", + "cleanupFailedTitle": "No se pudo limpiar por completo la indexación", + "cleanupFailedDesc": "El libro sigue marcado como no indexado, pero no se pudieron eliminar algunos datos parciales de búsqueda. Vuelve a indexarlo y reinicia la aplicación si aún aparece en la búsqueda." }, "home": { "library": "Biblioteca", diff --git a/packages/core/src/i18n/locales/fr/library.json b/packages/core/src/i18n/locales/fr/library.json index 14d3d0741..9703d9b94 100644 --- a/packages/core/src/i18n/locales/fr/library.json +++ b/packages/core/src/i18n/locales/fr/library.json @@ -158,7 +158,9 @@ "extractionFailedTitle": "Impossible de lire le livre", "extractionFailedDesc": "ReadAny n'a pas pu extraire de texte lisible de ce livre. Essayez de télécharger ou d'exporter une nouvelle copie sans DRM.", "vectorizationFailedTitle": "Impossible de vectoriser le livre", - "vectorizationFailedDesc": "ReadAny n'a pas pu terminer la vectorisation de ce livre. Vérifiez les paramètres du modèle vectoriel et réessayez." + "vectorizationFailedDesc": "ReadAny n'a pas pu terminer la vectorisation de ce livre. Vérifiez les paramètres du modèle vectoriel et réessayez.", + "cleanupFailedTitle": "Nettoyage incomplet de l'indexation", + "cleanupFailedDesc": "Le livre reste indiqué comme non indexé, mais certaines données de recherche partielles n'ont pas pu être supprimées. Relancez l'indexation, puis redémarrez l'application si ce livre apparaît encore dans la recherche." }, "home": { "library": "Bibliothèque", diff --git a/packages/core/src/i18n/locales/ja/library.json b/packages/core/src/i18n/locales/ja/library.json index 9086192d1..42e0b1fe1 100644 --- a/packages/core/src/i18n/locales/ja/library.json +++ b/packages/core/src/i18n/locales/ja/library.json @@ -158,7 +158,9 @@ "extractionFailedTitle": "書籍を読み取れませんでした", "extractionFailedDesc": "この書籍から読み取り可能なテキストを抽出できませんでした。DRM フリーの新しいコピーをダウンロードまたは書き出してください。", "vectorizationFailedTitle": "書籍をベクトル化できませんでした", - "vectorizationFailedDesc": "この書籍のベクトル化を完了できませんでした。ベクトルモデルの設定を確認して、もう一度お試しください。" + "vectorizationFailedDesc": "この書籍のベクトル化を完了できませんでした。ベクトルモデルの設定を確認して、もう一度お試しください。", + "cleanupFailedTitle": "インデックスを完全に削除できませんでした", + "cleanupFailedDesc": "書籍は未インデックスとして表示されますが、一部の検索データを削除できませんでした。再度インデックスを作成し、検索に残る場合はアプリを再起動してください。" }, "home": { "library": "ライブラリ", diff --git a/packages/core/src/i18n/locales/ko/library.json b/packages/core/src/i18n/locales/ko/library.json index 2b43fcc12..a61d48f32 100644 --- a/packages/core/src/i18n/locales/ko/library.json +++ b/packages/core/src/i18n/locales/ko/library.json @@ -158,7 +158,9 @@ "extractionFailedTitle": "책을 읽을 수 없음", "extractionFailedDesc": "이 책에서 읽을 수 있는 텍스트를 추출하지 못했습니다. DRM이 없는 새 사본을 다운로드하거나 내보낸 뒤 다시 시도하세요.", "vectorizationFailedTitle": "책을 벡터화할 수 없음", - "vectorizationFailedDesc": "이 책의 벡터화를 완료하지 못했습니다. 벡터 모델 설정을 확인하고 다시 시도하세요." + "vectorizationFailedDesc": "이 책의 벡터화를 완료하지 못했습니다. 벡터 모델 설정을 확인하고 다시 시도하세요.", + "cleanupFailedTitle": "인덱싱을 완전히 정리하지 못함", + "cleanupFailedDesc": "책은 인덱싱되지 않은 상태로 표시되지만 일부 검색 데이터를 제거하지 못했습니다. 다시 인덱싱하고 검색에 계속 나타나면 앱을 다시 시작하세요." }, "home": { "library": "서재", diff --git a/packages/core/src/i18n/locales/zh-TW/library.json b/packages/core/src/i18n/locales/zh-TW/library.json index c588e04fb..2ab628124 100644 --- a/packages/core/src/i18n/locales/zh-TW/library.json +++ b/packages/core/src/i18n/locales/zh-TW/library.json @@ -158,7 +158,9 @@ "extractionFailedTitle": "無法讀取書籍", "extractionFailedDesc": "ReadAny 無法從此書中擷取可讀文字。請重新下載或匯出一份不含 DRM 的副本。", "vectorizationFailedTitle": "無法向量化書籍", - "vectorizationFailedDesc": "ReadAny 無法完成此書的向量化。請檢查向量模型設定後再試一次。" + "vectorizationFailedDesc": "ReadAny 無法完成此書的向量化。請檢查向量模型設定後再試一次。", + "cleanupFailedTitle": "無法完全清理索引", + "cleanupFailedDesc": "本書仍會顯示為未索引,但部分搜尋資料無法刪除。請重新建立索引;如果搜尋中仍出現本書,請重新啟動應用程式。" }, "home": { "library": "書庫", diff --git a/packages/core/src/i18n/locales/zh/library.json b/packages/core/src/i18n/locales/zh/library.json index 160c1cf1d..90faf5d63 100644 --- a/packages/core/src/i18n/locales/zh/library.json +++ b/packages/core/src/i18n/locales/zh/library.json @@ -171,7 +171,9 @@ "extractionFailedTitle": "无法读取书籍", "extractionFailedDesc": "ReadAny 无法从此书中提取可读文本。请重新下载或导出一份不含 DRM 的副本。", "vectorizationFailedTitle": "无法向量化书籍", - "vectorizationFailedDesc": "ReadAny 无法完成此书的向量化。请检查向量模型设置后重试。" + "vectorizationFailedDesc": "ReadAny 无法完成此书的向量化。请检查向量模型设置后重试。", + "cleanupFailedTitle": "无法完全清理索引", + "cleanupFailedDesc": "本书仍会显示为未索引,但部分搜索数据无法删除。请重新建立索引;如果搜索中仍出现本书,请重启应用。" }, "home": { "library": "书库", diff --git a/packages/core/src/rag/index.ts b/packages/core/src/rag/index.ts index a931def99..f4341b739 100644 --- a/packages/core/src/rag/index.ts +++ b/packages/core/src/rag/index.ts @@ -62,7 +62,11 @@ export type { export { vectorizeBook } from "./vectorize"; export type { VectorizeCallback } from "./vectorize"; -export { resetBookVectorization, triggerVectorizeBook } from "./vectorize-trigger"; +export { + resetBookVectorization, + triggerVectorizeBook, + VectorizationCleanupError, +} from "./vectorize-trigger"; export type { VectorizeStatusCallback, VectorizeTriggerConfig, diff --git a/packages/core/src/rag/vectorize-trigger.test.ts b/packages/core/src/rag/vectorize-trigger.test.ts index f8615ffe1..da319ac6e 100644 --- a/packages/core/src/rag/vectorize-trigger.test.ts +++ b/packages/core/src/rag/vectorize-trigger.test.ts @@ -36,6 +36,7 @@ vi.mock("./vector-db", () => ({ vi.mock("../utils/event-bus", () => ({ eventBus: eventBusMocks })); import { + VectorizationCleanupError, canStoreInSharedVectorDB, resetBookVectorization, triggerVectorizeBook, @@ -315,4 +316,47 @@ describe("failed vectorization cleanup", () => { expect(databaseMocks.setVectorIndexProvenance).not.toHaveBeenCalled(); expect(eventBusMocks.emit).not.toHaveBeenCalledWith("vectorize:completed", expect.anything()); }); + + it("publishes error instead of cancelled when partial-index cleanup rejects", async () => { + const controller = new AbortController(); + const progressStatuses: string[] = []; + const onBookReset = vi.fn(); + databaseMocks.deleteChunks + .mockResolvedValueOnce(undefined) + .mockRejectedValueOnce(new Error("failed to delete partial chunks")); + databaseMocks.insertChunks.mockImplementationOnce(async () => { + controller.abort(); + }); + + const vectorization = triggerVectorizeBook( + "book-1", + [{ index: 0, title: "Chapter", content: "content" }], + { + vectorModelEnabled: true, + vectorModelMode: "remote", + selectedBuiltinModelId: null, + remoteModel: { + url: "https://example.com/v1/embeddings", + apiKey: "test", + modelId: "test-model", + }, + }, + { onBookUpdate: vi.fn(), onBookReset }, + (progress) => progressStatuses.push(progress.status), + controller.signal, + ); + + await expect(vectorization).rejects.toBeInstanceOf(VectorizationCleanupError); + expect(progressStatuses.at(-1)).toBe("error"); + expect(onBookReset).toHaveBeenCalledWith("book-1", { + isVectorized: false, + vectorizeProgress: 0, + }); + expect(eventBusMocks.emit).not.toHaveBeenCalledWith("vectorize:cancelled", expect.anything()); + expect(eventBusMocks.emit).not.toHaveBeenCalledWith("vectorize:completed", expect.anything()); + expect(eventBusMocks.emit).toHaveBeenCalledWith("vectorize:error", { + bookId: "book-1", + error: expect.stringContaining("failed to delete partial chunks"), + }); + }); }); diff --git a/packages/core/src/rag/vectorize-trigger.ts b/packages/core/src/rag/vectorize-trigger.ts index 91dcbdf8f..864911205 100644 --- a/packages/core/src/rag/vectorize-trigger.ts +++ b/packages/core/src/rag/vectorize-trigger.ts @@ -77,6 +77,20 @@ export function isVectorizationAbort(error: unknown): boolean { return error instanceof Error && error.name === "AbortError"; } +export class VectorizationCleanupError extends Error { + readonly operationError: unknown; + readonly cleanupError: unknown; + + constructor(operationError: unknown, cleanupError: unknown) { + const cleanupMessage = + cleanupError instanceof Error ? cleanupError.message : String(cleanupError); + super(`Vectorization stopped, but partial index cleanup failed: ${cleanupMessage}`); + this.name = "VectorizationCleanupError"; + this.operationError = operationError; + this.cleanupError = cleanupError; + } +} + /** sqlite-vec currently owns one global vector dimension. */ export function canStoreInSharedVectorDB( stats: { totalVectors: number; dimension: number }, @@ -323,10 +337,20 @@ export async function triggerVectorizeBook( }); } catch (err) { const message = err instanceof Error ? err.message : String(err); + let cleanupError: unknown; try { await resetBookVectorization(bookId, callbacks); - } catch (cleanupError) { - console.error(`[Vectorize] Failed to clean up partial index for ${bookId}:`, cleanupError); + } catch (errorDuringCleanup) { + cleanupError = errorDuringCleanup; + } + + if (cleanupError) { + const failure = new VectorizationCleanupError(err, cleanupError); + progress.status = "error"; + progress.error = failure.message; + onProgress?.(progress); + eventBus.emit("vectorize:error", { bookId, error: failure.message }); + throw failure; } const cancelled = isVectorizationAbort(err); From afde9986061384e55c31fc42ad1f12a7eb12050c Mon Sep 17 00:00:00 2001 From: Chai Date: Sun, 16 Aug 2026 22:12:23 -0400 Subject: [PATCH 10/10] fix(rag): cancel timed-out extraction --- .../src/components/rag/ExtractorWebView.tsx | 174 +++++++----------- .../rag/extractor-request-boundary.test.ts | 104 +++++++++++ .../src/lib/rag/extractor-request-boundary.ts | 108 +++++++++++ 3 files changed, 279 insertions(+), 107 deletions(-) create mode 100644 packages/app-expo/src/lib/rag/extractor-request-boundary.test.ts create mode 100644 packages/app-expo/src/lib/rag/extractor-request-boundary.ts diff --git a/packages/app-expo/src/components/rag/ExtractorWebView.tsx b/packages/app-expo/src/components/rag/ExtractorWebView.tsx index affa694f6..7d4280193 100644 --- a/packages/app-expo/src/components/rag/ExtractorWebView.tsx +++ b/packages/app-expo/src/components/rag/ExtractorWebView.tsx @@ -3,9 +3,10 @@ import type { Book } from "@readany/core/types"; import { Asset } from "expo-asset"; import { forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from "react"; import { StyleSheet, View } from "react-native"; -import { WebView } from "react-native-webview"; +import { WebView, type WebViewMessageEvent } from "react-native-webview"; import { toBookExtractionError } from "../../lib/rag/extractor-error"; import { createExtractorCommand } from "../../lib/rag/extractor-format"; +import { ExtractorRequestBoundary } from "../../lib/rag/extractor-request-boundary"; const READER_HTML_ASSET = Asset.fromModule(require("../../../assets/reader/reader.html")); const EXTRACTION_TIMEOUT_MS = 45_000; @@ -20,16 +21,6 @@ export interface ExtractorRef { ) => Promise; } -interface PendingExtraction { - requestId: string; - resolve: (chapters: ChapterData[]) => void; - reject: (err: Error) => void; - timeoutId: ReturnType; - bookFormat?: Book["format"]; - signal?: AbortSignal; - abortHandler?: () => void; -} - function getAbortError(signal: AbortSignal): Error { const reason = signal.reason; if (reason instanceof Error) { @@ -45,24 +36,29 @@ export const ExtractorWebView = forwardRef((_, ref) => { const webViewRef = useRef(null); const [htmlUri, setHtmlUri] = useState(null); const [ready, setReady] = useState(false); - - // Pending extraction requests - const pendingRequests = useRef([]); + const [requestBoundary] = useState( + () => + new ExtractorRequestBoundary({ + timeoutMs: EXTRACTION_TIMEOUT_MS, + sendCancel: (requestId) => { + webViewRef.current?.injectJavaScript(` + window.postMessage(${JSON.stringify( + JSON.stringify({ type: "cancelExtraction", requestId }), + )}, "*"); + true; + `); + }, + onCancelError: (requestId, error) => { + console.warn(`[ExtractorWebView] Failed to cancel request ${requestId}:`, error); + }, + }), + ); useEffect(() => { return () => { - for (const pending of pendingRequests.current) { - clearTimeout(pending.timeoutId); - if (pending.abortHandler) { - pending.signal?.removeEventListener("abort", pending.abortHandler); - } - pending.reject( - toBookExtractionError(new Error("Extractor WebView unmounted"), pending.bookFormat), - ); - } - pendingRequests.current = []; + requestBoundary.rejectAll(); }; - }, []); + }, [requestBoundary]); useEffect(() => { const loadAsset = async () => { @@ -78,19 +74,16 @@ export const ExtractorWebView = forwardRef((_, ref) => { loadAsset(); }, []); - // biome-ignore lint/suspicious/noExplicitAny: Required for React Native WebView events - const handleMessage = useCallback((event: any) => { - try { - const msg = JSON.parse(event.nativeEvent.data); - if (msg.type === "ready") { - setReady(true); - } else if (msg.type === "loaded") { - const pending = pendingRequests.current.find( - (request) => request.requestId === msg.requestId, - ); - if (!pending) return; - // Trigger extraction once the book is fully loaded - webViewRef.current?.injectJavaScript(` + const handleMessage = useCallback( + (event: WebViewMessageEvent) => { + try { + const msg = JSON.parse(event.nativeEvent.data); + if (msg.type === "ready") { + setReady(true); + } else if (msg.type === "loaded") { + if (!requestBoundary.has(msg.requestId)) return; + // Trigger extraction once the book is fully loaded + webViewRef.current?.injectJavaScript(` if (window.handleExtractChapters) { window.handleExtractChapters(${JSON.stringify(msg.requestId)}); } else { @@ -98,42 +91,33 @@ export const ExtractorWebView = forwardRef((_, ref) => { } true; `); - } else if (msg.type === "chaptersExtracted") { - const index = pendingRequests.current.findIndex( - (request) => request.requestId === msg.requestId, - ); - const pending = index >= 0 ? pendingRequests.current.splice(index, 1)[0] : undefined; - if (!pending) return; - - clearTimeout(pending.timeoutId); - if (pending.abortHandler) { - pending.signal?.removeEventListener("abort", pending.abortHandler); - } - if (msg.error) { - pending.reject(toBookExtractionError(new Error(String(msg.error)), pending.bookFormat)); - } else if (msg.chapters) { - pending.resolve(msg.chapters); - } - } else if (msg.type === "debug") { - console.log("[ExtractorWebView]", msg.message); - } else if (msg.type === "error") { - console.error("[ExtractorWebView] WebView error:", msg.message); - const index = pendingRequests.current.findIndex( - (request) => request.requestId === msg.requestId, - ); - const pending = index >= 0 ? pendingRequests.current.splice(index, 1)[0] : undefined; - if (pending) { - clearTimeout(pending.timeoutId); - if (pending.abortHandler) { - pending.signal?.removeEventListener("abort", pending.abortHandler); + } else if (msg.type === "chaptersExtracted") { + const classificationFormat = requestBoundary.getContext(msg.requestId); + if (msg.error) { + requestBoundary.reject( + msg.requestId, + toBookExtractionError(new Error(String(msg.error)), classificationFormat), + ); + } else if (msg.chapters) { + requestBoundary.resolve(msg.requestId, msg.chapters); } - pending.reject(toBookExtractionError(new Error(String(msg.message)), pending.bookFormat)); + } else if (msg.type === "debug") { + console.log("[ExtractorWebView]", msg.message); + } else if (msg.type === "error") { + if (!requestBoundary.has(msg.requestId)) return; + console.error("[ExtractorWebView] WebView error:", msg.message); + const classificationFormat = requestBoundary.getContext(msg.requestId); + requestBoundary.reject( + msg.requestId, + toBookExtractionError(new Error(String(msg.message)), classificationFormat), + ); } + } catch (err) { + console.warn("[ExtractorWebView] Failed to parse message:", err); } - } catch (err) { - console.warn("[ExtractorWebView] Failed to parse message:", err); - } - }, []); + }, + [requestBoundary], + ); useImperativeHandle(ref, () => ({ extractChapters: ( @@ -160,41 +144,21 @@ export const ExtractorWebView = forwardRef((_, ref) => { ); } - const timeoutId = setTimeout(() => { - const index = pendingRequests.current.findIndex((pending) => pending.reject === reject); - if (index >= 0) pendingRequests.current.splice(index, 1); - signal?.removeEventListener("abort", abortHandler); - reject( - toBookExtractionError( - new Error("Timed out extracting book content"), - classificationFormat, - ), - ); - }, EXTRACTION_TIMEOUT_MS); - - const abortHandler = () => { - clearTimeout(timeoutId); - const index = pendingRequests.current.indexOf(pendingRequest); - if (index >= 0) pendingRequests.current.splice(index, 1); - webViewRef.current?.injectJavaScript(` - window.postMessage(${JSON.stringify( - JSON.stringify({ type: "cancelExtraction", requestId }), - )}, "*"); - true; - `); - reject(getAbortError(signal as AbortSignal)); - }; - const pendingRequest: PendingExtraction = { + requestBoundary.add({ requestId, resolve, reject, - timeoutId, - bookFormat: classificationFormat, + context: classificationFormat, signal, - abortHandler, - }; - pendingRequests.current.push(pendingRequest); - signal?.addEventListener("abort", abortHandler, { once: true }); + abortError: () => getAbortError(signal as AbortSignal), + timeoutError: () => + toBookExtractionError( + new Error("Timed out extracting book content"), + classificationFormat, + ), + disposeError: () => + toBookExtractionError(new Error("Extractor WebView unmounted"), classificationFormat), + }); // Command the webview to open the book first. // It will reply with "loaded" when it finishes rendering. @@ -204,11 +168,7 @@ export const ExtractorWebView = forwardRef((_, ref) => { true; `); } catch (error) { - clearTimeout(timeoutId); - const index = pendingRequests.current.indexOf(pendingRequest); - if (index >= 0) pendingRequests.current.splice(index, 1); - signal?.removeEventListener("abort", abortHandler); - reject(toBookExtractionError(error, classificationFormat)); + requestBoundary.reject(requestId, toBookExtractionError(error, classificationFormat)); } }); }, diff --git a/packages/app-expo/src/lib/rag/extractor-request-boundary.test.ts b/packages/app-expo/src/lib/rag/extractor-request-boundary.test.ts new file mode 100644 index 000000000..9f075c85b --- /dev/null +++ b/packages/app-expo/src/lib/rag/extractor-request-boundary.test.ts @@ -0,0 +1,104 @@ +import { afterEach, describe, expect, it, vi } from "vitest"; +import { ExtractorRequestBoundary } from "./extractor-request-boundary"; +import { ReaderExtractionSessions } from "./reader-extraction-sessions"; + +function deferred() { + let resolve: (value: T) => void = () => {}; + const promise = new Promise((promiseResolve) => { + resolve = promiseResolve; + }); + return { promise, resolve }; +} + +afterEach(() => { + vi.useRealTimers(); +}); + +describe("ExtractorRequestBoundary timeout", () => { + it("cancels and releases timed-out A before isolated B completes", async () => { + vi.useFakeTimers(); + const order: string[] = []; + const sessions = new ReaderExtractionSessions<{ chapters: string[] }>(); + const boundary = new ExtractorRequestBoundary({ + timeoutMs: 45_000, + sendCancel: (requestId) => { + order.push(`cancel:${requestId}`); + sessions.cancel(requestId); + }, + }); + const parserA = deferred<{ chapters: string[] }>(); + const openA = sessions.open("A", () => parserA.promise); + const resultA = new Promise((resolve, reject) => { + boundary.add({ + requestId: "A", + resolve, + reject: (error) => { + order.push("reject:A"); + reject(error); + }, + timeoutError: () => new Error("Timed out extracting book content"), + }); + }); + + const timedOut = expect(resultA).rejects.toThrow("Timed out extracting book content"); + await vi.advanceTimersByTimeAsync(45_000); + await timedOut; + expect(order).toEqual(["cancel:A", "reject:A"]); + + const cancelledOpen = expect(openA).rejects.toMatchObject({ name: "AbortError" }); + parserA.resolve({ chapters: ["A chapter"] }); + await cancelledOpen; + expect(boundary.resolve("A", ["late A chapter"])).toBe(false); + + const openB = sessions.open("B", async () => ({ chapters: ["B chapter"] })); + const resultB = new Promise((resolve, reject) => { + boundary.add({ + requestId: "B", + resolve, + reject, + timeoutError: () => new Error("B timed out"), + }); + }); + const bookB = await openB; + expect(boundary.resolve("B", bookB.chapters)).toBe(true); + + await expect(resultB).resolves.toEqual(["B chapter"]); + expect(sessions.getBook("B").chapters).toEqual(["B chapter"]); + await vi.runAllTimersAsync(); + expect(order).toEqual(["cancel:A", "reject:A"]); + }); + + it("keeps explicit abort cancellation one-shot and ignores its late reply", async () => { + vi.useFakeTimers(); + const cancelled: string[] = []; + const controller = new AbortController(); + const boundary = new ExtractorRequestBoundary({ + timeoutMs: 45_000, + sendCancel: (requestId) => cancelled.push(requestId), + }); + const result = new Promise((resolve, reject) => { + boundary.add({ + requestId: "A", + resolve, + reject, + signal: controller.signal, + abortError: () => { + const error = new Error("cancelled"); + error.name = "AbortError"; + return error; + }, + timeoutError: () => new Error("timed out"), + }); + }); + const rejected = expect(result).rejects.toMatchObject({ name: "AbortError" }); + + controller.abort(); + controller.abort(); + + await rejected; + expect(cancelled).toEqual(["A"]); + expect(boundary.resolve("A", ["late chapter"])).toBe(false); + await vi.runAllTimersAsync(); + expect(cancelled).toEqual(["A"]); + }); +}); diff --git a/packages/app-expo/src/lib/rag/extractor-request-boundary.ts b/packages/app-expo/src/lib/rag/extractor-request-boundary.ts new file mode 100644 index 000000000..b54f3f3cf --- /dev/null +++ b/packages/app-expo/src/lib/rag/extractor-request-boundary.ts @@ -0,0 +1,108 @@ +interface ExtractorRequestBoundaryOptions { + timeoutMs: number; + sendCancel: (requestId: string) => void; + onCancelError?: (requestId: string, error: unknown) => void; +} + +interface AddExtractorRequest { + requestId: string; + resolve: (result: Result) => void; + reject: (error: Error) => void; + timeoutError: () => Error; + disposeError?: () => Error; + signal?: AbortSignal; + abortError?: () => Error; + context?: Context; +} + +interface PendingExtractorRequest extends AddExtractorRequest { + timeoutId: ReturnType; + abortHandler?: () => void; +} + +function defaultAbortError(): Error { + const error = new Error("Vectorization cancelled"); + error.name = "AbortError"; + return error; +} + +/** Owns RN pending requests and their cancellation notification to the reader. */ +export class ExtractorRequestBoundary { + private readonly requests = new Map>(); + + constructor(private readonly options: ExtractorRequestBoundaryOptions) {} + + add(request: AddExtractorRequest): void { + if (this.requests.has(request.requestId)) { + throw new Error(`Duplicate extractor request: ${request.requestId}`); + } + + const pending: PendingExtractorRequest = { + ...request, + timeoutId: setTimeout(() => { + this.cancel(request.requestId, request.timeoutError()); + }, this.options.timeoutMs), + }; + this.requests.set(request.requestId, pending); + + if (request.signal) { + pending.abortHandler = () => { + this.cancel(request.requestId, request.abortError?.() ?? defaultAbortError()); + }; + if (request.signal.aborted) pending.abortHandler(); + else request.signal.addEventListener("abort", pending.abortHandler, { once: true }); + } + } + + has(requestId: string): boolean { + return this.requests.has(requestId); + } + + getContext(requestId: string): Context | undefined { + return this.requests.get(requestId)?.context; + } + + resolve(requestId: string, result: Result): boolean { + const pending = this.take(requestId); + if (!pending) return false; + pending.resolve(result); + return true; + } + + reject(requestId: string, error: Error): boolean { + const pending = this.take(requestId); + if (!pending) return false; + pending.reject(error); + return true; + } + + cancel(requestId: string, error: Error): boolean { + const pending = this.take(requestId); + if (!pending) return false; + try { + this.options.sendCancel(requestId); + } catch (cancelError) { + this.options.onCancelError?.(requestId, cancelError); + } + pending.reject(error); + return true; + } + + rejectAll(): void { + for (const requestId of [...this.requests.keys()]) { + const pending = this.take(requestId); + if (pending) pending.reject(pending.disposeError?.() ?? new Error("Extractor disposed")); + } + } + + private take(requestId: string): PendingExtractorRequest | undefined { + const pending = this.requests.get(requestId); + if (!pending) return undefined; + this.requests.delete(requestId); + clearTimeout(pending.timeoutId); + if (pending.abortHandler) { + pending.signal?.removeEventListener("abort", pending.abortHandler); + } + return pending; + } +}

zaXa#xMW%Cwz9=5ZbHG-`Ltt^e+U&2*1@2^(qLTM@15!l1CIK>_e9HnS?M}0_)*dg~ zdX|hoqNB%#?U^L9SKaLSgLxbAkufS0n^L}my!(&k3pL%_jm`~dQS?th zCrY>WwJnwt6$fy>Q#qOYtEMZjK%Ext-<7EJKE6u4($$&EZ)pYMx^6M&3k1(~+MUwA zT(%?Gjz-IA#8|Tnf=}6qSq_)u%$Oa3^%L*TD_d}l_)^C67SHjqE7Zv{wA{n9*H5*v zX3Y)6Z3y^wJ*aQvh88}GRWQq>Ej_ti^YN*(F8tD!vMzw0B|l$HZp{|FI;pSeUppmbx|r^GpdNMD>Fv9Op4~gw{JFY|QkA@3KAiHcb9yT$ zLcTIn9LEFHZ%RAR_+MTGta0gz3Ji_|;}P4&J-R#{Hf<9Imf?wpk8tgJ0B?^O+87+iS>EHvY3big z0cYc$W|4CF8vqKK4Ar!$02Qg~@|$^o4Y6cT2BqDk-mYo5sqG?Y{g~1L;>^MNNDV9d z8w3A~Lf}aW!yXl8^2EkaIVnNqSBRwptqPdX6|tjws)e&Z$O&|ZIv!q>N%T?y?7(1= ztj)B?a3XwA96&HRM=NnOZ}QbdD#M997W5XizV59ikL<>*2S1VrP%-JwXQqU3_F~Lv zkNPJ6&UVgPlbmR+pWWy!f*GKdSUY|AR6~F>HT>>p-uIkXg$C;h(m^Qnd@ABr4?E6t zUmG)8QvjUE69_LpP?22xRx@&dR(+SJ6x#@&9fCZD8?MS*!cS0Hfj21HN|mDl2Zne$ zNoG_%9;pz%zEV0QN$^OXKiZCiZ$=TAF#D-QKeJe^i#e{Y2;@dD+7PtI$X+r(W* zr_D9R=8W{LBJy?F>qbg7alA7Hb}6xjZIQC+SS^HCWv_<_CD-440LQAcv%@0xwj|sq zGRI7xYXb$}EVjvulS>)>f$e?Y9mP*0ApJfxb~7ulBEA(!%h+}Abkx-U>r^59d2Q** z{j~Nk(CALnj7S*&TNg<0#M@p%ISrMWreZ?+1XY0GW-dc_sn@f=`nRXuUfjwVwB$%C zPT=F4dD9R&Q*LN;tz`-|yDBqSaGhu-62<;OE~v%_pFU0C=ifOA?Jp}TGkMhkeusY} zbp2bBb0n6pV!gg_$q8Ly%7N2#(09 zZD`H1&GXY*sj};3sCJ4iZ@Dmt%^0Qsr|*Qo-L#V!By zv}oM^aJi>x=N~|L;+?PaTe*S0U;4|PJHs#6(5oGjWawK-@B7OK#dc;=GB@O%+|9*& zK!_Qvrp-Fj7S9_=U8ftfE8zzFcUn|Aqpri68>NLEXT{i*_FrK*f2!wTbvV{U z8p4BsXVAS#5d>;8iTHo0mj8c>Chj9l%bS`cg_XKLyxZ2W2LAK_{r4Csk+Qn?0)Wq` zht_Q}-84x_gA;$7oVQ>qAvdpz_F=lq^U4F$ZBk6r44>wxOhRc>wO0uf(sCL;iGXTLq^<1ew|YJ-yh~4goI|ATctsCOhqiBA+=I!# zAV?4EF!fNLFnKvuy+8}K9yLQ1Q_l&IiIXB>nn?30TPFfIZf2x{(9O^Aey+`jlc%aW zk#eF7i^73A4~$Yb1_X$Fd@76=JE86`QNFz?Zzo9~vYY>`%HU^iy2y!pJ|TKvRfwrE z(-wPRYbjz}!!?!zaXUl8E12`_-3Hr~jm-HSjI3275Mv0Gas$W~=S?${?W?{`PS#9b ziUi1>11P1osG=$?UjEqM<1+H_oUFrS`pPB(&r7;#x32ZDDF!)5fHDS;X5AQj@&GuY zM!+0lRDvYk_7uPiHryk%eGhB9=UNXx(AB_`ND)%469-`Z*4SuGUT)#SQ+0y@YLx0d z6$?&zEOkC=oic9z$_6o1VMlr%dur$m6J^Y|xIXP3oajTiJa-8DR3@^ls6(!Cpo_y{ zT({T{VcBREs$6IOh6ok;Ev}dMjV+~Da4bb|>yHI;NuVJsW8Se4dD$DhfsdVDGBX-( z-IS=<#)V{m%gY1L>gf1gUue+H%betUgmKr%Wl|ooy)l16^fTa6R6qAe&123(uSTm# z{Rlb*IB=m= z8_=FaJE2bRfh-r)0o<<*)z*E+(yR}%iP6s)A7QiIqKMv5{6bdXu^nVgcWiG20XeYN zP8{LnlD{ow4BqNCJ*C+c>J=80Ai~E#sg5*hE9?GfqDBKSp3J#xpj$VZq5JAs@)M#| z1Mmn&nU9@V1}flq4Tbu;e>I&?KZX!D$uWLJ;*~#K_U|cpd-E>8?G@;zr{y>)7Q+EO z_zZ-nPIb~PvDXzSa_9ARP)G;rpBWzd1h-s|(bjW0B* z8w>vZ!Z=-=le6?RVE>{v&;LQu01d}sdX*j^+KdYm2!1=t^i0ylkK?vSQa)Q*^VlZYt^7|Sevvgs(LN@*1(<3fs;J=QKLOqO`kvzv$Vg?qD85954 z`C)}ZVZy7NCEfpK)c`be-y zJ!bV)cjb4Zwb!oIeGx03i5j|%60YxVr+qLV5X?y(p~VS)NQ-1vm<`25u*P9f1jRce>pL%1ks5MkiQPtD z(%6mF%*6_ZtnG%8^D5L26*VS}rerjND2;i!x3*0GO3FsTk6BG+sxD5S#2KETZN^#Z zfXW#r7_QnCy@c__K4k+W52}m|OCHWw@Z^{LDLsu2z@RhP^?nK3;eclBu*5KOUc!XEZ9>M*N{B zKwle|PSEg3P-iwBi=ACmdeQIk)xEF6T5n2uv!89oioRc(H7j#`_l7FMly*sB<21>cKyRuTZFcPB=kJebqBLauSp2+>Zkd7|y?yr4spBjXY zZ(~h_sAZj+r=ej$3AuEMUi)+d&FCT7S9}ytWV5mJE~!Byo|4D-St@cY8V6Slm{bzW zT)T-a6iNRRy-BXpP9DaD@)W3Nls2!4f8C8u2?b69D|AnJYNxOs zCJa=Beg)9tSBQ=PR3VS^vmesu_i*wg92!3(ZBYNJSEHCSSO5Pt_)r1z_BJtT`p0pC znki(Mt_BaanURR8cj7F#BxPo>bg2vsP#7G+{~l-m59#1P&+!wWon~f|kdgOVe$;B) z4#&ea-A#yd!@{y#E=Rkbm++d5DI*q`!7~+pepR>sR!;0SwBN>YC4;yXW(0l3!Ng~l zNVF&IHfU^1@G3J>RkpnT^M^NycGrlM05HO5H(h&d`*}O;73gA|bzSHl|4=6u2xiRw ziAn`zfZ@1<3XsYYnuqNiqExY9o1Szr_P!`6KomkosrJ-Ih=mlpYRO9#U!)^pKz?WI z%A}a&F{3I#%u_q|j1Ji7TlOw#B%#e^23xj`_~FMOz+-Wvm2;5WZr6J*0C_u9YHY#* zLyu3c7l#w{-D;jKz1MFDc#z34`?K&YIA0lG8qAnDj;%yMhpbI=Mdy37NnoX4CC#X) z>kPEZr3=_Y3SX^Gd86@^9!pJckFLh?IXSo_!B& zvj_5ej%$i2@^hKF>(7#w-Vk-VJ2hbaOtf%4%z7P%Nps8>U7I%4w*TeDw`mmZd-!F~*EoSpO6D!gj(hq|hAVHiC%ARG!!+Jg z9plT)9uR=84)w0Y627f}f>zogJjbcZA|urRq~~$2Bw!#oB`8i0VINm@UzFNzC#&&i z($vldViU1dDY8S8(oT+mEWN<{%_!z=Hk&3PiEpXD4Xji@4<>S9q`UUMOZHEz z79WbNWp7SZzIWxQ-f*Y-}VjXd4I@Jq5ec}X4=jhSM^%fTTLd{*> zExnr?^Wq&(RBN|7-yo`~VHg$qhb!6a)N^Hr(Kax}&i|bHLtslsd@krzS|Ou&K;Le*Z5@Q;bEs zC-6fr<3&RSjCJAlK*Fl0)h|Q1LxV^eB-u0mf~^5XGry|bmBcXcTIp@nRDp$0D?w>L zwrHQ?6R&kuj6Q4}*&u7Y5Y%eS5f=%Y^CYTqebp9A2rvPS)R~l}(Ec7GWesOCu$tLC zlc1!AVQp6~aTB21A=E8?#J!{&+|ONA`MyXPg2TtmP9y|}2d*P>X4 zcFZV!Np5mjjlg2!e~YHY&fD3deB5_iaR(!o_|6!E@UMcLz6J(4F!@jhh5Gb@ZBwGn z9|vrcuGK*r1#OvXKm|@8eV%WB%T;v=v+%$~&0g`k0^yg~AFXf$6`1@nldiljp?8F- z+E^xrpP4eFCUI_1Lwk4t-fOf76rf(BszpV#RZb=suj)uwl)_57-WTx;>TM`e@XIFA zdp=FOi(qa0rMSRvZGq2qPk&Q9Qv*X#wSGI7!rpge_cMoDsub;#11=t6WxS(RERshH z4VCj-%(~Dr9*#-&fMZh5bgfk0IGMsAba_a%Gt8XhnTiq%LATUZIk6q<=M@D#joZ0m z)o+dCX^&1n$s$m6b1cqpwtth*GEEc(2U3b~6RyQ0PIa5L_^6EntMxzYY{7xQb^7`D zi*ef}Y<8uR+GV!mcJJiH4xW)oMyKalfW@p2V>v!TJ zm?OS-N?Ko;2?M=R*Ho7$R&)Fpc(b1)s2T*2c34dXJ+;sc0&s;u_XcST|8FWDQ2Z70 z(s}#nIy1j(0VG8c(KoTyU+2fj!gRtXvxR=Cx@DEzhmXnlLA#ONm|4bkBiFG2$(Q8W z&m++5d+z!1M(iN_e*oMOrbW<22L@fJwl+d%tA0v{%joY{6&G^5^hp))Nio6qK^|;b zy~3#9`5;%zqosBI5eO?R{R25=_0)Gz$!Szb-^8W0`W|#OU+0lVL zOG!xk5H`65FV6{#Cu|wKe$6gM_{n3vSy|K*(uPuzF(dB!7SShzLinenAYiL!U%3z$ z`&;f$Cd12r=X1U_3_iWKVRBsY9H~8&EY?gD2CLOvDc)})E7+S$`FGzaFCuY1S(%8I z&Zsj+6Hzp}dv*gP#tpyfQP^{!Rp{}n&TD>5isG(lLbL)&3IxEjCVGC!3;MzJ3+$#{ z+5RgkV5_%nlI6*5XTuei#O_*m*!-ucymdJY}BOAw&s2R6-1$dCpg)xN+hBWah)Z3nhwP* zfAM!-Jh!XXGK6D96T*A0ZGcpPyAuqw{*d-DnEIIo4R?x`jp1yZkA8=GL*kO{kaZi9 zIm|DLFU86=PiBlnOhpkrpXZn=pQ1v4g?ptUoSdj{ar8L5{D9ReRL)|&^WCymYs;Oh zH4^@wc|KV-DoU+aU`c){0Nkv_HhQmt*;zyr24*$iZ8Nv`QhT$qLRwMJEiu-6SN347 z$}k8y$qlq%-wFCu&b{zsF_6tfu!B9UXJPHrE2jf(a}2lsgTi22_vrTMqI=#~oo&Ag ztDT)^{&~Y>6TPh!76FO2D!Q35a0K26(<>$IfEoO&vMRu9!9O*y6u+fwXg{MKN^ zQ|;e)f)^Axdy-9b#W>YB(p7#HI3+Bx*f}t?ZA9+szBtk$jibU2lmxGS&h1<%Dqo_k z6{-g?35Sta&~EETDkf|r$R^5GBE&M6laY0%DFm7|qa?Y3Mk7BKP{q9hKbKUmcFrIS zNi+|}B9tmyegyOZkB~1fsG^1QssS7l01}7FDqG@)?63N8vWjjJqM7F1P1a~p1}twx z;!CcE5x@%VJb4fs^hGs#RVN4_QJJquKp=PTGo804ZB1nCxEwWn9DMIcJz%?TgQOkCNZxp3 z;UQeVu*qNehXmOc;%>rwz8aL>>TWmXg_tZx4AQPA2KQC(Hb#IGKJlP)xUyLnV>t^%4nD3<0t|m;l;a zS<72NtOpuvXw0iai@Cp{cG0`(pf{N5ko=LV24^|;Q_kP2BHifAtmS8LU(84aZQ$BiRKu7i^i7ZXln;Z?cGNx0kvl;^_Mooc{`?2{Wq z#YzhAx%X;D_KXCleOvo_h0@kH=W_B>Sm_l+~%tmG7OhgCQF zDE$3QFMPDTU>9v}f*&Fjufl`ItYMhj``Xx+sKoxPIa=;%12Vtx5{}6p0t9Kd_G(pU zbCgf#`~+lS`UwFeHuFg!p0JLRGf_y949agSjS5@7OuHs5cpUs^xb#|80_|;(RJ_c= z21oA4nR{TnJ}?gyOnglErvA)iO@iJId|hy;kNvi}ZyQW0Tb{>ZwjeoR#Z*Z(5;fS2 zZ;<6)Cc+~&e1ucyNgKPdxc>O=&~L+DT~OLR703s-pO+ylXsn|5PkGz7z`*lnQ}8i2 z3&iek!ft)g#1UgTjhN0BA&}ucvt4s8-TJnnC7-UwFJV8@<%_cs;|6NsdDQYsIC*o-CqUe%)O-5r`<^l zQQgpDJ;se&y?)nnWd|UwRxP@y3I}3&H+cvzVAtzDhjSf@vv`F8=aWv5)&XU-gE*e~ ztIc>rZZ*5OuYShoJL?q1>oam&i5x|STQ(at&A2^V zc6m+(aCiA5ywu_PxtM^(pkWB`GC=ksPra=8^`Ke(QHs?m{lD(Q%yL2?$ol3@0du1N z@#JW?LS?j2;QINvEpBc+yu{gli|(Ra_KyyM#1Lk^_relbz9$$o>!SXuGb-N^kJ)v6 zKf!6t{@2jP@Cdb;8*zKIhX=Qry}Jk{76e1xEwffK`v*W1#F#eZuz$F`l>Qx$nG`Kb zs>{&tQ^efPYu~V3;1oFF2U-uK7F7T`BH0UezN83AIi$*yq*{lPhMZsyG=bq6W_*&~ zWF|%V8bQj!VIx6>yv|7U14by*to)b-iLl^_^!}`Vo1Jva)UlQ)QGQ8Gxw47`RZ(@$ z=}<($sjm7CxaPm(EO=SI9pOd{(bM`~7)UKZA*s5l;HFi*p%)!w3aQ%aY@?sOh(d<& zlh5nY`2YtgORMWmp5ni4C47#_re^iz8wC@@7;PzK8vDtD(Aop5?)k4h#qZ8Xek!yD zzCL5m{{9ajOlJ0$i|2qwRfBK$nK4nPaz8*tb=Khcbdtl4ktm-u?CH*`%CiakkbeMc zlN=`%*^nK1>>)f7FvpwndqjI`eTxRm^lr{O2Z#7BMiuX z*KXE*eU&9IP1YX^BiZ(qMqi%Fyh|Sn#v3lL{N4eqbbP75n{IW^D|pYy9^ZRc>Oe<#aip&m@zL%U?)iIA-KjJ?cAM9^U?}GE z`~U@_$o(=YwE4B&*)tERuF=BFO4rHblYQ<4sIcL3%d_GnlHGL>X(@@`($nY9Dp?QD z=wc<#sd@Kr+Y}xSlN0W8>=M&SS1>kxj>VbsJhHUMl8eS?Gyctd@{w!s6A3wCtPJ2%EX@q; zmNO|Vu%P|22J13v$b+!=xAB|q6i14I_0d}VAL#;~$4+xAE)H^jR)h8OfK%9L_kKXH zfq+_>4BSqlw%+~bLsP_fHAJ@DDFG%C=Yi>RKJBODD1^|2>&`oP?p@;Bs*5&7pO z>RejcwraJ}(qG|Cdr*ZrWoB7{LccXz?RDZX8t z{PX#@)~TK5JpGtPVTvVi88}}GV?7(c>}?nmN=`UZbFJvW+jL2h;)cIxEU?(uO?L`x zwwuVk)lRZP?r121@!Sz^493w$aXCL7l=-Np_=iXJvmQ6c$QYNQ80~+8zZ~mkr42gKNgm7gu;fe{HHEd8Q~9Ym8PAt&L2L&P8>MMGl?=Le}|W z68(mAWRSCb{Q34>wqao|^(PM&{5VXPHHt&`=_u4mgyBQ=MxZ1|Nl+ZL;556fXiaSB!Dz(zLlej|A(9G!~z>xQeA7{eu2hE2%Kz3$Alyck|YJpZ>p9j@mv-m zu?-)Owl2JqnQQr|tNcxqZ6N9kvN`11EleW;HC^wZHc2?Q80ObH00XH%#np=Y?=(;-S!+E*ib=3m9pFd_ItBt`i33x(l62uV)tqQ$GyQ7UDrh@GRofb$|F zT~9EzfrRlJS*wk++s{a^ty!WJ&i7j>}02@a>G9Yl`YF zo?*l_@nFX>N|LSd#kz&*cy-W1Z>43{6awH?ND{nRU1jb4R7HAy3xlMMHdXO=>;Pv1)2U;A@e-zl%1;{=N8&W@HMUA*T>=>t2;AY5s8VkFTm zJfEip@3ZkFnZzI7%^ELytaM}UEA|MC*#%oa=jCi<7M|cm$?|;WHIdfYp*-;*OBqKT ztPnem>7iLH4P%u(N9U)-L}_@=+{I~~Gp6hz2Ri>4q@JxTEOayu{(tuc`Yw?Rc`(;bR3dD(mNQ0ReAP}*Zi^> zuA^`31%C(oFHnm{JzIVc%8VCA{_Gt$pR+sN^7kbq*J}_;==qIV73wu$-QnT2_wn>% zJ+9t)XqPlM3YN6|{ubg2Z+szilAKfs3T+PLNP+qMAY$e9rk$!BT1R*r^C=OyY9Kh4 zATId>oK?2k->tLdgeRFLp^Xnz0?Lx|_w)3rkavkX+9p0G%JODoR*A07Hh(C2x0r<= zU$E!dJO-__8eZXF)WK>NqsIwie$<)F5v57^zuwM3DxFui_P>4J`g8baqQ>Ve@b0zz zW#!J&H1&ZbQ<>X*Jo#EzmQpytWToeuYMD`E$`bL9*Il4`Eo;&XzSK7q4nZ{#0;!j+u83BMqXCKFAC#R)`+aWCuS#F_gJ`RVWRf6lIa~PU7H!}E&aUQ zBp+wK3L8_ud49yXhy1qi(oCE=1}c7GV^D>ZEv~G-{`#UnBUW`FMPn`Oe%d5zsGsx4 zWRPK^ZIyPj!zsHhnJAszU!$nxgl~y_1Y4|ybIJF|K|wR8R3zsZ`qC<{;Fr)V1+Cmv z;k*9)*(VH8r%tsexCz{H&2AFlPa2ZSM^Zu~P_JGxLsZsfisg@vQtV^RLiIL@rR?+S zg)!N&P-}WHu$`LMG}BanK##z6lXj`}4*AuoILB1QD1h>OmmxC6a zQzBA*4I$)1>LH|;~JR6REcHUXVRCaN+iD1)kQK4hXf`u=yeFY2b!I+WAd;PCT>OCwmyV&0qp&0Z8l)!;oVu)J%eEt+ zU8h;%;j`rQ`Eo@2D`*RJYxF4Qw)ee`!cn4zW%Rm>wdVr0&H>D*Qj1{4i%d?S4ve=b zmV(j8DOi$Kq26=dHH;4g(*slg04lu`q<9jFoA{|{DG$ifidJw<5}&+se4l`Y=ZI{h z+iZ<={w-8FV{)C-p;CTD^Nmhr-+DduPJO9v62}gv--OsUl`L5yaNpv;dbJ%^wQj2H zDTd}&qnGI2D&vkrg*pf?*0$rEN{E!%zs>4n4!L9iR4zM9*d(c1BtI2CZ4j)IlU>ub zGGI#{iE57FsOT0P6$)2{3Q3?vq5aeqPmHY3F}5q z9y%K{Woo$^c|8T87=F5e*+bv<2!96Z?Xn-uT0()qzM1Lcaj6=OBNm|3v1BhpO<6wO zab4&Laji=L?-rc&2an-YWGgVL{ zBd*W^XHi~pw7wMljWR8UK0$7EaWnOd<5unm{GFoC7A;1U%Sm*kh29HV)Lf9C>|C3p z$N4b3O_v`;wgf3$6k2bQ^CcWmi89W`+fs*%3Tv?@Gvwkihs+ZjT6g)bWRpO$ksA_| zm&yJNoif5BJN^7&D%4GOw5z33`S4P5@74AL_R?dq*eowHz2I9E_OMdbb!X7*mzp8~ z+@aj5dEieUBii>-K?kX&D4}=}yITY6cQ42gLz+9do#7ypTSZ|)%49A-m-?@`sW-Pu zfbj~}AZ)P2F(K3df&_KLxKARRb4BC1M1v9eRA+GgQ-{gn!x|L7%(sJrL;E9#1TVhZb-lV@ z_z$|3w#hcAbNw`_PiLrLKiOSijYSWkz)H{mcO4o6j?|ucc_C(QnD?2-St#Q*aIat!pGKDs)4k~q;WPdeqfP*I_a ze&O(6otkdRCnyrtE|>z1_&pWk927=2o~^qn+9eG}ddg*96nLFMB?BC z%70LooFJ?z607)rr3zfsFI^J;wjilz&k-#r$XKJB_HKQhUx%kY=Zj$e7M_atlz#<) zRCS6-E1{%}UEL)iqTf zVw7M@&gbzNlefCBJB#Bs@d(hx0rdE1HN$)Rr2?6rA@uPlR7U(0e>xf>oyJUTNs8#T z)%!X8uUxERQ^_P=bULz^0vLEU(ya7DvC#dhX0sA+>UUOcaN+X-PfX3n&|8wRXOPL8 zmR#J~v76Qg^kz!)%qfFZv#D)l*PH&$5CwBgJ)Mr`(_gTE>Y;qtVOW6H+dN;KHZ`%w z(+jX_y)7)*QTw*hZpMbZ&2ze=3ECNcov^#g8?uv_LU7bur4muq8G2>l;=lNaSf_zy z{40DggLvV(>w^dMNwc7Rgu?2rdDuHR(|oILjC=nZKK*Z(B&1;)C6xjGW-jf?&l)DL zUR~6D)YU8zXYW1~)5kkriB}yaqw05gqs8QwHg{IHzU0qCvtlmKU3w`hL-Tn>v8DA1 zrRC3oP_wez>>K?Mn_##1^(+`LTmA_m>VoR{(c z7FXwQm0*f}BaIB&VaPda6X1FL+Yu@?31hppRRrpbW~D*;&oxDJQWpPXe7sDRa2ecI z>uhmBaHT~ZbheMZ)mNDlYZQbDWG)C!e!{K*vBy|~^EI>TfSi~AR*8>7rliL)9&r)-7oVTiO9 z`K>Yo=b?-PfpzlwQnJc%LhrBhKgD00GZ0)28br%~!UE%@Ur*|$9f_9Ov>{sf1Al$= zl+;trQdvXQtZH+Qu`mZ(?Z+^gcXhwJ!)eX!ecP4mK~sGc2alDgQ)|CaCr48lovvY)z{UNH zlNpRu$zlk7!|~;2{%Zpya?r??z5ub_j;!$Dq0Sm<4jctWFbuL_Llf+%feR*mL7vRdhZh0@H;6o{olgGGJVIqAyfb&U_4)XqUE zk5EHcV4%#sl<}NGYO;osYcDWC;dfl7B*|o^n)RYMefYWnWV8GQSQm?`DPiF45ppZ; zODASuYL4euCd7Hv2qcyd1n9Q)1`Fvv9;*yd<%BLkMqp5x5{f8aeFv8k+ zf2JjaDh9EW<-$V?8hKv=o;X zw?J`?M#k6fK7 z9eR6pDQ{!}@H+>u-ckCN5s-#- zh0_fn5oI`BC*<<2^fVpR?URY;th8e2L=3GiKYD=0%wjfbMx*PJcsuc#)MS9j#;rBu zD1Rfszv}|n-voNYl)Lj(<22t}*uRvt2ioL~yo(CdRKiqid#joCDFI~INp+3SPl)a8 zDK);~o$(WBrTmNTa)A%;MT-1OtJCHB%J&CGgfEvj#8NJ`UXMM!DJdr@PL2duvpWfM zQ+?sG_-UMy_=hohwVZ_cmxVLMzGVrdCElze-9n7k+)pt~8ooPEi3+87SqeLX&Poicu{f^-Vu()-1H^Z-_+?LP2ozh-aXUDyuyyBEevbdInB zJ$lI%=a6ku21o9F6uuLyMDdB?sO{|jqG*L5sOT#(1V&)&bM5(llM-S*c7#8xw?dv= zne2)dl;+B7Lw8Bn`_uKnILV_Pfj|OM={CbT>@U!tIwKDf!sd>EGgj}}iEnPu$6L;# zRs96-%3tF~#Y3%VjP0Vg_++={1e-8>^uvgT+k_s}G29fTsJ8I=a>bpUI$Bf^T#f1< z6W*6g0@wL`syozt{s-XXOu%DO@@FV9EC_k4P9l+Ne_~ZV!T>G>3O3&2=i)^&__m*l z>-TbZ!M}bZAkt&*igQ!XkqkzCYzYSvRF*HF?))!@F}J+uVACva-1_A^*!nrWAJ=&A zO6#IOTNMyx<+1O-C`jjF7TRCxS54PKZf3L}@RDCpSr4!J1C1^12J)m6L~zykk1kl* zhDfJ9KxF!UNE#k`W54Q(fbFuMN}}bj4xCh%?!&tG9GK1`B>G8AxyWu$4L#7mfcuAS zsgph|QrAKt?^RBb;OT)@KR3p%ik?6VC(>T#$#IO`XKkpZG7=AKof=?D#NlrqT?$L?VN zv^(lq9=+(~8b+pE9So44!*JMwP0&|&M#Z9m(r%@ETZ+^?er`kMN*fk^Aq!dB5psuG z5l?3svQX~@G5ooQW z=~5|@pd;Rk6mul{4U)MP&>wNU%4vCLi!9g6btMgeW-P`i$`ErxBRD48g?~z)rC7We7#~meUej)N z>_hF)d)4@}wj+rmYj%a<8UhXO9n5h=XD7ot_i9ieRWwjV76HVTVKxYkPG_a=$K~C* z#s4}}CV5#^=i=n){wPMZidAw#D%09dDS))? za0Z(#W{abVMrG;lJVQzpj3R_7w|_KfN^OoA9DIf%Cl<%owFU9B5K!Xi-h*h0F8A!6 zivEcE-b8{u9g4=sH*|QY9KGZ+0jwv9ZJE3t_nvaB`KAI_If47#pOY6@a|gR_cn9#q zaSS-s9vIX5(JGMLqaJVfU^kYLOJ053(ECg!W{^tc*9iIgbnQ43P2)TL=_-~j=#y`u6t1mchA zT@TW#N~jfGwV3tPljmy0&il*BRH(?~0RUz1k-{@lTRx4emfXX!x8b(IF?B{CDZJJ2 z`8w_C0BCH1hoJ3+Lb_;8Kl1}G>S@k~LFk?g0x2Fz;)6N@$u;nzP}AH9SvaYb9*iUP zVT9m7|DDJi>$fQ$Nr6nPfcG?uB&z1UdYVW^*Q9TynP;&{i$uTGei{8!sF`BN`+eps zakwD9sD_4xZrGap;tE5@^R>PH-=drWlfJ*r8MEt#e!Irx2Zw*$U9zF7bKl0=*yBfk zL=QC6mlP&k{ylGW_ZL+rPgXAXA2_BLh?zz628RG%7>lCx+8#q1q_6`6hlUPUUl?nq z{C5Z)u0aq$*|DB;$p$D{;kXq*b56bEPli`Puyrukrd^2$V>2ZNRa3O#{R=H=rxe5n|^NgSH zlaSDhO)}E;Ht+dN@fGy&S%lkyd5n}$e6+XpFb-4w+>k_DcK5;l9n!{rb;UP~cLiAv zTmj3$=6<(ghu2WP4-UrB`GUTTvG~u50%T#BdRiwVZ{)$4L^ZwPG5jWw_=1QL;B%Q6 zkjtPthJ7D$F(w)8oDhA3+|+iqFBcQCpcW+QbonVKZr&#QF`2d@6?;29nFOrT#3D8wqcYZ0ulP_=i{UlZJQ?Wx8$Y8_!X$bK}MZ& zr0#90L!+{JW$$u*EDhB5b}?n~^};xXi^dtihm_!}{WvF!4=twCwuZdo`|Z7tIVrkR zzZxz0zUu8>QgnI7l)#N(HZa(e;8$PkF*^pG9LzH&h^zHt@uEeFBA>V{uff+X9Scn?f${JbjO)ku!iYU8k6!V!e*lj{jkzC6zcecdoC7ZD@G$w|ERnp0?h;Hn~%r7vGP4oEu@`yRdK==@|GS5>35dt&=+4qX0K6}lP=-hvlF{)$NhF$8b7jN^` zB0dHX+6m7Q9F-yOlJ!6hEbuC-{Pyfw?w(P5dYUCn^ws`W?+B{lz9?Guhf!;=7zEzM zsvB(l{_XXBj8>H!d;?1bq8!W>+PQhggIc!Qd{@Aht7ty4dg-M?4x#PEpH@0D7sq28)cQGu!p4XxP7pW%0j z*U%%-RL5XSSVFY^;rT%l8TZ^rgoTCgYWd7sM@w4%4RT4SF0^kg4CF)M_kq_M7r%CndDZ^kQx z_5gg;3xTB8xU#yavalL8uq<|RdbmT!g~FWvDf(l~kgXBG(IwIvEp?l*2m*lapP$94MHF+3kQZL9Mv< zFfJU$yx(;q7C#@Z42R-w5vvPRM83F6)v~p7#55Jj5=jH;{{gh3r;AuD>JfN7WBm$k z>_@v7I5~AQ^gSAA_kNtK_&0k;G|KXBBUjt*TiWNvakD%AkFjzbhA)2w7~`+i$E06=yNbcrA(t}b zn_3G!$AS9>0sHr=&M339k(*B}cWBbJSGw!js!5r8&1@l>F;wt4#i(CXNv2AByc2a{ z>P+Bsxy2K?ymF>ZJ&kX3k&Cat$$ij>_k4)6V!Rso9`ub* z^Qi}=%@xzMFu?_xZAl3pEFn#S?GiAZ9v~}o8FL;ka->l& zr>)AYJRi#`vd>U-L$Qx!>|bYVCA_E}{B2B7q+H_rFf6Sqi@qh`2YM1a{0)#21cH;p z1JAXEhlF{!F?hFvpg6Y1(Q7pe7ljk`LAzk~OD)znH%NQYh6PVH5BV6IJfEFWhIvba zqMW)j%o~+&lhU7Z=>7o)w4rvgEj8>vX_O6n9-fF!Tpb>e#j-Hq9hu_R+cEray#3r3 z4^|K3To1fmpZd!x2OU>i{%hwgD=$MIP#2aGAxiU^4PFBHnXd24G@N0p21jTC;Bejl z5P$#weXv6pjF=}I;pMyon`6#B_WVW|d(95G5M7YKr~S)HBkTa5*y)Dizv&|`($Yq$ zNZHE07W1&;*NuylT~R~^%Cm}(BMA2r2%avkkFLvfn$t0w~bSA0c9+rNO) zacWbv)_Pf%^`4S*OFI;*N-rkR9Soxg&&?5juKPZas)h3nr+#b4^Sy5|k zF5y%q-}J7GkARe4t&7zY3v?FmNPgddJ25Dr6}j?IsWa64iNNy7bTKBqLOw04+wsWX zeOwNfs~+Q`#LQ)%mtnPLJH{E^KE|_5P)iaE0SEAt_y4xdvr4gJG2Mkc#3jz~9>x~h z>Qj0y6~>8Vj|%{#ObqMNaQn5#YwQYQ3dOcBb9u_HNSX<+B)h?|#Gk626i-B_&ZRhI zQAVhHZnAcjdv#5bFQ&=sDCQFqhQtk@2u?T*7NZw(7eeKeALCYs1SAg;J}^2~c%v!` z&l#fyIK;ta-lR*N7c0}aG|`SRWxFFxpTmvV@8F{G zRg+}dHesoLmI=zjjt4QXwqH@1Vfv<=f!3>kr(UIinB}Ap+sjhzB}L3TfU?W^l2qkm zh)+~=Nb_laU##dzmJ@UDS2QLD!ON=VwsRywoUqtoOoi`Trv_#(cbbO!kh)anr^DDa z)l)-~ly~!1HUi|SNbDq}BL`M;f>n-+a<{Z0R*z*I^7e)K0=i%o0_DV4+S9`vP%Od znA0^}2g{1CRKE)2LCSAn8zzqLhi)sN?s#@e-R9)S$|>7LqQH)Ay~%_<_Fm~V#=c!D zPJ2w{q$teH<3o*HVZ+{#sGxBCk_)c7+EL&do@80#aMbnjq3#%a;h}ran}Rr$&&@~2 zD5eoH$v}Vh+EF{bX-8o*=0*&R_m#DLDW3B>kF3bmXWKF0AWenRZ3iWy%>1LD7JLnj zgTYSk24MA0ihO;t_H`Edat2w)D=tORW|u2g%Zm@r9Aj!&$GA1}sq#A7iL3%Y$>vIl zL4&9yxg=OF^R;F(Zy{kvAfEWdVQ$c*Z{S|8-ZE9?%BhQ(c^z9wjAq;y;@bDLe{;GE zkSFwp!j&V{T6wVZmn8$x=#~j0)-({rT@e?m@bp$fN6E2EiCm>gnDVJ=&bOJE|L_hn zyuX2FhHzgqm!W*1S)cQ*c_yh*~sBVE1QO9N~qDl;aCU-x~)N>{_cBx|qBe2>rwx4TmV z9C`7^PCK94>w0;Oz%EKE9DS%OfiLt0m#l1uqB!Woy>D2(gViXts0Zv-VGhSB{Q<9PabthB0H&m^5&jz| znfkw2bN^$}C0M5&Slqg~g5NOH3``RZc_Ku%$H^ZLUl=pKW`{pK+gSU*huksg8fNmL zMeq&)vonwNEOf7^v?H)7QT9Oa3+7OupDb#eBL;%t&f{$69YV4IHe29Yo1&Q>R#N5M zNT^r0V2yd;+_FQ;{AAtQEWBN6hGoiQz?%?E%NTh1!YD8v|K`Mm$9XTKV!a8;S?E~A zlvr=Rsd&EFXLYf0j(M&Ik7XCVzGt-1?@3La-Af3>0*FW&Ej|zcS6)zo zAjZOOou>ANYZgUcXCQ9fioEMGGGEb$k$JUl2OiHI)9mitt1hV+sSQ{Ax}PpPHEi29 zZr}V>HA>6BQ}bWFnp>?{f1uQ%8D+;jzvg^!;|w}rj2Z|Y6i!YC3Jv2pJ9?XDg!X{k z8=N_!UKo6%^jjE@HEok`X|v9b_GGDb(VR}lCvKIk!*H>}RF2c%ArR~bug-)VvA>C8 z2S`cny$NrS*{YAIow7BhA68z+qB9bD|7`6eJINZh5sypOro5@yFCHtZNN?DQF!Ui_ zOM?G{z>sTyZ*JDOlBwy+sykPu6!hdXr^*F(i$^}sO{La?MuLEUwE^I7Vq7n{YP!zO z-kx(8XW(f?7L$o1)Yc!LcFl5f2G)EdF_)a;84)mdU1C*j@|rnO0^EA8y< zSMPqo{DnCxei%w`ToX>R9mgo({gP=9$U+|B_A@7Kye(H2CyVvv$08^QRtdxHeh6kf zu&GO63X(oHsPATLhieBe^xEtEF7wTMq19VE6S-5 z+)gfcnXYF>?gwsJhcBYk%L5JQE;=Xh=r(njRjy~QQ38J)oMa)qa*|TB5>v+ac*i+J z9W;j&BfBd8mt(&2^E)K6cVnE_FbTevY6|Qy6=_E={INftMq?!?Ff$n%fdYyCKZcE! zsZkab@NKH11rw~{j`8zc2rGR5)0WXVR@jsn;h>@TwoYs6osnn+zv)UF-!^)jKffQl z%cPn-6{zJ`P^_9LIn;c;u-K$rTzZ(@1>Tsx|9wl12QhSIfVA}QQMJK(q$l2R1p8&x z8p;e8O=e~y^|-KMpE_BD)n!VNERyd8K}9UCjMkOXBD?1OfWeN|U!iE1EZVd_FYpoj zlv(GA3=qGNJW_(_vQ<}Td&sMVnBu-ZKk2yRo_BwTNit_1nynGbAm^$ zl%=m6<^jHy*AFlhK?dIq5r*-(9q9xDHPJ)V5CQ_km5k$q=W)(+nj`%y!SZ)$7@qrF zQb`SIJ8`{3iK`Lyu8lJ_3CIYDhbrAUhbNxI?bdrSk+19gh2?Pcx=clV`KeGt_Y)jD zm*p_{%$Qemp%}Fob4~1pqSCWl(Sd4Jy?#Vy*H6TYyh(VZ-yr4gp{JDYzEa6NwRqQ$ zZjdXdv|PL+X~Ef&{@bQml5wni!xi2ga;W?1m4z5}saNcP+7D${`zHE>C@f}KFp@h# z!7JM{{!q_dm}CkHpu*+|Zj}8p>H>G~!_^;s_vMWv#Z$1OZASE{^H+J?W_z@&S_qo^ zqh@(k#ycf;-6S-hthpu6=)uX#WB?!_*T)E=O-v_Z9Q&J*#s9_4rG6`6kz%bts|f>^ zAQ{-MwOj@w$oj=tI`zzGYm4};$o})g&v8_YPLto?`K)f+YzH5}0ffXJeD*;tc7J%< zcxWfH)g$HwY1}ND_1P_g2`OISb#6qyl|vPieH7-=q6w_RX3vbZOpel~27i9k*~K@M z7AK-zxUkbA%!gZ94%I+j*p&ok<3Z*|p8)awLiBO19_~}CEQlRwZNV7z=F2FHSD48v zBrdaXmHFUwF%x?OGYJFYq!hK;hUsl&E-EXSCGBlvzy6N*a~$^3bfnKmjGVe+?4X?O z*}bO4hWv_{EE8m;T;5fIsF>vXN-?Hxo{+j05Sp)C)=ijmK`9dUELr`OEQ`vKwZ7p5 z8=Dwb7W-n%wCFVc;8E3b+#iPFg9)z6ndt1}WVV&Py^<~$7l+0!Mo9zX1_)R~m->u4 zwpbKbkjXX@&70Uspc+B|O;+3|GljHoru4ofE?o(~!3v3klnPbvC=*qB+grJTRN z4O)aZ#Lo0SY9`@!a!^h`6eR%vNGKDRlXTV%P3W(ag3XU1!6=LuhGu1JVygt5pT~)D z^rtSC!Bj8J*VsF+br4AqyLEy`3vIoF_gEQ~g zu-ULC_>F}X;O)Tv7)jNjyKf}!k;*ctin%pRB9ZK(UU`4a%sP?tu8NKLduQ}-7=yq( z#M?kYvx?fQkkna8I9XcfU2{?(UO+DQ+oycg#xc8a>r3C}+V-&Ju<@PvE@8NI# z&CGrNtKjDK73O6n`0n(D@jnMXj0sMb>@w3@+70RrObk5M^6M4t`qgyYi8scU4rVB} z-v9}fD`v2Z0;!z_E6TYGa~b*#Qbax<{Q61i5~bm9Z1xYJ(@SFUL2nT$d4klEMb6fU zyL6D+C1c`ObOG2m$?ic*;V~;|zAg?!jPP*G8kQQ{7J#yuA=zV;B`Rc9+TL9w=31J? ze$X2@3sBy2k|zxr&N2zHxPzt{^v2ZGh;Hv876~2UQ+ydA+a-mYiy=0{6Mz9(}%4 z%YfS2p;#6!Th^zQ9|Q&I-|GTMKCUDPx7Q0K)ANk{n(9Xo>2_HzZ3x8Az>1{wb3dJE zx0*CiwjGU2m=ZxJ+`*QopfsB(3swZ~Pdt5_&hXV`O8)}XVkTHDc%I5~y5=$wkHunZ?{4AvL{F=%v;-y>MUB5P8%!+@7LAGb*_H;g`CO zGP|<319Ii){Y{=CZniqv+eV`Bbx_$a?z!n7oBaM(wM)Tjvli%_C;6{lAotK`8#QLK zAplF51*t-X0q!*yS*B@OPp})O^)FrEx6H%ldV(mNRwvmt%M%=@Cx*e$UibOST893T zf>Ra1xKJ4>JCHO)cQXzADmdfD#+jmwCgCzD%KLieHKrT;fhbpy4zV~V@73fJs!-c; z@`YsQpRSKQoboATJJ-+`&?6%qkrl?9zvVAm-l$u<%_%mpmh8{E7%CV}FH_oL+$#rB zJ%q)PWVLMB_>{YtnO8`}QF{=BI2MUUQ~16qyKAm~+Ou^>Y_s=l zUta8SppepC77c6$dsL8$^D$5Arz0W*LM86EpfrUM!B%p6cR|L?_;U*v0wF`!s?*I{ zYUn1O%-mKqnfJS<`lHe+Rzgca8Bw-{>6~N($hdDQA;{`mNk&t*)!!tW z5sL=}%w&TnI0oFN&kgl(OK{ILzU~(NSySl{Sa(6wYcJyAoEvZ_^5Ucz7it=OnFWWf ztAD+DN+8$=Lfqa&B~GZXR2OUulK01@950ZT*)k@i>mj9VqJ~9#0KBv2hFenIZNW7( zvELV@`18e&2Es^gxh4j4TsQWsyq9Bs28>R=L!6vDmbdZ$t?cENh-V76nfYFv^UObo zFkj!+RSwwf_(iK8b6tSDQiadlRx#566d#z6ENnFR8Hn-*5&#>{Nx3G;Sg4=Q`_R12 z)azJ2>1+KgZ%2&z8Jjrj`x-p4@zbt^P!IHr^tl$U;+mlVF!$w+?TV#KTR)wAUBxNe z*uXv4$Lfz47{b|-7ED|Z4I10@%%Bmr7?tj*w4qhIJK(%ooDP?miXo#@U4RMv)=~^j za7>vB5;(;lZGgn1^)c1BbeB8HlX%1%=e5NNdtxROAL}~Ho^pJ%!kFhTkcvGkQ9S{a z*q<<+vzy0a`0wT=Kp(XP^=?jo2bM@yeR{r$h1xBDU0#>k>!NU4b%k?G_&{)m1cyvx zTa;JD4Kr^##n@>zx#J7m?@H7;N5io`LZPMjq|`I}z5K4cbzPmQyro7n9Zv`+#2@(_@RBn^B;hGJI~%V7@DBIg$vaeVto+dcH{qG zM$%8CVtzHFu4i{g6*svkEQ2Xy*58vJO8^0J`fz+nUFR4OX?vH+*XdjjHC*uKTu1A^ zH)JOw)XtWljSsl&jj`mSYKAY)wnv=IVv=b%NlIWTH>nzdADVXdB|37Nv2RQ{{yfM3WhPD;6r(GD$Z1s&_`PKR8=w?gOI2DZ72v%md1FwbatyYO_a^EjcD$^sbrNy~6qgvp0)v&Q>+VFAm{s4B$vM-V`8bpUcFE1a;`K!6O+8(u#l>yb{3MhNS-d7 zmDfzHjJGl@+N@4|y~$8#h@Pf}6JoLRL2N;-2{6jV(#<*@PqxtL_@=a%%3JfYPwh8&DN_c)&v`^c`y`J-4 zdDCESu2ISIywl~|)r_Iv@6i)e%%f`D7l^S@UR%+X2_N$h#g&CxU4UB;V=I*pH45g< z+deZhd`=$P(;_xSoUf9|KXe~Sf<>9$XYaxP!KI1EM2ZQ?NVZbz2UK-iTZo$K-uroVn7gE!$y&bdC`We1uY~HYg~-#sgj`>KOQp-$&?3NB1C}!D^V7I3`iox<~dM)faZ$Q|70Z#YpQA z!A|Ho8g&!GZ0tRaUQr+3o(Im(;M`7Ig;N+^i0k_+m=`yhO|J0$TgS$X`sx#8f5Bwf z;aCSa=#r|rq3LIX%wSiWpMgm=zJhzq$-4w4ms36bPctDSXRJ75fJH8xxE!Dl= znuf?<7^UmKrbn?oX4F2cJ3Fb*{qQo5E%r8LL#&#d#Kbd`5xSU;i_z};uyd4>%6w-4 zm`!Bpmt};IeOjEW`UFx;!uk;}W=0MT(*@RUf=V-d(I| z*#o5dwM`K}bExvJlSx^1y6}e$TQph-^78v-QIYW)XmJ?d>lFL@hMfw-TF?DMy%X=! zjX0;9O5d3jJ`!$BsFl3=`-IlZI;zzMD)ha#aN1~;24e66JZ?>3oUL>{i$+4)M6pk5 zV~2xM>fJpuD(u$zvbaMlK39f^9r6YXSyO#FBcYG|_1uz4c>m5H)e}g-hvb1jM$g^L z(#fh!7FK|fvx}@;T2w}Ymf?=&%D0vCmJi6=C4?@_T*j8=+b0fA`x{a0!1p;B+%oWi z>|8x@J}{B=sIxW*)7GtEiI^T8a@)tv-J;@A2gVTJ$k4e=6j}=Y%U~@hRM*?<(+v{EnYAUJaM}S&*f_C7I&I3OcpZwo^X7+xV7wJ@y`!E3c?p zSac*}vxaxPYuN8Z?e{u2WZm>S)igM9P8_i?twve?F!P>j^?itluF&EXj-X0^s4zufjhS ze_ezy1K&_z=S&0e>i!pZ^rt?-rM_{DtBGgirYwPrn2s%*q6_I~ufErZ{~Veb`xM(K zC1emcxS8s+`f6JNM?pedanf$UiOkNE=AH#FO39%HXI@|dAp@Z!hNlUpgf?Q_qJ3LA z>c}4)5`tLhTbLVpG`$%xD=!~VH8cHNluXa{+jN$;F|pTxsZ2%A{=G8Z9unLB?rDSq zGT@9fSyrciPl%Pii9QHr>|FaaKZOScspktuj)jPdb;n{+0wJkpibf(pWF)C@0#`~& z5lyP4ks(g174d5L4?}E!MJ92<2rn_7|NC)8ov>wjX=cJN#Fcu2Hx7E$1vKtEh6mB6@``edNU7XP6qFwwwW*1Ge+B7-WL|dRw;uCdQPKe6WMlXK#kBzCVPz;~ zd@^`&Rdo1zFR_$4-Z}%={A0Wu*V!oEv6li+{SRQB+xWXSi2m%1M@if`m^^KJCX3#z zsUb8$K1}0RoS9R~3;l&6{_Zv0d5je!_p;boE%(agoTC4MdV_vBpw~m#B#}FVkvt}#UL(tEOeR`UWg-IY^-ac=N6c1a*`^l~ZWL_TzMQYqN>O>P?jd@5i$C)8<9zIMGc?j*0Ke+1Rq7K9R65mJiAII>-3?ca z&|7z5h8eqMVlT>Q)AU{J-w09XhY+9HTR)7MjfYBk0~x@e;Afu;!)t4?8AYBe*3wU7 zE0bH>AQNs9+|GS+s1-VGFI$dTgkf7mHSl4<_K_u(NuGFlpvDt`=2x%z%vNj%4wKzj zo1})qVX+9~^i6%YNQG+cEFH0jM`~DMbS3HLqjm(1D6lgfU1x|?(e;WQ)xK~ct z`3gF-?!K!1N!Ro7^jh7s105OM5H|!rXtaNYp|v`yJ#}1h zrOBzms);#&Tz)9MgFCm%N08+;pO+Smefr!gl5#% zWe|-a!ZTAUL78!^-*a^+l#6CPyzpDE4yfdOcmfmUx{_WT_3LF4?vb5G`>RZgP_O&# z$?RyA>ZKwjB9Dh z+MWMrAEa9`|LmFq9{K6cC?+MHZB8GT(2c1L6zQ=Lc^pRhz7v>H+3LCZ-j*{7U%tyaFm*SM4 z>h*RDu|nL>nbu+(-5rHGz{HH)qVXw9t?0QZnerRch}MAw{zsyTCh<*al+pqUgaO1N zdQrqGLA7mQ1Yqa_>9gTySAyZni)?!vNS#}v($A|adIst0%Jw~SVLoFZn(}OncXzN~-pB}3E;Q1sv5WNKT=6T!LTJF+1(`lhd4UTl=0P7& zspO>N4PotbA9+us^Tzk*5u+s-bmn)&B8!sIDcij&I9;4E)2x4r>3kz6g@F9 zU0>(P2K<_Z_q_(%LO;lv1qh8N2%ZmtNHwQ1 zF2@qp<1UcNH?1}F^uh77bhILJNDp>-@0}(|OyOliav;%tTFA`OkQ&JMw+u45(2BLh zZXECGY^J><&+sP)%LVFJ*WLJ3py2hIxbOCA)-_s42(`WLzLB?M{$)9xJ&+)1ocHJ1 ziAW*cJ`={f?PeMXeI37vYwotT#OkK4+FvpimN&OuTJ?xK&p0Xg#4SE=HME`(ua{x;^st zXQB{nT>JyP;TqVcJq{coyshj25zJ5w$Im~rU4)aS)MB9yd5K@ z|F3};53ngGu>U3wV71uq<1>RGB-9}%XW;F2UBLOD*i%90{}h@3zdidegoXP)RizK? zE_zfNxsv_rhZkxb|Cj1KP5yY`pK84q4h)QP^l!NlzG)K)8TU!4$lyXVqozX>LuAs2 z;hWRv{}NO4`&v9*z6k9c>A6eXW9p%G*v_a&ul-37<6)fUx>pOeH^JIjYGiNN9JiM4 zvggMhEErH}b(z773I{UB__OJtDdv;&po%aEk3WH%{-osyRCioXqBfaYUX3+N3QL+o z{t_>0mRsu0e~eC-v!~%$@;&C$XjsYUCR&Y|Ka+2vbh6b1t2u|ai-h>hC7@Nmh2ULj z*c0xM)st{v&?jsm0XQs<{MYbJnR;c8VS+|aT52}AlR=vA$2vwgEYKmY=bC-(51}Jf zDfc}LS?I#@<@LUQkuEVYWXGlJsgBqP69kO+*q)X)HT<}^jecs6FnD0Mh|s|U%foHI z6V7&9mjoNPiB)m=Zbr0{;+mRcq|8u@DeQ*Eq`aIQK6s_F+uhuV1e8TJOBob&WE#o) zIPe;rV6v$cWObsc>)kSX(S)gdq{CK56!9*TFH6IxmC?;L0nkRxD!P!4P8(7#SBSG` zHLAvI4q}cALfC=0lFZbDtoCDQ0CfRa z(uJ1Gohj8n7V_Tcwkg`s5{Nlhd4rPZniE^ITi%$XI#;k`5}YbdWuACXEcFYfo6sQh z8^H--Snnu;c)65rca}!pUPjd^XvCT@67aBC`m@i>9`&k6hI6%(51tCQW{3kG(gJUU z%F`pZdzYu*Q>|E7uc9C#y$H;XY)Z$O@2jN$S%IU5ZFoQoM=r8AG8=pCrIvaZXz_M4 z&jTN@n?W8E!*3pEr|onMC;a9O@3Q$K;j;qK-c9e<87#L21Bjgi6i1 zy<4y=tZ&P^C0Mob()4WC{vlY~j5KRdyTXl=178i&~z8!m4b0@;>Rz@l25%4kUQT7uT{WEA8eEenU z>zj_Ygimi9UfnqQukLxU|4ND`z8*U>lI<(h#jIT3QR=tU%~fW?|fl}D(EPR{IdnnH7aA;rT?pSvn~RBJQP zB~H*zsjY`=K2gWTcp@I^cgxAGP&cqayxa_xI(p{&(K!v3T!fZ*aGQ>Uuf7~=ajGm} zkCll0X>nFx=HwLvm5tdIr{xc%(|x=^7+}7H_#kKlbxuz4rAJ6x4TavINkM z+7FK)Su|kTJIqemjo0{|k^mnv+~YDi44l}0uKm-y>il1inP`J ze@Kq_t@rN>1$~$WbOB3mU$1L0gghcx{C60^c)qh~uR+lo{{Z=YhqpgEt{M1)K7AE^ z1Rr(c4`=xzqRs3^OF>$+KkPIBPPj7^HF*vftd&IE9sQCnyRPSrT+-gd2d2oMZX6Zu zcC@mg{!u|PuSY@s^|gpN8qSQLM4NQCYhh~dU@(*WlhGCFL;WaZnG+5fOY5LdFBSX9 zsSJEV2WZ47)muSLk19x#q$}Yi+t-y;I_(o>>&x2!I5=uxukx6d4>7_ctEqy8Uto|?N>BslSp z-dpvcRg#AF*QVRD3P^KAAp&7FW&^UB5F7&jQ2TbkVI)TUN4%yPWr*ND`bT0O}V(@AybD zeAHQGI|+ape$uH6)LB+|(pQXO0B~0Da&r9T0UDm}Dkx&o{()5dn96)`c zNT@~i=JeGozl!sJ@4O<&qT{U(hWP#c2RPsw+qpe`KsYxfk8rv5arQJkooK=z$80&D zt-67hK7V#z?IlccXj`sbKXvNyV z`Zn3@5O?!UmpC`3!OY9=0jRvaZLZ@TA1$vYqTsyZ5?=C>_Yh4G*U#cBBGqQzSWSHJ z7Ygq8uT1dBIJTGtM`X$D!w*O3AbgCSGuarhZ7Jvu1JX7qx(FFg%_i63Ns#MY1kNHx zX?SE=_l0~X-xTTgjkbV)Ie4NayLhnMJ|$?+np8%MkCye33)Dbjgo`;J^t3HKBzd^C z)pZ}71{*HphiMs>j_RY|9#_sKo=e^fVzIa1w3U2aks@F@Y9iP^_ams_wY58&Hw$*L z8!^g~6+*XBnzxo4ia2{DN?eoYs&nbz1Os3x?+D-2YEJKsTNOF&1!ptI^4^yDgIlk# zsm0m)r3uNBp{5Q%DXt3lgUgTFAl@|sQtKp2{8cVE;rjc~XgpOmrDLBmFPGLS8LQl; zsfifkeBIg2M;Nz#Y*@*vkkHM3Im@-Je|t~o0teBhc<2C3 zUSyq0cvlAdeGGbBv>w`JNV&X?O-c}w(%&jmb&68=xE5el?Hs4q46=}py;KqaP9S6eY}tMlxul` zlR(!>D*!BkgMsNTqO_L-<9Uo^sz5KyaxVP`U^aVvM2+663zT~O?fSWqqz|i0XCkl2 ztAy-My~AM@vs#b*!GVrA&A%3%1U7%?yENf8xy}^6vv!3&C?sX{hY9CCVVD&yCQ~7e z403f{R^??{c=p5z_3`f6OODw9oT_^&5}m7Oy*jnUIr@J12e@g38bQlBN*+nyPT?Ni zHf*1*lpDxqc|Vt`Sx>-};}Nl@Pk&&~p7WM)-sx6WeGw4MQX|)l@{gAHJf<0oZWNH0 z0|QAVP2t#HP8^|J(Sw{XyL}c*W?bmfv@``0_qOF_6{jsi_Z7#^hrT6)Wisml`%9L_ zi}6GJtK=v8vX2e)%djy!49~G%A(8cxp0hcmoR6w@lWuU=)Z=V3By+PKL5XZnFe|NV zg0s0A6uSJ7GLyUS%=^MnneqLF3d_5dKqMaD^?8Y0FE_V|$oh+T%M4int~erX*M*(@ z{22j-a1Yeg2vPSdN%+Xj(txg+`}Pbk)CfR!y%+~z8&H*AxX3T$@xZ5{;N>GN&$xg{ zF0HIyJf(BT|3RO*sZ4@k@M}r`1makF5Ezbz>iSlDac(xTvkt8TkxZR~AEIV72}r61 zWIOGb%BrnD>w9+X=BtDt4E5;;9So0Smf zsfROg#7V1*0VA+v5VPk~Ndo=>Jk|61_g*!Oc6h8MXsjgu4`FW=)n)^2Yli}bQV0}x zr?_iyO7Q|E5FCoT2X`ry;ts`&y9d|e?(XiPc+v0AKL7PO`{ErVw~&{ySaUsd&aEx1 zcIW<&Qb=-!w`5G`bFmBAh@x$=Snv1~medIVRj4p}>KH@obNrX4IsI?d19-WK{NY5cvE~X0&n^Xu=c}Z5J*E1OY(1$ zFaWp5O3x*L{71Dli&(qsbO7&zH`uKZutkc6NZt5fKqr4DYAFC!fU`sxclx1|KsK&-Z>gzxonKQsWbz?D{RBaA*SuUT}|{(@#kwZvjlJ5^Yf6F@PWm~3eC zRWI7Ze1i}%Ne};|j?Z467rA^77Ddcm2H2MOu*uj{52@?}LSJN{zxydB#!f z)Zs%%&YK;kjQM=AebL{oonl7X{{vvdWJ>fH=Id8QaLCZS?~3;W%~=EU_v~)VQk&G} z96R|e-9_AivVP-Gna24B;cve()7>%|HUv0Eic#z2`yuIM-y1lhubRe|Ph@>Gu9Q6R zF&WVre0QxUPsRKLS(2m32MmJD_)IM}BoVM=zc-ZC^W~SU{ExZBz}CZ%;C=X|}LM>7>1P(|dY6;e5(Z z*&2@#?&q?Uij^}Sbo&8StU2*IF2nja`uk%KJ9DPL8M4+AQkt4~Jlg_1y(3bbnS#b7 zPPwmMYyH>F5G*o{yJ9r~H^EnAp(Pdmb|}A{73_dB|c*r%c|IsX2lfger#> zCMo2cA^ZfvOrJ-B&_$C66iNvX8QV-tWEdr)HRcO7gV2(;;6seafBUT!6qMb$`$m8n ze(o}?BJax|u70U4j@xZ%K6=`Y(^NZ}X9lzZ~=}GQFJM`nm1j)j<#PC2v#=r1pA4^ZV@-$HY$^*8HPbIVKLZrw;u=L5YxZujw z@`u-5;I#)m5Q;2ZAU|<_m{GB zWw}3{nqbk;5bFc;BYvCGwx>?ciSGG4=VG+x7xU*7mU4|DvXPtPLjLUpQXyH)9~^%V z0t`NVPEQ0MFrmE&#uhL9vLJxHU!c&C?fM6|F_83!V?v+)T|Z2rPH`jLbUH zqhY*;qo*Do5kD@cQ zu_PNluFXsNiRGE;)M5IJxsh)R`VH&ku`-tsfTu2|1IsnEg+wA+(8rQ^T*8LaB*r-@ z04QmA*dhFJ?|fH$pp`CG5-RUWfkrvkk#|PfJJyYU)|cdH$6Wr-n&#WB@|o%z*gd(K zdp;t8$~8->j2T}`nFQ>_Js><+ZiBKJ=p+%kC{hDr zzP$nO#2zc6a$#Sm^C|?%G6TTz!PeYLJ-Nty->0*ga?ucNy@3D6gA}@x`G8~Q8Rq)gf?1Tmt>FExX9 zDKPHJ9=|1lP0j>SDRt10L3`MSEM6WCYaK5>3k0tcaX-&{6;E-VCOrAQ$@G?L{;pNn zN$meTS~}OMjl%K3>HSSn_D8JEV31){fQ~HghUBKo+Rj>Kv5l`PuQ?kxir_s!3T4lG zQNqu|=dP6P{E!ugc=d*S;dY@2HBqHZKTresc6Q35#d0lyek+FyVWpfDv-(W|kG2%! z4U_@J+Q(N|O`6+#FXT+&L$I2Hhr)17apP58)OnGp?Q%b#}s@%TN zUeTMCLYp$p$o)JmkH`Kjl?kz;wdN!;>(uGxj!OXW^)~8XeJBJEL;N-* z1jJAoR+*>m>?#)-ZS}vPEdp{14M=T9lu1mvaodWCY#o%h*C{u_@eUlwX*B-<^tJaC z7{7`KLWbG34BAcdhh`8Is0Z%An+sNT>$;cNo*5;oP5W*tccJoDgVrA7H^2;OJh;uF zco$tlrUU%(14mFk%HVRyhXL&b=)?S|t)4bl&7b(7K@46n2ci)D*8Ke}{eu3CPous0 zGPVcW5|CG$wChJ`FaLa^m-?+4mD|vxN2Okg|Ma_X#i=HUn`p3@Es0xXrMQ}XNh#HO zmj5Y+4On+@1D;k;wbGceHzuupR+$8OI`--Vz%HnH3>AO&w*Er zdt&jnjPNcXGb=k3GVJ#IALN|;!MiM28oMg04qCPu;8IPT1N#xdT1$V~ea|$XD6omj zbZ_j}d2a(Wef29PN2W9M)vwXWeVF2=4hZf)pwJW^z^6>fLbX9nop_D@V>jJYSk{At zPW4{vW7U6vKiuWq5AYp*Xk60vw!Ksg)mJ2uZ58Wc)rVH)9)>v@tox!wMCc&< z*Oun%aHlrzRr*D>{{ZM_%Gb(Xtp5SJF-0zrQAWA?@-Ht^PFZcduJ2wxWmBvf2wHzdV%BT`dJNEt7D0M z?yck@dh8`S2Ar$7(xxT3!z=F;usfOOLJK{Ae=6LyxZ`JcBZi}-J5&UNx>vUU;R5?K z6|J>2S4tY2(mpYwNVEDas6R{Sy0#XERpThj3EXqXuCGxK4Lw4l-^(@6p|`dqj~8I2 zNilNBIcLww@HI@kn~iV2BF*ToR%H~SBaL)_ulJ50F1A)ntDwcYUvjj&Wr#VO<^M%& zJ&DUX$*nyV@^S!}za@jH7flge;V&b#bExJ{?rXS)S=7jh^48Z(wk2wE5)az*#*~v$ z4QY_t;Ln~X-CoUQK3#u$!z#$?Q3Bc{0U%%#a~Wm#{hiR-(Ceu{e;8$oc42Sru-971 z%^w7Yt5oacn@qf70TvIA$+bxtwJa806f3E{x}ZiAcUw~|-^|2&F87U*^?rO>6!GeK zR=~=Ail&{|)KFJk#1mHG0t|H{;|oZT3Ib)BtDIwX-y&1Upx}B>p?4S1rAsmmjni?7 zdh~NRN5tp^W;4lcq%s~6rmv$(kHd$@xBYf5V!`|IW6$`x%$ZewsFhO=Pb=j1CZBQkvWOC0OyNScml93vG;K)| zx_ViBM?Q<}eOqh^190dO^Zj~mn|W^DgGvq}G6)j^TTzr@ahKirQQCeR&PoVQ^>8K- z)AHFGUG%&T-5Bav>bexYi<#zx@PAX~BMIklQ zo!Dt)2dF(QF}^u?u*2QET@6;Qkff7HsmDb^4{dC2EBa`%XLM_PY_O!f?=V2H@zX0l zl&nTpazJAF@o~|(fZJa9jfy{*_l91JQp9;^5A){!anZ6EQ(JHAkO}DRcF=^FZ9x_Y zeKHb5wGwe7#7}DYigcS7W5owZYDg;Z=j&F~cVZTqxhr@Bo>9y32Dtt2``WubN$<|x z&|BgvGs@mB<7dM^t&Rl5uag*=j%?0@BF^JbkOOoId-Q)&67!jTzs$}|@j-FJWFaVA zBA7XIRIc#k=2GItThEpu=`a5FSd6r-EwuC$EA{3h{SksS6i#A!9+Whb$p*NV9n7yrZjKZWV=JOVKFp({pBy;;>`Q*g%ZcfKKOs0HKdXvYH{; z&&U^FcWhgDavOJtk@wYf1;Hya7^`W^Spf7A?pT8jSia9YH`=D~zAuH{m>{|fBuLd^ zjMnGaNc!_aB_1{g% zLs2npa#y9Qr)r-IO`@UGh|g$+UM}m8IdW)lS;T*P;MQP~XxRahFq7oFMj#g0_9q*> zICS8=(vb+O3ON~nY_yFW3Un=W`co=&;b zcr_bw`%(uy zTaWjME_9b@kX$-fB)m0KEiY;90*Ah5zJ9b|q|mhFVQC5VV!M=Y#q=n-l;^f2 zzhjh{3%1HuM6igq@vlNDSVjq2MEFFyvJXEAnJDRjvcd>GmTiF3YSjfpVoXFIM*iCj z;yF^1x}mq*C-Y6?GSuINC+Sd`xZdqj&qnLmq?+u96c*)p#RnAek)*6SM3x$!svl>Q zx6YkYo)F4UQPlpJoan6*L)GqRZxTp&0GA7_0mM#={@!-Q7p(Nj5?9?(?j5u;tN4~@ zKtqTWaZ2I#4nM>@@wlX@R?YkSCUAxF_MnSw8}mzpxS!+y1hPm8(z} z8#r1eHM1>=fq=i0J__2)bwrM_d@|Z+N!}ZRZ<=J4JFyb}Rs)ElKu<-zk=}oh7RoZvNMslP&OqMOUx&!*2g$;1N}TkS%oI}IA8 zDg2qLU*+E#ZPSGOkwU+1X-`D}jG^o2I=v7PS6S&jO1i)&pA7cNnJ{wbKXvnXIwrCZ zI(xMXRnVlUDU2qXbtUFs;m~BI0Q2XfFRtnh{`g!JQP{^CH0%O}&Vqj>BQX$Ky1zyF zjC^h171b7MEyNCzU#FG2$N9^g{$_og4E?~@+79gLASXwrT1!4hiPwAu-)mz0sYQ1J zL;K0^HplVZIdk=s{I|Q69hieSWNv{uM-dQ-IVDK%Sd1I^@eg?nhe)#5~B@j8IQETt1b5Ygsy} zD~w6mU`jQU6qOE8ol zm)Pa*&B?e>fk<5Mt-;;nGUC~bpVDk%piQ0RMb+Q(Z&122$Htn?h81Fd=6Fd1oDZMP z8y>MvjBK$oP56*1dM~*%b8=fY8>b7za+^x@?dI6TNut&@UCQralK&MeE#R@s0IhUW z{VCIv`1ISs-2Z5?wd;v(7(IN57*p`3co6Dhd6hjMk1cl^k*3%wpJjR-P6D|viHG4? z_&X)mvNyy4UeBF5G_!w-!Nle#1AaEIob;#Axf2Dc4WsaU=#X+~Yo!_kOWtEqI!NMr zyq_tcuw^$n%UQEG7pgM9PcE3XFQD#3q(j|4&MTdNBnT~RMB#DK0u5Du>JD}hdfJ%R zJ-8yS7%Tq)#HeoJF9glG=+=`qSTPCA=(>XXFwY32*t{`T8&cj2LFhww8875Vu=(HS z0MkF1Hz$9Oepw6b`pYsS@|&q_z*ff};@-oeYI>?SpPt%gdEl~r>|&_YU0}ac4Gy;RIgqy=0CT&)n>1FLs_Q zG*G~m!6q3J!;&_4GMC2hI?a2~5AX!uEHPgh!=sjVAlJzFtpsY7l5$Yt!ujXA zhWf!@bOtfPONTqq9YfZekd~v@gLiauk{uCfzT_)k*<;I z1hnzv;b#_B&+h`$l3iEhZxa7$=w|ga&Q^UyKF|Mpru>u8k)iVoxWHy~L9v=b!YB~)>_f+F5NGufRjH~Asm1;{;H2>&#!{9W~} z4p!|le}fV=g$Jc`tV;6q*5fOIEM~O#USgD9Q2?*9h`usOyzR81lr6<1{DLM+y7t*my!=YAeBTZ8(y%p~0^<+Khj%8V*{eCXY%F1JT8TY?3WW zt{O*PW!zLao65Ys4n~4h%qh|hdET50uy%=J8B0exBfo+cg>RsJWk`B`s^nUaI3AA; zr$s6m1V^6UcrnwxOgJXA#JYt|@DGqsMpVn(>nTfdV(Y4k5?8!EBHPpV$vV%uSx7|2 zs<<+(#rux8Au=9X{OMu%J*+dYD)Yl4xj$sWQ+GSTHj+f*dy3kTR8LhLr|2U2e8J+_ zNo2b^`Y;;G&5^a#@FaqX?>V1IttlbCY^r1?cUCI}6(=Hw8^z<4$SgEbMa6sSjeYlF zm$%B?gI17Q*5(gyNg6;pcVp&TdD?WSOt=2yS0Hyx0|1G!gfFc-7j?Mmy*j9?d4k8x z0Zo0}!zlA|w!$=>$teQoH%EyxVcbzOIE*84ZEt5#QYa^6E+CM=u)Z|l0lHv_&lJAse zB1C2Z!$On|NgY@yQ9d((!NQNq?D&8rkHUigO$$VeX+Zk1^ZPaWEi`)1v`O*63;AZU z(zTNn?W~6UxovWEQ|MHGf&0bQ$``?I%+8P?LI5*K@Qs3I(S*vu@#MfWmt0D*la$N? zR_-IfAZ77N#afA#zzsdL-xLu)mmOv&KP#Mz#^(7*I_+4$EL%47M&r2`!waSl8tGI2Tj%6bNHm*fiufFzuJ`k++7)F_Mj_b zo-UPYZafr&u4GlQ2C|b@NMDFOKh5f z)o1%q-j4B>N)~z!U#Rywd)W03xeUoYHXXE_OdWXqJT|UfEB$2iA7DeUR&F~-@i$>D z3AHN8&kpZO1D(7$a|&%A(gW9his_>P80eWNya$D=p$Yh;DRmt(g>H;Nj`5*1m^yRF`q(n zoquh3tSRE~lZvWPSg3KOJIcu{)rIp(%*wI8&v$irVF&UQCpR=spU~pW7xF?gu=JY( z2kfyPt!Gs!fvXgdezHBd{-EI}Sx%Q8vqDAtfdu4GwNWrv00dN6Tzqt!RhAtqFQHm1 z`43R^^Z8`b7~+O~D<5#27a$A^Nq5|exW)Ge`9@s%raH?D%VP*G!c+$p!!AiGSB?83 zuS@TBgE%-$J~y*5{`@f+6r7XHPwBbtjW>R_H>+I4{>)M6X!IXIhiZdrgTUlnif4+| zKdd=#(qtVHY0fcX=i`WpqyWn`uk!JRL15P!W5@fASjLr@KmL0yi#_bmk!Vzov9(KU zul)LsT0Be~_IswZF>PecrB%doe1bAv9>Ikj8Nc(qNN3c2a4({hDso*1O?BmdN1a<- z)OQVaY#ZNI%EBLHO(E!otqbG-cnx({+`xzn!NAf3Ri*mhsJHCgMRW(vE=xz;Q;<>V zgA*bxwB+yX?UqM^f+x17;~8!C>0+!Bns2ol>d;<%f2U!?2d_z6n|J5td7sW5-8~yZ zV($wiwwP#AdBj6zlHQ1zfe8&gfDIXglAgJ!Or(&cD-8mwXh_Fb2X>t6>y*K#-1)+| zHF`%{R%e?o$Ru)9ok{v0;t-3MNKy1QK%?yiNZbc z5Rao}rf`93E-26=BQm0S!O_qAAu?}AR)hHWo?JhCWC7{Ozw}r`} zg~aKqv7`@(^0CC(+#-OS&DTCkZ_SS(Ndb??3KUE)ybAH%bd(KTf!_VTGZo@!()L z{Ce6?HzVIr^e)2!luZj(bzv3MX#06Vy2HL47~WfonIq_3ZeBWQ428zc5Isdm{4960 zc%VorQp`K7oyQyG>u^4W@ICc+!SI5DfeYV0v21X8ZvRjmS#Y;n9e=k}UFoD7zUs$? zW~JqT2<VJB?WL@mny z^?6Gb@l#-m>Gc8e?xs(Y9LI=-x*D=NCagP0r98CV?X+xk26`hRvaNiXCiK!DDN%?d zI9$qO{((+4iS1R>GSdr-*5w?@OP-RH0*QUduQmU)0p!-jKozW;new6YkJV~l+U1|u z`m{^G>N)KRfco|PDk^xR+$L{I4Bkh@gw&Qq;>oZNk~Qz-YNa~+;szHReOT7<`_a^) zH9)*e??F*+ZX!U|Q6J~5NIxOPmmsUKmgzi`R{C+Qu7-^#w_5F7RVu;Dbkt%Pgg-A? zjLGAE$hUx7hMj6fVU1ZBBdajL9K;-uBKn$?<^ExSS9wKVf6H_sC=}gO%!TPadNY!X z2>Ih|2%*_t3=22yAzBS>vKdnIamJQCYtm06jPBEKRv82=2v^-Gi#oMjJrTMAf zk`Q9#LRMPw_a!F6xIDJe3vF|E+duZAQw`v1axm3|0c`Hc%iY+gqxo=Z?&quRA_wh9 zAB;?F$+u!c`73!4&OE+Q5eE(h(U}+3H*tQf;#z_Xdlm=#Hl2CHyRl{-4J0T(V1 zw)^b^EJwc6?i^s$$LdfZk{im6x99|xU zisU3>8nJejz1z*$Onh4=ta3~WKX2hxgs47oKj#&7W}Mxrj{!+=b`fjJxi|r=Hh3-e z7e1lHx!}hjLhj9*l9=gDG?-`&1q%tCi3hObltF>9eb66nxYBt;BYcHxn zxF7nwcZl>sZa4AM8@cG@`Zm_=5SWOt1ZSmrYI2S?XFc%mCdvx znO3$J)kJGU+|FDkVfg)*MOEgNFxg#`6tp7{L7$=6L7~vx?jI6*oIW}s-`GOJ&R|Ph z%wZ_8D`sa&cbNK-jB0ayumtJ;Iowyv0btd>j~xBv?I$%Z?-q;O2ywkd2_VG&H|KMz zB=bCYWl>aGRCyE6kAS_To8;V8F9d|1OI=BL3%VsAWcT$Sk^7ClRfkvE?Pc#jm#BFt z;QYD#XVL0|KEwLnEy`RJMt4b4T&?FD#L6=yYEcj4sa?}v%YHLj8(SaI)x@u)8$Mb% zBDWD~7xMZBs@lwRv{vDSW|&uMZIr*ECy1HosP;M*!tW-ZK%eX_%uE$NuA#bqG6oC? zK;Lp5v-}TG4Q?c%Y%}BJ%LuPcfo!Dh?RydY+AfWH)Gd|ZLl{nMj$%NXdt5!ww#m~0 z)BH+LIQ|fqA@PPXTh-}gOK%Rm--AJL3#S`py5@f$qJ~+*d!Dz+I-!nsb;2)9$h%kej#IsLJBu5Ouw?N{ ztgb}cHzK(D7eb~vHdFPB#_^1Txq3=Sy_koLQ<}84{_3g0xHF;Dgc2V7DyGQ7#@U~@ z50j#ceQpu#%W`qKCja;S+FFktvY(ryO=H}Y-?3%{|7lVz9At&FDVX>NbmZKXYx;Oq z0$OSqM|S;Xbpjse<2?{Nv&tT!zfR!5?#+EwlyHP&YSS15o}k)1M?yyzbv;SHXm3Vh zPooKslaQ1XErh z5PDm9ny4^NES?`>#3`L~#q7czWh#oGRgd$D`QPxe(5M-G7cv*i6Viw`KrE2#78@*jlB0OBDynmFptZ!Rw=KRO-s3Q2~PqPONB zilkOUCCPWKk27!Z=i|G=@3u?|KFLBeJ@^BM45TCk>D_j33bpfG{3W$0l!avxq?ICf z8~ve&n#`tCO{4M4C{yIUo$xXXAj^S1;0A;qBCIP20{mwXH#pCnVV z()D%s?1ue1X^ZRjP!57XZ4&g2&Vi=i&4?6}xH#YzS^UGS+xuS!TzX!e8ax6N-6*dl zWe~SoMMj>>W)O4K3_i(gQLFro6_4)r_~73v*(tPY0yC?H1mF18(r@~R6++m5fob*R z+UC5#C44}lEsFBCt5DJyHfrkogYj6Sv)`8eKGkk)O769$c>w);uH69W%gk@%gPj{*h5ht=YBqA zZ{k-&Ne+7*=LAW!$GJU+PnFNo1coEvDHI8Rp07Fgwn9fOPrN!O4^%7EjdmGY&S5lp z5>o0q0@G|Lha)a%Q&%jd&Zw6$mhavi$p2__j>zug9Tw{Wmot+g!2zebliQB1t+(8T z1qlWezn}!)6vOpBAR1=A*KlG<&djn zHQFUV$j0FkJ$zKxN({!PV0uG~G+C`!Y*c(M*mvD1qn2Y{$;R;)dy!vS*?tEu)oq(f za*F}TeeXww7n1hk8GJHmAD&1MF%7;{hP=LW^zW^UCq!kiV6* zG_{yZM|p0;+*uWk!sp25s}Xr(@yrGCM!RK^cw4ZSuGv#FhXB(p`%J_6>}X>I@4TAa z9xLIaHq7AD!pM3rz3QR9Y3}qNEa&CoylIQMB*?CjyBzCth}HU zV4*Ln+~_Iyi_Bx>d9JB-3Dy3as=eg#=|=~mMsb6UU$+&JeF7WIF_NAwQO-BS6)tDklzAZy-T=mG6C2E>Cq}f-^nyrUkw&@dZ%i}@q9n=8pVRt&R+E&?yaAN5{K!H5Z z_)9;+uaww(f8k~`t+=`$ex(Y=f-d|qP|~zqAFEOVy%HnTOI=ty&(FKI!|#dcB9K^L z(goLDQhzh`4Cs|Gptt6rW!x6r(wuJlUb>M95vj_fa6>SaX$h+GJ{UL{xH>TVc@Vt! z_EY9|{dBpoW}Bl+S*go13D_FJu!Qj@XPY(+8CGC_nLV#`v}1G>n2}4CrKNU|VeD`? zUNH*w-z?ca7}z3%_jTsznq||r{c5!S>j-v#s6+S&HnYx>pU09#P{f z687)j|9tyRJVHbxQTG+@uo`2^Bl*=rsg(QHo~j7j%HRy(b=u3D)r!Fx16rb#*bx4Br(jT))Rx&BuSv0vK`RS5X8XdYTZxvQRbjhe8Q) zPczg0A5I@hhANbT36P7y#0PNi=Mb8(W;(;-!t|J2NJ5VCmr#^itYQwxEqDV=doIa~ z5z0mV|4X+1uRWQ_$o=2kH(ya-Saax&_O5zS9uZM}j{mCRt5KIJLbu2I_nl6Da#sZH z;`o;A%Tt~%<4#$0kcj|DaD6WNl(F(axE7m2jkJ5`W znNOJ|j4b#E+>bTuh+g(Y$awg-0;U%xwvbranXq~Pn4v4%kCLm0B5j>v(SCD-+D`$V zKJahb1>i`#Xmhg5K!wu~WwO0Q6Z6%lyb4=Efl)SRsdvD)2HoAr8onSPVM&&r zB%lH7Ri%{N)!Gb%DLX}K+6%3=yixOml0%)!058u0pIFDZUh|KM&#oA=rN{>)m}19; z1$Yl0nDK1@Em2G#3@yJ8PWjdCLs}}o^nVWy-|GbzOH>k(e&P8ONn_~-TFd!kvvx0l zc#RTS8igvt-KIWI4}Eb4s_?eds=UN2W;(VMMLJ@BluZn!@RP6Tkn6K+yppoT(J_A@ zlv<)JDm6Tb^(oi#KYx zx~?x}zmTW#`N=i%G2Z`G;R9n0a%lmsQvJEm*gd12^izq?tHwVXURG*%J=X_y>wyC* zuX-I=<;1>4K7Pl19T|I#tV|03D$PowtU!|KY(%X+eVUayD20RgF=T3vX+WOc)byI~ zd*bT|9{3~aBUmC7hxC_7QO>8WN_Npm&`mz_sk{u6kWd9MukfG5(QBv8>|Kce-`eix z@1f$EJ6Rs+*|aUa!6ewtC0Ud^LI*$Z^kw1`XC?ZI8YA_~UM>0MJFUG|dKts6-c_13 zGqaihO~7s!h?p9hEVNW`_TWFS@L<9pOj!d;J1ogi2g} z-UESFf6e85V%?}pjQ{efv}eG|;vfyJl4HIAIq8tG2=rVop+wxU<@aFj`}llNE&fLE zsbF52CSXU$eiE|&AHWaS%I@8g_ba|BU30N1+Co;$O?LT5yOi&Oqc7MfBh&S*rc1x#eGTo^GYYo=JG` zS-Erd`)jy@D6k;&T4S%Wi~zP4HmCrb)d&wW3+STRdtq4yd*Qcq_qr95C%&*~_miB< zOQBI)l24Ik0vjr6s8*~IzkWfLJL@{~^x2esx%Myy zAAVY^t+))^AcR@DOEE*mDz~n!(}-Y`DG-q~2U>-|-Wl03E@!__-ubkI{Q5v(a85twvEtE_&u~DZYFTZuho~yZl6| zz~J}~p>m$2?v=fagu!4bMWzVXh<4e_;J)&%EmtK;!xw=7;2KmM*A zwXs+0m`lCwYdS7-Wy>fxb(V6fQ#edX@by^H*Jo+<$gnxMaiK@rK-u~9Zt$i^cfk1Pu1J&o z*?wAp=Y5gh+ER~eW0M`C@cfg?{JQ2vb!BDT+Lw{hD3;RGV|6?|8LV&`G!K7Xl_=T$ zNb=BaEGXd$?9W=61C?d+m52MdLO|)HqwB(yFu~Bvq4~P%eJbn+eC0T!o*$ObFgqL` z!$oxAP^GHYMRcE_C@uSIC0{hK(_e)>I9~=yt>%r7!eH#lV&Cd68Rq@dg4Wm zQJgKAg;iU>1J`&YUq6CA7FmX`{@U}~=yiYkt`C(?G zwCBnm0Rs*={MOw$J!kvy_PsG0@m(cjpp-d9-pjFKsd&NF;8j_xL2e4v$t#8~6s-+% zM+QUxVfJb1p3xpUpMJ3a&Eo8aD=EEr;y$ry$h7rmVZ7IqaJUK8dDjDeS=}FukSmlA z0}}1$_v!iSr2PkNp_uNR64CQc=IoBauxkt z9$s!#g?%6}fOKlI(eET`R*tZMR611K^Oin4xZriB5e2zJ(1|1r)mjbY(&l<(si+pq z<9N<9HAwty!`jEIqkul(+#)8;$xrLs>ja|gD51Nmm~E)CW-67rZ64J@S-2b$u8Vq+ z>{rOqEjE2BwSX~(+8@83Le7vTtS1#D_>=|KG+PIyc{Nct7C0}4gq|f|VXOL|3{Y%m z=_F*T1dbf%U2mwar&V7?CH}Oz8;M;T;J8O&a}r+Y8cG%uuW*T>iW=P>t1;6S7^)Ij za;vI>t3A749%3az_4j5ww#!I6tm_4KR^eW zyen~BlM7ivtA&^-RGtT^iR0A7#lhSWl3FRNese^0AvorY%WWD_Dqf5fM-~!+uWnAotg*3*Mk1}_ zX42f1(`pHXy@v=<@xg@?yqWIG>;T)I;;+dTGuvCTNJ^}f`3q%A>oXZH_yOHSJgQ$Gz#=iBzzc zaOhN_GZMpPQsV#S9^Lj2*vbAha&`gkqxr_~F zCaMIn6F=C(h(rv%ymsd`BUg?||74wiY%s$u*RADJArp0oiVGlZY>mlHamY4N#q9ne zOBUgixKgpXY8@}hURgxMGjf8G*7H^PrVM`YN5?qm66J*8dXloCrMgBjs{{T+s>qt~ z(*U+GdQn`U(k}JhLGA5o8UboebaH-HFy0i+n&%ygYbo0T-TrFdy<}pJ5DsAS33RN% zKb*NPM-n$N)Xn#U*v?^q`}}b>FGsX$C?mQ-<|`tr4>>Vp&Qyqh`M9du*kN?m=v%+P zy1v5!J*JThwBKz-Xtn4McMNgtZxb0?u~3T91~nOruWB-H1M&C}XehTAjYWGCx>EUAcle&acM#uh&zUs7 zsbn=`JYi>=Jzcv|?gJPiHH5ahQS%?5(IJ9Qo=5-C!wK7_#3`POg&g$!*2eVht$9ny z8F%EGLQ4UA?2dztPx(TcYzTay&A)iz_dDWGLaja(X{oj}q<9Sk6VXqhZ z_eH|^pU#9cO&TiBcWgM6F-(&C9vL^xvqgK zet8p_)DW15SIcrPb7j~{(vdr)KJ$>j=20^{iEq>+Lp0#KPSt*t?9+4wa*mhjTzJ@hE!u3k+S*~EuzqTV!tD1_@qtes; zwC=(Zb+B`xE?-4HWGZBu8A$kpL*wO5CTK6!-AcQ6PfzwoNL16@wc5}Z@9!M`XW{DN zTYHd(uVYAC9*-JY0;fF0_x3C-{5&0e{?WjiZedw5>s2>>yG_cC;IE*;nZhQRyuXRk$fmdSFu#ME?#4Y&6#ZUB zi~SsR)6#);##mNs_UIW*FqiMlJI3nb^gUOJ>$sK$6|>WRALpbm5N+7sTD*WAjf=RsmJCy@tmZzE#n2|BBj@pDVew{ z4(Q3+=X=mw)>ftF8O+GxcI+=VO4%7s92>8$=ZI#%ubfombLK1WG zfkb-sO|ykWy=))Z3a2mU+i)6cjy$FhBuI;z$hbccm;Qf@{Y6xqZ5OqR7LZ^e5G=R^ zcXxMpcXxLU0fJlMRzTrSA%Vh4AV?v>-3jh4LElr~9^>@(`2XFYLx&#gx!0QWx+q2~ zqyM&{?(gN>@slEx$4pK8MHN{UhH#uD>+To(8){w#*y0X_5c<~4XzCxz<})d7DmqEm zu~c1}40#L(h((h9?kphDPu6agQg~J%%nW&T2F)vtSd)Nv$;C4BF#IpKx7UQMQ>lXV zTKA37HA{5hNZNwI<>b)Gy)vHt)NjYyg&tHjydAn%#n>-{jis#HFX;Y#uj zo~gco;v^2lK=RwL17M_%Z9!AJ1u$c)PLbvgFU((BIX`$-(giEXY?I#zfUG#dcsh`1 zRXSp(Drk}l916p;00!{XpdQ?o0WeWt97or7pp=-QLW1Qk#1b5WJQztSO?B@tGjq}u z+35Va1TEq{kkvc~eb_XS4Zk;Z4B9ynk7wq)LKh5U2Rs}8_-j;7qcED*wYYx%+@Xu; zf{i*h|mHN=?45B9dxmzd}AzPBoWvzRSAZlgsBr(b8t&8UkX@wM~#9^=N07(L08` z^?EUOeTNaL|3ssiPcY7FuU!2QyPqDmi}Xkwj2)=Dv64;9%EZb`=ZLd?)y`>CY!0zW zI|B!hA<*pH)TS8^B^Ssj(537uiS3{___|qmXq?A&$h~Eh+B|P+c4{fXEmW*ghiw^g z;`1X9J+%t*R-?A)6enR3eW6Q1J|jXM}#ne55jS~_}&!Dofc zZIah&N#dX?sw7G58ldq;9+edm&zi!T9Qb?Q_0VeK@c_ohQu@nr7Ue*{y}wD7pT7R# zTmk9fm6!fnOE4k_bw-WJatjT_!oB6*8proi>}<0br(2FMX85r938)Ex#(>K_Q% zQm%UtVAisCbNXs&RjIbrT%}&&IlCE83mJ1Ac}LbDEj8wz(f%wlfs6b_$I37k*Iu}i z$@!%=5pYm_W(vunngDngvIgB9>58UotHSliw(s$@G!P_9;w_NVp^Q5b@(=(XI73t6 z{h2IYW#!{IfbzB^(Jmff1V477{~vw?FYpszbFAt2U)t+FttICn!A%$L|7y$I=_9(r zH_N?Oy#x=%9^ec5wwT|EdtH1y36AtXx;O>(Ws#hKYFta>y70w9j8E=>DiCsF^oG|3 z{qg-ccxV;<425(zWyu_(`Rv=}lr|oS@XQn6NBu@Sx$ed+*btIY*lE{K)0$lr5tB^q zvFkm^y>HlBm=`Kg(J(G3NJC*(0Wtoe`b9^NErJpuU2XuR*J%qKK%>ld#=2|DcN5D) z>9IE-r0pIdo!%-QJpjhs)m~X)(HUyVSP@nWQ1D;=cojV{LhfmgCt~{&x5N6~+bH^+VUf^fN1-f_Bu^tJ{oswr;L^yRw?>Qd*duzz@Ql-orY)q z9O+*L5;lN*IHrF1;{l)!AS0-p?cp6&1^=lnb~@XCjkf@sL>A^9fu_Ho`6{qMs1nAa zKD~2Ef>8rG!vyO6wgoWGtv}#+>B@^>1+n@n`rLEYrAeY3;*IJ<7KO9-7ZjI~S z_4E)s6j+k?3ZCWh)eE$}m{xDB@%Q)VF3z%JF|P;_ZC+`qvzY$dg5^cFD?m@uJUP?c zAnrHubkE;6cyn``GoQ{CK&;h^|L36l{+$jec3Qb{@s z&mtb6eltj45sL0lv>iUOyQR@)R&>uG!>{biekIG`CdwHh`6s(sLh-s%Lg$GXIAlrs zV}$)B*hFF2cq+av-To67V;Zl3;wPIF=i+tl_>VmGr}aZJf5ws8HdUwLP7s76WruhE z{Ga{z+YRlu$lC{%HrYzbFv+B9}D<&HdA zDEdj=_kg>i4qk#aZ5A*dee*S8^VeBT)$=Y8TtJ_F-4lYoQg7YTmRvl%a|vPnJUU|> zsHK=dAR(ch1TBOBv@ovo8r7R>^_u!z$j*Kg_l-;hz??SNrc57!=vQuK*)s8b_n(_( zy+zXSbi7Gbmr{?J&HI{JxYvx8>4XS(=)Z@^+BrU4Czv<3DAQ;w7-W$ih$cCj94*Y8 z(Gc!RASzN|aOyHT+ZRVVZU~V%jtG!-Zs$$^2u0sjgVr2jB=CH{UhlMz>3x70hkkbC zX1F2zy#W+>8C2bEGYa(?KRg|;5NjEKJY=Go=Q&CtJf{+bM`)=OfMW9c{{eg=UaGWB zSMEOek%4!vijrP285Jn)35z^+eb=N|TnNA!W&Y)T;!35jO_jU{OOZw6M=yf6Bo$dypjyJ)qyy3}{LPRLKnuX|PYd6Mj zE5}YneCJ26_TCny!cO;~5nq=~m*+b2Tuz?n9Sh+o^NLPWM3!PV(X#)nX9`z6^r`RGNdg~}jp75+0utQ|V(OWQT-IX0O)M$23yx!FyMCno;E??%Yehbb zHc*Gzo^S{mMX94!9zC3N5Ss}3G~cg6=3N?%`)mga)aGB*;7~CxqKZulw*7wm# z%``%?U4^g_&oq;Jh$YRca`A{3tCM~d8}(QPM^JUu2TnhFBGH;Tc9Ng&31x%=23PG* zzGd<2=WMcYM)k%wRakydQ|GthHhNG@-3L{beoH(wZXAp~uy)^BDJRsCl}i zD<6Yf=F8#;E_PCcD5q{Qh}nnXl>al$8*gHz-&QqU6Ck^fEFkc*c=7>oOR=H-HIwwA0C1FymvPb9h`g<)!zMtEt z>?}>N`(SJuzegIdZn8;_9uU`Sk{e-P_)JDK{C^9Y(jp_)`DG|rkFTcd57VG-+})tq z9?h(EdrS0M>aQRZ?3Ait?fvFG1H|-&FhupgC$hAx|JRrhAvu#cZD;x%Tyd+eb~kBrhD7Q! z>HfdTOL6!}>9P4o4}socL2Hdk_B?6?d4U$fx^b)fkup_ zK#rmv=k!T4Ma1;>juJM=#}y^pcGFQcD(VWM@Z@r?S&i6I+P_>S?B_aQ1hYW0W95%h zWV+vA{xLQq^6V|oR!tQ@I%xM&(>5pvB~xR@fnw@L@XrJ;m)VZimE51nr$@wcIb&`^ z976B}2`01H{`tb=GEL%urIen7PM3{`$fkSH2%QWvzQ^%Dgp7p@ckb10KEdw`_=5wm zm@Fx{(#w5X@uk70BPnCn@A9kQVTVogjJJ$Kn>onPaQ(Zc2gB^A_!Q0} zZ$s~&olgZgr6`>^jyTqD>DIW76hV9|q2>CPmF%Og=agj1%%5R5wKVqO3*o7%DuuPM zQit!nt=@0RRgIzNDn+3}g{Po66r0uy;M%MiVcGK`s7p*BmPz%0)B>s98da2Qq zDckZ3$DLVE#crK6be10vYTKq_dEI>^S)?wmp;GSQ;lZ)ob~&Yg(z!CTdJ&Y1 z7mn)Ngo!4ZfoXGzNRi)J*mVl#)7rIURgzTH47PVE5tvmaOWX2=A%1Y9dDe?o6ziz> zm$N3@8B!CVOfH55Cxk-TroSXsA9qjlUYs-w7QGI-<;7LYTT)T2;iTb4dahp9xR+p$ zs-O-9k~w<`7tp$h4UaaPOH{hujp3ev%AG|PQO{`Ta4-P}srutIJ&O-1r9}MWxU{#y z<>FLzg;L@WnXIRz?%X7rq!_7+PKvx)fhx9ot!15IG>o8lr#f&}@*2Gq$#HzjF_<fQfTlFqLw--EgMY6huk1en zXt-|WyhlJF_jlnr8&hd(t)MViZ_@OSr}4s5$PLS4WX%d2Jjj5Lp^vRrRy>S?oR_Ao z5b0PgYNM5)7~$X;=Pv^GVEV(i(AbEEv-@k+EVja%y zS`$3N(cX7v(LY1Y)n?qG%uyX($O7T(X`K0fh@E*MLX&DO*pkGx;u~&tjuL{qmy8|P zhl##fTOLB5%d%eCPoJxK|781f{f>o+S@TVyxG~rL1`C6BpNEdar3C>vUx&F;@PM%nrN&{4hw7nXZZ zl+ASn!7HJRo8F*s>K|8>@zi_8U#+J&j*Q;ua-=H>J4FWAw}cjjLj%~^%YL;<6ROrp zp1^`;u0&2mA?daDK?gUz5aO@X-;7f4Z4x#ppbDorQl-IZ-oW|#KS4mOa%o%C=$Ut9 zh!}6W)pK?{F#CPC z_qxeQoKmzwGs_{Cu)+$nyiy7TGCMjror8ob@YvBuKcx)?Ss!_|aD3^q zvt3J(o9GMi>=RE?cXX2jo&P{8%q^ijpxN=Lxq>JDHyfX?i{j)>_QJ(Sp%zhLDXfS4>$kX9@o6GiUu-@6QZICONBl$) zEK_CLtc(dtGF|H1Ev6Na)r4@E_&R>Jba^(@zPaYGpx;x~8yfWoiZ7d3C{#M%_8~R& z7|)co7|%D)b#%5`W!*$vZ&7qQ6UQ-+E($r$7c#|Xv$itILV6Ey8I!sm_8X){^xv^| zp%LC{KKx?ZY@ca)E=@>q*nF*Xbsu+%2ryPxlv(gnc4jIV)j3BZi{%}elrr6(SlV`$ zSEbQmIvx;U>aU^#I&@v$|L-3KT0le~3I*3atVe2vAa>5ujoMv3Fa0rI(BeM-n_bh8 zjxSpUvIZmmM<6B2D5W9x8v`iSStsO(yn3Rf39C0wQU1Rj#y64=_sB2 z-0+;99Z46>4YEsH7Rk$XZH{!`kiWgq7u5&N6Hd=K-ez}>X5Vwk(yzJ(P-kf_p6L0U zX$cpuk3?pCQ`MJK|F;mbpGx}2HmKgX!7}NP`^=r3N1Zq`h@oj~t%2knu6WSfpNlffv)N*dP_QFz zq&}VGRbQ2J$vq3snBm~^KGRUFS}CGwhqdvlUbwk?yW8Tnp7SH9ak~4uYbup#ZR|zQ zLp1Mg@_jAg!c*rj6X)tK-uky8hzzU-<}&b=-^e?gqT5<&*_j2^6SodB?%|gzH?%uD zC+~IAo#X3KC8OVid0|TlvI&q$N+rdYMBpnZFDi3 zM$MYh&+E>n0RC&#C!Am1x1E zm6Up{_Oku8bj#7S9|CCmAeDZG<;JrmA-5yba%fbuW8;c4ap!xKci!onym1Xj-16L} zM#TYzxd5V-4X6n5+0F_oev7Jl zJNFTm2ImJYoOAgj!8>p>9V^YvJX*$ai=e-}bxB6M-eVr>GhN1Ahb);Gky$BcK+Z8v z-T|jm__y;1wgtNh?)2etUyH>ac4^}Cd#37q+Qf(({-t98L%n_9Z(h+{F}p zfE53)x-$_Wjk;pGJsPdE!_eayVdyzZ)z*_NaD(VdA#D^yqWGE}yhpKt@eK(@yxgkM zF5do>5K(w;VjOz(i21}W6?iXi6W>0Tn0{pBnf|%jtz)DO`jL=g!)Ex+H{C<>J4er; z8MFtjE~G^Ecbz3FS!1yrQ&6!?ZO{kPYcZ>DE_dU!5#CJcgF=r>>okxXH$mziy|~uy zq*=-<9cD+%8F=I@J|YV)KV{Z%(i$v(eCMUZ;DXh)=526Vd+*Mj-+cR|S1_2_J1aZp zNr(ghdpJ6@kyjWsInHONgEP*GI}W@RP@pE4w`Oh-Oz|-8l#nRa45sV;_T8QZ^qs~7 zo^oarCw^X*x{crrg<(YMtQ4f#)kKLRaKjLZq})8uWUMBOdYB*@KoNF4SLZnrWWfx zt!!_zHVQ?o(;%ew#MVjGMV(Q<&i4%m69Kg(PcULqO}~iEJV%<@8E~~%Udt*qijF% z6_E`$$Z@HmmF8t{v{{$^ygX*hb<5)0@cok$NJsw;QAo#6jH0J0>Ab!S70>gN;6#XX zRah*`@5OY=nZB*}`m zbVwrrLC|Got%Uy2kILHSz7b097WLGl%A_@&&W~7af10T&J#YWuBS{4p2+s?~we}cZThL2K6F>TERo&Gtd+c`gn z%J5^u$Zu(COj4syfkMO((UGKW$Dy$*17xxSEMHy`e`UH8_%1;|cC+Vg~CrJKXcqSjX5E;$zURe4S4SGe$x&~isuL9}K;qCSvWn9L(B zL#L}YF8vM9f;3`kX6ue;WDi?r20Kmc@QyxQ(X@-IoF+R}Eksz=5m7kDHaqgeFnz16 zwkzEV&3^!e4mAD;In>%h`|LlcG5p?$69;dGMJMrKf~IH@qgCi!*vm}g7Wf6~%g9Sc zHYw(_!^l6H`9$RD*qTgL-0o7?mC1y5J5rM_yQea3FRTav9VX53N3M($WQ4W{z3MCq z`{GJ^Tyc&u9)~b1X}PW#kuK#_(T-+U&HT}SABVI7LKdD1sjjRKRa)B(idUGV%CN~# zb1XZ5%edscE0r{j=gdd{4ZaLDb*lqiN0J_rP1l#AC zlGM4+q*@3&)bJLr#M>bt2$Qjzh3o5dXKM(i9d(jV+Ka z_2)67`jk#L#ZYRd0lr8?A4)sV*WUg)s?PZ$|L>M$Kj`+uWBw@YH=nt&^XST{-zF&H z2{Rl=)!ZGn&!$NXerHO3KbrU*>!mp*)QCrb20Vk!MB|x#Ll!qSlH7HDk6+^|ztP5+ zu&GM){8bWo{4Xttj)>4IG$#O&x)gCmXQWf(?%$y@5Pzz>=@`it0ym0|L+ zBh=fl!O@v^;J0`d<)AhSWsqLecjqkSagzz{*6Qr-x$U{TRYw|gHs1G|Xp@uiqwqbJ zuW|XYwA-Yb0g|?SnOW+7e;n##w)NK+f5hfb&8l1*^2SnHS6QFC^^qz5WHjXG@R6Otnhn0ac%{<#1hbzx3%F|ZvK{-Fof(rl zZUUvfnup{=Lf+xtifjv;Njgj{v#r=++!8ymmmeqOl=KB!ms%!K10Yyk_)%bLlUfqd z2ubqC5B1vGo1WXBIoh?|BKE00hVQH3?+GV1HAa7& zX-zrSvvTy!A&qOXma!AxcI{50ESYB@mD|R4J@0vY5kp7P;yUL`)!WP>9;qTWb#Wg) ze@jO;^PRnSl0E^s?AW3H4D>mVE2|SE5P6v{lp=qy(3#w`br()!_`A_x%vs zye=Xf{Hc)Ri8y-n=~>Dn?d@$OnR@m}Q!B&1j-_&*HBpeMe>=Hrx*%F`kS$o}b9U=P z6B;B(2wd9!G#`E#tWT{f%$Z}x)@tZ8pOXwCfLp^m2p(lZPs5yBt~7>{zD$y2KQ59s z=9VeEYV#icH9d0P2Rc?xocf#u@{WW0zl;j~jj5_C`A#UeG_4biJ~?wfH0ulpS$|~< zj-Q#pWG13VvGoaR3CS-4DXTtbCUq~I+x>9Of4OQ~cQ2<#J*U}3duBb*i)+`f6%>yt zmuKNr;iQ}_^{0@Q$~zX+I>AmgwM^7kvP$vHdWPKc+Q(P?c9<)z>TG*E3AuYB{z`{J zaoF~r;w?aR_NJ1dvWAYAmWPZ>0Rwt|FVj5+BvUCqdP`p7%g;1=G^9H(QgMvRiC~J z)&2QiPdI#m?%gXEG9=_YSUay%>s*#$x&7;6QOEE@%|mcbZ$x+4o33CL+r%_pL8*uF zVL&-nY)v5&9w$G61jew{O8Z025skY&B6pzX`RbQLFQMe2%7RGT^3lSSq~u%CkF4X( z#W!cxxG;u4Qy9K)mk_FjxEw{pwrj?M?lKpWk<71I#~$m=x_9%dRc+5oWjhNkMXd$v zG<+K}oL`UgzfK-|8nn%rs4}+a5n4$-eT72l0}s!=th9EGe5tO1Yl*XbZRSda2y?!Z zMP=7v6g#H$1zrGD7x3K|La`R$FOF&oH?a(ig!jo~GX-Hn`OoMoSLvHj6na9@08)(s zsGra#`tLEjo3-K#pUHARw#;)cqp(g#y9XY-$SFGr<*YwN8uo6ZJW-rZRm><(6vITCQ+7B=f(Xy7?fPimIj5K6Hb`tTH4aj;&qym{&^m^Ez-uNcIxebgo)6>NIu|5VIiH9e#$KI@2SUl+QNR6s(%Y z-VwTe32d8KwZ*vPCaw4U;{w&yESa@vg}N?k@tJvEyr75F8XF}lmz#KySY2oz!sRP1 z>F@t=C=&i&tq>r5zvShV{kt%i)#(+30UTJQOr5nd4h?R`)ijc1NgHv)tA(ZY&ysl? zE0&Do0Y{Nx?{GW}P4c6aI$TRuB?;dbRF1H-c^Hc(xiVcr>ny4id(5?d##Kxrh1A$< z@6+MyH_~;C`&jFBpgi|KqMwJMA~v84AT8;mHC)xM9X!Lkz>;{4GhX?5r=~g?T8u+t z@L~ni1tsBk&ZJ*dFFl@v9-Qf2Ncr&>e@z>Qe#V#H*2kSDmWahOoET>P4-_MPcyT@T2qMA{};W*pMNb8qU}3h2n$VY z^u^H57TUV?!~M~SMiL`ngdYu*iVo<>Gwc4JVOWvh)pF9*@->gbFR+)!`mTyYya_&> zgANv_LzsquILgMiDN*EdlA`ymPC)tl0F##?Ev5S-ZPC8Z?uO+*W=nS>DML36-ZGxw zcYk+*k!zVAk|t*tW`Jxyh158c1k7Fj684@;^ETznt`JS*QvH&Sq2m0)^ik&63nW)? zXD`Z^>jR2yVb@y=;(D}nnt z(IK08v!ET9&GDY@LYnN99kI+6Y;Zo(Lc1X7K;>t9INoPFS}(!ALuUoqQC6yIMX54d z*3^a((pm5P1(miZXcv~4h>Z<1+gf~(b698{ynNQH@CGex1TC9f0J+k8ZWpVoCDBiy zL-NHnA0UQpAA3Xt#s#4X*i%h#mHgZ@Q}ztY>9M!xQ-Hd)kWr8hNdNR?~Rc>2`F(Xz?U>zg5AVtloB#`aw4g^xYH;jGpJ ztBG!iafe9pz?hKPl%7ba5q2Ba3w(0SkY?UC|>&}HPge$woT=jRl zx_2Sy4%c*S(NEbsj$k(<^ixf>XSLDSM3K2O&=hOb1210U(#Q*g)-+wcj}qV4X*c#^ z8=(OeKa~o;SEeV0dM;GkJNyFpz~VTWv=W;CxcC_tUmErhjIB7G_>)W|9?0bJ@6L20 zmQQ^aKG!%#IjJI!PIOs)jRP@9LVf-Nl$)8>^D|Fx1YaG=(zK<|btduGg#N5mc}D+n3R0w%@P_7W=Q>TTh_wFst7+_o=-Kx)@36H)_R@k! zx}&KSEXr%U;>^^UVVPn1U96=R+PKE`O#UlL^rcn2%QWJ|0K$9hV=BrToZ-n^bCwZ! zzm7oKnJgvBM02e}0PVR((MXyQM}|;V&R$jRwzj53`)I+zX1bcba$M!ExVPK~9l1jz z?2+*S%s6}FdUKT7r$?NosdV^M=P1>XVhqm0b*5wwKntoN;h0_^lxdqas~2!ge) z+6GBO+#4#kBmM!wo#Rl^%^!511_9GuBP9c9KJ z3X~W+tAtk%nQAc=C<@&FsZFKxE+2BlhlDr_)la*0Dhd|Jt7UjG76UyT2cGn?=jgl` zMqp4fPC4!~3G@KGNDHl-35R3BBZYUprKDECmf6C$_6j7sw$@y!HOdss+%rSJpA?a* zUpj=hC#+;?tU(Ke;zl|EpA(&m?YcM*)%B7xkZkFue2~ufi1)&LLiR_7#A}PG8*5); zQ`t6I!|3?E+l=Ip5~~D`3WGdqX1nHcXH^XyJU^}G<-}xI^t*)WrT8zA# zS+wCVV`!G50hBa;)>0e?=m%Q2l~{WAI2pe4iB?FT=wUM#wglJueIDIPE> zse8euw@e_?U57*Sc0)Yem1d%j|06K1@Ik+eLPFFzVt##p?%XDg8y5#FYh4}*;;OR9 z?KHA4kke0z8WDvdtS1L$(RRxAviCE1PhDhDgyY$(=S#5aY-tW)o08zP6q4T<5I}@0auPfI=K4-@2HugAh22fFBDJk5| zr1TzuLiVh=*n?8JkhqoN7*>jyvU4QoFbJSXdgr+i&j_$aA#>O%C;tynKzyX&vv%Dz(+U{#9z5%U_f+Y3})%;jz~`>8a^A-WB#No6%IZFcR=)p$Il%c zE9j#4FlCZHQ$LKcg-Yt~wsgp#9xl;)^wKxXRLlt*M#TojYR|CaX zGc3f@P_KH7s`x`Lh_w0f1KD_ctV|J2q(vjd1PHsVz~{0#pf4A-evA_k!6+>EQe0m< z)HlLaYB{@zvok*NQ3{NQ5s2miU8`)E*mh!;#< zOgKAnx^CQaSKTfpm8TTu=Im6cIg*VPz#v~>J91OVs=kJ-iOQe-j#ySH&coB>Ax6YL ztV6~k`B(A9q;-izYFL#ny1PAcQe~&1Y9-CtTn-y~m7}vte=FrlTB0?(=|_t@@Lgn4 zQJfk~FokqQg((D2gV4Lyz&X|F{y*&IN;=$abmx$*6T`enP{V)~ z)(KFOJI(Bf&(s!EtXNOWIudkofqpUFu#efgGxTXj%y$x(xtyQn`IbdCcp8z!nk4@Q zT@2}8;TH;@G*o|0@D2DyRpQjM4t1C!DO> zn)<6|TpNT}qYIpPE8pvC^S)N9oCT%F_xCi#;~82HzuS7&A^8083G3&d$`na~@;je< z+Ymp^e?IPeS zMgTVp=uv08+|9X4^>j}&0l1CT|&h5ZfvUCCiO5dN3y%yU9aZ?X-75#Jl5 zF*X%3T=i{auhn(e0m}kY|GMrm{n+71N&JILU{^pGt~8VNYkKlbh8ZlPg(#3w-<>yRiv@1!1v`2*oWE}GS9N|v`g)}PR2yjWoK$*OV^rr)x70JnVl7U? zl2r~{J2=shzfr>UOxIg}=fDwkz>~KyNcEXSPTq6S zFNlWGy+WeQqH#n0d47nJOQA|A4duY}mhlIVN+MO*drQ;P?xd>IWsY6=cQ5w@BeM4^ zn;Vr$jlOPNgFc)aw&{C;6bIRR##O68h@u*f{5zGw8ikh>}4*sHXN zK&bG&D;~QE5x{*k2Q=T7bE(#V(bP(cog$8{c_fr3mVcw-!5Ux(uOc4dc0Ximj}vNo zM>O7C-w%sHNjpWwa2~Tj=PuMVvE+Q1SaPKC-u5>&sx}LI6-}?pq%GpT{i{=RSw@ir zBb|H*Fl6pIYlyV5SQIY)G3wT$V2#5G-X$bawbRR=&x>HA(@O9{W!L*rSIUiD$`+q> z^qz0h*)8jFt^b1a+^O|D;?fAw^9Xmu<3j8^YtsBHo!dDy)@w|#-)WEyl9a#t1sTL$ z>d~UMqa9$gDC{3JhcwNs zeK!4Tn1P!#udEkWGVeysW@o#4BY1EQ@f}jS7ko4yc*?X!@bdDvxJkq;xG`NXvV}&9 zZ(!G7)Qi?e;_&UH13M;N;PjL~3f}oOH~+N3!yaV+3xP(_!gWTZ8ts#z{ScIe^qDZE z#Azk9k9fT(p=NWMAnrj*+(*rEJb1g}e--=x8}t-~b9*6D9WnWCP|;nrW4jSWMDHR# zoqsC-fYaLj8{vJOC!t7w^+B7`JiG6w2fKr)9r-L@-!(+!zFk}62vcSbQefspfJTJK z%Uw)L&_O_b_#7BvOP`N^r_IJ$`LA_9cHxm)f+x-WI0t#m4X;o&5qW`g{xpEv_hb6- zk%9-Rz;?EXWe|wxTo@f~QalRfWAB;jA#%`67b61!MN4fk~_6A|beP*R48RXjv>!dnRHZHw$5 zn72PnDeEs8dHII7zpQwx2_Ir%uU10!e6OF_c0Ci#*OD#FT)ZRKg;Td)3?b~ks{N#U zo?Yix)U^Cl*+bdyZ(k*VRlTIU5p`2}^YW;)b%^0=u zAU@S!%t$)+GX`vyF7S8MLUmpGYAdFfP<4%_y3XKPIYm4lKYbh<<#o;(3QD~8ULu9xW*epGM_Wbs}pO9D=!{H7|0g|clmwtuPt8pRb1UOb8h4X2VjYD0H z*;#T5^si@dGWY$Ddg!MDfeZ_NRJ}AMy*v*K0*Sp%jtTD)Dp>RFA|Dj5asVFc)=alX}PNn_!R&RGw`xwkt`PF7CEn&sB?X zQgxa7XxyK$Z`>0^@DgP`*-=0ClNt02b}huv;Ndwk$RMyai%N4hDBuY{)mP!jI|Yay zVWPr81SsW{c=@}tiaeFTjLkfrUWuUDHmh_;yqHQJgg^hXt~IOVi3KHHkQm9NiL0j9 zR$wISj1*2EDXoFQ>G9Bpqs6ohMSoJ;?Q}Qv>riatV|gXLEXmmS`%!4aI#Gk8j6GYdbR~${qZVVSncU3Ic029JTTDb8~~lbs^HJz=)L}T0?TivFEx)`S{nmUFYHu9a{pFZ)FoW`+A2yev8bW7WCpwKgb5a z`Bu#=rzIV;)_nEUzanwq7;rB&k`rAwOYp=xPvek5kv8jGLht*@(aNhzSe5*{)-(w| zrm*zgua#F6N%p6O%#M$%!_&GZqr$|UdPQ#2B{?o#F?*R<4G>ci%)%&(6GtAQ0?1yN z*w>mNSu>GAHMLOm#pa&qr!{r#d*5@CcHYF&`>e@JKDFI-K+S#dx8)#p3Za8bqXPIB z##ru(zy)<=p;%y~gIJNx8)7-zy3DfKwnIglsb)^$7Z8I+sL#h=4MuilpP2>ftr#q_ zu{n`Yo|m8Ys}09U`MSvbW4sJ-Q=`N(D@$C`&|G&lu4*rmjKLp$M}m3EfIg3_$yy=k z)7H?NYr}dmE7U_2rM+$`i3PWud*<8clCF#`OF6))z@W#|p*Cdwd;$T5;u2ds#=n|= zceDnA7ymwoL4dN6MV*kU>+shcEWI!@P+VzCN(>LH^KnICC;nmwei|w~Z@B*7a%@W| zo}r3?p(HH~X^>VZy$3)V`c!oxZz*wK1n|D&L6{KFB!wpdn+h!zCB$z1i;#|FiMD}r z|M!f77#(bt>aX{n?;BfX1Jssl=R4}fkr9}|#aspxaNW5BHdxRe>wXz;zZA1hhrx?R z%by->Uop2Y^0?o1l@TZlKCo#TYC;dbHhU4AI-dUrPGqawF-+6S&X0r;!= z=J;};vETK%h0vV0qq=C`o|_eVD<+dPVB=Ni{87Ky&t@TuAZw8}X-wTsj!)cV;X62n zbOso)3Vj!P7e&wx`&1dB$`lNr6PzOU5W%K}NF+J$)IqgMkAwKIPx@EYk_Qkvq)shh z#@ds&6gcom0!A^ct!!OR<`^B0xh`>cTZWRTfyIMC%oCb9$8i!3h(CByuQYy*T$9Er zYvN!Th3F8LooRHKT15x-oJm$n)wz7zN82XPY$lxJzie)b zUSFX*$xhJw_B38)EdsK_2))|^e$`wRf9#9w?}VHbqlHxtep7Nbkz@t^q^Y;X?MCZy z$^87)W{hL?CYCYg(pn!<r$+1QLCm;7;n@X80HCS2m@-Q{*4q97iPw#l zLOU<^Y?b^)?`&vg>A+;e)||IYs(yJN^h4mi^d~VW6e|*n0vNu6fd|q;l{!!&vIW~a zsORK@dtKkRhEQN1& z^jFpQ)6YUpLhjY+#SZXt@6k%wHDpIPi=PlvR4rHsv=|$C#R~!IQFPAS4!<2ID-F-_ zDxb+o%?|s8VpzPCl*oW}kp2-Qg5PUJmOWOynMUrm#tza3k|K&LIo{VUavU{X;m;~; zc}G5?-JL`-Sp74+gxI{5`_ zNKBmXlv_EV@SGM7rRI-yDtfW4QXsg(Z3yqUE__D0Y zenHw7kWa+SeFBZmeQ94iPRURS3>UkfJ5{XzX`tn>!H9ggWD#-b%yvBD((*_d*|JEW z(u8b|d;V9@#T!*IF^HtUjHYIamHePi z9N8v;u3y8*cg_0pUDx$|w(n0@zE;w)S-M}yh5pWUD!dZ-x(U`Sw zj#T?o6|2Vw*lnV(f~X??fg!YWx#7IdQn~DjUw;F_MsDAa=%VFcPGCZg?9cLZGX3(_{+k>`*AUp;tAed@D0uMnY6L~pHNN` zZl6<>_@Ll$Bnx0T*aT;625BxL{j!w960ATtljq6kzCT(7NdUazL7VC;ZRifPtPAXoB8P{PJ{!pePN(AJAM7f9xWK3q<--Rdjm;u+l z@U<4N56tl_QJ;<4aP_%rBmF@xMGZi7U(w=VfK!|sggZx^sdRwS!%m1lK4uNC*m;P# zd2UnyJVG1+ghy&8i5?D1$bA0R0;NPE*h*trl~qV5vcCshD&2%<7+ru%C)&7=GYIMV z-YP5~j+AEwOZ|j248Ea20MuM1S#A-K{~vX485P&kwTm{c!JWne!6CRi!QDN<-5Yla zB#@?Yf;++8-GaLWcXyYdIlcEQ=ZyQ~d%t_f9p~J;dacnvs#aCaS+c5T)qEZ#J1_)+ z&*6M#I$7DrEgKFGU_Bu;?fB4M1cTC@PfSNwSkCkU%A8%>^zqfeW%HBFP0PDmR!b54 z`Bn5A8S;IFu@Z>%|KheZ1)?eloH^=AH(+da}&VzR|(>iril0QI< zBv=ZJ=um@#y+(n!?IN!faUNNI4)Ozy-L}%WubLh>ggNyueoJdpRs2A0moLJHZmG&fVTn7gE7Y&>;3@H`Xg!D9Zu(mf%K1a&n{9V zMNzQyulN8>a5H&gdg-SRW$K};t+9Ph@Qeg}zr+pz#Q=0?fk#wC2Ay+sAj#0ty#-`J zY(g{!+1S$jL*KJrsrYN{ab2jlAwXT72qJ9ufU|@#keIxTM(W z7=Ol!arN@yAHZBWX&|O;EtWnNPUe}*qkFlp3d%Aq`H=>br_^R(e~Fd}dE*VvkHule z>P>Oca{X^go#}8ubkO8B#dvEGGxKQ|*3Yl(5F~EFx4~DDLtBA~1V#5&SdSK0FdYma z$T0E?V_DKwg<_1T2&E4O_acn$JJ80UBexUy6mROk}16x{|iHz^8y*e zg>#t-Q*i9>nRhNUE6ymejYsB>h#RPlIj>p=Y&-|lCWDNahlbKQ?!*-44Pr@`nHY$B z_O7F=c2!UyooG^%h1`%p)cv1sJbAS&+e3DK!}~`8l6O`uz*9h@wq$2AiH}jpR^%J*<~&GfT*jVzN`B- z8`OtHCOAnI$oMJwCZA}q@YXO8A0X{@uKIDwzvP`(Oy65Jw^(n=NQL8u`e-Ix1v-mQ z$9i!`sbE>UOwqSO8-9h+{psG7UZo2u+ztu~xyh&dQI0}0A_@`G($`QRam$|B#l=2H zWks_%KKMC2>8Mrl#T?@x|9(Z8>qg>|7eFW0*$-w_GZ!*UJgBkYzEBdIKb z<-M*~`aggOTVb9f(r?Snru;bY_Kz;pU%AutJ`KeXbFm;u{8-v$!429L5JCSPmtWvOWGut7SSfVCcu7 zbi=jN1NRG1gMg79{M&`)0N`z&(3I?YIUMJin}OX(o{Kw8T;WA;D=On|dqI1hFUywh zd&Ennkvd@e;Ewlzeqf%I&gw-aBuE$wLRB}P`dMm}o z%q{g8>_qhwxh{YvFW`&NKKHV}vOfvV+~V1pI^NNa`IA-Z`3FlH-ECr2*x;Ko6|}=; z&9&hsmY?1?XLh;N#OH>;3cPif((S_;8>U)Am5D!Q9Kz6s{63!4mJZoQ{9YJPmln?X zsQL!Lx&jfHk=StG%M?D$z{CTbGzQQEFEbHX&qNE})1@1$!eq|o5fAvN`XA=IVhBvb z&?M30wUuEbfi(Mzna^;BS64B~K~W{b564<(Kdd@U-nD%v{|JvAe6>Ft-zVw9+Ayzb z5DFAASusjd zlWUfvkCx!Foc(+CCBbmE7Q#<#=H?!ylj4E2Pn?voCNX~F0T!yRWlZW<(`Zd?%`?sv zZV+Ps?s7ApM$d@!!yk1roGt=}VLD3R**Bmwb)>*@*5ZIa0Gkk53iBw$Axsx-ZWiLx zoF~OM`#;lb_#S3`Lf2}UO%+zPS9nJYlP0pu%LyJ@iDWWFbY|yfZhRxkf30aak(!rO zuJfPi7aEaxgqb8AGk)Go46sC+Odf6J_ zudp(>n$c=jBXYSaG0%!Q1zygza*)yqVH;4x1tN4tM6SMTCc`em(v{! zCLI;8V`E0dyadksgI#9G!kl@Q!PVhIBQQbEK9Iz9GoX+5^??;2Hg_`a>~{ z|AfSzc41&ukU1kF@e#P{j0JEUV!RL<(~c^?F#F8w`39r;nB$+#k-kg9Bk zmrpkHpC0Od#1*7&Ho1pP27Kmrs+8aP=@&@t^9i}ljIWQ^W(}o)C3rf>6|_{vMG-73 zc9Z8>K;;MF&r`+No>W~0O~><-G3XHO1w%9-nET4(40)*-^h8+fan4Bkg|x-Qf-I)K zg+L3WLCn_F`nNPw$>|oPMYnmTx$t7xgh6@PIg7|6zU?5FtCKBM{@p8^Z>+Nvq#;JUM9JcfP&s8m?t6^h?nvvOQyGKO;$#Ovh$y41NhHx{cGki9 zAkI{ei*z1B@-~f`>`=>qxo_l34j)`ls0U#>Z-*2b68ad@BlP(#;NM{VUB zi#Whziy!GytH1v|4eX=o>G92br{N{;!)D@_s~oxZDfOrZE}+Mu5;H8sMOh=WQ*lzF zx}OgZi|cwi0}NKb&szzg4Ttl>L%+?wlm*2OAjb43G5Xsat&Z_RXZvAXJH}A9Z`q%5 zY+iUcN_3Q_Aue+<80ehfnQkD={9NAP5yQx_qzBCF&5~qR?^19diyV>}2v$3c4umDc^`KdmMaT#>;i@fKIjc+8TrW#?fw& zG2}!N;pBXozM`Ar1*RnP=xM6QpCHsVMeVRN!S=>SRuQpn*K5{`=?-@?2G?-^0hqgs zMiE{Sv3&f%=a_Oclq1XyJJZr%kbbH*M3;@9yi_(?>5gta_thhu<+`yLHakmMj|E{? zoog!3@&h31t)KWMDTP-036%kJxfdFAl&`5DtkL0=QPCBJN1dq-FPF|KmoqcU2n z!)hfvcY)Qw$`};gXedO(ZHa-MY=wii)~Gt6m7GrK(77Ue!C56etA`{>aMkUEs%*KY zSm|dXR5qmS+@8ROT}s?cf5fPg2#>9i938{OP<;*{NlITBhYsMLY2;=-TQp86IUW zu(Dm-=`$boOihzfW#CQ2nbO}^P%ll^_QU<*D zQP(LL(nv^TOq^;q`d2bp?^d;HkC2JD+v#Z*F$&p6wB9fqU!l=I4_4fOp%OMJ;N%{? zS*f^10(w?l+;Y1MV32+Idw7JNrbks4C4stRv-gY2tIT~r7C%;mQ6l3E&N>u(E*b+M zRSNqb?9Y@_<+TGS#*?7uKK`R;*c!9+q66ioxH;Vl6sAOepRkic(&&BUVq2+O*K%{3 z$nQQ-Fb2$k#$3bSXlVk0sApHSlo)zq&ryzB)qYGUJaKG9%+Il}gB05aUVKo4QNNSI zCN2U5iTviysFUGzMMIo&HjKss-inbCKyARsdFXI1B?5zuLm<_8wU1R>Yxv$LsyqdaiiPUn zD;j%Ad6BI;xYug8(2Nr*!1~)t+0PAP#AHu1Bn$?H$I25`Ge*NAjxD{E%Y_X@Gl6y7 zzJ4jE1e!-5sb3g3LtO54GWWSg2Qe@}(@E`SE{nn`Vs<(tpR+8gK2cq!(8QNSOW~F0 z>r?!Gh_fj;>l=<9PAk^sYHF!RaGZ>4^;$hn{RI&c3s<{NB>j?eu4OgcjNkfA>XMVj zx}n5VhO0C)*r0$J_v7Jr_5tP}zIu>fW+QOt`bwl$sDkM2%X_dejV*8Y_v6QN5gw7l*UUi8NfeusEE@wiYJ?&*%jJ{n*PLoEz?X-Ort8j z0<#t~WDmjL0lrM~P3+6H;CM2@A`v5&E>2W6Z?AZW%8NKQ{~+Gf9!PGx;3xBEfG?4Q zzY_G^gUH=sSlTio^2;mIVWEe<&TJ23qQn|4vCm%KTnJ1+~v%q9RQD;1_|aAT&KFk zj?f^wPpO3_BH8!o;|0mPx_nDt^Cs`hVeg|Dqu=3RcR$oDkO`NNDNp+jstbC<8AReV;2Z64oK_NMShqX@pX*UU$syLJvB^FNNQ8ZG5s`ci{6r84?U35P{0qNbja9F zcOA)?s($t^-oDn}rPD-~-!ew#cd~W-+&6*UXl!t5M37y`Qd0b}H81kxhFEOd>8OKT_k0Vtu(W%d5skT8(H{V9_g!Rjc z)VmZ|`QkFH3E;Kyaycsr>5cFM$baRO+SZ8m^v^oBCpgPoVCqK>NJIHZJX6rB%xTO3 zf%@+4pGS(u0y#?sfgcb9)PB!6`;9Fm(wTARG*m@Tv^ohKK0Gwj{L;G>EUrd3J9ow= zj5i=m1?qqvCOAWsb&V;W_NQ~}C(oGA)VJ1-?dU0YIp=@ymxApp?m7L&(w_))j5ofJ zdAj3M%J$?%z|irlvxBu)*MjafQkFIt>>bg_Ib;IcKajvPF#GP~e6~*>B%+=v|0M33 z++);g;Gyd|CS1U=PXlz76ME&IQE~W@K!4dsObtFI5{|YlbSj|XU{Y|c$u&+`$`eUB zmI@mL_tooB;-GzvVH|y9XI`ykNSY` z&@i)>W2AWapa9>6c=f2wMN9d9){((pypZ-i)dYpWl{e8ufhSVA^P|sM$7fc~cAqPO zd1s%Q;uate)cL9wx=$AgU=XRRgJORv-*n8X!Ysw}7o5t8A+WrAZwp|cANy|sd+{mw zp=YIKu9jwg^vQ(!vp%+$=3gpGoo*Nlu){y1TQ;{>g2Q$$n7sVxhYgY_XWu($#jRO> zaFiwL_penZ=`X?vn0}x}D$v{)!Zc9`-!TiGm_JlW^g!#_jXqV9p}4%J_4VsduoZ~* zO%XlM8`HLMf$$5ZYOx$XOpTEDww@_+3xB{(OM!BQO}*q4neXix-uGr*mTX{&3qQii zivwRJ=f1HH>iCb)sdr8g;$Vn&1olUtqFsmX4`Vt(=|Q5jL|%NdE((qBf#gaAMlnYB zbauxx=@Yb=NPGMy4>v_id)qWAq(>+(jBGy831&9l=1ie-=Kfq3<4MSQ(|O$rc^uE# zb2^ONU0NuZ{cbM>N+Wa41)3l}3+#btEh4PDs?@Ext*8_V+rVF6DP)1Nc9r0F$Pv%B zxzdz52s?*Gi}St@L8FPYboZkSzR(jAGKG|Zp1DTZhO;KGY-CLV1D|G=ph6gqx-@4% zwoc^s`G;uZ$Fo|EqX#uaoD^~Am%y=^n2mn`A0b)`tWVLnN}2hWvGLCbxT64KVV`(K z{sZr`h1Ue*%S@E#pxiZCPqWc$EBJKJK4C{I$wb$(w$RK(totDB+p)i^hhks8&^4Vr zlx!eC##ZSR9E91ykl}+)-sYpe9pts2eN*^XX(IB8DBsV*1izkHUw?|N!|V#EC!~Xi zJIHJBKO8_+20?zG+32Bt+#a0>LLnAeA5T!C+%d+T+`fI(G~g3iflfz@2jb@&Ra6St zS6ot+76(OMpj0wRED~q;BHjq8U2y`;_h*KI#aR8(B{M-`-OhCa=gNdQE)H zV0iO0|4bMP_V9bw9Ow(_*h}zNcDaT&A9?1skZthQliFPb%qsLtGd1gh)L1|(xL@hA zpynjj%)3ncrdfIBbprbF$~`g$AVJ{;asfq1MEno_#VSDX?Ti0G%@taWN}X&T-{FhR zvA5e7_CeLGvp7+Kpfi5D7|{$;WzHF3Dsx*<`VnDSuUmzRcoP49rH@V1_e|eeU4EkT z=VI>xWkL0tEBHl@HA4nIKSUxvI*PLyf-S8bp51ox;=S*nB7s#G4p4IA4wM99MwR zx*HaU$|{r;O6yZHcq4xPSh;(UXrg%FP*P!#RbNT@0joc*VP-&MdGapjR`A~VO5Qcp zHpb<-q?9QLgWoZk41rWZnR#xmATjl;H_(Uz&=r)e(uws_<5 z5mf@y5K5K-t$^xt7tT1NZ{#Nf{Lcs1X<2E7bjOE^%$s7}{Q;^nz-fC*AgoIti8qYN zd)7P&>_wQJQ&aW_P1h0T&#iGMzHvT}sn$`!-SGD!H?6uGz#VY)sdT0!%i+lG5gUSk zSIhHACTOnZar>q|VQo24d(hd(oPOdXQ&m}sqv1csZz#oUHf$)=w#B4HBuB+RSu*?zdp9 zx_WqQbmQndncu1o4$-=*FPfTl8m(spA4JXSM6Y^8pEd%+C`@ai(9KYD^E%oqJ~v*) zQ`lFfjGSY5D~gl%zt!#pl|@}B$V8-|JtsAi6&e`OXY~glMEwWQLHJD1<6%|y?^liP z(cV&2>-_;tApZfJhe6*E`CsBP+R{UFrhLNBdcKH${r%}8?}R|~`kYWW;IFhZBR&0l z`U0zBQ^B> z>*c}F6Yaojs7%oK(bal^6K#HhRh^p}Q}X9cdRQ-!6NQV=gD%iQGl=wB`~iquUeE7* zx+p#<^ju;78Zcn-q}-FEat+g=>F%xzjZ-hSC90TsQVV0NkQ(n zj!ko^I|al2q3G8KicMMjvWe%azg1dXl>4`^kQx3f>IrC4c;UmNw;`N=0D!+l{X6g* z6Z;GwaRX~p)Bn|by6hk$B#2KLyyw3upjpnsJ2MDk8S&K$ z5+mYzq;EyT{^sj0m_!2)#(`p})f!wr)*JZ z^=L)fSr3^s;e)_d6szTE#Al9FxGE%loe5er=|1T*E;fcbI@Oku^!aSLv5gLb?5zzx z0g2x{D?aKQ`e!O=T6Iy|#UTqnYN$Dx@R~VBj`F5X8J35DW;mjE)#OTTrySRRq`y}$ z(+x0&S{Jfgc<4)fKIXQ3enUGMM~&|_Am}eJJFg<-T-)`Hx@buHohwq#-Z#O8_XR^m zWf`1$X{sr?SoyxvTMD=_7<{nxMK*<&C@b_OOJNH&b#eQSTgg4~;5ZkxL;WvzpWiU3 zup~w%jt6|<(p!3gJ-TC^lCrY(iCPVL=#Y?u;d|2*f^s6w@XjA=xjIiurzZ8x`tx^o zX2Br(pg8FK%@@K5$tztuJb_Ot8GKlL+uS{1Hh`GLKzyr;unuVxX$1Z@dol8b4$+s`KxzS-GrB~|``cGUkvmi_a$40Gn8QDpTykIQJbbMh)n7)ULb1A$jGHV(xT6kJU2qVA%fTn z49Qe%C3T^>)m@@r$C%if-{D7LG`e$7))AD{>=YvmF6qQ)G9D`w7tx608e73i#s%V+ zI9nU`4KtBG`ERCod)u2Di#;fx6;?Ktd+o@X>0zBo6KkBx_2v9XKB6ysDz}_l`$Pq& z#C8;WMR;Vb=49+ZEfLxE{U@(HUjxbn`f_J%pMLdxc9RLPcPJ*EVO;Z7IEq(U&d_l1 zS8ilp@yeRM%zh8GS3=ZL*V+DLsCwdR`0BS=ZOiYz{(ky8*!ie^9zhCmf>t88g&*RT zM{!%4)c&e8tv3>{zQ~`)zaEV>hIBzV1T|ayXXj&3*4S})SBPn11k-1BJ(?=ZziFyT zQHA{%T^vO&^{+mhpeBYA2~8O~4*$0vm@%({S{GPyF~cAqSJ|Cc{K@X0*nv%*gF3=n zj!ths_CvqIIV1-;MB(fQk)q=IDjU(G$8(}c=#fphIW9B*%Ql0v=%NPrwLZyh%ZbcxwGIa3dD%U@>~N}wutl=nWC zeX7p#xWi%hr+`D_7z3gX{07XVWwDpz_E4Lh$EGC_vFOjl;(W)Zrq)?q0Bs;E`?|fK zwsuKKuP*LDjBZ&TbV=ofv07Z7F4T!F5+{&4K0DadRA05+I_kole)Hk@;n(j~3#nLs z9hmP2-uPLjoz}CC=d}r+ZoJyrI=x=qv5%hKLhZ}T**9ercx-(&uCBV$gyRT`dj)K`&rpWn^uTsZNO7$8rGqA@wP(<%1 z)bzOuN<6OK@sg1UuzVb$Z=kLGh(L7Mk(iciY5$L9py--Tf4-qXcD&WmDxbuu;C zKTQ$9j>-?9Zg(T_tokUm*Q4?z?-LEB%A+Z}Be7|{rTcqZVE=Yx(W+I61NSN}SJBhd z_k}6D!ia_Ddb1-Y$f9LwC@bsy*NlGSj$-EE!D)GJLog-S1S`O5b`}zOq}F9Yji4gI zU|rPOK3x6HEN{fFq;0s*Sp16OoC&k_w`X|?{f}ke2qu_{K=tc-gJPEQ-z(r(c`GtkKJV=>L1>( zTGR9I%Lqw2)B^nab+X#L2FO!#Gfy-Y4lj`K5pL8JY&b@O(oksbD9tH)vb}GuXL0R- z<uWyAAh)&K=ZNTR{#hJ^?`oszFf%+3b{+Xy-Vifj<4i9RFPkvdZK-? z7SIDFTh09Pz+$JZ52IBBOGRr}ThgcSj188&q24e}5|G0jG%OWXU-3fvuV4TK1CdK)%m9on9M-W99*yD=rNR9NZ1 zd1**f8G9`n+(a)%Y#N|OoriuI1uFrz`P{K_Zl-SI;m3WR*NTV-A$`NLqO zp$YNdxH<%7Gq)e=ORN{`8x;(^HozvTFz1>3*CphXbx%?;&aJ5gD>6@Xq528)Q8dyJ z@n)cdv2UnqMn?Z!68p`I!thka0)emDZV=!28ScYe;%7uO**udkXy0#F);R-yRM~Sq zUm$7dhWI;?_KAI4;V2Kme`LQwT#h$t{|mWqc0oGAf~I0e$Y8dIu54Re0QHSYS;chr!KYP2=r`smHv1+SKTI3 zvwWn!{0D%njV|yq(|uQZWrs=a&7*xK$M96vYx|U%COQqT+**mV=d`4r9%V919Jth>(WFDdJ6Gj>H=9kUyx?hidlbz?GEcN z%Q?SbISV+_Wfyi$3&~Db3?Y-wfF9KShtYVe#AYRpQ-y0VyJHmyg)*K71O_Do~#~7b-T_ z+jPEHcf4*$H@_1}?9`(D70X^b+dEsQt3ItaCr2(jPwndnJ!D#+vr2sO(-Fi>LG3RZ zUa5SU&pHhr2qAJG6fRAf$#1G{#bs6}B4ylQ;Q!d>8%fwska=wEBd2>*nlc+KFeQZD zL6y0EHZOxW#BmnQ2Ly(+LGfQoQ#&9>Mv5y1bsZmXmlG%(Mgzm}?oC9#D2t8TWweaP zmqkoEgg6>htd{o%w9P%^mR&}`)_}sLi7cp#xbbfjc&i><&9U!+{<~axOoGDUbEyO{ z@UR$A`qG=c>6&tqKLBNm`b?fCs2M&9B#nkoafod6NhkfX z3)+;T%_5iE>kLM88lAQ%7;a1^=LnLl8r$Qp47}kv5Jw#7U?8AAx9kw8^UB*2hjno& zo8k%Uc?TQ81;b4kdrYbLAEG1&cD$O?Qd3VKh!k=lpr9_Fr$~=1PcMU$`xt>?K3s=) zq;S8}epn}!ro6GqG5ARU-eM}~x*DGsNfBkDFt+W_C}A0Yo5sXHKG%<9hPoocgc>xQR|-;h(*gF0b<4{%0NFMkLg zQzBa{4S0GReG&`TAg97CEO9m<8m2;hRwiQds=sC@t!TlVz#visOb;L~CFy()WffgL zzr`1>uk)DvYC1j@C$}3xM-#7kTiml$Uy~;k4`zj-G!7m5k+F1zribliZ#wj{Bl?Is zR}V^c*j)uXA=i7u2=m=dPqpQol2|S7#~(7o)>a z#()NonBLXf-GNW5VsMbPiG}XA9X`&OdfA&m#3Ojl;$QXzA^t~(_Td?$>$tS0!4m*Z z^gD#0fvfU2bQuEc^Ok`cnzv*3*adCuvS;N@RT4TmWPv0O^>;Z=PCzE(Gb{)AIF!fB zKJ5$HjC}iPL3nA@F}Ve$VgUi|;-89=R3e|el!6=YA7*8%D*gJrIu%cNlF<|A}5^`G3! z|2G+UH2!H@Vfb$;E#WVm<%B|Lb*N86F~6o&8La=OZRTG#n*X2tL)#h`6wT8#I2E95 zT?l1fl(xDsdgZZ=gXW`EV083t4P8t9M0|PLJ{s4cL0g?bXSlGXA=mihG5s(oO z;NcNak&qCP(NWRS(NNLQ-eBNhy}`i7KtsbKz{1AG!^g)*$0Q^oz$3!J!^it85g0h= zJMai72nZ;6Z_wW0{olU+bOErD0T+OBIGDEpSZo+LY?wbk0b~FG3_KjPwEw=40Pu*& zuy6=4D9~VaEC4JF92_hhA}k_25;__T00tH+0X6`U3WpsDR~#Adt(u7o6ySqk!e0vd_XQ746af){ zgbWSk!v?@W^Fu&Df`dc&I|d8@nk6<46*~g1xS9#4OK|K3BHmk$oa%0Bi79-VO)g1O z*ULXk0CYHLOl&x8fGB`ho$B80h*^$!#B)ZW&(PzDu_T3JB$5bRG4`M(Mw7L{J^AtN zr;pq)mq{ewzI>^_TeA6y|6T8GqYOJA_CpOLCq=fzz`dQoT!Yw~0 zk1aD=N)Ar1RAp7%*++JR0;>prAB1ih2=mrD%z_aK5a6SsbFs%;)&p3WeG764w5J{UX-shYY#KS=g_#Fx@lNB-vKc*HX*Gr4+#FpIbC4%2Miq7r+tONJyRwp9*E?WiFWd^mq zM9gk=uf89T_H;{rxXXA#WMWe;PKPY{yLX=JE^*mSL~7)s=9;90+XD($W<~C0X*KoC zgQs(8Bc`SzYZ8r-DMO2fz4{vT<7$e3ltrdO+F24gmBhL1-bnJmvNQFg0OGrj47aJ( z$sUIXorjz-~ zNzS9BU1E*u%bIyqaX$9f&fl3PA2ZXR`Nug20=NY3yw>pzwpy;ayUi8)TGquKnwTmu z+oaygyu$A!Jzp>%h!Vt4#vVpwSVAbdjEv0HR&7;Rg&Qi z269{L(G2(ah#Yotrb3@+yILDwjgS*zj{+fD7+g8$thB&);oDQ5n>uYH>YR{*(VE%; z+&UZ@4FRiivxFiUY9uPT=khB7fR|PCvBkyXiyCQFi~U%khP_j|7Pv$Z@CE{X><{iT22yihvcNRtr=Ua z_iWy#<9`f~&a33NAnUZY`+3RfDA#1iTyDUn!>Bi%X`UKqJJb;xw z{Q!=;ztqwz-W6gMd(t|0w?(O}EwE%zB!P$olLvT_qhOKaH7&b;Z(VaD6EEcy$IOG; zqV`=vkT|_)AGxoi z)o6eHi>4OO{FtU$3T~T{6G-rJb;3b-oirB{v(4pMuh=3rWU(ewJ3mi+R#lt6Cq#^l zI&(Y7Z3@@PF=DybF89ML?P;oyFJ4vz^VB*~L5seZd4eXn2$q^GQsw|s=#@b1FHIR4 zCGHR7Z$oHU&sGz~hD5u_bW)WnH5&3Sde;gg_L|T1TT3JrSJlMgKb%o(cMSp{Vz+z` z@uFXgZlCmjEbg^Axw|zYs*Dm?qENq|l{V_E{UO3r7sU4mz;NnK8JF?voK(1DK*}q# z>Rm-$4^=64t+d~Yw|NYvEI%AeaGZ#bjqfF5U*m4Gj7wP@oI@BRe5^vA2zjY=Z%j-KVP)wyPeO1~%o8nl#~e@sy>;Sj4gj z%E)|_&N355nx-iIZe+{1*ZOT1%UrP(_rmM8IYnQ|Nn{P6drQv5-$mayM4^U(ZXd`n z?l$=u^iL$Yqh(QM4#Bp>?H;pFxevzq#r0E$4L7*LN~XV>>^l<y5mzF8{jWOYTTS2DAr#1wfrvjb5us*-&C;VQ z3P{sz&!eU`cV`^AI~HYtNrOdH8XwJ%<0*x}emOz-(s))hnbKfB;&J|mV`7+omkgzL znzf;1SllHc(T-F9KI;{aM_bg$ZHveWP-DBo+iGw~+yGsYs~2TR9@yttdvDQTyw%zM zsX21pJ#NuR!=*UlSpOXu3W&b(H5sCX7dk6;#Lq`OWrNqyjyLWLSAgl!U=*Z3EZkt# z*=LpM(5-^G#W6d0IiogAM)fu8SD~KP5v3=M&w*7#1NmWt`pVm)>_+!F$L%Jz`mqa% z@iL9a4nw!?9ngwdyj}sQTXaO1n<+RQmMi!tFhqT1nHz604VA38gEw&r4t4s|PGJdZ z{w;G!1DB!F|I(D9G_N&4U@-Am3b~iyJ2?G2HaJ4h6c4LDr>6iivnNKCb3V zTV%zyqgNij=>P7RahFJ6Gvla`eyc)4elbK6QurjTvc=sWYO;FFyv*ceSe*ULX*b`k zb?$b(drXi)Yz)ID07_(ubD0(!2SMFS0B22e4PUD#h#zevS2*Q@s1mn(msST%8l(&q z@P++r-O_eBnY+Kl-BvOcYV(O0M)kAFL)^!ky?mR_pF)2oXJ%$bF18lrf1YcXwF=Lq zZ)-Fm0=A-W-~cA*z&PhgAaIn`&VC%`#mr9#N^4cv+_Lu0QYMWfT3@G~jE-$;Nanf} z#TL7Cx5=P7C><`V=~chW!C>{g50$C>bq2j*II8?NOTTOhVc~de+mMe2KSP|x{_%1K z-pfkn&p&`S?>${^DAe-T1mjK@rVrS)YIwDpyaJ`j@H|ufi;bxC^GkG>d{i^AVavgM z@UR>*-7(%>N%N{}yiwyL_;+2gOQvP1CAwpZ_HSVcAn%MZ40kie1>N8Kktqwsskl`7 zeSxD!I?d;3{VGol7m0Z~7VWph8?~q;V`|cP?3Cas03TU_HH`DYCPMugZW0yp`h{es zxs9$_by-NOuv-0{?y3@e!dKc4C{%Cu367`%>@I^55l zK87eQ)F-^Rvp))7xoJ&32ILp#*2&lubDDOYAmnYtgEh=tz;5qHs%-N+U-SVUck$7WPh zaM8mpS(PG^P$VHWZ@>5~*t`@B8TN^-Y=G77Uj^#ENe?%}XI0Ht<$ry6ANs0dR@}Z> zc*i>=bBVd)_sdA%$p+X`=kZ+Juc{6_rpvpFL2yC9dXr9|uk5luuWf4^(5#HYl)g`P z8`)zPPmcn>t+Z~n*f=C8=r=H?<@;-9V71=bw|q&#y<-7fbPMv$j?ztsNx!EFe!9gJ z5%&6mi{&Y@&cDsVLW^`EdOVf4tX`zyMW68MjA)QRN$Zl%gd3gZpen7dLlYM`ei|h| zJ&!JxH^kQ7wX{E`FaMrUNTW#zO(n2emd-7bM<(A`Z#hR=+F8M6{Kt^oDc+`hpM*Em zwP*i$T?^ApSZ+<5T{_eKQ>U0I7-;d_*EK(LRO+_H80gh#bmmDchj#XB?HHLl{(`0# zxuc*Zn$kQDF{rfIfv1f4=0nf?SPnhHZ>PE+0VMhoDU|%Sl$e+H$5gg=F^-q2n5t+~ zS+``mdUw5VRSm*ex8rh^8*HL>XLDiE5?=t7VHu{0N=y~3?c_OpVKyVPg?-SD%AIx?!U11fS<#)Q~GK24^Q zJE5Unzk_H_0drqA9ikN{dx(KoBZt5E?wr@>E3xc#Qorr07L^o~VHL^7v6W6@h+uJm zG6)J0%pPnzeM$M^`dS7lqkl@XUmXfm;TsK*Fc##*HRmXxu!3>w;xmM0BjeYJNX!Z+ z6%r;K5P#_1B5HMY4+vb#0t9XB;tEQhPU|=~rRpdQeo7v`C@xj$S7t6d%xr4ac8C&w z-Jf|(sWYN;9KR8IiBH6DT{|PpgLiia&At;dC8Cav(9I)DM6v+hk<81u6=x?g6z6lz z^hnAGiK0jL#z?}&_j_2nW>==c3>$X%zbI9Hn&~i0>Kao6jo*?=Q&?3 zSN%mWw(e1on1i>IS{Q>oU;z^W2Q-6=RziIQMP4q766jTyK1FU@F1fuo@MuwAvhK%I zFc6R;a{8`;{mlKN&#T@s&2(Px3_1>q{k}f1)QhKXI#QRZ=ukKd#Pd}CU6NO_!n-sw zTW2?e)G3-?Ixc%?ADU$dy${#MENI;zEkYfdnj^|piTZjzHIjp3@IatjX!i{@ z$>(X75&ty=rPU+W@WiZ=q|{Vt2!fDXNeLFFqi*KvbqP_?+CDCQ&(!_~B()k$0A48+ zy!QR|yRb#+=vwIGwGK(y$1C{i8L8O?2=}L$GnWwpx=x&zFfETI!IC+TU7yALV)Oe5 z$_RawE>+oBajGxcF8$tvl*btu1Cb=9LN~KNW#OnB@C4&yjb{LPfejFh59 zh{Tn_lyf;c%f&4&n1ES@n40)uyIG*tZ;Zf9TK==}LiIX#-a_)e(^)!hwBI*kioJ$1 z3$+dZTOXb+kBnOf16pOrGIN7 z7o8l-qkr=7EMnw{7!K9!pS@~x#R>BlaOEz7?Y##2)vg>wE5X~~an#9$sS=U9`eI+7 zt&ix?8Df+bHF#Ljb-EP(8g z;7N&eLbBHT@m4`6<8qYTrrO~v&EYUDBJ$5}w~ukXr_1qc#3jpcE`pHAb&DB=dKzZ1 zF8a_8*50q0N?!F!1Y+H%TZN0zio%1VqY@qL)}j$g`>Yi==WO@7nX!E)JTNgwY1S7g z@+*ISzW1oA)>Jw3EtZr4%BP5rC_2SNKxf|&5nGr3+RCMwbQL?PkehH{PKgz45y`0G zh86o?4K@Odo7NGhvE5H(9HPiVFSlluPdst^S_%rh>P_0F2W&bJWKjt&hRWL6<%)XE zXiUYJ4OuN1RRwq0{Pd0B7~gv&?YcDadX{PmoI!{3Xx3p5s{y)?MT6MWt0%SIEJJbu1=FG^@#5mM+9^6>N5mvLob)A6NB=A93U!k5HFhCc7rmPKw^P zh7C%L$b=EJa>J{VY~4(b8xAsD$?XG!(8&E!o;IBC{F{>O60lDX7W$YV*&EeSEv~Y^ zBM@3&&y|!c_1WnYH?dtVAHE7K#2@MKpRQ+kdD11A1*PC{o6O+i3jwb)R3lS9%F!MI z=MpKr>|XQUw-#EV6J2$t2ha6M$~%LEhav;0*WjQswtz(+{drd(x(#VbLs=)1$=@ zRp5ePHq4ClzcOq_X3N0J8dkIN!hu{_F_Gog(bl5tRlX`uo>LK zuP;oNa{+m5lpAzxeQ*t3p_o^w+}IBd3en*zmoMvR*dRS!8BSEGNlm;Y+#8ZujtKw{ z4k7h$<<#5wKFX3a&;^8SSAeefSJ~ni3$4*G3!ZI0{sb%C`nYhyh>pIqTBN{!sfa4B zoMDvcunwXaB=v6L2EQ3NxtR(KxmD4Sf{tC1zK+13a9IfA(ELchZT7ZXzuf_e%SU<8cSzo3{?5-l8yj@D~WV)xHMh-@4% z9i0G#o`0@o4Jfrwlw$Nso`SXAZJG&&E5SZf|R;@A?Mz*yhjrlvWXrVViKDfrtGM zAao%sro%od@TcwSk0N{WWEVBTsOS7>4of8? z4B1?XS08TTZ5W(GLxhCIllZ~t)Zn67$ybdgQ-3dg((u*WOa;I;AO5yYR_5f0gZZ-? zs?Vil;&=@LrUNpB3dLQV!KCi{*C!^R^4P$#0mIN(30O{N?l3_g(6dH+4-Bx za+6a^znY-CK??cX7#5VYE9`2zU_DpK^V!xS@Iw{%p+?h8p72I|Qo(zZc;iU)zYZ{wHg>PqTimme(}f~_@q(aDTJdoxp5aKk$sH zpk8lE3CgQ8ETMQnuFE0335+JE^?)y|aJxdAXCEr2Wx1ITHhCDBl{48&Y?!3b?z~39 zjZ4Nt_2-`9X6YhTHC6?WXzl)2RCZD$Kh_H#CX(T7?7&edh8gG~wUBm;}=X6RK5a(59gK+iT(j&8X?H{W~tx^{ugt=)|KfGT?B%hbtmK53CYKA*uV z|FLGavQp!-X4MI0_=^c07HYM{;(W*h!D>~Xw?^@zGZY@^eB;<$x)~$uUX4L51z-iz z(#PN2@BYkiDq5(j!!mB*5G2n2v7IuHIVF#F#0nzET5UWbo07Qfvwe#@7K+M^ACpiX zSk*kE9u(Wo+1R`kmTD9c7IBC7;A3U5N!A~vuko=4cV_8}-^~@--Diqwx5b-7IlYi3 z5826We4`)X^t&RWm#1Z5;h7)iC5P75HQ0MH2>UOaDk_sIswU8~{)R5v{6&ggXiFtI zL*hGAgCKoLmyR>SuD+1&5Tw@QUW@uN9OHqnu+Fa=I>4ICdfYAra<;q=kQbx!u>#(X z1$n-IvstW6QR0ZIBzsz;2rs8Crk>5mnGV-j+9MT|I4k5GA-S@VCY3sbdj=>{*Df{& z;{^#u^|Wn7ucVyUsGeDnl=-^zDF97~5|fx4c;u_|?V2mORuTd#&aN9``dfK~x5t&P zeJ>(<4|}0;DKYOA()ZQ$wcXkBH-{y^MFB24vB@?wEpOslxxoJb4GiV6kf-aknB@!% zHUV~M{J9r|!_zA?hE!zm)ymPVaqu?GD1FK)o)5#wmP5%iECy(Dj}mXOtCK!PR=5lr z*IX_w5d2Dn!6eUQw~0Bjbj31?u?u*i1jv>GJ9|W>LcXQ`6i_uMs~>7^$g02Pnx*vo zrJzL3M1|?d&bB;m_JQa3GIqHJV3z?E;JXEHhH7ch^-PR~pwNTfo5B2-D zjV*+QX= z>+iZnBR0zDGzX{SE6_P3kO11R8bxy9HH9l0+DRsQL3h5UghV{Lw%Kg($y1{rH~I;P zo$X8t&f2@=8onf2t{@f8SYhDiBQs+aO+3Vskk)17WyCKQ5q0_7+O&U^GCNVHqf<}i zsm@t<5H3Q;SSxx3P$Kln9j3zLPClGF10TDH8I3tL%3eKyPdtACNwC#zU;^Az13#Gb zi_to$WimZqh@m;?nLZj-8SUA?_Ql^T(a6^6*RzW|G3g93WURAc=Ar^cI$t9En-a07 z)MB>&%HDM8itW(WCR;H>qpNV&{eh9hKlJ+-e7=HNeOFznr+4;(8# zna%j{Ezm4KbcMC_=yE&y7uMAPX*2NE9Rqon#ix6VZ{;xBNmqfr1~+;wLv`$^aAk$u z5^vg!!uc3v#$3>UV3;e?d$iac*L^tYF|;rW?e|IeN^tDP1IZP2Vbh=p5-G$@%RQZ7 z0Hwa0_Pu7+Y<=&3Z@Yn~F?{7zhTj8t96YK}Gk3jIleC^L=p{S!WbL<1aJXXpe6?@2 z7Xl5JWk#(UMaqmPdm16k7GsS+8UU`Et)2(3tb9y?^@GomN9&oWp1^;AaiREvqlM^_ zQD*uZT_8w#oRXbx3XE$sx4+0U4FazhTRZqM(V8T>>AHiymH# zyc+Xdw0teRPDoBn7QsqK6psGa-G_B$<*l?cc;BuNjF{FXN(*nv+OQ9M2{?%wikRr+ z4(^FqB+}Kqc(+54J+D-|m%+25RLn%N+>SHn6#1!yKltr922%++nEw7J3x{VuQ{bj> zf6u57<+bYq!Pth&0r`=jU!3kJCQ?Eu(o;r5I{-4z3~^sE|4Q)P<^=VpOum3!LSmG4=O29-x|8wfn0a(0TWrGnCTdNBz-{e}MP& zge8wcXQVbKVv)tJ$61vP4cTd_OzAi7DF~M3L9Qe;+0^3r%UyCwGm5FBgmGpQmzJO6 zITc>xj5VBU>cm8Uw9FicLj7L>Ox8YML5~Z<-gDX))-N1^bTxIRVvWW7j0{QgsJWj^ ztV%*9?HWWxW?vnFK5#`BMhsoOrVo3pH(D+rkF1O*vh?cR?&tT($-7n$Sg;*s>cuHpGu`fi}=5%v6rSzqnEW#l_J zDVbd<1hc?9mp(~ZX~6cjV4#zM;BidobzTbW$f~3mBVELT=lfZg87u|xfFtk3_5~Uj zsVmiM|JyL_o}x(p@*jYiL5GqF8=BDJoKu=KruB+LbC7`w$`(o(T#!JvX}JV{b;O|H}2qC!?V4X<}bm4d-UqD)G!LU?Eq!oaUDgPNu#Ml%u}w6%3K z`@Y30>gEwjec&Z$t`>kWq`*NpVLU^Dj&6$B#pLq{{e~wEp38B z)%i!uPO3dO?Ob193LY*tURmj3dP*H-xx!oh-m*Ttt3aPm_TEt(%EZow2`m$Ha|HJo zlb?Y#=5R3irtvc!kfSVR+Vy&6)Apz~Ik7jU3mI$l7HUK*R+bpVm|fi^SNT_;i0p^N ziydk5YdpU$`jt6D5%9tS@PUjtjSl4EPo(oG*KB8LIt|dn(TylIoXE@h ztnKG|PAEfF>nTEIaZPbDv$(r3OW7tT_LHE_=_M~nS)Hs@WDr{(SBmE6Id93!Z${SO zJcrVcusj7@i*j<$&PN$E@Ycn}v>H$#u=~O6xji;#YG5WxaHb~x_T5-B_bgVH7x{Lf z)|SuKr9k9;26Hu?mGGFW|I|Zw#aT7yx5F>3rxQA}97Evg>TP)~S;w4nE1X>)01uYJ zlmtx<<>{fH@paqhwVIomgOi)0mkdxA8njc!!k?6@Sz)xd-P|Q!3hTJl7*66Hr%bSrGLPBl#c1|dp z*(&enDD6H?p0rN4Iu27!K|p;7f5a@^3E3AGzEmnYPo_>Yut&sHvtCOEUQfvQaesOb zRVibYj)Lq&3Eijj6ypNxbpTXQYY(13Da~#Dpa^d>V=vJCq+41F)#O_iA7hc^QI<(v zy!vsqGpYsRXxM)TQ&rH>bcCw957nTO{{wuF5iE(?5&GtIFx+?DX~j-UEW3`zo&jsV zDLD!P%C>4>>(p{aOc+bC0$Aom1y2TKF7z zAL4KRh9H|0^cyC?yV~%m6sBw*IdMln@xFnf9k-<=h0uxYG9-BUkuY8b8tvmy<-scU ztqGKkUzrzNX0APn;Rvmm(Hk`AQV6az>9m%o$hL8e&`F6_5mTWg4{mNye08BEEe|}j z1udy{rz~@5R+WPD%)-d`Y4H!bSOUJ`#M9)$OL9^+q?Qo;3Zi-@{h$Aat^Bh32heuw zFD`zhLkpHHGc`~kXBMr5{rWEU=6;U+Jr&W#lhHv}hu2}HG5jdA+TGXE$s)wJnln5w z_{jxbQ4+U`H$2#gxO6fBnXY`hRc{>B_}UjbPu_nk-E{X{elX}*|KMhQq`tqcR;Fi^)Rr)?wv`dHk z!`HXI-B-aT_t@D##$Rvy*ZbQUG{#j-M4bf_eS_Cc=97OVV2BOZ79baj_g~q+%`w%~klp zDZ|{wv04Xu62o1>k#*5C9@!~9CXM~!N&@(<2?UNPrm@DD*sTm3g=)vhn(!Vc%2cZdW zKenkIr0_)c@=J9R2BYjC%^t9lb$(kgvcrLFvnU)TxSWT{7XT+kTq#k?K)z z7cY4|hW^><0PzGXra-g5$JFbGd*za~%jwF%Lhsf-Wes6?iMB#%QfKziSB>jS`}X-< zwOTD=mkHK=>W@@cETB&6XPFNtwr%?jjUmx#Z`gynnOcqap_B<&_{?BgAlDQ15I<@Z z71iUpz{h5Jb_b>cYAuYUT(1N4u9;CW!La>TkQF3Sj| zs5=LDU!iE0SJ0Xpj4`Y4quc13CvVYi7x?2=i{n@L(xAWJ^o#Y?z+pk8)w`)_X`jD> z%`=4YYpKUFTC0l=i}H~DRbr;Ym#%N0p$*q|F_ujVObE$RZNyxNHu~ad*)tH%XW&D! z23e!pgbao(T#s_KD(Q)`e^BD{PQRb#HX_F}(b8d&XUeLb@<)*#Lt~fXylFWq`-Fg< z#uxU+wp&}5Y!-%x(gLh6Q+Pzbn%zy}FzVv;t_Qm7=qO$*Q|wweYga*8WkNH~ZV)4Fk5m$K!JAcv5Vtf^PT{~?gAUoE8umD`_DucZtS8p_K zwbTHEgzuC27#i*LFQ5KiEf)clnB{0_^o*UsC_?;PU$_l(oIhTfJZcZv#Teu9CwCBx zQv<&i65a*{vUzrVg$1{3J9c;M9OaN!cP2MLw~`?Ygr?-N@?laRV~qhwHN?bGPoF?- zH~!cc1oqvXG`OiAi=zD^3<)+7*MGKE8~^WM;&Q#ve`)UY4={xD58&%#U2pi*l%4P5 zB60gDcM1Oh}6COh>q6ovD#4R zlFsF=d)#{gEh$^?8RpDijkM;|ol%|E#V?OA2MH>;_!zYL9&?ix7!e!T76# zj<(8}y^UC|b)0*AyFB%VvS_8C<4<7&48Cs0ic*&UV=E2A(n9oJN_r}EYqwNBAVTp` z_f$Em(Ngc2I>Mv0ARUvOE%zKj1d`Dt2?#T5w1lB|uI=tua#jZ7*c6C}Z*B06mAt>e zT5ix=w4uf#Z}E~belWpJPom=7^7*jb>v))ejio^;kWy_qbGuK_F8wjb`rd25lC^|J zjMK7Gw!l0RLKTHF{Z*&WlYfFoWRt?KwlKZe+C^nV?ihYzDEWiQ8xKifSDTpZ<2gWK zOymyd0em!05@c=KcyLY~OjG?$O0Cdrf?qu4i@IM@$W{E7qa4@TTr;2|fw+3MY8u-h zWEhrPT3X8=MBQE_RW(E7^R2>9CTImnxwDH)v26D^Zz$B=0;D|bZS z;o_jdv@qUy4EiO(ng`7bYqhR@ZTMo%I>}>ETFj<~Jv2S49i|9NmlhN#F(OSYvA7gX zk{%6+&HQX+<$-81vd-e1ZP}8wq80>deAAhJhxe*Ld(CdP6p2K^`VK!EFznxV!!s&> zJ$~&kBsZu%^x)UjgX*!e5>xg)lUr$M2`Im~NmN{6dO?P?4{v~ufqGHg z)Ae~g@ZdtmPz9%9{3Bt$Y=wpZgg=*|W%!h>>Jo^}KPH-g?!Vhs*utdPbSGP^0jXJ02ZG-H=9~}@s8}^go z-}baw=4RLpbG;Er?WApoTQyo#cDPXl7?Eu?uPnST=QDY?I~*5!p}4(d-NHrO$bm*@ z@3(=JpP<=n6S|6B8lN_0=Ntx(dQn<5C;vQ%vZ2M?&9q2D86$hWJLj7a=hV;P8>dTB zt}5-NpjwU0f8I-td9A7+91AV_$P$2B?yea7F|9bB25FIXiLk!XkCmUfep=1m%1w7J zG|bNc2o9s*5lFZW-a(`5Qj@qYyG5gx_lSG~agCtP3#+dmg`b=wQ-{~Ay#}}J*Vh%o zg|N0rE4t)3)qtp~mzU!*%-`r^dfS~{?{=>e*OQ zl0!hOzOO|`AEMlniX@00M26eTjvJ}_*eZNW75pM^b5iY&n6(@k*cTdk~hz)qvP`!zQ{Ya`aup5glqM4Rp*7~bK zh9IugPziEa3zz@O)SH$+j5_2A3iansOoPFGnwD*5_4U}a^*c5c=-tD~W$QT2fJ;9Z zEEshOV9=LTq55UBJ6wKWCGh?xlGxuP!AXV9%5i@n%0;4IXm^I1EF^yPPwGL`qdb^m8c)yvWrYM?aWM_{d?{0nlN*WzQywc*+)wsJ*cxi=oLhbp?wS>ZHx%!`q zSO(MqZxaqmj}5u!k`UX9^2Ju8*gzi{9Y`@A2X5|qa`!G%V7SMa60OOBl|oZ~q@_~= ziCl}F)UUl-aaowhDLD7)R$*z+kZTAY-T<8>KCc)mKr2q*ssb@4QC<1a^3Vb4);)0* zPY{tS>r6Eh5d5WMEKk)>njj7?#OjuJD8}IC`nVu*weot9(rn_^r)87zqf$DX=%GJ+ zQ~qYXG>30ZX^t9~xCMsge4@66u@lvZWku=92ao+(#2zn?Pn2BB3 zbyITahZR)>1w(QKE?E;3`B{2XX}YW!xfxUB0ofB-Z7)c%>wlY@g(gx#zFs$EO3BG6 zF^d@_@9d)9pj-2DRDX!zeGE2yLL?`NJUBi*kh=&hO5n=q4dKXb6e;%$Z?Q-i;WZFR zF3yg{`HUJIYRk}mC{QO|8^bw;{R4dPxg75Nx)FlrYm(MW)`1mffHHxCjv6w9rcdc} z?2KdgB6xWCT1IBaYhe9Gwx7icdnF>=TXe}cE6ccr-MSKn$aWW`NR)tVacWT7GFcR} zDDmM%7hFtu9F}ec>3#=5Fa^Y#{n2CsTFj0|yh~Lox;^ONV_LCv)f-IYYpD2i{{WeR zhYYGlvq&-MgE!z@gqM_)OzSMB8^%eicKVpmT-%sWUU-eT+U&u;a{nNg)MbYQ?nKgL#!_5Nc!vtD&eFQH@R--d&QaEIvrzj#K+2?FF(1d? zqP3t zhpI^pD)arnh||7Yz3X;_w)L^W=4Fsp$b6X`eDWkr0M_C9r^BSDL;2| zGhyzG4Dp&ijNgQqQ-G7%jm!q$oZ+*=$pq&{@AM|)UYsWSajv^IDA!&2Or_OUqr8RM z=rLXch+tlimkaG@@|P3oo#EeE4Y4=Zkle;Zgh!&OUf^#1&Qz25$g7|s0mjSjnYmc2 z$+9xWr(pW*qV)Noa^GznIgYE@xL+#BoMB7O4cTw?W#;qv@O(ysa50NrWg%1dF>esK ztcal=GoXEphQQU4vM?`dh}9A9DCeB|woZ_@#6936AD1>)fTDCfm7Va^s_iXPjS$f< z9lI*+Uk0I(@U*&#h(v{?6fsz@y_LdI$olNh`VWOetW{Ewk|$g~v|f+75FFPKm!O#S z)WKF>>X-ua?fVh@17I10`hinTZ`E6C09?YA!}1KXAX>IM5xFpTvmG=?TCSdY;GFz=_Zpx`$O)?Pxt*yn(axN=j0D;YClW(`T)I2ko%5Fk!*Y8CMf3vpE*FB-J)+t@th8V{bG_xOB1|r;p9C&1OESf-X zj-}`K3pbqM;ZqrX@Dxl(pD4N5#kXH$g1dMifa7Gq}fe`R?yA)2yIeF80iCAf35y?_PAFTS(J;%xxOH zkQTSgdN+zUJZl!J;VQB4rS#3y|4?IAepQQZr;P8#f;jt#U6BQzR}kqwK2+3EjL9QT z7DiiA$9#nzU{2tXG5y10?(^}ty)pZcUn(>qTYDj`h@AFG>dqNw$#zbjGD*CiZ77?_ z-4=~R`ncE9AEV)2#y?juoZEy&gL|$#F57 z;E-sUr)BQ(s4>Y??Y@u)Z^`A!+xI}K8%#{DA9*De#VJL^(6`Zrn9(}0-&mF1pH9w2 zCmmOy!`pE1rYq#o!STQd`u=N(iy@>2FNbC6F)Gl(?Oh|Q1g^@5TZcN*hUpr-`WD*b zL+-W12#IU8qe8<)v zKd|jrrmt&hJsdiHTFISgR5xqk=mR1r1@ELwS>=gxOcKY7{tQZv8#5#G;az|J0sK@h z9qK|hDpK1+R*>JQn3QwdwSE{>^Xa^T^6j+JOU}1C&@O6gTw#k|>?Cw%Kt!=0b(geb z)1tj2{0&=Kd;*DpU29FL?X%2X*kLg$>z}l2G`eQwUA7=(t^V6HIQpT*JYOw8`%G_e z44JJ#y35`xAm5AxHR2@Q5kWz=8I|Kx@)x}-fF;jQ@@v>SYfG?yJBjNC z&R3i^Jf+PFTI%&4oUizQ@4O?|qr>Ij&m@XKqIQiN3u}<7hAg&@CC8e>K=FYN41=NQ zgHd$4ns|cDtpF-|!mvQdtZf|L_|7zA_TF?h^X$^w=i@eWyz3T1eU}Ad*lljyL(AUu zio+fPM9c@hI8=#|B1bC>$Y7pKkeHGi+fWY57!+z;*gGPAmcwBr*0gSfXi)SuUkL-S z550DG%P;Geb)fNtBF_|qpyUgRtFK&uzdk9YcgQW{xsCj>{ikq>9NR;z`fg1VZi9$Z zO^PA?hJHm+TRDpjeWV0+AGyy+#}WK+XRF?%%mqvTrSCzkLymF|%y^JdmB)Yd4Ijo` z@&TN|tcZvjEQdJ4?>eeOy^Ix)Qn_dNfsbJ_?DpSFv8Y zA%AP2J;)edlTPwDKE;Q=)%lmS(Cdn?(b>_s5V0aUr@j7i8TaEnNy^__7oTgm(e1Ap zI3mEIaNifKw3AH1N(}yr_PvAaHy?kN-?h+c+2WtsUyReXVcR$gDyG_wHpOO1WaJpk zFSo*`+<~7uQ}-QA*)!3_bz}hVwZu$|b{mfUsx{O|Tm(n!W`40I(MP5W=o!A|lVVMh zSI6byO&wcmmDp_xAu7MH4&PMUrnE5U9=3}kWTy%rp5~GP^sr9gA&S$D)>$2zradg) z4uk~F`*Du=H|utocC5G%Pn}&(H7fpKf-H!TpdZ9XV+LE5HMF%kD=!{>9;2lEAehBU zFHl>hSV;J0kF78up8<6;9*kRjZ{(h}F{9MtpoQ+z*edA5Y11B_ykAfNh8E2+?-B1v zAfS;g?)umYN?4sb{S2yoi_J#waawj&+?2y&=h-Y{eRLCc?G>@6UWb%1W@4vkAOyuG zCfw9Z{VZi&8y@O|cHNfc+Qf(6lwPp5t+`VNfUc}k44)VRM=w87DnL^b-#sjsV)J}0Ju?jeulAcX zhy0tDm(>9wnZDRa`yb;M74i)DuR-4fQ2%44Z~o6hkTcTYrShsuI>`0t0O|j7gaQAM y1AqcRMSA}+l>hkhGXGsejP&u5_5x|hr|{DM&Hw<*|2Lx}-~BHkc^`~}`TqcCdY&Kv literal 0 HcmV?d00001 diff --git a/packages/app-expo/src/lib/rag/__fixtures__/gutenberg-11.mobi b/packages/app-expo/src/lib/rag/__fixtures__/gutenberg-11.mobi new file mode 100644 index 0000000000000000000000000000000000000000..562b9ab8d19b35bcf9c12d22c0e1df3e734066c1 GIT binary patch literal 240898 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