Skip to content

feat: add resumable and cancellable downloads - #68

Open
guoshulin1992-prog wants to merge 21 commits into
averygan:mainfrom
guoshulin1992-prog:feature/resumable-downloads
Open

guoshulin1992-prog wants to merge 21 commits into
averygan:mainfrom
guoshulin1992-prog:feature/resumable-downloads

Conversation

@guoshulin1992-prog

@guoshulin1992-prog guoshulin1992-prog commented Sep 2, 2026

Copy link
Copy Markdown

Summary

  • Add durable SQLite task records so interrupted downloads can be found after reopening the page or restarting the Docker container.
  • Resume failed, interrupted, or cancelled tasks in the same task directory, preserving yt-dlp partial files; provide Restart for a new task and Delete for explicit cleanup.
  • Add an attempt-scoped Cancel action that stops the downloader process tree, preserves partial files, and exposes cancelling then cancelled states.
  • Pin the selected yt-dlp resource identity and reject in-place resume when the source or selected formats change.
  • Add runtime ownership locking, attempt numbers, staged timeouts, final-file path checks, and restored task cards.

Relationship to #66

This is a stacked PR based on the work in #66, which is still open. The branch contains the progress feature as its base plus resumable-download and cancellation implementations. Please merge #66 first, or rebase/split this branch if an independent diff is preferred.

User-visible behavior

  • An active task shows Cancel. ReClip confirms that the process and its children have stopped before marking the task Cancelled.
  • A failed, interrupted, or cancelled task shows Continue, Restart, and Delete actions.
  • Continue keeps the same job ID and task directory and lets yt-dlp reuse compatible partial files.
  • Restart creates a new job ID and leaves the old task available for inspection or deletion.
  • Completed tasks are restored in the task list but are not automatically downloaded or saved again.
  • The Compose port remains bound to 127.0.0.1:8899 by default.

Verification

  • 60 Python tests passed on the development host; 2 Linux-specific tests were skipped on Windows.
  • 9 Node.js frontend tests passed.
  • py_compile, node --check, git diff --check, and Docker image build passed.
  • Temporary container smoke test passed on local port 8898: homepage returned 200 and /api/jobs returned an empty task list.

Security and data scope

  • Downloader invocation remains shell-free, uses a fixed server-owned format plan, and keeps the -- URL separator.
  • Source URLs are restricted to HTTP(S) without embedded credentials; final files must stay inside their task directory and must not be symlinks.
  • Cancellation is scoped by (job_id, attempt_no), so cancelling an old attempt cannot cancel a later retry.
  • Task history keeps the source URL, title, and partial files until the user explicitly deletes the task. The service has no authentication, so it is intended for local-only use and must not be exposed to a network.

Known limitations

  • Cancel is stop-and-preserve, not a separate Pause button; Continue starts the next attempt and may reuse partial files only when the source and selected format identity remain compatible.
  • Resume depends on the source protocol and current yt-dlp extractor behavior; changed or unsupported resources are rejected and require Restart.
  • HLS/DASH recovery, full browser E2E coverage, and the complete container-restart manual matrix are not included in this PR's verified scope.

robomello and others added 11 commits September 2, 2026 10:54
The url field from POST /api/download, /api/info, and /api/playlist
was passed straight into the yt-dlp argv list. Since these endpoints
are unauthenticated, a caller could pass a value like
"--exec=<cmd>" instead of a real URL; yt-dlp parses any argv item
starting with "-" as an option rather than a positional URL, so
this allowed arbitrary command execution on the host after a
(fake) download.

Fixes:
- Add is_safe_url() to require an http(s) scheme + host, rejecting
  anything that could be interpreted as a CLI flag.
- Insert a "--" argv separator before the URL in every yt-dlp
  invocation so option parsing stops regardless of URL content
  (defense in depth alongside the scheme check).
- Apply the check on all three endpoints that accept a URL.
The url field from POST /api/download, /api/info, and /api/playlist
was passed straight into the yt-dlp argv list. Since these endpoints
are unauthenticated, a caller could pass a value like
"--exec=<cmd>" instead of a real URL; yt-dlp parses any argv item
starting with "-" as an option rather than a positional URL, so
this allowed arbitrary command execution on the host after a
(fake) download.

Fixes:
- Add is_safe_url() to require an http(s) scheme + host, rejecting
  anything that could be interpreted as a CLI flag.
- Insert a "--" argv separator before the URL in every yt-dlp
  invocation so option parsing stops regardless of URL content
  (defense in depth alongside the scheme check).
- Apply the check on all three endpoints that accept a URL.
# Conflicts:
#	app.py
#	docker-compose.yml
@guoshulin1992-prog guoshulin1992-prog changed the title feat: add resumable downloads feat: add resumable and cancellable downloads Sep 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants