From 3cef529ebdb09f2d517f56536d06c6292492558e Mon Sep 17 00:00:00 2001 From: Bhscer Date: Mon, 7 Jul 2025 15:37:11 +0800 Subject: [PATCH] feat: support deleting only the cloud backup of a book (#1546) * feat: support delete the cloud storage of a book * feat: merge to delete function * fix: change a logic --- .../public/locales/zh-CN/translation.json | 3 ++ apps/readest-app/src/app/library/page.tsx | 28 +++++++++++- .../src/components/BookDetailModal.tsx | 45 +++++++++++++++++-- apps/readest-app/src/services/appService.ts | 25 ++++++----- apps/readest-app/src/types/system.ts | 2 +- 5 files changed, 87 insertions(+), 16 deletions(-) diff --git a/apps/readest-app/public/locales/zh-CN/translation.json b/apps/readest-app/public/locales/zh-CN/translation.json index b31a638a..5d6f9dae 100644 --- a/apps/readest-app/public/locales/zh-CN/translation.json +++ b/apps/readest-app/public/locales/zh-CN/translation.json @@ -127,7 +127,9 @@ "Upload Book": "上传书籍", "Auto Upload Books to Cloud": "自动上传书籍到云端", "Book deleted: {{title}}": "书籍已删除:{{title}}", + "Book's cloud store deleted: {{title}}": "书籍的云端储存已删除:{{title}}", "Failed to delete book: {{title}}": "删除书籍失败:{{title}}", + "Failed to delete book's cloud store: {{title}}": "删除书籍的云端储存失败:{{title}}", "Check Updates on Start": "启动时检查更新", "Insufficient storage quota": "云存储空间不足", "Font Weight": "字重", @@ -281,6 +283,7 @@ "Previous Page": "上一页", "Are you sure to delete {{count}} selected book(s)?_other": "您确定要删除 {{count}} 本选中的书籍吗?", "Are you sure to delete the selected book?": "您确定要删除选中的书籍吗?", + "Are you sure to delete the selected book's cloud store?": "您确定要删除选中书籍的云端储存吗?", "Deselect": "取消选择", "Select All": "全选", "No translation available.": "暂无可用的翻译", diff --git a/apps/readest-app/src/app/library/page.tsx b/apps/readest-app/src/app/library/page.tsx index fd49b695..aeb915bd 100644 --- a/apps/readest-app/src/app/library/page.tsx +++ b/apps/readest-app/src/app/library/page.tsx @@ -508,7 +508,7 @@ const LibraryPageContent = ({ searchParams }: { searchParams: ReadonlyURLSearchP const handleBookDelete = async (book: Book) => { try { - await appService?.deleteBook(book, !!book.uploadedAt); + await appService?.deleteBook(book, !!book.uploadedAt, true); await updateBook(envConfig, book); pushLibrary(); eventDispatcher.dispatch('toast', { @@ -530,6 +530,31 @@ const LibraryPageContent = ({ searchParams }: { searchParams: ReadonlyURLSearchP } }; + const handleBookDeleteCloudStore = async (book: Book) => { + try { + await appService?.deleteBook(book, !!book.uploadedAt, false); + await updateBook(envConfig, book); + pushLibrary(); + eventDispatcher.dispatch('toast', { + type: 'info', + timeout: 2000, + message: _('Book\'s cloud store deleted: {{title}}', { + title: book.title, + }), + }); + return true; + } catch (e){ + console.error(e); + eventDispatcher.dispatch('toast', { + message: _('Failed to delete book\'s cloud store: {{title}}', { + title: book.title, + }), + type: 'error', + }); + return false; + } + }; + const handleImportBooks = async () => { setIsSelectMode(false); console.log('Importing books...'); @@ -659,6 +684,7 @@ const LibraryPageContent = ({ searchParams }: { searchParams: ReadonlyURLSearchP handleBookUpload={handleBookUpload} handleBookDownload={handleBookDownload} handleBookDelete={handleBookDelete} + handleBookDeleteCloudStore={handleBookDeleteCloudStore} /> )} diff --git a/apps/readest-app/src/components/BookDetailModal.tsx b/apps/readest-app/src/components/BookDetailModal.tsx index 49ab5561..c71ec487 100644 --- a/apps/readest-app/src/components/BookDetailModal.tsx +++ b/apps/readest-app/src/components/BookDetailModal.tsx @@ -1,6 +1,6 @@ import clsx from 'clsx'; import React, { useEffect, useState } from 'react'; -import { MdDelete, MdCloudDownload, MdCloudUpload } from 'react-icons/md'; +import { MdOutlineDelete, MdOutlineCloudDownload, MdOutlineCloudUpload, MdOutlineCloudOff } from 'react-icons/md'; import { Book } from '@/types/book'; import { BookDoc } from '@/libs/document'; @@ -28,6 +28,7 @@ interface BookDetailModalProps { handleBookDownload?: (book: Book) => void; handleBookUpload?: (book: Book) => void; handleBookDelete?: (book: Book) => void; + handleBookDeleteCloudStore?: (book: Book) => void; } const BookDetailModal = ({ @@ -37,10 +38,12 @@ const BookDetailModal = ({ handleBookDownload, handleBookUpload, handleBookDelete, + handleBookDeleteCloudStore, }: BookDetailModalProps) => { const _ = useTranslation(); const [loading, setLoading] = useState(false); const [showDeleteAlert, setShowDeleteAlert] = useState(false); + const [showDeleteCloudStoreAlert, setShowDeleteCloudStoreAlert] = useState(false); const [bookMeta, setBookMeta] = useState(null); const [fileSize, setFileSize] = useState(null); const { envConfig } = useEnv(); @@ -73,6 +76,10 @@ const BookDetailModal = ({ setShowDeleteAlert(true); }; + const handleDeleteCloudStore = () => { + setShowDeleteCloudStoreAlert(true); + }; + const confirmDelete = async () => { handleClose(); setShowDeleteAlert(false); @@ -81,6 +88,14 @@ const BookDetailModal = ({ } }; + const confirmDeleteCloudStore = async () => { + handleClose(); + setShowDeleteCloudStoreAlert(false); + if (handleBookDeleteCloudStore) { + handleBookDeleteCloudStore(book); + } + }; + const handleRedownload = async () => { handleClose(); if (handleBookDownload) { @@ -132,17 +147,22 @@ const BookDetailModal = ({
{handleBookDelete && ( + )} + {book.uploadedAt && handleBookDeleteCloudStore && ( + )} {book.uploadedAt && handleBookDownload && ( )} {book.downloadedAt && handleBookUpload && ( )}
@@ -221,6 +241,23 @@ const BookDetailModal = ({ /> )} + {showDeleteCloudStoreAlert && ( +
+ { + setShowDeleteAlert(false); + }} + onConfirm={confirmDeleteCloudStore} + /> +
+ )} ); }; diff --git a/apps/readest-app/src/services/appService.ts b/apps/readest-app/src/services/appService.ts index 7d857f97..4d2fd15a 100644 --- a/apps/readest-app/src/services/appService.ts +++ b/apps/readest-app/src/services/appService.ts @@ -272,16 +272,18 @@ export abstract class BaseAppService implements AppService { } } - async deleteBook(book: Book, includingUploaded = false): Promise { - const fps = [getRemoteBookFilename(book), getCoverFilename(book)]; - const localDeleteFps = [getLocalBookFilename(book), getCoverFilename(book)]; - for (const fp of localDeleteFps) { - if (await this.fs.exists(fp, 'Books')) { - await this.fs.removeFile(fp, 'Books'); + async deleteBook(book: Book, includingUploaded = false, includingLocal = true): Promise { + if (includingLocal) { + const localDeleteFps = [getLocalBookFilename(book), getCoverFilename(book)]; + for (const fp of localDeleteFps) { + if (await this.fs.exists(fp, 'Books')) { + await this.fs.removeFile(fp, 'Books'); + } } } - for (const fp of fps) { - if (includingUploaded) { + if (includingUploaded) { + const fps = [getRemoteBookFilename(book), getCoverFilename(book)]; + for (const fp of fps) { console.log('Deleting uploaded file:', fp); const cfp = `${CLOUD_BOOKS_SUBDIR}/${fp}`; try { @@ -291,8 +293,11 @@ export abstract class BaseAppService implements AppService { } } } - book.deletedAt = Date.now(); - book.downloadedAt = null; + + if (includingLocal) { + book.deletedAt = Date.now(); + book.downloadedAt = null; + } if (includingUploaded) { book.uploadedAt = null; } diff --git a/apps/readest-app/src/types/system.ts b/apps/readest-app/src/types/system.ts index 11a78d55..75e82f3f 100644 --- a/apps/readest-app/src/types/system.ts +++ b/apps/readest-app/src/types/system.ts @@ -57,7 +57,7 @@ export interface AppService { overwrite?: boolean, transient?: boolean, ): Promise; - deleteBook(book: Book, includingUploaded?: boolean): Promise; + deleteBook(book: Book, includingUploaded?: boolean, includingLocal?: boolean): Promise; uploadBook(book: Book, onProgress?: ProgressHandler): Promise; downloadBook(book: Book, onlyCover?: boolean, onProgress?: ProgressHandler): Promise; isBookAvailable(book: Book): Promise;