Commit Graph

50 Commits

Author SHA1 Message Date
NimBold c548cb4fe0 fix(media): filter yt-dlp storyboards and clean format rows 2026-06-18 14:14:33 +03:30
NimBold 43e21bfc1e fix(media): restore live download progress 2026-06-18 13:48:34 +03:30
NimBold 2130412e84 fix(macos): unify bundled yt-dlp detection 2026-06-18 13:34:34 +03:30
NimBold 3a76c6f5d7 fix(security): redact secrets from plaintext persistence
- Strip password, cookies, and headers from download_queue before writing
  to store.bin; secrets remain in-memory for the active session only.
- Move extension pairing token from PersistedSettings to the OS keychain,
  rotating it on upgrade from versions that persisted it as plaintext.
- Add ignores_legacy_extension_pairing_token_field test to confirm serde
  silently drops the old field so existing installs migrate cleanly.
- Document intentional retention of URLs (signed params are the download
  source and cannot be redacted without breaking resume/retry).
2026-06-18 08:20:22 +03:30
NimBold e2dd387a8c fix(integration): decode persisted settings and align ports 2026-06-18 07:52:48 +03:30
NimBold 5200c1e453 fix(security): restrict frontend filesystem access 2026-06-18 07:44:18 +03:30
NimBold 97802adb70 fix(downloads): manage aria2 lifecycle correctly 2026-06-18 07:37:30 +03:30
NimBold 605992439d fix(queue): preserve media terminal states 2026-06-18 07:26:40 +03:30
NimBold 18de275d42 refactor(ffmpeg): use regex for robust version parsing 2026-06-18 06:52:47 +03:30
NimBold db99e10238 fix(core): add engine timeouts and symlink deno for yt-dlp 2026-06-17 20:20:21 +03:30
NimBold 2d27b14513 fix: resolve startup crash, double tray icon, and engine version detection
- Fix double tray icon by using correct tray id 'main' instead of 'main_startup'
- Fix yt-dlp version check to accept standalone binaries without _internal/
- Fix SIGABRT crash in did_finish_launching by replacing tokio::process::Command
  with std::process::Command + spawn_blocking to avoid tokio Child lifecycle issues
2026-06-17 19:48:00 +03:30
NimBold 0a1d367a99 feat(engine): replace vague error strings with structured get_engine_status diagnostics
Add a single Rust command returning per-engine structured status with
resolved path, version, error context, stderr tail, and remediation hints.

- Aria2: binary check, daemon startup error, RPC readiness, live stderr capture
- yt-dlp: binary check, onedir layout validation (_internal/Python.framework)
- FFmpeg / Deno: binary check, version parse
- UI: Ready/Error badge + expandable technical detail panel
- Old test_* commands kept for backward compatibility
2026-06-17 18:45:11 +03:30
NimBold 292240cb71 fix(media-download): resolve bundled aria2c and ffmpeg via restricted PATH instead of relying on system binaries
yt-dlp's --downloader aria2c searched PATH for the bare name, but
Firelink's bundled binaries use target-triple suffixed names
(e.g. aria2c-aarch64-apple-darwin) — so yt-dlp could never find them.

Add resolve_bundled_binary_path() helper that locates sidecar binaries
in production (.app bundle) and dev mode. Before spawning yt-dlp, create
a temp directory with bare-name symlinks to the suffixed binaries, set
PATH=tempdir:/usr/bin:/bin, and pass --ffmpeg-location pointing there.

Existing Settings → Engine checks (test_ffmpeg, test_aria2c via RPC,
test_ytdlp) continue using app_handle.shell().sidecar() and are
unaffected.
2026-06-17 18:32:56 +03:30
NimBold 7d1d3e1f9b fix(yt-dlp): replace onedir PyInstaller build with standalone macOS binary
The yt-dlp binary was a PyInstaller onedir build requiring an adjacent
_internal/ directory with the Python runtime. In the Tauri .app bundle,
sidecar binaries are placed in Contents/MacOS/ while the _internal/
resource ends up in Contents/Resources/binaries/, so the bootloader
could not find Python at runtime ([PYI-...:ERROR] Failed to load
Python shared library).

Replace it with the official yt-dlp_macos standalone onefile binary
from the yt-dlp release (v2026.06.09, universal2). This is fully
self-contained — it extracts its runtime to a temp directory at
invocation — and works regardless of Tauri's bundle layout.

