feat: node 伪终端获取详细日志

pull/91/head
士子☀️ 11 months ago
parent c2fee8d372
commit 535b9f6f07

@ -1,6 +1,5 @@
APP_TD_APPID=
APP_CLARITY_APPID=
APP_DOWNLOAD_DEBUG=true
GH_TOKEN=
LOAD_DEVTOOLS=

@ -1,5 +1,4 @@
APP_TD_APPID=
APP_CLARITY_APPID=
APP_DOWNLOAD_DEBUG=
GH_TOKEN=

@ -56,9 +56,9 @@
"electron-store": "^8.1.0",
"execa": "^8.0.1",
"fs-extra": "^11.2.0",
"iconv-lite": "^0.6.3",
"inversify": "^6.0.2",
"lodash": "^4.17.21",
"node-pty": "^1.0.0",
"reflect-metadata": "^0.2.1",
"ts-node": "^10.9.2",
"typeorm": "^0.3.19"

@ -4,4 +4,5 @@ export const external = [
"aws-sdk",
"mock-aws-s3",
"@cliqz/adblocker-electron-preload",
"node-pty",
];

@ -10,8 +10,6 @@ import { TYPES } from "../types";
import LoggerServiceImpl from "./LoggerService";
import StoreService from "./StoreService";
import VideoRepository from "../repository/VideoRepository";
import { execa, Options as ExecaOptions } from "execa";
import iconv from "iconv-lite";
import {
biliDownloaderBin,
formatHeaders,
@ -19,12 +17,12 @@ import {
m3u8DownloaderBin,
stripColors,
} from "../helper";
import * as pty from "node-pty";
export interface DownloadOptions {
abortSignal: AbortController;
encoding?: string;
onMessage?: (message: string) => void;
onErrMessage?: (message: string) => void;
}
@injectable()
@ -35,8 +33,6 @@ export default class DownloadService extends EventEmitter {
private limit: number;
private debug = process.env.APP_DOWNLOAD_DEBUG;
private signal: Record<number, AbortController> = {};
constructor(
@ -64,7 +60,7 @@ export default class DownloadService extends EventEmitter {
async stopTask(id: number) {
if (this.signal[id]) {
this.log(`taskId: ${id} stop`);
this.logger.info(`taskId: ${id} stop`);
this.signal[id].abort();
}
}
@ -77,7 +73,7 @@ export default class DownloadService extends EventEmitter {
);
this.emit("download-start", task.id);
this.log(`taskId: ${task.id} start`);
this.logger.info(`taskId: ${task.id} start`);
const controller = new AbortController();
this.signal[task.id] = controller;
@ -106,7 +102,7 @@ export default class DownloadService extends EventEmitter {
await this.process(params);
delete this.signal[task.id];
this.log(`taskId: ${task.id} success`);
this.logger.info(`taskId: ${task.id} success`);
await this.videoRepository.changeVideoStatus(
task.id,
@ -114,8 +110,8 @@ export default class DownloadService extends EventEmitter {
);
this.emit("download-success", task.id);
} catch (err: any) {
this.log(`taskId: ${task.id} failed`);
if (err.name === "AbortError") {
this.logger.info(`taskId: ${task.id} failed`);
if (err.message === "AbortError") {
// 下载暂停
await this.videoRepository.changeVideoStatus(
task.id,
@ -160,60 +156,45 @@ export default class DownloadService extends EventEmitter {
}
}
log(...args: unknown[]) {
if (this.debug) {
this.logger.info(`[DownloadService] `, ...args);
}
}
private _execa(
binPath: string,
args: string[],
params: DownloadOptions & ExecaOptions,
params: DownloadOptions,
): Promise<void> {
const {
abortSignal,
encoding = "utf-8",
onMessage,
onErrMessage,
...execOptions
} = params;
const { abortSignal, onMessage } = params;
return new Promise((resolve, reject) => {
const process = (data: Buffer, callback: (message: string) => void) => {
const items = iconv.decode(data, encoding);
items.split("\n").forEach((item) => {
const message = item.trim();
if (!message) return;
try {
this.logger.debug("DownloadService: ", message);
callback(message);
} catch (err) {
reject(err);
}
});
const process = (lines: string, callback: (message: string) => void) => {
const message = lines.replace(/\\./g, "");
if (!message) return;
try {
console.log(message);
callback(message);
} catch (err) {
reject(err);
}
};
const downloader = execa(binPath, args, {
...execOptions,
signal: abortSignal.signal,
console.log("args", binPath, args);
const ptyProcess = pty.spawn(binPath, args, {
cols: 200,
rows: 100,
});
if (downloader.stdout && onMessage) {
downloader.stdout.on("data", (data) => process(data, onMessage));
}
if (downloader.stderr && onErrMessage) {
downloader.stderr.on("data", (data) => process(data, onErrMessage));
if (onMessage) {
ptyProcess.onData((data) => process(data, onMessage));
}
downloader.on("error", (err) => {
reject(err);
abortSignal.signal.addEventListener("abort", () => {
ptyProcess.kill();
});
downloader.on("close", (code) => {
if (code === 0) {
ptyProcess.onExit(({ exitCode, signal }) => {
console.log("exitCode", exitCode, "signal", signal);
if (exitCode === 0 && signal === 0) {
resolve();
} else if (exitCode === 0 && signal === 1) {
reject(new Error("AbortError"));
} else {
reject(new Error("未知错误"));
}
@ -232,8 +213,6 @@ export default class DownloadService extends EventEmitter {
const spawnParams = [url, "--work-dir", local];
await this._execa(biliDownloaderBin, spawnParams, {
detached: true,
shell: true,
abortSignal,
onMessage: (message) => {
if (isLiveReg.test(message) || startDownloadReg.test(message)) {

@ -118,18 +118,21 @@ importers:
fs-extra:
specifier: ^11.2.0
version: 11.2.0
iconv-lite:
specifier: ^0.6.3
version: 0.6.3
inversify:
specifier: ^6.0.2
version: 6.0.2
lodash:
specifier: ^4.17.21
version: 4.17.21
node-pty:
specifier: ^1.0.0
version: 1.0.0
reflect-metadata:
specifier: ^0.2.1
version: 0.2.1
strip-final-newline:
specifier: ^4.0.0
version: 4.0.0
ts-node:
specifier: ^10.9.2
version: 10.9.2(@types/node@20.11.5)(typescript@5.3.3)
@ -7027,6 +7030,7 @@ packages:
engines: {node: '>=0.10.0'}
dependencies:
safer-buffer: 2.1.2
dev: true
/ieee754@1.2.1:
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
@ -8173,6 +8177,10 @@ packages:
thenify-all: 1.6.0
dev: false
/nan@2.18.0:
resolution: {integrity: sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w==}
dev: false
/nanoid@3.3.7:
resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
@ -8265,6 +8273,13 @@ packages:
- supports-color
dev: true
/node-pty@1.0.0:
resolution: {integrity: sha512-wtBMWWS7dFZm/VgqElrTvtfMq4GzJ6+edFI0Y0zyzygUSZMgZdraDUMUhCIvkjhJjme15qWmbyJbtAx4ot4uZA==}
requiresBuild: true
dependencies:
nan: 2.18.0
dev: false
/nopt@6.0.0:
resolution: {integrity: sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==}
engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}
@ -9869,6 +9884,7 @@ packages:
/safer-buffer@2.1.2:
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
dev: true
/sanitize-filename@1.6.3:
resolution: {integrity: sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg==}
@ -10308,6 +10324,11 @@ packages:
resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==}
engines: {node: '>=12'}
/strip-final-newline@4.0.0:
resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==}
engines: {node: '>=18'}
dev: false
/strip-indent@3.0.0:
resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==}
engines: {node: '>=8'}

Loading…
Cancel
Save