This is an automated email from the ASF dual-hosted git repository. pnoltes pushed a commit to branch feature/599-provide-and-use-c-service-in-rust in repository https://gitbox.apache.org/repos/asf/celix.git
commit 5329420835d1dc31f5f2b48e5d16ec1a3f7099a4 Author: Pepijn Noltes <pepijnnol...@gmail.com> AuthorDate: Mon Aug 28 23:16:48 2023 +0200 #599: Add poc svc tracker to rust experimental --- misc/experimental/rust/CMakeLists.txt | 15 +- misc/experimental/rust/celix/src/bundle_context.rs | 463 ++++++++++++++++----- misc/experimental/rust/celix/src/errno.rs | 10 + misc/experimental/rust/celix/src/lib.rs | 1 + misc/experimental/rust/celix/src/log_helper.rs | 111 ++--- .../rust/celix_bindings/src/celix_bindings.h | 1 - .../rust/shell_command_bundle/src/lib.rs | 47 +-- 7 files changed, 462 insertions(+), 186 deletions(-) diff --git a/misc/experimental/rust/CMakeLists.txt b/misc/experimental/rust/CMakeLists.txt index 6ab2287c..966f83c8 100644 --- a/misc/experimental/rust/CMakeLists.txt +++ b/misc/experimental/rust/CMakeLists.txt @@ -16,7 +16,7 @@ # under the License. option(CELIX_RUST_EXPERIMENTAL "Enable experimental rust bundle" OFF) -if (CELIX_RUST_EXPERIMENTAL) +if (CELIX_RUST_EXPERIMENTAL AND TARGET Celix::shell_tui AND TARGET Celix::shell AND TARGET Celix::log_admin) include(FetchContent) FetchContent_Declare( Corrosion @@ -29,7 +29,7 @@ if (CELIX_RUST_EXPERIMENTAL) #Note for now this includes framework, utils and shell_api maybe this should be separated in the future. file(GENERATE OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/include_paths.txt" CONTENT - "$<TARGET_PROPERTY:framework,INTERFACE_INCLUDE_DIRECTORIES>;$<TARGET_PROPERTY:utils,INTERFACE_INCLUDE_DIRECTORIES>;$<TARGET_PROPERTY:shell_api,INTERFACE_INCLUDE_DIRECTORIES>;$<TARGET_PROPERTY:Celix::log_service_api,INTERFACE_INCLUDE_DIRECTORIES>;$<TARGET_PROPERTY:Celix::log_helper,INTERFACE_INCLUDE_DIRECTORIES>") + "$<TARGET_PROPERTY:framework,INTERFACE_INCLUDE_DIRECTORIES>;$<TARGET_PROPERTY:utils,INTERFACE_INCLUDE_DIRECTORIES>;$<TARGET_PROPERTY:shell_api,INTERFACE_INCLUDE_DIRECTORIES>;$<TARGET_PROPERTY:Celix::log_service_api,INTERFACE_INCLUDE_DIRECTORIES>") corrosion_import_crate(MANIFEST_PATH Cargo.toml) corrosion_add_target_local_rustflags(rust_bundle_activator "-Cprefer-dynamic") @@ -40,11 +40,17 @@ if (CELIX_RUST_EXPERIMENTAL) get_target_property(ACTUAL_LIB_TARGET rust_bundle_activator INTERFACE_LINK_LIBRARIES) add_celix_bundle(rust_bundle ACTIVATOR ${ACTUAL_LIB_TARGET}) add_dependencies(rust_bundle rust_bundle_activator) - corrosion_add_target_local_rustflags(rust_shell_command_activator "-Cprefer-dynamic") - corrosion_link_libraries(rust_shell_command_activator Celix::framework) + corrosion_link_libraries(rust_shell_command_activator + Celix::framework + ) get_target_property(ACTUAL_LIB_TARGET rust_shell_command_activator INTERFACE_LINK_LIBRARIES) add_celix_bundle(rust_shell_command ACTIVATOR ${ACTUAL_LIB_TARGET}) + target_link_libraries(${ACTUAL_LIB_TARGET} INTERFACE + -Wl,--whole-archive + Celix::log_helper + -Wl,--no-whole-archive + ) add_dependencies(rust_shell_command rust_shell_command_activator) add_celix_container(rust_container NO_COPY @@ -56,6 +62,7 @@ if (CELIX_RUST_EXPERIMENTAL) add_celix_container(rust_shell_cnt NO_COPY BUNDLES + Celix::log_admin Celix::shell Celix::shell_tui rust_shell_command diff --git a/misc/experimental/rust/celix/src/bundle_context.rs b/misc/experimental/rust/celix/src/bundle_context.rs index 56ddc69a..e944dfa4 100644 --- a/misc/experimental/rust/celix/src/bundle_context.rs +++ b/misc/experimental/rust/celix/src/bundle_context.rs @@ -21,12 +21,17 @@ use std::any::type_name; use std::any::Any; use std::collections::HashMap; use std::ffi::c_void; +use std::ops::Deref; use std::ptr::null_mut; use std::sync::Arc; use std::sync::Mutex; use std::sync::Weak; -use celix_bindings::celix_bundleContext_log; +use celix_bindings::{celix_bundleContext_log, celix_bundleContext_stopTracker, celix_bundleContext_stopTrackerAsync, celix_bundleContext_trackServicesWithOptions, celix_bundleContext_trackServicesWithOptionsAsync, celix_properties_t}; +use celix_bindings::celix_service_filter_options; +use celix_bindings::celix_bundleContext_registerServiceWithOptionsAsync; +use celix_bindings::celix_bundleContext_unregisterServiceAsync; +use celix_bindings::celix_service_tracking_options_t; use celix_bindings::celix_service_use_options_t; use celix_bindings::celix_service_filter_options_t; use celix_bindings::celix_bundleContext_useServicesWithOptions; @@ -43,33 +48,64 @@ use super::LogLevel; pub struct ServiceRegistration { service_id: i64, + service_name: String, + unregister_async: bool, weak_ctx: Weak<BundleContext>, - _boxed_svc: Option<Box<dyn Any>>, + svc: Option<Box<dyn Any>>, + unmanaged_svc: Option<*mut dyn Any>, +} + +impl ServiceRegistration { + pub fn get_service_id(&self) -> i64 { + self.service_id + } + + pub fn get_service_name(&self) -> &str { + self.service_name.as_str() + } + + pub fn get_service(&self) -> Option<&dyn Any> { + if let Some(boxed_svc) = self.svc.as_ref() { + Some(boxed_svc.as_ref()) + } else if let Some(unmanaged_svc) = self.unmanaged_svc { + unsafe { + Some(&*unmanaged_svc) + } + } else { + None + } + } } impl Drop for ServiceRegistration { fn drop(&mut self) { let ctx = self.weak_ctx.upgrade(); match ctx { - Some(ctx) => ctx.unregister_service(self.service_id), + Some(ctx) => ctx.unregister_service(self.service_id, self.unregister_async), None => println!("Cannot unregister ServiceRegistration: BundleContext is gone"), } } } -pub struct ServiceRegistrationBuilder<'a> { +pub struct ServiceRegistrationBuilder<'a, T: ?Sized + 'static> { ctx: &'a BundleContext, - boxed_svc: Option<Box<dyn Any>>, + register_async: bool, + unregister_async: bool, + svc: Option<Box<T>>, //note box is needed for stable pointer value + unmanaged_svc: Option<*mut dyn Any>, service_name: String, service_version: String, service_properties: HashMap<String, String>, } -impl ServiceRegistrationBuilder<'_> { - fn new(ctx: &BundleContext) -> ServiceRegistrationBuilder { +impl<T> ServiceRegistrationBuilder<'_, T> { + fn new(ctx: &BundleContext) -> ServiceRegistrationBuilder<T> { ServiceRegistrationBuilder { ctx, - boxed_svc: None, + register_async: false, + unregister_async: false, + svc: None, + unmanaged_svc: None, service_name: "".to_string(), service_version: "".to_string(), service_properties: HashMap::new(), @@ -81,21 +117,21 @@ impl ServiceRegistrationBuilder<'_> { self } - fn with_service_name_if_not_set(&mut self, name: &str) -> &mut Self { + fn with_service_name_if_not_set(&mut self) -> &mut Self { if self.service_name.is_empty() { - self.service_name = name.to_string(); + self.service_name = type_name::<T>().to_string(); } self } - pub fn with_service<I: 'static>(&mut self, svc: I) -> &mut Self { - self.boxed_svc = Some(Box::new(svc)); - self.with_service_name_if_not_set(type_name::<I>()) + pub fn with_service(&mut self, svc: T) -> &mut Self { + self.svc = Some(Box::new(svc)); + self.with_service_name_if_not_set() } - pub fn with_boxed_service<T: ?Sized + 'static>(&mut self, svc: Box<T>) -> &mut Self { - self.boxed_svc = Some(Box::new(svc)); - self.with_service_name_if_not_set(type_name::<T>()) + pub fn with_unmanaged_service(&mut self, svc: *mut T) -> &mut Self { + self.unmanaged_svc = Some(svc); + self.with_service_name_if_not_set() } pub fn with_version(&mut self, version: &str) -> &mut Self { @@ -114,6 +150,26 @@ impl ServiceRegistrationBuilder<'_> { self } + pub fn with_register_async(&mut self) -> &mut Self { + self.register_async = true; + self + } + + pub fn with_register_sync(&mut self) -> &mut Self { + self.register_async = false; + self + } + + pub fn with_unregister_async(&mut self) -> &mut Self { + self.unregister_async = true; + self + } + + pub fn with_unregister_sync(&mut self) -> &mut Self { + self.unregister_async = false; + self + } + fn validate(&self) -> Result<(), Error> { let mut valid = true; if self.service_name.is_empty() { @@ -121,7 +177,7 @@ impl ServiceRegistrationBuilder<'_> { .log_error("Cannot register service. Service name is empty"); valid = false; } - if self.boxed_svc.is_none() /*&& self.arc_svc.is_none() */ { + if self.svc.is_none() && self.unmanaged_svc.is_none() { self.ctx .log_error("Cannot register service. No instance provided"); valid = false; @@ -132,25 +188,41 @@ impl ServiceRegistrationBuilder<'_> { } } - fn get_c_svc(&mut self) -> *mut c_void { - if let Some(boxed_svc) = self.boxed_svc.as_mut() { - let any_svc: &mut dyn Any = boxed_svc.as_mut(); - let boxed_svc_ptr = any_svc as *mut dyn Any; //note box still owns the instance - boxed_svc_ptr as *mut c_void + fn get_c_svc(svc_reg: &ServiceRegistration) -> *mut c_void { + if let Some(boxed_svc) = svc_reg.svc.as_ref() { + boxed_svc.deref() as *const dyn Any as *mut c_void + } else if let Some(unmanaged_svc) = svc_reg.unmanaged_svc { + unmanaged_svc as *mut c_void } else { null_mut() } } - - unsafe fn build_unsafe(&mut self, svc_ptr: *mut c_void) -> Result<ServiceRegistration, Error> { + + pub fn build(&mut self) -> Result<ServiceRegistration, Error> { + self.validate()?; + + let mut svc_reg = ServiceRegistration { + service_id: -1, + service_name: self.service_name.clone(), + unregister_async: self.unregister_async, + weak_ctx: self.ctx.get_self().clone(), + svc: if self.svc.is_none() { None } else { Some(self.svc.take().unwrap()) }, + unmanaged_svc: self.unmanaged_svc, + }; + + let svc_ptr = Self::get_c_svc(&svc_reg); let c_service_name = std::ffi::CString::new(self.service_name.as_str()).unwrap(); let c_service_version = std::ffi::CString::new(self.service_version.as_str()).unwrap(); - let c_service_properties = celix_properties_create(); - for (key, value) in self.service_properties.iter() { - let c_key = std::ffi::CString::new(key.as_str()).unwrap(); - let c_value = std::ffi::CString::new(value.as_str()).unwrap(); - celix_properties_set(c_service_properties, c_key.as_ptr(), c_value.as_ptr()); + let c_service_properties: *mut celix_properties_t; + unsafe { + c_service_properties = celix_properties_create(); + for (key, value) in self.service_properties.iter() { + let c_key = std::ffi::CString::new(key.as_str()).unwrap(); + let c_value = std::ffi::CString::new(value.as_str()).unwrap(); + celix_properties_set(c_service_properties, c_key.as_ptr(), c_value.as_ptr()); + } } + let opts = celix_service_registration_options_t { svc: svc_ptr, factory: null_mut(), @@ -166,71 +238,44 @@ impl ServiceRegistrationBuilder<'_> { asyncCallback: None, }; - let service_id: i64 = - celix_bundleContext_registerServiceWithOptions(self.ctx.get_c_bundle_context(), &opts); - if service_id >= 0 { - Ok(ServiceRegistration { - service_id, - weak_ctx: self.ctx.get_self().clone(), - _boxed_svc: self.boxed_svc.take(), - }) + if self.register_async { + unsafe { + svc_reg.service_id = celix_bundleContext_registerServiceWithOptions(self.ctx.get_c_bundle_context(), &opts); + } } else { - Err(Error::BundleException) + unsafe { + svc_reg.service_id = celix_bundleContext_registerServiceWithOptionsAsync(self.ctx.get_c_bundle_context(), &opts); + } } - } - - pub fn build(&mut self) -> Result<ServiceRegistration, Error> { - self.validate()?; - let svc_ptr = self.get_c_svc(); - unsafe { - self.build_unsafe(svc_ptr) + if svc_reg.service_id >= 0 { + Ok(svc_reg) + } else { + Err(Error::BundleException) } } } -pub struct ServiceUseBuilder<'a> { +pub struct ServiceUseBuilder<'a, T> { ctx: &'a BundleContext, many: bool, service_name: String, filter: String, - callback: Option<Box<Box<dyn Fn(&dyn Any)>>> + callback: Option<Box<dyn Fn(&T)>>, //note double boxed } -impl ServiceUseBuilder<'_> { - fn new(ctx: &BundleContext, many: bool) -> ServiceUseBuilder { +impl<T> ServiceUseBuilder<'_, T> { + fn new(ctx: &BundleContext, many: bool) -> ServiceUseBuilder<T> { ServiceUseBuilder { ctx, many, - service_name: "".to_string(), + service_name: type_name::<T>().to_string(), filter: "".to_string(), callback: None, } } - pub fn with_service<T: ?Sized + 'static>(&mut self) -> &mut Self { - self.service_name = type_name::<T>().to_string(); - self - } - - #[doc = " @brief Provide a callback which will be called when a service is available. T must be a Sized (non trait) type, due to the use of downcast_ref"] - pub fn with_callback<T: Sized + 'static>(&mut self, closure: Box<dyn Fn(&T)>)-> &mut Self { - if self.service_name.is_empty() { - self.with_service::<T>(); - } - - let any_closure = Box::new(move |any_svc: &dyn Any| { - if let Some(svc) = any_svc.downcast_ref::<T>() { - closure(svc); - } - }); - - self.callback = Some(Box::new(any_closure)); - self - } - - #[doc = " @brief Provide a callback which will be called when a service is available, with a dyn Any argument. Note that this is useful for trait objects."] - pub fn with_any_callback(&mut self, closure: Box<dyn Fn(&dyn Any)>)-> &mut Self { - self.callback = Some(Box::new(closure)); //note double boxed + pub fn with_callback(&mut self, closure: Box<dyn Fn(&T)>)-> &mut Self { + self.callback = Some(closure); self } @@ -245,17 +290,20 @@ impl ServiceUseBuilder<'_> { } fn validate(&self) -> Result<(), Error> { - if self.callback.is_none() || self.service_name.is_empty() { + if self.service_name.is_empty() { return Err(Error::BundleException); } Ok(()) } unsafe extern "C" fn use_service_c_callback(handle: *mut c_void, svc: *mut c_void) { - let boxed_fn = Box::from_raw(handle as *mut Box<dyn Fn(&dyn Any)>); - let any_svc_ptr = svc as *mut dyn Any; - let any_svc_ref = any_svc_ptr.as_ref().unwrap(); - boxed_fn(any_svc_ref); + let closure = handle as *const Box<dyn Fn(&T)>; + let closure = closure.as_ref().unwrap(); + + let typed_svc = svc as *const T; + let typed_svc = typed_svc.as_ref().unwrap(); + + closure(typed_svc); } pub fn build(&mut self) -> Result<isize, Error> { @@ -264,13 +312,9 @@ impl ServiceUseBuilder<'_> { let c_service_name = std::ffi::CString::new(self.service_name.as_str()).unwrap(); let c_filter = std::ffi::CString::new(self.filter.as_str()).unwrap(); let c_service_name_ptr: *const i8 = c_service_name.as_ptr(); - - //Note filter is for now unused, introduce when updating to use of celix_bundleContext_useServiceWithOptions let c_filter_ptr: *const i8 = if self.filter.is_empty() { null_mut()} else {c_filter.as_ptr() }; - let boxed_fn = self.callback.take().unwrap(); - let fn_ptr = Box::into_raw(boxed_fn) as *mut c_void; - + let c_closure_ptr = self.callback.as_ref().unwrap() as *const Box<dyn Fn(&T)> as *mut c_void; let opts = celix_service_use_options_t { filter: celix_service_filter_options_t { @@ -281,18 +325,20 @@ impl ServiceUseBuilder<'_> { ignoreServiceLanguage: false, }, waitTimeoutInSeconds: 0.0, - callbackHandle: fn_ptr, + callbackHandle: c_closure_ptr, use_: Some(Self::use_service_c_callback), useWithProperties: None, useWithOwner: None, flags: 0, }; - unsafe { - if self.many { + if self.many { + unsafe { let count = celix_bundleContext_useServicesWithOptions(self.ctx.get_c_bundle_context(), &opts); Ok(count as isize) - } else { + } + } else { + unsafe { let called = celix_bundleContext_useServiceWithOptions(self.ctx.get_c_bundle_context(), &opts); if called { Ok(1) @@ -304,6 +350,210 @@ impl ServiceUseBuilder<'_> { } } +struct ServiceTrackerCallbacks<T> { + set_callback: Option<Box<dyn Fn(Option<&T>)>>, + add_callback: Option<Box<dyn Fn(&T)>>, + remove_callback: Option<Box<dyn Fn(&T)>>, +} + +pub struct ServiceTracker<T> { + ctx: Arc<BundleContext>, + tracker_id: i64, + // shared_data: Mutex<SharedServiceTrackerData<T>>, + // data_condition: Condvar, + callbacks: Box<ServiceTrackerCallbacks<T>>, //Note in a box to ensure pointer value is stable after move + stop_async: bool, +} + +impl<T> ServiceTracker<T> { + pub fn close(&mut self) { + self.ctx.stop_tracker(self.tracker_id, self.stop_async); + self.tracker_id = -1; + } +} + +impl<T> Drop for ServiceTracker<T> { + fn drop(&mut self) { + self.close(); + } +} + +pub struct ServiceTrackerBuilder<'a, T> { + ctx: &'a BundleContext, + service_name: String, + filter: String, + track_async: bool, + stop_async: bool, + set_callback: Option<Box<dyn Fn(Option<&T>)>>, + add_callback: Option<Box<dyn Fn(&T)>>, + remove_callback: Option<Box<dyn Fn(&T)>>, +} + +impl<T> ServiceTrackerBuilder<'_, T> { + fn new(ctx: &BundleContext) -> ServiceTrackerBuilder<T> { + ServiceTrackerBuilder { + ctx, + service_name: type_name::<T>().to_string(), + filter: "".to_string(), + track_async: false, + stop_async: false, + set_callback: None, + add_callback: None, + remove_callback: None, + } + } + + pub fn with_service_name(&mut self, name: &str) -> &mut Self { + self.service_name = name.to_string(); + self + } + + pub fn with_filter(&mut self, filter: &str) -> &mut Self { + self.filter = filter.to_string(); + self + } + + pub fn with_set_callback(&mut self, closure: Box<dyn Fn(Option<&T>)>) -> &mut Self { + self.set_callback = Some(closure); + self + } + + pub fn with_add_callback(&mut self, closure: Box<dyn Fn(&T)>) -> &mut Self { + self.add_callback = Some(closure); + self + } + + pub fn with_remove_callback(&mut self, closure: Box<dyn Fn(&T)>) -> &mut Self { + self.remove_callback = Some(closure); + self + } + + pub fn with_track_async(&mut self) -> &mut Self { + self.track_async = true; + self + } + + pub fn with_track_sync(&mut self) -> &mut Self { + self.track_async = false; + self + } + + pub fn with_stop_async(&mut self) -> &mut Self { + self.stop_async = true; + self + } + + pub fn with_stop_sync(&mut self) -> &mut Self { + self.stop_async = false; + self + } + + fn validate(&self) -> Result<(), Error> { + if self.service_name.is_empty() { + return Err(Error::BundleException); + } + Ok(()) + } + + unsafe extern "C" fn set_callback_for_c(handle: *mut ::std::os::raw::c_void, svc: *mut ::std::os::raw::c_void) { + let callbacks = handle as *const ServiceTrackerCallbacks<T>; + let callbacks = callbacks.as_ref().unwrap(); + + if svc.is_null() { + if let Some(set_callback) = callbacks.set_callback.as_ref() { + set_callback(None); + } + } else { + let typed_svc = svc as *const T; + let typed_svc = typed_svc.as_ref().unwrap(); + if let Some(set_callback) = callbacks.set_callback.as_ref() { + set_callback(Some(typed_svc)); + } + } + } + + unsafe extern "C" fn add_callback_for_c(handle: *mut ::std::os::raw::c_void, svc: *mut ::std::os::raw::c_void) { + let callbacks = handle as *const ServiceTrackerCallbacks<T>; + let callbacks = callbacks.as_ref().unwrap(); + + let typed_svc = svc as *const T; + let typed_svc = typed_svc.as_ref().unwrap(); + + if let Some(add_callback) = callbacks.add_callback.as_ref() { + add_callback(typed_svc); + } + } + + unsafe extern "C" fn remove_callback_for_c(handle: *mut ::std::os::raw::c_void, svc: *mut ::std::os::raw::c_void) { + let callbacks = handle as *const ServiceTrackerCallbacks<T>; + let callbacks = callbacks.as_ref().unwrap(); + + let typed_svc = svc as *const T; + let typed_svc = typed_svc.as_ref().unwrap(); + + if let Some(remove_callback) = callbacks.remove_callback.as_ref() { + remove_callback(typed_svc); + } + } + + pub fn build(&mut self) -> Result<ServiceTracker<T>, Error> { + self.validate()?; + + let mut svc_tracker = ServiceTracker { + ctx: self.ctx.get_self().upgrade().unwrap(), + tracker_id: -1, + callbacks: Box::new(ServiceTrackerCallbacks { + set_callback: self.set_callback.take(), + add_callback: self.add_callback.take(), + remove_callback: self.remove_callback.take(), + }), + stop_async: self.stop_async, + }; + + let c_service_name = std::ffi::CString::new(self.service_name.as_str()).unwrap(); + let c_filter = std::ffi::CString::new(self.filter.as_str()).unwrap(); + let c_callback_handle = svc_tracker.callbacks.as_ref() as *const ServiceTrackerCallbacks<T> as *mut c_void; + + let opts = celix_service_tracking_options_t{ + filter: celix_service_filter_options { + serviceName: c_service_name.as_ptr(), + versionRange: null_mut(), + filter: if self.filter.is_empty() { null_mut() } else { c_filter.as_ptr() }, + serviceLanguage: null_mut(), + ignoreServiceLanguage: false, + }, + callbackHandle: c_callback_handle, + set: Some(Self::set_callback_for_c), + setWithProperties: None, + setWithOwner: None, + add: Some(Self::add_callback_for_c), + addWithProperties: None, + addWithOwner: None, + remove: Some(Self::remove_callback_for_c), + removeWithProperties: None, + removeWithOwner: None, + trackerCreatedCallbackData: null_mut(), + trackerCreatedCallback: None, + }; + + let svc_tracker_id: i64; + unsafe { + if self.track_async { + svc_tracker_id = celix_bundleContext_trackServicesWithOptionsAsync(self.ctx.get_c_bundle_context(), &opts); + } else { + svc_tracker_id = celix_bundleContext_trackServicesWithOptions(self.ctx.get_c_bundle_context(), &opts); + } + } + + if svc_tracker_id >= 0 { + svc_tracker.tracker_id = svc_tracker_id; + Ok(svc_tracker) + } else { + Err(Error::BundleException) + } + } +} + pub struct BundleContext { c_bundle_context: *mut celix_bundle_context_t, weak_self: Mutex<Option<Weak<BundleContext>>>, @@ -329,12 +579,6 @@ impl BundleContext { self.weak_self.lock().unwrap().clone().unwrap() } - fn unregister_service(&self, service_id: i64) { - unsafe { - celix_bundleContext_unregisterService(self.c_bundle_context, service_id); - } - } - fn log_to_c(&self, level: LogLevel, message: &str) { unsafe { let result = std::ffi::CString::new(message); @@ -385,20 +629,39 @@ impl BundleContext { self.log(LogLevel::Fatal, message); } - //TODO make generic - pub fn register_service(&self) -> ServiceRegistrationBuilder { + pub fn register_service<T>(&self) -> ServiceRegistrationBuilder<T> { ServiceRegistrationBuilder::new(self) } - //TODO make generic - pub fn use_service(&self) -> ServiceUseBuilder { + fn unregister_service(&self, service_id: i64, unregister_async: bool) { + unsafe { + if unregister_async { + celix_bundleContext_unregisterServiceAsync(self.c_bundle_context, service_id, null_mut(), None); + } else { + celix_bundleContext_unregisterService(self.c_bundle_context, service_id); + } + } + } + + pub fn use_service<T>(&self) -> ServiceUseBuilder<T> { ServiceUseBuilder::new(self, false) } - //TODO make generic - pub fn use_services(&self) -> ServiceUseBuilder { + pub fn use_services<T>(&self) -> ServiceUseBuilder<T> { ServiceUseBuilder::new(self, true) } + + pub fn track_services<T>(&self) -> ServiceTrackerBuilder<T> { ServiceTrackerBuilder::new(self) } + + fn stop_tracker(&self, tracker_id: i64, stop_async: bool) { + unsafe { + if stop_async { + celix_bundleContext_stopTrackerAsync(self.c_bundle_context, tracker_id, null_mut(), None); + } else { + celix_bundleContext_stopTracker(self.c_bundle_context, tracker_id); + } + } + } } pub fn bundle_context_new(c_bundle_context: *mut celix_bundle_context_t) -> Arc<BundleContext> { diff --git a/misc/experimental/rust/celix/src/errno.rs b/misc/experimental/rust/celix/src/errno.rs index af23d2ca..4f9f85ee 100644 --- a/misc/experimental/rust/celix/src/errno.rs +++ b/misc/experimental/rust/celix/src/errno.rs @@ -17,6 +17,7 @@ * under the License. */ +use std::fmt::{Debug, Formatter}; use celix_bindings::celix_status_t; pub const CELIX_SUCCESS: celix_status_t = celix_bindings::CELIX_SUCCESS as celix_status_t; @@ -47,3 +48,12 @@ impl Into<celix_status_t> for Error { } } } + +impl Debug for Error { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Error::BundleException => write!(f, "BundleException"), + Error::CelixStatusError(status) => write!(f, "CelixStatusError({})", status), + } + } +} diff --git a/misc/experimental/rust/celix/src/lib.rs b/misc/experimental/rust/celix/src/lib.rs index b30f995e..390a6280 100644 --- a/misc/experimental/rust/celix/src/lib.rs +++ b/misc/experimental/rust/celix/src/lib.rs @@ -43,6 +43,7 @@ mod bundle_context; pub use self::bundle_context::bundle_context_new; pub use self::bundle_context::BundleContext; pub use self::bundle_context::ServiceRegistration; +pub use self ::bundle_context::ServiceTracker; mod bundle_activator; // Re-export bundle activator types in the public API. diff --git a/misc/experimental/rust/celix/src/log_helper.rs b/misc/experimental/rust/celix/src/log_helper.rs index 64099ba5..72087eef 100644 --- a/misc/experimental/rust/celix/src/log_helper.rs +++ b/misc/experimental/rust/celix/src/log_helper.rs @@ -17,93 +17,98 @@ * under the License. */ -use std::sync::Arc; +use std::ops::Deref; +use std::sync::{Arc, Mutex, RwLock}; use super::BundleContext; use super::LogLevel; -use celix_bindings::celix_logHelper_create; -use celix_bindings::celix_logHelper_destroy; -use celix_bindings::celix_logHelper_log; -use celix_bindings::celix_log_helper_t; +use celix_bindings::celix_log_service_t; +use ServiceTracker; + pub struct LogHelper { - celix_log_helper: *mut celix_log_helper_t, + name: String, + tracker: Mutex<Option<ServiceTracker<celix_log_service_t>>>, + log_svc: RwLock<Option<*const celix_log_service_t>>, } impl LogHelper { - pub fn new(ctx: Arc<BundleContext>, name: &str) -> Self { - unsafe { - let result = std::ffi::CString::new(name); - match result { - Ok(c_str) => LogHelper { - celix_log_helper: celix_logHelper_create( - ctx.get_c_bundle_context(), - c_str.as_ptr() as *const i8, - ), - }, - Err(e) => { - ctx.log_error(&format!( - "Error creating CString: {}. Using \"error\" as log name", - e - )); - let c_str = std::ffi::CString::new("error").unwrap(); - LogHelper { - celix_log_helper: celix_logHelper_create( - ctx.get_c_bundle_context(), - c_str.as_ptr() as *const i8, - ), - } + + pub fn new(ctx: Arc<BundleContext>, name: &str) -> Arc<Self> { + let helper = Arc::new(LogHelper{ + name: name.to_string(), + tracker: Mutex::new(None), + log_svc: RwLock::new(None), + }); + let filter = format!("(name={})", name); + let weak_helper = Arc::downgrade(&helper); + let tracker = ctx.track_services::<celix_log_service_t>() + .with_service_name("celix_log_service") + .with_filter(filter.as_str()) + .with_set_callback(Box::new(move|optional_svc| { + if let Some(helper) = weak_helper.upgrade() { + helper.set_celix_log_service(optional_svc); } + })) + .build().unwrap(); + helper.tracker.lock().unwrap().replace(tracker); + helper + } + + pub fn get_name(&self) -> &str { + &self.name + } + + pub fn set_celix_log_service(&self, optional_svc: Option<&celix_log_service_t>) { + match optional_svc { + Some(svc) => { + let svc_ptr: *const celix_log_service_t = svc as *const celix_log_service_t; + self.log_svc.write().unwrap().replace(svc_ptr); + } + None => { + self.log_svc.write().unwrap().take(); } } } pub fn log(&self, level: LogLevel, message: &str) { - unsafe { - let result = std::ffi::CString::new(message); - match result { - Ok(c_str) => { - celix_logHelper_log( - self.celix_log_helper, - level.into(), - c_str.as_ptr() as *const i8, - ); + let str_result = std::ffi::CString::new(message).unwrap(); + let guard = self.log_svc.read().unwrap(); + if let Some(svc) = guard.as_ref() { + unsafe { + if svc.is_null() { + return; } - Err(e) => { - println!("Error creating CString: {}", e); + let svc = &**svc; + if svc.log.is_none() { + return; } + let log_fn = svc.deref().log.as_ref().unwrap(); + log_fn(svc.handle, level.into(), str_result.as_ptr()); } } } - pub fn trace(&self, message: &str) { + pub fn log_trace(&self, message: &str) { self.log(LogLevel::Trace, message); } - pub fn debug(&self, message: &str) { + pub fn log_debug(&self, message: &str) { self.log(LogLevel::Debug, message); } - pub fn info(&self, message: &str) { + pub fn log_info(&self, message: &str) { self.log(LogLevel::Info, message); } - pub fn warning(&self, message: &str) { + pub fn log_warning(&self, message: &str) { self.log(LogLevel::Warning, message); } - pub fn error(&self, message: &str) { + pub fn log_error(&self, message: &str) { self.log(LogLevel::Error, message); } - pub fn fatal(&self, message: &str) { + pub fn log_fatal(&self, message: &str) { self.log(LogLevel::Fatal, message); } } - -impl Drop for LogHelper { - fn drop(&mut self) { - unsafe { - celix_logHelper_destroy(self.celix_log_helper); - } - } -} diff --git a/misc/experimental/rust/celix_bindings/src/celix_bindings.h b/misc/experimental/rust/celix_bindings/src/celix_bindings.h index 5db2ff22..76c87b6a 100644 --- a/misc/experimental/rust/celix_bindings/src/celix_bindings.h +++ b/misc/experimental/rust/celix_bindings/src/celix_bindings.h @@ -33,4 +33,3 @@ #include "celix_shell_command.h" #include "celix_log_service.h" -#include "celix_log_helper.h" \ No newline at end of file diff --git a/misc/experimental/rust/shell_command_bundle/src/lib.rs b/misc/experimental/rust/shell_command_bundle/src/lib.rs index 3856734e..bb097765 100644 --- a/misc/experimental/rust/shell_command_bundle/src/lib.rs +++ b/misc/experimental/rust/shell_command_bundle/src/lib.rs @@ -21,12 +21,11 @@ extern crate celix; extern crate celix_bindings; extern crate rust_shell_api; -use std::any::Any; use std::ffi::c_char; use std::ffi::c_void; use std::sync::Arc; -use celix::BundleActivator; +use celix::{BundleActivator, LogHelper}; use celix::BundleContext; use celix::Error; use rust_shell_api::{RustShellCommandTrait, RustShellCommandStruct}; @@ -92,7 +91,7 @@ impl RustShellCommandTrait for RustShellCommandImpl { struct ShellCommandActivator { ctx: Arc<BundleContext>, - //log_helper: Box<dyn LogHelper>, + log_helper: Arc<LogHelper>, shell_command_provider: CShellCommandImpl, registrations: Vec<celix::ServiceRegistration>, } @@ -111,19 +110,17 @@ impl ShellCommandActivator { .with_property("command.description", "Simple command written in Rust") .build()?; self.registrations.push(registration); - self.ctx.log_info("C Shell Command registered"); + self.log_helper.log_info("C Shell Command registered"); //Register Rust trait service register using a Box - let rust_shell_command = Box::new(RustShellCommandImpl::new(self.ctx.clone())); - let rust_shell_command = Box::<dyn RustShellCommandTrait>::from(rust_shell_command); + let rust_shell_command: Arc<dyn RustShellCommandTrait> = Arc::new(RustShellCommandImpl::new(self.ctx.clone())); let registration = self.ctx.register_service() - //maybe make svc types more explicit, e.g.with type parameters - .with_boxed_service(rust_shell_command) + .with_service(rust_shell_command) .with_property(rust_shell_api::COMMAND_NAME, "exe_rust_command") .with_property(rust_shell_api::COMMAND_DESCRIPTION, "Simple command written in a Rust trait") .build()?; self.registrations.push(registration); - self.ctx.log_info("Rust trait Shell Command registered"); + self.log_helper.log_info("Rust trait Shell Command registered"); //Register Rust struct service (with closures) as value @@ -140,53 +137,47 @@ impl ShellCommandActivator { .with_property("command.description", "Simple command written in a Rust struct using a Rust closure") .build()?; self.registrations.push(registration); - self.ctx.log_info("Rust struct Shell Command registered"); + self.log_helper.log_info("Rust struct Shell Command registered"); Ok(()) } fn use_services(&mut self) -> Result<(), Error> { //test using C service - self.ctx.log_info("Use C service command service"); + self.log_helper.log_info("Use C service command service"); let count = self.ctx.use_services() .with_service_name("celix_shell_command") .with_filter("(command.name=exe_c_command_in_rust)") .with_callback(Box::new( |svc: &celix_shell_command_t| { if let Some(exe_cmd) = svc.executeCommand { - let c_str = std::ffi::CString::new("test").unwrap(); + let c_str = std::ffi::CString::new("test c service").unwrap(); unsafe { exe_cmd(svc.handle, c_str.as_ptr() as *const c_char, std::ptr::null_mut(), std::ptr::null_mut()); } } })) .build()?; - self.ctx.log_info(format!("Found {} celix_shell_command_t services", count).as_str()); + self.log_helper.log_info(format!("Found {} celix_shell_command_t services", count).as_str()); //test using Rust trait service - self.ctx.log_info("Use Rust trait service command service"); + self.log_helper.log_info("Use Rust trait service command service"); let count = self.ctx.use_services() - .with_service::<dyn RustShellCommandTrait>() - .with_any_callback(Box::new( |svc: &dyn Any| { - //Mote below downcast, this is a hack. Cannot downcast to trait. - //Fixme trait service users should not need impl type (impl details) - let typed_svc = svc.downcast_ref::<RustShellCommandImpl>(); - if let Some(svc) = typed_svc { - let _ = svc.execute_command("test"); - } + .with_callback(Box::new( |svc: &Arc<dyn RustShellCommandTrait>| { + let _ = svc.execute_command("test rest trait"); })) .build()?; - self.ctx.log_info(format!("Found {} RustShellCommandTrait services", count).as_str()); + self.log_helper.log_info(format!("Found {} RustShellCommandTrait services", count).as_str()); - self.ctx.log_info("Use Rust struct service command service"); + self.log_helper.log_info("Use Rust struct service command service"); let count = self.ctx.use_services() .with_callback(Box::new( |svc: &RustShellCommandStruct| { let exe_cmd = svc.execute_command.as_ref(); - let _ = exe_cmd("test"); + let _ = exe_cmd("test rust struct"); })) .build()?; - self.ctx.log_info(format!("Found {} RustShellCommandStruct services", count).as_str()); + self.log_helper.log_info(format!("Found {} RustShellCommandStruct services", count).as_str()); - self.ctx.log_info("Rust Shell Command started"); + self.log_helper.log_info("Rust Shell Command started"); Ok(()) } } @@ -196,7 +187,7 @@ impl BundleActivator for ShellCommandActivator { let result = ShellCommandActivator { ctx: ctx.clone(), shell_command_provider: CShellCommandImpl::new(ctx.clone()), - //log_helper: log_helper_new(&*ctx, "ShellCommandBundle"), + log_helper: LogHelper::new(ctx.clone(), "ShellCommandBundle"), registrations: Vec::new(), }; result