#![allow(clippy::missing_safety_doc)]
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
use std::ffi::c_void;
use crate::cancellation::CancellationToken;
use crate::models::enums::{DeviceFamily, IdentifierType};
use crate::services::display_catalog::{DisplayCatalogHandler, ProgressEvent};
pub const STORELIB_OK: i32 = 0;
pub const STORELIB_ERR_NULL: i32 = -1;
pub const STORELIB_ERR_HTTP: i32 = -2;
pub const STORELIB_ERR_JSON: i32 = -3;
pub const STORELIB_ERR_XML: i32 = -4;
pub const STORELIB_ERR_NOT_FOUND: i32 = -5;
pub const STORELIB_ERR_TIMEOUT: i32 = -6;
pub const STORELIB_ERR_OTHER: i32 = -7;
pub const STORELIB_ERR_CANCELLED: i32 = -8;
fn err_code(e: &crate::error::StoreError) -> i32 {
use crate::error::StoreError::*;
match e {
Http(_) => STORELIB_ERR_HTTP,
Json(_) => STORELIB_ERR_JSON,
Xml(_) => STORELIB_ERR_XML,
NotFound => STORELIB_ERR_NOT_FOUND,
TimedOut => STORELIB_ERR_TIMEOUT,
Cancelled => STORELIB_ERR_CANCELLED,
Other(_) => STORELIB_ERR_OTHER,
}
}
pub struct StorelibHandle {
handler: DisplayCatalogHandler,
rt: tokio::runtime::Runtime,
last_error: Option<CString>,
}
impl StorelibHandle {
fn set_error(&mut self, msg: &str) {
self.last_error = CString::new(msg).ok();
}
fn clear_error(&mut self) {
self.last_error = None;
}
}
#[no_mangle]
pub extern "C" fn storelib_new() -> *mut StorelibHandle {
let rt = match tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
{
Ok(r) => r,
Err(_) => return std::ptr::null_mut(),
};
let handle = Box::new(StorelibHandle {
handler: DisplayCatalogHandler::production(),
rt,
last_error: None,
});
Box::into_raw(handle)
}
#[no_mangle]
pub unsafe extern "C" fn storelib_free(handle: *mut StorelibHandle) {
if !handle.is_null() {
drop(Box::from_raw(handle));
}
}
#[no_mangle]
pub unsafe extern "C" fn storelib_last_error(handle: *const StorelibHandle) -> *const c_char {
if handle.is_null() {
return std::ptr::null();
}
match &(*handle).last_error {
Some(s) => s.as_ptr(),
None => std::ptr::null(),
}
}
#[no_mangle]
pub unsafe extern "C" fn storelib_query(
handle: *mut StorelibHandle,
id: *const c_char,
id_type: u32,
auth_token: *const c_char,
) -> i32 {
storelib_query_with_cancel(handle, id, id_type, auth_token, std::ptr::null())
}
#[no_mangle]
pub unsafe extern "C" fn storelib_is_found(handle: *const StorelibHandle) -> i32 {
if handle.is_null() {
return 0;
}
if (*handle).handler.is_found {
1
} else {
0
}
}
#[no_mangle]
pub unsafe extern "C" fn storelib_product_json(handle: *const StorelibHandle) -> *mut c_char {
if handle.is_null() {
return std::ptr::null_mut();
}
let listing = match &(*handle).handler.product_listing {
Some(l) => l,
None => return std::ptr::null_mut(),
};
match serde_json::to_string(listing) {
Ok(json) => cstring_into_raw(json),
Err(_) => std::ptr::null_mut(),
}
}
#[no_mangle]
pub unsafe extern "C" fn storelib_product_title(handle: *const StorelibHandle) -> *mut c_char {
if handle.is_null() {
return std::ptr::null_mut();
}
match (*handle).handler.title() {
Some(s) => cstring_into_raw(s.to_owned()),
None => std::ptr::null_mut(),
}
}
#[no_mangle]
pub unsafe extern "C" fn storelib_product_publisher(handle: *const StorelibHandle) -> *mut c_char {
if handle.is_null() {
return std::ptr::null_mut();
}
match (*handle).handler.publisher_name() {
Some(s) => cstring_into_raw(s.to_owned()),
None => std::ptr::null_mut(),
}
}
#[no_mangle]
pub unsafe extern "C" fn storelib_price_json(handle: *const StorelibHandle) -> *mut c_char {
if handle.is_null() {
return std::ptr::null_mut();
}
match (*handle).handler.price() {
Some(price) => match serde_json::to_string(price) {
Ok(json) => cstring_into_raw(json),
Err(_) => std::ptr::null_mut(),
},
None => std::ptr::null_mut(),
}
}
#[no_mangle]
pub unsafe extern "C" fn storelib_packages_listed_json(
handle: *const StorelibHandle,
) -> *mut c_char {
if handle.is_null() {
return std::ptr::null_mut();
}
match serde_json::to_string((*handle).handler.packages()) {
Ok(json) => cstring_into_raw(json),
Err(_) => std::ptr::null_mut(),
}
}
#[no_mangle]
pub unsafe extern "C" fn storelib_availabilities_json(
handle: *const StorelibHandle,
) -> *mut c_char {
if handle.is_null() {
return std::ptr::null_mut();
}
match serde_json::to_string(&(*handle).handler.availabilities()) {
Ok(json) => cstring_into_raw(json),
Err(_) => std::ptr::null_mut(),
}
}
#[no_mangle]
pub unsafe extern "C" fn storelib_wu_category_id(handle: *const StorelibHandle) -> *mut c_char {
if handle.is_null() {
return std::ptr::null_mut();
}
match (*handle).handler.wu_category_id() {
Some(s) => cstring_into_raw(s.to_owned()),
None => std::ptr::null_mut(),
}
}
#[no_mangle]
pub unsafe extern "C" fn storelib_query_batch_json(
handle: *mut StorelibHandle,
ids: *const *const c_char,
id_count: usize,
auth_token: *const c_char,
) -> *mut c_char {
storelib_query_batch_json_with_cancel(handle, ids, id_count, auth_token, std::ptr::null())
}
#[no_mangle]
pub unsafe extern "C" fn storelib_query_batch_json_with_cancel(
handle: *mut StorelibHandle,
ids: *const *const c_char,
id_count: usize,
auth_token: *const c_char,
cancel: *const StorelibCancellation,
) -> *mut c_char {
if handle.is_null() || ids.is_null() || id_count == 0 {
return std::ptr::null_mut();
}
let h = &mut *handle;
h.clear_error();
let mut owned: Vec<String> = Vec::with_capacity(id_count);
for i in 0..id_count {
let ptr = *ids.add(i);
if ptr.is_null() {
h.set_error("ids[i] is null");
return std::ptr::null_mut();
}
match CStr::from_ptr(ptr).to_str() {
Ok(s) => owned.push(s.to_owned()),
Err(_) => {
h.set_error("ids[i] is not valid UTF-8");
return std::ptr::null_mut();
}
}
}
let id_refs: Vec<&str> = owned.iter().map(String::as_str).collect();
let token = match cstr_opt_to_str(auth_token, h, "auth_token") {
Ok(t) => t,
Err(()) => return std::ptr::null_mut(),
};
let cancel_ref = cancel_token_ref(cancel);
match h.rt.block_on(
h.handler
.query_dcat_batch_with_cancel(&id_refs, token, cancel_ref),
) {
Ok(()) => match serde_json::to_string(h.handler.products()) {
Ok(json) => cstring_into_raw(json),
Err(e) => {
h.set_error(&e.to_string());
std::ptr::null_mut()
}
},
Err(e) => {
h.set_error(&e.to_string());
std::ptr::null_mut()
}
}
}
#[no_mangle]
pub unsafe extern "C" fn storelib_packages_json(
handle: *mut StorelibHandle,
msa_token: *const c_char,
) -> *mut c_char {
storelib_packages_json_with_cancel(handle, msa_token, std::ptr::null())
}
#[no_mangle]
pub unsafe extern "C" fn storelib_search_json(
handle: *mut StorelibHandle,
query: *const c_char,
family: u32,
) -> *mut c_char {
storelib_search_json_with_cancel(handle, query, family, std::ptr::null())
}
pub type StorelibProgressCallback = extern "C" fn(
stage: *const c_char,
message: *const c_char,
has_current: i32,
current: u32,
has_total: i32,
total: u32,
user_data: *mut c_void,
);
#[no_mangle]
pub unsafe extern "C" fn storelib_set_progress_callback(
handle: *mut StorelibHandle,
callback: Option<StorelibProgressCallback>,
user_data: *mut c_void,
) -> i32 {
if handle.is_null() {
return STORELIB_ERR_NULL;
}
let h = &mut *handle;
match callback {
Some(cb) => {
let user_data_addr = user_data as usize;
h.handler.progress.set(Box::new(move |e: ProgressEvent| {
let stage = CString::new(e.stage).unwrap_or_default();
let message = CString::new(e.message).unwrap_or_default();
let (has_c, cur) = match e.current {
Some(v) => (1, v),
None => (0, 0),
};
let (has_t, tot) = match e.total {
Some(v) => (1, v),
None => (0, 0),
};
(cb)(
stage.as_ptr(),
message.as_ptr(),
has_c,
cur,
has_t,
tot,
user_data_addr as *mut c_void,
);
}));
}
None => h.handler.progress.clear(),
}
STORELIB_OK
}
#[no_mangle]
pub unsafe extern "C" fn storelib_clear_progress_callback(handle: *mut StorelibHandle) -> i32 {
if handle.is_null() {
return STORELIB_ERR_NULL;
}
(*handle).handler.progress.clear();
STORELIB_OK
}
pub struct StorelibCancellation {
token: CancellationToken,
}
#[no_mangle]
pub extern "C" fn storelib_cancellation_new() -> *mut StorelibCancellation {
Box::into_raw(Box::new(StorelibCancellation {
token: CancellationToken::new(),
}))
}
#[no_mangle]
pub unsafe extern "C" fn storelib_cancellation_cancel(token: *const StorelibCancellation) {
if !token.is_null() {
(*token).token.cancel();
}
}
#[no_mangle]
pub unsafe extern "C" fn storelib_cancellation_is_cancelled(
token: *const StorelibCancellation,
) -> i32 {
if token.is_null() {
return 0;
}
if (*token).token.is_cancelled() {
1
} else {
0
}
}
#[no_mangle]
pub unsafe extern "C" fn storelib_cancellation_free(token: *mut StorelibCancellation) {
if !token.is_null() {
drop(Box::from_raw(token));
}
}
#[no_mangle]
pub unsafe extern "C" fn storelib_query_with_cancel(
handle: *mut StorelibHandle,
id: *const c_char,
id_type: u32,
auth_token: *const c_char,
cancel: *const StorelibCancellation,
) -> i32 {
if handle.is_null() || id.is_null() {
return STORELIB_ERR_NULL;
}
let h = &mut *handle;
h.clear_error();
let id_str = match cstr_required_to_str(id, h, "id") {
Ok(s) => s,
Err(()) => return STORELIB_ERR_NULL,
};
let token = match cstr_opt_to_str(auth_token, h, "auth_token") {
Ok(t) => t,
Err(()) => return STORELIB_ERR_NULL,
};
let id_enum = id_type_from_u32(id_type);
let cancel_ref = cancel_token_ref(cancel);
match h.rt.block_on(
h.handler
.query_dcat_with_cancel(id_str, id_enum, token, cancel_ref),
) {
Ok(_) => STORELIB_OK,
Err(e) => {
let code = err_code(&e);
h.set_error(&e.to_string());
code
}
}
}
#[no_mangle]
pub unsafe extern "C" fn storelib_packages_json_with_cancel(
handle: *mut StorelibHandle,
msa_token: *const c_char,
cancel: *const StorelibCancellation,
) -> *mut c_char {
if handle.is_null() {
return std::ptr::null_mut();
}
let h = &mut *handle;
h.clear_error();
let token = match cstr_opt_to_str(msa_token, h, "msa_token") {
Ok(t) => t,
Err(()) => return std::ptr::null_mut(),
};
let cancel_ref = cancel_token_ref(cancel);
match h.rt.block_on(
h.handler
.get_packages_for_product_with_cancel(token, cancel_ref),
) {
Ok(pkgs) => json_result_to_cstr(&pkgs, h),
Err(e) => {
h.set_error(&e.to_string());
std::ptr::null_mut()
}
}
}
#[no_mangle]
pub unsafe extern "C" fn storelib_search_json_with_cancel(
handle: *mut StorelibHandle,
query: *const c_char,
family: u32,
cancel: *const StorelibCancellation,
) -> *mut c_char {
if handle.is_null() || query.is_null() {
return std::ptr::null_mut();
}
let h = &mut *handle;
h.clear_error();
let query_str = match cstr_required_to_str(query, h, "query") {
Ok(s) => s,
Err(()) => return std::ptr::null_mut(),
};
let fam = family_from_u32(family);
let cancel_ref = cancel_token_ref(cancel);
match h.rt.block_on(
h.handler
.search_dcat_with_cancel(query_str, fam, cancel_ref),
) {
Ok(results) => json_result_to_cstr(&results, h),
Err(e) => {
h.set_error(&e.to_string());
std::ptr::null_mut()
}
}
}
#[no_mangle]
pub unsafe extern "C" fn storelib_free_string(s: *mut c_char) {
if !s.is_null() {
drop(CString::from_raw(s));
}
}
fn cstring_into_raw(s: String) -> *mut c_char {
match CString::new(s) {
Ok(cs) => cs.into_raw(),
Err(_) => std::ptr::null_mut(),
}
}
unsafe fn cstr_opt_to_str<'a>(
ptr: *const c_char,
h: &mut StorelibHandle,
name: &str,
) -> Result<Option<&'a str>, ()> {
if ptr.is_null() {
return Ok(None);
}
match CStr::from_ptr(ptr).to_str() {
Ok(s) => Ok(Some(s)),
Err(_) => {
h.set_error(&format!("{name} is not valid UTF-8"));
Err(())
}
}
}
unsafe fn cstr_required_to_str<'a>(
ptr: *const c_char,
h: &mut StorelibHandle,
name: &str,
) -> Result<&'a str, ()> {
match CStr::from_ptr(ptr).to_str() {
Ok(s) => Ok(s),
Err(_) => {
h.set_error(&format!("{name} is not valid UTF-8"));
Err(())
}
}
}
unsafe fn cancel_token_ref<'a>(
cancel: *const StorelibCancellation,
) -> Option<&'a CancellationToken> {
if cancel.is_null() {
None
} else {
Some(&(*cancel).token)
}
}
fn json_result_to_cstr<T: serde::Serialize + ?Sized>(
value: &T,
h: &mut StorelibHandle,
) -> *mut c_char {
match serde_json::to_string(value) {
Ok(json) => cstring_into_raw(json),
Err(e) => {
h.set_error(&e.to_string());
std::ptr::null_mut()
}
}
}
fn id_type_from_u32(v: u32) -> IdentifierType {
match v {
1 => IdentifierType::XboxTitleId,
2 => IdentifierType::PackageFamilyName,
3 => IdentifierType::ContentId,
4 => IdentifierType::LegacyWindowsPhoneProductId,
5 => IdentifierType::LegacyWindowsStoreProductId,
6 => IdentifierType::LegacyXboxProductId,
_ => IdentifierType::ProductId,
}
}
fn family_from_u32(v: u32) -> DeviceFamily {
match v {
1 => DeviceFamily::Mobile,
2 => DeviceFamily::Xbox,
3 => DeviceFamily::ServerCore,
4 => DeviceFamily::IotCore,
5 => DeviceFamily::HoloLens,
6 => DeviceFamily::Andromeda,
7 => DeviceFamily::Universal,
8 => DeviceFamily::Wcos,
_ => DeviceFamily::Desktop,
}
}