diff --git a/.gitignore b/.gitignore index ffdd02a0..c863a72b 100644 --- a/.gitignore +++ b/.gitignore @@ -31,4 +31,6 @@ src-tauri/gen/* !src-tauri/gen/android/ !src-tauri/gen/android/** -*.node \ No newline at end of file +*.node + +content \ No newline at end of file diff --git a/packages/risuko-app/package.json b/packages/risuko-app/package.json index 6a484455..b77cd84e 100644 --- a/packages/risuko-app/package.json +++ b/packages/risuko-app/package.json @@ -1,28 +1,28 @@ { - "name": "@risuko/app", - "version": "0.3.6", - "description": "Risuko download manager — launches the desktop app, downloading it from GitHub Releases on first run", - "license": "MIT", - "repository": { - "type": "git", - "url": "git+https://github.com/YueMiyuki/Risuko.git" - }, - "keywords": [ - "download", - "torrent", - "bittorrent", - "ed2k", - "m3u8", - "ftp", - "download-manager" - ], - "bin": { - "risuko-app": "bin.js" - }, - "files": [ - "bin.js" - ], - "engines": { - "node": ">=22.0.0" - } + "name": "@risuko/app", + "version": "0.3.6", + "description": "Risuko download manager — launches the desktop app, downloading it from GitHub Releases on first run", + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/YueMiyuki/Risuko.git" + }, + "keywords": [ + "download", + "torrent", + "bittorrent", + "ed2k", + "m3u8", + "ftp", + "download-manager" + ], + "bin": { + "risuko-app": "bin.js" + }, + "files": [ + "bin.js" + ], + "engines": { + "node": ">=22.0.0" + } } diff --git a/packages/risuko-cli/npm/darwin-arm64/package.json b/packages/risuko-cli/npm/darwin-arm64/package.json index 832ad949..3365d911 100644 --- a/packages/risuko-cli/npm/darwin-arm64/package.json +++ b/packages/risuko-cli/npm/darwin-arm64/package.json @@ -1,20 +1,20 @@ { - "name": "@risuko/cli-darwin-arm64", - "version": "0.3.6", - "description": "Risuko CLI binary for macOS ARM64", - "repository": { - "type": "git", - "url": "git+https://github.com/YueMiyuki/Risuko.git", - "directory": "packages/risuko-cli/npm/darwin-arm64" - }, - "license": "MIT", - "os": [ - "darwin" - ], - "cpu": [ - "arm64" - ], - "files": [ - "risuko" - ] + "name": "@risuko/cli-darwin-arm64", + "version": "0.3.6", + "description": "Risuko CLI binary for macOS ARM64", + "repository": { + "type": "git", + "url": "git+https://github.com/YueMiyuki/Risuko.git", + "directory": "packages/risuko-cli/npm/darwin-arm64" + }, + "license": "MIT", + "os": [ + "darwin" + ], + "cpu": [ + "arm64" + ], + "files": [ + "risuko" + ] } diff --git a/packages/risuko-cli/npm/darwin-x64/package.json b/packages/risuko-cli/npm/darwin-x64/package.json index cd4b606d..b93a4256 100644 --- a/packages/risuko-cli/npm/darwin-x64/package.json +++ b/packages/risuko-cli/npm/darwin-x64/package.json @@ -1,20 +1,20 @@ { - "name": "@risuko/cli-darwin-x64", - "version": "0.3.6", - "description": "Risuko CLI binary for macOS x64", - "repository": { - "type": "git", - "url": "git+https://github.com/YueMiyuki/Risuko.git", - "directory": "packages/risuko-cli/npm/darwin-x64" - }, - "license": "MIT", - "os": [ - "darwin" - ], - "cpu": [ - "x64" - ], - "files": [ - "risuko" - ] + "name": "@risuko/cli-darwin-x64", + "version": "0.3.6", + "description": "Risuko CLI binary for macOS x64", + "repository": { + "type": "git", + "url": "git+https://github.com/YueMiyuki/Risuko.git", + "directory": "packages/risuko-cli/npm/darwin-x64" + }, + "license": "MIT", + "os": [ + "darwin" + ], + "cpu": [ + "x64" + ], + "files": [ + "risuko" + ] } diff --git a/packages/risuko-cli/npm/linux-arm64-gnu/package.json b/packages/risuko-cli/npm/linux-arm64-gnu/package.json index 46ed6daf..6f9d7a43 100644 --- a/packages/risuko-cli/npm/linux-arm64-gnu/package.json +++ b/packages/risuko-cli/npm/linux-arm64-gnu/package.json @@ -1,20 +1,20 @@ { - "name": "@risuko/cli-linux-arm64-gnu", - "version": "0.3.6", - "description": "Risuko CLI binary for Linux ARM64", - "repository": { - "type": "git", - "url": "git+https://github.com/YueMiyuki/Risuko.git", - "directory": "packages/risuko-cli/npm/linux-arm64-gnu" - }, - "license": "MIT", - "os": [ - "linux" - ], - "cpu": [ - "arm64" - ], - "files": [ - "risuko" - ] + "name": "@risuko/cli-linux-arm64-gnu", + "version": "0.3.6", + "description": "Risuko CLI binary for Linux ARM64", + "repository": { + "type": "git", + "url": "git+https://github.com/YueMiyuki/Risuko.git", + "directory": "packages/risuko-cli/npm/linux-arm64-gnu" + }, + "license": "MIT", + "os": [ + "linux" + ], + "cpu": [ + "arm64" + ], + "files": [ + "risuko" + ] } diff --git a/packages/risuko-cli/npm/linux-x64-gnu/package.json b/packages/risuko-cli/npm/linux-x64-gnu/package.json index db95ebc8..509c36a7 100644 --- a/packages/risuko-cli/npm/linux-x64-gnu/package.json +++ b/packages/risuko-cli/npm/linux-x64-gnu/package.json @@ -1,20 +1,20 @@ { - "name": "@risuko/cli-linux-x64-gnu", - "version": "0.3.6", - "description": "Risuko CLI binary for Linux x64", - "repository": { - "type": "git", - "url": "git+https://github.com/YueMiyuki/Risuko.git", - "directory": "packages/risuko-cli/npm/linux-x64-gnu" - }, - "license": "MIT", - "os": [ - "linux" - ], - "cpu": [ - "x64" - ], - "files": [ - "risuko" - ] + "name": "@risuko/cli-linux-x64-gnu", + "version": "0.3.6", + "description": "Risuko CLI binary for Linux x64", + "repository": { + "type": "git", + "url": "git+https://github.com/YueMiyuki/Risuko.git", + "directory": "packages/risuko-cli/npm/linux-x64-gnu" + }, + "license": "MIT", + "os": [ + "linux" + ], + "cpu": [ + "x64" + ], + "files": [ + "risuko" + ] } diff --git a/packages/risuko-cli/npm/win32-arm64-msvc/package.json b/packages/risuko-cli/npm/win32-arm64-msvc/package.json index 3b797379..3b0640ee 100644 --- a/packages/risuko-cli/npm/win32-arm64-msvc/package.json +++ b/packages/risuko-cli/npm/win32-arm64-msvc/package.json @@ -1,20 +1,20 @@ { - "name": "@risuko/cli-win32-arm64-msvc", - "version": "0.3.6", - "description": "Risuko CLI binary for Windows ARM64", - "repository": { - "type": "git", - "url": "git+https://github.com/YueMiyuki/Risuko.git", - "directory": "packages/risuko-cli/npm/win32-arm64-msvc" - }, - "license": "MIT", - "os": [ - "win32" - ], - "cpu": [ - "arm64" - ], - "files": [ - "risuko.exe" - ] + "name": "@risuko/cli-win32-arm64-msvc", + "version": "0.3.6", + "description": "Risuko CLI binary for Windows ARM64", + "repository": { + "type": "git", + "url": "git+https://github.com/YueMiyuki/Risuko.git", + "directory": "packages/risuko-cli/npm/win32-arm64-msvc" + }, + "license": "MIT", + "os": [ + "win32" + ], + "cpu": [ + "arm64" + ], + "files": [ + "risuko.exe" + ] } diff --git a/packages/risuko-cli/npm/win32-x64-msvc/package.json b/packages/risuko-cli/npm/win32-x64-msvc/package.json index fc71bd03..e945407c 100644 --- a/packages/risuko-cli/npm/win32-x64-msvc/package.json +++ b/packages/risuko-cli/npm/win32-x64-msvc/package.json @@ -1,20 +1,20 @@ { - "name": "@risuko/cli-win32-x64-msvc", - "version": "0.3.6", - "description": "Risuko CLI binary for Windows x64", - "repository": { - "type": "git", - "url": "git+https://github.com/YueMiyuki/Risuko.git", - "directory": "packages/risuko-cli/npm/win32-x64-msvc" - }, - "license": "MIT", - "os": [ - "win32" - ], - "cpu": [ - "x64" - ], - "files": [ - "risuko.exe" - ] + "name": "@risuko/cli-win32-x64-msvc", + "version": "0.3.6", + "description": "Risuko CLI binary for Windows x64", + "repository": { + "type": "git", + "url": "git+https://github.com/YueMiyuki/Risuko.git", + "directory": "packages/risuko-cli/npm/win32-x64-msvc" + }, + "license": "MIT", + "os": [ + "win32" + ], + "cpu": [ + "x64" + ], + "files": [ + "risuko.exe" + ] } diff --git a/packages/risuko-cli/package.json b/packages/risuko-cli/package.json index a8a98956..5ba4df9c 100644 --- a/packages/risuko-cli/package.json +++ b/packages/risuko-cli/package.json @@ -1,37 +1,37 @@ { - "name": "@risuko/cli", - "version": "0.3.6", - "description": "Risuko download engine CLI — multi-protocol downloads (HTTP, BitTorrent, ED2K, M3U8, FTP/SFTP)", - "license": "MIT", - "repository": { - "type": "git", - "url": "git+https://github.com/YueMiyuki/Risuko.git" - }, - "keywords": [ - "download", - "torrent", - "bittorrent", - "ed2k", - "m3u8", - "ftp", - "cli", - "download-manager" - ], - "bin": { - "risuko": "bin.js" - }, - "files": [ - "bin.js" - ], - "optionalDependencies": { - "@risuko/cli-darwin-arm64": "0.3.6", - "@risuko/cli-darwin-x64": "0.3.6", - "@risuko/cli-linux-arm64-gnu": "0.3.6", - "@risuko/cli-linux-x64-gnu": "0.3.6", - "@risuko/cli-win32-arm64-msvc": "0.3.6", - "@risuko/cli-win32-x64-msvc": "0.3.6" - }, - "engines": { - "node": ">= 18" - } + "name": "@risuko/cli", + "version": "0.3.6", + "description": "Risuko download engine CLI — multi-protocol downloads (HTTP, BitTorrent, ED2K, M3U8, FTP/SFTP)", + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/YueMiyuki/Risuko.git" + }, + "keywords": [ + "download", + "torrent", + "bittorrent", + "ed2k", + "m3u8", + "ftp", + "cli", + "download-manager" + ], + "bin": { + "risuko": "bin.js" + }, + "files": [ + "bin.js" + ], + "optionalDependencies": { + "@risuko/cli-darwin-arm64": "0.3.6", + "@risuko/cli-darwin-x64": "0.3.6", + "@risuko/cli-linux-arm64-gnu": "0.3.6", + "@risuko/cli-linux-x64-gnu": "0.3.6", + "@risuko/cli-win32-arm64-msvc": "0.3.6", + "@risuko/cli-win32-x64-msvc": "0.3.6" + }, + "engines": { + "node": ">= 18" + } } diff --git a/packages/risuko-js/npm/darwin-arm64/package.json b/packages/risuko-js/npm/darwin-arm64/package.json index 54843033..a94df78d 100644 --- a/packages/risuko-js/npm/darwin-arm64/package.json +++ b/packages/risuko-js/npm/darwin-arm64/package.json @@ -1,21 +1,21 @@ { - "name": "@risuko/js-darwin-arm64", - "version": "0.3.6", - "description": "Risuko JS native module for macOS ARM64", - "repository": { - "type": "git", - "url": "git+https://github.com/YueMiyuki/Risuko.git", - "directory": "packages/risuko-js/npm/darwin-arm64" - }, - "license": "MIT", - "os": [ - "darwin" - ], - "cpu": [ - "arm64" - ], - "main": "risuko.darwin-arm64.node", - "files": [ - "risuko.darwin-arm64.node" - ] + "name": "@risuko/js-darwin-arm64", + "version": "0.3.6", + "description": "Risuko JS native module for macOS ARM64", + "repository": { + "type": "git", + "url": "git+https://github.com/YueMiyuki/Risuko.git", + "directory": "packages/risuko-js/npm/darwin-arm64" + }, + "license": "MIT", + "os": [ + "darwin" + ], + "cpu": [ + "arm64" + ], + "main": "risuko.darwin-arm64.node", + "files": [ + "risuko.darwin-arm64.node" + ] } diff --git a/packages/risuko-js/npm/darwin-x64/package.json b/packages/risuko-js/npm/darwin-x64/package.json index 90af7cc0..7fc03bc8 100644 --- a/packages/risuko-js/npm/darwin-x64/package.json +++ b/packages/risuko-js/npm/darwin-x64/package.json @@ -1,21 +1,21 @@ { - "name": "@risuko/js-darwin-x64", - "version": "0.3.6", - "description": "Risuko JS native module for macOS x64", - "repository": { - "type": "git", - "url": "git+https://github.com/YueMiyuki/Risuko.git", - "directory": "packages/risuko-js/npm/darwin-x64" - }, - "license": "MIT", - "os": [ - "darwin" - ], - "cpu": [ - "x64" - ], - "main": "risuko.darwin-x64.node", - "files": [ - "risuko.darwin-x64.node" - ] + "name": "@risuko/js-darwin-x64", + "version": "0.3.6", + "description": "Risuko JS native module for macOS x64", + "repository": { + "type": "git", + "url": "git+https://github.com/YueMiyuki/Risuko.git", + "directory": "packages/risuko-js/npm/darwin-x64" + }, + "license": "MIT", + "os": [ + "darwin" + ], + "cpu": [ + "x64" + ], + "main": "risuko.darwin-x64.node", + "files": [ + "risuko.darwin-x64.node" + ] } diff --git a/packages/risuko-js/npm/linux-arm64-gnu/package.json b/packages/risuko-js/npm/linux-arm64-gnu/package.json index bfeae190..b4df486f 100644 --- a/packages/risuko-js/npm/linux-arm64-gnu/package.json +++ b/packages/risuko-js/npm/linux-arm64-gnu/package.json @@ -1,24 +1,24 @@ { - "name": "@risuko/js-linux-arm64-gnu", - "version": "0.3.6", - "description": "Risuko JS native module for Linux ARM64", - "repository": { - "type": "git", - "url": "git+https://github.com/YueMiyuki/Risuko.git", - "directory": "packages/risuko-js/npm/linux-arm64-gnu" - }, - "license": "MIT", - "os": [ - "linux" - ], - "cpu": [ - "arm64" - ], - "main": "risuko.linux-arm64-gnu.node", - "files": [ - "risuko.linux-arm64-gnu.node" - ], - "libc": [ - "glibc" - ] + "name": "@risuko/js-linux-arm64-gnu", + "version": "0.3.6", + "description": "Risuko JS native module for Linux ARM64", + "repository": { + "type": "git", + "url": "git+https://github.com/YueMiyuki/Risuko.git", + "directory": "packages/risuko-js/npm/linux-arm64-gnu" + }, + "license": "MIT", + "os": [ + "linux" + ], + "cpu": [ + "arm64" + ], + "main": "risuko.linux-arm64-gnu.node", + "files": [ + "risuko.linux-arm64-gnu.node" + ], + "libc": [ + "glibc" + ] } diff --git a/packages/risuko-js/npm/linux-x64-gnu/package.json b/packages/risuko-js/npm/linux-x64-gnu/package.json index 25a1259f..932ac675 100644 --- a/packages/risuko-js/npm/linux-x64-gnu/package.json +++ b/packages/risuko-js/npm/linux-x64-gnu/package.json @@ -1,24 +1,24 @@ { - "name": "@risuko/js-linux-x64-gnu", - "version": "0.3.6", - "description": "Risuko JS native module for Linux x64", - "repository": { - "type": "git", - "url": "git+https://github.com/YueMiyuki/Risuko.git", - "directory": "packages/risuko-js/npm/linux-x64-gnu" - }, - "license": "MIT", - "os": [ - "linux" - ], - "cpu": [ - "x64" - ], - "main": "risuko.linux-x64-gnu.node", - "files": [ - "risuko.linux-x64-gnu.node" - ], - "libc": [ - "glibc" - ] + "name": "@risuko/js-linux-x64-gnu", + "version": "0.3.6", + "description": "Risuko JS native module for Linux x64", + "repository": { + "type": "git", + "url": "git+https://github.com/YueMiyuki/Risuko.git", + "directory": "packages/risuko-js/npm/linux-x64-gnu" + }, + "license": "MIT", + "os": [ + "linux" + ], + "cpu": [ + "x64" + ], + "main": "risuko.linux-x64-gnu.node", + "files": [ + "risuko.linux-x64-gnu.node" + ], + "libc": [ + "glibc" + ] } diff --git a/packages/risuko-js/npm/win32-arm64-msvc/package.json b/packages/risuko-js/npm/win32-arm64-msvc/package.json index baa644fa..695d5554 100644 --- a/packages/risuko-js/npm/win32-arm64-msvc/package.json +++ b/packages/risuko-js/npm/win32-arm64-msvc/package.json @@ -1,21 +1,21 @@ { - "name": "@risuko/js-win32-arm64-msvc", - "version": "0.3.6", - "description": "Risuko JS native module for Windows ARM64", - "repository": { - "type": "git", - "url": "git+https://github.com/YueMiyuki/Risuko.git", - "directory": "packages/risuko-js/npm/win32-arm64-msvc" - }, - "license": "MIT", - "os": [ - "win32" - ], - "cpu": [ - "arm64" - ], - "main": "risuko.win32-arm64-msvc.node", - "files": [ - "risuko.win32-arm64-msvc.node" - ] + "name": "@risuko/js-win32-arm64-msvc", + "version": "0.3.6", + "description": "Risuko JS native module for Windows ARM64", + "repository": { + "type": "git", + "url": "git+https://github.com/YueMiyuki/Risuko.git", + "directory": "packages/risuko-js/npm/win32-arm64-msvc" + }, + "license": "MIT", + "os": [ + "win32" + ], + "cpu": [ + "arm64" + ], + "main": "risuko.win32-arm64-msvc.node", + "files": [ + "risuko.win32-arm64-msvc.node" + ] } diff --git a/packages/risuko-js/npm/win32-x64-msvc/package.json b/packages/risuko-js/npm/win32-x64-msvc/package.json index 19451e63..312b3512 100644 --- a/packages/risuko-js/npm/win32-x64-msvc/package.json +++ b/packages/risuko-js/npm/win32-x64-msvc/package.json @@ -1,21 +1,21 @@ { - "name": "@risuko/js-win32-x64-msvc", - "version": "0.3.6", - "description": "Risuko JS native module for Windows x64", - "repository": { - "type": "git", - "url": "git+https://github.com/YueMiyuki/Risuko.git", - "directory": "packages/risuko-js/npm/win32-x64-msvc" - }, - "license": "MIT", - "os": [ - "win32" - ], - "cpu": [ - "x64" - ], - "main": "risuko.win32-x64-msvc.node", - "files": [ - "risuko.win32-x64-msvc.node" - ] + "name": "@risuko/js-win32-x64-msvc", + "version": "0.3.6", + "description": "Risuko JS native module for Windows x64", + "repository": { + "type": "git", + "url": "git+https://github.com/YueMiyuki/Risuko.git", + "directory": "packages/risuko-js/npm/win32-x64-msvc" + }, + "license": "MIT", + "os": [ + "win32" + ], + "cpu": [ + "x64" + ], + "main": "risuko.win32-x64-msvc.node", + "files": [ + "risuko.win32-x64-msvc.node" + ] } diff --git a/packages/risuko-js/package.json b/packages/risuko-js/package.json index daa3bbda..191f1241 100644 --- a/packages/risuko-js/package.json +++ b/packages/risuko-js/package.json @@ -1,47 +1,47 @@ { - "name": "@risuko/risuko-js", - "version": "0.3.6", - "description": "Risuko download engine — Node.js native bindings for multi-protocol downloads (HTTP, BitTorrent, ED2K, M3U8, FTP/SFTP)", - "main": "index.js", - "types": "index.d.ts", - "license": "MIT", - "repository": { - "type": "git", - "url": "git+https://github.com/YueMiyuki/Risuko.git" - }, - "keywords": [ - "download", - "torrent", - "bittorrent", - "ed2k", - "m3u8", - "ftp", - "download-manager" - ], - "files": [ - "index.js", - "index.d.ts" - ], - "napi": { - "name": "risuko", - "triples": { - "defaults": true, - "additional": [ - "aarch64-apple-darwin", - "aarch64-unknown-linux-gnu", - "aarch64-pc-windows-msvc" - ] - } - }, - "optionalDependencies": { - "@risuko/js-darwin-arm64": "0.3.6", - "@risuko/js-darwin-x64": "0.3.6", - "@risuko/js-linux-x64-gnu": "0.3.6", - "@risuko/js-linux-arm64-gnu": "0.3.6", - "@risuko/js-win32-x64-msvc": "0.3.6", - "@risuko/js-win32-arm64-msvc": "0.3.6" - }, - "engines": { - "node": ">= 22" - } + "name": "@risuko/risuko-js", + "version": "0.3.6", + "description": "Risuko download engine — Node.js native bindings for multi-protocol downloads (HTTP, BitTorrent, ED2K, M3U8, FTP/SFTP)", + "main": "index.js", + "types": "index.d.ts", + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/YueMiyuki/Risuko.git" + }, + "keywords": [ + "download", + "torrent", + "bittorrent", + "ed2k", + "m3u8", + "ftp", + "download-manager" + ], + "files": [ + "index.js", + "index.d.ts" + ], + "napi": { + "name": "risuko", + "triples": { + "defaults": true, + "additional": [ + "aarch64-apple-darwin", + "aarch64-unknown-linux-gnu", + "aarch64-pc-windows-msvc" + ] + } + }, + "optionalDependencies": { + "@risuko/js-darwin-arm64": "0.3.6", + "@risuko/js-darwin-x64": "0.3.6", + "@risuko/js-linux-x64-gnu": "0.3.6", + "@risuko/js-linux-arm64-gnu": "0.3.6", + "@risuko/js-win32-x64-msvc": "0.3.6", + "@risuko/js-win32-arm64-msvc": "0.3.6" + }, + "engines": { + "node": ">= 22" + } } diff --git a/src-tauri/risuko-engine/src/engine/http.rs b/src-tauri/risuko-engine/src/engine/http.rs index d5c5a16f..c8cc9de6 100644 --- a/src-tauri/risuko-engine/src/engine/http.rs +++ b/src-tauri/risuko-engine/src/engine/http.rs @@ -22,11 +22,6 @@ const PART_SUFFIX: &str = ".part"; const DEFAULT_MIN_SPLIT_SIZE: u64 = 1024 * 1024; /// Max retries per piece on transient errors const CHUNK_MAX_RETRIES: u32 = 5; -/// Max auto-retries for the single-connection path on transient network -/// errors. Each retry resumes in place from the partial `.part` file, so a -/// flaky link recovers without dropping the task to Error / needing a manual -/// resume. Mirrors the per-piece retry budget of the multi-chunk path -const SINGLE_MAX_RETRIES: u32 = 5; /// Resume granularity: every multi-chunk download is divided into 1 MiB pieces. /// Workers pull pieces off a shared queue (work-stealing for free) and resume /// preserves per-piece byte progress so a SIGKILL never loses more than the @@ -859,6 +854,14 @@ async fn run_single_uri_download( }) .unwrap_or(8) .max(1) as usize; + let max_worker_retries = options + .get("max-worker-retries") + .and_then(|v| { + v.as_u64() + .or_else(|| v.as_str().and_then(|s| s.parse().ok())) + }) + .map(|v| v.max(1).min(u64::from(u32::MAX)) as u32) + .unwrap_or(CHUNK_MAX_RETRIES); // aria2-compatible `min-split-size` (bytes, with K/M suffixes accepted). // Default 1 MiB — smaller files use a single connection. @@ -1006,6 +1009,7 @@ async fn run_single_uri_download( &chunk_completed, stall.clone(), falloc_mode, + max_worker_retries, ) .await; if let Ok(ref path) = result { @@ -1098,7 +1102,7 @@ async fn run_single_uri_download( match attempt { Err(ref e) - if single_attempt < SINGLE_MAX_RETRIES + if single_attempt < max_worker_retries && !cancelled.load(Ordering::Relaxed) && !cancel_token.is_cancelled() && is_transient_single_error(e) => @@ -1106,7 +1110,7 @@ async fn run_single_uri_download( single_attempt += 1; let resumed = completed.load(Ordering::Relaxed); tracing::warn!( - "Single-connection attempt {single_attempt}/{SINGLE_MAX_RETRIES} failed \ + "Single-connection attempt {single_attempt}/{max_worker_retries} failed \ ({e}); resuming from {resumed} bytes" ); speed.store(0, Ordering::Relaxed); @@ -1157,6 +1161,7 @@ async fn run_single_uri_download( &chunk_completed, stall.clone(), falloc_mode, + max_worker_retries, ) .await; if let Ok(ref path) = mc_result { @@ -1533,6 +1538,7 @@ async fn run_multi_chunk( chunk_completed: &[Arc], stall: StallWatchdog, falloc_mode: super::falloc::Mode, + max_retries: u32, ) -> Result { total.store(content_length, Ordering::Relaxed); @@ -1649,6 +1655,7 @@ async fn run_multi_chunk( tl, etag, wc, + max_retries, ) .await })); @@ -1716,9 +1723,7 @@ async fn run_multi_chunk( return Err(format!("fsync before rename failed: {e}")); } delete_chunk_meta(part_path); - tracing::debug!( - "run_multi_chunk finalizing: part_path={part_path:?}, filename={filename:?}" - ); + tracing::debug!("run_multi_chunk finalizing: part_path={part_path:?}, filename={filename:?}"); finalize_download(part_path, filename, dir_path) } @@ -1739,6 +1744,7 @@ async fn piece_worker( task_limiter: Arc, expected_etag: Option, worker_completed: Option>, + max_retries: u32, ) -> Result<(), String> { let mut retry_count: u32 = 0; loop { @@ -1799,15 +1805,15 @@ async fn piece_worker( // pin a worker in an infinite loop queue.release(idx); retry_count += 1; - if retry_count > CHUNK_MAX_RETRIES { + if retry_count > max_retries { return Err(format!( - "Worker {worker_id} failed after {CHUNK_MAX_RETRIES} retries on \ + "Worker {worker_id} failed after {max_retries} retries on \ piece {idx}: server closed stream early" )); } tracing::warn!( "Worker {worker_id} piece {idx} attempt \ - {retry_count}/{CHUNK_MAX_RETRIES}: early EOF, will retry" + {retry_count}/{max_retries}: early EOF, will retry" ); tokio::time::sleep(std::time::Duration::from_secs(retry_count as u64)).await; } @@ -1819,15 +1825,15 @@ async fn piece_worker( Some(e) => { queue.release(idx); retry_count += 1; - if retry_count > CHUNK_MAX_RETRIES { + if retry_count > max_retries { return Err(format!( - "Worker {worker_id} failed after {CHUNK_MAX_RETRIES} retries on \ + "Worker {worker_id} failed after {max_retries} retries on \ piece {idx}: {e}" )); } tracing::warn!( "Worker {worker_id} piece {idx} attempt \ - {retry_count}/{CHUNK_MAX_RETRIES}: {e}, will retry" + {retry_count}/{max_retries}: {e}, will retry" ); tokio::time::sleep(std::time::Duration::from_secs(retry_count as u64)).await; } @@ -2639,9 +2645,7 @@ fn adopt_suggested_filename( && fs::metadata(current_part_path) .map(|m| m.len() > 0) .unwrap_or(false); - tracing::debug!( - "adopt_suggested_filename: current_has_bytes={current_has_bytes}" - ); + tracing::debug!("adopt_suggested_filename: current_has_bytes={current_has_bytes}"); if current_has_bytes { tracing::debug!("adopt_suggested_filename: rejected (current .part has bytes)"); return None; diff --git a/src-tauri/risuko-engine/src/engine/manager.rs b/src-tauri/risuko-engine/src/engine/manager.rs index cded756c..987df670 100644 --- a/src-tauri/risuko-engine/src/engine/manager.rs +++ b/src-tauri/risuko-engine/src/engine/manager.rs @@ -2379,9 +2379,14 @@ impl TaskManager { .iter_mut() .find(|t| t.gid == gid) .ok_or_else(|| format!("Task {} not found or not paused", gid))?; - if task.status != TaskStatus::Paused { + if task.status != TaskStatus::Paused && task.status != TaskStatus::Error { return Err(format!("Task {} not found or not paused", gid)); } + // Clear stale error state so the task re-enters the queue cleanly + if task.status == TaskStatus::Error { + task.error_code = None; + task.error_message = None; + } is_torrent = task.kind == TaskKind::Torrent; if is_torrent { task.status = TaskStatus::Active; diff --git a/src-tauri/risuko-engine/src/engine/options.rs b/src-tauri/risuko-engine/src/engine/options.rs index 2697bec0..88b3972a 100644 --- a/src-tauri/risuko-engine/src/engine/options.rs +++ b/src-tauri/risuko-engine/src/engine/options.rs @@ -60,6 +60,7 @@ impl EngineOptions { "purge-record-on-start", "task-routing-rules", "file-category-dirs", + "max-worker-retries", ] { if let Some(v) = user.get(key) { global.insert(key.into(), v.clone()); diff --git a/src/renderer/components/Preference/Basic.vue b/src/renderer/components/Preference/Basic.vue index 7da390cf..0b48b952 100644 --- a/src/renderer/components/Preference/Basic.vue +++ b/src/renderer/components/Preference/Basic.vue @@ -463,6 +463,14 @@ +
+
+ + +
+
@@ -678,6 +686,7 @@ const initForm = (config) => { autoRetry, autoRetryInterval, autoRetryStrategy, + workerMaxRetries, autoHideWindow, btForceEncryption, btSaveMetadata, @@ -717,6 +726,7 @@ const initForm = (config) => { autoRetryStrategy === RETRY_STRATEGY_EXPONENTIAL ? RETRY_STRATEGY_EXPONENTIAL : RETRY_STRATEGY_STATIC, + workerMaxRetries: normalizePositiveInt(workerMaxRetries, 5, 1, 20), autoHideWindow: parseBooleanConfig(autoHideWindow), btForceEncryption: parseBooleanConfig(btForceEncryption), btSaveMetadata: parseBooleanConfig(btSaveMetadata), @@ -1077,6 +1087,15 @@ export default { : RETRY_STRATEGY_STATIC; } + if ("workerMaxRetries" in data) { + data.workerMaxRetries = normalizePositiveInt( + this.form.workerMaxRetries, + 5, + 1, + 20, + ); + } + logger.log("[Risuko] preference changed data:", data); usePreferenceStore() diff --git a/src/shared/configKeys.ts b/src/shared/configKeys.ts index 0cbccea4..8ffd5609 100644 --- a/src/shared/configKeys.ts +++ b/src/shared/configKeys.ts @@ -104,6 +104,7 @@ const systemKeys = [ "max-download-limit", "max-overall-download-limit", "max-overall-upload-limit", + "max-worker-retries", "netrc-path", "no-netrc", "no-proxy", diff --git a/src/shared/locales/en-US/preferences.ts b/src/shared/locales/en-US/preferences.ts index e1407111..8c6ee702 100644 --- a/src/shared/locales/en-US/preferences.ts +++ b/src/shared/locales/en-US/preferences.ts @@ -55,6 +55,7 @@ export default { "auto-retry-strategy-exponential": "Exponential backoff", "auto-retry-interval": "Base retry interval", "auto-retry-interval-unit": "seconds", + "worker-max-retries": "Max retries per worker piece", "auto-detect-low-speed-tasks": "Auto detect low speed tasks", "auto-detect-low-speed-tasks-tips": "If a running task stays below the speed threshold, pause and restart it automatically.", diff --git a/src/shared/locales/zh-CN/preferences.ts b/src/shared/locales/zh-CN/preferences.ts index 2cd3c132..bd78ae41 100644 --- a/src/shared/locales/zh-CN/preferences.ts +++ b/src/shared/locales/zh-CN/preferences.ts @@ -54,6 +54,7 @@ export default { "auto-retry-strategy-exponential": "指数退避", "auto-retry-interval": "基础重试间隔", "auto-retry-interval-unit": "秒", + "worker-max-retries": "分片下载最大重试次数", "auto-detect-low-speed-tasks": "自动检测低速任务", "auto-detect-low-speed-tasks-tips": "当任务速度持续低于阈值时,自动暂停并重启任务。",