mini_fs/
zip.rs

1use std::cell::RefCell;
2use std::fs;
3use std::io::{self, Cursor, ErrorKind, Read, Seek, SeekFrom};
4use std::path::Path;
5
6use zip_::ZipArchive;
7
8use crate::index::Index;
9use crate::store::Store;
10use crate::{Entries, Entry};
11
12/// Zip archive store.
13///
14/// # Remarks
15///
16/// When used with a `std::fs::File`, the file will remain open for the lifetime
17/// of the Zip.
18pub struct ZipFs<T: Read + Seek> {
19    inner: RefCell<T>,
20    index: Option<Index<()>>,
21}
22
23/// Entry in the Zip archive.
24pub struct ZipFsFile {
25    inner: Cursor<Box<[u8]>>,
26}
27
28impl Read for ZipFsFile {
29    #[inline]
30    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
31        self.inner.read(buf)
32    }
33}
34
35impl Seek for ZipFsFile {
36    #[inline]
37    fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
38        self.inner.seek(pos)
39    }
40}
41
42impl ZipFs<fs::File> {
43    pub fn open<P: AsRef<Path>>(path: P) -> io::Result<Self> {
44        let file = fs::OpenOptions::new()
45            .read(true)
46            .write(false)
47            .create(false)
48            .open(path)?;
49        Ok(Self::new(file))
50    }
51}
52
53impl<T: Read + Seek> ZipFs<T> {
54    pub fn new(inner: T) -> Self {
55        Self {
56            inner: RefCell::new(inner),
57            index: None,
58        }
59    }
60
61    /// Index the contents of the archive.
62    ///
63    /// Having an index allows you to list the contents of the archive using the
64    /// entries_path and entries methods.
65    pub fn index(mut self) -> io::Result<Self> {
66        let mut index = Index::new();
67        let mut file = self.inner.borrow_mut();
68        file.seek(SeekFrom::Start(0))?;
69        let mut archive = ZipArchive::new(&mut *file)?;
70        for i in 0..archive.len() {
71            let file = archive.by_index(i)?;
72            let path = file.sanitized_name();
73
74            index.insert(path, ());
75        }
76        self.index = Some(index);
77        drop(file);
78        Ok(self)
79    }
80}
81
82impl<T: Read + Seek> Store for ZipFs<T> {
83    type File = ZipFsFile;
84    fn open_path(&self, path: &Path) -> io::Result<Self::File> {
85        let mut file = self.inner.borrow_mut();
86        file.seek(SeekFrom::Start(0))?;
87
88        let mut archive = ZipArchive::new(&mut *file)?;
89        let name = path.to_str().ok_or(io::Error::new(
90            ErrorKind::Other,
91            "Utf8 path conversion error.",
92        ));
93        let mut file = archive.by_name(name?)?;
94
95        let mut v = Vec::new();
96        file.read_to_end(&mut v)?;
97        Ok(ZipFsFile {
98            inner: Cursor::new(v.into()),
99        })
100    }
101
102    fn entries_path(&self, path: &Path) -> io::Result<Entries> {
103        if let Some(ref idx) = self.index {
104            Ok(Entries::new(idx.entries(path).map(|ent| {
105                let name = ent.name.to_os_string();
106                let kind = ent.kind;
107                Ok(Entry { name, kind })
108            })))
109        } else {
110            panic!("You have to call the `Zip::index` method on this zip archive before you can list its entries.")
111        }
112    }
113}