aes_externalobj 0.1.2

ExtendScript external object library implementation in Rust
Documentation
#![allow(unused_macros)]

use std::ffi::{c_void, CString, c_long};
use std::sync::Once;
//use libc::strdup;
use crate::bindings::*;
use aes_types::*;
use crate::server_interface::ServerInterface;

static INIT: Once = Once::new();

pub(crate) static mut CLIENT_DATA: Option<ClientData> = None;
/// All functions need to be coded in the same way, following the function
/// definition below. ExtendScript passes in an array of arguments as VariantData,
/// and supplies a VariantData element preset to Undefined for the return value.
/// You should return any error code. If the function succeeds, the return value
/// is kESErrOK.




#[repr(C)]
#[repr(align(8))]
pub(crate) struct ClientData {
    pub(crate) server: SoHServer,
    pub(crate) server_interface: *mut SoServerInterface,
}

unsafe impl Send for ClientData {}
unsafe impl Sync for ClientData {}
impl Clone for ClientData {
    fn clone(&self) -> Self {
        ClientData {
            server: self.server,
            server_interface: self.server_interface,
        }
    }
}




// Macro for defining ExtendScript functions with argument validation
#[macro_export]
macro_rules! es_function {
    ($name:ident, $expected_argc:expr, $body:expr) => {
        #[no_mangle]
        pub extern "C" fn $name(argv: *mut TaggedData, argc: c_long, retval: *mut TaggedData) -> c_long {
            if !validate_args(argv, argc, $expected_argc) {
                return make_error_result(ES_ERR_BAD_ARGUMENT_LIST, retval);
            }

            unsafe {
                let args = std::slice::from_raw_parts(argv, argc as usize);
                $body(args, retval)
            }
        }
    };
}

// Macro for getting string arguments with error handling
#[macro_export]
macro_rules! get_string_arg {
    ($args:expr, $index:expr, $retval:expr) => {
        match { get_string_from_tagged_data(&$args[$index]) } {
            Some(s) => s,
            None => return make_error_result(ES_ERR_BAD_ARGUMENT_LIST, $retval)
        }
    };
}

// Macro for getting integer arguments with error handling
#[macro_export]
macro_rules! get_integer_arg {
    ($args:expr, $index:expr, $retval:expr) => {
        match { get_integer_from_tagged_data(&$args[$index]) } {
            Some(i) => i,
            None => return make_error_result(ES_ERR_BAD_ARGUMENT_LIST, $retval)
        }
    };
}

// Macro for getting boolean arguments with error handling
#[macro_export]
macro_rules! get_bool_arg {
    ($args:expr, $index:expr, $retval:expr) => {
        match { get_bool_from_tagged_data(&$args[$index]) } {
            Some(b) => b,
            None => return make_error_result(ES_ERR_BAD_ARGUMENT_LIST, $retval)
        }
    };
}

// Macro for getting live object arguments with error handling
#[macro_export]
macro_rules! get_live_object_arg {
    ($args:expr, $index:expr, $retval:expr) => {
        match { get_live_object_from_tagged_data(&$args[$index]) } {
            Some(obj) => obj,
            None => return make_error_result(ES_ERR_BAD_ARGUMENT_LIST, $retval)
        }
    };
}

#[macro_export]
macro_rules! get_live_object_from_data {
    ($data:expr, $retval:expr) => {
        match { get_live_object_from_tagged_data($data) } {
            Some(obj) => obj,
            None => return make_error_result(ES_ERR_BAD_ARGUMENT_LIST, $retval)
        }
    };
}

// Macro for getting client data with error handling
#[macro_export]
macro_rules! get_client_data {
    () => {
        match { CLIENT_DATA.as_ref() } {
            Some(data) => data,
            None => return make_error_result(ES_ERR_NO_MEMORY, retval)
        }
    };
}

