fix(sync): more robust books sync for stale library, closes #3099 (#3108)

This commit is contained in:
Huang Xin
2026-01-29 14:05:58 +01:00
committed by GitHub
parent c58e172a54
commit bdb25999c9
3 changed files with 38 additions and 15 deletions
@@ -6,11 +6,13 @@ import { useAuth } from '@/context/AuthContext';
import { useLibraryStore } from '@/store/libraryStore';
import { SYNC_BOOKS_INTERVAL_SEC } from '@/services/constants';
import { throttle } from '@/utils/throttle';
import { debounce } from '@/utils/debounce';
export const useBooksSync = () => {
const { user } = useAuth();
const { appService } = useEnv();
const { library, isSyncing, setLibrary, setIsSyncing, setSyncProgress } = useLibraryStore();
const { library, isSyncing, libraryLoaded } = useLibraryStore();
const { setLibrary, setIsSyncing, setSyncProgress } = useLibraryStore();
const { useSyncInited, syncedBooks, syncBooks, lastSyncedAtBooks } = useSync();
const isPullingRef = useRef(false);
@@ -37,11 +39,12 @@ export const useBooksSync = () => {
}
try {
isPullingRef.current = true;
await syncBooks([], 'pull');
const library = useLibraryStore.getState().library;
await syncBooks([], 'pull', libraryLoaded && library.length === 0 ? 0 : undefined);
} finally {
isPullingRef.current = false;
}
}, [user, syncBooks]);
}, [user, libraryLoaded, syncBooks]);
// eslint-disable-next-line react-hooks/exhaustive-deps
const handleAutoSync = useCallback(
@@ -75,12 +78,11 @@ export const useBooksSync = () => {
}, [user, syncBooks, getNewBooks]);
useEffect(() => {
if (!user || !useSyncInited) return;
if (!user || !useSyncInited || !libraryLoaded) return;
pullLibrary();
}, [user, useSyncInited, pullLibrary]);
}, [user, useSyncInited, libraryLoaded, pullLibrary]);
const updateLibrary = async () => {
if (isSyncing) return;
const updateLibrary = useCallback(async () => {
if (!syncedBooks?.length) return;
// Process old books first so that when we update the library the order is preserved
@@ -149,12 +151,23 @@ export const useBooksSync = () => {
setIsSyncing(false);
}
}
};
useEffect(() => {
updateLibrary();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [syncedBooks]);
// eslint-disable-next-line react-hooks/exhaustive-deps
const debouncedUpdateLibrary = useCallback(
debounce(() => updateLibrary(), 10000),
[updateLibrary],
);
useEffect(() => {
if (isSyncing) {
debouncedUpdateLibrary();
} else {
updateLibrary();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [syncedBooks, updateLibrary, debouncedUpdateLibrary]);
return { pullLibrary, pushLibrary };
};
+11 -3
View File
@@ -1,5 +1,6 @@
import { useCallback, useEffect, useState } from 'react';
import { useRouter } from 'next/navigation';
import { useEnv } from '@/context/EnvContext';
import { useSyncContext } from '@/context/SyncContext';
import { SyncData, SyncOp, SyncResult, SyncType } from '@/libs/sync';
import { useSettingsStore } from '@/store/settingsStore';
@@ -36,7 +37,8 @@ const computeMaxTimestamp = (records: BookDataRecord[]): number => {
const ONE_DAY_IN_MS = 24 * 60 * 60 * 1000;
export function useSync(bookKey?: string) {
const router = useRouter();
const { settings, setSettings } = useSettingsStore();
const { envConfig } = useEnv();
const { settings, setSettings, saveSettings } = useSettingsStore();
const { getConfig, setConfig } = useBookDataStore();
const { setIsSyncing } = useReaderStore();
const config = bookKey ? getConfig(bookKey) : null;
@@ -152,6 +154,7 @@ export function useSync(bookKey?: string) {
}
} finally {
setSyncing(false);
saveSettings(envConfig, settings);
}
};
@@ -175,13 +178,18 @@ export function useSync(bookKey?: string) {
};
const syncBooks = useCallback(
async (books?: Book[], op: SyncOp = 'both') => {
async (books?: Book[], op: SyncOp = 'both', since?: number) => {
if (!lastSyncedAtInited) return;
if ((op === 'push' || op === 'both') && books?.length) {
await pushChanges({ books });
}
if (op === 'pull' || op === 'both') {
await pullChanges('books', lastSyncedAtBooks + 1, setLastSyncedAtBooks, setSyncingBooks);
await pullChanges(
'books',
since ?? lastSyncedAtBooks + 1,
setLastSyncedAtBooks,
setSyncingBooks,
);
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps
+3 -1
View File
@@ -6,6 +6,7 @@ import { md5Fingerprint } from '@/utils/md5';
interface LibraryState {
library: Book[]; // might contain deleted books
libraryLoaded: boolean;
isSyncing: boolean;
syncProgress: number;
checkOpenWithBooks: boolean;
@@ -36,6 +37,7 @@ interface LibraryState {
export const useLibraryStore = create<LibraryState>((set, get) => ({
library: [],
libraryLoaded: false,
isSyncing: false,
syncProgress: 0,
currentBookshelf: [],
@@ -56,7 +58,7 @@ export const useLibraryStore = create<LibraryState>((set, get) => ({
setCheckLastOpenBooks: (check) => set({ checkLastOpenBooks: check }),
setLibrary: (books) => {
const { refreshGroups } = get();
set({ library: books });
set({ library: books, libraryLoaded: true });
refreshGroups();
},
updateBook: async (envConfig: EnvConfigType, book: Book) => {