tectonic_bundles/
ttb_net.rs1use crate::{
12 ttb::{TTBFileIndex, TTBFileInfo, TTBv1Header},
13 Bundle, CachableBundle, FileIndex, FileInfo, NET_RETRY_ATTEMPTS, NET_RETRY_SLEEP_MS,
14};
15use flate2::read::GzDecoder;
16use std::{
17 convert::TryFrom,
18 io::{Cursor, Read},
19 thread,
20 time::Duration,
21};
22use tectonic_errors::prelude::*;
23use tectonic_geturl::{DefaultBackend, DefaultRangeReader, GetUrlBackend, RangeReader};
24use tectonic_io_base::{InputHandle, InputOrigin, IoProvider, OpenResult};
25use tectonic_status_base::{tt_note, tt_warning, StatusBackend};
26
27fn read_fileinfo(fileinfo: &TTBFileInfo, reader: &mut DefaultRangeReader) -> Result<Box<dyn Read>> {
30 let stream = reader.read_range(fileinfo.start, fileinfo.gzip_len as usize)?;
32 Ok(Box::new(GzDecoder::new(stream)))
33}
34
35pub struct TTBNetBundle<T>
41where
42 for<'a> T: FileIndex<'a>,
43{
44 url: String,
45 index: T,
46
47 reader: Option<DefaultRangeReader>,
50}
51
52impl TTBNetBundle<TTBFileIndex> {
54 pub fn new(url: String) -> Result<Self> {
58 Ok(TTBNetBundle {
59 reader: None,
60 index: TTBFileIndex::default(),
61 url,
62 })
63 }
64
65 fn connect_reader(&mut self) -> Result<()> {
66 if self.reader.is_some() {
67 return Ok(());
68 }
69 let geturl_backend = DefaultBackend::default();
70 self.reader = Some(geturl_backend.open_range_reader(&self.url));
71 Ok(())
72 }
73
74 fn get_header(&mut self) -> Result<TTBv1Header> {
75 self.connect_reader()?;
76 let mut header: [u8; 70] = [0u8; 70];
77 self.reader
78 .as_mut()
79 .unwrap()
80 .read_range(0, 70)?
81 .read_exact(&mut header)?;
82 let header = TTBv1Header::try_from(header)?;
83 Ok(header)
84 }
85
86 fn ensure_index(&mut self) -> Result<()> {
88 if self.index.is_initialized() {
89 return Ok(());
90 }
91
92 let mut reader = self.get_index_reader()?;
93 self.index.initialize(&mut reader)?;
94 Ok(())
95 }
96}
97
98impl IoProvider for TTBNetBundle<TTBFileIndex> {
99 fn input_open_name(
100 &mut self,
101 name: &str,
102 status: &mut dyn StatusBackend,
103 ) -> OpenResult<InputHandle> {
104 if let Err(e) = self.ensure_index() {
105 return OpenResult::Err(e);
106 };
107
108 let info = match self.search(name) {
109 None => return OpenResult::NotAvailable,
110 Some(s) => s,
111 };
112
113 self.open_fileinfo(&info, status)
116 }
117}
118
119impl Bundle for TTBNetBundle<TTBFileIndex> {
120 fn all_files(&self) -> Vec<String> {
121 self.index.iter().map(|x| x.path().to_owned()).collect()
122 }
123
124 fn get_digest(&mut self) -> Result<tectonic_io_base::digest::DigestData> {
125 let header = self.get_header()?;
126 Ok(header.digest)
127 }
128}
129
130impl CachableBundle<'_, TTBFileIndex> for TTBNetBundle<TTBFileIndex> {
131 fn get_location(&mut self) -> String {
132 self.url.clone()
133 }
134
135 fn initialize_index(&mut self, source: &mut dyn Read) -> Result<()> {
136 self.index.initialize(source)?;
137 Ok(())
138 }
139
140 fn index(&mut self) -> &mut TTBFileIndex {
141 &mut self.index
142 }
143
144 fn search(&mut self, name: &str) -> Option<TTBFileInfo> {
145 self.index.search(name)
146 }
147
148 fn get_index_reader(&mut self) -> Result<Box<dyn Read>> {
149 self.connect_reader()?;
150 let header = self.get_header()?;
151
152 read_fileinfo(
153 &TTBFileInfo {
154 start: header.index_start,
155 gzip_len: header.index_gzip_len,
156 real_len: header.index_real_len,
157 path: "".to_owned(),
158 name: "".to_owned(),
159 hash: None,
160 },
161 self.reader.as_mut().unwrap(),
162 )
163 }
164
165 fn open_fileinfo(
166 &mut self,
167 info: &TTBFileInfo,
168 status: &mut dyn StatusBackend,
169 ) -> OpenResult<InputHandle> {
170 let mut v: Vec<u8> = Vec::with_capacity(info.real_len as usize);
171 tt_note!(status, "downloading {}", info.name);
172
173 if info.gzip_len == 0 {
176 return OpenResult::Ok(InputHandle::new_read_only(
177 info.name.to_owned(),
178 Cursor::new(v),
179 InputOrigin::Other,
180 ));
181 }
182
183 for i in 0..NET_RETRY_ATTEMPTS {
185 let mut reader = match read_fileinfo(info, self.reader.as_mut().unwrap()) {
186 Ok(r) => r,
187 Err(e) => {
188 tt_warning!(status,
189 "failure fetching \"{}\" from network ({}/{NET_RETRY_ATTEMPTS})",
190 info.name, i+1; e
191 );
192 thread::sleep(Duration::from_millis(NET_RETRY_SLEEP_MS));
193 continue;
194 }
195 };
196
197 match reader.read_to_end(&mut v) {
198 Ok(_) => {}
199 Err(e) => {
200 tt_warning!(status,
201 "failure downloading \"{}\" from network ({}/{NET_RETRY_ATTEMPTS})",
202 info.name, i+1; e.into()
203 );
204 thread::sleep(Duration::from_millis(NET_RETRY_SLEEP_MS));
205 continue;
206 }
207 };
208
209 return OpenResult::Ok(InputHandle::new_read_only(
210 info.name.to_owned(),
211 Cursor::new(v),
212 InputOrigin::Other,
213 ));
214 }
215
216 OpenResult::Err(anyhow!(
217 "failed to download \"{}\"; please check your network connection.",
218 info.name
219 ))
220 }
221}