{"record":{"id":"79d33d929c77fc9e","repo":"tonhowtf/omniget","slug":"download-cancelled-by-user","errorCode":null,"errorMessage":"Download cancelled by user","messagePattern":"Download cancelled by user","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"info","filePath":"src-tauri/omniget-core/src/core/hls_downloader.rs","lineNumber":143,"sourceCode":"            None,\n        )\n        .await\n    }\n\n    #[allow(clippy::too_many_arguments)]\n    pub async fn download_with_quality(\n        &self,\n        m3u8_url: &str,\n        output_path: &str,\n        referer: &str,\n        bytes_tx: Option<UnboundedSender<u64>>,\n        cancel_token: CancellationToken,\n        max_concurrent: u32,\n        max_retries: u32,\n        max_height: Option<u32>,\n    ) -> anyhow::Result<HlsDownloadResult> {\n        if cancel_token.is_cancelled() {\n            anyhow::bail!(\"Download cancelled by user\");\n        }\n\n        let m3u8_text = self.fetch_m3u8_with_retry(m3u8_url, referer, 3).await?;\n\n        let m3u8_bytes = m3u8_text.as_bytes();\n\n        if let Ok((_, master)) = parse_master_playlist(m3u8_bytes) {\n            if let Some(variant) = select_best_variant(&master, max_height) {\n                let available: Vec<u64> = master\n                    .variants\n                    .iter()\n                    .filter(|v| !v.is_i_frame)\n                    .map(variant_height)\n                    .collect();\n                tracing::info!(\n                    \"[hls] variant selected: {}p (requested: {}, available: {:?})\",\n                    variant_height(variant),\n                    max_height","sourceCodeStart":125,"sourceCodeEnd":161,"githubUrl":"https://github.com/tonhowtf/omniget/blob/8600b91f4246848bac346874daa9e61c1fc5677a/src-tauri/omniget-core/src/core/hls_downloader.rs#L125-L161","documentation":"download_with_quality checks the caller-supplied CancellationToken before doing any work and immediately bails if cancellation was already requested. The library surfaces cooperative cancellation as a regular anyhow error rather than a panic or silent no-op, so callers can distinguish user-aborted downloads from network failures.","triggerScenarios":"Calling download() -> download_with_quality with a CancellationToken on which cancel() was already invoked (e.g. the UI fired a cancel while the task was still being spawned), or a token that some other component cancelled before the download started.","commonSituations":"User clicks Cancel in the app at nearly the same moment the download task is enqueued; a timeout watchdog cancels the token just before the worker starts; reusing a single token across sequential downloads without resetting it.","solutions":["Treat this error as an expected outcome: catch it and skip retry logic, since the user asked to cancel.","If the download should proceed, create a fresh (non-cancelled) CancellationToken for the attempt instead of reusing an already-cancelled one.","Check token.is_cancelled() before spawning the download task to avoid starting work that will be rejected.","If the cancel was accidental (e.g. watchdog fired early), fix the cancellation trigger's timing before re-invoking download()."],"exampleFix":"// before\nlet token = shared_token.clone();\nshared_token.cancel(); // watchdog fired earlier\nhls.download(url, quality, token).await?; // -> \"Download cancelled by user\"\n// after\nlet token = CancellationToken::new(); // fresh token for a new attempt\nhls.download(url, quality, token).await?;","handlingStrategy":"try-catch","validationCode":"if cancel_token.is_cancelled() {\n    // skip starting the download at all\n    return;\n}","typeGuard":null,"tryCatchPattern":"match hls.download(url, quality, token.clone()).await {\n    Ok(res) => handle_result(res),\n    Err(e) if e.to_string() == \"Download cancelled by user\" => {\n        // expected: no retry, update UI to 'cancelled'\n    },\n    Err(e) => retry_or_report(e),\n}","preventionTips":["Issue a fresh CancellationToken for each download attempt.","Check is_cancelled() before enqueueing a download task.","Never reuse a token that a previous operation already cancelled.","Distinguish cancellation errors from real failures in your retry logic."],"tags":["cancellation","hls","user-abort","async"],"backgroundTag":"invalid-state-transition","analyzedSha":"8600b91f4246848bac346874daa9e61c1fc5677a","analyzedAt":"2026-09-12T14:29:19.317Z","contentChangedAt":"2026-09-12T14:29:19.317Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}