{"record":{"id":"095101a8a742f73a","repo":"tonhowtf/omniget","slug":"download-cancelled-095101","errorCode":null,"errorMessage":"Download cancelled","messagePattern":"Download cancelled","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"info","filePath":"src-tauri/src/platforms/p2p/mod.rs","lineNumber":166,"sourceCode":"        write_half.write_all(b\"OK\\n\").await?;\n        write_half.flush().await?;\n\n        let _ = progress.send(ProgressUpdate::percent(0.0)).await;\n\n        let sanitized = sanitize_filename::sanitize(&file_name);\n        let output_path = opts.output_dir.join(&sanitized);\n        if let Some(parent) = output_path.parent() {\n            tokio::fs::create_dir_all(parent).await?;\n        }\n\n        let mut file = File::create(&output_path).await?;\n        let mut received: u64 = 0;\n        let mut buf = vec![0u8; CHUNK_SIZE];\n\n        while received < file_size {\n            if opts.cancel_token.is_cancelled() {\n                let _ = tokio::fs::remove_file(&output_path).await;\n                anyhow::bail!(\"Download cancelled\");\n            }\n\n            let to_read = ((file_size - received) as usize).min(CHUNK_SIZE);\n            let n = reader.read(&mut buf[..to_read]).await?;\n            if n == 0 {\n                break;\n            }\n\n            file.write_all(&buf[..n]).await?;\n            received += n as u64;\n\n            if file_size > 0 {\n                let pct = (received as f64 / file_size as f64) * 100.0;\n                let _ = progress.send(ProgressUpdate::percent(pct)).await;\n            }\n        }\n\n        file.flush().await?;","sourceCodeStart":148,"sourceCodeEnd":184,"githubUrl":"https://github.com/tonhowtf/omniget/blob/8600b91f4246848bac346874daa9e61c1fc5677a/src-tauri/src/platforms/p2p/mod.rs#L148-L184","documentation":"The receiver's download loop polls opts.cancel_token each iteration; when the user (or caller) cancels, download deletes the partial output file and bails with \"Download cancelled\". This is an intentional cooperative-cancellation signal, not a fault.","triggerScenarios":"Caller cancels the CancellationToken passed in opts while the while received < file_size loop is reading chunks; next loop iteration detects is_cancelled(), removes output_path, and bails.","commonSituations":"User presses cancel in the UI, the frontend drops the transfer request, or an app-shutdown hook cancels all in-flight downloads.","solutions":["No fix needed — treat as expected control flow: catch it and update UI to 'cancelled'.","If it fires unexpectedly, audit who holds a clone of the cancel_token and whether anything cancels it early.","If you need the partial file for resumption, copy/rename it before cancelling; the handler deletes output_path unconditionally."],"exampleFix":"// before\n// caller\nif err.to_string() == \"Download cancelled\" { /* crash path */ }\n// after\nmatch download(opts).await {\n    Err(e) if e.to_string().contains(\"Download cancelled\") => ui.set_status(\"Cancelled\"),\n    Err(e) => ui.show_error(e),\n    Ok(()) => ui.set_status(\"Done\"),\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"match download(opts).await {\n    Err(e) if e.to_string() == \"Download cancelled\" => ui.set_status(\"Cancelled\"),\n    Err(e) => ui.show_error(e),\n    Ok(path) => ui.show_file(path),\n}","preventionTips":["Only cancel when the user truly aborts; don't reuse one token for unrelated aborts.","Keep a clone of the cancel token per download and drop it when the transfer completes.","Show a confirm dialog before cancelling large downloads.","Copy partial output aside first if resumption may be wanted — the handler deletes the file."],"tags":["cancellation","p2p","download","control-flow"],"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"}