Removes the entire _internal/ directory (~70 MB of Python framework,
native .so/.dylib files, etc.) and the stale .gitignore entry.
2026-06-17 18:19:28 +03:30
NimBold b8b4f9e437 fix(aria2): add readiness loop and surface real spawn errors to frontend
aria2c was spawned without a readiness check, causing the frontend to
always see 'error sending request for url' even when the process was
merely starting slowly. If spawn or sidecar creation failed, the error
was only logged and never surfaced to the user.

- Add 5-second readiness loop (poll aria2.getVersion every 100ms) after
  spawning aria2c during app setup, blocking setup until RPC is available
  or timeout expires.
- Store spawn/readiness errors in Aria2DaemonGuard.startup_error and
  check it in test_aria2c before attempting RPC, so the real error
  reason reaches the frontend.
- Keep child process lifecycle in Aria2DaemonGuard (killed on Drop).
2026-06-17 18:11:47 +03:30
NimBold cb39113117 feat(observability): add centralized diagnostics logging engine and native log viewer panel
- Configure tauri-plugin-log with 10MB rotation and 3-file retention
- Add high-frequency format filter to strip [download]% progress ticks
- Hook yt-dlp, aria2c, and native reqwest runners with lifecycle log macros
- Add export_logs Tauri command for async log file export via save dialog
- Create DiagnosticsView React component with streaming monospace console
- Apply severity highlighting: ERROR=red, WARN=yellow, INFO=grey, DEBUG=dim
- Wire Diagnostics tab into sidebar navigation and ActiveView routing
- Log native download failures to log::error! in download.rs
2026-06-17 10:57:32 +03:30
NimBold 69a7192fda fix(permissions): widen sidecar scope to allow-spawn and unrestricted args for runtime reliability 2026-06-17 10:44:24 +03:30
NimBold 6521457cfe fix(infra): harden loopback server lifecycle 2026-06-17 10:07:57 +03:30
NimBold 374b861246 fix(lifecycle): focus primary window on second launch 2026-06-17 09:53:59 +03:30
NimBold 58f4a8a14d fix(security): harden sidecar and media processing lifecycles 2026-06-17 09:45:31 +03:30
NimBold 7c7317adc9 fix(retry): honor attempt caps per download URL 2026-06-17 09:27:17 +03:30
NimBold f6851682dd feat(retry): add connection-aware exponential backoff across all download backends
Transient network drops and Wi-Fi timeouts no longer promote a download
straight to a hard Failed state. A shared retry engine classifies the
error, emits a transient `Retrying` state, and runs a 3-strike
exponential backoff (2s / 5s / 10s) while preserving the active download
allocation (semaphore permit / worker slot). Resumability primitives are
reused on every retry so no downloaded bytes are discarded.

Engine core (src-tauri/src/retry.rs, new):
- BACKOFF_SCHEDULE = [2s, 5s, 10s], MAX_RETRIES = 3
- backoff_for(strike): schedule index with graceful clamp beyond range
- is_transient_network_error(msg): string classifier covering reqwest,
  yt-dlp, and aria2c phrasing; permanent conditions (HTTP 401/403/404/
  410/451, not-found, permission denied, out-of-disk) checked first so
  they always fail fast
- backoff_and_emit / backoff_and_emit_cancel: cancel-safe sleep helpers
  driving a Retrying state emit before each delay
- 9 unit tests covering schedule math, clamping, transient vs permanent
  classification, and permanent-wins-over-transient precedence

State model (src-tauri/src/ipc.rs):
- DownloadStatus::Retrying variant (serialized "retrying")
- DownloadStateEvent::retrying(id, reason) constructor
- Regenerated TypeScript binding: src/bindings/DownloadStatus.ts

Native reqwest backend (src-tauri/src/download.rs):
- DownloadEvent::Retrying variant (headless mirror)
- CoordinatorEventSink::emit_retrying() drives the production
  download-state channel with status "retrying"
- download_file retry core rewritten: transient -> emit_retrying ->
  backoff_for sleep inside a control_rx select (pause/cancel honored
  mid-backoff) -> re-issue Range header; permanent errors or strike
  exhaustion advance to the next URL then hard Failed

yt-dlp media backend (src-tauri/src/lib.rs):
- child spawn+stream loop wrapped in a 3-strike re-spawn loop; stderr
  tail classified and, if transient, the process is re-spawned after
  backoff (--continue resumes); --retry-wait=2 added to aria2c downloader

