Harden queue lifecycle and auth/update check concurrency (v4.1.8)

This commit is contained in:
xRangerDE 2026-02-20 23:54:55 +01:00
parent 8b508177b5
commit 4ade0a46ac
7 changed files with 60 additions and 9 deletions

View File

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

View File

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

View File

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

View File

@ -8,7 +8,7 @@ import { autoUpdater } from 'electron-updater';
// ==========================================
// CONFIG & CONSTANTS
// ==========================================
const APP_VERSION = '4.1.7';
const APP_VERSION = '4.1.8';
const UPDATE_CHECK_URL = 'http://24-music.de/version.json';
// Paths
@ -396,6 +396,7 @@ let autoUpdateStartupTimer: NodeJS.Timeout | null = null;
let autoUpdateCheckInProgress = false;
let autoUpdateReadyToInstall = false;
let lastAutoUpdateCheckAt = 0;
let twitchLoginInFlight: Promise<boolean> | null = null;
// ==========================================
// TOOL PATHS
@ -1447,6 +1448,10 @@ function emitQueueUpdated(force = false): void {
mainWindow?.webContents.send('queue-updated', downloadQueue);
}
function hasQueueItemId(id: string): boolean {
return downloadQueue.some((item) => item.id === id);
}
function getRuntimeMetricsSnapshot(): RuntimeMetricsSnapshot {
return {
...runtimeMetrics,
@ -1644,6 +1649,22 @@ async function twitchLogin(): Promise<boolean> {
}
}
function requestTwitchLogin(): Promise<boolean> {
if (twitchLoginInFlight) {
return twitchLoginInFlight;
}
let loginPromise: Promise<boolean>;
loginPromise = twitchLogin().finally(() => {
if (twitchLoginInFlight === loginPromise) {
twitchLoginInFlight = null;
}
});
twitchLoginInFlight = loginPromise;
return loginPromise;
}
async function ensureTwitchAuth(forceRefresh = false): Promise<boolean> {
if (!config.client_id || !config.client_secret) {
accessToken = null;
@ -1654,7 +1675,7 @@ async function ensureTwitchAuth(forceRefresh = false): Promise<boolean> {
return true;
}
return await twitchLogin();
return await requestTwitchLogin();
}
function normalizeLogin(input: string): string {
@ -2749,6 +2770,14 @@ async function processQueue(): Promise<void> {
}
}
if (!hasQueueItemId(item.id)) {
appendDebugLog('queue-item-finished-removed', { itemId: item.id });
runtimeMetrics.activeItemId = null;
runtimeMetrics.activeItemTitle = null;
activeQueueItemId = null;
continue;
}
const wasPaused = pauseRequested || (finalResult.error || '').includes('pausiert');
item.status = finalResult.success ? 'completed' : (wasPaused ? 'paused' : 'error');
item.progress = finalResult.success ? 100 : item.progress;
@ -2813,6 +2842,13 @@ function createWindow(): void {
mainWindow.loadFile(path.join(__dirname, '../src/index.html'));
mainWindow.webContents.on('did-finish-load', () => {
emitQueueUpdated(true);
if (isDownloading) {
mainWindow?.webContents.send('download-started');
}
});
mainWindow.on('closed', () => {
mainWindow = null;
});
@ -2961,6 +2997,7 @@ ipcMain.handle('save-config', (_, newConfig: Partial<Config>) => {
if (config.client_id !== previousClientId || config.client_secret !== previousClientSecret) {
accessToken = null;
twitchLoginInFlight = null;
}
if (config.metadata_cache_minutes !== previousCacheMinutes) {
@ -3021,6 +3058,9 @@ ipcMain.handle('remove-from-queue', (_, id: string) => {
if (currentProcess) {
currentProcess.kill();
}
activeQueueItemId = null;
runtimeMetrics.activeItemId = null;
runtimeMetrics.activeItemTitle = null;
appendDebugLog('queue-item-removed-active-cancelled', { id });
}

View File

@ -178,7 +178,7 @@ interface ApiBridge {
cutVideo(inputFile: string, startTime: number, endTime: number): Promise<{ success: boolean; outputFile: string | null }>;
mergeVideos(inputFiles: string[], outputFile: string): Promise<{ success: boolean; outputFile: string | null }>;
getVersion(): Promise<string>;
checkUpdate(): Promise<{ checking?: boolean; error?: boolean }>;
checkUpdate(): Promise<{ checking?: boolean; error?: boolean; skipped?: 'ready-to-install' | 'in-progress' | 'throttled' | 'error' | string }>;
downloadUpdate(): Promise<{ downloading?: boolean; error?: boolean }>;
installUpdate(): Promise<void>;
openExternal(url: string): Promise<void>;

View File

@ -3,7 +3,16 @@ async function checkUpdateSilent(): Promise<void> {
}
async function checkUpdate(): Promise<void> {
await window.api.checkUpdate();
const result = await window.api.checkUpdate();
if (result?.error) {
return;
}
const skippedReason = result?.skipped;
if (skippedReason === 'in-progress' || skippedReason === 'ready-to-install' || skippedReason === 'throttled') {
return;
}
setTimeout(() => {
if (byId('updateBanner').style.display !== 'flex') {

View File

@ -10,6 +10,8 @@ async function init(): Promise<void> {
config.language = language;
const initialQueue = await window.api.getQueue();
queue = Array.isArray(initialQueue) ? initialQueue : [];
downloading = await window.api.isDownloading();
markQueueActivity();
const version = await window.api.getVersion();
byId('versionText').textContent = `v${version}`;