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}