webview_bundle/
builder.rs

1use crate::checksum::{make_checksum, CHECKSUM_LEN};
2use crate::header::HeaderWriterOptions;
3use crate::index::{Index, IndexEntry, IndexWriterOptions};
4use crate::version::Version;
5use crate::{Bundle, BundleManifest, Header, IndexWriter, Writer};
6use http::HeaderMap;
7use lz4_flex::compress_prepend_size;
8use std::collections::HashMap;
9
10#[derive(Debug, PartialEq, Clone)]
11pub struct BundleEntry {
12  compressed: Vec<u8>,
13  pub headers: Option<HeaderMap>,
14}
15
16impl BundleEntry {
17  pub fn new(data: &[u8], headers: Option<HeaderMap>) -> Self {
18    let compressed = compress_prepend_size(data);
19    Self {
20      compressed,
21      headers,
22    }
23  }
24
25  pub fn data(&self) -> &[u8] {
26    &self.compressed
27  }
28
29  pub fn is_empty(&self) -> bool {
30    self.compressed.is_empty()
31  }
32
33  pub fn len(&self) -> usize {
34    self.compressed.len()
35  }
36}
37
38impl From<&[u8]> for BundleEntry {
39  fn from(data: &[u8]) -> Self {
40    Self::new(data, None)
41  }
42}
43
44impl From<Vec<u8>> for BundleEntry {
45  fn from(data: Vec<u8>) -> Self {
46    Self::new(&data, None)
47  }
48}
49
50impl From<(&[u8], Option<HeaderMap>)> for BundleEntry {
51  fn from((data, headers): (&[u8], Option<HeaderMap>)) -> Self {
52    Self::new(data, headers)
53  }
54}
55
56impl From<(Vec<u8>, Option<HeaderMap>)> for BundleEntry {
57  fn from((data, headers): (Vec<u8>, Option<HeaderMap>)) -> Self {
58    Self::new(&data, headers)
59  }
60}
61
62impl From<(&[u8], HeaderMap)> for BundleEntry {
63  fn from((data, headers): (&[u8], HeaderMap)) -> Self {
64    Self::new(data, Some(headers))
65  }
66}
67
68impl From<(Vec<u8>, HeaderMap)> for BundleEntry {
69  fn from((data, headers): (Vec<u8>, HeaderMap)) -> Self {
70    Self::new(&data, Some(headers))
71  }
72}
73
74#[derive(Debug, Default, Clone, Copy, PartialEq)]
75pub struct BundleBuilderOptions {
76  pub(crate) header: HeaderWriterOptions,
77  pub(crate) index: IndexWriterOptions,
78  pub(crate) data_checksum_seed: u32,
79}
80
81impl BundleBuilderOptions {
82  pub fn new() -> Self {
83    Self::default()
84  }
85
86  pub fn header(&mut self, options: HeaderWriterOptions) -> &mut Self {
87    self.header = options;
88    self
89  }
90
91  pub fn index(&mut self, options: IndexWriterOptions) -> &mut Self {
92    self.index = options;
93    self
94  }
95
96  pub fn data_checksum_seed(&mut self, seed: u32) -> &mut Self {
97    self.data_checksum_seed = seed;
98    self
99  }
100}
101
102#[derive(Debug, Default)]
103pub struct BundleBuilder {
104  entries: HashMap<String, BundleEntry>,
105  version: Version,
106  options: BundleBuilderOptions,
107}
108
109impl BundleBuilder {
110  pub fn new() -> Self {
111    Self::default()
112  }
113
114  pub fn new_with_capacity(capacity: usize) -> Self {
115    Self {
116      entries: HashMap::with_capacity(capacity),
117      ..Self::default()
118    }
119  }
120
121  pub fn new_with_options(options: BundleBuilderOptions) -> Self {
122    Self {
123      options,
124      ..Self::default()
125    }
126  }
127
128  pub fn version(&self) -> Version {
129    self.version
130  }
131
132  pub fn set_version(&mut self, version: Version) -> &mut Self {
133    self.version = version;
134    self
135  }
136
137  pub fn options(&self) -> &BundleBuilderOptions {
138    &self.options
139  }
140
141  pub fn set_options(&mut self, options: BundleBuilderOptions) -> &mut Self {
142    self.options = options;
143    self
144  }
145
146  pub fn entries(&self) -> &HashMap<String, BundleEntry> {
147    &self.entries
148  }
149
150  pub fn insert_entry<S: Into<String>, E: Into<BundleEntry>>(
151    &mut self,
152    path: S,
153    entry: E,
154  ) -> Option<BundleEntry> {
155    let p: String = path.into();
156    let e: BundleEntry = entry.into();
157    self.entries.insert(p, e)
158  }
159
160  pub fn get_entry(&self, path: &str) -> Option<&BundleEntry> {
161    self.entries.get(path)
162  }
163
164  pub fn get_entry_mut(&mut self, path: &str) -> Option<&mut BundleEntry> {
165    self.entries.get_mut(path)
166  }
167
168  pub fn remove_entry(&mut self, path: &str) -> Option<BundleEntry> {
169    self.entries.remove(path)
170  }
171
172  pub fn contains_path(&self, path: &str) -> bool {
173    self.entries.contains_key(path)
174  }
175
176  pub fn build(&self) -> crate::Result<Bundle> {
177    let index = self.build_index();
178    let header = self.build_header(&index)?;
179    let manifest = BundleManifest { header, index };
180    let data = self.build_data();
181    Ok(Bundle { manifest, data })
182  }
183
184  pub(crate) fn build_header(&self, index: &Index) -> crate::Result<Header> {
185    let index_bytes_size =
186      IndexWriter::new_with_options(&mut vec![], self.options().index).write(index)?;
187    let index_size = (index_bytes_size - CHECKSUM_LEN) as u32;
188    let header = Header::new(self.version(), index_size);
189    Ok(header)
190  }
191
192  pub(crate) fn build_index(&self) -> Index {
193    let mut index = Index::new_with_capacity(self.entries().len());
194    let mut offset = 0;
195    for (path, entry) in self.entries() {
196      let len = entry.len() as u64;
197      let mut index_entry = IndexEntry::new(offset, len);
198      if let Some(headers) = entry.headers.as_ref() {
199        index_entry.headers.clone_from(headers);
200      }
201      index.insert_entry(path, index_entry);
202      offset += len;
203      offset += CHECKSUM_LEN as u64;
204    }
205    index
206  }
207
208  pub(crate) fn build_data(&self) -> Vec<u8> {
209    let mut data = vec![];
210    for entry in self.entries().values() {
211      let checksum = make_checksum(self.options.data_checksum_seed, entry.data());
212      data.extend_from_slice(entry.data());
213      data.extend_from_slice(&checksum.to_be_bytes());
214    }
215    data
216  }
217}