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 1654e21e52199dc8dacddbd1ef1bfb575f496c86
Author: Pepijn Noltes <pepijnnol...@gmail.com>
AuthorDate: Tue Aug 22 20:24:42 2023 +0200

    #599: Refactor bundle context for service registration builder
---
 misc/experimental/rust/CMakeLists.txt              |  22 +-
 misc/experimental/rust/Cargo.toml                  |   1 +
 misc/experimental/rust/celix/src/bundle_context.rs | 227 ++++++++++++++++++++-
 misc/experimental/rust/celix/src/lib.rs            |   1 +
 misc/experimental/rust/celix/src/log_level.rs      |  40 ++--
 .../rust/{ => shell_command_bundle}/Cargo.toml     |  19 +-
 .../rust/shell_command_bundle/src/lib.rs           | 107 ++++++++++
 7 files changed, 373 insertions(+), 44 deletions(-)

diff --git a/misc/experimental/rust/CMakeLists.txt 
b/misc/experimental/rust/CMakeLists.txt
index a98150f2..6ab2287c 100644
--- a/misc/experimental/rust/CMakeLists.txt
+++ b/misc/experimental/rust/CMakeLists.txt
@@ -41,11 +41,11 @@ if (CELIX_RUST_EXPERIMENTAL)
     add_celix_bundle(rust_bundle ACTIVATOR ${ACTUAL_LIB_TARGET})
     add_dependencies(rust_bundle rust_bundle_activator)
 
-#    corrosion_add_target_local_rustflags(rust_shell_tui_activator 
"-Cprefer-dynamic")
-#    corrosion_link_libraries(rust_shell_tui_activator Celix::framework)
-#    get_target_property(ACTUAL_LIB_TARGET rust_shell_tui_activator 
INTERFACE_LINK_LIBRARIES)
-#    add_celix_bundle(rust_shell_tui ACTIVATOR ${ACTUAL_LIB_TARGET})
-#    add_dependencies(rust_shell_tui rust_shell_tui_activator)
+    corrosion_add_target_local_rustflags(rust_shell_command_activator 
"-Cprefer-dynamic")
+    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})
+    add_dependencies(rust_shell_command rust_shell_command_activator)
 
     add_celix_container(rust_container NO_COPY
         BUNDLES
@@ -54,11 +54,11 @@ if (CELIX_RUST_EXPERIMENTAL)
             rust_bundle
     )
 
-#    add_celix_container(rust_shell_tui_cnt NO_COPY
-#        BUNDLES
-#            Celix::shell
-#            Celix::shell_tui
-#            rust_shell_tui
-#    )
+    add_celix_container(rust_shell_cnt NO_COPY
+        BUNDLES
+            Celix::shell
+            Celix::shell_tui
+            rust_shell_command
+    )
 
 endif()
diff --git a/misc/experimental/rust/Cargo.toml 
b/misc/experimental/rust/Cargo.toml
index d6679dd3..95f253d0 100644
--- a/misc/experimental/rust/Cargo.toml
+++ b/misc/experimental/rust/Cargo.toml
@@ -20,5 +20,6 @@ members = [
     "celix_bindings",
     "celix",
     "hello_world_activator",
+    "shell_command_bundle",
     #"rust_shell_tui",
 ]
diff --git a/misc/experimental/rust/celix/src/bundle_context.rs 
b/misc/experimental/rust/celix/src/bundle_context.rs
index 4bfacce2..5ff380a4 100644
--- a/misc/experimental/rust/celix/src/bundle_context.rs
+++ b/misc/experimental/rust/celix/src/bundle_context.rs
@@ -17,11 +17,199 @@
  * under the License.
  */
 
-use super::LogLevel;
+use std::ptr::null_mut;
+use std::ffi::c_void;
+use std::collections::HashMap;
+use std::sync::Arc;
+use std::sync::Weak;
+use std::any::type_name;
+use std::any::Any;
+use std::sync::Mutex;
 
 use celix_bindings::celix_bundle_context_t;
 use celix_bindings::celix_bundleContext_log;
