diff --git a/api/src/main/java/telegram/files/HttpVerticle.java b/api/src/main/java/telegram/files/HttpVerticle.java index be8e9e4..694dc30 100644 --- a/api/src/main/java/telegram/files/HttpVerticle.java +++ b/api/src/main/java/telegram/files/HttpVerticle.java @@ -155,6 +155,7 @@ public class HttpVerticle extends AbstractVerticle { router.post("/file/start-download-multiple").handler(this::handleFileStartDownloadMultiple); router.post("/file/cancel-download").handler(this::handleFileCancelDownload); router.post("/file/toggle-pause-download").handler(this::handleFileTogglePauseDownload); + router.post("/file/remove").handler(this::handleFileRemove); router.post("/file/auto-download").handler(this::handleAutoDownload); router.route() @@ -673,6 +674,24 @@ public class HttpVerticle extends AbstractVerticle { .onFailure(ctx::fail); } + private void handleFileRemove(RoutingContext ctx) { + TelegramVerticle telegramVerticle = getTelegramVerticle(ctx); + if (telegramVerticle == null) { + return; + } + + JsonObject jsonObject = ctx.body().asJsonObject(); + Integer fileId = jsonObject.getInteger("fileId"); + if (fileId == null) { + ctx.fail(400); + return; + } + + telegramVerticle.removeFile(fileId) + .onSuccess(r -> ctx.end()) + .onFailure(ctx::fail); + } + private void handleAutoDownload(RoutingContext ctx) { TelegramVerticle telegramVerticle = getTelegramVerticle(ctx); if (telegramVerticle == null) { diff --git a/api/src/main/java/telegram/files/TelegramVerticle.java b/api/src/main/java/telegram/files/TelegramVerticle.java index 3e8fdc9..e858b51 100644 --- a/api/src/main/java/telegram/files/TelegramVerticle.java +++ b/api/src/main/java/telegram/files/TelegramVerticle.java @@ -17,7 +17,6 @@ import cn.hutool.log.Log; import cn.hutool.log.LogFactory; import io.vertx.core.AbstractVerticle; import io.vertx.core.Future; -import io.vertx.core.MultiMap; import io.vertx.core.Promise; import io.vertx.core.impl.NoStackTraceException; import io.vertx.core.json.Json; @@ -452,6 +451,40 @@ public class TelegramVerticle extends AbstractVerticle { .mapEmpty(); } + public Future removeFile(Integer fileId) { + return client.execute(new TdApi.GetFile(fileId)) + .compose(file -> DataVerticle.fileRepository + .getByUniqueId(file.remote.uniqueId) + .map(fileRecord -> Tuple.tuple(file, fileRecord)) + ) + .compose(tuple2 -> { + TdApi.File file = tuple2.v1; + FileRecord fileRecord = tuple2.v2; + if (fileRecord == null) { + return Future.failedFuture("File not found"); + } + + if (fileRecord.isTransferStatus(FileRecord.TransferStatus.completed)) { + if (FileUtil.del(fileRecord.localPath())) { + log.debug("[%s] Remove file success: %s".formatted(this.getRootId(), fileRecord.localPath())); + } + } + + if (file.local != null && StrUtil.isNotBlank(file.local.path)) { + return client.execute(new TdApi.DeleteFile(fileId)) + .map(file); + } + return Future.succeededFuture(file); + }) + .compose(file -> DataVerticle.fileRepository.deleteByUniqueId(file.remote.uniqueId).map(file)) + .onSuccess(file -> sendEvent(EventPayload.build(EventPayload.TYPE_FILE_STATUS, new JsonObject() + .put("fileId", fileId) + .put("uniqueId", file.remote.uniqueId) + .put("removed", true) + ))) + .mapEmpty(); + } + public Future toggleAutoDownload(Long chatId, JsonObject params) { return DataVerticle.settingRepository.getByKey(SettingKey.autoDownload) .compose(settingAutoRecords -> { diff --git a/web/src/components/file-control.tsx b/web/src/components/file-control.tsx index 6df4f72..3352b06 100644 --- a/web/src/components/file-control.tsx +++ b/web/src/components/file-control.tsx @@ -1,7 +1,7 @@ import { type DownloadStatus, type TelegramFile } from "@/lib/types"; import { useFileControl } from "@/hooks/use-file-control"; import { Button } from "@/components/ui/button"; -import { ArrowDown, Loader2, Pause, SquareX, StepForward } from "lucide-react"; +import {ArrowDown, FileX, Loader2, Pause, SquareX, StepForward} from "lucide-react"; import { Tooltip, TooltipContent, @@ -52,13 +52,9 @@ export default function FileControl({ !hovered && (file.downloadStatus === "downloading" || file.downloadStatus === "paused"); - const { start, starting, togglePause, togglingPause, cancel, cancelling } = + const { start, starting, togglePause, togglingPause, cancel, cancelling, remove, removing } = useFileControl(file); - if (file.downloadStatus === "completed") { - return null; - } - const statusMapping: Record = { idle: [ { @@ -104,7 +100,14 @@ export default function FileControl({ loading: cancelling, }, ], - completed: [], + completed: [ + { + onClick: () => remove(file.id), + tooltipText: "Remove", + icon: , + loading: removing, + }, + ], }; const actionButtons = ( diff --git a/web/src/hooks/use-file-control.tsx b/web/src/hooks/use-file-control.tsx index e92e1bd..f8d8d53 100644 --- a/web/src/hooks/use-file-control.tsx +++ b/web/src/hooks/use-file-control.tsx @@ -21,6 +21,10 @@ export function useFileControl(file: TelegramFile) { (key, { arg }: { arg: { fileId: number; isPaused: boolean } }) => POST(key, arg), ); + const { trigger: removeFile, isMutating: removing } = useSWRMutation( + "/file/remove", + (key, { arg }: { arg: { fileId: number } }) => POST(key, arg), + ); const downloadControl = { cancel: (fileId: number) => { @@ -59,9 +63,15 @@ export function useFileControl(file: TelegramFile) { }); } }, + remove: (fileId: number) => { + if (file && file.downloadStatus === "completed") { + void removeFile({ fileId }); + } + }, cancelling, starting, togglingPause, + removing, }; return { diff --git a/web/src/hooks/use-files.ts b/web/src/hooks/use-files.ts index 1c5cb54..6b5fea8 100644 --- a/web/src/hooks/use-files.ts +++ b/web/src/hooks/use-files.ts @@ -30,8 +30,8 @@ export function useFiles(accountId: string, chatId: string) { { fileId: number; downloadStatus: DownloadStatus; - localPath: string; - completionDate: number; + localPath?: string; + completionDate?: number; downloadedSize: number; transferStatus?: TransferStatus; } @@ -89,8 +89,24 @@ export function useFiles(accountId: string, chatId: string) { completionDate: number; downloadedSize: number; transferStatus?: TransferStatus; + removed?: boolean; }; + if (data.removed) { + setLatestFileStatus((prev) => ({ + ...prev, + [data.uniqueId]: { + fileId: data.fileId, + downloadStatus: "idle", + localPath: undefined, + completionDate: undefined, + downloadedSize: 0, + transferStatus: "idle", + }, + })); + return; + } + setLatestFileStatus((prev) => ({ ...prev, [data.uniqueId]: {