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
This commit is contained in:
Bhscer
2025-07-07 15:37:11 +08:00
committed by GitHub
parent 85ac399e2c
commit 3cef529ebd
5 changed files with 87 additions and 16 deletions
@@ -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.": "暂无可用的翻译",
+27 -1
View File
@@ -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}
/>
)}
<AboutWindow />
@@ -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<BookDoc['metadata'] | null>(null);
const [fileSize, setFileSize] = useState<number | null>(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 = ({
<div className='flex flex-wrap items-center gap-x-4'>
{handleBookDelete && (
<button onClick={handleDelete}>
<MdDelete className='fill-red-500' />
<MdOutlineDelete className='fill-red-500' />
</button>
)}
{book.uploadedAt && handleBookDeleteCloudStore && (
<button onClick={handleDeleteCloudStore}>
<MdOutlineCloudOff className='fill-red-500' />
</button>
)}
{book.uploadedAt && handleBookDownload && (
<button onClick={handleRedownload}>
<MdCloudDownload className='fill-base-content' />
<MdOutlineCloudDownload className='fill-base-content' />
</button>
)}
{book.downloadedAt && handleBookUpload && (
<button onClick={handleReupload}>
<MdCloudUpload className='fill-base-content' />
<MdOutlineCloudUpload className='fill-base-content' />
</button>
)}
</div>
@@ -221,6 +241,23 @@ const BookDetailModal = ({
/>
</div>
)}
{showDeleteCloudStoreAlert && (
<div
className={clsx(
'fixed bottom-0 left-0 right-0 z-50 flex justify-center',
'pb-[calc(env(safe-area-inset-bottom)+16px)]',
)}
>
<Alert
title={_('Confirm Deletion')}
message={_('Are you sure to delete the selected book\'s cloud store?')}
onCancel={() => {
setShowDeleteAlert(false);
}}
onConfirm={confirmDeleteCloudStore}
/>
</div>
)}
</div>
);
};
+15 -10
View File
@@ -272,16 +272,18 @@ export abstract class BaseAppService implements AppService {
}
}
async deleteBook(book: Book, includingUploaded = false): Promise<void> {
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<void> {
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;
}
+1 -1
View File
@@ -57,7 +57,7 @@ export interface AppService {
overwrite?: boolean,
transient?: boolean,
): Promise<Book | null>;
deleteBook(book: Book, includingUploaded?: boolean): Promise<void>;
deleteBook(book: Book, includingUploaded?: boolean, includingLocal?: boolean): Promise<void>;
uploadBook(book: Book, onProgress?: ProgressHandler): Promise<void>;
downloadBook(book: Book, onlyCover?: boolean, onProgress?: ProgressHandler): Promise<void>;
isBookAvailable(book: Book): Promise<boolean>;