Skip to main content

tectonic/io/
format_cache.rs

1// Copyright 2018-2020 the Tectonic Project
2// Licensed under the MIT License.
3
4//! Code for locally caching compiled format files.
5
6use std::{
7    io::{BufReader, Write},
8    path::PathBuf,
9};
10use tectonic_errors::{anyhow::bail, Result};
11
12use super::{InputHandle, InputOrigin, IoProvider, OpenResult};
13use crate::{digest::DigestData, status::StatusBackend};
14
15/// A local cache for compiled format files.
16///
17/// The format cache takes care of saving compiled format files. It uses the
18/// same root cache directory as the `LocalCache` item, but is implemented
19/// separately so that there is a way to save the format files associated with
20/// backends that may not have their own LocalCache.
21pub struct FormatCache {
22    bundle_digest: DigestData,
23    formats_base: PathBuf,
24}
25
26impl FormatCache {
27    /// Create a new `FormatCache`.
28    ///
29    /// The `bundle_digest` should be the result of the `Bundle::get_digest()`
30    /// call for whichever bundle is active. The `formats_base` path should be
31    /// a local cache directory.
32    pub fn new(bundle_digest: DigestData, formats_base: PathBuf) -> FormatCache {
33        FormatCache {
34            bundle_digest,
35            formats_base,
36        }
37    }
38
39    /// Get an on-disk path name for a given format file. This function simply
40    /// produces a path that may or may not exist.
41    #[allow(clippy::manual_split_once)] // requires Rust 1.52 (note that we don't actually define our MSRV)
42    fn path_for_format(&mut self, name: &str) -> Result<PathBuf> {
43        // Remove all extensions from the format name. PathBuf.file_stem() doesn't
44        // do what we want since it only strips one extension, so here we go:
45
46        let stem = match name.split('.').next() {
47            Some(s) => s,
48            None => {
49                bail!("incomprehensible format file name \"{}\"", name);
50            }
51        };
52
53        let mut p = self.formats_base.clone();
54        p.push(format!(
55            "{}-{}-{}.fmt",
56            self.bundle_digest,
57            stem,
58            crate::FORMAT_SERIAL
59        ));
60        Ok(p)
61    }
62}
63
64impl IoProvider for FormatCache {
65    fn input_open_format(
66        &mut self,
67        name: &str,
68        _status: &mut dyn StatusBackend,
69    ) -> OpenResult<InputHandle> {
70        let path = match self.path_for_format(name) {
71            Ok(p) => p,
72            Err(e) => return OpenResult::Err(e),
73        };
74
75        let f = match super::try_open_file(path) {
76            OpenResult::Ok(f) => f,
77            OpenResult::NotAvailable => return OpenResult::NotAvailable,
78            OpenResult::Err(e) => return OpenResult::Err(e),
79        };
80
81        OpenResult::Ok(InputHandle::new_read_only(
82            name,
83            BufReader::new(f),
84            InputOrigin::Other,
85        ))
86    }
87
88    fn write_format(
89        &mut self,
90        name: &str,
91        data: &[u8],
92        _status: &mut dyn StatusBackend,
93    ) -> Result<()> {
94        let final_path = self.path_for_format(name)?;
95        let mut temp_dest = tempfile::Builder::new()
96            .prefix("format_")
97            .rand_bytes(6)
98            .tempfile_in(&self.formats_base)?;
99        temp_dest.write_all(data)?;
100        temp_dest.persist(final_path)?;
101        Ok(())
102    }
103}