Reduce queue IPC churn and cancel removed active downloads (v4.1.7)

This commit is contained in:
xRangerDE 2026-02-20 21:59:37 +01:00
parent 10b80b3f48
commit 8b508177b5
4 changed files with 63 additions and 19 deletions

View File

@ -1,12 +1,12 @@
{ {
"name": "twitch-vod-manager", "name": "twitch-vod-manager",
"version": "4.1.6", "version": "4.1.7",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "twitch-vod-manager", "name": "twitch-vod-manager",
"version": "4.1.6", "version": "4.1.7",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"axios": "^1.6.0", "axios": "^1.6.0",

View File

@ -1,6 +1,6 @@
{ {
"name": "twitch-vod-manager", "name": "twitch-vod-manager",
"version": "4.1.6", "version": "4.1.7",
"description": "Twitch VOD Manager - Download Twitch VODs easily", "description": "Twitch VOD Manager - Download Twitch VODs easily",
"main": "dist/main.js", "main": "dist/main.js",
"author": "xRangerDE", "author": "xRangerDE",

View File

@ -457,7 +457,7 @@
<div class="settings-card"> <div class="settings-card">
<h3 id="updateTitle">Updates</h3> <h3 id="updateTitle">Updates</h3>
<p id="versionInfo" style="margin-bottom: 10px; color: var(--text-secondary);">Version: v4.1.6</p> <p id="versionInfo" style="margin-bottom: 10px; color: var(--text-secondary);">Version: v4.1.7</p>
<button class="btn-secondary" id="checkUpdateBtn" onclick="checkUpdate()">Nach Updates suchen</button> <button class="btn-secondary" id="checkUpdateBtn" onclick="checkUpdate()">Nach Updates suchen</button>
</div> </div>
@ -502,7 +502,7 @@
<div class="status-dot" id="statusDot"></div> <div class="status-dot" id="statusDot"></div>
<span id="statusText">Nicht verbunden</span> <span id="statusText">Nicht verbunden</span>
</div> </div>
<span id="versionText">v4.1.6</span> <span id="versionText">v4.1.7</span>
</div> </div>
</main> </main>
</div> </div>

View File

@ -8,7 +8,7 @@ import { autoUpdater } from 'electron-updater';
// ========================================== // ==========================================
// CONFIG & CONSTANTS // CONFIG & CONSTANTS
// ========================================== // ==========================================
const APP_VERSION = '4.1.6'; const APP_VERSION = '4.1.7';
const UPDATE_CHECK_URL = 'http://24-music.de/version.json'; const UPDATE_CHECK_URL = 'http://24-music.de/version.json';
// Paths // Paths
@ -344,10 +344,13 @@ let mainWindow: BrowserWindow | null = null;
let config = loadConfig(); let config = loadConfig();
let accessToken: string | null = null; let accessToken: string | null = null;
let downloadQueue: QueueItem[] = loadQueue(); let downloadQueue: QueueItem[] = loadQueue();
let queueIdCounter = 0;
let lastQueueBroadcastFingerprint = '';
let isDownloading = false; let isDownloading = false;
let currentProcess: ChildProcess | null = null; let currentProcess: ChildProcess | null = null;
let currentDownloadCancelled = false; let currentDownloadCancelled = false;
let pauseRequested = false; let pauseRequested = false;
let activeQueueItemId: string | null = null;
let downloadStartTime = 0; let downloadStartTime = 0;
let downloadedBytes = 0; let downloadedBytes = 0;
const userIdLoginCache = new Map<string, string>(); const userIdLoginCache = new Map<string, string>();
@ -1416,6 +1419,34 @@ function getQueueCounts(queueData: QueueItem[] = downloadQueue): RuntimeMetricsS
return counts; return counts;
} }
function generateQueueItemId(): string {
queueIdCounter = (queueIdCounter + 1) % 1000;
return `${Date.now()}-${queueIdCounter}`;
}
function getQueueBroadcastFingerprint(queueData: QueueItem[] = downloadQueue): string {
return queueData.map((item) => [
item.id,
item.status,
Math.round((Number(item.progress) || 0) * 10),
item.currentPart || 0,
item.totalParts || 0,
item.speed || '',
item.eta || '',
item.last_error || ''
].join(':')).join('|');
}
function emitQueueUpdated(force = false): void {
const nextFingerprint = getQueueBroadcastFingerprint(downloadQueue);
if (!force && nextFingerprint === lastQueueBroadcastFingerprint) {
return;
}
lastQueueBroadcastFingerprint = nextFingerprint;
mainWindow?.webContents.send('queue-updated', downloadQueue);
}
function getRuntimeMetricsSnapshot(): RuntimeMetricsSnapshot { function getRuntimeMetricsSnapshot(): RuntimeMetricsSnapshot {
return { return {
...runtimeMetrics, ...runtimeMetrics,
@ -2634,7 +2665,7 @@ async function processQueue(): Promise<void> {
isDownloading = true; isDownloading = true;
pauseRequested = false; pauseRequested = false;
mainWindow?.webContents.send('download-started'); mainWindow?.webContents.send('download-started');
mainWindow?.webContents.send('queue-updated', downloadQueue); emitQueueUpdated();
while (isDownloading && !pauseRequested) { while (isDownloading && !pauseRequested) {
const item = pickNextPendingQueueItem(); const item = pickNextPendingQueueItem();
@ -2652,11 +2683,12 @@ async function processQueue(): Promise<void> {
runtimeMetrics.downloadsStarted += 1; runtimeMetrics.downloadsStarted += 1;
runtimeMetrics.activeItemId = item.id; runtimeMetrics.activeItemId = item.id;
runtimeMetrics.activeItemTitle = item.title; runtimeMetrics.activeItemTitle = item.title;
activeQueueItemId = item.id;
currentDownloadCancelled = false; currentDownloadCancelled = false;
item.status = 'downloading'; item.status = 'downloading';
saveQueue(downloadQueue); saveQueue(downloadQueue);
mainWindow?.webContents.send('queue-updated', downloadQueue); emitQueueUpdated();
item.last_error = ''; item.last_error = '';
@ -2710,7 +2742,7 @@ async function processQueue(): Promise<void> {
totalParts: item.totalParts totalParts: item.totalParts
} as DownloadProgress); } as DownloadProgress);
saveQueue(downloadQueue); saveQueue(downloadQueue);
mainWindow?.webContents.send('queue-updated', downloadQueue); emitQueueUpdated();
await sleep(retryDelaySeconds * 1000); await sleep(retryDelaySeconds * 1000);
} else { } else {
runtimeMetrics.retriesExhausted += 1; runtimeMetrics.retriesExhausted += 1;
@ -2730,6 +2762,7 @@ async function processQueue(): Promise<void> {
runtimeMetrics.activeItemId = null; runtimeMetrics.activeItemId = null;
runtimeMetrics.activeItemTitle = null; runtimeMetrics.activeItemTitle = null;
activeQueueItemId = null;
appendDebugLog('queue-item-finished', { appendDebugLog('queue-item-finished', {
itemId: item.id, itemId: item.id,
@ -2738,16 +2771,17 @@ async function processQueue(): Promise<void> {
}); });
saveQueue(downloadQueue); saveQueue(downloadQueue);
mainWindow?.webContents.send('queue-updated', downloadQueue); emitQueueUpdated();
} }
isDownloading = false; isDownloading = false;
pauseRequested = false; pauseRequested = false;
runtimeMetrics.activeItemId = null; runtimeMetrics.activeItemId = null;
runtimeMetrics.activeItemTitle = null; runtimeMetrics.activeItemTitle = null;
activeQueueItemId = null;
saveQueue(downloadQueue); saveQueue(downloadQueue);
mainWindow?.webContents.send('queue-updated', downloadQueue); emitQueueUpdated();
mainWindow?.webContents.send('download-finished'); mainWindow?.webContents.send('download-finished');
appendDebugLog('queue-finished', { items: downloadQueue.length }); appendDebugLog('queue-finished', { items: downloadQueue.length });
} }
@ -2969,27 +3003,37 @@ ipcMain.handle('add-to-queue', (_, item: Omit<QueueItem, 'id' | 'status' | 'prog
const queueItem: QueueItem = { const queueItem: QueueItem = {
...item, ...item,
id: Date.now().toString(), id: generateQueueItemId(),
status: 'pending', status: 'pending',
progress: 0 progress: 0
}; };
downloadQueue.push(queueItem); downloadQueue.push(queueItem);
saveQueue(downloadQueue); saveQueue(downloadQueue);
mainWindow?.webContents.send('queue-updated', downloadQueue); emitQueueUpdated();
return downloadQueue; return downloadQueue;
}); });
ipcMain.handle('remove-from-queue', (_, id: string) => { ipcMain.handle('remove-from-queue', (_, id: string) => {
const wasActiveItem = activeQueueItemId === id;
if (wasActiveItem) {
currentDownloadCancelled = true;
if (currentProcess) {
currentProcess.kill();
}
appendDebugLog('queue-item-removed-active-cancelled', { id });
}
downloadQueue = downloadQueue.filter(item => item.id !== id); downloadQueue = downloadQueue.filter(item => item.id !== id);
saveQueue(downloadQueue); saveQueue(downloadQueue);
mainWindow?.webContents.send('queue-updated', downloadQueue); emitQueueUpdated();
return downloadQueue; return downloadQueue;
}); });
ipcMain.handle('clear-completed', () => { ipcMain.handle('clear-completed', () => {
downloadQueue = downloadQueue.filter(item => item.status !== 'completed'); downloadQueue = downloadQueue.filter(item => item.status !== 'completed');
saveQueue(downloadQueue); saveQueue(downloadQueue);
mainWindow?.webContents.send('queue-updated', downloadQueue); emitQueueUpdated();
return downloadQueue; return downloadQueue;
}); });
@ -3003,7 +3047,7 @@ ipcMain.handle('reorder-queue', (_, orderIds: string[]) => {
downloadQueue = withOrder; downloadQueue = withOrder;
saveQueue(downloadQueue); saveQueue(downloadQueue);
mainWindow?.webContents.send('queue-updated', downloadQueue); emitQueueUpdated();
return downloadQueue; return downloadQueue;
}); });
@ -3020,7 +3064,7 @@ ipcMain.handle('retry-failed-downloads', () => {
}); });
saveQueue(downloadQueue); saveQueue(downloadQueue);
mainWindow?.webContents.send('queue-updated', downloadQueue); emitQueueUpdated();
if (!isDownloading) { if (!isDownloading) {
void processQueue(); void processQueue();
@ -3034,12 +3078,12 @@ ipcMain.handle('start-download', async () => {
const hasPendingItems = downloadQueue.some(item => item.status === 'pending'); const hasPendingItems = downloadQueue.some(item => item.status === 'pending');
if (!hasPendingItems) { if (!hasPendingItems) {
mainWindow?.webContents.send('queue-updated', downloadQueue); emitQueueUpdated();
return false; return false;
} }
saveQueue(downloadQueue); saveQueue(downloadQueue);
mainWindow?.webContents.send('queue-updated', downloadQueue); emitQueueUpdated();
processQueue(); processQueue();
return true; return true;