Fix startup stall and queue status flicker (v3.8.1)
Defer heavy tool auto-install checks from startup to first-use paths to avoid long launch stalls, and merge backend queue snapshots with in-flight renderer progress state so download status text no longer jumps between placeholder states.
This commit is contained in:
parent
3d404d75e1
commit
159f442d43
4
typescript-version/package-lock.json
generated
4
typescript-version/package-lock.json
generated
@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "twitch-vod-manager",
|
||||
"version": "3.8.0",
|
||||
"version": "3.8.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "twitch-vod-manager",
|
||||
"version": "3.8.0",
|
||||
"version": "3.8.1",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"axios": "^1.6.0",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "twitch-vod-manager",
|
||||
"version": "3.8.0",
|
||||
"version": "3.8.1",
|
||||
"description": "Twitch VOD Manager - Download Twitch VODs easily",
|
||||
"main": "dist/main.js",
|
||||
"author": "xRangerDE",
|
||||
|
||||
@ -335,7 +335,7 @@
|
||||
|
||||
<div class="settings-card">
|
||||
<h3>Updates</h3>
|
||||
<p id="versionInfo" style="margin-bottom: 10px; color: var(--text-secondary);">Version: v3.8.0</p>
|
||||
<p id="versionInfo" style="margin-bottom: 10px; color: var(--text-secondary);">Version: v3.8.1</p>
|
||||
<button class="btn-secondary" onclick="checkUpdate()">Nach Updates suchen</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -346,7 +346,7 @@
|
||||
<div class="status-dot" id="statusDot"></div>
|
||||
<span id="statusText">Nicht verbunden</span>
|
||||
</div>
|
||||
<span id="versionText">v3.8.0</span>
|
||||
<span id="versionText">v3.8.1</span>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
import { app, BrowserWindow, ipcMain, dialog, shell } from 'electron';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import { spawn, ChildProcess, execSync, exec, execFileSync, spawnSync } from 'child_process';
|
||||
import { spawn, ChildProcess, execSync, exec, spawnSync } from 'child_process';
|
||||
import axios from 'axios';
|
||||
import { autoUpdater } from 'electron-updater';
|
||||
|
||||
// ==========================================
|
||||
// CONFIG & CONSTANTS
|
||||
// ==========================================
|
||||
const APP_VERSION = '3.8.0';
|
||||
const APP_VERSION = '3.8.1';
|
||||
const UPDATE_CHECK_URL = 'http://24-music.de/version.json';
|
||||
|
||||
// Paths
|
||||
@ -273,15 +273,36 @@ async function downloadFile(url: string, destinationPath: string): Promise<boole
|
||||
}
|
||||
}
|
||||
|
||||
function extractZip(zipPath: string, destinationDir: string): boolean {
|
||||
async function extractZip(zipPath: string, destinationDir: string): Promise<boolean> {
|
||||
try {
|
||||
fs.mkdirSync(destinationDir, { recursive: true });
|
||||
execFileSync('powershell', [
|
||||
'-NoProfile',
|
||||
'-ExecutionPolicy', 'Bypass',
|
||||
'-Command',
|
||||
`Expand-Archive -Path '${zipPath.replace(/'/g, "''")}' -DestinationPath '${destinationDir.replace(/'/g, "''")}' -Force`
|
||||
], { windowsHide: true, stdio: 'ignore' });
|
||||
|
||||
const command = `Expand-Archive -Path '${zipPath.replace(/'/g, "''")}' -DestinationPath '${destinationDir.replace(/'/g, "''")}' -Force`;
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const proc = spawn('powershell', [
|
||||
'-NoProfile',
|
||||
'-ExecutionPolicy', 'Bypass',
|
||||
'-Command',
|
||||
command
|
||||
], { windowsHide: true });
|
||||
|
||||
let stderr = '';
|
||||
proc.stderr?.on('data', (data) => {
|
||||
stderr += data.toString();
|
||||
});
|
||||
|
||||
proc.on('close', (code) => {
|
||||
if (code === 0) {
|
||||
resolve();
|
||||
} else {
|
||||
reject(new Error(`Expand-Archive exit code ${code}: ${stderr.trim()}`));
|
||||
}
|
||||
});
|
||||
|
||||
proc.on('error', (err) => reject(err));
|
||||
});
|
||||
|
||||
return true;
|
||||
} catch (e) {
|
||||
appendDebugLog('extract-zip-failed', { zipPath, destinationDir, error: String(e) });
|
||||
@ -327,7 +348,7 @@ async function ensureStreamlinkInstalled(): Promise<boolean> {
|
||||
fs.rmSync(TOOLS_STREAMLINK_DIR, { recursive: true, force: true });
|
||||
fs.mkdirSync(TOOLS_STREAMLINK_DIR, { recursive: true });
|
||||
|
||||
const extractOk = extractZip(zipPath, TOOLS_STREAMLINK_DIR);
|
||||
const extractOk = await extractZip(zipPath, TOOLS_STREAMLINK_DIR);
|
||||
try { fs.unlinkSync(zipPath); } catch { }
|
||||
if (!extractOk) return false;
|
||||
|
||||
@ -368,7 +389,7 @@ async function ensureFfmpegInstalled(): Promise<boolean> {
|
||||
fs.rmSync(TOOLS_FFMPEG_DIR, { recursive: true, force: true });
|
||||
fs.mkdirSync(TOOLS_FFMPEG_DIR, { recursive: true });
|
||||
|
||||
const extractOk = extractZip(zipPath, TOOLS_FFMPEG_DIR);
|
||||
const extractOk = await extractZip(zipPath, TOOLS_FFMPEG_DIR);
|
||||
try { fs.unlinkSync(zipPath); } catch { }
|
||||
if (!extractOk) return false;
|
||||
|
||||
@ -1653,12 +1674,7 @@ ipcMain.handle('save-video-dialog', async (_, defaultName: string) => {
|
||||
app.whenReady().then(() => {
|
||||
refreshBundledToolPaths();
|
||||
createWindow();
|
||||
|
||||
void (async () => {
|
||||
const streamlinkOk = await ensureStreamlinkInstalled();
|
||||
const ffmpegOk = await ensureFfmpegInstalled();
|
||||
appendDebugLog('startup-tools-check', { streamlinkOk, ffmpegOk });
|
||||
})();
|
||||
appendDebugLog('startup-tools-check-skipped', 'Deferred to first use');
|
||||
|
||||
app.on('activate', () => {
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
|
||||
@ -21,7 +21,7 @@ async function init(): Promise<void> {
|
||||
updateDownloadButtonState();
|
||||
|
||||
window.api.onQueueUpdated((q: QueueItem[]) => {
|
||||
queue = Array.isArray(q) ? q : [];
|
||||
queue = mergeQueueState(Array.isArray(q) ? q : []);
|
||||
renderQueue();
|
||||
});
|
||||
|
||||
@ -82,6 +82,33 @@ async function init(): Promise<void> {
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
function mergeQueueState(nextQueue: QueueItem[]): QueueItem[] {
|
||||
const prevById = new Map(queue.map((item) => [item.id, item]));
|
||||
|
||||
return nextQueue.map((item) => {
|
||||
const prev = prevById.get(item.id);
|
||||
if (!prev) {
|
||||
return item;
|
||||
}
|
||||
|
||||
if (item.status !== 'downloading') {
|
||||
return item;
|
||||
}
|
||||
|
||||
return {
|
||||
...item,
|
||||
progress: item.progress > 0 ? item.progress : prev.progress,
|
||||
speed: item.speed || prev.speed,
|
||||
eta: item.eta || prev.eta,
|
||||
currentPart: item.currentPart || prev.currentPart,
|
||||
totalParts: item.totalParts || prev.totalParts,
|
||||
downloadedBytes: item.downloadedBytes || prev.downloadedBytes,
|
||||
totalBytes: item.totalBytes || prev.totalBytes,
|
||||
progressStatus: item.progressStatus || prev.progressStatus
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function updateDownloadButtonState(): void {
|
||||
const btn = byId('btnStart');
|
||||
btn.textContent = downloading ? 'Stoppen' : 'Start';
|
||||
@ -90,7 +117,7 @@ function updateDownloadButtonState(): void {
|
||||
|
||||
async function syncQueueAndDownloadState(): Promise<void> {
|
||||
const latestQueue = await window.api.getQueue();
|
||||
queue = Array.isArray(latestQueue) ? latestQueue : [];
|
||||
queue = mergeQueueState(Array.isArray(latestQueue) ? latestQueue : []);
|
||||
renderQueue();
|
||||
|
||||
const backendDownloading = await window.api.isDownloading();
|
||||
|
||||
Loading…
Reference in New Issue
Block a user