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(
37        &mut self,
38        url: impl Into<String>,
39        bytes: impl Into<Vec<u8>>,
40    ) -> Option<Vec<u8>> {
41        self.resources.insert(url.into(), bytes.into())
42    }
43
44    /// Refuses every subsequent publication, so failure laws can be exercised.
45    pub fn refuse_publication(&mut self) {
46        self.refuse_publication = true;
47    }
48
49    /// Counts publications abandoned without reaching their final path.
50    #[must_use]
51    pub fn abandoned(&self) -> usize {
52        self.abandoned.get()
53    }
54
55    /// Observes bytes published at one final path.
56    #[must_use]
57    pub fn file(&self, path: &Path) -> Option<Vec<u8>> {
58        self.files.borrow().get(path).cloned()
59    }
60
61    /// Observes terminal events in emission order.
62    #[must_use]
63    pub fn events(&self) -> Vec<DownloadEvent> {
64        self.events.borrow().clone()
65    }
66
67    /// Observes byte-progress reports in emission order.
68    #[must_use]
69    pub fn progress(&self) -> Vec<DownloadProgress> {
70        self.progress.borrow().clone()
71    }
72}
73
74impl FetchStreamAlg for ReferenceDownloadEnv {
75    type Error = ReferenceFetchError;
76    type Stream = Cursor<Vec<u8>>;
77
78    fn open_fetch(&self, url: &str) -> Result<FetchStream<Self::Stream>, Self::Error> {
79        let bytes = self
80            .resources
81            .get(url)
82            .cloned()
83            .ok_or_else(|| ReferenceFetchError::UnknownResource(url.to_owned()))?;
84        let total = u64::try_from(bytes.len()).ok();
85        Ok(FetchStream::new(Cursor::new(bytes), total))
86    }
87
88    fn read_fetch(
89        &self,
90        stream: &mut Self::Stream,
91        buffer: &mut [u8],
92    ) -> Result<usize, Self::Error> {
93        Ok(stream.read(buffer).unwrap_or(0))
94    }
95}
96
97impl AtomicPublishAlg for ReferenceDownloadEnv {
98    type Error = ReferencePublishError;
99    type Publication = (PathBuf, Vec<u8>);
100
101    fn begin_publication(&self, destination: &Path) -> Result<Self::Publication, Self::Error> {
102        Ok((destination.to_owned(), Vec::default()))
103    }
104
105    fn write_publication(
106        &self,
107        publication: &mut Self::Publication,
108        bytes: &[u8],
109    ) -> Result<(), Self::Error> {
110        if self.refuse_publication {
111            return Err(ReferencePublishError);
112        }
113        publication.1.extend_from_slice(bytes);
114        Ok(())
115    }
116
117    fn commit_publication(&self, publication: Self::Publication) -> Result<(), Self::Error> {
118        self.files.borrow_mut().insert(publication.0, publication.1);
119        Ok(())
120    }
121
122    fn abort_publication(&self, _: Self::Publication) {
123        self.abandoned.set(self.abandoned.get() + 1);
124    }
125}
126
127impl DownloadReportAlg for ReferenceDownloadEnv {
128    type Event = DownloadEvent;
129
130    fn report_download(&self, event: DownloadEvent) {
131        self.events.borrow_mut().push(event);
132    }
133}
134
135impl DownloadProgressAlg for ReferenceDownloadEnv {
136    type Progress = DownloadProgress;
137
138    fn report_progress(&self, progress: DownloadProgress) {
139        self.progress.borrow_mut().push(progress);
140    }
141}
142
143impl DownloadObservationAlg for ReferenceDownloadEnv {
144    type Event = DownloadEvent;
145    type Progress = DownloadProgress;
146
147    fn download_progress(
148        &self,
149        destination: &Path,
150        downloaded: u64,
151        total: Option<u64>,
152    ) -> Self::Progress {
153        DownloadSyntax.download_progress(destination, downloaded, total)
154    }
155
156    fn download_succeeded(&self, destination: &Path, bytes: u64) -> Self::Event {
157        DownloadSyntax.download_succeeded(destination, bytes)
158    }
159
160    fn download_failed(&self, destination: &Path, message: String) -> Self::Event {
161        DownloadSyntax.download_failed(destination, message)
162    }
163}