aria2c backend (src-tauri/src/queue.rs):
- retry-wait=2 option so aria2's internal retries are not rapid-fire
- handle_aria2_download_error: intercepts transient onDownloadError,
  backs off, re-issues addUri, and rotates the stale gid -> id mapping
  via rotate_aria2_gid (fresh addUri mints a new gid; not rotating would
  detach subsequent WS events and leak the semaphore permit permanently)
- aria2_payloads / aria2_retry_strikes tracking with cleanup on terminal
  outcomes

Verification: cargo build clean; cargo test --lib 37 passed / 0 failed.
2026-06-17 09:16:17 +03:30
NimBold bb618aef7d feat(queue): implement backend-driven download queue coordinator
This commit replaces the frontend-imperative download dispatcher with a centralized backend `QueueManager`. It acts as the sole concurrency gatekeeper using a single `tokio::sync::Semaphore` across all download paths (aria2 RPC, native HTTP, yt-dlp media).

Backend Changes:
- **queue**: Added `QueueManager` to manage an ordered `VecDeque` of tasks, semaphore permits, and retirement debt (CAS resize).
- **commands**: Replaced direct start commands with `enqueue_download`, `enqueue_many`, `move_in_queue`, and `remove_from_queue`.
- **ipc**: Exported `DownloadStateEvent` and `QueueDirection` to the frontend.
- **tests**: Added 11 integration tests covering idle-parking, idempotent releases, CAS underflow prevention, and gid-completion races.

