use std::ffi::CStr;
use std::ffi::CString;
use std::os::unix::ffi::OsStrExt;
use std::path::Path;
use std::path::PathBuf;
use std::pin::Pin;
use std::ptr;
use std::sync::Arc;
use futures::Future;
use futures::stream::Stream;
use futures::stream::StreamExt;
use futures::task::Context;
use futures::task::Poll;
use tracing::instrument;
use crate::error::Code;
use crate::error::RarError;
use crate::error::When;
use crate::error::Error;
use crate::flags::OpenMode;
use crate::flags::Operation;
mod data;
pub use data::Entry;
use data::Handle;
use data::HeaderData;
use data::OpenArchiveData;
#[cfg(feature = "async-std")]
#[inline]
async fn spawn_blocking<T: Send + 'static>(func: impl FnOnce() -> T + Send + 'static) -> Result<T, Error> {
Ok(async_std::task::spawn_blocking(func).await)
}
#[cfg(feature = "tokio")]
#[inline]
async fn spawn_blocking<T: Send + 'static>(func: impl FnOnce() -> T + Send + 'static) -> Result<T, Error> {
Ok(tokio::task::spawn_blocking(func).await?)
}
#[derive(Clone, Copy)]
struct UserData(*mut [u8; 256]);
unsafe impl Send for UserData {}
unsafe impl Sync for UserData {}
impl Default for UserData {
#[inline]
fn default() -> Self {
let allocation = Box::new([0u8; 256]);
Self(Box::into_raw(allocation))
}
}
pub struct OpenArchive {
path: PathBuf,
handle: Arc<Handle>,
operation: Operation,
destination: Option<CString>,
damaged: bool,
#[allow(clippy::type_complexity)]
current_future: Option<Pin<Box<dyn Future<Output = Result<Entry, Error>> + Send>>>,
userdata: UserData
}
impl OpenArchive {
#[instrument(err, level = "info", skip(password))]
pub(crate) async fn open(filename: &Path, mode: OpenMode, password: Option<CString>, destination: Option<&Path>, operation: Operation) -> Result<Self, Error> {
let destination = destination.map(path_to_cstring).map_or(Ok(None), |r| r.map(Some)).map_err(Error::from)?;
let data = OpenArchiveData::new(path_to_cstring(filename)?, mode as u32);
let (handle, data) = spawn_blocking(move || {
let p = unsafe { unrar_sys::RAROpenArchive(&mut data.as_ffi() as *mut _) } as *mut unrar_sys::HANDLE;
match p.is_null() {
false => Ok((Arc::new(Handle::from_ffi(p)), data)),
true => Err(Error::NulHandle)
}
}).await??;
let result = Code::try_from(data.open_result).or(Err(RarError::InvalidCode(data.open_result)))?;
if let Some(pw) = password {
unsafe { unrar_sys::RARSetPassword(handle.as_ffi(), pw.as_ptr() as *const _) }
}
match result {
Code::Success => Ok(Self::new(filename.into(), handle, operation, destination)),
e => Err(Error::Rar(RarError::from((e, When::Open))))
}
}
fn new(path: PathBuf, handle: Arc<Handle>, operation: Operation, destination: Option<CString>) -> Self {
let mut this = Self{
path,
handle,
operation,
destination,
damaged: false,
current_future: None,
userdata: UserData::default()
};
this.queue_next_future();
this
}
#[instrument(err, level = "info", skip(self), fields(archive.path = %self.path.display(), archive.operation = ?self.operation, archive.destination = ?self.destination))]
pub async fn process(&mut self) -> Result<Vec<Entry>, Error> {
let mut results = Vec::new();
while let Some(item) = self.next().await {
results.push(item?);
}
Ok(results)
}
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 {
match msg {
unrar_sys::UCM_CHANGEVOLUME => {
let ptr = p1 as *const _;
let next = std::str::from_utf8(unsafe { CStr::from_ptr(ptr) }.to_bytes()).unwrap();
let our_option = unsafe { &mut *(user_data as *mut Option<String>) };
*our_option = Some(String::from(next));
match p2 {
unrar_sys::RAR_VOL_ASK => -1,
_ => 1
}
},
_ => 0
}
}
#[inline]
fn queue_next_future(&mut self) {
self.current_future = Some(Box::pin(next_entry(self.handle.clone(), self.operation, self.destination.clone(), self.userdata)));
}
}
async fn next_entry(handle: Arc<Handle>, operation: Operation, destination: Option<CString>, userdata: UserData) -> Result<Entry, Error> {
unsafe {
unrar_sys::RARSetCallback(handle.as_ffi(), OpenArchive::callback, userdata.0 as unrar_sys::LPARAM);
}
let read_result: Result<(Code, HeaderData), Error> = {
let handle = handle.clone();
let mut header = HeaderData::default();
spawn_blocking(move || unsafe {
let read_result = unrar_sys::RARReadHeader(handle.as_ffi(), &mut header as *mut _ as *mut _) as u32;
match Code::try_from(read_result) {
Ok(code) => Ok((code, header)),
Err(_) => Err(RarError::InvalidCode(read_result).into())
}
}).await?
};
let (code, header) = read_result?;
let process_result = match code {
Code::Success => {
let result = spawn_blocking(move || unsafe {
let process_result = unrar_sys::RARProcessFile(
handle.as_ffi(),
operation as i32,
destination.as_ref().map(|s| s.as_ptr() as *const _).unwrap_or(ptr::null()),
ptr::null()
) as u32;
Code::try_from(process_result).or(Err(RarError::InvalidCode(process_result)))
}).await?;
Ok(result?)
},
Code::EndArchive => Err(RarError::EndArchive),
c => Err(RarError::from((c, When::Read)))
}?;
match process_result {
Code::Success => Ok(Entry::try_from(header)?),
c => Err(RarError::from((c, When::Process)).into())
}
}
impl Stream for OpenArchive {
type Item = Result<Entry, Error>;
#[instrument(level = "trace", skip(self, ctx), fields(archive.path = %self.path.display(), archive.operation = ?self.operation, archive.destination = ?self.destination))]
#[inline]
fn poll_next(mut self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
if(self.damaged) {
return Poll::Ready(None);
}
match self.current_future.as_mut() {
Some(current_future) => match Pin::new(current_future).poll(ctx) {
Poll::Pending => Poll::Pending,
Poll::Ready(Ok(v)) => {
self.queue_next_future();
Poll::Ready(Some(Ok(v)))
},
Poll::Ready(Err(Error::Rar(RarError::EndArchive))) => {
self.current_future = None;
Poll::Ready(None)
},
Poll::Ready(Err(e)) => {
self.damaged = true;
self.current_future = None;
Poll::Ready(Some(Err(e)))
}
},
None => Poll::Ready(None)
}
}
}
impl Drop for OpenArchive {
#[inline]
fn drop(&mut self) {
unsafe { unrar_sys::RARCloseArchive(self.handle.as_ffi()) };
unsafe { Box::from_raw(self.userdata.0) };
}
}
fn path_to_cstring(input: &Path) -> Result<CString, std::ffi::FromVecWithNulError> {
let mut bytes = Vec::from(input.as_os_str().as_bytes());
bytes.push(0);
CString::from_vec_with_nul(bytes)
}