Skip to main content

rsynko_memory/
download.rs

1use crate::DownloadSyntax;
2use crate::{DownloadEvent, DownloadProgress};
3use rsynko_download::*;
4use std::cell::{Cell, RefCell};
5use std::collections::BTreeMap;
6use std::io::{Cursor, Read};
7use std::path::{Path, PathBuf};
8use thiserror::Error;
9
10/// Denotes retrieval failure in the deterministic download interpreter.
11#[derive(Debug, Error, PartialEq, Eq)]
12pub enum ReferenceFetchError {
13    /// Denotes a resource absent from the reference environment.
14    #[error("unknown resource {0}")]
15    UnknownResource(String),
16}
17
18/// Denotes publication failure in the deterministic download interpreter.
19#[derive(Debug, Error, PartialEq, Eq)]
20#[error("publication refused")]
21pub struct ReferencePublishError;
22
23/// Interprets fetching, atomic publication, and reporting entirely in memory.
24#[derive(Debug, Default)]
25pub struct ReferenceDownloadEnv {
26    resources: BTreeMap<String, Vec<u8>>,
27    refuse_publication: bool,
28    files: RefCell<BTreeMap<PathBuf, Vec<u8>>>,
29    abandoned: Cell<usize>,
30    events: RefCell<Vec<DownloadEvent>>,
31    progress: RefCell<Vec<DownloadProgress>>,
32}
33
34impl ReferenceDownloadEnv {
35    /// Associates exact bytes with one resource URL.
36    pub fn register_resource(&mut self, url: impl Into<String>, bytes: impl Into<Vec<u8>>) -> Option<Vec<u8>> {
37        self.resources.insert(url.into(), bytes.into())
38    }
39
40    /// Refuses every subsequent publication, so failure laws can be exercised.
41    pub fn refuse_publication(&mut self) {
42        self.refuse_publication = true;
43    }
44
45    /// Counts publications abandoned without reaching their final path.
46    #[must_use]
47    pub fn abandoned(&self) -> usize {
48        self.abandoned.get()
49    }
50
51    /// Observes bytes published at one final path.
52    #[must_use]
53    pub fn file(&self, path: &Path) -> Option<Vec<u8>> {
54        self.files.borrow().get(path).cloned()
55    }
56
57    /// Observes terminal events in emission order.
58    #[must_use]
59    pub fn events(&self) -> Vec<DownloadEvent> {
60        self.events.borrow().clone()
61    }
62
63    /// Observes byte-progress reports in emission order.
64    #[must_use]
65    pub fn progress(&self) -> Vec<DownloadProgress> {
66        self.progress.borrow().clone()
67    }
68}
69
70impl FetchStreamAlg for ReferenceDownloadEnv {
71    type Error = ReferenceFetchError;
72    type Stream = Cursor<Vec<u8>>;
73
74    fn open_fetch(&self, url: &str) -> Result<FetchStream<Self::Stream>, Self::Error> {
75        let bytes =
76            self.resources.get(url).cloned().ok_or_else(|| ReferenceFetchError::UnknownResource(url.to_owned()))?;
77        let total = u64::try_from(bytes.len()).ok();
78        Ok(FetchStream::new(Cursor::new(bytes), total))
79    }
80
81    fn read_fetch(&self, stream: &mut Self::Stream, buffer: &mut [u8]) -> Result<usize, Self::Error> {
82        Ok(stream.read(buffer).unwrap_or(0))
83    }
84}
85
86impl AtomicPublishAlg for ReferenceDownloadEnv {
87    type Error = ReferencePublishError;
88    type Publication = (PathBuf, Vec<u8>);
89
90    fn begin_publication(&self, destination: &Path) -> Result<Self::Publication, Self::Error> {
91        Ok((destination.to_owned(), Vec::default()))
92    }
93
94    fn write_publication(&self, publication: &mut Self::Publication, bytes: &[u8]) -> Result<(), Self::Error> {
95        if self.refuse_publication {
96            return Err(ReferencePublishError);
97        }
98        publication.1.extend_from_slice(bytes);
99        Ok(())
100    }
101
102    fn commit_publication(&self, publication: Self::Publication) -> Result<(), Self::Error> {
103        self.files.borrow_mut().insert(publication.0, publication.1);
104        Ok(())
105    }
106
107    fn abort_publication(&self, _: Self::Publication) {
108        self.abandoned.set(self.abandoned.get() + 1);
109    }
110}
111
112impl DownloadReportAlg for ReferenceDownloadEnv {
113    type Event = DownloadEvent;
114
115    fn report_download(&self, event: DownloadEvent) {
116        self.events.borrow_mut().push(event);
117    }
118}
119
120impl DownloadProgressAlg for ReferenceDownloadEnv {
121    type Progress = DownloadProgress;
122
123    fn report_progress(&self, progress: DownloadProgress) {
124        self.progress.borrow_mut().push(progress);
125    }
126}
127
128impl DownloadObservationAlg for ReferenceDownloadEnv {
129    type Event = DownloadEvent;
130    type Progress = DownloadProgress;
131
132    fn download_progress(&self, destination: &Path, downloaded: u64, total: Option<u64>) -> Self::Progress {
133        DownloadSyntax.download_progress(destination, downloaded, total)
134    }
135
136    fn download_succeeded(&self, destination: &Path, bytes: u64) -> Self::Event {
137        DownloadSyntax.download_succeeded(destination, bytes)
138    }
139
140    fn download_failed(&self, destination: &Path, message: String) -> Self::Event {
141        DownloadSyntax.download_failed(destination, message)
142    }
143}