// Server interface macros
#[macro_export]
macro_rules! get_server_interface {
    ($retval:expr) => {
        match { CLIENT_DATA.as_ref().and_then(|data| data.server_interface.as_ref()) } {
            Some(interface) => interface,
            None => return make_error_result(ES_ERR_NO_MEMORY, $retval)
        }
    };
}

#[macro_export]
macro_rules! get_server {
    ($retval:expr) => {
        match { CLIENT_DATA.as_ref() } {
            Some(data) => data.server,
            None => return make_error_result(ES_ERR_NO_MEMORY, $retval)
        }
    };
}

#[macro_export]
macro_rules! call_server_interface {
    ($interface:expr, $func:ident, $($arg:expr),*) => {
        ($interface.$func)($($arg),*)
    };
}

#[macro_export]
macro_rules! tagged_data_free {
    ($server:expr, $interface:expr, $data:expr) => {
        let _ = call_server_interface!($interface, taggedDataFree, $server, $data);
    };
}

// Helper functions for working with TaggedData


// Safe wrapper for getting values from TaggedData
pub unsafe fn get_string_from_tagged_data(data: &TaggedData) -> Option<String> {
    if data.type_ == TYPE_STRING as c_long {
        let c_str = std::ffi::CStr::from_ptr(data.data.string);
        c_str.to_str().ok().map(|s| s.to_string())
    } else {
        None
    }
}

pub unsafe fn get_bool_from_tagged_data(data: &TaggedData) -> Option<bool> {
    if data.type_ == TYPE_BOOL as c_long {
        Some(data.data.intval != 0)
    } else {
        None
    }
}

pub unsafe fn get_integer_from_tagged_data(data: &TaggedData) -> Option<i32> {
    if data.type_ == TYPE_INTEGER as c_long {
        Some(data.data.intval)
    } else {
        None
    }
}

pub unsafe fn get_double_from_tagged_data(data: &TaggedData) -> Option<f64> {
    if data.type_ == TYPE_DOUBLE as c_long {
        Some(data.data.fltval)
    } else {
        None
    }
}

pub unsafe fn get_live_object_from_tagged_data(data: &TaggedData) -> Option<*mut c_long> {

    println!("data.type_ = {}", data.type_);

    if data.type_ == TYPE_LIVE_OBJECT as c_long || data.type_ == TYPE_LIVE_OBJECT_RELEASE as c_long {
        Some(data.data.hObject)
    } else {
        None
    }
}

// Helper function to get client data from object

unsafe fn get_client_data<T>(h_object: SoHObject) -> Option<&'static mut T> {
    let mut ptr: *mut c_void = std::ptr::null_mut();
    let client_data = CLIENT_DATA.as_ref()?;
    let server_interface = client_data.server_interface.as_ref()?;

    if (server_interface.getClientData)(h_object, &mut ptr) != ES_ERR_OK {
        return None;
    }

    if ptr.is_null() {
        return None;
    }

    Some(&mut *(ptr as *mut T))
}

// Error handling helpers
pub fn make_error_result(error: i32, retval: *mut TaggedData) -> c_long {
    unsafe {
        *retval = TaggedData::new_undefined();
    }
    error as c_long
}

pub fn make_success_result<T: Into<TaggedData>>(value: T, retval: *mut TaggedData) -> c_long {
    unsafe {
        *retval = value.into();
    }
    ES_ERR_OK as c_long
}

// Validation helpers
pub fn validate_args(argv: *mut TaggedData, argc: c_long, expected_argc: c_long) -> bool {
    !argv.is_null() && argc == expected_argc
}

// Script evaluation helper
pub unsafe fn eval_script(script: &str, retval: *mut TaggedData) -> c_long {
    *retval = TaggedData::new_script(script);
    ES_ERR_OK as c_long
}

/// Returns an initialized ServerInterface if available
pub fn get_server_interface() -> Option<ServerInterface<'static>> {
    unsafe {
        CLIENT_DATA.as_ref().map(|data| {
            ServerInterface::new(
                data.server_interface.as_ref().unwrap(),
                data.server
            )
        })
    }
}