{"record":{"id":"3cb19cba70c5270f","repo":"tonhowtf/omniget","slug":"download-cancelled-youtube","errorCode":null,"errorMessage":"Download cancelled","messagePattern":"Download cancelled","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"info","filePath":"src-tauri/omniget-core/src/platforms/youtube.rs","lineNumber":368,"sourceCode":"        opts: &DownloadOptions,\n        progress: mpsc::Sender<ProgressUpdate>,\n        ytdlp_path: &std::path::Path,\n        quality_height: Option<u32>,\n    ) -> anyhow::Result<DownloadResult> {\n        let playlist_dir = opts\n            .output_dir\n            .join(sanitize_filename::sanitize(&info.title));\n        tokio::fs::create_dir_all(&playlist_dir).await?;\n\n        let total = info.available_qualities.len();\n        let mut total_bytes = 0u64;\n        let mut last_path = playlist_dir.clone();\n        let mut success_count = 0usize;\n        let mut last_err: Option<anyhow::Error> = None;\n\n        for (i, entry) in info.available_qualities.iter().enumerate() {\n            if opts.cancel_token.is_cancelled() {\n                anyhow::bail!(\"Download cancelled\");\n            }\n\n            let (video_tx, mut video_rx) = mpsc::channel::<ProgressUpdate>(16);\n            let progress_tx = progress.clone();\n            let video_idx = i;\n            let video_total = total;\n            let forwarder = tokio::spawn(async move {\n                let mut max_pct = 0.0_f64;\n                while let Some(pu) = video_rx.recv().await {\n                    max_pct = max_pct.max(pu.percent);\n                    let overall = (video_idx as f64 / video_total as f64) * 100.0\n                        + (max_pct / video_total as f64);\n                    let _ = progress_tx\n                        .send(ProgressUpdate::rich(\n                            overall,\n                            None,\n                            None,\n                            pu.speed_bps,","sourceCodeStart":350,"sourceCodeEnd":386,"githubUrl":"https://github.com/tonhowtf/omniget/blob/8600b91f4246848bac346874daa9e61c1fc5677a/src-tauri/omniget-core/src/platforms/youtube.rs#L350-L386","documentation":"During YouTube playlist downloads (youtube.rs download_playlist), the loop over playlist entries checks opts.cancel_token at the top of each iteration and bails if cancelled. Unlike per-video cancellation, this fires before starting a new entry, so completed videos are kept.","triggerScenarios":"Cancelling the cancel_token while a playlist download loop is between entries; calling download which delegates to download_playlist and then cancelling mid-playlist.","commonSituations":"User hits stop during a long playlist download; app shutdown triggers cancellation; UI cancels after N videos.","solutions":["This is an intentional abort — catch it, report partial success (success_count), and clean up temp state.","If only pausing is desired, implement a pause flag instead of cancelling the token.","Check cancellation before calling download to avoid starting a playlist you'll immediately cancel."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"if opts.cancel_token.is_cancelled() {\n    return; // don't start a playlist that is already cancelled\n}","typeGuard":null,"tryCatchPattern":"match download(url, opts).await {\n    Err(e) if e.to_string() == \"Download cancelled\" => report_partial(success_count, total),\n    other => other?,\n}","preventionTips":["Use a pause flag for pause/resume instead of the cancel token","Report partial success counts on cancellation","Don't reuse a cancelled token for a new playlist run"],"tags":["youtube","playlist","cancellation","async"],"backgroundTag":"operation-cancelled","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"}