wvb 0.2.0-next.68e19d2

Offline-first web resources delivery system for webview mounted frameworks/platforms
Documentation
use crate::checksum::{CHECKSUM_LEN, make_checksum};
use crate::header::HeaderWriterOptions;
use crate::index::{Index, IndexEntry, IndexWriterOptions};
use crate::version::Version;
use crate::{Bundle, BundleDescriptor, Header, IndexWriter, Writer};
use http::HeaderMap;
use lz4_flex::compress_prepend_size;
use std::collections::HashMap;

#[derive(Debug, PartialEq, Clone)]
pub struct BundleEntry {
  compressed: Vec<u8>,
  content_type: String,
  content_length: u64,
  pub headers: Option<HeaderMap>,
}

impl BundleEntry {
  pub fn new(data: &[u8], content_type: impl Into<String>, headers: Option<HeaderMap>) -> Self {
    let compressed = compress_prepend_size(data);
    Self {
      compressed,
      content_type: content_type.into(),
      content_length: data.len() as u64,
      headers,
    }
  }

  pub fn data(&self) -> &[u8] {
    &self.compressed
  }

  pub fn is_empty(&self) -> bool {
    self.compressed.is_empty()
  }

  pub fn content_type(&self) -> &str {
    &self.content_type
  }

  pub fn content_length(&self) -> u64 {
    self.content_length
  }

  pub fn len(&self) -> usize {
    self.compressed.len()
  }
}

#[derive(Debug, Default, Clone, Copy, PartialEq)]
pub struct BundleBuilderOptions {
  pub(crate) header: HeaderWriterOptions,
  pub(crate) index: IndexWriterOptions,
  pub(crate) data_checksum_seed: u32,
}

impl BundleBuilderOptions {
  pub fn new() -> Self {
    Self::default()
  }

  pub fn header(&mut self, options: HeaderWriterOptions) -> &mut Self {
    self.header = options;
    self
  }

  pub fn index(&mut self, options: IndexWriterOptions) -> &mut Self {
    self.index = options;
    self
  }

  pub fn data_checksum_seed(&mut self, seed: u32) -> &mut Self {
    self.data_checksum_seed = seed;
    self
  }
}

#[derive(Debug, Default)]
pub struct BundleBuilder {
  entries: HashMap<String, BundleEntry>,
  version: Version,
  options: BundleBuilderOptions,
}

impl BundleBuilder {
  pub fn new() -> Self {
    Self::default()
  }

  pub fn new_with_capacity(capacity: usize) -> Self {
    Self {
      entries: HashMap::with_capacity(capacity),
      ..Self::default()
    }
  }

  pub fn new_with_options(options: BundleBuilderOptions) -> Self {
    Self {
      options,
      ..Self::default()
    }
  }

  pub fn version(&self) -> Version {
    self.version
  }

  pub fn set_version(&mut self, version: Version) -> &mut Self {
    self.version = version;
    self
  }

  pub fn options(&self) -> &BundleBuilderOptions {
    &self.options
  }

  pub fn set_options(&mut self, options: BundleBuilderOptions) -> &mut Self {
    self.options = options;
    self
  }

  pub fn entries(&self) -> &HashMap<String, BundleEntry> {
    &self.entries
  }

  pub fn insert_entry<S: Into<String>, E: Into<BundleEntry>>(
    &mut self,
    path: S,
    entry: E,
  ) -> Option<BundleEntry> {
    let p: String = path.into();
    let e: BundleEntry = entry.into();
    self.entries.insert(p, e)
  }

  pub fn get_entry(&self, path: &str) -> Option<&BundleEntry> {
    self.entries.get(path)
  }

  pub fn get_entry_mut(&mut self, path: &str) -> Option<&mut BundleEntry> {
    self.entries.get_mut(path)
  }

  pub fn remove_entry(&mut self, path: &str) -> Option<BundleEntry> {
    self.entries.remove(path)
  }

  pub fn contains_path(&self, path: &str) -> bool {
    self.entries.contains_key(path)
  }

  pub fn build(&self) -> crate::Result<Bundle> {
    let index = self.build_index();
    let header = self.build_header(&index)?;
    let manifest = BundleDescriptor { header, index };
    let data = self.build_data();
    Ok(Bundle {
      descriptor: manifest,
      data,
    })
  }

  pub(crate) fn build_header(&self, index: &Index) -> crate::Result<Header> {
    let index_bytes_size =
      IndexWriter::new_with_options(&mut vec![], self.options().index).write(index)?;
    let index_size = (index_bytes_size - CHECKSUM_LEN) as u32;
    let header = Header::new(self.version(), index_size);
    Ok(header)
  }

  pub(crate) fn build_index(&self) -> Index {
    let mut index = Index::new_with_capacity(self.entries().len());
    let mut offset = 0;
    for (path, entry) in self.entries() {
      let len = entry.len() as u64;
      let mut index_entry =
        IndexEntry::new(offset, len, entry.content_type(), entry.content_length);
      if let Some(headers) = entry.headers.as_ref() {
        index_entry.headers.clone_from(headers);
      }
      index.insert_entry(path, index_entry);
      offset += len;
      offset += CHECKSUM_LEN as u64;
    }
    index
  }

  pub(crate) fn build_data(&self) -> Vec<u8> {
    let mut data = vec![];
    for entry in self.entries().values() {
      let checksum = make_checksum(self.options.data_checksum_seed, entry.data());
      data.extend_from_slice(entry.data());
      data.extend_from_slice(&checksum.to_be_bytes());
    }
    data
  }
}