unrar_async/
open_archive.rs

1use std::ffi::CStr;
2use std::ffi::CString;
3use std::os::unix::ffi::OsStrExt;
4use std::path::Path;
5use std::path::PathBuf;
6use std::pin::Pin;
7use std::ptr;
8use std::sync::Arc;
9
10use futures::Future;
11use futures::stream::Stream;
12use futures::stream::StreamExt;
13use futures::task::Context;
14use futures::task::Poll;
15use tracing::instrument;
16
17use crate::error::Code;
18use crate::error::RarError;
19use crate::error::When;
20use crate::error::Error;
21//use crate::flags::ArchiveFlags;
22use crate::flags::OpenMode;
23use crate::flags::Operation;
24//use crate::flags::VolumeInfo;
25
26mod data;
27pub use data::Entry;
28use data::Handle;
29use data::HeaderData;
30use data::OpenArchiveData;
31
32#[cfg(feature = "async-std")]
33#[inline]
34async fn spawn_blocking<T: Send + 'static>(func: impl FnOnce() -> T + Send + 'static) -> Result<T, Error> {
35	Ok(async_std::task::spawn_blocking(func).await)
36}
37
38#[cfg(feature = "tokio")]
39#[inline]
40async fn spawn_blocking<T: Send + 'static>(func: impl FnOnce() -> T + Send + 'static) -> Result<T, Error> {
41	Ok(tokio::task::spawn_blocking(func).await?)
42}
43
44#[derive(Clone, Copy)]
45struct UserData(*mut [u8; 256]);
46unsafe impl Send for UserData {}
47unsafe impl Sync for UserData {}
48
49impl Default for UserData {
50	#[inline]
51	fn default() -> Self {
52		let allocation = Box::new([0u8; 256]);
53		Self(Box::into_raw(allocation))
54	}
55}
56
57/// Represents an archive that is open for operation
58pub struct OpenArchive {
59	path: PathBuf,
60	handle: Arc<Handle>,
61	operation: Operation,
62	destination: Option<CString>,
63	damaged: bool,
64	//flags: ArchiveFlags,
65	#[allow(clippy::type_complexity)]
66	current_future: Option<Pin<Box<dyn Future<Output = Result<Entry, Error>> + Send>>>,
67	userdata: UserData
68}
69
70impl OpenArchive {
71	#[instrument(err, level = "info", skip(password))]
72	pub(crate) async fn open(filename: &Path, mode: OpenMode, password: Option<CString>, destination: Option<&Path>, operation: Operation) -> Result<Self, Error> {
73		let destination = destination.map(path_to_cstring).map_or(Ok(None), |r| r.map(Some)).map_err(Error::from)?;
74		//let filename = WideCStr::from_os_str(filename).unwrap(); // Already checked by Archive::new()
75		let data = OpenArchiveData::new(path_to_cstring(filename)?, mode as u32);
76		let (handle, data) = spawn_blocking(move || {
77			let p = unsafe { unrar_sys::RAROpenArchive(&mut data.as_ffi() as *mut _) } as *mut unrar_sys::Handle;
78			match p.is_null() {
79				false => Ok((Arc::new(Handle::from_ffi(p)), data)),
80				true => Err(Error::NulHandle)
81			}
82		}).await??;
83		let result = Code::try_from(data.open_result).or(Err(RarError::InvalidCode(data.open_result)))?;
84
85		if let Some(pw) = password {
86			unsafe { unrar_sys::RARSetPassword(handle.as_ffi(), pw.as_ptr() as *const _) }
87		}
88
89		match result {
90			Code::Success => Ok(Self::new(filename.into(), handle, operation, destination)),
91			e => Err(Error::Rar(RarError::from((e, When::Open))))
92		}
93	}
94
95	fn new(path: PathBuf, handle: Arc<Handle>, operation: Operation, destination: Option<CString>) -> Self {
96		let mut this = Self{
97			path,
98			handle,
99			operation,
100			destination,
101			damaged: false,
102			current_future: None,
103			userdata: UserData::default()
104		};
105		this.queue_next_future();
106		this
107	}
108
109	/// Process the archive in full; collect and return the results
110	#[instrument(err, level = "info", skip(self), fields(archive.path = %self.path.display(), archive.operation = ?self.operation, archive.destination = ?self.destination))]
111	pub async fn process(&mut self) -> Result<Vec<Entry>, Error> {
112		let mut results = Vec::new();
113		while let Some(item) = self.next().await {
114			results.push(item?);
115		}
116		Ok(results)
117	}
118
119	extern "C" fn callback(msg: unrar_sys::UINT, user_data: unrar_sys::LPARAM, p1: unrar_sys::LPARAM, p2: unrar_sys::LPARAM) -> std::os::raw::c_int {
120		match msg {
121			unrar_sys::UCM_CHANGEVOLUME => {
122				let ptr = p1 as *const _;
123				let next = std::str::from_utf8(unsafe { CStr::from_ptr(ptr) }.to_bytes()).unwrap();
124				let our_option = unsafe { &mut *(user_data as *mut Option<String>) };
125				*our_option = Some(String::from(next));
126				match p2 {
127					// Next volume not found; -1 means stop
128					unrar_sys::RAR_VOL_ASK => -1,
129					// Next volume found; 1 means continue
130					_ => 1
131				}
132			},
133			_ => 0
134		}
135	}
136
137	#[inline]
138	fn queue_next_future(&mut self) {
139		self.current_future = Some(Box::pin(next_entry(self.handle.clone(), self.operation, self.destination.clone(), self.userdata)));
140	}
141
142	/*
143	#[inline]
144	pub fn is_locked(&self) -> bool {
145		self.flags.contains(ArchiveFlags::LOCK)
146	}
147
148	#[inline]
149	pub fn has_encrypted_headers(&self) -> bool {
150		self.flags.contains(ArchiveFlags::ENC_HEADERS)
151	}
152
153	#[inline]
154	pub fn has_recovery_record(&self) -> bool {
155		self.flags.contains(ArchiveFlags::RECOVERY)
156	}
157
158	#[inline]
159	pub fn has_comment(&self) -> bool {
160		self.flags.contains(ArchiveFlags::COMMENT)
161	}
162
163	#[inline]
164	/// Solid archive; all files are in a single compressed block
165	pub fn is_solid(&self) -> bool {
166		self.flags.contains(ArchiveFlags::SOLID)
167	}
168
169	#[inline]
170	/// Indicates whether or not the archive file is split into multiple volumes, and - if so - whether or not the file is the first volume
171	pub fn volume_info(&self) -> VolumeInfo {
172		if(self.flags.contains(ArchiveFlags::FIRST_VOLUME)) {
173			VolumeInfo::First
174		} else if(self.flags.contains(ArchiveFlags::VOLUME)) {
175			VolumeInfo::Subsequent
176		} else {
177			VolumeInfo::None
178		}
179	}
180	*/
181}
182
183async fn next_entry(handle: Arc<Handle>, operation: Operation, destination: Option<CString>, userdata: UserData) -> Result<Entry, Error> {
184	unsafe {
185		unrar_sys::RARSetCallback(handle.as_ffi(), Some(OpenArchive::callback), userdata.0 as unrar_sys::LPARAM);
186	}
187
188	let read_result: Result<(Code, HeaderData), Error> = {
189		let handle = handle.clone();
190		let mut header = HeaderData::default();
191		spawn_blocking(move || unsafe {
192			let read_result = unrar_sys::RARReadHeader(handle.as_ffi(), &mut header as *mut _ as *mut _) as u32;
193			match Code::try_from(read_result) {
194				Ok(code) => Ok((code, header)),
195				Err(_) => Err(RarError::InvalidCode(read_result).into())
196			}
197		}).await?
198	};
199	let (code, header) = read_result?;
200
201	let process_result = match code {
202		Code::Success => {
203			let result = spawn_blocking(move || unsafe {
204				let process_result = unrar_sys::RARProcessFile(
205					handle.as_ffi(),
206					operation as i32,
207					destination.as_ref().map(|s| s.as_ptr() as *const _).unwrap_or(ptr::null()),
208					ptr::null()
209				) as u32;
210				Code::try_from(process_result).or(Err(RarError::InvalidCode(process_result)))
211			}).await?;
212			Ok(result?)
213		},
214		Code::EndArchive => Err(RarError::EndArchive),
215		c => Err(RarError::from((c, When::Read)))
216	}?;
217
218	match process_result {
219		Code::Success => Ok(Entry::try_from(header)?),
220		c => Err(RarError::from((c, When::Process)).into())
221	}
222}
223
224impl Stream for OpenArchive {
225	type Item = Result<Entry, Error>;
226
227	#[instrument(level = "trace", skip(self, ctx), fields(archive.path = %self.path.display(), archive.operation = ?self.operation, archive.destination = ?self.destination))]
228	#[inline]
229	fn poll_next(mut self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
230		if(self.damaged) {
231			return Poll::Ready(None);
232		}
233
234		match self.current_future.as_mut() {
235			Some(current_future) => match Pin::new(current_future).poll(ctx) {
236				Poll::Pending => Poll::Pending,
237				Poll::Ready(Ok(v)) => {
238					self.queue_next_future();
239					Poll::Ready(Some(Ok(v)))
240				},
241				Poll::Ready(Err(Error::Rar(RarError::EndArchive))) => {
242					self.current_future = None;
243					Poll::Ready(None)
244				},
245				Poll::Ready(Err(e)) => {
246					self.damaged = true;
247					self.current_future = None;
248					Poll::Ready(Some(Err(e)))
249				}
250			},
251			None => Poll::Ready(None)
252		}
253	}
254}
255
256impl Drop for OpenArchive {
257	#[inline]
258	fn drop(&mut self) {
259		unsafe { unrar_sys::RARCloseArchive(self.handle.as_ffi()) };
260		let _ = unsafe { Box::from_raw(self.userdata.0) };
261	}
262}
263
264fn path_to_cstring(input: &Path) -> Result<CString, std::ffi::FromVecWithNulError> {
265	let mut bytes = Vec::from(input.as_os_str().as_bytes());
266	bytes.push(0);
267	CString::from_vec_with_nul(bytes)
268}
269