use crate::DownloadSyntax;
use crate::{DownloadEvent, DownloadProgress};
use rsynko_download::*;
use std::cell::{Cell, RefCell};
use std::collections::BTreeMap;
use std::io::{Cursor, Read};
use std::path::{Path, PathBuf};
use thiserror::Error;
#[derive(Debug, Error, PartialEq, Eq)]
pub enum ReferenceFetchError {
#[error("unknown resource {0}")]
UnknownResource(String),
}
#[derive(Debug, Error, PartialEq, Eq)]
#[error("publication refused")]
pub struct ReferencePublishError;
#[derive(Debug, Default)]
pub struct ReferenceDownloadEnv {
resources: BTreeMap<String, Vec<u8>>,
refuse_publication: bool,
files: RefCell<BTreeMap<PathBuf, Vec<u8>>>,
abandoned: Cell<usize>,
events: RefCell<Vec<DownloadEvent>>,
progress: RefCell<Vec<DownloadProgress>>,
}
impl ReferenceDownloadEnv {
pub fn register_resource(&mut self, url: impl Into<String>, bytes: impl Into<Vec<u8>>) -> Option<Vec<u8>> {
self.resources.insert(url.into(), bytes.into())
}
pub fn refuse_publication(&mut self) {
self.refuse_publication = true;
}
#[must_use]
pub fn abandoned(&self) -> usize {
self.abandoned.get()
}
#[must_use]
pub fn file(&self, path: &Path) -> Option<Vec<u8>> {
self.files.borrow().get(path).cloned()
}
#[must_use]
pub fn events(&self) -> Vec<DownloadEvent> {
self.events.borrow().clone()
}
#[must_use]
pub fn progress(&self) -> Vec<DownloadProgress> {
self.progress.borrow().clone()
}
}
impl FetchStreamAlg for ReferenceDownloadEnv {
type Error = ReferenceFetchError;
type Stream = Cursor<Vec<u8>>;
fn open_fetch(&self, url: &str) -> Result<FetchStream<Self::Stream>, Self::Error> {
let bytes =
self.resources.get(url).cloned().ok_or_else(|| ReferenceFetchError::UnknownResource(url.to_owned()))?;
let total = u64::try_from(bytes.len()).ok();
Ok(FetchStream::new(Cursor::new(bytes), total))
}
fn read_fetch(&self, stream: &mut Self::Stream, buffer: &mut [u8]) -> Result<usize, Self::Error> {
Ok(stream.read(buffer).unwrap_or(0))
}
}
impl AtomicPublishAlg for ReferenceDownloadEnv {
type Error = ReferencePublishError;
type Publication = (PathBuf, Vec<u8>);
fn begin_publication(&self, destination: &Path) -> Result<Self::Publication, Self::Error> {
Ok((destination.to_owned(), Vec::default()))
}
fn write_publication(&self, publication: &mut Self::Publication, bytes: &[u8]) -> Result<(), Self::Error> {
if self.refuse_publication {
return Err(ReferencePublishError);
}
publication.1.extend_from_slice(bytes);
Ok(())
}
fn commit_publication(&self, publication: Self::Publication) -> Result<(), Self::Error> {
self.files.borrow_mut().insert(publication.0, publication.1);
Ok(())
}
fn abort_publication(&self, _: Self::Publication) {
self.abandoned.set(self.abandoned.get() + 1);
}
}
impl DownloadReportAlg for ReferenceDownloadEnv {
type Event = DownloadEvent;
fn report_download(&self, event: DownloadEvent) {
self.events.borrow_mut().push(event);
}
}
impl DownloadProgressAlg for ReferenceDownloadEnv {
type Progress = DownloadProgress;
fn report_progress(&self, progress: DownloadProgress) {
self.progress.borrow_mut().push(progress);
}
}
impl DownloadObservationAlg for ReferenceDownloadEnv {
type Event = DownloadEvent;
type Progress = DownloadProgress;
fn download_progress(&self, destination: &Path, downloaded: u64, total: Option<u64>) -> Self::Progress {
DownloadSyntax.download_progress(destination, downloaded, total)
}
fn download_succeeded(&self, destination: &Path, bytes: u64) -> Self::Event {
DownloadSyntax.download_succeeded(destination, bytes)
}
fn download_failed(&self, destination: &Path, message: String) -> Self::Event {
DownloadSyntax.download_failed(destination, message)
}
}