mini_fs/
tar.rs

1use std::cell::{Cell, RefCell};
2use std::fs;
3use std::io::{self, Cursor, ErrorKind, Read, Seek, SeekFrom};
4use std::path::Path;
5
6use flate2::read::GzDecoder;
7use tar_::Archive;
8
9use crate::index::Index;
10use crate::store::Store;
11use crate::{Entries, Entry};
12
13/// Tar archive.
14///
15/// # Remarks
16///
17/// When used with a `std::fs::File`, the file will remain open for the lifetime
18/// of the Tar.
19pub struct TarFs<F: Read + Seek> {
20    gzip: Cell<bool>,
21    inner: RefCell<F>,
22    index: Option<Index<SeekFrom>>,
23}
24
25/// Entry in the Tar archive.
26pub struct TarFsFile {
27    inner: Cursor<Box<[u8]>>,
28}
29
30impl Read for TarFsFile {
31    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
32        self.inner.read(buf)
33    }
34}
35
36impl Seek for TarFsFile {
37    #[inline]
38    fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
39        self.inner.seek(pos)
40    }
41}
42
43impl<T: Read + Seek> Store for TarFs<T> {
44    type File = TarFsFile;
45
46    fn open_path(&self, path: &Path) -> io::Result<Self::File> {
47        if self.gzip.get() {
48            let mut file = self.inner.borrow_mut();
49            file.seek(SeekFrom::Start(0))?;
50            self.open_read(path, GzDecoder::new(&mut *file))
51        } else {
52            let mut file = self.inner.borrow_mut();
53            file.seek(SeekFrom::Start(0))?;
54            match self.open_read(path, &mut *file) {
55                Ok(entry) => Ok(entry),
56                Err(ref e) if e.kind() == ErrorKind::NotFound => {
57                    Err(io::Error::from(ErrorKind::NotFound))
58                }
59                Err(_) => {
60                    self.gzip.set(true);
61                    drop(file);
62                    self.open_path(path)
63                }
64            }
65        }
66    }
67
68    fn entries_path(&self, path: &Path) -> io::Result<Entries> {
69        if let Some(ref idx) = self.index {
70            Ok(Entries::new(idx.entries(path).map(|ent| {
71                let name = ent.name.to_os_string();
72                let kind = ent.kind;
73                Ok(Entry { name, kind })
74            })))
75        } else {
76            panic!("You have to call the `Zip::index` method on this zip archive before you can list its entries.")
77        }
78    }
79}
80
81impl TarFs<fs::File> {
82    /// Open a file from the native filesystem.
83    pub fn open<P: AsRef<Path>>(path: P) -> io::Result<Self> {
84        let file = fs::OpenOptions::new()
85            .read(true)
86            .write(false)
87            .create(false)
88            .open(path)?;
89        Ok(Self::new(file))
90    }
91}
92
93impl<T: Read + Seek> TarFs<T> {
94    pub fn new(inner: T) -> Self {
95        Self {
96            inner: RefCell::new(inner),
97            gzip: Cell::new(false),
98            index: None,
99        }
100    }
101
102    fn open_read<R: Read>(&self, path: &Path, read: R) -> io::Result<TarFsFile> {
103        let mut archive = Archive::new(read);
104        for entry in archive.entries()? {
105            let mut entry = entry?;
106            if path == entry.path()? {
107                let mut data = Vec::new();
108                entry.read_to_end(&mut data)?;
109                return Ok(TarFsFile {
110                    inner: Cursor::new(data.into()),
111                });
112            }
113        }
114        Err(io::Error::from(ErrorKind::NotFound))
115    }
116
117    /// Index the contents of the archive.
118    ///
119    /// Having an index allows you to list the contents of the archive using the
120    /// entries_path and entries methods.
121    pub fn index(self) -> io::Result<Self> {
122        unimplemented!("TarFs indexing hasn't been implemented yet.");
123    }
124}