Skip to main content

phoxal_bundle/
asset.rs

1//! Participant-facing asset reads.
2
3use std::io::Read;
4
5use phoxal_model::AssetId;
6
7use crate::{ASSETS_DIR, BundleError, BundlePath, BundleRoot, open_bundle_file};
8
9/// Reads the files below `<bundle>/assets`.
10///
11/// There is no declared asset set to consult: an [`AssetId`] is already a
12/// validated relative forward-slash path with no `.` or `..` segment, and
13/// [`BundlePath`] validates the joined path again, so a read cannot name
14/// anything outside `assets/`. That pair of checks is the whole fence.
15#[derive(Clone, Debug)]
16pub struct ParticipantAssets {
17    root: BundleRoot,
18}
19
20impl ParticipantAssets {
21    pub(crate) const fn new(root: BundleRoot) -> Self {
22        Self { root }
23    }
24
25    pub(crate) fn relocate(&mut self, path: std::path::PathBuf) {
26        self.root.relocate(path);
27    }
28
29    /// Read one asset out of the bundle.
30    ///
31    /// # Errors
32    ///
33    /// Returns [`BundleError::MissingFile`] when the bundle carries no such
34    /// asset, and [`BundleError::ReadFile`] when it cannot be read.
35    pub fn read(&self, id: &AssetId) -> Result<Vec<u8>, BundleError> {
36        let path = Self::path(id)?;
37        let mut file = open_bundle_file(&self.root, &path)?;
38        let mut bytes = Vec::new();
39        file.read_to_end(&mut bytes)
40            .map_err(|source| BundleError::ReadFile {
41                path: path.filesystem_path(self.root.path()),
42                source,
43            })?;
44        Ok(bytes)
45    }
46
47    /// Where one logical asset sits in the bundle.
48    pub(crate) fn path(id: &AssetId) -> Result<BundlePath, BundleError> {
49        Ok(BundlePath::new(format!("{ASSETS_DIR}/{}", id.as_str()))?)
50    }
51}