-use celix_bindings::celix_log_level_e;
+use celix_bindings::celix_properties_create;
+use celix_bindings::celix_properties_set;
+use celix_bindings::celix_bundleContext_registerServiceWithOptions;
+use celix_bindings::celix_bundleContext_unregisterService;
+use celix_bindings::celix_service_registration_options_t;
+
+use super::Error;
+use super::LogLevel;
+
+pub struct ServiceRegistration {
+    service_id: i64,
+    weak_ctx: Weak<BundleContextImpl>,
+    boxed_svc: Option<Box<dyn Any>>,
+    // arc_svc: Option<Arc<dyn Any>>,
+}
+
+impl Drop for ServiceRegistration {
+    fn drop(&mut self) {
+        let ctx = self.weak_ctx.upgrade();
+        match ctx {
+            Some(ctx) => ctx.unregister_service(self.service_id),
+            None => println!("Cannot unregister ServiceRegistration: 
BundleContext is gone"),
+        }
+    }
+}
+
+pub struct ServiceRegistrationBuilder<'a> {
+    ctx: &'a BundleContextImpl,
+    boxed_svc: Option<Box<dyn Any>>,
+    // _arc_svc: Option<Arc<dyn Any>>,
+    unmanaged_svc: *mut c_void,
+    service_name: String,
+    service_version: String,
+    service_properties: HashMap<String, String>,
+}
+
+impl ServiceRegistrationBuilder<'_> {
+    fn new<'a>(ctx: &'a BundleContextImpl) -> ServiceRegistrationBuilder<'a> {
+        ServiceRegistrationBuilder {
+            ctx,
+            boxed_svc: None,
+            //arc_svc: None,
+            unmanaged_svc: null_mut(),
+            service_name: "".to_string(),
+            service_version: "".to_string(),
+            service_properties: HashMap::new(),
+        }
+    }
+
+    pub fn with_service_name(&mut self, name: &str) -> &mut Self {
+        self.service_name = name.to_string();
+        self
+    }
+
+    fn with_type_name_as_service_name<I>(&mut self) -> &mut Self {
+        if self.service_name.is_empty() {
+            self.service_name = type_name::<I>().to_string();
+        }
+        self
+    }
+
+    pub fn with_service<I:'static>(&mut self, instance: I) -> &mut Self {
+        self.boxed_svc = Some(Box::new(instance));
+        //self.arc_svc = None;
+        self.unmanaged_svc = null_mut();
+        self.with_type_name_as_service_name::<I>()
+    }
+
+    //TODO check if dyn is needed (e.g. a trait object is needed)
+    pub fn with_boxed_service<I:'static>(&mut self, instance: Box</*dyn*/ I>) 
-> &mut Self {
+        self.boxed_svc = Some(instance);
+        //self.arc_svc = None;
+        self.unmanaged_svc = null_mut();
+        self.with_type_name_as_service_name::<I>()
+    }
+
+    //TODO check if dyn is needed (e.g. a trait object is needed)
+    // pub fn with_arc_instance<I:'static>(&mut self, instance: Arc</*dyn*/ 
I>) -> &mut Self {
+    //     self.boxed_svc = None;
+    //     self.arc_svc = Some(instance);
+    //     self.unmanaged_svc = null_mut();
+    //     self.with_type_name_as_service_name::<I>()
+    // }
+
+    pub fn with_unmanaged_service<I>(&mut self, instance: *mut I) -> &mut Self 
{
+        self.boxed_svc = None;
+        //self.arc_svc = None;
+        self.unmanaged_svc = instance as *mut c_void;
+        self.with_type_name_as_service_name::<I>()
+    }
+
+    pub fn with_version(&mut self, version: &str) -> &mut Self {
+        self.service_version = version.to_string();
+        self
+    }
+
+    pub fn with_properties(&mut self, properties: HashMap<String, String>) -> 
&mut Self {
+        self.service_properties = properties;
+        self
+    }
+
+    pub fn with_property(&mut self, key: &str, value: &str) -> &mut Self {
+        self.service_properties.insert(key.to_string(), value.to_string());
+        self
+    }
+
+    fn validate(&self) -> Result<(), Error> {
+        let mut valid = true;
+        if self.service_name.is_empty() {
+            self.ctx.log_error("Cannot register service. Service name is 
empty");
+            valid = false;
+        }
+        if self.boxed_svc.is_none() && /*self.arc_svc.is_none() &&*/ 
self.unmanaged_svc.is_null() {
+            self.ctx.log_error("Cannot register service. No instance 
provided");
+            valid = false;
+        }
+        match valid {
+            true => Ok(()),
+            false => Err(Error::BundleException),
+        }
+    }
+
+    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
+        // } else if let Some(arc_svc) = self.arc_svc.as_mut() {
+        //     let any_svc: &mut dyn Any = arc_svc.as_mut();
+        //     let arc_svc_ptr = arc_svc as *mut dyn Any; //note arc still 
owns the instance
+        //     arc_svc_ptr as *mut c_void
+        } else if self.unmanaged_svc.is_null() {
+            panic!("Cannot get c_svc. No instance provided");
+        } else {
+            self.unmanaged_svc as *mut c_void
+        }
+    }
+
+    unsafe fn build_unsafe(&mut self) -> Result<ServiceRegistration, Error> {
+        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();
+        let c_service = self.get_c_svc();
+        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: c_service,
+            factory: null_mut(),
+            serviceName: c_service_name.as_ptr() as *const i8,
+            properties: c_service_properties,
+            serviceLanguage: null_mut(),
+            serviceVersion: if self.service_version.is_empty() { null_mut() } 
else { c_service_version.as_ptr() as *const i8},
+            asyncData: null_mut(),
+            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(), //to ensure that a possible 
box instance is not dropped
+                // arc_svc: self.arc_svc.take(), //to ensure that a possible 
arc instance is not dropped
+            })
+        } else {
+            Err(Error::BundleException)
+        }
+    }
+
+    pub fn build(&mut self) -> Result<ServiceRegistration, Error> {
+        self.validate()?;
+        unsafe {
+            //TODO make unsafe part smaller (if possible)
+            self.build_unsafe()
+        }
+    }
+}
 
 pub trait BundleContext {
     fn get_c_bundle_context(&self) -> *mut celix_bundle_context_t;
@@ -39,18 +227,41 @@ pub trait BundleContext {
     fn log_error(&self, message: &str);
 
     fn log_fatal(&self, message: &str);
+
+    fn register_service(&self) -> ServiceRegistrationBuilder;
 }
 
 struct BundleContextImpl {
     c_bundle_context: *mut celix_bundle_context_t,
+    weak_self : Mutex<Option<Weak<BundleContextImpl>>>,
 }
 
 impl BundleContextImpl {
-    pub fn new(c_bundle_context: *mut celix_bundle_context_t) -> Self {
-        BundleContextImpl {
+    fn new(c_bundle_context: *mut celix_bundle_context_t) -> Arc<Self> {
+        let ctx = Arc::new(BundleContextImpl {
             c_bundle_context,
+            weak_self: Mutex::new(None),
+        });
+        let weak_ref = Arc::downgrade(&ctx);
+        ctx.set_self(weak_ref);
+        ctx
+    }
+
+    fn set_self(&self, weak_self: Weak<BundleContextImpl>) {
+        let mut guard = self.weak_self.lock().unwrap();
+        *guard = Some(weak_self);
+    }
+
+    fn get_self(&self) -> Weak<BundleContextImpl> {
+        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);
@@ -84,8 +295,12 @@ impl BundleContext for BundleContextImpl {
     fn log_error(&self, message: &str){ self.log(LogLevel::Error, message); }
 
     fn log_fatal(&self, message: &str){ self.log(LogLevel::Fatal, message); }
+
+    fn register_service(&self) -> ServiceRegistrationBuilder {
+        ServiceRegistrationBuilder::new(self)
+    }
 }
 
-pub fn bundle_context_new(c_bundle_context: *mut celix_bundle_context_t) -> 
Box<dyn BundleContext> {
-    Box::new(BundleContextImpl::new(c_bundle_context))
+pub fn bundle_context_new(c_bundle_context: *mut celix_bundle_context_t) -> 
Arc<dyn BundleContext> {
+    BundleContextImpl::new(c_bundle_context)
 }
diff --git a/misc/experimental/rust/celix/src/lib.rs 
b/misc/experimental/rust/celix/src/lib.rs
index 791a3cda..9befdeb8 100644
--- a/misc/experimental/rust/celix/src/lib.rs
+++ b/misc/experimental/rust/celix/src/lib.rs
@@ -41,6 +41,7 @@ pub use self::log_level::LogLevel as LogLevel;
 mod bundle_context;
 // Re-export bundle context types in the public API.
 pub use self::bundle_context::BundleContext as BundleContext;
+pub use self::bundle_context::ServiceRegistration as ServiceRegistration;
 pub use self::bundle_context::bundle_context_new as bundle_context_new;
 
 mod bundle_activator;
diff --git a/misc/experimental/rust/celix/src/log_level.rs 
b/misc/experimental/rust/celix/src/log_level.rs
index d2b22470..bc9b5e48 100644
--- a/misc/experimental/rust/celix/src/log_level.rs
+++ b/misc/experimental/rust/celix/src/log_level.rs
@@ -18,13 +18,13 @@
  */
 
 use celix_bindings::celix_log_level_e;
-use celix_bindings::celix_log_level_CELIX_LOG_LEVEL_TRACE;
-use celix_bindings::celix_log_level_CELIX_LOG_LEVEL_DEBUG;
-use celix_bindings::celix_log_level_CELIX_LOG_LEVEL_INFO;
-use celix_bindings::celix_log_level_CELIX_LOG_LEVEL_WARNING;
-use celix_bindings::celix_log_level_CELIX_LOG_LEVEL_ERROR;
-use celix_bindings::celix_log_level_CELIX_LOG_LEVEL_FATAL;
-use celix_bindings::celix_log_level_CELIX_LOG_LEVEL_DISABLED;
+use celix_bindings::celix_log_level_CELIX_LOG_LEVEL_TRACE as 
CELIX_LOG_LEVEL_TRACE;
+use celix_bindings::celix_log_level_CELIX_LOG_LEVEL_DEBUG as 
CELIX_LOG_LEVEL_DEBUG;
+use celix_bindings::celix_log_level_CELIX_LOG_LEVEL_INFO as 
CELIX_LOG_LEVEL_INFO;
+use celix_bindings::celix_log_level_CELIX_LOG_LEVEL_WARNING as 
CELIX_LOG_LEVEL_WARNING;
+use celix_bindings::celix_log_level_CELIX_LOG_LEVEL_ERROR as 
CELIX_LOG_LEVEL_ERROR;
+use celix_bindings::celix_log_level_CELIX_LOG_LEVEL_FATAL as 
CELIX_LOG_LEVEL_FATAL;
+use celix_bindings::celix_log_level_CELIX_LOG_LEVEL_DISABLED as 
CELIX_LOG_LEVEL_DISABLED;
 
 pub enum LogLevel {
     Trace,
@@ -38,12 +38,12 @@ pub enum LogLevel {
 impl From<celix_log_level_e> for LogLevel {
     fn from(level: celix_log_level_e) -> Self {
         match level {
-            celix_log_level_CELIX_LOG_LEVEL_TRACE => LogLevel::Trace,
-            celix_log_level_CELIX_LOG_LEVEL_DEBUG => LogLevel::Debug,
-            celix_log_level_CELIX_LOG_LEVEL_INFO => LogLevel::Info,
-            celix_log_level_CELIX_LOG_LEVEL_WARNING => LogLevel::Warning,
-            celix_log_level_CELIX_LOG_LEVEL_ERROR => LogLevel::Error,
-            celix_log_level_CELIX_LOG_LEVEL_FATAL => LogLevel::Fatal,
+            CELIX_LOG_LEVEL_TRACE => LogLevel::Trace,
+            CELIX_LOG_LEVEL_DEBUG => LogLevel::Debug,
+            CELIX_LOG_LEVEL_INFO => LogLevel::Info,
+            CELIX_LOG_LEVEL_WARNING => LogLevel::Warning,
+            CELIX_LOG_LEVEL_ERROR => LogLevel::Error,
+            CELIX_LOG_LEVEL_FATAL => LogLevel::Fatal,
             _ => LogLevel::Disabled,
         }
     }
@@ -52,13 +52,13 @@ impl From<celix_log_level_e> for LogLevel {
 impl Into<celix_log_level_e> for LogLevel {
     fn into(self) -> celix_log_level_e {
         match self {
-            LogLevel::Trace => celix_log_level_CELIX_LOG_LEVEL_TRACE,
-            LogLevel::Debug => celix_log_level_CELIX_LOG_LEVEL_DEBUG,
-            LogLevel::Info => celix_log_level_CELIX_LOG_LEVEL_INFO,
-            LogLevel::Warning => celix_log_level_CELIX_LOG_LEVEL_WARNING,
-            LogLevel::Error => celix_log_level_CELIX_LOG_LEVEL_ERROR,
-            LogLevel::Fatal => celix_log_level_CELIX_LOG_LEVEL_FATAL,
-            _ => celix_log_level_CELIX_LOG_LEVEL_DISABLED,
+            LogLevel::Trace => CELIX_LOG_LEVEL_TRACE,
+            LogLevel::Debug => CELIX_LOG_LEVEL_DEBUG,
+            LogLevel::Info => CELIX_LOG_LEVEL_INFO,
+            LogLevel::Warning => CELIX_LOG_LEVEL_WARNING,
+            LogLevel::Error => CELIX_LOG_LEVEL_ERROR,
+            LogLevel::Fatal => CELIX_LOG_LEVEL_FATAL,
+            _ => CELIX_LOG_LEVEL_DISABLED,
         }
     }
 }
diff --git a/misc/experimental/rust/Cargo.toml 
b/misc/experimental/rust/shell_command_bundle/Cargo.toml
similarity index 75%
copy from misc/experimental/rust/Cargo.toml
copy to misc/experimental/rust/shell_command_bundle/Cargo.toml
index d6679dd3..1773c986 100644
--- a/misc/experimental/rust/Cargo.toml
+++ b/misc/experimental/rust/shell_command_bundle/Cargo.toml
@@ -15,10 +15,15 @@
 # specific language governing permissions and limitations
 # under the License.
 
-[workspace]
-members = [
-    "celix_bindings",
-    "celix",
-    "hello_world_activator",
-    #"rust_shell_tui",
-]
+[package]
+name = "rust_shell_command_activator"
+version = "0.0.1"
+
+[dependencies]
+celix_bindings = { path = "../celix_bindings" }
+celix = { path = "../celix" }
+
+[lib]
+name = "rust_shell_command_activator"
+path = "src/lib.rs"
+crate-type = ["cdylib"]
diff --git a/misc/experimental/rust/shell_command_bundle/src/lib.rs 
b/misc/experimental/rust/shell_command_bundle/src/lib.rs
new file mode 100644
index 00000000..cc9b83f9
--- /dev/null
+++ b/misc/experimental/rust/shell_command_bundle/src/lib.rs
@@ -0,0 +1,107 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+extern crate celix_bindings;
+extern crate celix;
+
+use std::sync::Arc;
+use std::ffi::c_void;
+use std::ffi::c_char;
+
+use celix::BundleActivator;
+use celix::BundleContext;
+use celix::Error;
+
+use celix_bindings::celix_shell_command_t;
+use celix_bindings::FILE;
+
+//temporary, should be moved in a separate API crate
+// trait RustShellCommand {
+//     fn execute_command(&mut self, command_line: &str, command_args: 
Vec<&str>) -> Result<(), Error>;
+// }
+
+struct ShellCommandProvider {
+    ctx: Arc<dyn BundleContext>,
+}
+impl ShellCommandProvider {
+    fn new(ctx: Arc<dyn BundleContext>) -> Self {
+        ctx.log_info("Shell Command created");
+        ShellCommandProvider{
+            ctx,
+        }
+    }
+
+    extern "C" fn call_execute_command(handle: *mut c_void, command_line: 
*const c_char, _out_stream: *mut FILE, _error_stream: *mut FILE) -> bool {
+        if handle.is_null() || command_line.is_null() {
+            return false;
+        }
+        unsafe {
+            println!("call_execute_command");
+            let obj = &mut *(handle as *mut ShellCommandProvider);
+            let str_command_line = 
std::ffi::CStr::from_ptr(command_line).to_str().unwrap();
+            obj.execute_command(str_command_line);
+        }
+        true
+    }
+
+    fn execute_command(&mut self, command_line: &str) {
+        self.ctx.log_info(format!("Execute command: {}", 
command_line).as_str());
+    }
+}
+
+struct ShellCommandActivator {
+    ctx: Arc<dyn BundleContext>,
+    //log_helper: Box<dyn LogHelper>,
+    shell_command_provider: ShellCommandProvider,
+    registration: Option<celix::ServiceRegistration>,
+}
+
+impl BundleActivator for ShellCommandActivator {
+    fn new(ctx: Arc<dyn celix::BundleContext>) -> Self {
+        let result = ShellCommandActivator {
+            ctx: ctx.clone(),
+            shell_command_provider: ShellCommandProvider::new(ctx.clone()),
+            //log_helper: log_helper_new(&*ctx, "ShellCommandBundle"),
+            registration: None,
+        };
+        result
+    }
+    fn start(&mut self) -> Result<(), Error> {
+        let registration = self.ctx.register_service()
+            .with_service( celix_shell_command_t{
+                handle: &mut self.shell_command_provider as *mut 
ShellCommandProvider as *mut c_void,
+                executeCommand: 
Some(ShellCommandProvider::call_execute_command),
+                })
+            .with_service_name("celix_shell_command")
+            .with_property("command.name", "exe_in_rust")
+            .with_property("command.description", "Simple command written in 
Rust")
+            .build()?;
+        self.registration = Some(registration);
+        self.ctx.log_info("Rust Shell Command started");
+        Ok(())
+    }
+
+    fn stop(&mut self) -> Result<(), Error> {
+        self.registration = None;
+        self.ctx.log_info("Rust Shell Command stopped");
+        Ok(())
+    }
+}
+
+celix::generate_bundle_activator!(ShellCommandActivator);

Reply via email to