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", "name": "twitch-vod-manager",
"version": "4.2.3", "version": "4.2.4",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "twitch-vod-manager", "name": "twitch-vod-manager",
"version": "4.2.3", "version": "4.2.4",
"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.2.3", "version": "4.2.4",
"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

@ -450,6 +450,10 @@
<input type="checkbox" id="duplicatePreventionToggle" checked> <input type="checkbox" id="duplicatePreventionToggle" checked>
<span id="duplicatePreventionLabel">Duplikate in Queue verhindern</span> <span id="duplicatePreventionLabel">Duplikate in Queue verhindern</span>
</label> </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>
<div class="form-group"> <div class="form-group">
<label id="metadataCacheMinutesLabel">Metadata-Cache (Minuten)</label> <label id="metadataCacheMinutesLabel">Metadata-Cache (Minuten)</label>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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