From d01897d62aafea62340cd777ab220e76311e1679 Mon Sep 17 00:00:00 2001 From: Huang Xin Date: Sun, 27 Jul 2025 22:48:58 +0800 Subject: [PATCH] fix: load backup library data if main library data is unavailable, closes #1672 (#1689) --- apps/readest-app/src/pages/api/sync.ts | 14 ++++-- apps/readest-app/src/services/appService.ts | 54 ++++++++++++++++++--- apps/readest-app/src/utils/book.ts | 3 ++ 3 files changed, 60 insertions(+), 11 deletions(-) diff --git a/apps/readest-app/src/pages/api/sync.ts b/apps/readest-app/src/pages/api/sync.ts index a221019a..8459d427 100644 --- a/apps/readest-app/src/pages/api/sync.ts +++ b/apps/readest-app/src/pages/api/sync.ts @@ -144,8 +144,12 @@ export async function POST(req: NextRequest) { .insert(dbRec) .select() .single(); - console.log('Inserted record:', inserted); - if (insertError) return { error: insertError.message }; + if (insertError) { + console.log(`Failed to insert ${table} record:`, JSON.stringify(dbRec)); + return { + error: insertError.message, + }; + } authoritativeRecords.push(inserted); } else { const clientUpdatedAt = dbRec.updated_at ? new Date(dbRec.updated_at).getTime() : 0; @@ -166,8 +170,10 @@ export async function POST(req: NextRequest) { .match(matchConditions) .select() .single(); - console.log('Updated record:', updated); - if (updateError) return { error: updateError.message }; + if (updateError) { + console.log(`Failed to update ${table} record:`, JSON.stringify(dbRec)); + return { error: updateError.message }; + } authoritativeRecords.push(updated); } else { authoritativeRecords.push(serverData); diff --git a/apps/readest-app/src/services/appService.ts b/apps/readest-app/src/services/appService.ts index 86ce2dea..53daf8ef 100644 --- a/apps/readest-app/src/services/appService.ts +++ b/apps/readest-app/src/services/appService.ts @@ -16,6 +16,7 @@ import { formatAuthors, getFilename, getPrimaryLanguage, + getLibraryBackupFilename, } from '@/utils/book'; import { partialMD5 } from '@/utils/md5'; import { BookDoc, DocumentLoader, EXTS } from '@/libs/document'; @@ -537,17 +538,44 @@ export abstract class BaseAppService implements AppService { : this.getCoverImageUrl(book); } + private async loadJSONFile( + filename: string, + ): Promise<{ success: boolean; data?: unknown; error?: unknown }> { + try { + const txt = await this.fs.readFile(filename, 'Books', 'text'); + if (!txt || typeof txt !== 'string' || txt.trim().length === 0) { + return { success: false, error: 'File is empty or invalid' }; + } + try { + const data = JSON.parse(txt as string); + return { success: true, data }; + } catch (parseError) { + return { success: false, error: `JSON parse error: ${parseError}` }; + } + } catch (error) { + return { success: false, error }; + } + } + async loadLibraryBooks(): Promise { console.log('Loading library books...'); let books: Book[] = []; const libraryFilename = getLibraryFilename(); + const backupFilename = getLibraryBackupFilename(); - try { - const txt = await this.fs.readFile(libraryFilename, 'Books', 'text'); - books = JSON.parse(txt as string); - } catch { - await this.fs.createDir('', 'Books', true); - await this.fs.writeFile(libraryFilename, 'Books', '[]'); + const mainResult = await this.loadJSONFile(libraryFilename); + if (mainResult.success) { + books = mainResult.data as Book[]; + } else { + const backupResult = await this.loadJSONFile(backupFilename); + if (backupResult.success) { + books = backupResult.data as Book[]; + console.warn('Loaded library from backup file:', backupFilename); + } else { + await this.fs.createDir('', 'Books', true); + await this.fs.writeFile(libraryFilename, 'Books', '[]'); + await this.fs.writeFile(backupFilename, 'Books', '[]'); + } } await Promise.all( @@ -564,7 +592,19 @@ export abstract class BaseAppService implements AppService { async saveLibraryBooks(books: Book[]): Promise { // eslint-disable-next-line @typescript-eslint/no-unused-vars const libraryBooks = books.map(({ coverImageUrl, ...rest }) => rest); - await this.fs.writeFile(getLibraryFilename(), 'Books', JSON.stringify(libraryBooks)); + const jsonData = JSON.stringify(libraryBooks, null, 2); + const libraryFilename = getLibraryFilename(); + const backupFilename = getLibraryBackupFilename(); + + const saveResults = await Promise.allSettled([ + this.fs.writeFile(backupFilename, 'Books', jsonData), + this.fs.writeFile(libraryFilename, 'Books', jsonData), + ]); + const backupSuccess = saveResults[0].status === 'fulfilled'; + const mainSuccess = saveResults[1].status === 'fulfilled'; + if (!backupSuccess || !mainSuccess) { + throw new Error('Failed to save library books'); + } } private imageToArrayBuffer(imageUrl?: string, imageFile?: string): Promise { diff --git a/apps/readest-app/src/utils/book.ts b/apps/readest-app/src/utils/book.ts index 1c8e666d..ef640154 100644 --- a/apps/readest-app/src/utils/book.ts +++ b/apps/readest-app/src/utils/book.ts @@ -11,6 +11,9 @@ export const getDir = (book: Book) => { export const getLibraryFilename = () => { return 'library.json'; }; +export const getLibraryBackupFilename = () => { + return 'library_backup.json'; +}; export const getRemoteBookFilename = (book: Book) => { // S3 storage: https://docs.aws.amazon.com/zh_cn/AmazonS3/latest/userguide/object-keys.html if (getStorageType() === 'r2') {