release: 4.2.4 improve updater and queue persistence

This commit is contained in:
xRangerDE 2026-03-06 02:48:07 +01:00
parent b7cd8fbec2
commit 47df9664a4
10 changed files with 69 additions and 9 deletions

4
package-lock.json generated
View File

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

View File

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

View File

@ -450,6 +450,10 @@
<input type="checkbox" id="duplicatePreventionToggle" checked>
<span id="duplicatePreventionLabel">Duplikate in Queue verhindern</span>
</label>
<label style="display:flex; align-items:center; gap:8px; margin-top: 8px;">
<input type="checkbox" id="persistQueueToggle" checked>
<span id="persistQueueLabel">Queue zwischen App-Starts speichern</span>
</label>
</div>
<div class="form-group">
<label id="metadataCacheMinutesLabel">Metadata-Cache (Minuten)</label>

View File

@ -83,6 +83,7 @@ interface Config {
smart_queue_scheduler: boolean;
performance_mode: PerformanceMode;
prevent_duplicate_downloads: boolean;
persist_queue_on_restart: boolean;
metadata_cache_minutes: number;
}
@ -238,6 +239,7 @@ const defaultConfig: Config = {
smart_queue_scheduler: true,
performance_mode: DEFAULT_PERFORMANCE_MODE,
prevent_duplicate_downloads: true,
persist_queue_on_restart: true,
metadata_cache_minutes: DEFAULT_METADATA_CACHE_MINUTES
};
@ -272,6 +274,7 @@ function normalizeConfigTemplates(input: Config): Config {
smart_queue_scheduler: input.smart_queue_scheduler !== false,
performance_mode: normalizePerformanceMode(input.performance_mode),
prevent_duplicate_downloads: input.prevent_duplicate_downloads !== false,
persist_queue_on_restart: input.persist_queue_on_restart !== false,
metadata_cache_minutes: normalizeMetadataCacheMinutes(input.metadata_cache_minutes)
};
}
@ -300,6 +303,10 @@ function saveConfig(config: Config): void {
// QUEUE MANAGEMENT
// ==========================================
function loadQueue(): QueueItem[] {
if (config.persist_queue_on_restart === false) {
return [];
}
try {
if (fs.existsSync(QUEUE_FILE)) {
const data = fs.readFileSync(QUEUE_FILE, 'utf-8');
@ -314,7 +321,22 @@ function loadQueue(): QueueItem[] {
let queueSaveTimer: NodeJS.Timeout | null = null;
let pendingQueueSnapshot: QueueItem[] | null = null;
function clearQueueFileFromDisk(): void {
try {
if (fs.existsSync(QUEUE_FILE)) {
fs.unlinkSync(QUEUE_FILE);
}
} catch (e) {
console.error('Error clearing queue file:', e);
}
}
function writeQueueToDisk(queue: QueueItem[]): void {
if (config.persist_queue_on_restart === false) {
clearQueueFileFromDisk();
return;
}
try {
fs.writeFileSync(QUEUE_FILE, JSON.stringify(queue, null, 2));
} catch (e) {
@ -323,6 +345,16 @@ function writeQueueToDisk(queue: QueueItem[]): void {
}
function saveQueue(queue: QueueItem[], force = false): void {
if (config.persist_queue_on_restart === false) {
pendingQueueSnapshot = null;
if (queueSaveTimer) {
clearTimeout(queueSaveTimer);
queueSaveTimer = null;
}
clearQueueFileFromDisk();
return;
}
pendingQueueSnapshot = queue;
if (force) {
@ -3207,6 +3239,7 @@ function setupAutoUpdater() {
autoUpdaterInitialized = true;
autoUpdater.autoDownload = false;
autoUpdater.autoInstallOnAppQuit = true;
autoUpdater.autoRunAppAfterInstall = true;
autoUpdater.on('checking-for-update', () => {
console.log('Checking for updates...');
@ -3308,6 +3341,7 @@ ipcMain.handle('save-config', (_, newConfig: Partial<Config>) => {
const previousClientId = config.client_id;
const previousClientSecret = config.client_secret;
const previousCacheMinutes = config.metadata_cache_minutes;
const previousPersistQueueOnRestart = config.persist_queue_on_restart;
config = normalizeConfigTemplates({ ...config, ...newConfig });
@ -3321,6 +3355,18 @@ ipcMain.handle('save-config', (_, newConfig: Partial<Config>) => {
}
saveConfig(config);
if (config.persist_queue_on_restart === false) {
pendingQueueSnapshot = null;
if (queueSaveTimer) {
clearTimeout(queueSaveTimer);
queueSaveTimer = null;
}
clearQueueFileFromDisk();
} else if (previousPersistQueueOnRestart === false) {
saveQueue(downloadQueue, true);
}
return config;
});
@ -3526,7 +3572,7 @@ ipcMain.handle('download-update', async () => {
});
ipcMain.handle('install-update', () => {
autoUpdater.quitAndInstall(false, true);
autoUpdater.quitAndInstall(true, true);
});
ipcMain.handle('open-external', async (_, url: string) => {

View File

@ -13,6 +13,7 @@ interface AppConfig {
smart_queue_scheduler?: boolean;
performance_mode?: 'stability' | 'balanced' | 'speed';
prevent_duplicate_downloads?: boolean;
persist_queue_on_restart?: boolean;
metadata_cache_minutes?: number;
[key: string]: unknown;
}

View File

@ -46,6 +46,7 @@ const UI_TEXT_DE = {
performanceModeSpeed: 'Max Geschwindigkeit',
smartSchedulerLabel: 'Smart Queue Scheduler aktivieren',
duplicatePreventionLabel: 'Duplikate in Queue verhindern',
persistQueueLabel: 'Queue zwischen App-Starts speichern',
metadataCacheMinutesLabel: 'Metadata-Cache (Minuten)',
filenameTemplatesTitle: 'Dateinamen-Templates',
vodTemplateLabel: 'VOD-Template',
@ -208,11 +209,11 @@ const UI_TEXT_DE = {
downloadNow: 'Jetzt herunterladen',
downloadLabel: 'Download',
ready: 'bereit zur Installation!',
installNow: 'Jetzt installieren',
installNow: 'Jetzt installieren & neu starten',
modalAvailableTitle: 'Update verfugbar',
modalAvailableMessage: 'Version {version} ist verfugbar. Jetzt herunterladen?',
modalReadyTitle: 'Update bereit',
modalReadyMessage: 'Version {version} wurde heruntergeladen. Jetzt installieren?',
modalReadyMessage: 'Version {version} wurde heruntergeladen. Jetzt installieren und neu starten?',
modalDismiss: 'Nein',
modalDownloadConfirm: 'Ja, herunterladen',
modalInstallConfirm: 'Ja, installieren',

View File

@ -46,6 +46,7 @@ const UI_TEXT_EN = {
performanceModeSpeed: 'Max Speed',
smartSchedulerLabel: 'Enable smart queue scheduler',
duplicatePreventionLabel: 'Prevent duplicate queue entries',
persistQueueLabel: 'Keep queue between app restarts',
metadataCacheMinutesLabel: 'Metadata Cache (Minutes)',
filenameTemplatesTitle: 'Filename Templates',
vodTemplateLabel: 'VOD Template',
@ -208,11 +209,11 @@ const UI_TEXT_EN = {
downloadNow: 'Download now',
downloadLabel: 'Download',
ready: 'ready to install!',
installNow: 'Install now',
installNow: 'Install now & restart',
modalAvailableTitle: 'Update available',
modalAvailableMessage: 'Version {version} is available. Download it now?',
modalReadyTitle: 'Update ready',
modalReadyMessage: 'Version {version} has been downloaded. Install it now?',
modalReadyMessage: 'Version {version} has been downloaded. Install and restart now?',
modalDismiss: 'No',
modalDownloadConfirm: 'Yes, download',
modalInstallConfirm: 'Yes, install',

View File

@ -318,6 +318,7 @@ function collectDownloadSettingsPayload(): Partial<AppConfig> {
performance_mode: byId<HTMLSelectElement>('performanceMode').value as 'stability' | 'balanced' | 'speed',
smart_queue_scheduler: byId<HTMLInputElement>('smartSchedulerToggle').checked,
prevent_duplicate_downloads: byId<HTMLInputElement>('duplicatePreventionToggle').checked,
persist_queue_on_restart: byId<HTMLInputElement>('persistQueueToggle').checked,
metadata_cache_minutes: parseInt(byId<HTMLInputElement>('metadataCacheMinutes').value, 10) || 10
};
}
@ -358,6 +359,7 @@ function getSettingsFingerprint(payload: Partial<AppConfig>): string {
effective.performance_mode ?? 'balanced',
effective.smart_queue_scheduler !== false,
effective.prevent_duplicate_downloads !== false,
effective.persist_queue_on_restart !== false,
effective.metadata_cache_minutes ?? 10,
effective.filename_template_vod ?? '{title}.mp4',
effective.filename_template_parts ?? '{date}_Part{part_padded}.mp4',
@ -373,6 +375,7 @@ function syncSettingsFormFromConfig(): void {
byId<HTMLSelectElement>('performanceMode').value = (config.performance_mode as string) || 'balanced';
byId<HTMLInputElement>('smartSchedulerToggle').checked = (config.smart_queue_scheduler as boolean) !== false;
byId<HTMLInputElement>('duplicatePreventionToggle').checked = (config.prevent_duplicate_downloads as boolean) !== false;
byId<HTMLInputElement>('persistQueueToggle').checked = (config.persist_queue_on_restart as boolean) !== false;
byId<HTMLInputElement>('metadataCacheMinutes').value = String((config.metadata_cache_minutes as number) || 10);
byId<HTMLInputElement>('vodFilenameTemplate').value = (config.filename_template_vod as string) || '{title}.mp4';
byId<HTMLInputElement>('partsFilenameTemplate').value = (config.filename_template_parts as string) || '{date}_Part{part_padded}.mp4';
@ -481,7 +484,8 @@ function initSettingsAutoSave(): void {
'downloadMode',
'performanceMode',
'smartSchedulerToggle',
'duplicatePreventionToggle'
'duplicatePreventionToggle',
'persistQueueToggle'
] as const;
const debouncedSaveIds = [

View File

@ -88,6 +88,7 @@ function applyLanguageToStaticUI(): void {
setText('performanceModeSpeed', UI_TEXT.static.performanceModeSpeed);
setText('smartSchedulerLabel', UI_TEXT.static.smartSchedulerLabel);
setText('duplicatePreventionLabel', UI_TEXT.static.duplicatePreventionLabel);
setText('persistQueueLabel', UI_TEXT.static.persistQueueLabel);
setText('metadataCacheMinutesLabel', UI_TEXT.static.metadataCacheMinutesLabel);
setText('filenameTemplatesTitle', UI_TEXT.static.filenameTemplatesTitle);
setText('vodTemplateLabel', UI_TEXT.static.vodTemplateLabel);

View File

@ -366,8 +366,10 @@ function refreshUpdateUiTexts(): void {
async function checkUpdateSilent(): Promise<void> {
try {
shouldOpenUpdateModalOnAvailable = true;
await window.api.checkUpdate();
} catch {
shouldOpenUpdateModalOnAvailable = false;
// ignore silent updater errors
}
}