hassium_core/fetch/
mod.rs

1pub mod engines;
2
3pub mod prelude {
4    pub use super::{engines::prelude::*, engines::*};
5}
6
7use crate::id::ID;
8use std::{
9    mem::replace,
10    sync::{Arc, Mutex},
11};
12
13pub type FetchProcessID = ID<FetchProcess>;
14
15#[derive(Debug, PartialEq, Copy, Clone)]
16pub enum FetchCancelReason {
17    User,
18    Error,
19}
20
21#[derive(Debug, PartialEq, Copy, Clone)]
22pub enum FetchStatus {
23    Empty,
24    InProgress(f32),
25    Done,
26    Canceled(FetchCancelReason),
27}
28
29pub trait FetchProcessReader: Send + Sync {
30    fn status(&self) -> FetchStatus;
31    fn read(&self) -> Option<Vec<u8>>;
32    fn box_clone(&self) -> Box<FetchProcessReader>;
33}
34
35impl Clone for Box<FetchProcessReader> {
36    fn clone(&self) -> Self {
37        self.box_clone()
38    }
39}
40
41#[derive(Clone)]
42pub struct FetchProcess {
43    id: FetchProcessID,
44    inner: Arc<Mutex<(FetchStatus, Option<Vec<u8>>)>>,
45}
46
47impl Default for FetchProcess {
48    fn default() -> Self {
49        Self::new()
50    }
51}
52
53impl FetchProcess {
54    #[inline]
55    pub fn new() -> Self {
56        Self {
57            id: FetchProcessID::new(),
58            inner: Arc::new(Mutex::new((FetchStatus::Empty, None))),
59        }
60    }
61
62    #[inline]
63    pub fn new_start() -> Self {
64        Self {
65            id: FetchProcessID::new(),
66            inner: Arc::new(Mutex::new((FetchStatus::InProgress(0.0), None))),
67        }
68    }
69
70    #[inline]
71    pub fn new_done(data: Vec<u8>) -> Self {
72        Self {
73            id: FetchProcessID::new(),
74            inner: Arc::new(Mutex::new((FetchStatus::Done, Some(data)))),
75        }
76    }
77
78    #[inline]
79    pub fn new_cancel(reason: FetchCancelReason) -> Self {
80        Self {
81            id: FetchProcessID::new(),
82            inner: Arc::new(Mutex::new((FetchStatus::Canceled(reason), None))),
83        }
84    }
85
86    #[inline]
87    pub fn id(&self) -> FetchProcessID {
88        self.id
89    }
90
91    pub fn start(&mut self) {
92        if let Ok(mut meta) = self.inner.lock() {
93            *meta = (FetchStatus::InProgress(0.0), None);
94        }
95    }
96
97    pub fn progress(&mut self, value: f32) {
98        if let Ok(mut meta) = self.inner.lock() {
99            *meta = (FetchStatus::InProgress(value), None);
100        }
101    }
102
103    pub fn done(&mut self, data: Vec<u8>) {
104        if let Ok(mut meta) = self.inner.lock() {
105            *meta = (FetchStatus::Done, Some(data));
106        }
107    }
108
109    pub fn cancel(&mut self, reason: FetchCancelReason) {
110        if let Ok(mut meta) = self.inner.lock() {
111            *meta = (FetchStatus::Canceled(reason), None);
112        }
113    }
114
115    pub fn readers_count(&self) -> usize {
116        Arc::strong_count(&self.inner) + Arc::weak_count(&self.inner) - 1
117    }
118}
119
120impl FetchProcessReader for FetchProcess {
121    fn status(&self) -> FetchStatus {
122        if let Ok(meta) = self.inner.lock() {
123            meta.0
124        } else {
125            FetchStatus::Empty
126        }
127    }
128
129    fn read(&self) -> Option<Vec<u8>> {
130        if let Ok(mut meta) = self.inner.lock() {
131            if meta.0 == FetchStatus::Done {
132                let old: (FetchStatus, Option<Vec<u8>>) =
133                    replace(&mut meta, (FetchStatus::Empty, None));
134                return old.1;
135            }
136        }
137        None
138    }
139
140    fn box_clone(&self) -> Box<FetchProcessReader> {
141        Box::new((*self).clone())
142    }
143}
144
145pub trait FetchEngine: Send + Sync {
146    fn fetch(&mut self, path: &str) -> Result<Box<FetchProcessReader>, FetchStatus>;
147
148    fn cancel(&mut self, reader: Box<FetchProcessReader>) {
149        #[allow(clippy::cast_ptr_alignment)]
150        let ptr = Box::into_raw(reader) as *mut FetchProcess;
151        unsafe {
152            (*ptr).cancel(FetchCancelReason::User);
153            Box::from_raw(ptr);
154        }
155    }
156}
157
158#[cfg(test)]
159mod tests {
160    use super::*;
161
162    #[test]
163    #[cfg(not(feature = "web"))]
164    fn test_general() {
165        let mut engine = engines::fs::FsFetchEngine::new(&".");
166        let reader = engine.fetch("Cargo.toml").unwrap();
167        let reader2 = reader.clone();
168        #[cfg(feature = "parallel")]
169        {
170            assert_eq!(reader.status(), FetchStatus::InProgress(0.0));
171            assert_eq!(reader2.status(), FetchStatus::InProgress(0.0));
172        }
173        loop {
174            match reader.status() {
175                FetchStatus::InProgress(_) => continue,
176                _ => break,
177            }
178        }
179        assert_eq!(reader.status(), FetchStatus::Done);
180        assert_eq!(reader2.status(), FetchStatus::Done);
181        assert!(!reader.read().unwrap().is_empty());
182        assert_eq!(reader.status(), FetchStatus::Empty);
183        assert_eq!(reader2.status(), FetchStatus::Empty);
184        drop(reader);
185        drop(reader2);
186    }
187}