From 3b9faa789cea3c9466834b30e74b7ff146981614 Mon Sep 17 00:00:00 2001 From: NimBold Date: Wed, 8 Jul 2026 22:19:15 +0330 Subject: [PATCH] chore(release): prepare 1.0.2 Remove reduced yt-dlp info-json reuse so metadata fallback downloads re-extract from the original URL instead of crashing on incomplete extractor metadata. Update README and changelog for the 1.0.2 desktop release and point the bundled extension submodule at Firelink Companion 2.0.2. --- CHANGELOG.md | 20 ++++ Extensions/Firefox | 2 +- README.md | 8 +- package-lock.json | 4 +- package.json | 2 +- src-tauri/Cargo.lock | 2 +- src-tauri/Cargo.toml | 2 +- src-tauri/src/lib.rs | 194 ++++---------------------------------- src-tauri/tauri.conf.json | 2 +- 9 files changed, 51 insertions(+), 185 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 58c5a52..bd86e34 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,26 @@ All notable changes to Firelink will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.0.2] - 2026-07-08 + +### New +- Add explicit **Fetch media** actions for Firelink Companion so video and audio pages can be sent to Firelink from the extension popup or page context menu. +- Add a Chromium extension install path in Firelink's Integrations settings and release documentation, addressing the Chromium support request in [#4](https://github.com/nimbold/Firelink/issues/4). + +### Improved +- Improve YouTube and social-media download handling with better proxy support, cleaner diagnostics, safer log redaction, and more reliable metadata fetching based on reports in [#5](https://github.com/nimbold/Firelink/issues/5) and [#8](https://github.com/nimbold/Firelink/issues/8). +- Let category subfolders be disabled so files can save directly into a base download folder, addressing [#6](https://github.com/nimbold/Firelink/issues/6). +- Refresh bundled media engines and release tooling for the current cross-platform desktop builds. +- Strengthen release packaging checks for macOS, Windows, and Linux, including macOS ad-hoc signing verification and Windows installer icon handling. + +### Fixed +- Fix YouTube downloads that failed when Chrome's cookie database could not be copied by retrying public media without browser cookies, addressing [#7](https://github.com/nimbold/Firelink/issues/7). +- Fix the hidden-sidebar trap on Settings and other pages so the sidebar can always be shown again, addressing [#9](https://github.com/nimbold/Firelink/issues/9). +- Fix Windows proxy detection so system proxy schemes are preserved for metadata and media requests, addressing [#5](https://github.com/nimbold/Firelink/issues/5). +- Fix captured and auto-captured browser links so they always route through Firelink's Add window before reaching the download list. +- Fix media downloads after a metadata fallback so pages without selectable preview formats can still let yt-dlp choose the best downloadable file instead of crashing. +- Fix media cleanup and retry paths so failed, canceled, or retried media downloads leave fewer stale temporary files behind. + ## [1.0.1] - 2026-07-04 ### Fixed diff --git a/Extensions/Firefox b/Extensions/Firefox index 9b215e1..be632f3 160000 --- a/Extensions/Firefox +++ b/Extensions/Firefox @@ -1 +1 @@ -Subproject commit 9b215e1108a885a1335c5bc5513a72fec826fef2 +Subproject commit be632f368cc42e416ecdb94727d68a6d6c5bfeaf diff --git a/README.md b/README.md index 73e67a0..3a83ae8 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ **A fast, focused desktop download manager for macOS, Windows, and Linux.** - [![Version](https://img.shields.io/badge/version-1.0.1-6f42c1?style=flat-square)](https://github.com/nimbold/Firelink/releases) + [![Version](https://img.shields.io/badge/version-1.0.2-6f42c1?style=flat-square)](https://github.com/nimbold/Firelink/releases) [![macOS](https://img.shields.io/badge/macOS-111111?style=flat-square&logo=apple&logoColor=white)](#platforms) [![Windows](.github/badges/windows.svg)](#platforms) [![Linux](https://img.shields.io/badge/Linux-FCC624?style=flat-square&logo=linux&logoColor=black)](#platforms) @@ -45,7 +45,7 @@ It is now a cross-platform Rust/Tauri app with a React and TypeScript interface. - **Persistent queues** with pause, resume, retry, redownload, sorting, multi-select, and bulk actions. - **Scheduling** with start/stop windows, speed rules, and post-queue actions. - **File organization** with categories, default folders, per-download overrides, and reveal/trash actions. -- **Browser handoff** through local pairing, signed requests, replay protection, and server checks. +- **Browser handoff** through local pairing, signed requests, Add window review, replay protection, and server checks. - **Desktop integration** with tray controls, notifications, sounds, sleep prevention, and secure credential storage. - **Diagnostics** with engine health checks, structured logs, and package verification. @@ -74,12 +74,14 @@ Firelink Companion sends browser links and downloads to the desktop app. What it adds: - Automatic capture for regular browser downloads. +- Explicit Fetch media actions from the popup and page context menu. - Context-menu actions for links and selected text. - Firefox and Chromium support. - Signed local requests using the token from **Settings -> Integrations**. - Fallback to the browser download when Firelink is closed or rejects a handoff. +- Captured links always open Firelink's Add window before anything is added to the download list. -Install the extension, open Firelink, then pair it from **Settings -> Integrations**. Firefox users can install from Mozilla Add-ons. Chromium users can use the [manual load-unpacked flow](https://github.com/nimbold/Firelink-Extension#manual-chromium-installation) with `firelink-chromium.zip` from the [extension releases](https://github.com/nimbold/Firelink-Extension/releases). +Install the extension, open Firelink, then pair it from **Settings -> Integrations**. Firefox users can install from Mozilla Add-ons. Chromium users can use the [manual load-unpacked flow](https://github.com/nimbold/Firelink-Extension#manual-chromium-installation) with `firelink-chromium.zip` from the [extension releases](https://github.com/nimbold/Firelink-Extension/releases). Firelink Companion 2.0.2 is the matching extension release for Firelink 1.0.2. The extension lives in [Firelink-Extension](https://github.com/nimbold/Firelink-Extension). This repo also vendors it as the `Extensions/Firefox` submodule. diff --git a/package-lock.json b/package-lock.json index 5931759..3fa12ea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "firelink", - "version": "1.0.1", + "version": "1.0.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "firelink", - "version": "1.0.1", + "version": "1.0.2", "license": "MIT", "dependencies": { "@formkit/auto-animate": "^0.9.0", diff --git a/package.json b/package.json index 59f243d..ec51f89 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "firelink", "private": true, - "version": "1.0.1", + "version": "1.0.2", "description": "A fast cross-platform desktop download manager powered by Rust, Tauri, React, aria2, and yt-dlp.", "license": "MIT", "homepage": "https://github.com/nimbold/Firelink", diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 4a2ca4e..8d645dd 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -1380,7 +1380,7 @@ checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "firelink" -version = "1.0.1" +version = "1.0.2" dependencies = [ "async-trait", "axum", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index dea5346..2439afc 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "firelink" -version = "1.0.1" +version = "1.0.2" description = "A fast cross-platform desktop download manager powered by Rust and Tauri" authors = ["NimBold"] edition = "2021" diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 5af21a7..71a0929 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -12,34 +12,6 @@ use tauri::{Emitter, Manager}; use tauri_plugin_deep_link::DeepLinkExt; use ts_rs::TS; -fn get_metadata_cache() -> &'static std::sync::Mutex> { - static CACHE: OnceLock>> = OnceLock::new(); - CACHE.get_or_init(|| std::sync::Mutex::new(HashMap::new())) -} - -fn metadata_info_cache_key( - url: &str, - cookie_source: Option<&str>, - username: Option<&str>, - password: Option<&str>, - headers: Option<&str>, - cookies: Option<&str>, - proxy: Option<&str>, - user_agent: Option<&str>, -) -> String { - serde_json::json!([ - url, - cookie_source.unwrap_or(""), - username.unwrap_or(""), - password.unwrap_or(""), - headers.unwrap_or(""), - cookies.unwrap_or(""), - proxy.unwrap_or(""), - user_agent.unwrap_or("") - ]) - .to_string() -} - fn sanitize_metadata_filename(filename: &str) -> Option { let normalized = filename.trim().replace('\\', "/"); let basename = std::path::Path::new(&normalized) @@ -946,25 +918,6 @@ async fn remove_file_best_effort_with_retry(path: &std::path::Path) { } } -fn remove_file_best_effort_with_retry_blocking(path: &std::path::Path) { - for attempt in 0..=5 { - match std::fs::remove_file(path) { - Ok(()) => return, - Err(error) if error.kind() == std::io::ErrorKind::NotFound => return, - Err(error) if attempt == 5 => { - log::warn!( - "failed to remove temporary media metadata '{}' after retries: {}", - path.display(), - error - ); - } - Err(_) => { - std::thread::sleep(std::time::Duration::from_millis(200)); - } - } - } -} - async fn cleanup_media_artifacts(out_path: &std::path::Path, remove_primary: bool) { let Some(parent) = out_path.parent() else { return; @@ -1499,17 +1452,6 @@ async fn fetch_media_metadata_uncached( cookies: Option, proxy: Option, ) -> Result { - let info_cache_key = metadata_info_cache_key( - &url, - cookie_browser.as_deref(), - username.as_deref(), - password.as_deref(), - headers.as_deref(), - cookies.as_deref(), - proxy.as_deref(), - user_agent.as_deref(), - ); - // Pass bundled tools by absolute path so extraction never depends on // system Python, a user-managed PATH, or auto-detection heuristics. let deno_path = resolve_bundled_binary_path(&app_handle, "deno") @@ -1603,12 +1545,6 @@ async fn fetch_media_metadata_uncached( let value: serde_json::Value = serde_json::from_slice(&output.stdout) .map_err(|e| format!("Failed to parse JSON: {}", e))?; - if let Ok(mut cache) = get_metadata_cache().lock() { - if let Ok(json_str) = String::from_utf8(output.stdout.clone()) { - cache.insert(info_cache_key, json_str); - } - } - let title = value .get("title") .and_then(|v| v.as_str()) @@ -2889,42 +2825,7 @@ pub(crate) async fn start_media_download_internal( } } - let mut temp_info_path = None; - if let Ok(mut cache) = get_metadata_cache().lock() { - let info_cache_key = metadata_info_cache_key( - &url, - effective_cookie_source.as_deref(), - username.as_deref(), - password.as_deref(), - headers.as_deref(), - cookies.as_deref(), - proxy.as_deref(), - user_agent.as_deref(), - ); - if let Some(json_str) = cache.remove(&info_cache_key) { - let temp_dir = std::env::temp_dir(); - let path = temp_dir.join(format!("firelink_ytdlp_{}.info.json", id)); - if std::fs::write(&path, json_str).is_ok() { - temp_info_path = Some(path); - } - } - } - - struct CleanupPath(Option); - impl Drop for CleanupPath { - fn drop(&mut self) { - if let Some(path) = self.0.take() { - remove_file_best_effort_with_retry_blocking(&path); - } - } - } - let _cleanup = CleanupPath(temp_info_path.clone()); - - if let Some(path) = temp_info_path.as_ref() { - cmd = cmd.arg("--load-info-json").arg(path); - } else { - cmd = cmd.arg("--").arg(&url); - } + cmd = cmd.arg("--").arg(&url); let (mut rx, child) = cmd .spawn() @@ -4585,11 +4486,10 @@ mod tests { collect_download_uris, filename_from_content_disposition, filename_from_url_disposition_query, filename_from_url_path, is_excluded_yt_dlp_format, is_browser_cookie_extraction_error, json_lower, media_metadata_cache_key, - metadata_info_cache_key, media_output_template, media_progress_speed, - normalize_speed_limit_for_aria2, parse_firelink_deep_link, parse_ffmpeg_version, - parse_media_progress_line, redact_log_line, sanitize_ytdlp_config_value, - should_cleanup_media_artifacts_after_failure, FirelinkDeepLink, MediaProgress, - MEDIA_PROGRESS_PREFIX, + media_output_template, media_progress_speed, normalize_speed_limit_for_aria2, + parse_firelink_deep_link, parse_ffmpeg_version, parse_media_progress_line, + redact_log_line, sanitize_ytdlp_config_value, should_cleanup_media_artifacts_after_failure, + FirelinkDeepLink, MediaProgress, MEDIA_PROGRESS_PREFIX, }; use serde_json::json; use std::time::{Duration, Instant}; @@ -4690,76 +4590,6 @@ mod tests { assert_ne!(base, changed_user_agent); } - #[test] - fn metadata_info_cache_key_includes_request_headers_and_cookies() { - let base = metadata_info_cache_key( - "https://example.com/watch?v=1", - Some("firefox"), - Some("user-one"), - Some("pass-one"), - Some("User-Agent: Browser A"), - Some("session=one"), - None, - Some("Custom UA A"), - ); - let changed_headers = metadata_info_cache_key( - "https://example.com/watch?v=1", - Some("firefox"), - Some("user-one"), - Some("pass-one"), - Some("User-Agent: Browser B"), - Some("session=one"), - None, - Some("Custom UA A"), - ); - let changed_cookies = metadata_info_cache_key( - "https://example.com/watch?v=1", - Some("firefox"), - Some("user-one"), - Some("pass-one"), - Some("User-Agent: Browser A"), - Some("session=two"), - None, - Some("Custom UA A"), - ); - let changed_username = metadata_info_cache_key( - "https://example.com/watch?v=1", - Some("firefox"), - Some("user-two"), - Some("pass-one"), - Some("User-Agent: Browser A"), - Some("session=one"), - None, - Some("Custom UA A"), - ); - let changed_password = metadata_info_cache_key( - "https://example.com/watch?v=1", - Some("firefox"), - Some("user-one"), - Some("pass-two"), - Some("User-Agent: Browser A"), - Some("session=one"), - None, - Some("Custom UA A"), - ); - let changed_user_agent = metadata_info_cache_key( - "https://example.com/watch?v=1", - Some("firefox"), - Some("user-one"), - Some("pass-one"), - Some("User-Agent: Browser A"), - Some("session=one"), - None, - Some("Custom UA B"), - ); - - assert_ne!(base, changed_headers); - assert_ne!(base, changed_cookies); - assert_ne!(base, changed_username); - assert_ne!(base, changed_password); - assert_ne!(base, changed_user_agent); - } - #[test] fn retryable_media_failures_preserve_resumable_artifacts() { assert!(!should_cleanup_media_artifacts_after_failure( @@ -4995,6 +4825,20 @@ mod tests { assert_eq!(option.filesize_approx, Some(1_468_000_000)); } + #[test] + fn returns_no_media_options_for_webpage_only_metadata() { + let formats = vec![json!({ + "format_id": "0", + "ext": "html", + "protocol": "https", + "format": "default webpage", + "vcodec": "none", + "acodec": "none" + })]; + + assert!(build_media_format_options(&formats, Some(30.0)).is_empty()); + } + #[test] fn classifies_browser_cookie_database_errors_for_fallback() { assert!(is_browser_cookie_extraction_error( diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 0a30391..fa8fb92 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "Firelink", - "version": "1.0.1", + "version": "1.0.2", "identifier": "com.nimbold.firelink", "build": { "beforeDevCommand": "node scripts/stage-engines.js && npm run dev",