Frontend Changes:
- **store**: Made `useDownloadStore` reactive to the backend via the `download-state` event.
- **store**: Removed `processQueue` and introduced `pendingOrder` to track the accurate sequence of queued items.
- **ui**: Updated `DownloadItem` with queue visuals (clock icon, position badge).
- **ui**: Added Move Up/Down controls to interact with the backend queue reordering API.
2026-06-16 17:52:20 +03:30
NimBold 2727d07d62 feat: implement native OS file management and trash utilities 2026-06-16 15:45:22 +03:30
NimBold e41e761b33 feat: implement pre-download media parsing engine and quality selection modal 2026-06-16 15:39:14 +03:30
NimBold 4027ac39ab feat: implement persistent rolling log system with tauri-plugin-log 2026-06-16 15:19:57 +03:30
NimBold 85cdabd72b feat: implement settings engine with directory picker and aria2 configuration sync 2026-06-16 15:10:29 +03:30
NimBold 275eb6e5c4 feat: implement native system tray, background notifications, and download resumability via tauri-plugin-store 2026-06-16 11:38:02 +03:30
NimBold b6678493d3 fix(backend): resolve clippy warnings breaking CI pipeline 2026-06-16 11:21:26 +03:30
NimBold a40e6cfef8 refactor(backend): migrate to tauri v2 sidecars and resolve pipe deadlocks
- Replace tokio::process with tauri_plugin_shell sidecar API for native cross-compilation bundle execution.
- Implement non-blocking stdout/stderr multiplexing in yt-dlp spawn to prevent OS pipe buffer deadlocks.
- Refactor capabilities to restrict execution explicitly to sidecars instead of wildcards.
- Rename local binaries to strictly adhere to target-triple architecture suffixes.
- Drop manual get_binary_name path resolutions in favor of Tauri's externalBin bundler.
2026-06-16 11:08:26 +03:30
NimBold fde5eaacba fix: resolve UI bugs and pasting behavior
- Add global Cmd+V keyboard paste support for downloading links
- Restore striped background styling for alternate rows in download list
- Add ghost rows to seamlessly fill empty download space
- Map correctly formatted item size into the queue immediately
- Remove unused Tauri manager imports from backend
2026-06-15 19:57:20 +03:30
NimBold b4b3104c67 fix(yt-dlp): correct js-runtimes syntax to prevent runtime warnings 2026-06-15 19:29:35 +03:30
NimBold ea6ede8a7f fix(yt-dlp): remove buggy ios,tv player client spoofing
YouTube seems to have enforced DRM checks specifically for the ios,tv player clients, which caused yt-dlp to return 'This video is DRM protected' for all standard videos when using that extractor arg. Removing the arg allows yt-dlp to fallback to web/android which still bypasses bot detection successfully.
2026-06-15 19:10:55 +03:30
NimBold e2744526d8 fix(yt-dlp): remove invalid --js-runtime-path flag
The previous refactor introduced a hallucinated yt-dlp flag from the review report, which caused yt-dlp to immediately fail with 'no such option: --js-runtime-path'. This commit fixes the syntax to correctly format the runtime path into the --js-runtimes argument.
2026-06-15 19:07:15 +03:30
NimBold f4b74898fd refactor(yt-dlp): modernize bot evasion and progress parsing
- Fix config location typo to enable authentication
- Wire up bundled Deno to handle YouTube PoW challenges
- Implement iOS/TV player spoofing to bypass web blocks
- Replace fragile regex progress parsing with structured templates
- Add auto-reconnect logic to Aria2 WebSocket
- Deduplicate media domains and fetch dynamically on frontend
2026-06-15 19:02:12 +03:30
NimBold 44c089fadf refactor(aria2): modernize architecture with websockets and state mapping
- Replace polling loops with global tokio_tungstenite WebSocket listener
- Map Download IDs to Aria2 GIDs in AppState
- Route all standard downloads to Aria2 daemon, mapping all config options
- Refactor path resolution into a DRY helper function
- Add missing terabyte multiplier to frontend speed parser
2026-06-15 18:42:03 +03:30
NimBold 12761caf10 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
2026-06-15 18:21:44 +03:30
NimBold d15e537916 fix: disable aria2 system proxy fallback and fix download-failed event payload 2026-06-15 17:57:26 +03:30
NimBold 601b04c4cb fix: implement aria2 polling to prevent UI from sticking on queued 2026-06-15 17:51:32 +03:30
NimBold 58f0f0dfb1 fix(security): remediate SSRF, path traversal, and command injection vulnerabilities 2026-06-15 14:38:02 +03:30
NimBold 1b04860c94 fix: resolve P3 audit findings 2026-06-15 14:24:34 +03:30
NimBold 789a8161b2 fix: resolve P2 and P3 audit findings 2026-06-15 14:18:16 +03:30
NimBold 285ec0a81d fix: resolve P0 and P1 audit findings
- Fix state corruption on deleted queues in frontend
- Prevent concurrent dispatch of duplicate downloads
- Fix file paths when deleting native downloads
- Ensure pausing and resuming items persist state to database
- Remove duplicate IPC invocations for speed limits
- Stop PropertiesModal from resetting inputs during download progress
- Switch aria2 secret to use secure v4 UUID
- Fix SSRF vulnerability in fetch_metadata via DNS rebinding block
- Resolve race condition when reading media download logs
- Gracefully handle panics when acquiring prevent sleep lock
- Rate limit native downloads respecting global settings
- Enforce TLS certificate validation in backend requests
2026-06-15 14:10:45 +03:30
NimBold 22d59e3c11 chore: fix cargo warnings and remove unused import 2026-06-15 13:41:53 +03:30
NimBold 9e26be5b2c fix: app nap ipc drop and implement delete modal 2026-06-15 13:29:20 +03:30
NimBold d046c95c28 fix(security): add notification plugin permissions to capabilities 2026-06-15 12:33:14 +03:30
NimBold 6cf360bce0 fix: address codebase review issues
- refactor(backend): use tokio::sync::Mutex for DbState and update commands to async
- fix(backend): remove unconditional post-queue system action in scheduler
- refactor(backend): remove dead WebSocket aria2 progress loop
- fix(backend): use character count for deep link payload length check
- fix(backend): implement dynamic port fallback for extension server
- build(backend): apply macos codesigning step for release builds
- security(backend): add explicitly defined Content-Security-Policy
- fix(frontend): replace pause_download API call with remove_download for file cleanup
- fix(frontend): resolve bug ignoring 0% progress reporting
- fix(frontend): append instead of overwrite deep link URLs when Add Modal is open
- style(frontend): append standard .dark class for dark mode themes
- style(frontend): remove ghost row layout hack from download table
- build: decouple typescript binding generation from build step
2026-06-15 11:53:24 +03:30
NimBold 2ddcd2d99a fix(scheduler): wire post queue action on stop time and remove unused field warning 2026-06-15 11:13:11 +03:30
NimBold e7608dc8b4 fix(ci): support clean checkout builds 2026-06-15 10:43:31 +03:30
NimBold 6593f9e76a refactor(repo): promote tauri app to repository root 2026-06-15 10:33:40 +03:30