unrar_async/
open_archive.rs1use 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;
21use crate::flags::OpenMode;
23use crate::flags::Operation;
24mod 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
57pub struct OpenArchive {
59 path: PathBuf,
60 handle: Arc<Handle>,
61 operation: Operation,
62 destination: Option<CString>,
63 damaged: bool,
64 #[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 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 #[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 unrar_sys::RAR_VOL_ASK => -1,
129 _ => 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 }
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