暂停下载逻辑

pull/20/head
士子☀️ 2 years ago
parent c02c2253ee
commit 613adada7d

@ -56,4 +56,9 @@ export default class DownloadController implements Controller {
await this.videoRepository.changeVideoStatus(vid, DownloadStatus.Watting);
this.downloadService.addTask(task);
}
@handle("stop-download")
async stopDownload(e: IpcMainEvent, id: number) {
this.downloadService.stopTask(id);
}
}

@ -8,6 +8,7 @@ const progressReg = /Progress:\s(\d+)\/(\d+)\s\(.+?\).+?\((.+?\/s).*?\)/g;
export const spawnDownload = (
id: number,
abortSignal: AbortController,
url: string,
local: string,
name: string
@ -21,13 +22,13 @@ export const spawnDownload = (
// name,
// ]);
const downloader = spawn(downloaderPath, [
url,
"--workDir",
local,
"--saveName",
name,
]);
const downloader = spawn(
downloaderPath,
[url, "--workDir", local, "--saveName", name],
{
signal: abortSignal.signal,
}
);
downloader.stdout.on("data", (data) => {
const str = iconv.decode(Buffer.from(data), "gbk");
@ -41,26 +42,18 @@ export const spawnDownload = (
const progress: DownloadProgress = { id, cur, total, speed };
event.emit("download-progress", progress);
}
console.log("str: ", item);
});
});
// TODO: 错误处理
downloader.stderr.on("data", (data) => {
const str = iconv.decode(Buffer.from(data), "gbk");
str.split("\n").forEach((item) => {
if (item.trim() == "") {
return;
}
});
console.log(str);
downloader.on("error", (err) => {
reject(err);
});
downloader.on("close", (code) => {
if (code === 0) {
resolve();
} else {
reject();
reject(new Error("未知错误"));
}
});
});

