forked from akai/readest
sync: use the location CFI to eliminate occasional one-off page glitches (#268)
This commit is contained in:
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -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<string | null>(null);
|
||||
|
||||
const lastSyncedBooksAt = settings.lastSyncedAtBooks ?? 0;
|
||||
const lastSyncedConfigsAt = config?.lastSyncedAtConfig ?? settings.lastSyncedAtConfigs ?? 0;
|
||||
const lastSyncedNotesAt = config?.lastSyncedAtNotes ?? settings.lastSyncedAtNotes ?? 0;
|
||||
|
||||
const [lastSyncedAtBooks, setLastSyncedAtBooks] = useState<number>(
|
||||
lastSyncedBooksAt > 0 ? lastSyncedBooksAt - SEVEN_DAYS_IN_MS : 0,
|
||||
);
|
||||
const [lastSyncedAtConfigs, setLastSyncedAtConfigs] = useState<number>(
|
||||
lastSyncedConfigsAt > 0 ? lastSyncedConfigsAt - SEVEN_DAYS_IN_MS : 0,
|
||||
);
|
||||
const [lastSyncedAtNotes, setLastSyncedAtNotes] = useState<number>(
|
||||
lastSyncedNotesAt > 0 ? lastSyncedNotesAt - SEVEN_DAYS_IN_MS : 0,
|
||||
);
|
||||
const [lastSyncedAtBooks, setLastSyncedAtBooks] = useState<number>(0);
|
||||
const [lastSyncedAtConfigs, setLastSyncedAtConfigs] = useState<number>(0);
|
||||
const [lastSyncedAtNotes, setLastSyncedAtNotes] = useState<number>(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 (
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
class EventDispatcher {
|
||||
private syncListeners: Map<string, Set<(event: CustomEvent) => boolean>>;
|
||||
private asyncListeners: Map<string, Set<(event: CustomEvent) => Promise<void>>>;
|
||||
private asyncListeners: Map<string, Set<(event: CustomEvent) => Promise<void> | void>>;
|
||||
|
||||
constructor() {
|
||||
this.syncListeners = new Map();
|
||||
this.asyncListeners = new Map();
|
||||
}
|
||||
|
||||
on(event: string, callback: (event: CustomEvent) => Promise<void>): void {
|
||||
on(event: string, callback: (event: CustomEvent) => Promise<void> | 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>): void {
|
||||
off(event: string, callback: (event: CustomEvent) => Promise<void> | void): void {
|
||||
this.asyncListeners.get(event)?.delete(callback);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user