1use core::ffi::{c_char, c_void};
17use std::ffi::{CStr, CString};
18
19use crate::casc::{FileIdHint, Storage};
20use crate::support::RawCString;
21
22extern "C" {
23 fn whiteout_casc_shim_openOnline(
24 product: *const c_char,
25 region: *const c_char,
26 build_key: *const c_char,
27 http: *mut c_void,
28 cache_dir: *const c_char,
29 locale_mask: u32,
30 pool: *mut c_void,
31 ) -> *mut c_void;
32
33 fn whiteout_casc_shim_openWithProgress(
34 path: *const c_char,
35 product: *const c_char,
36 locale_mask: u32,
37 flags: u32,
38 progress: Option<ProgressFn>,
39 user: *mut c_void,
40 pool: *mut c_void,
41 ) -> *mut c_void;
42
43 fn whiteout_casc_shim_openOnlineWithProgress(
44 product: *const c_char,
45 region: *const c_char,
46 build_key: *const c_char,
47 http: *mut c_void,
48 cache_dir: *const c_char,
49 locale_mask: u32,
50 flags: u32,
51 progress: Option<ProgressFn>,
52 user: *mut c_void,
53 pool: *mut c_void,
54 ) -> *mut c_void;
55
56 fn whiteout_casc_shim_setProgressCallback(
57 self_: *mut c_void,
58 progress: Option<ProgressFn>,
59 user: *mut c_void,
60 );
61
62 fn whiteout_casc_shim_progressStepName(step: i32) -> *const c_char;
63
64 fn whiteout_casc_shim_readBatch(
65 self_: *const c_void,
66 paths: *const *const c_char,
67 file_data_ids: *const i32,
68 hints: *const i32,
69 count: usize,
70 ) -> *mut c_void;
71
72 fn whiteout_casc_shim_readBatch_count(snapshot: *mut c_void) -> usize;
73 fn whiteout_casc_shim_readBatch_data_at(
74 snapshot: *mut c_void,
75 index: usize,
76 ) -> crate::support::RawBytes;
77 fn whiteout_casc_shim_readBatch_success_at(snapshot: *mut c_void, index: usize) -> i32;
78 fn whiteout_casc_shim_readBatch_error_at(snapshot: *mut c_void, index: usize) -> RawCString;
79 fn whiteout_casc_shim_readBatch_free(snapshot: *mut c_void);
80}
81
82#[repr(i32)]
86#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
87pub enum ProgressStep {
88 ResolvingVersion = 0,
90 LoadingBuildConfig = 1,
92 LoadingCdnConfig = 2,
94 LoadingIndexFiles = 3,
96 MappingArchives = 4,
98 LoadingArchiveIndexes = 5,
100 LoadingEncodingTable = 6,
102 LoadingVfsManifests = 7,
104 LoadingRootManifest = 8,
106 Ready = 9,
108}
109
110impl ProgressStep {
111 fn from_raw(v: i32) -> ProgressStep {
112 match v {
113 0 => ProgressStep::ResolvingVersion,
114 1 => ProgressStep::LoadingBuildConfig,
115 2 => ProgressStep::LoadingCdnConfig,
116 3 => ProgressStep::LoadingIndexFiles,
117 4 => ProgressStep::MappingArchives,
118 5 => ProgressStep::LoadingArchiveIndexes,
119 6 => ProgressStep::LoadingEncodingTable,
120 7 => ProgressStep::LoadingVfsManifests,
121 8 => ProgressStep::LoadingRootManifest,
122 _ => ProgressStep::Ready,
123 }
124 }
125
126 pub fn name(self) -> &'static str {
128 unsafe {
130 CStr::from_ptr(whiteout_casc_shim_progressStepName(self as i32))
131 .to_str()
132 .unwrap_or("Unknown")
133 }
134 }
135}
136
137#[repr(i32)]
139#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
140pub enum ProgressState {
141 Begin = 0,
143 Update = 1,
145 End = 2,
147}
148
149impl ProgressState {
150 fn from_raw(v: i32) -> ProgressState {
151 match v {
152 0 => ProgressState::Begin,
153 1 => ProgressState::Update,
154 _ => ProgressState::End,
155 }
156 }
157}
158
159#[repr(C)]
161struct RawProgressInfo {
162 size: u32,
163 step: i32,
164 state: i32,
165 _pad: i32,
166 object: *const c_char,
167 current: u64,
168 total: u64,
169 bytes_done: u64,
170 bytes_total: u64,
171 step_index: u32,
172 step_count: u32,
173 elapsed_ms: f64,
174 overall_fraction: f64,
175}
176
177#[derive(Clone, Copy, Debug)]
182pub struct ProgressInfo<'a> {
183 pub step: ProgressStep,
185 pub state: ProgressState,
187 pub object: &'a str,
189 pub current: u64,
191 pub total: u64,
193 pub bytes_done: u64,
195 pub bytes_total: u64,
197 pub step_index: u32,
199 pub step_count: u32,
201 pub elapsed_ms: f64,
203 pub overall_fraction: f64,
205}
206
207type ProgressFn = extern "C" fn(user: *mut c_void, info: *const RawProgressInfo) -> i32;
208
209struct ProgressCtx<'f> {
213 handler: &'f mut dyn FnMut(&ProgressInfo) -> bool,
214 panic: Option<Box<dyn core::any::Any + Send>>,
215}
216
217extern "C" fn progress_trampoline(user: *mut c_void, info: *const RawProgressInfo) -> i32 {
218 if user.is_null() || info.is_null() {
219 return 1;
220 }
221 let ctx = unsafe { &mut *(user as *mut ProgressCtx) };
224 if ctx.panic.is_some() {
225 return 0; }
227 let raw = unsafe { &*info };
228 let object = if raw.object.is_null() {
229 ""
230 } else {
231 unsafe { CStr::from_ptr(raw.object) }.to_str().unwrap_or("")
232 };
233 let event = ProgressInfo {
234 step: ProgressStep::from_raw(raw.step),
235 state: ProgressState::from_raw(raw.state),
236 object,
237 current: raw.current,
238 total: raw.total,
239 bytes_done: raw.bytes_done,
240 bytes_total: raw.bytes_total,
241 step_index: raw.step_index,
242 step_count: raw.step_count,
243 elapsed_ms: raw.elapsed_ms,
244 overall_fraction: raw.overall_fraction,
245 };
246
247 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| (ctx.handler)(&event)));
248 match result {
249 Ok(keep_going) => i32::from(keep_going),
250 Err(payload) => {
251 ctx.panic = Some(payload);
252 0
253 }
254 }
255}
256
257fn with_progress_ctx<R>(
260 handler: Option<&mut dyn FnMut(&ProgressInfo) -> bool>,
261 body: impl FnOnce(Option<ProgressFn>, *mut c_void) -> R,
262) -> R {
263 match handler {
264 None => body(None, core::ptr::null_mut()),
265 Some(handler) => {
266 let mut ctx = ProgressCtx {
267 handler,
268 panic: None,
269 };
270 let out = body(
271 Some(progress_trampoline),
272 &mut ctx as *mut ProgressCtx as *mut c_void,
273 );
274 if let Some(payload) = ctx.panic.take() {
275 std::panic::resume_unwind(payload);
276 }
277 out
278 }
279 }
280}
281
282pub trait AsHttpHandler {
288 fn as_http_ptr(&self) -> *mut c_void;
290}
291
292impl AsHttpHandler for crate::interfaces::HostHttpHandler {
293 fn as_http_ptr(&self) -> *mut c_void {
294 self.as_ptr()
295 }
296}
297
298impl AsHttpHandler for crate::host::SimpleHttpHandler {
299 fn as_http_ptr(&self) -> *mut c_void {
300 self.raw.as_ptr() as *mut c_void
301 }
302}
303
304#[derive(Clone, Debug, PartialEq, Eq)]
307pub enum BatchReadRequest {
308 Path(String),
310 FileId {
312 id: i32,
314 hint: FileIdHint,
316 },
317}
318
319impl BatchReadRequest {
320 pub fn path(path: impl Into<String>) -> Self {
322 BatchReadRequest::Path(path.into())
323 }
324
325 pub fn file_id(id: i32) -> Self {
327 BatchReadRequest::FileId {
328 id,
329 hint: FileIdHint::None,
330 }
331 }
332}
333
334#[derive(Clone, Debug, PartialEq, Eq)]
336pub struct BatchReadResult {
337 pub data: Option<Vec<u8>>,
339 pub error: String,
341}
342
343impl BatchReadResult {
344 pub fn is_ok(&self) -> bool {
346 self.data.is_some()
347 }
348}
349
350impl Storage {
351 pub fn open_online(
364 product: &str,
365 region: &str,
366 http: &dyn AsHttpHandler,
367 build_key: Option<&str>,
368 cache_dir: Option<&str>,
369 locale_mask: u32,
370 pool: Option<&crate::interfaces::HostWorkerPool>,
371 ) -> Option<Storage> {
372 let product_cstr = CString::new(product).unwrap_or_default();
373 let region_cstr =
374 CString::new(if region.is_empty() { "us" } else { region }).unwrap_or_default();
375 let build_key_cstr = CString::new(build_key.unwrap_or("")).unwrap_or_default();
376 let cache_dir_cstr = CString::new(cache_dir.unwrap_or("")).unwrap_or_default();
377
378 unsafe {
381 Storage::from_raw(whiteout_casc_shim_openOnline(
382 product_cstr.as_ptr(),
383 region_cstr.as_ptr(),
384 build_key_cstr.as_ptr(),
385 http.as_http_ptr(),
386 cache_dir_cstr.as_ptr(),
387 locale_mask,
388 pool.map_or(core::ptr::null_mut(), |p| p.as_ptr()),
389 ) as *mut _)
390 }
391 }
392
393 pub fn open_with_progress(
406 path: &str,
407 product: Option<&str>,
408 locale_mask: u32,
409 flags: u32,
410 pool: Option<&crate::interfaces::HostWorkerPool>,
411 progress: &mut dyn FnMut(&ProgressInfo) -> bool,
412 ) -> Option<Storage> {
413 let path_cstr = CString::new(path).unwrap_or_default();
414 let product_cstr = CString::new(product.unwrap_or("")).unwrap_or_default();
415 let pool_ptr = pool.map_or(core::ptr::null_mut(), |p| p.as_ptr());
416
417 with_progress_ctx(Some(progress), |cb, user| {
418 unsafe {
421 Storage::from_raw(whiteout_casc_shim_openWithProgress(
422 path_cstr.as_ptr(),
423 product_cstr.as_ptr(),
424 locale_mask,
425 flags,
426 cb,
427 user,
428 pool_ptr,
429 ) as *mut _)
430 }
431 })
432 }
433
434 #[allow(clippy::too_many_arguments)]
440 pub fn open_online_with_progress(
441 product: &str,
442 region: &str,
443 http: &dyn AsHttpHandler,
444 build_key: Option<&str>,
445 cache_dir: Option<&str>,
446 locale_mask: u32,
447 flags: u32,
448 pool: Option<&crate::interfaces::HostWorkerPool>,
449 progress: &mut dyn FnMut(&ProgressInfo) -> bool,
450 ) -> Option<Storage> {
451 let product_cstr = CString::new(product).unwrap_or_default();
452 let region_cstr =
453 CString::new(if region.is_empty() { "us" } else { region }).unwrap_or_default();
454 let build_key_cstr = CString::new(build_key.unwrap_or("")).unwrap_or_default();
455 let cache_dir_cstr = CString::new(cache_dir.unwrap_or("")).unwrap_or_default();
456 let http_ptr = http.as_http_ptr();
457 let pool_ptr = pool.map_or(core::ptr::null_mut(), |p| p.as_ptr());
458
459 with_progress_ctx(Some(progress), |cb, user| {
460 unsafe {
462 Storage::from_raw(whiteout_casc_shim_openOnlineWithProgress(
463 product_cstr.as_ptr(),
464 region_cstr.as_ptr(),
465 build_key_cstr.as_ptr(),
466 http_ptr,
467 cache_dir_cstr.as_ptr(),
468 locale_mask,
469 flags,
470 cb,
471 user,
472 pool_ptr,
473 ) as *mut _)
474 }
475 })
476 }
477
478 pub fn with_progress<R>(
494 &mut self,
495 progress: &mut dyn FnMut(&ProgressInfo) -> bool,
496 body: impl FnOnce(&mut Storage) -> R,
497 ) -> R {
498 let handle = self.raw.as_ptr() as *mut c_void;
499 with_progress_ctx(Some(progress), |cb, user| {
500 unsafe { whiteout_casc_shim_setProgressCallback(handle, cb, user) };
503 let out = body(self);
504 unsafe { whiteout_casc_shim_setProgressCallback(handle, None, core::ptr::null_mut()) };
505 out
506 })
507 }
508
509 pub fn read_batch(&self, requests: &[BatchReadRequest]) -> Vec<BatchReadResult> {
517 if requests.is_empty() {
518 return Vec::new();
519 }
520
521 let mut owned: Vec<Option<CString>> = Vec::with_capacity(requests.len());
524 let mut ids: Vec<i32> = Vec::with_capacity(requests.len());
525 let mut hints: Vec<i32> = Vec::with_capacity(requests.len());
526 for r in requests {
527 match r {
528 BatchReadRequest::Path(p) => {
529 owned.push(Some(CString::new(p.as_str()).unwrap_or_default()));
530 ids.push(-1);
531 hints.push(0);
532 }
533 BatchReadRequest::FileId { id, hint } => {
534 owned.push(None);
535 ids.push(*id);
536 hints.push(*hint as i32);
537 }
538 }
539 }
540 let ptrs: Vec<*const c_char> = owned
541 .iter()
542 .map(|o| o.as_ref().map_or(core::ptr::null(), |c| c.as_ptr()))
543 .collect();
544
545 unsafe {
548 let snap = whiteout_casc_shim_readBatch(
549 self.raw.as_ptr() as *const c_void,
550 ptrs.as_ptr(),
551 ids.as_ptr(),
552 hints.as_ptr(),
553 requests.len(),
554 );
555 if snap.is_null() {
556 return Vec::new();
557 }
558 let n = whiteout_casc_shim_readBatch_count(snap);
559 let mut out = Vec::with_capacity(n);
560 for i in 0..n {
561 let ok = whiteout_casc_shim_readBatch_success_at(snap, i) != 0;
562 let data = if ok {
565 let raw = whiteout_casc_shim_readBatch_data_at(snap, i);
566 if raw.data.is_null() {
567 Some(Vec::new())
568 } else {
569 Some(core::slice::from_raw_parts(raw.data, raw.size).to_vec())
570 }
571 } else {
572 None
573 };
574 let raw_err = whiteout_casc_shim_readBatch_error_at(snap, i);
575 let error = if raw_err.chars.is_null() {
576 String::new()
577 } else {
578 String::from_utf8_lossy(core::slice::from_raw_parts(
579 raw_err.chars as *const u8,
580 raw_err.length,
581 ))
582 .into_owned()
583 };
584 out.push(BatchReadResult { data, error });
585 }
586 whiteout_casc_shim_readBatch_free(snap);
587 out
588 }
589 }
590}