Feat: Implement download cancellation feature with stop button and progress handling

This commit is contained in:
MasterAcnolo
2026-04-12 11:22:18 +02:00
parent d05b9e2511
commit 60137a15dc
7 changed files with 145 additions and 20 deletions

View File

@@ -1,4 +1,4 @@
const { fetchDownload } = require("../services/download.services");
const { fetchDownload, cancelDownload } = require("../services/download.services");
const { logger } = require("../logger");
const { isValidUrl, isSafePath } = require("../helpers/validation.helpers");
const { notifyDownloadFinished } = require("../helpers/notify.helpers");
@@ -64,5 +64,15 @@ function speedController(req, res) {
});
}
function cancelDownloadController(req, res) {
const cancelled = cancelDownload();
if (cancelled) {
logger.info("Download and queue cancelled by user");
res.send("✅ Download stopped! Queue cleared.");
} else {
logger.warn("No download to cancel");
res.status(400).send("❌ No download in progress !");
}
}
module.exports = { downloadController, progressController, speedController};
module.exports = { downloadController, progressController, speedController, cancelDownloadController};

View File

@@ -1,8 +1,9 @@
const express = require("express");
const router = express.Router();
const { downloadController, progressController, speedController } = require("../controller/download.controller");
const { downloadController, progressController, speedController, cancelDownloadController } = require("../controller/download.controller");
router.post("/", downloadController);
router.post("/cancel", cancelDownloadController);
// SSE for download progress
router.get("/progress", progressController);

View File

@@ -7,6 +7,8 @@ const notify = require("../helpers/notify.helpers");
const path = require("path");
const { isSafePath } = require("../helpers/validation.helpers");
let currentDownloadProcess = null;
function fetchDownload(options, listeners, speedListeners) {
return new Promise((resolve, reject) => {
@@ -33,12 +35,15 @@ function fetchDownload(options, listeners, speedListeners) {
logger.info(`[yt-dlp args] ${args.join(" ")}`);
const child = execFile(userYtDlp, args);
currentDownloadProcess = child;
child.on("error", err => reject(new Error(`YT-DLP Error : ${err.message}`)));
child.on("close", code => {
currentDownloadProcess = null;
listeners.forEach(fn => fn("done"));
if (code === 0) resolve(safeOutputFolder);
else if (code === null) reject(new Error(`Download cancelled by user`));
else reject(new Error(`YT-DLP failed with code : ${code}`));
});
@@ -76,4 +81,31 @@ function fetchDownload(options, listeners, speedListeners) {
});
}
module.exports = { fetchDownload };
function cancelDownload() {
if (currentDownloadProcess) {
logger.info("Cancelling download and killing all child processes...");
// Force kill the process and all its children with SIGKILL
try {
// Try to kill with SIGKILL on Windows (process group) or Unix
if (process.platform === 'win32') {
// On Windows, kill the process tree
require('child_process').execSync(`taskkill /PID ${currentDownloadProcess.pid} /T /F`, { stdio: 'ignore' });
} else {
// On Unix, send SIGKILL to process group
process.kill(-currentDownloadProcess.pid, 'SIGKILL');
}
} catch (err) {
logger.warn(`Error killing process: ${err.message}`);
// Fallback to regular kill
currentDownloadProcess.kill('SIGKILL');
}
currentDownloadProcess = null;
logger.info("Download cancelled successfully");
return true;
}
return false;
}
module.exports = { fetchDownload, cancelDownload };