Files
ERitors-Scribe-Desktop/hooks/useSyncBooks.ts
natreex 64ed90d993 Remove unused components and models for improved maintainability
- Deleted redundant components (`AddActionButton`, `AlertBox`, `AlertStack`, `BackButton`, `CancelButton`, and `CollapsableArea`) and related files.
- Removed unused models (`Book`, `BookSerie`, `BookTables`, `Character`, and `Chapter`) to reduce codebase clutter.
- Updated project structure and references to reflect these removals.
2026-03-22 22:37:31 -04:00

261 lines
10 KiB
TypeScript

import {useContext} from 'react';
import {apiGet, apiPost} from '@/lib/api/client';
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/types/book-tables';
import {BookSyncCompare, SyncedBook} from '@/lib/types/synced-book';
import {useTranslations} from '@/lib/i18n';
import * as tauri from '@/lib/tauri';
interface RemovedItemRecord {
removal_id: string;
table_name: string;
entity_id: string;
book_id: string | null;
user_id: string;
deleted_at: number;
}
interface SyncedBooksResponse {
books: SyncedBook[];
tombstones: RemovedItemRecord[];
}
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,
setBooksToSyncFromServer,
setBooksToSyncToServer
} = useContext(BooksSyncContext);
async function upload(bookId: string): Promise<boolean> {
if (isCurrentlyOffline()) return false;
try {
const bookToSync: CompleteBook = await tauri.uploadBookToServer(bookId) as CompleteBook;
if (!bookToSync) {
errorMessage(t('bookCard.uploadError'));
return false;
}
const response: boolean = await apiPost('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 apiGet('book/sync/download', session.accessToken, lang, {bookId});
if (!response) {
errorMessage(t('bookCard.downloadError'));
return false;
}
const syncStatus: boolean = await tauri.syncSaveBook(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 apiPost('book/sync/server-to-client', {
bookToSync: bookToFetch
}, session.accessToken, lang);
if (!response) {
errorMessage(t('bookCard.syncFromServerError'));
return false;
}
const syncStatus: boolean = await tauri.syncBookToClient(response);
if (!syncStatus) {
errorMessage(t('bookCard.syncFromServerError'));
return false;
}
setBooksToSyncFromServer((prev: BookSyncCompare[]): BookSyncCompare[] =>
prev.filter((book: BookSyncCompare): boolean => book.id !== bookId)
);
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 tauri.syncBookToServer(bookToFetch) as CompleteBook;
if (!bookToSync) {
errorMessage(t('bookCard.syncToServerError'));
return false;
}
const response: boolean = await apiPatch('book/sync/client-to-server', {
book: bookToSync
}, session.accessToken, lang);
if (!response) {
errorMessage(t('bookCard.syncToServerError'));
return false;
}
setBooksToSyncToServer((prev: BookSyncCompare[]): BookSyncCompare[] =>
prev.filter((book: BookSyncCompare): boolean => book.id !== bookId)
);
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 syncAllFromServer(): Promise<void> {
for (const diff of booksToSyncFromServer) {
await syncFromServer(diff.id);
}
}
async function refreshBooks(): Promise<void> {
try {
let localBooksResponse: SyncedBook[] = [];
let serverBooksResponse: SyncedBook[] = [];
if (!isCurrentlyOffline()) {
if (offlineMode.isDatabaseInitialized) {
localBooksResponse = await tauri.getSyncedBooks() as SyncedBook[];
const lastOnlineStr: string | null = localStorage.getItem('lastOnlineTimestamp');
const lastOnlineTimestamp: number = lastOnlineStr ? parseInt(lastOnlineStr, 10) : 0;
const localTombstones: RemovedItemRecord[] = await tauri.getTombstonesSince(lastOnlineTimestamp) as RemovedItemRecord[];
const serverResponse: SyncedBooksResponse = await apiPost<SyncedBooksResponse>(
'books/synced',
{ lastOnlineTimestamp, tombstones: localTombstones },
session.accessToken,
lang
);
serverBooksResponse = serverResponse.books;
await tauri.applyBookTombstones(serverResponse.tombstones as tauri.TombstoneRecord[]);
} else {
// No local DB but online - just get server books without tombstones
const serverResponse: SyncedBooksResponse = await apiPost<SyncedBooksResponse>(
'books/synced',
{ lastOnlineTimestamp: 0, tombstones: [] },
session.accessToken,
lang
);
serverBooksResponse = serverResponse.books;
}
} else {
if (offlineMode.isDatabaseInitialized) {
localBooksResponse = await tauri.getSyncedBooks() as SyncedBook[];
}
}
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,
syncAllFromServer,
refreshBooks,
localOnlyBooks,
serverOnlyBooks,
booksToSyncToServer,
booksToSyncFromServer
};
}