use crate::{
fmx_Data, fmx_DataVect, fmx_ExprEnv, fmx_ExtPluginType, fmx__fmxcpt, fmx_ptrtype,
write_to_u16_buff, AllowedVersions, ApplicationVersion, Data, DataVect, ExprEnv,
ExternStringType, ExternVersion, FMError, FM_ExprEnv_RegisterExternalFunctionEx,
FM_ExprEnv_RegisterScriptStep, FM_ExprEnv_UnRegisterExternalFunction,
FM_ExprEnv_UnRegisterScriptStep, QuadChar, Text,
};
use widestring::U16CStr;
pub trait Plugin {
fn id() -> &'static [u8; 4];
fn name() -> &'static str;
fn description() -> &'static str;
fn url() -> &'static str;
fn register_functions() -> Vec<Registration>;
fn enable_configure_button() -> bool {
false
}
fn enable_init_and_shutdown() -> bool {
true
}
fn enable_idle() -> bool {
false
}
fn enable_file_and_session_shutdown() -> bool {
false
}
fn session_shutdown(_session_id: fmx_ptrtype) {}
fn file_shutdown(_session_id: fmx_ptrtype, _file_id: fmx_ptrtype) {}
fn preferences() {}
fn idle(_session_id: fmx_ptrtype) {}
fn not_idle(_session_id: fmx_ptrtype) {}
fn script_paused(_session_id: fmx_ptrtype) {}
fn script_running(_session_id: fmx_ptrtype) {}
fn un_safe(_session_id: fmx_ptrtype) {}
}
pub trait PluginInternal<T>
where
T: Plugin,
{
fn get_string(
which_string: ExternStringType,
_win_lang_id: u32,
out_buffer_size: u32,
out_buffer: *mut u16,
) {
use ExternStringType::*;
let string = match which_string {
Name => T::name().to_string(),
AppConfig => T::description().to_string(),
Options => {
let mut options: String = ::std::str::from_utf8(T::id()).unwrap().to_string();
options.push('1');
options.push(if T::enable_configure_button() {
'Y'
} else {
'n'
});
options.push('n');
options.push(if T::enable_init_and_shutdown() {
'Y'
} else {
'n'
});
options.push(if T::enable_idle() { 'Y' } else { 'n' });
options.push(if T::enable_file_and_session_shutdown() {
'Y'
} else {
'n'
});
options.push('n');
options
}
HelpUrl => T::url().to_string(),
Blank => "".to_string(),
};
unsafe { write_to_u16_buff(out_buffer, out_buffer_size, &string) }
}
fn initialize(
ext_version: ExternVersion,
app_version: ApplicationVersion,
app_version_number: fmx_ptrtype,
) -> ExternVersion {
let plugin_id = QuadChar::new(T::id());
for f in T::register_functions() {
if ext_version < f.min_ext_version()
|| !f.is_fm_version_allowed(&app_version)
|| !is_version_high_enough(app_version_number, f.min_fm_version())
{
continue;
}
if f.register(&plugin_id) != FMError::NoError {
return ExternVersion::DoNotEnable;
}
}
ExternVersion::V190
}
fn shutdown(version: ExternVersion) {
let plugin_id = QuadChar::new(T::id());
for f in T::register_functions() {
if version < f.min_ext_version() {
continue;
}
f.unregister(&plugin_id);
}
}
}
fn is_version_high_enough(app_version_number: fmx_ptrtype, min_version: &str) -> bool {
let string = unsafe { U16CStr::from_ptr_str(app_version_number as *const u16) };
let string = string.to_string_lossy();
let version_number = string.split(' ').last().unwrap();
let (major, minor, patch) = semantic_version(version_number);
let (min_major, min_minor, min_patch) = semantic_version(min_version);
match (major, min_major, minor, min_minor, patch, min_patch) {
(None, None, ..) => false,
(Some(major), Some(min_major), ..) if major < min_major => false,
(Some(major), Some(min_major), ..) if major > min_major => true,
(Some(major), Some(min_major), _, None, ..) if major == min_major => true,
(.., Some(minor), Some(min_minor), _, _) if minor < min_minor => false,
(.., Some(minor), Some(min_minor), _, _) if minor > min_minor => true,
(.., Some(minor), Some(min_minor), _, None) if minor == min_minor => true,
(.., Some(patch), Some(min_patch)) if patch < min_patch => false,
(.., Some(patch), Some(min_patch)) if patch > min_patch => true,
_ => true,
}
}
fn semantic_version(version: &str) -> (Option<u8>, Option<u8>, Option<u8>) {
let mut str_vec = version.split('.');
let major = str_vec.next();
let minor = str_vec.next();
let patch = str_vec.next();
(
match major {
Some(n) => n.parse::<u8>().ok(),
None => None,
},
match minor {
Some(n) => n.parse::<u8>().ok(),
None => None,
},
match patch {
Some(n) => n.parse::<u8>().ok(),
None => None,
},
)
}
#[macro_export]
macro_rules! register_plugin {
($x:ident) => {
#[no_mangle]
pub static mut gfmx_ExternCallPtr: *mut fmx_ExternCallStruct = std::ptr::null_mut();
#[no_mangle]
unsafe extern "C" fn FMExternCallProc(pb: *mut fmx_ExternCallStruct) {
gfmx_ExternCallPtr = pb;
use FMExternCallType::*;
match (*pb).whichCall {
Init => {
(*pb).result = $x::initialize(
(*pb).extnVersion,
ApplicationVersion::from((*pb).parm1),
(*pb).parm2,
) as u64
}
Idle => {
use IdleType::*;
match IdleType::from((*pb).parm1) {
Idle => $x::idle((*pb).parm2),
NotIdle => $x::not_idle((*pb).parm2),
ScriptPaused => $x::script_paused((*pb).parm2),
ScriptRunning => $x::script_running((*pb).parm2),
Unsafe => $x::un_safe((*pb).parm2),
}
}
Shutdown => $x::shutdown((*pb).extnVersion),
AppPrefs => $x::preferences(),
GetString => $x::get_string(
(*pb).parm1.into(),
(*pb).parm2 as u32,
(*pb).parm3 as u32,
(*pb).result as *mut u16,
),
SessionShutdown => $x::session_shutdown((*pb).parm2),
FileShutdown => $x::file_shutdown((*pb).parm2, (*pb).parm3),
}
}
impl PluginInternal<$x> for $x {}
pub fn execute_filemaker_script<F, S>(
file_name: F,
script_name: S,
control: ScriptControl,
parameter: Option<Data>,
) -> FMError
where
F: ToText,
S: ToText,
{
unsafe {
(*gfmx_ExternCallPtr).execute_filemaker_script(
file_name,
script_name,
control,
parameter,
)
}
}
lazy_static! {
static ref GLOBAL_STATE: RwLock<HashMap<String, String>> = RwLock::new(HashMap::new());
}
pub fn store_state(key: &str, value: &str) {
let mut hmap = GLOBAL_STATE.write().unwrap();
(*hmap).insert(String::from(key), String::from(value));
}
pub fn get_state(key: &str) -> Option<String> {
let hmap = GLOBAL_STATE.read().unwrap();
(*hmap).get(key).cloned()
}
};
}
pub enum Registration {
Function {
id: i16,
name: &'static str,
definition: &'static str,
description: &'static str,
min_args: i16,
max_args: i16,
display_in_dialogs: bool,
compatibility_flags: u32,
min_ext_version: ExternVersion,
min_fm_version: &'static str,
allowed_versions: AllowedVersions,
function_ptr: fmx_ExtPluginType,
},
ScriptStep {
id: i16,
name: &'static str,
definition: &'static str,
description: &'static str,
display_in_dialogs: bool,
compatibility_flags: u32,
min_ext_version: ExternVersion,
min_fm_version: &'static str,
allowed_versions: AllowedVersions,
function_ptr: fmx_ExtPluginType,
},
}
impl Registration {
pub fn register(&self, plugin_id: &QuadChar) -> FMError {
let mut _x = fmx__fmxcpt::new();
let (id, n, desc, def, display, flags, func_ptr) = match *self {
Registration::Function {
id,
name,
description,
definition,
display_in_dialogs,
compatibility_flags,
function_ptr,
..
} => (
id,
name,
description,
definition,
display_in_dialogs,
compatibility_flags,
function_ptr,
),
Registration::ScriptStep {
id,
name,
description,
definition,
display_in_dialogs,
compatibility_flags,
function_ptr,
..
} => (
id,
name,
description,
definition,
display_in_dialogs,
compatibility_flags,
function_ptr,
),
};
let mut name = Text::new();
name.assign(n);
let mut description = Text::new();
description.assign(desc);
let mut definition = Text::new();
definition.assign(def);
let flags = if display { 0x0000FF00 } else { 0 } | flags;
let error = match self {
Registration::Function {
min_args, max_args, ..
} => unsafe {
FM_ExprEnv_RegisterExternalFunctionEx(
plugin_id.ptr,
id,
name.ptr,
definition.ptr,
description.ptr,
*min_args,
*max_args,
flags,
func_ptr,
&mut _x,
)
},
Registration::ScriptStep { .. } => unsafe {
FM_ExprEnv_RegisterScriptStep(
plugin_id.ptr,
id,
name.ptr,
definition.ptr,
description.ptr,
flags,
func_ptr,
&mut _x,
)
},
};
_x.check();
error
}
pub fn min_ext_version(&self) -> ExternVersion {
match self {
Registration::Function {
min_ext_version, ..
} => *min_ext_version,
Registration::ScriptStep {
min_ext_version, ..
} => *min_ext_version,
}
}
pub fn min_fm_version(&self) -> &str {
match self {
Registration::Function { min_fm_version, .. }
| Registration::ScriptStep { min_fm_version, .. } => *min_fm_version,
}
}
pub fn is_fm_version_allowed(&self, version: &ApplicationVersion) -> bool {
let allowed_versions = match self {
Registration::Function {
allowed_versions, ..
}
| Registration::ScriptStep {
allowed_versions, ..
} => allowed_versions,
};
use ApplicationVersion::*;
match version {
Developer => allowed_versions.developer,
Pro => allowed_versions.pro,
Runtime => allowed_versions.runtime,
SASE => allowed_versions.sase,
Web => allowed_versions.web,
}
}
pub fn unregister(&self, plugin_id: &QuadChar) {
let mut _x = fmx__fmxcpt::new();
match self {
Registration::Function { id, .. } => unsafe {
FM_ExprEnv_UnRegisterExternalFunction(plugin_id.ptr, *id, &mut _x);
},
Registration::ScriptStep { id, .. } => unsafe {
FM_ExprEnv_UnRegisterScriptStep(plugin_id.ptr, *id, &mut _x);
},
}
_x.check();
}
}
pub trait FileMakerFunction {
fn function(id: i16, env: &ExprEnv, args: &DataVect, result: &mut Data) -> FMError;
extern "C" fn extern_func(
id: i16,
env_ptr: *const fmx_ExprEnv,
args_ptr: *const fmx_DataVect,
result_ptr: *mut fmx_Data,
) -> FMError {
let arguments = DataVect::from_ptr(args_ptr);
let env = ExprEnv::from_ptr(env_ptr);
let mut result = Data::from_ptr(result_ptr);
Self::function(id, &env, &arguments, &mut result)
}
}