support_bundle_viewer/
bundle_accessor.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5//! APIs to help access bundles
6
7use crate::index::SupportBundleIndex;
8use anyhow::Result;
9use async_trait::async_trait;
10use camino::Utf8Path;
11use std::io;
12use std::pin::Pin;
13use std::task::Context;
14use std::task::Poll;
15use tokio::io::AsyncRead;
16use tokio::io::ReadBuf;
17
18/// An I/O source which can read to a buffer
19///
20/// This describes access to individual files within the bundle.
21pub trait FileAccessor: AsyncRead + Unpin + Send {}
22impl<T: AsyncRead + Unpin + Send + ?Sized> FileAccessor for T {}
23
24pub type BoxedFileAccessor<'a> = Box<dyn FileAccessor + 'a>;
25
26/// Describes how the support bundle's data and metadata are accessed.
27#[async_trait]
28pub trait SupportBundleAccessor: Send {
29    /// Access the index of a support bundle
30    async fn get_index(&self) -> Result<SupportBundleIndex>;
31
32    /// Access a file within the support bundle
33    async fn get_file<'a>(&mut self, path: &Utf8Path) -> Result<BoxedFileAccessor<'a>>
34    where
35        Self: 'a;
36}
37
38pub struct LocalFileAccess {
39    archive: zip::read::ZipArchive<std::fs::File>,
40}
41
42impl LocalFileAccess {
43    pub fn new(path: &Utf8Path) -> Result<Self> {
44        let file = std::fs::File::open(path)?;
45        Ok(Self {
46            archive: zip::read::ZipArchive::new(file)?,
47        })
48    }
49}
50
51// Access for: Local zip files
52#[async_trait]
53impl SupportBundleAccessor for LocalFileAccess {
54    async fn get_index(&self) -> Result<SupportBundleIndex> {
55        let names: Vec<&str> = self.archive.file_names().collect();
56        let all_names = names.join("\n");
57        Ok(SupportBundleIndex::new(&all_names))
58    }
59
60    async fn get_file<'a>(&mut self, path: &Utf8Path) -> Result<BoxedFileAccessor<'a>> {
61        let mut file = self.archive.by_name(path.as_str())?;
62        let mut buf = Vec::new();
63        std::io::copy(&mut file, &mut buf)?;
64
65        Ok(Box::new(AsyncZipFile { buf, copied: 0 }))
66    }
67}
68
69// We're currently buffering the entire file into memory, mostly because dealing with the lifetime
70// of ZipArchive and ZipFile objects is so difficult.
71pub struct AsyncZipFile {
72    buf: Vec<u8>,
73    copied: usize,
74}
75
76impl AsyncRead for AsyncZipFile {
77    fn poll_read(
78        mut self: Pin<&mut Self>,
79        _cx: &mut Context<'_>,
80        buf: &mut ReadBuf<'_>,
81    ) -> Poll<io::Result<()>> {
82        let to_copy = std::cmp::min(self.buf.len() - self.copied, buf.remaining());
83        if to_copy == 0 {
84            return Poll::Ready(Ok(()));
85        }
86        let src = &self.buf[self.copied..];
87        buf.put_slice(&src[..to_copy]);
88        self.copied += to_copy;
89        Poll::Ready(Ok(()))
90    }
91}