From 12761caf10ed396b3167c9a85b998a81df1ddfa8 Mon Sep 17 00:00:00 2001 From: NimBold Date: Mon, 15 Jun 2026 18:21:44 +0330 Subject: [PATCH] feat: enhance locations and download categorization - Fix system proxy fetching logic in download queue - Expand recognized file formats across Windows, Mac, and Linux - Automatically assign matching category folder for new downloads - Add backend command to instantly create category directories on bulk base path selection --- src-tauri/src/lib.rs | 6 ++++- src-tauri/src/parity.rs | 32 ++++++++++++++++++++++----- src/components/AddDownloadsModal.tsx | 7 ++++++ src/components/SettingsView.tsx | 33 +++++++++++++++++++++------- src/ipc.ts | 1 + src/store/useDownloadStore.ts | 15 +++++++++---- src/utils/downloads.ts | 12 +++++----- 7 files changed, 82 insertions(+), 24 deletions(-) diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 6b289a0..5755eed 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -678,6 +678,9 @@ async fn start_download( options.insert("all-proxy".to_string(), serde_json::json!(prox)); } else { options.insert("all-proxy".to_string(), serde_json::json!("")); + options.insert("http-proxy".to_string(), serde_json::json!("")); + options.insert("https-proxy".to_string(), serde_json::json!("")); + options.insert("no-proxy".to_string(), serde_json::json!("*")); } if let Some(cook) = cookies { options.insert("header".to_string(), serde_json::json!(format!("Cookie: {}", cook))); @@ -1527,7 +1530,8 @@ pub fn run() { check_file_exists, delete_file, toggle_tray_icon, set_extension_pairing_token, set_extension_frontend_ready, set_concurrent_limit, set_global_speed_limit, remove_download, parity::get_system_proxy, parity::get_file_category, parity::check_for_updates, parity::is_supported_media, - db_save_settings, db_load_settings, db_get_all_downloads, db_save_download, db_delete_download, db_get_all_queues, db_save_queue, db_delete_queue + db_save_settings, db_load_settings, db_get_all_downloads, db_save_download, db_delete_download, db_get_all_queues, db_save_queue, db_delete_queue, + parity::create_category_directories ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); diff --git a/src-tauri/src/parity.rs b/src-tauri/src/parity.rs index 29f91df..c51be2a 100644 --- a/src-tauri/src/parity.rs +++ b/src-tauri/src/parity.rs @@ -26,11 +26,12 @@ pub fn get_file_category(filename: String) -> DownloadCategory { .map(|s| s.to_lowercase()) .unwrap_or_default(); - let music_exts = ["aac", "aif", "aiff", "alac", "amr", "ape", "au", "caf", "flac", "m4a", "m4b", "mid", "midi", "mp3", "oga", "ogg", "opus", "ra", "wav", "weba", "wma"]; - let movie_exts = ["3g2", "3gp", "avi", "divx", "f4v", "flv", "m2ts", "m4v", "mkv", "mov", "mp4", "mpeg", "mpg", "mts", "ogm", "ogv", "rm", "rmvb", "ts", "vob", "webm", "wmv"]; - let compressed_exts = ["7z", "ace", "alz", "apk", "appx", "ar", "arc", "arj", "bz", "bz2", "cab", "cpio", "deb", "dmg", "gz", "gzip", "iso", "jar", "lha", "lzh", "lz", "lz4", "lzip", "lzma", "pak", "pkg", "rar", "rpm", "sit", "sitx", "tar", "tbz", "tbz2", "tgz", "tlz", "txz", "war", "whl", "xar", "xz", "z", "zip", "zipx", "zst"]; - let picture_exts = ["ai", "apng", "avif", "bmp", "cr2", "cr3", "dng", "emf", "eps", "gif", "heic", "heif", "ico", "indd", "jfif", "jpeg", "jpg", "jxl", "nef", "orf", "pbm", "pgm", "png", "pnm", "ppm", "psd", "raw", "rw2", "svg", "tga", "tif", "tiff", "webp", "wmf"]; - let document_exts = ["azw", "azw3", "csv", "djvu", "doc", "docm", "docx", "dot", "dotx", "epub", "fb2", "htm", "html", "ics", "key", "log", "md", "mobi", "pdf", "numbers", "odp", "ods", "odt", "pages", "pot", "potx", "pps", "ppsx", "ppt", "pptm", "pptx", "rtf", "tex", "txt", "vcf", "xls", "xlsm", "xlsx", "xml", "xps", "yaml", "yml"]; + let music_exts = ["mp3", "wav", "aac", "flac", "ogg", "m4a", "wma", "alac", "ape", "mid", "midi"]; + let movie_exts = ["mp4", "mkv", "avi", "mov", "wmv", "flv", "webm", "m4v", "mpeg", "mpg", "3gp", "ts", "vob"]; + let compressed_exts = ["zip", "rar", "7z", "tar", "gz", "xz", "bz2", "lz", "lzma", "zst", "iso", "cab", "tgz", "tbz", "z", "sit", "sitx"]; + let picture_exts = ["jpg", "jpeg", "png", "gif", "webp", "bmp", "tiff", "svg", "ico", "heic", "raw", "psd", "ai"]; + let document_exts = ["pdf", "doc", "docx", "xls", "xlsx", "ppt", "pptx", "txt", "rtf", "csv", "md", "epub", "mobi", "azw3"]; + let app_exts = ["exe", "msi", "bat", "cmd", "app", "dmg", "pkg", "apk", "appx", "deb", "rpm", "appimage", "run", "sh", "bin", "jar"]; if music_exts.contains(&ext.as_str()) { DownloadCategory::Musics @@ -42,6 +43,8 @@ pub fn get_file_category(filename: String) -> DownloadCategory { DownloadCategory::Pictures } else if document_exts.contains(&ext.as_str()) { DownloadCategory::Documents + } else if app_exts.contains(&ext.as_str()) { + DownloadCategory::Applications } else { DownloadCategory::Other } @@ -137,6 +140,25 @@ fn cmp_versions(a: &str, b: &str) -> std::cmp::Ordering { a_ver.cmp(&b_ver) } +#[tauri::command] +pub async fn create_category_directories(app_handle: tauri::AppHandle, paths: Vec) -> Result<(), String> { + use tauri::Manager; + for path in paths { + let mut expanded = std::path::PathBuf::from(&path); + if path.starts_with("~/") { + if let Ok(home) = app_handle.path().home_dir() { + expanded = home.join(&path[2..]); + } + } + if !expanded.exists() { + if let Err(e) = tokio::fs::create_dir_all(&expanded).await { + eprintln!("Failed to create directory {}: {}", path, e); + } + } + } + Ok(()) +} + #[tauri::command] pub fn is_supported_media(url: String) -> bool { if let Ok(parsed_url) = reqwest::Url::parse(&url) { diff --git a/src/components/AddDownloadsModal.tsx b/src/components/AddDownloadsModal.tsx index 036050a..0fff7d1 100644 --- a/src/components/AddDownloadsModal.tsx +++ b/src/components/AddDownloadsModal.tsx @@ -415,6 +415,13 @@ export const AddDownloadsModal = () => { if (active && firstReadyIndex !== null) { setSelectedItemIndex(firstReadyIndex); + const firstFile = updatedItems[firstReadyIndex].file; + if (firstFile) { + const category = categoryForFileName(firstFile); + const settingsStore = useSettingsStore.getState(); + const categoryDir = (settingsStore.downloadDirectories && settingsStore.downloadDirectories[category]) || settingsStore.defaultDownloadPath || '~/Downloads'; + setSaveLocation(categoryDir); + } } }, 400); diff --git a/src/components/SettingsView.tsx b/src/components/SettingsView.tsx index c3d9ae8..32a4521 100644 --- a/src/components/SettingsView.tsx +++ b/src/components/SettingsView.tsx @@ -137,14 +137,31 @@ export default function SettingsView() { }); if (base && typeof base === 'string') { const cleanBase = base.replace(/\/$/, ''); - settings.setCategoryDirectory('Musics', `${cleanBase}/Musics`); - settings.setCategoryDirectory('Movies', `${cleanBase}/Movies`); - settings.setCategoryDirectory('Compressed', `${cleanBase}/Compressed`); - settings.setCategoryDirectory('Documents', `${cleanBase}/Documents`); - settings.setCategoryDirectory('Pictures', `${cleanBase}/Pictures`); - settings.setCategoryDirectory('Applications', `${cleanBase}/Applications`); - settings.setCategoryDirectory('Other', `${cleanBase}/Other`); - showToast("Updated all categories to use base folder"); + const paths = [ + `${cleanBase}/Musics`, + `${cleanBase}/Movies`, + `${cleanBase}/Compressed`, + `${cleanBase}/Documents`, + `${cleanBase}/Pictures`, + `${cleanBase}/Applications`, + `${cleanBase}/Other` + ]; + + settings.setCategoryDirectory('Musics', paths[0]); + settings.setCategoryDirectory('Movies', paths[1]); + settings.setCategoryDirectory('Compressed', paths[2]); + settings.setCategoryDirectory('Documents', paths[3]); + settings.setCategoryDirectory('Pictures', paths[4]); + settings.setCategoryDirectory('Applications', paths[5]); + settings.setCategoryDirectory('Other', paths[6]); + + try { + await invoke('create_category_directories', { paths }); + } catch (e) { + console.error("Failed to create directories on disk:", e); + } + + showToast("Updated and created all category folders at base"); } } catch (e) { console.error("Failed to browse base path:", e); diff --git a/src/ipc.ts b/src/ipc.ts index 1700576..6319877 100644 --- a/src/ipc.ts +++ b/src/ipc.ts @@ -93,6 +93,7 @@ type CommandMap = { db_get_all_queues: { args: undefined; result: string[] }; db_save_queue: { args: { id: string; data: string }; result: void }; db_delete_queue: { args: { id: string }; result: void }; + create_category_directories: { args: { paths: string[] }; result: void }; }; type CommandName = keyof CommandMap; diff --git a/src/store/useDownloadStore.ts b/src/store/useDownloadStore.ts index 1328a20..c7c26e1 100644 --- a/src/store/useDownloadStore.ts +++ b/src/store/useDownloadStore.ts @@ -8,8 +8,15 @@ import { useSettingsStore } from './useSettingsStore'; export type { DownloadCategory } from '../utils/downloads'; -const getProxyArgs = (settings: ReturnType) => { - if (settings.proxyMode === 'system') return 'system'; +const getProxyArgs = async (settings: ReturnType) => { + if (settings.proxyMode === 'system') { + try { + return await invoke('get_system_proxy'); + } catch (e) { + console.warn("Failed to get system proxy:", e); + return null; + } + } if (settings.proxyMode === 'custom' && settings.proxyHost) { return `http://${settings.proxyHost}:${settings.proxyPort}`; } @@ -420,7 +427,7 @@ export const useDownloadStore = create((set, get) => ({ username: item.username || (login ? login.username : null), password: item.password || keychainPassword, headers: item.headers || null, - proxy: getProxyArgs(settings), + proxy: await getProxyArgs(settings), userAgent: settings.customUserAgent || null, maxTries: settings.maxAutomaticRetries }); @@ -440,7 +447,7 @@ export const useDownloadStore = create((set, get) => ({ mirrors: item.mirrors || null, userAgent: settings.customUserAgent || null, maxTries: settings.maxAutomaticRetries, - proxy: getProxyArgs(settings) + proxy: await getProxyArgs(settings) }); } } catch (e) { diff --git a/src/utils/downloads.ts b/src/utils/downloads.ts index 4626667..844ba92 100644 --- a/src/utils/downloads.ts +++ b/src/utils/downloads.ts @@ -19,12 +19,12 @@ const MEDIA_DOMAINS = [ export const categoryForFileName = (fileName: string): DownloadCategory => { const ext = fileName.split('.').pop()?.toLowerCase() || ''; - if (['mp4', 'mkv', 'avi', 'mov', 'wmv', 'flv', 'webm', 'm4v'].includes(ext)) return 'Movies'; - if (['mp3', 'wav', 'aac', 'flac', 'ogg', 'm4a', 'wma'].includes(ext)) return 'Musics'; - if (['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'rtf'].includes(ext)) return 'Documents'; - if (['exe', 'dmg', 'apk', 'app', 'pkg', 'deb', 'rpm', 'msi', 'iso', 'bin', 'run'].includes(ext)) return 'Applications'; - if (['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp', 'tiff', 'svg'].includes(ext)) return 'Pictures'; - if (['zip', 'rar', '7z', 'tar', 'gz', 'xz', 'bz2'].includes(ext)) return 'Compressed'; + if (['mp4', 'mkv', 'avi', 'mov', 'wmv', 'flv', 'webm', 'm4v', 'mpeg', 'mpg', '3gp', 'ts', 'vob'].includes(ext)) return 'Movies'; + if (['mp3', 'wav', 'aac', 'flac', 'ogg', 'm4a', 'wma', 'alac', 'ape', 'mid', 'midi'].includes(ext)) return 'Musics'; + if (['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'rtf', 'csv', 'md', 'epub', 'mobi', 'azw3'].includes(ext)) return 'Documents'; + if (['exe', 'msi', 'bat', 'cmd', 'app', 'dmg', 'pkg', 'apk', 'appx', 'deb', 'rpm', 'appimage', 'run', 'sh', 'bin', 'jar'].includes(ext)) return 'Applications'; + if (['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp', 'tiff', 'svg', 'ico', 'heic', 'raw', 'psd', 'ai'].includes(ext)) return 'Pictures'; + if (['zip', 'rar', '7z', 'tar', 'gz', 'xz', 'bz2', 'lz', 'lzma', 'zst', 'iso', 'cab', 'tgz', 'tbz', 'z', 'sit', 'sitx'].includes(ext)) return 'Compressed'; return 'Other'; };