async_zip/tokio/read/
fs.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 concurrent ZIP reader which acts over a file system path.
5//!
6//! Concurrency is achieved as a result of:
7//! - Wrapping the provided path within an [`Arc`] to allow shared ownership.
8//! - Constructing a new [`File`] from the path when reading.
9//!
10//! ### Usage
11//! Unlike the [`seek`] module, we no longer hold a mutable reference to any inner reader which in turn, allows the
12//! construction of concurrent [`ZipEntryReader`]s. Though, note that each individual [`ZipEntryReader`] cannot be sent
13//! between thread boundaries due to the masked lifetime requirement. Therefore, the overarching [`ZipFileReader`]
14//! should be cloned and moved into those contexts when needed.
15//!
16//! ### Concurrent Example
17//! ```no_run
18//! # use async_zip::tokio::read::fs::ZipFileReader;
19//! # use async_zip::error::Result;
20//! # use futures_lite::io::AsyncReadExt;
21//! #
22//! async fn run() -> Result<()> {
23//!     let reader = ZipFileReader::new("./foo.zip").await?;
24//!     let result = tokio::join!(read(&reader, 0), read(&reader, 1));
25//!
26//!     let data_0 = result.0?;
27//!     let data_1 = result.1?;
28//!
29//!     // Use data within current scope.
30//!
31//!     Ok(())
32//! }
33//!
34//! async fn read(reader: &ZipFileReader, index: usize) -> Result<Vec<u8>> {
35//!     let mut entry = reader.reader_without_entry(index).await?;
36//!     let mut data = Vec::new();
37//!     entry.read_to_end(&mut data).await?;
38//!     Ok(data)
39//! }
40//! ```
41//!
42//! ### Parallel Example
43//! ```no_run
44//! # use async_zip::tokio::read::fs::ZipFileReader;
45//! # use async_zip::error::Result;
46//! # use futures_lite::io::AsyncReadExt;
47//! #
48//! async fn run() -> Result<()> {
49//!     let reader = ZipFileReader::new("./foo.zip").await?;
50//!     
51//!     let handle_0 = tokio::spawn(read(reader.clone(), 0));
52//!     let handle_1 = tokio::spawn(read(reader.clone(), 1));
53//!
54//!     let data_0 = handle_0.await.expect("thread panicked")?;
55//!     let data_1 = handle_1.await.expect("thread panicked")?;
56//!
57//!     // Use data within current scope.
58//!
59//!     Ok(())
60//! }
61//!
62//! async fn read(reader: ZipFileReader, index: usize) -> Result<Vec<u8>> {
63//!     let mut entry = reader.reader_without_entry(index).await?;
64//!     let mut data = Vec::new();
65//!     entry.read_to_end(&mut data).await?;
66//!     Ok(data)
67//! }
68//! ```
69
70#[cfg(doc)]
71use crate::base::read::seek;
72
73use crate::base::read::io::entry::{WithEntry, WithoutEntry, ZipEntryReader};
74use crate::error::{Result, ZipError};
75use crate::file::ZipFile;
76
77use std::path::{Path, PathBuf};
78use std::sync::Arc;
79
80use tokio::fs::File;
81use tokio::io::BufReader;
82use tokio_util::compat::{Compat, TokioAsyncReadCompatExt};
83
84struct Inner {
85    path: PathBuf,
86    file: ZipFile,
87}
88
89/// A concurrent ZIP reader which acts over a file system path.
90#[derive(Clone)]
91pub struct ZipFileReader {
92    inner: Arc<Inner>,
93}
94
95impl ZipFileReader {
96    /// Constructs a new ZIP reader from a file system path.
97    pub async fn new<P>(path: P) -> Result<ZipFileReader>
98    where
99        P: AsRef<Path>,
100    {
101        let file = crate::base::read::file(File::open(&path).await?.compat()).await?;
102        Ok(ZipFileReader::from_raw_parts(path, file))
103    }
104
105    /// Constructs a ZIP reader from a file system path and ZIP file information derived from that path.
106    ///
107    /// Providing a [`ZipFile`] that wasn't derived from that path may lead to inaccurate parsing.
108    pub fn from_raw_parts<P>(path: P, file: ZipFile) -> ZipFileReader
109    where
110        P: AsRef<Path>,
111    {
112        ZipFileReader { inner: Arc::new(Inner { path: path.as_ref().to_owned(), file }) }
113    }
114
115    /// Returns this ZIP file's information.
116    pub fn file(&self) -> &ZipFile {
117        &self.inner.file
118    }
119
120    /// Returns the file system path provided to the reader during construction.
121    pub fn path(&self) -> &Path {
122        &self.inner.path
123    }
124
125    /// Returns a new entry reader if the provided index is valid.
126    pub async fn reader_without_entry(
127        &self,
128        index: usize,
129    ) -> Result<ZipEntryReader<'static, Compat<BufReader<File>>, WithoutEntry>> {
130        let stored_entry = self.inner.file.entries.get(index).ok_or(ZipError::EntryIndexOutOfBounds)?;
131        let mut fs_file = BufReader::new(File::open(&self.inner.path).await?).compat();
132
133        stored_entry.seek_to_data_offset(&mut fs_file).await?;
134
135        Ok(ZipEntryReader::new_with_owned(
136            fs_file,
137            stored_entry.entry.compression(),
138            stored_entry.entry.compressed_size(),
139        ))
140    }
141
142    /// Returns a new entry reader if the provided index is valid.
143    pub async fn reader_with_entry(
144        &self,
145        index: usize,
146    ) -> Result<ZipEntryReader<'_, Compat<BufReader<File>>, WithEntry<'_>>> {
147        let stored_entry = self.inner.file.entries.get(index).ok_or(ZipError::EntryIndexOutOfBounds)?;
148        let mut fs_file = BufReader::new(File::open(&self.inner.path).await?).compat();
149
150        stored_entry.seek_to_data_offset(&mut fs_file).await?;
151
152        let reader = ZipEntryReader::new_with_owned(
153            fs_file,
154            stored_entry.entry.compression(),
155            stored_entry.entry.compressed_size(),
156        );
157
158        Ok(reader.into_with_entry(stored_entry))
159    }
160}