use std::{
ffi::{c_char, c_void},
sync::{LazyLock, Mutex},
};
use url::Url;
use crate::{Error, Id};
pub static RUNTIME: LazyLock<Mutex<tokio::runtime::Handle>> = LazyLock::new(|| {
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap();
let handle = runtime.handle().clone();
std::thread::Builder::new()
.name("libmoq".into())
.spawn(move || {
runtime.block_on(std::future::pending::<()>());
})
.expect("failed to spawn runtime thread");
Mutex::new(handle)
});
pub fn enter<C: ReturnCode, F: FnOnce() -> C>(f: F) -> i32 {
let handle = RUNTIME.lock().unwrap();
let _guard = handle.enter();
match std::panic::catch_unwind(std::panic::AssertUnwindSafe(f)) {
Ok(ret) => ret.code(),
Err(_) => Error::Panic.code(),
}
}
#[derive(Clone, Copy)]
pub struct OnStatus {
user_data: *mut c_void,
on_status: Option<extern "C" fn(user_data: *mut c_void, code: i32)>,
}
impl OnStatus {
pub unsafe fn new(
user_data: *mut c_void,
on_status: Option<extern "C" fn(user_data: *mut c_void, code: i32)>,
) -> Self {
Self { user_data, on_status }
}
pub fn call<C: ReturnCode>(&self, ret: C) {
if let Some(on_status) = &self.on_status {
on_status(self.user_data, ret.code());
}
}
}
unsafe impl Send for OnStatus {}
pub trait ReturnCode {
fn code(&self) -> i32;
}
impl ReturnCode for () {
fn code(&self) -> i32 {
0
}
}
impl ReturnCode for i32 {
fn code(&self) -> i32 {
*self
}
}
impl ReturnCode for Result<i32, Error> {
fn code(&self) -> i32 {
match self {
Ok(code) if *code < 0 => Error::InvalidCode.code(),
Ok(code) => *code,
Err(e) => e.code(),
}
}
}
impl ReturnCode for Result<usize, Error> {
fn code(&self) -> i32 {
match self {
Ok(code) => i32::try_from(*code).unwrap_or_else(|_| Error::InvalidCode.code()),
Err(e) => e.code(),
}
}
}
impl ReturnCode for Result<Id, Error> {
fn code(&self) -> i32 {
match self {
Ok(id) => i32::from(*id),
Err(e) => e.code(),
}
}
}
impl ReturnCode for Result<(), Error> {
fn code(&self) -> i32 {
match self {
Ok(()) => 0,
Err(e) => e.code(),
}
}
}
impl ReturnCode for usize {
fn code(&self) -> i32 {
i32::try_from(*self).unwrap_or_else(|_| Error::InvalidCode.code())
}
}
impl ReturnCode for Id {
fn code(&self) -> i32 {
i32::from(*self)
}
}
pub fn parse_id(id: u32) -> Result<Id, Error> {
Id::try_from(id)
}
pub fn parse_id_optional(id: u32) -> Result<Option<Id>, Error> {
match id {
0 => Ok(None),
id => Ok(Some(parse_id(id)?)),
}
}
pub fn parse_url(url: *const c_char, url_len: usize) -> Result<Url, Error> {
let url = unsafe { parse_str(url, url_len)? };
Ok(Url::parse(url)?)
}
pub unsafe fn parse_str<'a>(cstr: *const c_char, cstr_len: usize) -> Result<&'a str, Error> {
let slice = unsafe { parse_slice(cstr as *const u8, cstr_len)? };
let string = std::str::from_utf8(slice)?;
Ok(string)
}
pub unsafe fn parse_slice<'a>(data: *const u8, size: usize) -> Result<&'a [u8], Error> {
if data.is_null() {
if size == 0 {
return Ok(&[]);
}
return Err(Error::InvalidPointer);
}
let data = unsafe { std::slice::from_raw_parts(data, size) };
Ok(data)
}