-
-
Notifications
You must be signed in to change notification settings - Fork 18
Add Web local version using File System API #33
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
22a5d92
31a94c8
06acc32
fb13346
564e56a
850932d
f3ab047
17aced4
33659a7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| import { useEffect, useReducer } from "react"; | ||
| import { DEFAULT_CONTEXT_VALUE, LibraryContext, LibraryState } from "./context"; | ||
| import { Options, initClient } from "@/lib/services/library"; | ||
| import { reducer } from "./reducer"; | ||
|
|
||
| const webLibraryFromHandle = (handle: FileSystemDirectoryHandle): Options => ({ | ||
| libraryDirectoryHandle: handle, | ||
| libraryType: "calibre", | ||
| connectionType: "web", | ||
| }); | ||
|
|
||
| interface LibraryProviderProps { | ||
| children: React.ReactNode; | ||
| directoryHandle: FileSystemDirectoryHandle; | ||
| } | ||
| export const WebCalibreLibraryProvider = ({ | ||
| children, | ||
| directoryHandle, | ||
| }: LibraryProviderProps) => { | ||
| const [context, dispatch] = useReducer(reducer, DEFAULT_CONTEXT_VALUE); | ||
|
|
||
| useEffect(() => { | ||
| if (context.state === LibraryState.error) { | ||
| console.log(context.error); | ||
| } | ||
| }, [context]); | ||
|
|
||
| useEffect(() => { | ||
| initClient(webLibraryFromHandle(directoryHandle)) | ||
| .then((client) => { | ||
| dispatch({ | ||
| type: "init", | ||
| client, | ||
| }); | ||
| }) | ||
| .catch(() => { | ||
| dispatch({ type: "error", error: new Error("Failed to init Library") }); | ||
| }); | ||
|
Comment on lines
+36
to
+38
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. style: Generic error message makes debugging difficult - consider preserving the original error or adding more context Prompt To Fix With AIThis is a comment left during a code review.
Path: src/lib/contexts/library/WebProvider.tsx
Line: 36:38
Comment:
**style:** Generic error message makes debugging difficult - consider preserving the original error or adding more context
How can I resolve this? If you propose a fix, please make it concise. |
||
|
|
||
| return () => { | ||
| dispatch({ type: "shutdown" }); | ||
| }; | ||
| }, [directoryHandle]); | ||
|
Comment on lines
+28
to
+43
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. logic: Loading state is never set during initialization - consider dispatching a loading action before calling Prompt To Fix With AIThis is a comment left during a code review.
Path: src/lib/contexts/library/WebProvider.tsx
Line: 28:43
Comment:
**logic:** Loading state is never set during initialization - consider dispatching a loading action before calling `initClient`
How can I resolve this? If you propose a fix, please make it concise. |
||
|
|
||
| return ( | ||
| <LibraryContext.Provider value={context}> | ||
| {!context.loading && children} | ||
| </LibraryContext.Provider> | ||
| ); | ||
| }; | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,5 +1,6 @@ | ||||||
| import { LibraryProvider } from "./Provider"; | ||||||
| import { LocalCalibreLibraryProvider } from "./Provider"; | ||||||
| import {WebCalibreLibraryProvider} from "./WebProvider"; | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. style: Missing space after opening brace in import statement
Suggested change
Prompt To Fix With AIThis is a comment left during a code review.
Path: src/lib/contexts/library/index.ts
Line: 2:2
Comment:
**style:** Missing space after opening brace in import statement
```suggestion
import { WebCalibreLibraryProvider } from "./WebProvider";
```
How can I resolve this? If you propose a fix, please make it concise. |
||||||
| import { LibraryState } from "./context"; | ||||||
| import { useLibrary } from "./hooks"; | ||||||
|
|
||||||
| export { LibraryProvider, useLibrary, LibraryState }; | ||||||
| export { LocalCalibreLibraryProvider, WebCalibreLibraryProvider, useLibrary, LibraryState }; | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| export const isDefined = <T>(value: T | undefined): value is T => | ||
| value !== undefined; | ||
|
|
||
| export const isNonNull = <T>(value: T | null): value is T => value !== null; | ||
|
|
||
| export const isSomething = <T>(value: T | undefined | null): value is T => | ||
| value !== undefined && value !== null; |
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,3 @@ | ||||||||||||||
| export const isDesktop = () => { | ||||||||||||||
| return "__TAURI__" in window && window.__TAURI__; | ||||||||||||||
| }; | ||||||||||||||
|
Comment on lines
+1
to
+3
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. logic: Missing error handling for environments where
Suggested change
Prompt To Fix With AIThis is a comment left during a code review.
Path: src/lib/platform.ts
Line: 1:3
Comment:
**logic:** Missing error handling for environments where `window` is undefined (e.g., SSR or Node.js contexts)
```suggestion
export const isDesktop = () => {
return typeof window !== "undefined" && "__TAURI__" in window && window.__TAURI__;
};
```
How can I resolve this? If you propose a fix, please make it concise. |
||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,149 @@ | ||
| import initSqlJs from "sql.js"; | ||
|
|
||
| import { LibraryBook } from "@/bindings"; | ||
|
|
||
| import { Library, WebConnectionOptions } from "../_types"; | ||
| import { loadDb } from "./web/db"; | ||
| import { | ||
| createAuthorRepository, | ||
| createBookAuthorLinkRepository, | ||
| createBookRepository, | ||
| } from "./web/repositories/sqljs"; | ||
| import { createCatalogService } from "./web/services/catalog"; | ||
| import { createAuthorService } from "./web/services/author"; | ||
| import { libraryAuthor } from "./web/entities/LibraryAuthor"; | ||
|
|
||
| export const genWebCalibreClient = async ( | ||
| options: WebConnectionOptions, | ||
| ): Promise<Library> => { | ||
| const SQL = await initSqlJs({ | ||
| // TODO: Now Citadel requires being online on the web? ... that is probably fine, yes. | ||
| locateFile: (file: string) => `https://sql.js.org/dist/${file}`, | ||
| }); | ||
| const bookRepo = createBookRepository(options.libraryDirectoryHandle, SQL); | ||
| const bookAuthorLinkRepo = createBookAuthorLinkRepository( | ||
| options.libraryDirectoryHandle, | ||
| SQL, | ||
| ); | ||
| const authorRepo = createAuthorRepository( | ||
| options.libraryDirectoryHandle, | ||
| SQL, | ||
| ); | ||
| const authorService = createAuthorService(authorRepo); | ||
| const catalogService = createCatalogService( | ||
| bookRepo, | ||
| bookAuthorLinkRepo, | ||
| authorRepo, | ||
| ); | ||
|
|
||
| return { | ||
| listBooks: async () => { | ||
| const db = await loadDb(options.libraryDirectoryHandle, SQL); | ||
| if (!db) return []; | ||
|
|
||
| const res = db.exec("SELECT * FROM 'books'"); | ||
| if (res.length === 0) { | ||
| return []; | ||
| } | ||
| const rows = res[0].values; | ||
|
|
||
| const coverImagePromises = rows.map((row) => { | ||
| const path = (row[9] ?? "").toString(); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. style: Magic number Prompt To Fix With AIThis is a comment left during a code review.
Path: src/lib/services/library/_internal/adapters/web.ts
Line: 51:51
Comment:
**style:** Magic number `row[9]` for accessing cover path column is brittle and unclear. Consider using named constants or a mapping function.
How can I resolve this? If you propose a fix, please make it concise. |
||
| if (path === "") { | ||
| return null; | ||
| } | ||
| return coverImageFromPath(path, options.libraryDirectoryHandle); | ||
| }); | ||
| const coverImages = await Promise.all(coverImagePromises); | ||
|
|
||
| const addCoverImageToBook = ( | ||
| book: LibraryBook, | ||
| coverImage: File | null, | ||
| ): LibraryBook => { | ||
| if (coverImage === null) { | ||
| return book; | ||
| } | ||
| return { | ||
| ...book, | ||
| cover_image: { | ||
| kind: "Remote", | ||
| url: URL.createObjectURL(coverImage), | ||
| local_path: null, | ||
| }, | ||
| }; | ||
| }; | ||
| const books = (await catalogService.all()).map((book, index) => | ||
| addCoverImageToBook(book, coverImages[index]), | ||
| ); | ||
|
|
||
| return books; | ||
| }, | ||
| listAuthors: async () => { | ||
| return (await authorService.all()).map(libraryAuthor.fromAuthor); | ||
| }, | ||
| listValidFileTypes: () => { | ||
| return Promise.resolve([]); | ||
| }, | ||
| getCoverPathForBook: () => { | ||
| throw new Error("Not implemented"); | ||
| }, | ||
| getCoverUrlForBook: () => { | ||
| throw new Error("Not implemented"); | ||
| }, | ||
| getDefaultFilePathForBook: () => { | ||
| throw new Error("Not implemented"); | ||
| }, | ||
| getImportableFileMetadata: () => { | ||
| throw new Error("Not implemented"); | ||
| }, | ||
| sendToDevice: () => { | ||
| throw new Error("Not implemented"); | ||
| }, | ||
| updateBook: () => { | ||
| throw new Error("Not implemented"); | ||
| }, | ||
| addImportableFileByMetadata: () => { | ||
| throw new Error("Not implemented"); | ||
| }, | ||
| checkFileImportable: () => { | ||
| throw new Error("Not implemented"); | ||
| }, | ||
| }; | ||
| }; | ||
|
|
||
| const coverImageFromPath = async ( | ||
| path: string, | ||
| libraryDirectoryHandle: FileSystemDirectoryHandle, | ||
| ): Promise<File | null> => { | ||
| const parts = path.split("/"); | ||
| const maxDepth = parts.length; | ||
| let currDepth = 0; | ||
| let handle: FileSystemDirectoryHandle | FileSystemFileHandle | null = | ||
| libraryDirectoryHandle; | ||
|
|
||
| while (currDepth < maxDepth) { | ||
| try { | ||
| // File names can be invalid, which throws an error. | ||
| handle = await handle.getDirectoryHandle(parts[currDepth]); | ||
| } catch (e) { | ||
| console.log(e, handle, parts[currDepth], parts, currDepth); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. style: Console.log in production code should be replaced with proper error handling or logging system. Prompt To Fix With AIThis is a comment left during a code review.
Path: src/lib/services/library/_internal/adapters/web.ts
Line: 129:129
Comment:
**style:** Console.log in production code should be replaced with proper error handling or logging system.
How can I resolve this? If you propose a fix, please make it concise. |
||
| handle = null; | ||
| break; | ||
| } | ||
| if (handle === null) { | ||
| break; | ||
| } | ||
| currDepth++; | ||
| } | ||
|
|
||
| if (handle === null) { | ||
| return null; | ||
| } | ||
| try { | ||
| // Getting the cover image can throw errors | ||
| const fileHandle = await handle.getFileHandle("cover.jpg"); | ||
| return fileHandle.getFile(); | ||
| } catch (e) { | ||
| return null; | ||
| } | ||
| }; | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
logic: Logic flaw: web provider renders when
libraryFSDirectoryHandleexists butlibraryPathis null, bypassing the first-time setup check. This could lead to inconsistent state.Prompt To Fix With AI