diff --git a/lib/config-store.js b/lib/config-store.js index 0d02fe6..add1e54 100644 --- a/lib/config-store.js +++ b/lib/config-store.js @@ -40,6 +40,16 @@ const DEFAULTS = { suffix: '', chars: 'both', // 'letters' | 'numbers' | 'both' length: 0 // 0 = same as original basename length + }, + folderMonitor: { + enabled: false, + folderPath: '', + recursive: false, + filterMode: 'include', // 'include' | 'exclude' + extensions: '', // comma-separated: 'mp4,mkv,avi' + skipDuplicates: true, + delaySec: 3, + autoStart: true } }, history: [] @@ -116,10 +126,18 @@ class ConfigStore { ...(data.hosterSettings && data.hosterSettings[name] || {}) }; } + const savedGlobal = data.globalSettings || {}; const globalSettings = { ...DEFAULTS.globalSettings, - ...(data.globalSettings || {}) + ...savedGlobal }; + // Deep-merge nested objects so new keys are always present + for (const key of Object.keys(DEFAULTS.globalSettings)) { + const def = DEFAULTS.globalSettings[key]; + if (def && typeof def === 'object' && !Array.isArray(def)) { + globalSettings[key] = { ...def, ...(savedGlobal[key] || {}) }; + } + } return { hosters, hosterSettings, globalSettings, history: data.history || [] }; } catch { return JSON.parse(JSON.stringify(DEFAULTS)); diff --git a/lib/folder-monitor.js b/lib/folder-monitor.js new file mode 100644 index 0000000..6fde2ca --- /dev/null +++ b/lib/folder-monitor.js @@ -0,0 +1,98 @@ +const { EventEmitter } = require('events'); +const path = require('path'); +const chokidar = require('chokidar'); + +class FolderMonitor extends EventEmitter { + constructor() { + super(); + this._watcher = null; + this._settings = null; + this._seenFiles = new Set(); + this._batchBuffer = []; + this._batchTimer = null; + } + + get running() { + return !!this._watcher; + } + + start(settings) { + this.stop(); + this._settings = settings; + + const folderPath = String(settings.folderPath || '').trim(); + if (!folderPath) throw new Error('Kein Ordnerpfad angegeben'); + + const watchOptions = { + persistent: true, + ignoreInitial: true, + depth: settings.recursive ? undefined : 0, + awaitWriteFinish: { + stabilityThreshold: Math.max(1000, (settings.delaySec || 3) * 1000), + pollInterval: 500 + } + }; + + this._watcher = chokidar.watch(folderPath, watchOptions); + this._watcher.on('add', (filePath) => this._onNewFile(filePath)); + this._watcher.on('error', (err) => this.emit('error', err)); + } + + stop() { + if (this._watcher) { + this._watcher.close().catch(() => {}); + this._watcher = null; + } + if (this._batchTimer) { + clearTimeout(this._batchTimer); + this._batchTimer = null; + } + this._batchBuffer = []; + this._seenFiles = new Set(); + } + + status() { + return { + running: this.running, + folderPath: this._settings ? this._settings.folderPath : '', + seenCount: this._seenFiles.size + }; + } + + _onNewFile(filePath) { + const settings = this._settings; + if (!settings) return; + + // Extension filter + const ext = path.extname(filePath).replace(/^\./, '').toLowerCase(); + const rawExtensions = String(settings.extensions || '').trim(); + if (rawExtensions) { + const extList = rawExtensions.split(',').map(e => e.trim().toLowerCase().replace(/^\./, '')).filter(Boolean); + if (extList.length > 0) { + const matches = extList.includes(ext); + if (settings.filterMode === 'include' && !matches) return; + if (settings.filterMode === 'exclude' && matches) return; + } + } + + // Skip duplicates (session-based) + if (settings.skipDuplicates) { + const normalized = filePath.replace(/\\/g, '/').toLowerCase(); + if (this._seenFiles.has(normalized)) return; + this._seenFiles.add(normalized); + } + + // Batch: collect files over 200ms window then emit together + this._batchBuffer.push(filePath); + if (this._batchTimer) clearTimeout(this._batchTimer); + this._batchTimer = setTimeout(() => { + const files = this._batchBuffer.splice(0); + this._batchTimer = null; + if (files.length > 0) { + this.emit('new-files', files); + } + }, 200); + } +} + +module.exports = FolderMonitor; diff --git a/main.js b/main.js index 9ae00ad..70468cf 100644 --- a/main.js +++ b/main.js @@ -10,11 +10,13 @@ const VoeUploader = require('./lib/voe-upload'); const DoodstreamUploader = require('./lib/doodstream-upload'); const { checkForUpdate, installUpdate, abortUpdate } = require('./lib/updater'); const backupCrypto = require('./lib/backup-crypto'); +const FolderMonitor = require('./lib/folder-monitor'); let mainWindow; let tray = null; const configStore = new ConfigStore(app); let uploadManager = null; +let folderMonitor = new FolderMonitor(); const HEALTH_CHECK_TIMEOUT = 25000; // --- Debug logging (writes to upload-debug.log next to the app) --- @@ -484,6 +486,17 @@ app.whenReady().then(() => { mainWindow.hide(); }); + // Auto-start folder monitor if enabled + try { + const launchConfig = configStore.load(); + const fm = launchConfig.globalSettings && launchConfig.globalSettings.folderMonitor; + if (fm && fm.enabled && fm.folderPath) { + startFolderMonitor(fm); + } + } catch (err) { + debugLog(`folder-monitor auto-start failed: ${err.message}`); + } + // Auto-check for updates after 3 seconds setTimeout(async () => { try { @@ -499,6 +512,10 @@ app.on('window-all-closed', () => { app.quit(); }); +app.on('before-quit', () => { + try { folderMonitor.stop(); } catch {} +}); + // --- IPC Handlers --- // Debug log from renderer @@ -811,6 +828,51 @@ ipcMain.handle('save-global-settings', async (_event, globalSettings) => { return true; }); +// --- Folder Monitor --- +function startFolderMonitor(settings) { + try { + folderMonitor.stop(); + folderMonitor.removeAllListeners(); + folderMonitor.on('new-files', (files) => { + debugLog(`folder-monitor: ${files.length} new file(s)`); + if (mainWindow && !mainWindow.isDestroyed()) { + mainWindow.webContents.send('folder-monitor:new-files', files); + } + }); + folderMonitor.on('error', (err) => { + debugLog(`folder-monitor error: ${err.message}`); + }); + folderMonitor.start(settings); + debugLog(`folder-monitor started: ${settings.folderPath}`); + } catch (err) { + debugLog(`folder-monitor start failed: ${err.message}`); + throw err; + } +} + +ipcMain.handle('folder-monitor:start', (_event, settings) => { + startFolderMonitor(settings); + return { ok: true }; +}); + +ipcMain.handle('folder-monitor:stop', () => { + folderMonitor.stop(); + debugLog('folder-monitor stopped'); + return { ok: true }; +}); + +ipcMain.handle('folder-monitor:status', () => { + return folderMonitor.status(); +}); + +ipcMain.handle('folder-monitor:select-folder', async () => { + const result = await dialog.showOpenDialog(mainWindow, { + properties: ['openDirectory'] + }); + if (result.canceled || !result.filePaths.length) return null; + return result.filePaths[0]; +}); + // --- Always on top --- ipcMain.handle('set-always-on-top', async (_event, value) => { if (mainWindow && !mainWindow.isDestroyed()) { diff --git a/package-lock.json b/package-lock.json index 7a0e240..20c911b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,13 +1,14 @@ { "name": "multi-hoster-uploader", - "version": "1.0.0", + "version": "1.8.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "multi-hoster-uploader", - "version": "1.0.0", + "version": "1.8.6", "dependencies": { + "chokidar": "^3.6.0", "undici": "^7.16.0" }, "devDependencies": { @@ -901,6 +902,19 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/app-builder-bin": { "version": "5.0.0-alpha.10", "resolved": "https://registry.npmjs.org/app-builder-bin/-/app-builder-bin-5.0.0-alpha.10.tgz", @@ -1202,6 +1216,18 @@ ], "license": "MIT" }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/bl": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", @@ -1253,6 +1279,18 @@ "node": "20 || >=22" } }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/buffer": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", @@ -1523,6 +1561,30 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, "node_modules/chownr": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", @@ -2684,6 +2746,18 @@ "node": ">=10" } }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/foreground-child": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", @@ -2774,6 +2848,20 @@ "dev": true, "license": "ISC" }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -2892,6 +2980,18 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/glob/node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -3273,6 +3373,18 @@ "node": ">= 12" } }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/is-ci": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz", @@ -3302,6 +3414,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -3312,6 +3433,18 @@ "node": ">=8" } }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-interactive": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", @@ -3329,6 +3462,15 @@ "dev": true, "license": "MIT" }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, "node_modules/is-unicode-supported": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", @@ -4058,9 +4200,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -4288,6 +4428,18 @@ "dev": true, "license": "ISC" }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/plist": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/plist/-/plist-3.1.0.tgz", @@ -4461,6 +4613,18 @@ "node": ">=10" } }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -5047,6 +5211,18 @@ "tmp": "^0.2.0" } }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, "node_modules/truncate-utf8-bytes": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", diff --git a/package.json b/package.json index 2161326..237bf27 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "release:gitea": "node scripts/release_gitea.mjs" }, "dependencies": { + "chokidar": "^3.6.0", "undici": "^7.16.0" }, "devDependencies": { diff --git a/preload.js b/preload.js index 8581453..2d67ab6 100644 --- a/preload.js +++ b/preload.js @@ -55,6 +55,15 @@ contextBridge.exposeInMainWorld('api', { exportBackup: (password) => ipcRenderer.invoke('export-backup', password), importBackup: (password) => ipcRenderer.invoke('import-backup', password), + // Folder Monitor + folderMonitorStart: (settings) => ipcRenderer.invoke('folder-monitor:start', settings), + folderMonitorStop: () => ipcRenderer.invoke('folder-monitor:stop'), + folderMonitorStatus: () => ipcRenderer.invoke('folder-monitor:status'), + folderMonitorSelectFolder: () => ipcRenderer.invoke('folder-monitor:select-folder'), + onFolderMonitorNewFiles: (callback) => { + ipcRenderer.on('folder-monitor:new-files', (_event, data) => callback(data)); + }, + // Debug debugTestUpload: () => ipcRenderer.invoke('debug-test-upload'), debugLog: (msg) => ipcRenderer.invoke('debug-log', msg), @@ -81,5 +90,6 @@ contextBridge.exposeInMainWorld('api', { ipcRenderer.removeAllListeners('app:update-available'); ipcRenderer.removeAllListeners('app:update-progress'); ipcRenderer.removeAllListeners('shutdown-countdown'); + ipcRenderer.removeAllListeners('folder-monitor:new-files'); } }); diff --git a/renderer/app.js b/renderer/app.js index 0104ad4..e46b02c 100644 --- a/renderer/app.js +++ b/renderer/app.js @@ -76,6 +76,31 @@ async function init() { }); window.api.onShutdownCountdown(handleShutdownCountdown); + // Folder monitor: auto-queue new files + window.api.onFolderMonitorNewFiles((files) => { + window.api.debugLog('folder-monitor: received ' + files.length + ' file(s)'); + const fm = config.globalSettings && config.globalSettings.folderMonitor; + const hosters = getSelectedHosters(); + if (hosters.length > 0 && fm && fm.autoStart) { + // Add files directly to queue and start upload + const newFiles = []; + for (const p of files) { + if (!selectedFiles.find(f => f.path === p) && !_pendingFiles.find(f => f.path === p)) { + const name = p.split('\\').pop().split('/').pop(); + newFiles.push({ path: p, name, size: null }); + } + } + if (newFiles.length > 0) { + selectedFiles.push(...newFiles); + buildQueuePreview(); + updateUploadView(); + if (!uploading && !healthCheckRunning) startUpload(); + } + } else { + addPathsToQueue(files); + } + }); + window.api.debugLog('init complete, all listeners registered'); // Restore always-on-top state @@ -1605,7 +1630,8 @@ function updateStatusBar() { document.getElementById('sbState').textContent = stateText; document.getElementById('sbSpeed').textContent = formatSpeed(lastUploadStats.globalSpeedKbs || 0); - document.getElementById('sbTotal').textContent = `${formatSize(lastUploadStats.totalBytes || 0)} / ${formatSize(stats.totalSize)}`; + const uploadedSize = Math.max(0, stats.totalSize - stats.remainingSize); + document.getElementById('sbTotal').textContent = `${formatSize(uploadedSize)} / ${formatSize(stats.remainingSize)}`; document.getElementById('sbEta').textContent = `ETA ${etaSeconds > 0 ? formatTime(etaSeconds) : '--:--'}`; document.getElementById('sbConnections').textContent = `Aktive Verbindungen ${lastUploadStats.activeJobs || 0}`; document.getElementById('sbQueueCount').textContent = `Gesamt ${stats.total}`; @@ -1738,6 +1764,74 @@ function renderSettings() { `; container.appendChild(generalPanel); + // --- Folder Monitor Panel --- + const fm = globalSettings.folderMonitor || {}; + const folderMonitorPanel = document.createElement('div'); + folderMonitorPanel.className = 'hoster-settings-panel'; + folderMonitorPanel.innerHTML = ` +
+ + Ordnerüberwachung + ${fm.enabled && fm.folderPath ? 'Aktiv' : 'Inaktiv'} +
+ + `; + container.appendChild(folderMonitorPanel); + + // Toggle folder monitor panel + folderMonitorPanel.querySelector('.hoster-panel-header').addEventListener('click', () => { + const body = folderMonitorPanel.querySelector('.hoster-panel-body'); + const arrow = folderMonitorPanel.querySelector('.panel-arrow'); + const isOpen = body.style.display !== 'none'; + body.style.display = isOpen ? 'none' : 'block'; + arrow.innerHTML = isOpen ? '▶' : '▼'; + }); + + document.getElementById('fmChooseFolderBtn')?.addEventListener('click', async () => { + const folder = await window.api.folderMonitorSelectFolder(); + if (folder) { + document.getElementById('fmFolderPathInput').value = folder; + scheduleSettingsSave(); + } + }); + if (configuredAccounts.length === 0) { const empty = document.createElement('div'); empty.className = 'settings-empty'; @@ -1841,7 +1935,17 @@ async function saveSettings(options = {}) { parallelUploadCount: Math.max(0, Math.min(100, parseInt(document.getElementById('parallelUploadCountInput')?.value || '0', 10) || 0)), scaleParallelUploads: !!document.getElementById('scaleParallelUploadsInput')?.checked, removeFromQueueOnDone: !!document.getElementById('removeFromQueueOnDoneInput')?.checked, - globalMaxSpeedKbs: Math.max(0, Math.round((parseFloat(document.getElementById('globalMaxSpeedMbsInput')?.value || '0') || 0) * 1024)) + globalMaxSpeedKbs: Math.max(0, Math.round((parseFloat(document.getElementById('globalMaxSpeedMbsInput')?.value || '0') || 0) * 1024)), + folderMonitor: { + enabled: !!document.getElementById('fmEnabledInput')?.checked, + folderPath: (document.getElementById('fmFolderPathInput')?.value || '').trim(), + recursive: !!document.getElementById('fmRecursiveInput')?.checked, + filterMode: document.getElementById('fmFilterModeInput')?.value || 'include', + extensions: (document.getElementById('fmExtensionsInput')?.value || '').trim(), + skipDuplicates: !!document.getElementById('fmSkipDuplicatesInput')?.checked, + delaySec: Math.max(1, parseInt(document.getElementById('fmDelaySecInput')?.value || '3', 10) || 3), + autoStart: !!document.getElementById('fmAutoStartInput')?.checked + } }; // Always on top setting @@ -1870,6 +1974,21 @@ async function saveSettings(options = {}) { hosterSettings = config.hosterSettings || {}; clearTimeout(settingsSaveTimer); + // Start/stop folder monitor based on settings + const fmSettings = globalSettings.folderMonitor; + const badge = document.getElementById('folderMonitorStatusBadge'); + if (fmSettings && fmSettings.enabled && fmSettings.folderPath) { + try { + await window.api.folderMonitorStart(fmSettings); + if (badge) { badge.textContent = 'Aktiv'; badge.className = 'panel-status active'; } + } catch { + if (badge) { badge.textContent = 'Fehler'; badge.className = 'panel-status'; } + } + } else { + await window.api.folderMonitorStop(); + if (badge) { badge.textContent = 'Inaktiv'; badge.className = 'panel-status'; } + } + const feedback = document.getElementById('saveFeedback'); feedback.textContent = feedbackText; setTimeout(() => {