Files
ERitors-Scribe-Desktop/hooks/useSyncBooks.ts
natreex 7f34421212 Add error handling, enhance syncing, and refactor deletion logic
- Introduce new error messages for syncing and book deletion in `en.json`.
- Update `DeleteBook` to support local-only deletion and synced book management.
- Refine offline/online behavior with `deleteLocalToo` checkbox and update related state handling.
- Extend repository and IPC methods to handle optional IDs for updates.
- Add `SyncQueueContext` for queueing offline changes and improving synchronization workflows.
- Enhance refined text generation logic in `DraftCompanion` and `GhostWriter` components.
- Replace PUT with PATCH for world updates to align with API expectations.
- Streamline `AlertBox` by integrating dynamic translation keys for deletion prompts.
2026-01-10 15:50:03 -05:00

207 lines
8.1 KiB
TypeScript

import {useContext} from 'react';
import System from '@/lib/models/System';
import {SessionContext} from '@/context/SessionContext';
import {LangContext} from '@/context/LangContext';
import {AlertContext} from '@/context/AlertContext';
import OfflineContext from '@/context/OfflineContext';
import {BooksSyncContext} from '@/context/BooksSyncContext';
import {CompleteBook} from '@/lib/models/Book';
import {BookSyncCompare, SyncedBook} from '@/lib/models/SyncedBook';
import {useTranslations} from 'next-intl';
export default function useSyncBooks() {
const t = useTranslations();
const {session} = useContext(SessionContext);
const {lang} = useContext(LangContext);
const {errorMessage} = useContext(AlertContext);
const {isCurrentlyOffline, offlineMode} = useContext(OfflineContext);
const {
booksToSyncToServer,
booksToSyncFromServer,
localOnlyBooks,
serverOnlyBooks,
setLocalOnlyBooks,
setServerOnlyBooks,
setServerSyncedBooks,
setLocalSyncedBooks
} = useContext(BooksSyncContext);
async function upload(bookId: string): Promise<boolean> {
if (isCurrentlyOffline()) return false;
try {
const bookToSync: CompleteBook = await window.electron.invoke<CompleteBook>('db:book:uploadToServer', bookId);
if (!bookToSync) {
errorMessage(t('bookCard.uploadError'));
return false;
}
const response: boolean = await System.authPostToServer('book/sync/upload', {
book: bookToSync
}, session.accessToken, lang);
if (!response) {
errorMessage(t('bookCard.uploadError'));
return false;
}
const uploadedBook: SyncedBook | undefined = localOnlyBooks.find((book: SyncedBook): boolean => book.id === bookId);
setLocalOnlyBooks((prevBooks: SyncedBook[]): SyncedBook[] => {
return prevBooks.filter((book: SyncedBook): boolean => book.id !== bookId);
});
if (uploadedBook) {
setLocalSyncedBooks((prev: SyncedBook[]): SyncedBook[] => [...prev, uploadedBook]);
setServerSyncedBooks((prev: SyncedBook[]): SyncedBook[] => [...prev, uploadedBook]);
}
return true;
} catch (e: unknown) {
if (e instanceof Error) {
errorMessage(e.message);
} else {
errorMessage(t('bookCard.uploadError'));
}
return false;
}
}
async function download(bookId: string): Promise<boolean> {
if (isCurrentlyOffline()) return false;
try {
const response: CompleteBook = await System.authGetQueryToServer('book/sync/download', session.accessToken, lang, {bookId});
if (!response) {
errorMessage(t('bookCard.downloadError'));
return false;
}
const syncStatus: boolean = await window.electron.invoke<boolean>('db:book:syncSave', response);
if (!syncStatus) {
errorMessage(t('bookCard.downloadError'));
return false;
}
const downloadedBook: SyncedBook | undefined = serverOnlyBooks.find((book: SyncedBook): boolean => book.id === bookId);
setServerOnlyBooks((prevBooks: SyncedBook[]): SyncedBook[] => {
return prevBooks.filter((book: SyncedBook): boolean => book.id !== bookId);
});
if (downloadedBook) {
setLocalSyncedBooks((prev: SyncedBook[]): SyncedBook[] => [...prev, downloadedBook]);
setServerSyncedBooks((prev: SyncedBook[]): SyncedBook[] => [...prev, downloadedBook]);
}
return true;
} catch (e: unknown) {
if (e instanceof Error) {
errorMessage(e.message);
} else {
errorMessage(t('bookCard.downloadError'));
}
return false;
}
}
async function syncFromServer(bookId: string): Promise<boolean> {
if (isCurrentlyOffline()) return false;
try {
const bookToFetch: BookSyncCompare | undefined = booksToSyncFromServer.find((book: BookSyncCompare): boolean => book.id === bookId);
if (!bookToFetch) {
errorMessage(t('bookCard.syncFromServerError'));
return false;
}
const response: CompleteBook = await System.authPostToServer('book/sync/server-to-client', {
bookToSync: bookToFetch
}, session.accessToken, lang);
if (!response) {
errorMessage(t('bookCard.syncFromServerError'));
return false;
}
const syncStatus: boolean = await window.electron.invoke<boolean>('db:book:sync:toClient', response);
if (!syncStatus) {
errorMessage(t('bookCard.syncFromServerError'));
return false;
}
return true;
} catch (e: unknown) {
if (e instanceof Error) {
errorMessage(e.message);
} else {
errorMessage(t('bookCard.syncFromServerError'));
}
return false;
}
}
async function syncToServer(bookId: string): Promise<boolean> {
if (isCurrentlyOffline()) return false;
try {
const bookToFetch: BookSyncCompare | undefined = booksToSyncToServer.find((book: BookSyncCompare): boolean => book.id === bookId);
if (!bookToFetch) {
errorMessage(t('bookCard.syncToServerError'));
return false;
}
const bookToSync: CompleteBook = await window.electron.invoke<CompleteBook>('db:book:sync:toServer', bookToFetch);
if (!bookToSync) {
errorMessage(t('bookCard.syncToServerError'));
return false;
}
const response: boolean = await System.authPatchToServer('book/sync/client-to-server', {
book: bookToSync
}, session.accessToken, lang);
if (!response) {
errorMessage(t('bookCard.syncToServerError'));
return false;
}
return true;
} catch (e: unknown) {
if (e instanceof Error) {
errorMessage(e.message);
} else {
errorMessage(t('bookCard.syncToServerError'));
}
return false;
}
}
async function syncAllToServer(): Promise<void> {
for (const diff of booksToSyncToServer) {
await syncToServer(diff.id);
}
}
async function refreshBooks(): Promise<void> {
try {
let localBooksResponse: SyncedBook[] = [];
let serverBooksResponse: SyncedBook[] = [];
if (!isCurrentlyOffline()) {
if (offlineMode.isDatabaseInitialized) {
localBooksResponse = await window.electron.invoke<SyncedBook[]>('db:books:synced');
}
serverBooksResponse = await System.authGetQueryToServer<SyncedBook[]>('books/synced', session.accessToken, lang);
} else {
if (offlineMode.isDatabaseInitialized) {
localBooksResponse = await window.electron.invoke<SyncedBook[]>('db:books:synced');
}
}
setServerSyncedBooks(serverBooksResponse);
setLocalSyncedBooks(localBooksResponse);
} catch (e: unknown) {
if (e instanceof Error) {
errorMessage(e.message);
} else {
errorMessage(t('bookCard.refreshError'));
}
}
}
return {
upload,
download,
syncFromServer,
syncToServer,
syncAllToServer,
refreshBooks,
localOnlyBooks,
serverOnlyBooks,
booksToSyncToServer,
booksToSyncFromServer
};
}