aware-tectonic 0.16.10

A modernized, complete, embeddable TeX/LaTeX engine. Tectonic is forked from the XeTeX extension to the classic "Web2C" implementation of TeX and uses the TeXLive distribution of support files. This is the Aware Software fork of tectonic 0.16.9: identical to upstream except that its bundle crate (aware-tectonic-bundles) does not contact the network when the bundle cache is warm.
Documentation
// Copyright 2018-2020 the Tectonic Project
// Licensed under the MIT License.

//! Code for locally caching compiled format files.

use std::{
    io::{BufReader, Write},
    path::PathBuf,
};
use tectonic_errors::{anyhow::bail, Result};

use super::{InputHandle, InputOrigin, IoProvider, OpenResult};
use crate::{digest::DigestData, status::StatusBackend};

/// A local cache for compiled format files.
///
/// The format cache takes care of saving compiled format files. It uses the
/// same root cache directory as the `LocalCache` item, but is implemented
/// separately so that there is a way to save the format files associated with
/// backends that may not have their own LocalCache.
pub struct FormatCache {
    bundle_digest: DigestData,
    formats_base: PathBuf,
}

impl FormatCache {
    /// Create a new `FormatCache`.
    ///
    /// The `bundle_digest` should be the result of the `Bundle::get_digest()`
    /// call for whichever bundle is active. The `formats_base` path should be
    /// a local cache directory.
    pub fn new(bundle_digest: DigestData, formats_base: PathBuf) -> FormatCache {
        FormatCache {
            bundle_digest,
            formats_base,
        }
    }

    /// Get an on-disk path name for a given format file. This function simply
    /// produces a path that may or may not exist.
    #[allow(clippy::manual_split_once)] // requires Rust 1.52 (note that we don't actually define our MSRV)
    fn path_for_format(&mut self, name: &str) -> Result<PathBuf> {
        // Remove all extensions from the format name. PathBuf.file_stem() doesn't
        // do what we want since it only strips one extension, so here we go:

        let stem = match name.split('.').next() {
            Some(s) => s,
            None => {
                bail!("incomprehensible format file name \"{}\"", name);
            }
        };

        let mut p = self.formats_base.clone();
        p.push(format!(
            "{}-{}-{}.fmt",
            self.bundle_digest,
            stem,
            crate::FORMAT_SERIAL
        ));
        Ok(p)
    }
}

impl IoProvider for FormatCache {
    fn input_open_format(
        &mut self,
        name: &str,
        _status: &mut dyn StatusBackend,
    ) -> OpenResult<InputHandle> {
        let path = match self.path_for_format(name) {
            Ok(p) => p,
            Err(e) => return OpenResult::Err(e),
        };

        let f = match super::try_open_file(path) {
            OpenResult::Ok(f) => f,
            OpenResult::NotAvailable => return OpenResult::NotAvailable,
            OpenResult::Err(e) => return OpenResult::Err(e),
        };

        OpenResult::Ok(InputHandle::new_read_only(
            name,
            BufReader::new(f),
            InputOrigin::Other,
        ))
    }

    fn write_format(
        &mut self,
        name: &str,
        data: &[u8],
        _status: &mut dyn StatusBackend,
    ) -> Result<()> {
        let final_path = self.path_for_format(name)?;
        let mut temp_dest = tempfile::Builder::new()
            .prefix("format_")
            .rand_bytes(6)
            .tempfile_in(&self.formats_base)?;
        temp_dest.write_all(data)?;
        temp_dest.persist(final_path)?;
        Ok(())
    }
}