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",
|
"name": "twitch-vod-manager",
|
||||||
"version": "3.8.0",
|
"version": "3.8.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "twitch-vod-manager",
|
"name": "twitch-vod-manager",
|
||||||
"version": "3.8.0",
|
"version": "3.8.1",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^1.6.0",
|
"axios": "^1.6.0",
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "twitch-vod-manager",
|
"name": "twitch-vod-manager",
|
||||||
"version": "3.8.0",
|
"version": "3.8.1",
|
||||||
"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",
|
||||||
|
|||||||
@ -335,7 +335,7 @@
|
|||||||
|
|
||||||
<div class="settings-card">
|
<div class="settings-card">
|
||||||
<h3>Updates</h3>
|
<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>
|
<button class="btn-secondary" onclick="checkUpdate()">Nach Updates suchen</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -346,7 +346,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">v3.8.0</span>
|
<span id="versionText">v3.8.1</span>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,14 +1,14 @@
|
|||||||
import { app, BrowserWindow, ipcMain, dialog, shell } from 'electron';
|
import { app, BrowserWindow, ipcMain, dialog, shell } from 'electron';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as fs from 'fs';
|
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 axios from 'axios';
|
||||||
import { autoUpdater } from 'electron-updater';
|
import { autoUpdater } from 'electron-updater';
|
||||||
|
|
||||||
// ==========================================
|
// ==========================================
|
||||||
// CONFIG & CONSTANTS
|
// CONFIG & CONSTANTS
|
||||||
// ==========================================
|
// ==========================================
|
||||||
const APP_VERSION = '3.8.0';
|
const APP_VERSION = '3.8.1';
|
||||||
const UPDATE_CHECK_URL = 'http://24-music.de/version.json';
|
const UPDATE_CHECK_URL = 'http://24-music.de/version.json';
|
||||||
|
|
||||||
// Paths
|
// 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 {
|
try {
|
||||||
fs.mkdirSync(destinationDir, { recursive: true });
|
fs.mkdirSync(destinationDir, { recursive: true });
|
||||||
execFileSync('powershell', [
|
|
||||||
|
const command = `Expand-Archive -Path '${zipPath.replace(/'/g, "''")}' -DestinationPath '${destinationDir.replace(/'/g, "''")}' -Force`;
|
||||||
|
|
||||||
|
await new Promise<void>((resolve, reject) => {
|
||||||
|
const proc = spawn('powershell', [
|
||||||
'-NoProfile',
|
'-NoProfile',
|
||||||
'-ExecutionPolicy', 'Bypass',
|
'-ExecutionPolicy', 'Bypass',
|
||||||
'-Command',
|
'-Command',
|
||||||
`Expand-Archive -Path '${zipPath.replace(/'/g, "''")}' -DestinationPath '${destinationDir.replace(/'/g, "''")}' -Force`
|
command
|
||||||
], { windowsHide: true, stdio: 'ignore' });
|
], { 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;
|
return true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
appendDebugLog('extract-zip-failed', { zipPath, destinationDir, error: String(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.rmSync(TOOLS_STREAMLINK_DIR, { recursive: true, force: true });
|
||||||
fs.mkdirSync(TOOLS_STREAMLINK_DIR, { recursive: 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 { }
|
try { fs.unlinkSync(zipPath); } catch { }
|
||||||
if (!extractOk) return false;
|
if (!extractOk) return false;
|
||||||
|
|
||||||
@ -368,7 +389,7 @@ async function ensureFfmpegInstalled(): Promise<boolean> {
|
|||||||
fs.rmSync(TOOLS_FFMPEG_DIR, { recursive: true, force: true });
|
fs.rmSync(TOOLS_FFMPEG_DIR, { recursive: true, force: true });
|
||||||
fs.mkdirSync(TOOLS_FFMPEG_DIR, { recursive: 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 { }
|
try { fs.unlinkSync(zipPath); } catch { }
|
||||||
if (!extractOk) return false;
|
if (!extractOk) return false;
|
||||||
|
|
||||||
@ -1653,12 +1674,7 @@ ipcMain.handle('save-video-dialog', async (_, defaultName: string) => {
|
|||||||
app.whenReady().then(() => {
|
app.whenReady().then(() => {
|
||||||
refreshBundledToolPaths();
|
refreshBundledToolPaths();
|
||||||
createWindow();
|
createWindow();
|
||||||
|
appendDebugLog('startup-tools-check-skipped', 'Deferred to first use');
|
||||||
void (async () => {
|
|
||||||
const streamlinkOk = await ensureStreamlinkInstalled();
|
|
||||||
const ffmpegOk = await ensureFfmpegInstalled();
|
|
||||||
appendDebugLog('startup-tools-check', { streamlinkOk, ffmpegOk });
|
|
||||||
})();
|
|
||||||
|
|
||||||
app.on('activate', () => {
|
app.on('activate', () => {
|
||||||
if (BrowserWindow.getAllWindows().length === 0) {
|
if (BrowserWindow.getAllWindows().length === 0) {
|
||||||
|
|||||||
@ -21,7 +21,7 @@ async function init(): Promise<void> {
|
|||||||
updateDownloadButtonState();
|
updateDownloadButtonState();
|
||||||
|
|
||||||
window.api.onQueueUpdated((q: QueueItem[]) => {
|
window.api.onQueueUpdated((q: QueueItem[]) => {
|
||||||
queue = Array.isArray(q) ? q : [];
|
queue = mergeQueueState(Array.isArray(q) ? q : []);
|
||||||
renderQueue();
|
renderQueue();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -82,6 +82,33 @@ async function init(): Promise<void> {
|
|||||||
}, 2000);
|
}, 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 {
|
function updateDownloadButtonState(): void {
|
||||||
const btn = byId('btnStart');
|
const btn = byId('btnStart');
|
||||||
btn.textContent = downloading ? 'Stoppen' : 'Start';
|
btn.textContent = downloading ? 'Stoppen' : 'Start';
|
||||||
@ -90,7 +117,7 @@ function updateDownloadButtonState(): void {
|
|||||||
|
|
||||||
async function syncQueueAndDownloadState(): Promise<void> {
|
async function syncQueueAndDownloadState(): Promise<void> {
|
||||||
const latestQueue = await window.api.getQueue();
|
const latestQueue = await window.api.getQueue();
|
||||||
queue = Array.isArray(latestQueue) ? latestQueue : [];
|
queue = mergeQueueState(Array.isArray(latestQueue) ? latestQueue : []);
|
||||||
renderQueue();
|
renderQueue();
|
||||||
|
|
||||||
const backendDownloading = await window.api.isDownloading();
|
const backendDownloading = await window.api.isDownloading();
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user