@ -108,12 +108,14 @@ export enum DownloadStatus {
Ready = "ready",
Watting = "watting",
Downloading = "downloading",
Stopped = "stopped",
Success = "success",
Failed = "failed",
}
export interface DownloadService extends EventEmitter {
addTask: (task: Task) => Promise<void>;
stopTask: (id: number) => Promise<void>;
}
export type Task = {

@ -36,6 +36,7 @@ declare interface ElectronAPI {
) => Promise<VideoResponse>;
startDownload: (vid: number) => Promise<void>;
openUrl: (url: string) => Promise<void>;
stopDownload: (id: number) => Promise<void>;
}
declare interface LinkMessage {

@ -22,6 +22,7 @@ const handleApi: ElectronAPI = {
openUrl: (url: string) => ipcRenderer.invoke("open-url", url),
rendererEvent: (c, l) => ipcRenderer.on(c, l),
removeEventListener: (c, l) => ipcRenderer.removeListener(c, l),
stopDownload: (id) => ipcRenderer.invoke("stop-download", id),
};
contextBridge.exposeInMainWorld(apiKey, handleApi);

@ -22,6 +22,8 @@ export default class DownloadServiceImpl
private debug = process.env.DOWNLOAD_DEBUG;
private signal: Record<number, AbortController> = {};
constructor(
@inject(TYPES.LoggerService)
private readonly logger: LoggerServiceImpl,
@ -36,6 +38,13 @@ export default class DownloadServiceImpl
this.runTask();
}
async stopTask(id: number) {
if (this.signal[id]) {
this.log(`taskId: ${id} stop`);
this.signal[id].abort();
}
}
async execute(task: Task) {
try {
await this.videoRepository.changeVideoStatus(
@ -45,7 +54,10 @@ export default class DownloadServiceImpl
this.emit("download-start", task.id);
this.log(`taskId: ${task.id} start`);
await task.process(task.id, ...task.params);
const controller = new AbortController();
this.signal[task.id] = controller;
await task.process(task.id, controller, ...task.params);
delete this.signal[task.id];
this.log(`taskId: ${task.id} success`);
await this.videoRepository.changeVideoStatus(
@ -53,15 +65,23 @@ export default class DownloadServiceImpl
DownloadStatus.Success
);
this.emit("download-success", task.id);
} catch (err) {
} catch (err: any) {
this.log(`taskId: ${task.id} failed`);
// 传输失败
// TODO: 下载失败的任务
await this.videoRepository.changeVideoStatus(
task.id,
DownloadStatus.Failed
);
// this.emit("download-failed", task.id);
if (err.name === "AbortError") {
// 下载暂停
await this.videoRepository.changeVideoStatus(
task.id,
DownloadStatus.Stopped
);
this.emit("download-stop", task.id);
} else {
// 下载失败
await this.videoRepository.changeVideoStatus(
task.id,
DownloadStatus.Failed
);
this.emit("download-failed", task.id, err);
}
} finally {
// 处理当前正在活动的任务
const doneId = this.active.findIndex((i) => i.id === task.id);

@ -55,6 +55,7 @@ export default class MainWindowServiceImpl
this.downloadService.on("download-success", this.onDownloadSuccess);
this.downloadService.on("download-failed", this.onDownloadFailed);
this.downloadService.on("download-start", this.onDownloadStart);
this.downloadService.on("download-stop", this.onDownloadStart);
}
readyToShow = () => {
@ -80,7 +81,7 @@ export default class MainWindowServiceImpl
this.webContents.send("download-success", id);
};
onDownloadFailed = async (id: number) => {
onDownloadFailed = async (id: number, err: any) => {
const promptTone = this.storeService.get("promptTone");
if (promptTone) {
const video = await this.videoRepository.findVideo(id);
@ -90,11 +91,15 @@ export default class MainWindowServiceImpl
body: `${video?.name} 下载失败`,
}).show();
}
this.logger.error("下载失败:", err);
this.webContents.send("download-failed", id);
};
onDownloadStart = async (id: number) => {
this.webContents.send("download-start", id);
};
onDownloadStop = async (id: number) => {
this.webContents.send("download-stop", id);
};
}

@ -1,12 +1,25 @@
import React, { FC, ReactNode, useEffect, useRef, useState } from "react";
import { message, Progress, Radio, RadioChangeEvent, Space, Tag } from "antd";
import {
Button,
message,
Progress,
Radio,
RadioChangeEvent,
Space,
Tag,
} from "antd";
import "./index.scss";
import PageContainer from "../../components/PageContainer";
import { usePagination } from "ahooks";
import useElectron from "../../hooks/electron";
import { DownloadStatus } from "../../types";
import { ProList } from "@ant-design/pro-components";
import { SyncOutlined } from "@ant-design/icons";
import {
FolderOpenOutlined,
PauseCircleOutlined,
PlayCircleOutlined,
SyncOutlined,
} from "@ant-design/icons";
import { useSelector } from "react-redux";
import { selectStore } from "../../store/appSlice";
import { tdApp } from "../../utils";
@ -23,6 +36,7 @@ const HomePage: FC = () => {
rendererEvent,
removeEventListener,
openDir,
stopDownload,
} = useElectron();
const appStore = useSelector(selectStore);
const [filter, setFilter] = useState(DownloadFilter.list);
@ -88,34 +102,70 @@ const HomePage: FC = () => {
await openDir(appStore.local);
};
const onClickStopDownload = async (item: DownloadItem) => {
await stopDownload(item.id);
refresh();
};
const renderActionButtons = (
dom: ReactNode,
item: DownloadItem
): ReactNode => {
if (item.status === DownloadStatus.Ready) {
return [
<a key="download" onClick={() => onStartDownload(item)}>
</a>,
<Button
type="text"
key="download"
icon={<PlayCircleOutlined />}
title="下载"
onClick={() => onStartDownload(item)}
/>,
];
}
if (item.status === DownloadStatus.Downloading) {
return [];
return [
<Button
type="text"
key="stop"
title="暂停"
icon={<PauseCircleOutlined />}
onClick={() => onClickStopDownload(item)}
/>,
];
}
if (item.status === DownloadStatus.Failed) {
return [
<a key="redownload" onClick={() => onStartDownload(item)}>
</a>,
<Button
type="text"
key="redownload"
title="重新下载"
icon={<PlayCircleOutlined />}
onClick={() => onStartDownload(item)}
/>,
];
}
if (item.status === DownloadStatus.Watting) {
return ["等待下载"];
}
if (item.status === DownloadStatus.Stopped) {
return [
<Button
type="text"
key="restart"
icon={<PlayCircleOutlined />}
title="继续下载"
onClick={() => onStartDownload(item)}
/>,
];
}
return [
<a key="redownload" onClick={onOpenDir}>
</a>,
<Button
type="text"
key="redownload"
onClick={() => onOpenDir()}
title="打开文件位置"
icon={<FolderOpenOutlined />}
/>,
];
};
@ -131,6 +181,8 @@ const HomePage: FC = () => {
tag = <Tag color="success"></Tag>;
} else if (item.status === DownloadStatus.Failed) {
tag = <Tag color="error"></Tag>;
} else if (item.status === DownloadStatus.Stopped) {
tag = <Tag color="default"></Tag>;
}
return (
<Space>

@ -46,6 +46,7 @@ declare interface ElectronAPI {
) => Promise<VideoResponse>;
startDownload: (vid: number) => Promise<void>;
openUrl: (url: string) => Promise<void>;
stopDownload: (id: number) => Promise<void>;
}
declare interface Favorite {

@ -4,4 +4,5 @@ export enum DownloadStatus {
Success = "success",
Failed = "failed",
Watting = "watting",
Stopped = "stopped",
}

Loading…
Cancel
Save