diff --git a/apps/readest-app/src/app/library/hooks/useBooksSync.ts b/apps/readest-app/src/app/library/hooks/useBooksSync.ts index 33b47f74..14d23e1a 100644 --- a/apps/readest-app/src/app/library/hooks/useBooksSync.ts +++ b/apps/readest-app/src/app/library/hooks/useBooksSync.ts @@ -11,9 +11,13 @@ export const useBooksSync = () => { const { appService } = useEnv(); const { library, setLibrary } = useLibraryStore(); const { syncedBooks, syncBooks, lastSyncedAtBooks } = useSync(); + const syncBooksPullingRef = useRef(false); useEffect(() => { if (!user) return; + if (syncBooksPullingRef.current) return; + syncBooksPullingRef.current = true; + syncBooks([], 'pull'); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); @@ -61,12 +65,12 @@ export const useBooksSync = () => { if (!matchingBook.deletedAt && matchingBook.uploadedAt && !oldBook.downloadedAt) { await appService?.downloadBook(oldBook, true); } - return { - ...oldBook, - ...matchingBook, - updatedAt: oldBook.updatedAt, - progress: matchingBook.progress ?? oldBook.progress, - }; + const mergedBook = + matchingBook.updatedAt > oldBook.updatedAt + ? { ...oldBook, ...matchingBook, updatedAt: oldBook.updatedAt } + : { ...matchingBook, ...oldBook, updatedAt: oldBook.updatedAt }; + mergedBook.progress = matchingBook.progress ?? oldBook.progress; + return mergedBook; } return oldBook; }; diff --git a/apps/readest-app/src/app/reader/components/ReaderContent.tsx b/apps/readest-app/src/app/reader/components/ReaderContent.tsx index 1a2f1715..862d10a7 100644 --- a/apps/readest-app/src/app/reader/components/ReaderContent.tsx +++ b/apps/readest-app/src/app/reader/components/ReaderContent.tsx @@ -91,6 +91,7 @@ const ReaderContent: React.FC<{ ids?: string; settings: SystemSettings }> = ({ i const { book } = getBookData(bookKey) || {}; const { isPrimary } = getViewState(bookKey) || {}; if (isPrimary && book && config) { + eventDispatcher.dispatch('sync-book-progress', { bookKey }); const settings = useSettingsStore.getState().settings; await saveConfig(envConfig, bookKey, config, settings); } diff --git a/apps/readest-app/src/app/reader/hooks/useProgressSync.ts b/apps/readest-app/src/app/reader/hooks/useProgressSync.ts index 021e5ddf..8925758a 100644 --- a/apps/readest-app/src/app/reader/hooks/useProgressSync.ts +++ b/apps/readest-app/src/app/reader/hooks/useProgressSync.ts @@ -7,6 +7,7 @@ import { useReaderStore } from '@/store/readerStore'; import { useSettingsStore } from '@/store/settingsStore'; import { useTranslation } from '@/hooks/useTranslation'; import { deserializeConfig, serializeConfig } from '@/utils/serializer'; +import { CFI } from '@/libs/document'; import { eventDispatcher } from '@/utils/event'; import { DEFAULT_BOOK_SEARCH_CONFIG, SYNC_PROGRESS_INTERVAL_SEC } from '@/services/constants'; @@ -49,6 +50,21 @@ export const useProgressSync = (bookKey: string) => { } }; + const handleSyncBookProgress = (event: CustomEvent) => { + const { bookKey: syncBookKey } = event.detail; + if (syncBookKey === bookKey) { + syncConfig(); + } + }; + + useEffect(() => { + eventDispatcher.on('sync-book-progress', handleSyncBookProgress); + return () => { + eventDispatcher.off('sync-book-progress', handleSyncBookProgress); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [bookKey]); + useEffect(() => { if (!progress || firstPulled.current) return; firstPulled.current = true; @@ -96,12 +112,12 @@ export const useProgressSync = (bookKey: string) => { DEFAULT_BOOK_SEARCH_CONFIG, ); setConfig(bookKey, { ...config, ...newConfig }); - if ((syncedConfig.progress?.[1] ?? 0) > 0 && (config?.progress?.[1] ?? 0) > 0) { - const syncedFraction = syncedConfig.progress![0] / syncedConfig.progress![1]; - const configFraction = config!.progress![0] / config!.progress![1]; - if (syncedFraction > configFraction) { + const syncedCFI = syncedConfig.location; + const configCFI = config?.location; + if (syncedCFI && configCFI) { + if (CFI.compare(configCFI, syncedCFI) < 0) { if (view) { - view.goToFraction(syncedFraction); + view.goTo(syncedCFI); eventDispatcher.dispatch('toast', { type: 'success', message: _('Reading Progress Synced'), diff --git a/apps/readest-app/src/hooks/useSync.ts b/apps/readest-app/src/hooks/useSync.ts index a28d91de..194ffaf4 100644 --- a/apps/readest-app/src/hooks/useSync.ts +++ b/apps/readest-app/src/hooks/useSync.ts @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; import { useSyncContext } from '@/context/SyncContext'; import { SyncData, SyncOp, SyncResult, SyncType } from '@/libs/sync'; import { useSettingsStore } from '@/store/settingsStore'; @@ -40,22 +40,11 @@ export function useSync(bookKey?: string) { const [syncingBooks, setSyncingBooks] = useState(false); const [syncingConfigs, setSyncingConfigs] = useState(false); const [syncingNotes, setSyncingNotes] = useState(false); - const [syncError, setSyncError] = useState(null); - - const lastSyncedBooksAt = settings.lastSyncedAtBooks ?? 0; - const lastSyncedConfigsAt = config?.lastSyncedAtConfig ?? settings.lastSyncedAtConfigs ?? 0; - const lastSyncedNotesAt = config?.lastSyncedAtNotes ?? settings.lastSyncedAtNotes ?? 0; - - const [lastSyncedAtBooks, setLastSyncedAtBooks] = useState( - lastSyncedBooksAt > 0 ? lastSyncedBooksAt - SEVEN_DAYS_IN_MS : 0, - ); - const [lastSyncedAtConfigs, setLastSyncedAtConfigs] = useState( - lastSyncedConfigsAt > 0 ? lastSyncedConfigsAt - SEVEN_DAYS_IN_MS : 0, - ); - const [lastSyncedAtNotes, setLastSyncedAtNotes] = useState( - lastSyncedNotesAt > 0 ? lastSyncedNotesAt - SEVEN_DAYS_IN_MS : 0, - ); + const [lastSyncedAtBooks, setLastSyncedAtBooks] = useState(0); + const [lastSyncedAtConfigs, setLastSyncedAtConfigs] = useState(0); + const [lastSyncedAtNotes, setLastSyncedAtNotes] = useState(0); + const lastSyncedAtInited = useRef(false); const [syncing, setSyncing] = useState(false); // null means unsynced, empty array means synced no changes @@ -70,6 +59,19 @@ export function useSync(bookKey?: string) { const { syncClient } = useSyncContext(); + useEffect(() => { + if (!settings || !config) return; + if (lastSyncedAtInited.current) return; + lastSyncedAtInited.current = true; + + const lastSyncedBooksAt = settings.lastSyncedAtBooks ?? 0; + const lastSyncedConfigsAt = config?.lastSyncedAtConfig ?? settings.lastSyncedAtConfigs ?? 0; + const lastSyncedNotesAt = config?.lastSyncedAtNotes ?? settings.lastSyncedAtNotes ?? 0; + setLastSyncedAtBooks(lastSyncedBooksAt > 0 ? lastSyncedBooksAt - SEVEN_DAYS_IN_MS : 0); + setLastSyncedAtConfigs(lastSyncedConfigsAt > 0 ? lastSyncedConfigsAt - SEVEN_DAYS_IN_MS : 0); + setLastSyncedAtNotes(lastSyncedNotesAt > 0 ? lastSyncedNotesAt - SEVEN_DAYS_IN_MS : 0); + }, [settings, config]); + // bookId is for configs and notes only, if bookId is provided, only pull changes for that book // and update the lastSyncedAt for that book in the book config const pullChanges = async ( diff --git a/apps/readest-app/src/utils/event.ts b/apps/readest-app/src/utils/event.ts index 17554a9c..b82a35b4 100644 --- a/apps/readest-app/src/utils/event.ts +++ b/apps/readest-app/src/utils/event.ts @@ -1,20 +1,20 @@ class EventDispatcher { private syncListeners: Map boolean>>; - private asyncListeners: Map Promise>>; + private asyncListeners: Map Promise | void>>; constructor() { this.syncListeners = new Map(); this.asyncListeners = new Map(); } - on(event: string, callback: (event: CustomEvent) => Promise): void { + on(event: string, callback: (event: CustomEvent) => Promise | void): void { if (!this.asyncListeners.has(event)) { this.asyncListeners.set(event, new Set()); } this.asyncListeners.get(event)!.add(callback); } - off(event: string, callback: (event: CustomEvent) => Promise): void { + off(event: string, callback: (event: CustomEvent) => Promise | void): void { this.asyncListeners.get(event)?.delete(callback); }