romm_api/core/
interrupt.rs1use std::sync::atomic::{AtomicBool, Ordering};
2use std::sync::Arc;
3
4use thiserror::Error;
5use tokio::sync::Notify;
6
7use crate::error::{DownloadError, RommError};
8
9#[derive(Debug, Error)]
10#[error("operation cancelled by user")]
11pub struct CancelledByUser;
12
13#[derive(Clone, Debug)]
14pub struct InterruptContext {
15 cancelled: Arc<AtomicBool>,
16 notify: Arc<Notify>,
17}
18
19impl InterruptContext {
20 pub fn new() -> Self {
21 let this = Self {
22 cancelled: Arc::new(AtomicBool::new(false)),
23 notify: Arc::new(Notify::new()),
24 };
25 let watcher = this.clone();
26 tokio::spawn(async move {
27 if tokio::signal::ctrl_c().await.is_ok() {
28 watcher.cancel();
29 }
30 });
31 this
32 }
33
34 pub fn cancel(&self) {
35 self.cancelled.store(true, Ordering::SeqCst);
36 self.notify.notify_waiters();
37 }
38
39 pub fn is_cancelled(&self) -> bool {
40 self.cancelled.load(Ordering::SeqCst)
41 }
42
43 pub async fn cancelled(&self) {
44 if self.is_cancelled() {
45 return;
46 }
47 self.notify.notified().await;
48 }
49}
50
51impl Default for InterruptContext {
52 fn default() -> Self {
53 Self::new()
54 }
55}
56
57pub fn cancelled_download_error() -> DownloadError {
58 DownloadError::Cancelled(CancelledByUser)
59}
60
61pub fn cancelled_error() -> DownloadError {
63 cancelled_download_error()
64}
65
66pub fn is_cancelled_download(err: &DownloadError) -> bool {
67 matches!(err, DownloadError::Cancelled(_))
68}
69
70pub fn is_cancelled_error(err: &RommError) -> bool {
71 err.is_cancelled()
72}
73
74#[cfg(test)]
75mod tests {
76 use super::*;
77
78 #[test]
79 fn cancelled_error_is_classified() {
80 let err = RommError::from(cancelled_download_error());
81 assert!(is_cancelled_error(&err));
82 assert!(is_cancelled_download(&cancelled_download_error()));
83 }
84
85 #[tokio::test]
86 async fn context_cancel_sets_flag() {
87 let ctx = InterruptContext::new();
88 assert!(!ctx.is_cancelled());
89 ctx.cancel();
90 assert!(ctx.is_cancelled());
91 }
92}