fix(cache): invalidate config and doc cache, closes #2595 and closes #2572 (#2623)

This commit is contained in:
Huang Xin
2025-12-05 14:03:57 +08:00
committed by GitHub
parent b08b7de8e9
commit 8de6fa267e
8 changed files with 75 additions and 60 deletions
@@ -34,6 +34,7 @@ import { useTheme } from '@/hooks/useTheme';
import { useUICSS } from '@/hooks/useUICSS';
import { useDemoBooks } from './hooks/useDemoBooks';
import { useBooksSync } from './hooks/useBooksSync';
import { useBookDataStore } from '@/store/bookDataStore';
import { useScreenWakeLock } from '@/hooks/useScreenWakeLock';
import { useOpenWithBooks } from '@/hooks/useOpenWithBooks';
import { SelectedFile, useFileSelector } from '@/hooks/useFileSelector';
@@ -87,6 +88,7 @@ const LibraryPageContent = ({ searchParams }: { searchParams: ReadonlyURLSearchP
const _ = useTranslation();
const { selectFiles } = useFileSelector(appService, _);
const { safeAreaInsets: insets, isRoundedWindow } = useThemeStore();
const { clearBookData } = useBookDataStore();
const { settings, setSettings, saveSettings } = useSettingsStore();
const { isSettingsDialogOpen, setSettingsDialogOpen } = useSettingsStore();
const [showCatalogManager, setShowCatalogManager] = useState(false);
@@ -531,6 +533,7 @@ const LibraryPageContent = ({ searchParams }: { searchParams: ReadonlyURLSearchP
try {
await appService?.deleteBook(book, deleteAction);
await updateBook(envConfig, book);
clearBookData(book.hash);
if (syncBooks) pushLibrary();
eventDispatcher.dispatch('toast', {
type: 'info',
@@ -5,7 +5,6 @@ import { Book } from '@/types/book';
import { BookMetadata } from '@/libs/document';
import { useEnv } from '@/context/EnvContext';
import { useThemeStore } from '@/store/themeStore';
import { useSettingsStore } from '@/store/settingsStore';
import { useTranslation } from '@/hooks/useTranslation';
import { useMetadataEdit } from './useMetadataEdit';
import { DeleteAction } from '@/types/system';
@@ -46,14 +45,13 @@ const BookDetailModal: React.FC<BookDetailModalProps> = ({
handleBookMetadataUpdate,
}) => {
const _ = useTranslation();
const { envConfig } = useEnv();
const { safeAreaInsets } = useThemeStore();
const [loading, setLoading] = useState(false);
const [activeDeleteAction, setActiveDeleteAction] = useState<DeleteAction | null>(null);
const [editMode, setEditMode] = useState(false);
const [bookMeta, setBookMeta] = useState<BookMetadata | null>(null);
const [fileSize, setFileSize] = useState<number | null>(null);
const { envConfig } = useEnv();
const { settings } = useSettingsStore();
// Initialize metadata edit hook
const {
@@ -97,7 +95,7 @@ const BookDetailModal: React.FC<BookDetailModalProps> = ({
const fetchBookDetails = async () => {
const appService = await envConfig.getAppService();
try {
const details = book.metadata || (await appService.fetchBookDetails(book, settings));
const details = book.metadata || (await appService.fetchBookDetails(book));
setBookMeta(details);
const size = await appService.getBookFileSize(book);
setFileSize(size);
@@ -19,7 +19,8 @@ const LangPanel: React.FC<SettingsPanelPanelProp> = ({ bookKey, onRegisterReset
const { token } = useAuth();
const { envConfig } = useEnv();
const { settings, applyUILanguage } = useSettingsStore();
const { getViewSettings, setViewSettings, recreateViewer } = useReaderStore();
const { getView, getViewSettings, setViewSettings, recreateViewer } = useReaderStore();
const view = getView(bookKey);
const viewSettings = getViewSettings(bookKey) || settings.globalViewSettings;
const [uiLanguage, setUILanguage] = useState(viewSettings.uiLanguage);
@@ -191,7 +192,7 @@ const LangPanel: React.FC<SettingsPanelPanelProp> = ({ bookKey, onRegisterReset
bookKey,
'replaceQuotationMarks',
replaceQuotationMarks,
true,
false,
false,
).then(() => {
recreateViewer(envConfig, bookKey);
@@ -231,7 +232,7 @@ const LangPanel: React.FC<SettingsPanelPanelProp> = ({ bookKey, onRegisterReset
bookKey,
'convertChineseVariant',
convertChineseVariant,
true,
false,
false,
).then(() => {
recreateViewer(envConfig, bookKey);
@@ -268,6 +269,7 @@ const LangPanel: React.FC<SettingsPanelPanelProp> = ({ bookKey, onRegisterReset
className='toggle'
checked={translationEnabled}
onChange={() => setTranslationEnabled(!translationEnabled)}
disabled={!bookKey}
/>
</div>
@@ -311,7 +313,7 @@ const LangPanel: React.FC<SettingsPanelPanelProp> = ({ bookKey, onRegisterReset
</div>
</div>
{isCJKEnv() && (
{(isCJKEnv() || view?.language.isCJK) && (
<div className='w-full'>
<h2 className='mb-2 font-medium'>{_('Punctuation')}</h2>
<div className='card border-base-200 bg-base-100 border shadow'>
@@ -333,7 +335,7 @@ const LangPanel: React.FC<SettingsPanelPanelProp> = ({ bookKey, onRegisterReset
</div>
)}
{isCJKEnv() && (
{(isCJKEnv() || view?.language.isCJK) && (
<div className='w-full'>
<h2 className='mb-2 font-medium'>{_('Convert Simplified and Traditional Chinese')}</h2>
<div className='card border-base-200 bg-base-100 border shadow'>
+5 -5
View File
@@ -668,7 +668,7 @@ export abstract class BaseAppService implements AppService {
return null;
}
async loadBookContent(book: Book, settings: SystemSettings): Promise<BookContent> {
async loadBookContent(book: Book): Promise<BookContent> {
let file: File;
const fp = getLocalBookFilename(book);
if (await this.fs.exists(fp, 'Books')) {
@@ -692,7 +692,7 @@ export abstract class BaseAppService implements AppService {
throw new Error(BOOK_FILE_NOT_FOUND_ERROR);
}
}
return { book, file, config: await this.loadBookConfig(book, settings) };
return { book, file };
}
async loadBookConfig(book: Book, settings: SystemSettings): Promise<BookConfig> {
@@ -711,13 +711,13 @@ export abstract class BaseAppService implements AppService {
}
}
async fetchBookDetails(book: Book, settings: SystemSettings) {
async fetchBookDetails(book: Book) {
const fp = getLocalBookFilename(book);
if (!(await this.fs.exists(fp, 'Books')) && book.uploadedAt) {
await this.downloadBook(book);
}
const { file } = (await this.loadBookContent(book, settings)) as BookContent;
const bookDoc = (await new DocumentLoader(file).open()).book as BookDoc;
const { file } = await this.loadBookContent(book);
const bookDoc = (await new DocumentLoader(file).open()).book;
const f = file as ClosableFile;
if (f && f.close) {
await f.close();
@@ -27,6 +27,7 @@ interface BookDataState {
) => void;
updateBooknotes: (key: string, booknotes: BookNote[]) => BookConfig | undefined;
getBookData: (keyOrId: string) => BookData | null;
clearBookData: (keyOrId: string) => void;
}
export const useBookDataStore = create<BookDataState>((set, get) => ({
@@ -35,6 +36,16 @@ export const useBookDataStore = create<BookDataState>((set, get) => ({
const id = keyOrId.split('-')[0]!;
return get().booksData[id] || null;
},
clearBookData: (keyOrId: string) => {
const id = keyOrId.split('-')[0]!;
set((state) => {
const newBooksData = { ...state.booksData };
delete newBooksData[id];
return {
booksData: newBooksData,
};
});
},
getConfig: (key: string | null) => {
if (!key) return null;
const id = key.split('-')[0]!;
+45 -43
View File
@@ -144,51 +144,53 @@ export const useReaderStore = create<ReaderStore>((set, get) => ({
},
}));
try {
const appService = await envConfig.getAppService();
const { settings } = useSettingsStore.getState();
if (!bookData || reload) {
const appService = await envConfig.getAppService();
const { library } = useLibraryStore.getState();
const book = library.find((b) => b.hash === id);
if (!book) {
throw new Error('Book not found');
}
const content = (await appService.loadBookContent(book, settings)) as BookContent;
const { file, config } = content;
console.log('Loading book', key);
const { book: bookDoc } = await new DocumentLoader(file).open();
await updateToc(
bookDoc,
config.viewSettings?.sortedTOC ?? false,
config.viewSettings?.convertChineseVariant ?? 'none',
);
if (!bookDoc.metadata.title) {
bookDoc.metadata.title = getBaseFilename(file.name);
}
book.sourceTitle = formatTitle(bookDoc.metadata.title);
// Correct language codes mistakenly set with language names
if (typeof bookDoc.metadata?.language === 'string') {
if (bookDoc.metadata.language in SUPPORTED_LANGNAMES) {
bookDoc.metadata.language = SUPPORTED_LANGNAMES[bookDoc.metadata.language]!;
}
}
// Set the book's language for formerly imported books, newly imported books have this field set
const primaryLanguage = getPrimaryLanguage(bookDoc.metadata.language);
book.primaryLanguage = book.primaryLanguage ?? primaryLanguage;
book.metadata = book.metadata ?? bookDoc.metadata;
// TODO: uncomment this when we can ensure metaHash is correctly generated for all books
// book.metaHash = book.metaHash ?? getMetadataHash(bookDoc.metadata);
book.metaHash = getMetadataHash(bookDoc.metadata);
const isFixedLayout = FIXED_LAYOUT_FORMATS.has(book.format);
useBookDataStore.setState((state) => ({
booksData: {
...state.booksData,
[id]: { id, book, file, config, bookDoc, isFixedLayout },
},
}));
const { library } = useLibraryStore.getState();
const book = library.find((b) => b.hash === id);
if (!book) {
throw new Error('Book not found');
}
const booksData = useBookDataStore.getState().booksData;
const config = booksData[id]?.config as BookConfig;
let bookDoc = bookData?.bookDoc;
let file = bookData?.file;
if (!bookDoc || !file || reload) {
const content = (await appService.loadBookContent(book)) as BookContent;
file = content.file;
console.log('Loading book', key);
const doc = await new DocumentLoader(file).open();
bookDoc = doc.book;
}
const config = await appService.loadBookConfig(book, settings);
await updateToc(
bookDoc,
config.viewSettings?.sortedTOC ?? false,
config.viewSettings?.convertChineseVariant ?? 'none',
);
if (!bookDoc.metadata.title) {
bookDoc.metadata.title = getBaseFilename(file.name);
}
book.sourceTitle = formatTitle(bookDoc.metadata.title);
// Correct language codes mistakenly set with language names
if (typeof bookDoc.metadata?.language === 'string') {
if (bookDoc.metadata.language in SUPPORTED_LANGNAMES) {
bookDoc.metadata.language = SUPPORTED_LANGNAMES[bookDoc.metadata.language]!;
}
}
// Set the book's language for formerly imported books, newly imported books have this field set
const primaryLanguage = getPrimaryLanguage(bookDoc.metadata.language);
book.primaryLanguage = book.primaryLanguage ?? primaryLanguage;
book.metadata = book.metadata ?? bookDoc.metadata;
// TODO: uncomment this when we can ensure metaHash is correctly generated for all books
// book.metaHash = book.metaHash ?? getMetadataHash(bookDoc.metadata);
book.metaHash = getMetadataHash(bookDoc.metadata);
const isFixedLayout = FIXED_LAYOUT_FORMATS.has(book.format);
useBookDataStore.setState((state) => ({
booksData: {
...state.booksData,
[id]: { id, book, file, config, bookDoc, isFixedLayout },
},
}));
const configViewSettings = config.viewSettings!;
const globalViewSettings = settings.globalViewSettings;
set((state) => ({
-1
View File
@@ -293,5 +293,4 @@ export interface BooksGroup {
export interface BookContent {
book: Book;
file: File;
config: BookConfig;
}
+2 -2
View File
@@ -112,9 +112,9 @@ export interface AppService {
isBookAvailable(book: Book): Promise<boolean>;
getBookFileSize(book: Book): Promise<number | null>;
loadBookConfig(book: Book, settings: SystemSettings): Promise<BookConfig>;
fetchBookDetails(book: Book, settings: SystemSettings): Promise<BookMetadata>;
fetchBookDetails(book: Book): Promise<BookMetadata>;
saveBookConfig(book: Book, config: BookConfig, settings?: SystemSettings): Promise<void>;
loadBookContent(book: Book, settings: SystemSettings): Promise<BookContent>;
loadBookContent(book: Book): Promise<BookContent>;
loadLibraryBooks(): Promise<Book[]>;
saveLibraryBooks(books: Book[]): Promise<void>;
getCoverImageUrl(book: Book): string;