From 46b1e155b627019ecc4675dd72efc9082403de90 Mon Sep 17 00:00:00 2001 From: Dariusz L Date: Wed, 24 Jun 2026 22:12:06 +0200 Subject: [PATCH] feat: remember and normalize downloaded subfolder paths for reuse in download targets --- .../actions/resolve_download_methods.js | 18 +++++ .../search/download_target_methods.js | 67 +++++++++++++++++++ 2 files changed, 85 insertions(+) diff --git a/web/resolver/actions/resolve_download_methods.js b/web/resolver/actions/resolve_download_methods.js index 72f9f41..f9fbf74 100644 --- a/web/resolver/actions/resolve_download_methods.js +++ b/web/resolver/actions/resolve_download_methods.js @@ -492,6 +492,18 @@ export const resolveDownloadMethods = { if (progress.filename) info.filename = progress.filename; }, + rememberDownloadSubfolderAfterCompletion(info = {}, progress = {}) { + const category = info?.category || progress?.category || ''; + const subfolder = info?.subfolder || progress?.subfolder || ''; + if (!category || !subfolder) return; + + this.rememberDownloadedSubfolder?.( + category, + subfolder, + info?.baseDirectory || progress?.base_directory || progress?.baseDirectory || '' + ); + }, + renderDownloadStatusMessage(message, type, progress = {}, info = {}) { const contextMenuModel = this.getDownloadFolderContext(progress, info); return this.renderStatusMessage(message, type, { @@ -742,6 +754,7 @@ export const resolveDownloadMethods = { const snapshot = this.rememberDownloadSnapshotForMissing(info.missing, { downloadId, category: info.category || '', + subfolder: info.subfolder || '', filename: progress.filename || info.filename || '', downloadPath: progress.path || info.downloadPath || '', downloadDirectory: progress.directory || info.downloadDirectory || '', @@ -1261,6 +1274,7 @@ export const resolveDownloadMethods = { this.rememberDownloadSnapshotForMissing(missing, { downloadId: null, category, + subfolder, filename, downloadPath: '', downloadDirectory: '', @@ -1340,6 +1354,7 @@ export const resolveDownloadMethods = { progressDiv, downloadBtn, category, + subfolder, filename, downloadPath: data.path || '', downloadDirectory: data.directory || '', @@ -1450,6 +1465,7 @@ export const resolveDownloadMethods = { isActive: false }); this.renderDownloadSnapshot(downloadId, completedSnapshot, { progressDiv, downloadBtn }); + this.rememberDownloadSubfolderAfterCompletion(info, progress); this.rememberCompletedDownloadHistory?.(downloadId, info, progress); delete this.activeDownloads[downloadId]; this.updateDownloadAllButtonState(); @@ -2515,6 +2531,7 @@ export const resolveDownloadMethods = { this.rememberDownloadSnapshotForMissing(missing, { downloadId: null, category: targetSelection.category, + subfolder: targetSelection.subfolder, filename, downloadPath: '', downloadDirectory: '', @@ -2589,6 +2606,7 @@ export const resolveDownloadMethods = { progressDiv, downloadBtn: btn, category: targetSelection.category, + subfolder: targetSelection.subfolder, filename, downloadPath: data.path || '', downloadDirectory: data.directory || '', diff --git a/web/resolver/search/download_target_methods.js b/web/resolver/search/download_target_methods.js index ce206b3..5db385b 100644 --- a/web/resolver/search/download_target_methods.js +++ b/web/resolver/search/download_target_methods.js @@ -503,6 +503,73 @@ export const downloadTargetMethods = { return this.downloadSubfolders.get(this.normalizeDownloadCategory(category)) || []; }, + normalizeDownloadSubfolderPath(value = '') { + return String(value || '') + .replace(/\//g, '\\') + .split('\\') + .map(part => part.trim()) + .filter(part => part && part !== '.' && part !== '..') + .join('\\'); + }, + + getDownloadSubfolderPrefixes(value = '') { + const parts = this.normalizeDownloadSubfolderPath(value).split('\\').filter(Boolean); + const prefixes = []; + let current = ''; + parts.forEach(part => { + current = current ? `${current}\\${part}` : part; + prefixes.push(current); + }); + return prefixes; + }, + + rememberDownloadedSubfolder(category = '', subfolder = '', baseDirectory = '') { + const key = this.normalizeDownloadCategory(category); + if (!key || key === 'unknown') return; + + const prefixes = this.getDownloadSubfolderPrefixes(subfolder); + if (!prefixes.length) return; + + const cached = this.downloadSubfolders.get(key); + if (!Array.isArray(cached)) { + this.downloadSubfolders.delete(key); + return; + } + + const targetBaseDirectory = String(baseDirectory || this.getDownloadTargetBaseDirectory(key) || ''); + const normalizeBase = (value = '') => String(value || '') + .replace(/[\\/]+/g, '/') + .replace(/\/+$/g, '') + .toLowerCase(); + const normalizeValue = (value = '') => this.normalizeDownloadSubfolderPath(value).toLowerCase(); + const makeIdentity = (value = '', base = '') => `${normalizeValue(value)}::${normalizeBase(base)}`; + const known = new Set(cached.map(option => makeIdentity( + this.getSubfolderOptionValue(option), + this.getSubfolderOptionBaseDirectory(option) + ))); + const next = [...cached]; + + prefixes.forEach(prefix => { + const identity = makeIdentity(prefix, targetBaseDirectory); + if (known.has(identity)) return; + known.add(identity); + next.push({ + value: prefix, + label: prefix, + base_directory: targetBaseDirectory + }); + }); + + next.sort((a, b) => { + const valueCompare = this.getSubfolderOptionValue(a) + .localeCompare(this.getSubfolderOptionValue(b), undefined, { sensitivity: 'base' }); + if (valueCompare !== 0) return valueCompare; + return this.getSubfolderOptionBaseDirectory(a) + .localeCompare(this.getSubfolderOptionBaseDirectory(b), undefined, { sensitivity: 'base' }); + }); + this.downloadSubfolders.set(key, next); + }, + getSubfolderOptionValue(option) { return String( option && typeof option === 'object'