1use core::ffi::{c_char, c_void};
17use std::ffi::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_readBatch(
34 self_: *const c_void,
35 paths: *const *const c_char,
36 file_data_ids: *const i32,
37 hints: *const i32,
38 count: usize,
39 ) -> *mut c_void;
40
41 fn whiteout_casc_shim_readBatch_count(snapshot: *mut c_void) -> usize;
42 fn whiteout_casc_shim_readBatch_data_at(
43 snapshot: *mut c_void,
44 index: usize,
45 ) -> crate::support::RawBytes;
46 fn whiteout_casc_shim_readBatch_success_at(snapshot: *mut c_void, index: usize) -> i32;
47 fn whiteout_casc_shim_readBatch_error_at(snapshot: *mut c_void, index: usize) -> RawCString;
48 fn whiteout_casc_shim_readBatch_free(snapshot: *mut c_void);
49}
50
51pub trait AsHttpHandler {
57 fn as_http_ptr(&self) -> *mut c_void;
59}
60
61impl AsHttpHandler for crate::interfaces::HostHttpHandler {
62 fn as_http_ptr(&self) -> *mut c_void {
63 self.as_ptr()
64 }
65}
66
67impl AsHttpHandler for crate::host::SimpleHttpHandler {
68 fn as_http_ptr(&self) -> *mut c_void {
69 self.raw.as_ptr() as *mut c_void
70 }
71}
72
73#[derive(Clone, Debug, PartialEq, Eq)]
76pub enum BatchReadRequest {
77 Path(String),
79 FileId {
81 id: i32,
83 hint: FileIdHint,
85 },
86}
87
88impl BatchReadRequest {
89 pub fn path(path: impl Into<String>) -> Self {
91 BatchReadRequest::Path(path.into())
92 }
93
94 pub fn file_id(id: i32) -> Self {
96 BatchReadRequest::FileId {
97 id,
98 hint: FileIdHint::None,
99 }
100 }
101}
102
103#[derive(Clone, Debug, PartialEq, Eq)]
105pub struct BatchReadResult {
106 pub data: Option<Vec<u8>>,
108 pub error: String,
110}
111
112impl BatchReadResult {
113 pub fn is_ok(&self) -> bool {
115 self.data.is_some()
116 }
117}
118
119impl Storage {
120 pub fn open_online(
133 product: &str,
134 region: &str,
135 http: &dyn AsHttpHandler,
136 build_key: Option<&str>,
137 cache_dir: Option<&str>,
138 locale_mask: u32,
139 pool: Option<&crate::interfaces::HostWorkerPool>,
140 ) -> Option<Storage> {
141 let product_cstr = CString::new(product).unwrap_or_default();
142 let region_cstr = CString::new(if region.is_empty() { "us" } else { region })
143 .unwrap_or_default();
144 let build_key_cstr = CString::new(build_key.unwrap_or("")).unwrap_or_default();
145 let cache_dir_cstr = CString::new(cache_dir.unwrap_or("")).unwrap_or_default();
146
147 unsafe {
150 Storage::from_raw(whiteout_casc_shim_openOnline(
151 product_cstr.as_ptr(),
152 region_cstr.as_ptr(),
153 build_key_cstr.as_ptr(),
154 http.as_http_ptr(),
155 cache_dir_cstr.as_ptr(),
156 locale_mask,
157 pool.map_or(core::ptr::null_mut(), |p| p.as_ptr()),
158 ) as *mut _)
159 }
160 }
161
162 pub fn read_batch(&self, requests: &[BatchReadRequest]) -> Vec<BatchReadResult> {
170 if requests.is_empty() {
171 return Vec::new();
172 }
173
174 let mut owned: Vec<Option<CString>> = Vec::with_capacity(requests.len());
177 let mut ids: Vec<i32> = Vec::with_capacity(requests.len());
178 let mut hints: Vec<i32> = Vec::with_capacity(requests.len());
179 for r in requests {
180 match r {
181 BatchReadRequest::Path(p) => {
182 owned.push(Some(CString::new(p.as_str()).unwrap_or_default()));
183 ids.push(-1);
184 hints.push(0);
185 }
186 BatchReadRequest::FileId { id, hint } => {
187 owned.push(None);
188 ids.push(*id);
189 hints.push(*hint as i32);
190 }
191 }
192 }
193 let ptrs: Vec<*const c_char> = owned
194 .iter()
195 .map(|o| o.as_ref().map_or(core::ptr::null(), |c| c.as_ptr()))
196 .collect();
197
198 unsafe {
201 let snap = whiteout_casc_shim_readBatch(
202 self.raw.as_ptr() as *const c_void,
203 ptrs.as_ptr(),
204 ids.as_ptr(),
205 hints.as_ptr(),
206 requests.len(),
207 );
208 if snap.is_null() {
209 return Vec::new();
210 }
211 let n = whiteout_casc_shim_readBatch_count(snap);
212 let mut out = Vec::with_capacity(n);
213 for i in 0..n {
214 let ok = whiteout_casc_shim_readBatch_success_at(snap, i) != 0;
215 let data = if ok {
218 let raw = whiteout_casc_shim_readBatch_data_at(snap, i);
219 if raw.data.is_null() {
220 Some(Vec::new())
221 } else {
222 Some(core::slice::from_raw_parts(raw.data, raw.size).to_vec())
223 }
224 } else {
225 None
226 };
227 let raw_err = whiteout_casc_shim_readBatch_error_at(snap, i);
228 let error = if raw_err.chars.is_null() {
229 String::new()
230 } else {
231 String::from_utf8_lossy(core::slice::from_raw_parts(
232 raw_err.chars as *const u8,
233 raw_err.length,
234 ))
235 .into_owned()
236 };
237 out.push(BatchReadResult { data, error });
238 }
239 whiteout_casc_shim_readBatch_free(snap);
240 out
241 }
242 }
243}