fix: 删书彻底清理文件与DB + 阅读位置跨设备同步
删除不彻底的根因是库里 file_path 存了旧 Windows 相对路径(uploads\xxx.epub),Linux 容器里 os.path.exists 永远为假:
- 新增 _upload_file() 归一化路径(取文件名挂到当前 uploads 目录),读/删都走它,老库迁移的文件也能定位;文件删除挪到后台任务,失败不影响接口
- 删除后对 SQLite 执行 VACUUM,让 db 文件真正缩小
- 删除时一并清理 reading_progress
阅读位置原来只存 localStorage,换设备丢失:
- 新增 reading_progress 表 + GET/PUT /books/{id}/progress 接口
- 前端打开时优先取服务端位置(本地兜底并双端拉齐),翻页防抖同步到服务端,关闭时把防抖窗口内的最后一页立即刷上去
This commit is contained in:
@@ -184,7 +184,16 @@ export default function EpubReader({ book, onClose, onAsk }: {
|
||||
setToc(entries);
|
||||
setFontSize(initialFont);
|
||||
const host = viewerRef.current!;
|
||||
const savedCfi = parseSavedCfi(readSaved(positionKey));
|
||||
// 阅读位置优先取服务端(跨设备同步),本地 localStorage 只做离线兜底
|
||||
const localCfi = parseSavedCfi(readSaved(positionKey));
|
||||
let savedCfi = localCfi;
|
||||
try {
|
||||
const progress = await api.getReadingProgress(book.id);
|
||||
if (progress?.cfi) {
|
||||
savedCfi = progress.cfi;
|
||||
if (localCfi !== progress.cfi) save(positionKey, progress.cfi); // 双端拉齐
|
||||
}
|
||||
} catch { /* 拉取失败不阻断打开,用本地位置 */ }
|
||||
restoringPositionRef.current = true;
|
||||
cfiRef.current = savedCfi;
|
||||
setContinuous(false);
|
||||
@@ -255,7 +264,12 @@ export default function EpubReader({ book, onClose, onAsk }: {
|
||||
cfiRef.current = start.cfi;
|
||||
if (restoringPositionRef.current) return;
|
||||
window.clearTimeout(saveTimer);
|
||||
saveTimer = window.setTimeout(() => { if (!disposed) save(positionKey, start.cfi!); }, 300);
|
||||
saveTimer = window.setTimeout(() => {
|
||||
if (disposed) return;
|
||||
save(positionKey, start.cfi!);
|
||||
// 同步到服务端(跨设备);失败静默,下次 relocate 会再试
|
||||
void api.saveReadingProgress(book.id, start.cfi!).catch(() => {});
|
||||
}, 300);
|
||||
});
|
||||
rendition.on("selected", (cfi: string, contents: Contents) => {
|
||||
if (disposed) return;
|
||||
@@ -299,6 +313,11 @@ export default function EpubReader({ book, onClose, onAsk }: {
|
||||
return () => {
|
||||
disposed = true;
|
||||
controller.abort();
|
||||
// 关书页时把防抖窗口里的位置立即刷到服务端,避免「最后一页丢失」
|
||||
if (cfiRef.current && !restoringPositionRef.current) {
|
||||
save(positionKey, cfiRef.current);
|
||||
void api.saveReadingProgress(book.id, cfiRef.current).catch(() => {});
|
||||
}
|
||||
window.clearTimeout(saveTimer);
|
||||
observer?.disconnect();
|
||||
renditionRef.current = null;
|
||||
|
||||
@@ -27,6 +27,12 @@ export interface ReadingNote {
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
export interface ReadingProgress {
|
||||
book_id: string;
|
||||
cfi: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
export interface Chapter {
|
||||
id: string;
|
||||
book_id: string;
|
||||
@@ -161,6 +167,10 @@ export const api = {
|
||||
return res.json() as Promise<Book>;
|
||||
},
|
||||
deleteBook: (id: string) => request<{ deleted: string }>(`/books/${id}`, { method: "DELETE" }),
|
||||
// 阅读位置(跨设备同步):服务端存最近位置,本地 localStorage 只做离线兜底
|
||||
getReadingProgress: (bookId: string) => request<ReadingProgress | null>(`/books/${bookId}/progress`),
|
||||
saveReadingProgress: (bookId: string, cfi: string) =>
|
||||
request<ReadingProgress>(`/books/${bookId}/progress`, { method: "PUT", body: JSON.stringify({ cfi }) }),
|
||||
|
||||
// Conversations
|
||||
listConversations: (bookId?: string, kind?: string) => {
|
||||
|
||||
Reference in New Issue
Block a user