use core::ffi::{c_char, c_void};
use std::ffi::{CStr, CString};
use crate::casc::{FileIdHint, Storage};
use crate::support::RawCString;
extern "C" {
fn whiteout_casc_shim_openOnline(
product: *const c_char,
region: *const c_char,
build_key: *const c_char,
http: *mut c_void,
cache_dir: *const c_char,
locale_mask: u32,
pool: *mut c_void,
) -> *mut c_void;
fn whiteout_casc_shim_openWithProgress(
path: *const c_char,
product: *const c_char,
locale_mask: u32,
flags: u32,
progress: Option<ProgressFn>,
user: *mut c_void,
pool: *mut c_void,
) -> *mut c_void;
fn whiteout_casc_shim_openOnlineWithProgress(
product: *const c_char,
region: *const c_char,
build_key: *const c_char,
http: *mut c_void,
cache_dir: *const c_char,
locale_mask: u32,
flags: u32,
progress: Option<ProgressFn>,
user: *mut c_void,
pool: *mut c_void,
) -> *mut c_void;
fn whiteout_casc_shim_setProgressCallback(
self_: *mut c_void,
progress: Option<ProgressFn>,
user: *mut c_void,
);
fn whiteout_casc_shim_progressStepName(step: i32) -> *const c_char;
fn whiteout_casc_shim_readBatch(
self_: *const c_void,
paths: *const *const c_char,
file_data_ids: *const i32,
hints: *const i32,
count: usize,
) -> *mut c_void;
fn whiteout_casc_shim_readBatch_count(snapshot: *mut c_void) -> usize;
fn whiteout_casc_shim_readBatch_data_at(
snapshot: *mut c_void,
index: usize,
) -> crate::support::RawBytes;
fn whiteout_casc_shim_readBatch_success_at(snapshot: *mut c_void, index: usize) -> i32;
fn whiteout_casc_shim_readBatch_error_at(snapshot: *mut c_void, index: usize) -> RawCString;
fn whiteout_casc_shim_readBatch_free(snapshot: *mut c_void);
}
#[repr(i32)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum ProgressStep {
ResolvingVersion = 0,
LoadingBuildConfig = 1,
LoadingCdnConfig = 2,
LoadingIndexFiles = 3,
MappingArchives = 4,
LoadingArchiveIndexes = 5,
LoadingEncodingTable = 6,
LoadingVfsManifests = 7,
LoadingRootManifest = 8,
Ready = 9,
}
impl ProgressStep {
fn from_raw(v: i32) -> ProgressStep {
match v {
0 => ProgressStep::ResolvingVersion,
1 => ProgressStep::LoadingBuildConfig,
2 => ProgressStep::LoadingCdnConfig,
3 => ProgressStep::LoadingIndexFiles,
4 => ProgressStep::MappingArchives,
5 => ProgressStep::LoadingArchiveIndexes,
6 => ProgressStep::LoadingEncodingTable,
7 => ProgressStep::LoadingVfsManifests,
8 => ProgressStep::LoadingRootManifest,
_ => ProgressStep::Ready,
}
}
pub fn name(self) -> &'static str {
unsafe {
CStr::from_ptr(whiteout_casc_shim_progressStepName(self as i32))
.to_str()
.unwrap_or("Unknown")
}
}
}
#[repr(i32)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum ProgressState {
Begin = 0,
Update = 1,
End = 2,
}
impl ProgressState {
fn from_raw(v: i32) -> ProgressState {
match v {
0 => ProgressState::Begin,
1 => ProgressState::Update,
_ => ProgressState::End,
}
}
}
#[repr(C)]
struct RawProgressInfo {
size: u32,
step: i32,
state: i32,
_pad: i32,
object: *const c_char,
current: u64,
total: u64,
bytes_done: u64,
bytes_total: u64,
step_index: u32,
step_count: u32,
elapsed_ms: f64,
overall_fraction: f64,
}
#[derive(Clone, Copy, Debug)]
pub struct ProgressInfo<'a> {
pub step: ProgressStep,
pub state: ProgressState,
pub object: &'a str,
pub current: u64,
pub total: u64,
pub bytes_done: u64,
pub bytes_total: u64,
pub step_index: u32,
pub step_count: u32,
pub elapsed_ms: f64,
pub overall_fraction: f64,
}
type ProgressFn = extern "C" fn(user: *mut c_void, info: *const RawProgressInfo) -> i32;
struct ProgressCtx<'f> {
handler: &'f mut dyn FnMut(&ProgressInfo) -> bool,
panic: Option<Box<dyn core::any::Any + Send>>,
}
extern "C" fn progress_trampoline(user: *mut c_void, info: *const RawProgressInfo) -> i32 {
if user.is_null() || info.is_null() {
return 1;
}
let ctx = unsafe { &mut *(user as *mut ProgressCtx) };
if ctx.panic.is_some() {
return 0; }
let raw = unsafe { &*info };
let object = if raw.object.is_null() {
""
} else {
unsafe { CStr::from_ptr(raw.object) }.to_str().unwrap_or("")
};
let event = ProgressInfo {
step: ProgressStep::from_raw(raw.step),
state: ProgressState::from_raw(raw.state),
object,
current: raw.current,
total: raw.total,
bytes_done: raw.bytes_done,
bytes_total: raw.bytes_total,
step_index: raw.step_index,
step_count: raw.step_count,
elapsed_ms: raw.elapsed_ms,
overall_fraction: raw.overall_fraction,
};
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| (ctx.handler)(&event)));
match result {
Ok(keep_going) => i32::from(keep_going),
Err(payload) => {
ctx.panic = Some(payload);
0
}
}
}
fn with_progress_ctx<R>(
handler: Option<&mut dyn FnMut(&ProgressInfo) -> bool>,
body: impl FnOnce(Option<ProgressFn>, *mut c_void) -> R,
) -> R {
match handler {
None => body(None, core::ptr::null_mut()),
Some(handler) => {
let mut ctx = ProgressCtx {
handler,
panic: None,
};
let out = body(
Some(progress_trampoline),
&mut ctx as *mut ProgressCtx as *mut c_void,
);
if let Some(payload) = ctx.panic.take() {
std::panic::resume_unwind(payload);
}
out
}
}
}
pub trait AsHttpHandler {
fn as_http_ptr(&self) -> *mut c_void;
}
impl AsHttpHandler for crate::interfaces::HostHttpHandler {
fn as_http_ptr(&self) -> *mut c_void {
self.as_ptr()
}
}
impl AsHttpHandler for crate::host::SimpleHttpHandler {
fn as_http_ptr(&self) -> *mut c_void {
self.raw.as_ptr() as *mut c_void
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum BatchReadRequest {
Path(String),
FileId {
id: i32,
hint: FileIdHint,
},
}
impl BatchReadRequest {
pub fn path(path: impl Into<String>) -> Self {
BatchReadRequest::Path(path.into())
}
pub fn file_id(id: i32) -> Self {
BatchReadRequest::FileId {
id,
hint: FileIdHint::None,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BatchReadResult {
pub data: Option<Vec<u8>>,
pub error: String,
}
impl BatchReadResult {
pub fn is_ok(&self) -> bool {
self.data.is_some()
}
}
impl Storage {
pub fn open_online(
product: &str,
region: &str,
http: &dyn AsHttpHandler,
build_key: Option<&str>,
cache_dir: Option<&str>,
locale_mask: u32,
pool: Option<&crate::interfaces::HostWorkerPool>,
) -> Option<Storage> {
let product_cstr = CString::new(product).unwrap_or_default();
let region_cstr =
CString::new(if region.is_empty() { "us" } else { region }).unwrap_or_default();
let build_key_cstr = CString::new(build_key.unwrap_or("")).unwrap_or_default();
let cache_dir_cstr = CString::new(cache_dir.unwrap_or("")).unwrap_or_default();
unsafe {
Storage::from_raw(whiteout_casc_shim_openOnline(
product_cstr.as_ptr(),
region_cstr.as_ptr(),
build_key_cstr.as_ptr(),
http.as_http_ptr(),
cache_dir_cstr.as_ptr(),
locale_mask,
pool.map_or(core::ptr::null_mut(), |p| p.as_ptr()),
) as *mut _)
}
}
pub fn open_with_progress(
path: &str,
product: Option<&str>,
locale_mask: u32,
flags: u32,
pool: Option<&crate::interfaces::HostWorkerPool>,
progress: &mut dyn FnMut(&ProgressInfo) -> bool,
) -> Option<Storage> {
let path_cstr = CString::new(path).unwrap_or_default();
let product_cstr = CString::new(product.unwrap_or("")).unwrap_or_default();
let pool_ptr = pool.map_or(core::ptr::null_mut(), |p| p.as_ptr());
with_progress_ctx(Some(progress), |cb, user| {
unsafe {
Storage::from_raw(whiteout_casc_shim_openWithProgress(
path_cstr.as_ptr(),
product_cstr.as_ptr(),
locale_mask,
flags,
cb,
user,
pool_ptr,
) as *mut _)
}
})
}
#[allow(clippy::too_many_arguments)]
pub fn open_online_with_progress(
product: &str,
region: &str,
http: &dyn AsHttpHandler,
build_key: Option<&str>,
cache_dir: Option<&str>,
locale_mask: u32,
flags: u32,
pool: Option<&crate::interfaces::HostWorkerPool>,
progress: &mut dyn FnMut(&ProgressInfo) -> bool,
) -> Option<Storage> {
let product_cstr = CString::new(product).unwrap_or_default();
let region_cstr =
CString::new(if region.is_empty() { "us" } else { region }).unwrap_or_default();
let build_key_cstr = CString::new(build_key.unwrap_or("")).unwrap_or_default();
let cache_dir_cstr = CString::new(cache_dir.unwrap_or("")).unwrap_or_default();
let http_ptr = http.as_http_ptr();
let pool_ptr = pool.map_or(core::ptr::null_mut(), |p| p.as_ptr());
with_progress_ctx(Some(progress), |cb, user| {
unsafe {
Storage::from_raw(whiteout_casc_shim_openOnlineWithProgress(
product_cstr.as_ptr(),
region_cstr.as_ptr(),
build_key_cstr.as_ptr(),
http_ptr,
cache_dir_cstr.as_ptr(),
locale_mask,
flags,
cb,
user,
pool_ptr,
) as *mut _)
}
})
}
pub fn with_progress<R>(
&mut self,
progress: &mut dyn FnMut(&ProgressInfo) -> bool,
body: impl FnOnce(&mut Storage) -> R,
) -> R {
let handle = self.raw.as_ptr() as *mut c_void;
with_progress_ctx(Some(progress), |cb, user| {
unsafe { whiteout_casc_shim_setProgressCallback(handle, cb, user) };
let out = body(self);
unsafe { whiteout_casc_shim_setProgressCallback(handle, None, core::ptr::null_mut()) };
out
})
}
pub fn read_batch(&self, requests: &[BatchReadRequest]) -> Vec<BatchReadResult> {
if requests.is_empty() {
return Vec::new();
}
let mut owned: Vec<Option<CString>> = Vec::with_capacity(requests.len());
let mut ids: Vec<i32> = Vec::with_capacity(requests.len());
let mut hints: Vec<i32> = Vec::with_capacity(requests.len());
for r in requests {
match r {
BatchReadRequest::Path(p) => {
owned.push(Some(CString::new(p.as_str()).unwrap_or_default()));
ids.push(-1);
hints.push(0);
}
BatchReadRequest::FileId { id, hint } => {
owned.push(None);
ids.push(*id);
hints.push(*hint as i32);
}
}
}
let ptrs: Vec<*const c_char> = owned
.iter()
.map(|o| o.as_ref().map_or(core::ptr::null(), |c| c.as_ptr()))
.collect();
unsafe {
let snap = whiteout_casc_shim_readBatch(
self.raw.as_ptr() as *const c_void,
ptrs.as_ptr(),
ids.as_ptr(),
hints.as_ptr(),
requests.len(),
);
if snap.is_null() {
return Vec::new();
}
let n = whiteout_casc_shim_readBatch_count(snap);
let mut out = Vec::with_capacity(n);
for i in 0..n {
let ok = whiteout_casc_shim_readBatch_success_at(snap, i) != 0;
let data = if ok {
let raw = whiteout_casc_shim_readBatch_data_at(snap, i);
if raw.data.is_null() {
Some(Vec::new())
} else {
Some(core::slice::from_raw_parts(raw.data, raw.size).to_vec())
}
} else {
None
};
let raw_err = whiteout_casc_shim_readBatch_error_at(snap, i);
let error = if raw_err.chars.is_null() {
String::new()
} else {
String::from_utf8_lossy(core::slice::from_raw_parts(
raw_err.chars as *const u8,
raw_err.length,
))
.into_owned()
};
out.push(BatchReadResult { data, error });
}
whiteout_casc_shim_readBatch_free(snap);
out
}
}
}