diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..6ea9261 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,14 @@ +{ + "permissions": { + "allow": [ + "Bash(rustc --version:*)", + "Bash(cargo:*)", + "Bash(protoc:*)", + "Bash(cmd.exe /c \"rustc --version\")", + "Bash(cmd.exe /c \"cargo --version\")", + "Bash(python -m build:*)", + "Bash(cmd.exe /c \"cd /d c:\\\\dev\\\\Customer\\\\openiap\\\\rustapi\\\\python && python -m build\")", + "Bash(cmd.exe /c \"cd /d c:\\\\dev\\\\Customer\\\\openiap\\\\rustapi\\\\python && python -m build 2>&1\")" + ] + } +} diff --git a/crates/clib/clib_openiap.h b/crates/clib/clib_openiap.h index e754bb0..6906f39 100644 --- a/crates/clib/clib_openiap.h +++ b/crates/clib/clib_openiap.h @@ -629,6 +629,112 @@ typedef struct InvokeOpenRPARequestWrapper { int32_t request_id; } InvokeOpenRPARequestWrapper; +/** + * AceWrapper is a C-compatible wrapper for Access Control Entry (Ace) + */ +typedef struct AceWrapper { + const char *id; + bool deny; + int32_t rights; +} AceWrapper; + +/** + * WorkItemQueueWrapper is a C-compatible wrapper for WorkItemQueue + */ +typedef struct WorkItemQueueWrapper { + const char *workflowid; + const char *robotqueue; + const char *amqpqueue; + const char *projectid; + const char *usersrole; + int32_t maxretries; + int32_t retrydelay; + int32_t initialdelay; + const char *success_wiqid; + const char *failed_wiqid; + const char *success_wiq; + const char *failed_wiq; + const char *id; + const char *name; + const char *packageid; + const struct AceWrapper *const *acl; + int32_t acl_len; + int32_t request_id; +} WorkItemQueueWrapper; + +/** + * AddWorkItemQueueResponseWrapper is a C-compatible wrapper for AddWorkItemQueueResponse + */ +typedef struct AddWorkItemQueueResponseWrapper { + bool success; + const char *error; + const struct WorkItemQueueWrapper *workitemqueue; + int32_t request_id; +} AddWorkItemQueueResponseWrapper; + +/** + * AddWorkItemQueueRequestWrapper is a C-compatible wrapper for AddWorkItemQueueRequest + */ +typedef struct AddWorkItemQueueRequestWrapper { + const struct WorkItemQueueWrapper *workitemqueue; + bool skiprole; + int32_t request_id; +} AddWorkItemQueueRequestWrapper; + +/** + * Callback type for add_workitem_queue_async + */ +typedef void (*AddWorkItemQueueCallback)(struct AddWorkItemQueueResponseWrapper *wrapper); + +/** + * UpdateWorkItemQueueResponseWrapper is a C-compatible wrapper for UpdateWorkItemQueueResponse + */ +typedef struct UpdateWorkItemQueueResponseWrapper { + bool success; + const char *error; + const struct WorkItemQueueWrapper *workitemqueue; + int32_t request_id; +} UpdateWorkItemQueueResponseWrapper; + +/** + * UpdateWorkItemQueueRequestWrapper is a C-compatible wrapper for UpdateWorkItemQueueRequest + */ +typedef struct UpdateWorkItemQueueRequestWrapper { + const struct WorkItemQueueWrapper *workitemqueue; + bool skiprole; + bool purge; + int32_t request_id; +} UpdateWorkItemQueueRequestWrapper; + +/** + * Callback type for update_workitem_queue_async + */ +typedef void (*UpdateWorkItemQueueCallback)(struct UpdateWorkItemQueueResponseWrapper *wrapper); + +/** + * DeleteWorkItemQueueResponseWrapper is a C-compatible wrapper for DeleteWorkItemQueueResponse + */ +typedef struct DeleteWorkItemQueueResponseWrapper { + bool success; + const char *error; + int32_t request_id; +} DeleteWorkItemQueueResponseWrapper; + +/** + * DeleteWorkItemQueueRequestWrapper is a C-compatible wrapper for DeleteWorkItemQueueRequest + */ +typedef struct DeleteWorkItemQueueRequestWrapper { + const char *wiq; + const char *wiqid; + bool purge; + int32_t request_id; +} DeleteWorkItemQueueRequestWrapper; + +/** + * Callback type for delete_workitem_queue_async + */ +typedef void (*DeleteWorkItemQueueCallback)(struct DeleteWorkItemQueueResponseWrapper *wrapper); + void error(const char *message); void info(const char *message); @@ -1009,3 +1115,62 @@ struct InvokeOpenRPAResponseWrapper *invoke_openrpa(struct ClientWrapper *client int32_t timeout); void free_invoke_openrpa_response(struct InvokeOpenRPAResponseWrapper *response); + +/** + * Synchronous add_workitem_queue FFI function + */ +struct AddWorkItemQueueResponseWrapper *add_workitem_queue(struct ClientWrapper *client, + struct AddWorkItemQueueRequestWrapper *options); + +/** + * Asynchronous add_workitem_queue FFI function + */ +void add_workitem_queue_async(struct ClientWrapper *client, + struct AddWorkItemQueueRequestWrapper *options, + AddWorkItemQueueCallback callback); + +/** + * Free AddWorkItemQueueResponseWrapper + */ +void free_add_workitem_queue_response(struct AddWorkItemQueueResponseWrapper *response); + +/** + * Free WorkItemQueueWrapper + */ +void free_workitem_queue_wrapper(struct WorkItemQueueWrapper *wrapper); + +/** + * Synchronous update_workitem_queue FFI function + */ +struct UpdateWorkItemQueueResponseWrapper *update_workitem_queue(struct ClientWrapper *client, + struct UpdateWorkItemQueueRequestWrapper *options); + +/** + * Asynchronous update_workitem_queue FFI function + */ +void update_workitem_queue_async(struct ClientWrapper *client, + struct UpdateWorkItemQueueRequestWrapper *options, + UpdateWorkItemQueueCallback callback); + +/** + * Free UpdateWorkItemQueueResponseWrapper + */ +void free_update_workitem_queue_response(struct UpdateWorkItemQueueResponseWrapper *response); + +/** + * Synchronous delete_workitem_queue FFI function + */ +struct DeleteWorkItemQueueResponseWrapper *delete_workitem_queue(struct ClientWrapper *client, + struct DeleteWorkItemQueueRequestWrapper *options); + +/** + * Asynchronous delete_workitem_queue FFI function + */ +void delete_workitem_queue_async(struct ClientWrapper *client, + struct DeleteWorkItemQueueRequestWrapper *options, + DeleteWorkItemQueueCallback callback); + +/** + * Free DeleteWorkItemQueueResponseWrapper + */ +void free_delete_workitem_queue_response(struct DeleteWorkItemQueueResponseWrapper *response); diff --git a/crates/clib/src/lib.rs b/crates/clib/src/lib.rs index f827c8a..5a38c39 100644 --- a/crates/clib/src/lib.rs +++ b/crates/clib/src/lib.rs @@ -7,6 +7,7 @@ use openiap_client::openiap::{ QueryRequest, SigninRequest, UploadRequest, WatchEvent, WatchRequest, }; use openiap_client::{Client, ClientEvent, CreateCollectionRequest, CreateIndexRequest, CustomCommandRequest, DeleteManyRequest, DeleteOneRequest, DeleteWorkitemRequest, DropCollectionRequest, DropIndexRequest, GetIndexesRequest, InsertManyRequest, InsertOrUpdateOneRequest, InvokeOpenRpaRequest, PopWorkitemRequest, PushWorkitemRequest, QueueEvent, QueueMessageRequest, RegisterExchangeRequest, RegisterQueueRequest, Timestamp, UpdateOneRequest, UpdateWorkitemRequest, Workitem, WorkitemFile}; +use openiap_client::openiap::{Ace, AddWorkItemQueueRequest, UpdateWorkItemQueueRequest, DeleteWorkItemQueueRequest, WorkItemQueue}; #[cfg(all(test, not(windows)))] mod tests; @@ -7805,3 +7806,868 @@ pub extern "C" fn free_invoke_openrpa_response(response: *mut InvokeOpenRPARespo let _ = Box::from_raw(response); } } + +// ============================================================================ +// WorkItemQueue FFI Wrappers +// ============================================================================ + +/// AceWrapper is a C-compatible wrapper for Access Control Entry (Ace) +#[repr(C)] +#[derive(Debug, Clone)] +pub struct AceWrapper { + pub id: *const c_char, + pub deny: bool, + pub rights: i32, +} + +/// WorkItemQueueWrapper is a C-compatible wrapper for WorkItemQueue +#[repr(C)] +#[derive(Debug, Clone)] +pub struct WorkItemQueueWrapper { + pub workflowid: *const c_char, + pub robotqueue: *const c_char, + pub amqpqueue: *const c_char, + pub projectid: *const c_char, + pub usersrole: *const c_char, + pub maxretries: i32, + pub retrydelay: i32, + pub initialdelay: i32, + pub success_wiqid: *const c_char, + pub failed_wiqid: *const c_char, + pub success_wiq: *const c_char, + pub failed_wiq: *const c_char, + pub id: *const c_char, + pub name: *const c_char, + pub packageid: *const c_char, + pub acl: *const *const AceWrapper, // Array of ACE pointers + pub acl_len: i32, // Length of ACL array + pub request_id: i32, +} + +fn wrap_workitem_queue(wiq: WorkItemQueue) -> WorkItemQueueWrapper { + // Convert ACL entries to AceWrapper pointers + let acl_wrappers: Vec<*const AceWrapper> = wiq.acl.iter().map(|ace| { + Box::into_raw(Box::new(AceWrapper { + id: CString::new(ace.id.clone()).unwrap().into_raw(), + deny: ace.deny, + rights: ace.rights, + })) as *const AceWrapper + }).collect(); + + let acl_len = acl_wrappers.len() as i32; + let acl_ptr = if acl_wrappers.is_empty() { + std::ptr::null() + } else { + let boxed_slice = acl_wrappers.into_boxed_slice(); + let ptr = boxed_slice.as_ptr(); + std::mem::forget(boxed_slice); + ptr + }; + + WorkItemQueueWrapper { + workflowid: CString::new(wiq.workflowid).unwrap().into_raw(), + robotqueue: CString::new(wiq.robotqueue).unwrap().into_raw(), + amqpqueue: CString::new(wiq.amqpqueue).unwrap().into_raw(), + projectid: CString::new(wiq.projectid).unwrap().into_raw(), + usersrole: CString::new(wiq.usersrole).unwrap().into_raw(), + maxretries: wiq.maxretries, + retrydelay: wiq.retrydelay, + initialdelay: wiq.initialdelay, + success_wiqid: CString::new(wiq.success_wiqid).unwrap().into_raw(), + failed_wiqid: CString::new(wiq.failed_wiqid).unwrap().into_raw(), + success_wiq: CString::new(wiq.success_wiq).unwrap().into_raw(), + failed_wiq: CString::new(wiq.failed_wiq).unwrap().into_raw(), + id: CString::new(wiq.id).unwrap().into_raw(), + name: CString::new(wiq.name).unwrap().into_raw(), + packageid: CString::new(wiq.packageid).unwrap().into_raw(), + acl: acl_ptr, + acl_len, + request_id: 0, + } +} + +fn unwrap_workitem_queue(wrapper: &WorkItemQueueWrapper) -> WorkItemQueue { + // Convert ACL from C to Rust + let acl = if wrapper.acl.is_null() || wrapper.acl_len <= 0 { + vec![] + } else { + let acl_slice = unsafe { + std::slice::from_raw_parts(wrapper.acl, wrapper.acl_len as usize) + }; + acl_slice.iter().filter_map(|&ace_ptr| { + if ace_ptr.is_null() { + None + } else { + let ace = unsafe { &*ace_ptr }; + Some(Ace { + id: c_char_to_str(ace.id), + deny: ace.deny, + rights: ace.rights, + }) + } + }).collect() + }; + + WorkItemQueue { + workflowid: c_char_to_str(wrapper.workflowid), + robotqueue: c_char_to_str(wrapper.robotqueue), + amqpqueue: c_char_to_str(wrapper.amqpqueue), + projectid: c_char_to_str(wrapper.projectid), + usersrole: c_char_to_str(wrapper.usersrole), + maxretries: wrapper.maxretries, + retrydelay: wrapper.retrydelay, + initialdelay: wrapper.initialdelay, + success_wiqid: c_char_to_str(wrapper.success_wiqid), + failed_wiqid: c_char_to_str(wrapper.failed_wiqid), + success_wiq: c_char_to_str(wrapper.success_wiq), + failed_wiq: c_char_to_str(wrapper.failed_wiq), + id: c_char_to_str(wrapper.id), + name: c_char_to_str(wrapper.name), + packageid: c_char_to_str(wrapper.packageid), + acl, + createdbyid: String::new(), + createdby: String::new(), + created: None, + modifiedbyid: String::new(), + modifiedby: String::new(), + modified: None, + version: 0, + } +} + +/// AddWorkItemQueueRequestWrapper is a C-compatible wrapper for AddWorkItemQueueRequest +#[repr(C)] +pub struct AddWorkItemQueueRequestWrapper { + pub workitemqueue: *const WorkItemQueueWrapper, + pub skiprole: bool, + pub request_id: i32, +} + +/// AddWorkItemQueueResponseWrapper is a C-compatible wrapper for AddWorkItemQueueResponse +#[repr(C)] +#[derive(Debug, Clone)] +pub struct AddWorkItemQueueResponseWrapper { + pub success: bool, + pub error: *const c_char, + pub workitemqueue: *const WorkItemQueueWrapper, + pub request_id: i32, +} + +/// Synchronous add_workitem_queue FFI function +#[no_mangle] +#[tracing::instrument(skip_all)] +pub extern "C" fn add_workitem_queue( + client: *mut ClientWrapper, + options: *mut AddWorkItemQueueRequestWrapper, +) -> *mut AddWorkItemQueueResponseWrapper { + let options = match safe_wrapper(options) { + Some(options) => options, + None => { + let error_msg = CString::new("Invalid options").unwrap().into_raw(); + let response = AddWorkItemQueueResponseWrapper { + success: false, + error: error_msg, + workitemqueue: std::ptr::null(), + request_id: 0, + }; + return Box::into_raw(Box::new(response)); + } + }; + let client_wrapper = match safe_wrapper(client) { + Some(client) => client, + None => { + let error_msg = CString::new("Client is not connected").unwrap().into_raw(); + let response = AddWorkItemQueueResponseWrapper { + success: false, + error: error_msg, + workitemqueue: std::ptr::null(), + request_id: options.request_id, + }; + return Box::into_raw(Box::new(response)); + } + }; + + let wiq_wrapper = match safe_wrapper(options.workitemqueue as *mut WorkItemQueueWrapper) { + Some(w) => w, + None => { + let error_msg = CString::new("Invalid workitem queue").unwrap().into_raw(); + let response = AddWorkItemQueueResponseWrapper { + success: false, + error: error_msg, + workitemqueue: std::ptr::null(), + request_id: options.request_id, + }; + return Box::into_raw(Box::new(response)); + } + }; + + let wiq = unwrap_workitem_queue(wiq_wrapper); + let request = AddWorkItemQueueRequest { + workitemqueue: Some(wiq), + skiprole: options.skiprole, + }; + + if client_wrapper.client.is_none() { + let error_msg = CString::new("Client is not connected").unwrap().into_raw(); + let response = AddWorkItemQueueResponseWrapper { + success: false, + error: error_msg, + workitemqueue: std::ptr::null(), + request_id: options.request_id, + }; + return Box::into_raw(Box::new(response)); + } + let client = client_wrapper.client.clone().unwrap(); + + let result = tokio::task::block_in_place(|| { + let handle = client.get_runtime_handle(); + handle.block_on(client.add_workitem_queue(request, openiap_client::EnvConfig::new())) + }); + + match result { + Ok(wiq) => { + let wrapped = wrap_workitem_queue(wiq); + Box::into_raw(Box::new(AddWorkItemQueueResponseWrapper { + success: true, + error: std::ptr::null(), + workitemqueue: Box::into_raw(Box::new(wrapped)), + request_id: options.request_id, + })) + } + Err(e) => { + let error_msg = CString::new(format!("Add workitem queue failed: {:?}", e)) + .unwrap() + .into_raw(); + Box::into_raw(Box::new(AddWorkItemQueueResponseWrapper { + success: false, + error: error_msg, + workitemqueue: std::ptr::null(), + request_id: options.request_id, + })) + } + } +} + +/// Callback type for add_workitem_queue_async +type AddWorkItemQueueCallback = extern "C" fn(wrapper: *mut AddWorkItemQueueResponseWrapper); + +/// Asynchronous add_workitem_queue FFI function +#[no_mangle] +#[tracing::instrument(skip_all)] +pub extern "C" fn add_workitem_queue_async( + client: *mut ClientWrapper, + options: *mut AddWorkItemQueueRequestWrapper, + callback: AddWorkItemQueueCallback, +) { + let options = match safe_wrapper(options) { + Some(options) => options, + None => { + let error_msg = CString::new("Invalid options").unwrap().into_raw(); + let response = AddWorkItemQueueResponseWrapper { + success: false, + error: error_msg, + workitemqueue: std::ptr::null(), + request_id: 0, + }; + return callback(Box::into_raw(Box::new(response))); + } + }; + let client_wrapper = match safe_wrapper(client) { + Some(client) => client, + None => { + let error_msg = CString::new("Client is not connected").unwrap().into_raw(); + let response = AddWorkItemQueueResponseWrapper { + success: false, + error: error_msg, + workitemqueue: std::ptr::null(), + request_id: options.request_id, + }; + return callback(Box::into_raw(Box::new(response))); + } + }; + + let wiq_wrapper = match safe_wrapper(options.workitemqueue as *mut WorkItemQueueWrapper) { + Some(w) => w, + None => { + let error_msg = CString::new("Invalid workitem queue").unwrap().into_raw(); + let response = AddWorkItemQueueResponseWrapper { + success: false, + error: error_msg, + workitemqueue: std::ptr::null(), + request_id: options.request_id, + }; + return callback(Box::into_raw(Box::new(response))); + } + }; + + let wiq = unwrap_workitem_queue(wiq_wrapper); + let request = AddWorkItemQueueRequest { + workitemqueue: Some(wiq), + skiprole: options.skiprole, + }; + + if client_wrapper.client.is_none() { + let error_msg = CString::new("Client is not connected").unwrap().into_raw(); + let response = AddWorkItemQueueResponseWrapper { + success: false, + error: error_msg, + workitemqueue: std::ptr::null(), + request_id: options.request_id, + }; + return callback(Box::into_raw(Box::new(response))); + } + let client = client_wrapper.client.clone().unwrap(); + let handle = client.get_runtime_handle(); + let request_id = options.request_id; + + let _guard = handle.enter(); + handle.spawn(async move { + let result = client.add_workitem_queue(request, openiap_client::EnvConfig::new()).await; + + let response = match result { + Ok(wiq) => { + let wrapped = wrap_workitem_queue(wiq); + AddWorkItemQueueResponseWrapper { + success: true, + error: std::ptr::null(), + workitemqueue: Box::into_raw(Box::new(wrapped)), + request_id, + } + } + Err(e) => { + let error_msg = CString::new(format!("Add workitem queue failed: {:?}", e)) + .unwrap() + .into_raw(); + AddWorkItemQueueResponseWrapper { + success: false, + error: error_msg, + workitemqueue: std::ptr::null(), + request_id, + } + } + }; + callback(Box::into_raw(Box::new(response))); + }); +} + +/// Free AddWorkItemQueueResponseWrapper +#[no_mangle] +#[tracing::instrument(skip_all)] +pub extern "C" fn free_add_workitem_queue_response(response: *mut AddWorkItemQueueResponseWrapper) { + if response.is_null() { + return; + } + unsafe { + if !(*response).error.is_null() { + let _ = CString::from_raw((*response).error as *mut c_char); + } + if !(*response).workitemqueue.is_null() { + free_workitem_queue_wrapper((*response).workitemqueue as *mut WorkItemQueueWrapper); + } + let _ = Box::from_raw(response); + } +} + +/// Free WorkItemQueueWrapper +#[no_mangle] +#[tracing::instrument(skip_all)] +pub extern "C" fn free_workitem_queue_wrapper(wrapper: *mut WorkItemQueueWrapper) { + if wrapper.is_null() { + return; + } + unsafe { + if !(*wrapper).workflowid.is_null() { + let _ = CString::from_raw((*wrapper).workflowid as *mut c_char); + } + if !(*wrapper).robotqueue.is_null() { + let _ = CString::from_raw((*wrapper).robotqueue as *mut c_char); + } + if !(*wrapper).amqpqueue.is_null() { + let _ = CString::from_raw((*wrapper).amqpqueue as *mut c_char); + } + if !(*wrapper).projectid.is_null() { + let _ = CString::from_raw((*wrapper).projectid as *mut c_char); + } + if !(*wrapper).usersrole.is_null() { + let _ = CString::from_raw((*wrapper).usersrole as *mut c_char); + } + if !(*wrapper).success_wiqid.is_null() { + let _ = CString::from_raw((*wrapper).success_wiqid as *mut c_char); + } + if !(*wrapper).failed_wiqid.is_null() { + let _ = CString::from_raw((*wrapper).failed_wiqid as *mut c_char); + } + if !(*wrapper).success_wiq.is_null() { + let _ = CString::from_raw((*wrapper).success_wiq as *mut c_char); + } + if !(*wrapper).failed_wiq.is_null() { + let _ = CString::from_raw((*wrapper).failed_wiq as *mut c_char); + } + if !(*wrapper).id.is_null() { + let _ = CString::from_raw((*wrapper).id as *mut c_char); + } + if !(*wrapper).name.is_null() { + let _ = CString::from_raw((*wrapper).name as *mut c_char); + } + if !(*wrapper).packageid.is_null() { + let _ = CString::from_raw((*wrapper).packageid as *mut c_char); + } + // Free ACL array and its contents + if !(*wrapper).acl.is_null() && (*wrapper).acl_len > 0 { + let acl_slice = std::slice::from_raw_parts( + (*wrapper).acl as *mut *mut AceWrapper, + (*wrapper).acl_len as usize + ); + for &ace_ptr in acl_slice { + if !ace_ptr.is_null() { + // Free the id string in AceWrapper + if !(*ace_ptr).id.is_null() { + let _ = CString::from_raw((*ace_ptr).id as *mut c_char); + } + // Free the AceWrapper itself + let _ = Box::from_raw(ace_ptr); + } + } + // Free the array of pointers + let _ = Box::from_raw(std::slice::from_raw_parts_mut( + (*wrapper).acl as *mut *const AceWrapper, + (*wrapper).acl_len as usize + ).as_mut_ptr()); + } + let _ = Box::from_raw(wrapper); + } +} + +// ============================================================================ +// UpdateWorkItemQueue FFI +// ============================================================================ + +/// UpdateWorkItemQueueRequestWrapper is a C-compatible wrapper for UpdateWorkItemQueueRequest +#[repr(C)] +pub struct UpdateWorkItemQueueRequestWrapper { + pub workitemqueue: *const WorkItemQueueWrapper, + pub skiprole: bool, + pub purge: bool, + pub request_id: i32, +} + +/// UpdateWorkItemQueueResponseWrapper is a C-compatible wrapper for UpdateWorkItemQueueResponse +#[repr(C)] +#[derive(Debug, Clone)] +pub struct UpdateWorkItemQueueResponseWrapper { + pub success: bool, + pub error: *const c_char, + pub workitemqueue: *const WorkItemQueueWrapper, + pub request_id: i32, +} + +/// Synchronous update_workitem_queue FFI function +#[no_mangle] +#[tracing::instrument(skip_all)] +pub extern "C" fn update_workitem_queue( + client: *mut ClientWrapper, + options: *mut UpdateWorkItemQueueRequestWrapper, +) -> *mut UpdateWorkItemQueueResponseWrapper { + let options = match safe_wrapper(options) { + Some(options) => options, + None => { + let error_msg = CString::new("Invalid options").unwrap().into_raw(); + let response = UpdateWorkItemQueueResponseWrapper { + success: false, + error: error_msg, + workitemqueue: std::ptr::null(), + request_id: 0, + }; + return Box::into_raw(Box::new(response)); + } + }; + let client_wrapper = match safe_wrapper(client) { + Some(client) => client, + None => { + let error_msg = CString::new("Client is not connected").unwrap().into_raw(); + let response = UpdateWorkItemQueueResponseWrapper { + success: false, + error: error_msg, + workitemqueue: std::ptr::null(), + request_id: options.request_id, + }; + return Box::into_raw(Box::new(response)); + } + }; + + let wiq_wrapper = match safe_wrapper(options.workitemqueue as *mut WorkItemQueueWrapper) { + Some(w) => w, + None => { + let error_msg = CString::new("Invalid workitem queue").unwrap().into_raw(); + let response = UpdateWorkItemQueueResponseWrapper { + success: false, + error: error_msg, + workitemqueue: std::ptr::null(), + request_id: options.request_id, + }; + return Box::into_raw(Box::new(response)); + } + }; + + let wiq = unwrap_workitem_queue(wiq_wrapper); + let request = UpdateWorkItemQueueRequest { + workitemqueue: Some(wiq), + skiprole: options.skiprole, + purge: options.purge, + }; + + if client_wrapper.client.is_none() { + let error_msg = CString::new("Client is not connected").unwrap().into_raw(); + let response = UpdateWorkItemQueueResponseWrapper { + success: false, + error: error_msg, + workitemqueue: std::ptr::null(), + request_id: options.request_id, + }; + return Box::into_raw(Box::new(response)); + } + let client = client_wrapper.client.clone().unwrap(); + + let result = tokio::task::block_in_place(|| { + let handle = client.get_runtime_handle(); + handle.block_on(client.update_workitem_queue(request, openiap_client::EnvConfig::new())) + }); + + match result { + Ok(wiq) => { + let wrapped = wrap_workitem_queue(wiq); + Box::into_raw(Box::new(UpdateWorkItemQueueResponseWrapper { + success: true, + error: std::ptr::null(), + workitemqueue: Box::into_raw(Box::new(wrapped)), + request_id: options.request_id, + })) + } + Err(e) => { + let error_msg = CString::new(format!("Update workitem queue failed: {:?}", e)) + .unwrap() + .into_raw(); + Box::into_raw(Box::new(UpdateWorkItemQueueResponseWrapper { + success: false, + error: error_msg, + workitemqueue: std::ptr::null(), + request_id: options.request_id, + })) + } + } +} + +/// Callback type for update_workitem_queue_async +type UpdateWorkItemQueueCallback = extern "C" fn(wrapper: *mut UpdateWorkItemQueueResponseWrapper); + +/// Asynchronous update_workitem_queue FFI function +#[no_mangle] +#[tracing::instrument(skip_all)] +pub extern "C" fn update_workitem_queue_async( + client: *mut ClientWrapper, + options: *mut UpdateWorkItemQueueRequestWrapper, + callback: UpdateWorkItemQueueCallback, +) { + let options = match safe_wrapper(options) { + Some(options) => options, + None => { + let error_msg = CString::new("Invalid options").unwrap().into_raw(); + let response = UpdateWorkItemQueueResponseWrapper { + success: false, + error: error_msg, + workitemqueue: std::ptr::null(), + request_id: 0, + }; + return callback(Box::into_raw(Box::new(response))); + } + }; + let client_wrapper = match safe_wrapper(client) { + Some(client) => client, + None => { + let error_msg = CString::new("Client is not connected").unwrap().into_raw(); + let response = UpdateWorkItemQueueResponseWrapper { + success: false, + error: error_msg, + workitemqueue: std::ptr::null(), + request_id: options.request_id, + }; + return callback(Box::into_raw(Box::new(response))); + } + }; + + let wiq_wrapper = match safe_wrapper(options.workitemqueue as *mut WorkItemQueueWrapper) { + Some(w) => w, + None => { + let error_msg = CString::new("Invalid workitem queue").unwrap().into_raw(); + let response = UpdateWorkItemQueueResponseWrapper { + success: false, + error: error_msg, + workitemqueue: std::ptr::null(), + request_id: options.request_id, + }; + return callback(Box::into_raw(Box::new(response))); + } + }; + + let wiq = unwrap_workitem_queue(wiq_wrapper); + let request = UpdateWorkItemQueueRequest { + workitemqueue: Some(wiq), + skiprole: options.skiprole, + purge: options.purge, + }; + + if client_wrapper.client.is_none() { + let error_msg = CString::new("Client is not connected").unwrap().into_raw(); + let response = UpdateWorkItemQueueResponseWrapper { + success: false, + error: error_msg, + workitemqueue: std::ptr::null(), + request_id: options.request_id, + }; + return callback(Box::into_raw(Box::new(response))); + } + let client = client_wrapper.client.clone().unwrap(); + let handle = client.get_runtime_handle(); + let request_id = options.request_id; + + let _guard = handle.enter(); + handle.spawn(async move { + let result = client.update_workitem_queue(request, openiap_client::EnvConfig::new()).await; + + let response = match result { + Ok(wiq) => { + let wrapped = wrap_workitem_queue(wiq); + UpdateWorkItemQueueResponseWrapper { + success: true, + error: std::ptr::null(), + workitemqueue: Box::into_raw(Box::new(wrapped)), + request_id, + } + } + Err(e) => { + let error_msg = CString::new(format!("Update workitem queue failed: {:?}", e)) + .unwrap() + .into_raw(); + UpdateWorkItemQueueResponseWrapper { + success: false, + error: error_msg, + workitemqueue: std::ptr::null(), + request_id, + } + } + }; + callback(Box::into_raw(Box::new(response))); + }); +} + +/// Free UpdateWorkItemQueueResponseWrapper +#[no_mangle] +#[tracing::instrument(skip_all)] +pub extern "C" fn free_update_workitem_queue_response(response: *mut UpdateWorkItemQueueResponseWrapper) { + if response.is_null() { + return; + } + unsafe { + if !(*response).error.is_null() { + let _ = CString::from_raw((*response).error as *mut c_char); + } + if !(*response).workitemqueue.is_null() { + free_workitem_queue_wrapper((*response).workitemqueue as *mut WorkItemQueueWrapper); + } + let _ = Box::from_raw(response); + } +} + +// ============================================================================ +// DeleteWorkItemQueue FFI +// ============================================================================ + +/// DeleteWorkItemQueueRequestWrapper is a C-compatible wrapper for DeleteWorkItemQueueRequest +#[repr(C)] +pub struct DeleteWorkItemQueueRequestWrapper { + pub wiq: *const c_char, + pub wiqid: *const c_char, + pub purge: bool, + pub request_id: i32, +} + +/// DeleteWorkItemQueueResponseWrapper is a C-compatible wrapper for DeleteWorkItemQueueResponse +#[repr(C)] +#[derive(Debug, Clone)] +pub struct DeleteWorkItemQueueResponseWrapper { + pub success: bool, + pub error: *const c_char, + pub request_id: i32, +} + +/// Synchronous delete_workitem_queue FFI function +#[no_mangle] +#[tracing::instrument(skip_all)] +pub extern "C" fn delete_workitem_queue( + client: *mut ClientWrapper, + options: *mut DeleteWorkItemQueueRequestWrapper, +) -> *mut DeleteWorkItemQueueResponseWrapper { + let options = match safe_wrapper(options) { + Some(options) => options, + None => { + let error_msg = CString::new("Invalid options").unwrap().into_raw(); + let response = DeleteWorkItemQueueResponseWrapper { + success: false, + error: error_msg, + request_id: 0, + }; + return Box::into_raw(Box::new(response)); + } + }; + let client_wrapper = match safe_wrapper(client) { + Some(client) => client, + None => { + let error_msg = CString::new("Client is not connected").unwrap().into_raw(); + let response = DeleteWorkItemQueueResponseWrapper { + success: false, + error: error_msg, + request_id: options.request_id, + }; + return Box::into_raw(Box::new(response)); + } + }; + + let request = DeleteWorkItemQueueRequest { + wiq: c_char_to_str(options.wiq), + wiqid: c_char_to_str(options.wiqid), + purge: options.purge, + }; + + if client_wrapper.client.is_none() { + let error_msg = CString::new("Client is not connected").unwrap().into_raw(); + let response = DeleteWorkItemQueueResponseWrapper { + success: false, + error: error_msg, + request_id: options.request_id, + }; + return Box::into_raw(Box::new(response)); + } + let client = client_wrapper.client.clone().unwrap(); + + let result = tokio::task::block_in_place(|| { + let handle = client.get_runtime_handle(); + handle.block_on(client.delete_workitem_queue(request, openiap_client::EnvConfig::new())) + }); + + match result { + Ok(_) => { + Box::into_raw(Box::new(DeleteWorkItemQueueResponseWrapper { + success: true, + error: std::ptr::null(), + request_id: options.request_id, + })) + } + Err(e) => { + let error_msg = CString::new(format!("Delete workitem queue failed: {:?}", e)) + .unwrap() + .into_raw(); + Box::into_raw(Box::new(DeleteWorkItemQueueResponseWrapper { + success: false, + error: error_msg, + request_id: options.request_id, + })) + } + } +} + +/// Callback type for delete_workitem_queue_async +type DeleteWorkItemQueueCallback = extern "C" fn(wrapper: *mut DeleteWorkItemQueueResponseWrapper); + +/// Asynchronous delete_workitem_queue FFI function +#[no_mangle] +#[tracing::instrument(skip_all)] +pub extern "C" fn delete_workitem_queue_async( + client: *mut ClientWrapper, + options: *mut DeleteWorkItemQueueRequestWrapper, + callback: DeleteWorkItemQueueCallback, +) { + let options = match safe_wrapper(options) { + Some(options) => options, + None => { + let error_msg = CString::new("Invalid options").unwrap().into_raw(); + let response = DeleteWorkItemQueueResponseWrapper { + success: false, + error: error_msg, + request_id: 0, + }; + return callback(Box::into_raw(Box::new(response))); + } + }; + let client_wrapper = match safe_wrapper(client) { + Some(client) => client, + None => { + let error_msg = CString::new("Client is not connected").unwrap().into_raw(); + let response = DeleteWorkItemQueueResponseWrapper { + success: false, + error: error_msg, + request_id: options.request_id, + }; + return callback(Box::into_raw(Box::new(response))); + } + }; + + let request = DeleteWorkItemQueueRequest { + wiq: c_char_to_str(options.wiq), + wiqid: c_char_to_str(options.wiqid), + purge: options.purge, + }; + + if client_wrapper.client.is_none() { + let error_msg = CString::new("Client is not connected").unwrap().into_raw(); + let response = DeleteWorkItemQueueResponseWrapper { + success: false, + error: error_msg, + request_id: options.request_id, + }; + return callback(Box::into_raw(Box::new(response))); + } + let client = client_wrapper.client.clone().unwrap(); + let handle = client.get_runtime_handle(); + let request_id = options.request_id; + + let _guard = handle.enter(); + handle.spawn(async move { + let result = client.delete_workitem_queue(request, openiap_client::EnvConfig::new()).await; + + let response = match result { + Ok(_) => { + DeleteWorkItemQueueResponseWrapper { + success: true, + error: std::ptr::null(), + request_id, + } + } + Err(e) => { + let error_msg = CString::new(format!("Delete workitem queue failed: {:?}", e)) + .unwrap() + .into_raw(); + DeleteWorkItemQueueResponseWrapper { + success: false, + error: error_msg, + request_id, + } + } + }; + callback(Box::into_raw(Box::new(response))); + }); +} + +/// Free DeleteWorkItemQueueResponseWrapper +#[no_mangle] +#[tracing::instrument(skip_all)] +pub extern "C" fn free_delete_workitem_queue_response(response: *mut DeleteWorkItemQueueResponseWrapper) { + if response.is_null() { + return; + } + unsafe { + if !(*response).error.is_null() { + let _ = CString::from_raw((*response).error as *mut c_char); + } + let _ = Box::from_raw(response); + } +} diff --git a/python/openiap/main.py b/python/openiap/main.py index 23e3cfb..719b845 100644 --- a/python/openiap/main.py +++ b/python/openiap/main.py @@ -1669,7 +1669,7 @@ def callback(response_ptr): wiqid=c_char_p(wiqid.encode('utf-8'))) self.trace("Calling pop_workitem_async") - self.lib.pop_workitem_async(self.client, byref(req), downloadfolder, cb) + self.lib.pop_workitem_async(self.client, byref(req), downloadfolder.encode('utf-8'), cb) self.trace("pop_workitem_async called") event.wait() @@ -2154,6 +2154,386 @@ class InvokeOpenRPAResponseWrapper(ctypes.Structure): self.lib.free_invoke_openrpa_response(ref) return result + def add_workitem_queue(self, name, workflowid="", robotqueue="", amqpqueue="", projectid="", + usersrole="", maxretries=3, retrydelay=0, initialdelay=0, + success_wiqid="", failed_wiqid="", success_wiq="", failed_wiq="", + skiprole=False, packageid="", acl=None): + """ + Add a new workitem queue. + :param name: str - Name of the workitem queue + :param workflowid: str - Workflow ID + :param robotqueue: str - Robot queue name + :param amqpqueue: str - AMQP queue name + :param projectid: str - Project ID + :param usersrole: str - Users role + :param maxretries: int - Maximum retries + :param retrydelay: int - Retry delay in seconds + :param initialdelay: int - Initial delay in seconds + :param success_wiqid: str - Success workitem queue ID + :param failed_wiqid: str - Failed workitem queue ID + :param success_wiq: str - Success workitem queue name + :param failed_wiq: str - Failed workitem queue name + :param skiprole: bool - Skip role creation + :param packageid: str - Package ID + :param acl: list of dicts - Access Control List entries + Example: [{"id": "role-id", "deny": False, "rights": 65}] + :return: dict with workitem queue details + :raises: ClientError + """ + self.trace("Inside add_workitem_queue") + + class AceWrapper(ctypes.Structure): + _fields_ = [ + ("id", c_char_p), + ("deny", c_bool), + ("rights", c_int) + ] + + class WorkItemQueueWrapper(ctypes.Structure): + _fields_ = [ + ("workflowid", c_char_p), + ("robotqueue", c_char_p), + ("amqpqueue", c_char_p), + ("projectid", c_char_p), + ("usersrole", c_char_p), + ("maxretries", c_int), + ("retrydelay", c_int), + ("initialdelay", c_int), + ("success_wiqid", c_char_p), + ("failed_wiqid", c_char_p), + ("success_wiq", c_char_p), + ("failed_wiq", c_char_p), + ("id", c_char_p), + ("name", c_char_p), + ("packageid", c_char_p), + ("acl", ctypes.POINTER(ctypes.POINTER(AceWrapper))), + ("acl_len", c_int), + ("request_id", c_int) + ] + + class AddWorkItemQueueRequestWrapper(ctypes.Structure): + _fields_ = [ + ("workitemqueue", ctypes.POINTER(WorkItemQueueWrapper)), + ("skiprole", c_bool), + ("request_id", c_int) + ] + + class AddWorkItemQueueResponseWrapper(ctypes.Structure): + _fields_ = [ + ("success", c_bool), + ("error", c_char_p), + ("workitemqueue", ctypes.POINTER(WorkItemQueueWrapper)), + ("request_id", c_int) + ] + + # Build ACL array if provided + acl_array = None + acl_pointers = [] + acl_len = 0 + if acl: + for ace_dict in acl: + ace = AceWrapper( + id=ace_dict.get("id", "").encode('utf-8'), + deny=ace_dict.get("deny", False), + rights=ace_dict.get("rights", 0) + ) + acl_pointers.append(ctypes.pointer(ace)) + acl_len = len(acl_pointers) + AcePointerArray = ctypes.POINTER(AceWrapper) * acl_len + acl_array = AcePointerArray(*acl_pointers) + + wiq = WorkItemQueueWrapper( + workflowid=workflowid.encode('utf-8'), + robotqueue=robotqueue.encode('utf-8'), + amqpqueue=amqpqueue.encode('utf-8'), + projectid=projectid.encode('utf-8'), + usersrole=usersrole.encode('utf-8'), + maxretries=maxretries, + retrydelay=retrydelay, + initialdelay=initialdelay, + success_wiqid=success_wiqid.encode('utf-8'), + failed_wiqid=failed_wiqid.encode('utf-8'), + success_wiq=success_wiq.encode('utf-8'), + failed_wiq=failed_wiq.encode('utf-8'), + id=b'', + name=name.encode('utf-8'), + packageid=packageid.encode('utf-8'), + acl=ctypes.cast(acl_array, ctypes.POINTER(ctypes.POINTER(AceWrapper))) if acl_array else None, + acl_len=acl_len, + request_id=0 + ) + + req = AddWorkItemQueueRequestWrapper( + workitemqueue=ctypes.pointer(wiq), + skiprole=skiprole, + request_id=0 + ) + + self.lib.add_workitem_queue.argtypes = [c_void_p, ctypes.POINTER(AddWorkItemQueueRequestWrapper)] + self.lib.add_workitem_queue.restype = ctypes.POINTER(AddWorkItemQueueResponseWrapper) + + self.trace("Calling add_workitem_queue") + ref = self.lib.add_workitem_queue(self.client, ctypes.byref(req)) + response = ref.contents + + if not response.success: + error_message = response.error.decode('utf-8') if response.error else 'Unknown error' + self.lib.free_add_workitem_queue_response(ref) + raise ClientError(f"Add workitem queue failed: {error_message}") + + result_wiq = response.workitemqueue.contents + + # Parse ACL from response + result_acl = [] + if result_wiq.acl and result_wiq.acl_len > 0: + for i in range(result_wiq.acl_len): + ace_ptr = result_wiq.acl[i] + if ace_ptr: + ace = ace_ptr.contents + result_acl.append({ + "id": ace.id.decode('utf-8') if ace.id else "", + "deny": ace.deny, + "rights": ace.rights + }) + + result = { + "id": result_wiq.id.decode('utf-8') if result_wiq.id else "", + "name": result_wiq.name.decode('utf-8') if result_wiq.name else "", + "workflowid": result_wiq.workflowid.decode('utf-8') if result_wiq.workflowid else "", + "robotqueue": result_wiq.robotqueue.decode('utf-8') if result_wiq.robotqueue else "", + "amqpqueue": result_wiq.amqpqueue.decode('utf-8') if result_wiq.amqpqueue else "", + "projectid": result_wiq.projectid.decode('utf-8') if result_wiq.projectid else "", + "usersrole": result_wiq.usersrole.decode('utf-8') if result_wiq.usersrole else "", + "maxretries": result_wiq.maxretries, + "retrydelay": result_wiq.retrydelay, + "initialdelay": result_wiq.initialdelay, + "success_wiqid": result_wiq.success_wiqid.decode('utf-8') if result_wiq.success_wiqid else "", + "failed_wiqid": result_wiq.failed_wiqid.decode('utf-8') if result_wiq.failed_wiqid else "", + "success_wiq": result_wiq.success_wiq.decode('utf-8') if result_wiq.success_wiq else "", + "failed_wiq": result_wiq.failed_wiq.decode('utf-8') if result_wiq.failed_wiq else "", + "packageid": result_wiq.packageid.decode('utf-8') if result_wiq.packageid else "", + "acl": result_acl, + } + + self.lib.free_add_workitem_queue_response(ref) + return result + + def update_workitem_queue(self, id, name="", workflowid="", robotqueue="", amqpqueue="", projectid="", + usersrole="", maxretries=3, retrydelay=0, initialdelay=0, + success_wiqid="", failed_wiqid="", success_wiq="", failed_wiq="", + skiprole=False, purge=False, packageid="", acl=None): + """ + Update an existing workitem queue. + :param id: str - ID of the workitem queue to update + :param name: str - Name of the workitem queue + :param workflowid: str - Workflow ID + :param robotqueue: str - Robot queue name + :param amqpqueue: str - AMQP queue name + :param projectid: str - Project ID + :param usersrole: str - Users role + :param maxretries: int - Maximum retries + :param retrydelay: int - Retry delay in seconds + :param initialdelay: int - Initial delay in seconds + :param success_wiqid: str - Success workitem queue ID + :param failed_wiqid: str - Failed workitem queue ID + :param success_wiq: str - Success workitem queue name + :param failed_wiq: str - Failed workitem queue name + :param skiprole: bool - Skip role creation + :param purge: bool - Purge all workitems in the queue + :param packageid: str - Package ID + :return: dict with workitem queue details + :raises: ClientError + :param acl: list of dicts - Access Control List entries + Example: [{"id": "role-id", "deny": False, "rights": 65}] + """ + self.trace("Inside update_workitem_queue") + + class AceWrapper(ctypes.Structure): + _fields_ = [ + ("id", c_char_p), + ("deny", c_bool), + ("rights", c_int) + ] + + class WorkItemQueueWrapper(ctypes.Structure): + _fields_ = [ + ("workflowid", c_char_p), + ("robotqueue", c_char_p), + ("amqpqueue", c_char_p), + ("projectid", c_char_p), + ("usersrole", c_char_p), + ("maxretries", c_int), + ("retrydelay", c_int), + ("initialdelay", c_int), + ("success_wiqid", c_char_p), + ("failed_wiqid", c_char_p), + ("success_wiq", c_char_p), + ("failed_wiq", c_char_p), + ("id", c_char_p), + ("name", c_char_p), + ("packageid", c_char_p), + ("acl", ctypes.POINTER(ctypes.POINTER(AceWrapper))), + ("acl_len", c_int), + ("request_id", c_int) + ] + + class UpdateWorkItemQueueRequestWrapper(ctypes.Structure): + _fields_ = [ + ("workitemqueue", ctypes.POINTER(WorkItemQueueWrapper)), + ("skiprole", c_bool), + ("purge", c_bool), + ("request_id", c_int) + ] + + class UpdateWorkItemQueueResponseWrapper(ctypes.Structure): + _fields_ = [ + ("success", c_bool), + ("error", c_char_p), + ("workitemqueue", ctypes.POINTER(WorkItemQueueWrapper)), + ("request_id", c_int) + ] + + # Build ACL array if provided + acl_array = None + acl_pointers = [] + acl_len = 0 + if acl: + for ace_dict in acl: + ace = AceWrapper( + id=ace_dict.get("id", "").encode('utf-8'), + deny=ace_dict.get("deny", False), + rights=ace_dict.get("rights", 0) + ) + acl_pointers.append(ctypes.pointer(ace)) + acl_len = len(acl_pointers) + AcePointerArray = ctypes.POINTER(AceWrapper) * acl_len + acl_array = AcePointerArray(*acl_pointers) + + wiq = WorkItemQueueWrapper( + workflowid=workflowid.encode('utf-8'), + robotqueue=robotqueue.encode('utf-8'), + amqpqueue=amqpqueue.encode('utf-8'), + projectid=projectid.encode('utf-8'), + usersrole=usersrole.encode('utf-8'), + maxretries=maxretries, + retrydelay=retrydelay, + initialdelay=initialdelay, + success_wiqid=success_wiqid.encode('utf-8'), + failed_wiqid=failed_wiqid.encode('utf-8'), + success_wiq=success_wiq.encode('utf-8'), + failed_wiq=failed_wiq.encode('utf-8'), + id=id.encode('utf-8'), + name=name.encode('utf-8'), + packageid=packageid.encode('utf-8'), + acl=ctypes.cast(acl_array, ctypes.POINTER(ctypes.POINTER(AceWrapper))) if acl_array else None, + acl_len=acl_len, + request_id=0 + ) + + req = UpdateWorkItemQueueRequestWrapper( + workitemqueue=ctypes.pointer(wiq), + skiprole=skiprole, + purge=purge, + request_id=0 + ) + + self.lib.update_workitem_queue.argtypes = [c_void_p, ctypes.POINTER(UpdateWorkItemQueueRequestWrapper)] + self.lib.update_workitem_queue.restype = ctypes.POINTER(UpdateWorkItemQueueResponseWrapper) + + self.trace("Calling update_workitem_queue") + ref = self.lib.update_workitem_queue(self.client, ctypes.byref(req)) + response = ref.contents + + if not response.success: + error_message = response.error.decode('utf-8') if response.error else 'Unknown error' + self.lib.free_update_workitem_queue_response(ref) + raise ClientError(f"Update workitem queue failed: {error_message}") + + result_wiq = response.workitemqueue.contents + + # Parse ACL from response + result_acl = [] + if result_wiq.acl and result_wiq.acl_len > 0: + for i in range(result_wiq.acl_len): + ace_ptr = result_wiq.acl[i] + if ace_ptr: + ace = ace_ptr.contents + result_acl.append({ + "id": ace.id.decode('utf-8') if ace.id else "", + "deny": ace.deny, + "rights": ace.rights + }) + + result = { + "id": result_wiq.id.decode('utf-8') if result_wiq.id else "", + "name": result_wiq.name.decode('utf-8') if result_wiq.name else "", + "workflowid": result_wiq.workflowid.decode('utf-8') if result_wiq.workflowid else "", + "robotqueue": result_wiq.robotqueue.decode('utf-8') if result_wiq.robotqueue else "", + "amqpqueue": result_wiq.amqpqueue.decode('utf-8') if result_wiq.amqpqueue else "", + "projectid": result_wiq.projectid.decode('utf-8') if result_wiq.projectid else "", + "usersrole": result_wiq.usersrole.decode('utf-8') if result_wiq.usersrole else "", + "maxretries": result_wiq.maxretries, + "retrydelay": result_wiq.retrydelay, + "initialdelay": result_wiq.initialdelay, + "success_wiqid": result_wiq.success_wiqid.decode('utf-8') if result_wiq.success_wiqid else "", + "failed_wiqid": result_wiq.failed_wiqid.decode('utf-8') if result_wiq.failed_wiqid else "", + "success_wiq": result_wiq.success_wiq.decode('utf-8') if result_wiq.success_wiq else "", + "failed_wiq": result_wiq.failed_wiq.decode('utf-8') if result_wiq.failed_wiq else "", + "packageid": result_wiq.packageid.decode('utf-8') if result_wiq.packageid else "", + "acl": result_acl, + } + + self.lib.free_update_workitem_queue_response(ref) + return result + + def delete_workitem_queue(self, wiq="", wiqid="", purge=False): + """ + Delete a workitem queue. + :param wiq: str - Name of the workitem queue to delete + :param wiqid: str - ID of the workitem queue to delete + :param purge: bool - Purge all workitems in the queue + :return: bool - True if successful + :raises: ClientError + """ + self.trace("Inside delete_workitem_queue") + + class DeleteWorkItemQueueRequestWrapper(ctypes.Structure): + _fields_ = [ + ("wiq", c_char_p), + ("wiqid", c_char_p), + ("purge", c_bool), + ("request_id", c_int) + ] + + class DeleteWorkItemQueueResponseWrapper(ctypes.Structure): + _fields_ = [ + ("success", c_bool), + ("error", c_char_p), + ("request_id", c_int) + ] + + req = DeleteWorkItemQueueRequestWrapper( + wiq=wiq.encode('utf-8'), + wiqid=wiqid.encode('utf-8'), + purge=purge, + request_id=0 + ) + + self.lib.delete_workitem_queue.argtypes = [c_void_p, ctypes.POINTER(DeleteWorkItemQueueRequestWrapper)] + self.lib.delete_workitem_queue.restype = ctypes.POINTER(DeleteWorkItemQueueResponseWrapper) + + self.trace("Calling delete_workitem_queue") + ref = self.lib.delete_workitem_queue(self.client, ctypes.byref(req)) + response = ref.contents + + if not response.success: + error_message = response.error.decode('utf-8') if response.error else 'Unknown error' + self.lib.free_delete_workitem_queue_response(ref) + raise ClientError(f"Delete workitem queue failed: {error_message}") + + self.lib.free_delete_workitem_queue_response(ref) + return True + def __del__(self): if hasattr(self, 'lib'): self.lib.free_client.argtypes = [POINTER(ClientWrapper)] diff --git a/python/pyproject.toml b/python/pyproject.toml index 8959b9f..e45e431 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "openiap-edge" -version = "0.0.41" +version = "0.0.42" authors = [ { name="OpenIAP ApS / Allan Zimmerman", email="info@openiap.io" }, ] diff --git a/python/setup.py b/python/setup.py index 4de0d9a..a4a1ca8 100644 --- a/python/setup.py +++ b/python/setup.py @@ -2,7 +2,7 @@ setup( name="openiap", - version="0.0.41", + version="0.0.42", author="OpenIAP ApS / Allan Zimmerman", author_email="info@openiap.io", description="Simple openiap api wrapper using proto", diff --git a/python/test.py b/python/test.py index a524b5b..0e79fd4 100644 --- a/python/test.py +++ b/python/test.py @@ -83,6 +83,33 @@ def onclientevent(result, counter): workitem["state"] = "successful" client.update_workitem(workitem, files) + # Test workitem queue CRUD operations + print("*********************************") + print("Testing workitem queue CRUD operations") + print("*********************************") + + # Add a new workitem queue + wiq_result = client.add_workitem_queue( + name="python_test_wiq", + maxretries=5, + retrydelay=60, + initialdelay=0 + ) + print("Created workitem queue:", wiq_result) + wiq_id = wiq_result["id"] + + # Update the workitem queue + updated_wiq = client.update_workitem_queue( + id=wiq_id, + name="python_test_wiq_updated", + maxretries=10, + retrydelay=120 + ) + print("Updated workitem queue:", updated_wiq) + + # Delete the workitem queue + delete_result = client.delete_workitem_queue(wiqid=wiq_id, purge=True) + print("Deleted workitem queue:", delete_result) query_result = client.query(collectionname="entities", query="{}", projection="{\"name\": 1}", orderby="", queryas="", explain=False, skip=0, top=0) print(query_result)