mod server; mod bridge; mod events; mod logging; mod db; use std::ffi::{c_char, c_int, CStr, CString}; // --- Server --- #[unsafe(no_mangle)] pub extern "C" fn rgl_start(port: c_int) -> c_int { match server::start(port as u16) { Ok(()) => 0, Err(_) => -1, } } #[unsafe(no_mangle)] pub extern "C" fn rgl_stop() { server::stop(); } // --- Logging --- #[unsafe(no_mangle)] pub extern "C" fn rgl_log_init(path: *const c_char) { let path = unsafe { CStr::from_ptr(path) }.to_str().unwrap_or(""); logging::init(path); } #[unsafe(no_mangle)] pub extern "C" fn rgl_log(level: *const c_char, tag: *const c_char, msg: *const c_char) { let level = unsafe { CStr::from_ptr(level) }.to_str().unwrap_or("INFO"); let tag = unsafe { CStr::from_ptr(tag) }.to_str().unwrap_or(""); let msg_bytes = unsafe { CStr::from_ptr(msg) }.to_bytes(); let msg = events::win1251_to_utf8(msg_bytes); logging::log(level, tag, &msg); } // --- Database --- #[unsafe(no_mangle)] pub extern "C" fn rgl_db_init(path: *const c_char) { let path = unsafe { CStr::from_ptr(path) }.to_str().unwrap_or(""); db::init(path); } #[unsafe(no_mangle)] pub extern "C" fn rgl_db_get(key: *const c_char) -> *mut c_char { let key = unsafe { CStr::from_ptr(key) }.to_str().unwrap_or(""); match db::get(key) { Some(v) => CString::new(v).unwrap_or_default().into_raw(), None => std::ptr::null_mut(), } } #[unsafe(no_mangle)] pub extern "C" fn rgl_db_set(key: *const c_char, value: *const c_char) -> c_int { let key = unsafe { CStr::from_ptr(key) }.to_str().unwrap_or(""); let value = unsafe { CStr::from_ptr(value) }.to_str().unwrap_or(""); if db::set(key, value) { 0 } else { -1 } } #[unsafe(no_mangle)] pub extern "C" fn rgl_db_delete(key: *const c_char) -> c_int { let key = unsafe { CStr::from_ptr(key) }.to_str().unwrap_or(""); if db::delete(key) { 0 } else { -1 } } /// List all key-value pairs with prefix as JSON. Caller must free. #[unsafe(no_mangle)] pub extern "C" fn rgl_db_list(prefix: *const c_char) -> *mut c_char { let prefix = unsafe { CStr::from_ptr(prefix) }.to_str().unwrap_or(""); let json = db::list_keys_json(prefix); CString::new(json).unwrap_or_default().into_raw() } /// Delete all keys with prefix. Returns count deleted. #[unsafe(no_mangle)] pub extern "C" fn rgl_db_delete_prefix(prefix: *const c_char) -> c_int { let prefix = unsafe { CStr::from_ptr(prefix) }.to_str().unwrap_or(""); db::delete_prefix(prefix) as c_int } /// Submit async batch. Returns request id instantly. #[unsafe(no_mangle)] pub extern "C" fn rgl_db_submit(ops: *const c_char) -> u32 { let ops = unsafe { CStr::from_ptr(ops) }.to_str().unwrap_or("[]"); db::submit_batch(ops) } /// Poll batch result. Returns JSON string if ready (caller must free), null if pending. #[unsafe(no_mangle)] pub extern "C" fn rgl_db_poll(id: u32) -> *mut c_char { match db::poll_result(id) { Some(r) => CString::new(r).unwrap_or_default().into_raw(), None => std::ptr::null_mut(), } } // --- Modules --- #[unsafe(no_mangle)] pub extern "C" fn rgl_register_module(name: *const c_char, static_dir: *const c_char) { let name = unsafe { CStr::from_ptr(name) }.to_str().unwrap_or(""); let dir = unsafe { CStr::from_ptr(static_dir) }.to_str().unwrap_or(""); server::register_module(name, dir); } /// Register a chat command (for tracking/web visibility). #[unsafe(no_mangle)] pub extern "C" fn rgl_register_command(name: *const c_char, owner: *const c_char) { let name = unsafe { CStr::from_ptr(name) }.to_str().unwrap_or(""); let owner = unsafe { CStr::from_ptr(owner) }.to_str().unwrap_or(""); server::register_command(name, owner); } /// Get registered commands as JSON. Caller must free. #[unsafe(no_mangle)] pub extern "C" fn rgl_get_commands() -> *mut c_char { let json = server::get_commands_json(); CString::new(json).unwrap_or_default().into_raw() } // --- Events --- #[unsafe(no_mangle)] pub extern "C" fn rgl_push_event( event_name: *const c_char, json_args: *const c_char, ) -> *mut c_char { let name = unsafe { CStr::from_ptr(event_name) }.to_str().unwrap_or(""); let args_bytes = unsafe { CStr::from_ptr(json_args) }.to_bytes(); let args = events::win1251_to_utf8(args_bytes); match bridge::push_event(name, &args) { Some(response) => CString::new(response).unwrap_or_default().into_raw(), None => std::ptr::null_mut(), } } // --- Bridge --- #[unsafe(no_mangle)] pub extern "C" fn rgl_poll() -> *mut c_char { match bridge::poll_requests() { Some(json) => CString::new(json).unwrap_or_default().into_raw(), None => std::ptr::null_mut(), } } #[unsafe(no_mangle)] pub extern "C" fn rgl_respond(request_id: c_int, result_json: *const c_char) { let result = unsafe { CStr::from_ptr(result_json) }.to_str().unwrap_or(""); bridge::respond(request_id as u32, result); } #[unsafe(no_mangle)] pub extern "C" fn rgl_free(s: *mut c_char) { if !s.is_null() { unsafe { drop(CString::from_raw(s)) }; } } #[unsafe(no_mangle)] pub extern "C" fn rgl_hello() -> c_int { 42 }