Skip to main content

tectonic_bundles/
ttb_fs.rs

1// Copyright 2023-2024 the Tectonic Project
2// Licensed under the MIT License.
3
4//! Read ttb v1 bundles on the filesystem.
5//!
6//! The main type offered by this module is the [`Ttbv1NetBundle`] struct.
7
8use crate::{
9    ttb::{TTBFileIndex, TTBFileInfo, TTBv1Header},
10    Bundle, FileIndex, FileInfo,
11};
12use flate2::read::GzDecoder;
13use std::{
14    convert::TryFrom,
15    fs::File,
16    io::{Cursor, Read, Seek, SeekFrom},
17    path::Path,
18};
19use tectonic_errors::prelude::*;
20use tectonic_io_base::{digest::DigestData, InputHandle, InputOrigin, IoProvider, OpenResult};
21use tectonic_status_base::StatusBackend;
22
23/// Read a [`TTBFileInfo`] from this bundle.
24/// We assume that `fileinfo` points to a valid file in this bundle.
25fn read_fileinfo<'a>(fileinfo: &TTBFileInfo, reader: &'a mut File) -> Result<Box<dyn Read + 'a>> {
26    reader.seek(SeekFrom::Start(fileinfo.start))?;
27    Ok(Box::new(GzDecoder::new(
28        reader.take(fileinfo.gzip_len as u64),
29    )))
30}
31
32/// A bundle backed by a ZIP file.
33pub struct TTBFsBundle<T>
34where
35    for<'a> T: FileIndex<'a>,
36{
37    file: File,
38    index: T,
39}
40
41/// The internal file-information struct used by the [`TTBFsBundle`].
42impl TTBFsBundle<TTBFileIndex> {
43    /// Create a new ZIP bundle for a generic readable and seekable stream.
44    pub fn new(file: File) -> Result<Self> {
45        Ok(TTBFsBundle {
46            file,
47            index: TTBFileIndex::default(),
48        })
49    }
50
51    fn get_header(&mut self) -> Result<TTBv1Header> {
52        self.file.seek(SeekFrom::Start(0))?;
53        let mut header: [u8; 70] = [0u8; 70];
54        self.file.read_exact(&mut header)?;
55        self.file.seek(SeekFrom::Start(0))?;
56        let header = TTBv1Header::try_from(header)?;
57        Ok(header)
58    }
59
60    // Fill this bundle's search rules, fetching files from our backend.
61    fn fill_index(&mut self) -> Result<()> {
62        let header = self.get_header()?;
63        let info = TTBFileInfo {
64            start: header.index_start,
65            gzip_len: header.index_real_len,
66            real_len: header.index_gzip_len,
67            path: "/INDEX".to_owned(),
68            name: "INDEX".to_owned(),
69            hash: None,
70        };
71
72        let mut reader = read_fileinfo(&info, &mut self.file)?;
73        self.index.initialize(&mut reader)?;
74
75        Ok(())
76    }
77
78    /// Open a file on the filesystem as a zip bundle.
79    pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
80        Self::new(File::open(path)?)
81    }
82}
83
84impl IoProvider for TTBFsBundle<TTBFileIndex> {
85    fn input_open_name(
86        &mut self,
87        name: &str,
88        _status: &mut dyn StatusBackend,
89    ) -> OpenResult<InputHandle> {
90        // Fetch index if it is empty
91        if self.index.is_empty() {
92            if let Err(e) = self.fill_index() {
93                return OpenResult::Err(e);
94            }
95        }
96
97        let info = match self.index.search(name) {
98            None => return OpenResult::NotAvailable,
99            Some(s) => s,
100        };
101
102        let mut v: Vec<u8> = Vec::with_capacity(info.real_len as usize);
103
104        match read_fileinfo(&info, &mut self.file) {
105            Err(e) => return OpenResult::Err(e),
106            Ok(mut b) => {
107                if let Err(e) = b.read_to_end(&mut v) {
108                    return OpenResult::Err(e.into());
109                }
110            }
111        };
112
113        OpenResult::Ok(InputHandle::new_read_only(
114            name,
115            Cursor::new(v),
116            InputOrigin::Other,
117        ))
118    }
119}
120
121impl Bundle for TTBFsBundle<TTBFileIndex> {
122    fn all_files(&self) -> Vec<String> {
123        self.index.iter().map(|x| x.path().to_owned()).collect()
124    }
125
126    fn get_digest(&mut self) -> Result<DigestData> {
127        let header = self.get_header()?;
128        Ok(header.digest)
129    }
130}