- 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.
261 lines
10 KiB
TypeScript
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
|
|
};
|
|
}
|