Skip to main content

async_zip/base/read/
seek.rs

1// Copyright (c) 2022 Harry [Majored] [hello@majored.pw]
2// MIT License (https://github.com/Majored/rs-async-zip/blob/main/LICENSE)
3
4//! A ZIP reader which acts over a seekable source.
5//!
6//! ### Example
7//! ```no_run
8//! # use async_zip::base::read::seek::ZipFileReader;
9//! # use async_zip::error::Result;
10//! # use futures_lite::io::AsyncReadExt;
11//! # use tokio::fs::File;
12//! # use tokio_util::compat::TokioAsyncReadCompatExt;
13//! # use tokio::io::BufReader;
14//! #
15//! async fn run() -> Result<()> {
16//!     let mut data = BufReader::new(File::open("./foo.zip").await?);
17//!     let mut reader = ZipFileReader::new(data.compat()).await?;
18//!
19//!     let mut data = Vec::new();
20//!     let mut entry = reader.reader_without_entry(0).await?;
21//!     entry.read_to_end(&mut data).await?;
22//!
23//!     // Use data within current scope.
24//!
25//!     Ok(())
26//! }
27//! ```
28
29use crate::base::read::io::entry::ZipEntryReader;
30use crate::error::{Result, ZipError};
31use crate::file::ZipFile;
32use std::sync::Arc;
33
34#[cfg(feature = "tokio")]
35use crate::tokio::read::seek::ZipFileReader as TokioZipFileReader;
36
37use futures_lite::io::{AsyncBufRead, AsyncSeek};
38
39#[cfg(feature = "tokio")]
40use tokio_util::compat::{Compat, TokioAsyncReadCompatExt};
41
42use super::io::entry::{WithEntry, WithoutEntry};
43
44/// A ZIP reader which acts over a seekable source.
45#[derive(Clone)]
46pub struct ZipFileReader<R> {
47    reader: R,
48    file: Arc<ZipFile>,
49}
50
51impl<R> ZipFileReader<R>
52where
53    R: AsyncBufRead + AsyncSeek + Unpin,
54{
55    /// Constructs a new ZIP reader from a seekable source.
56    pub async fn new(mut reader: R) -> Result<ZipFileReader<R>> {
57        let file = crate::base::read::file(&mut reader).await?;
58        Ok(ZipFileReader::from_raw_parts(reader, file))
59    }
60
61    /// Constructs a ZIP reader from a seekable source and ZIP file information derived from that source.
62    ///
63    /// Providing a [`ZipFile`] that wasn't derived from that source may lead to inaccurate parsing.
64    pub fn from_raw_parts(reader: R, file: ZipFile) -> ZipFileReader<R> {
65        ZipFileReader { reader, file: Arc::new(file) }
66    }
67
68    /// Returns this ZIP file's information.
69    pub fn file(&self) -> &ZipFile {
70        &self.file
71    }
72
73    /// Returns a mutable reference to the inner seekable source.
74    ///
75    /// Swapping the source (eg. via std::mem operations) may lead to inaccurate parsing.
76    pub fn inner_mut(&mut self) -> &mut R {
77        &mut self.reader
78    }
79
80    /// Returns the inner seekable source by consuming self.
81    pub fn into_inner(self) -> R {
82        self.reader
83    }
84
85    /// Returns a new entry reader if the provided index is valid.
86    pub async fn reader_without_entry(&mut self, index: usize) -> Result<ZipEntryReader<'_, R, WithoutEntry>> {
87        let stored_entry = self.file.entries.get(index).ok_or(ZipError::EntryIndexOutOfBounds)?;
88        stored_entry.seek_to_data_offset(&mut self.reader).await?;
89
90        Ok(ZipEntryReader::new_with_borrow(
91            &mut self.reader,
92            stored_entry.entry.compression(),
93            stored_entry.entry.compressed_size(),
94        ))
95    }
96
97    /// Returns a new entry reader if the provided index is valid.
98    pub async fn reader_with_entry(&mut self, index: usize) -> Result<ZipEntryReader<'_, R, WithEntry<'_>>> {
99        let stored_entry = self.file.entries.get(index).ok_or(ZipError::EntryIndexOutOfBounds)?;
100
101        stored_entry.seek_to_data_offset(&mut self.reader).await?;
102
103        let reader = ZipEntryReader::new_with_borrow(
104            &mut self.reader,
105            stored_entry.entry.compression(),
106            stored_entry.entry.compressed_size(),
107        );
108
109        Ok(reader.into_with_entry(stored_entry))
110    }
111
112    /// Returns a new entry reader if the provided index is valid.
113    /// Consumes self
114    pub async fn into_entry<'a>(mut self, index: usize) -> Result<ZipEntryReader<'a, R, WithoutEntry>>
115    where
116        R: 'a,
117    {
118        let stored_entry = self.file.entries.get(index).ok_or(ZipError::EntryIndexOutOfBounds)?;
119
120        stored_entry.seek_to_data_offset(&mut self.reader).await?;
121
122        Ok(ZipEntryReader::new_with_owned(
123            self.reader,
124            stored_entry.entry.compression(),
125            stored_entry.entry.compressed_size(),
126        ))
127    }
128}
129
130#[cfg(test)]
131mod tests {
132    use std::sync::Arc;
133
134    use futures_lite::io::Cursor;
135
136    use super::ZipFileReader;
137    use crate::ZipFileBuilder;
138
139    #[test]
140    fn clone_shares_file_metadata() {
141        let reader = ZipFileReader::from_raw_parts(Cursor::new(Vec::new()), ZipFileBuilder::new().build());
142        let cloned_reader = reader.clone();
143
144        assert!(Arc::ptr_eq(&reader.file, &cloned_reader.file));
145    }
146}
147
148#[cfg(feature = "tokio")]
149impl<R> ZipFileReader<Compat<R>>
150where
151    R: tokio::io::AsyncBufRead + tokio::io::AsyncSeek + Unpin,
152{
153    /// Constructs a new tokio-specific ZIP reader from a seekable source.
154    pub async fn with_tokio(reader: R) -> Result<TokioZipFileReader<R>> {
155        let mut reader = reader.compat();
156        let file = crate::base::read::file(&mut reader).await?;
157        Ok(ZipFileReader::from_raw_parts(reader, file))
158    }
159}