diff --git a/c/csrc/include/longbridge.h b/c/csrc/include/longbridge.h index b12880f15..0d02bd504 100644 --- a/c/csrc/include/longbridge.h +++ b/c/csrc/include/longbridge.h @@ -1665,6 +1665,48 @@ typedef struct lb_async_result_t { typedef void (*lb_async_callback_t)(const struct lb_async_result_t*); +/** + * A single alert indicator configuration for a symbol. + */ +typedef struct lb_alert_item_t { + /** + * Unique alert identifier. + */ + const char *id; + /** + * Identifier of the indicator that triggers this alert. + */ + const char *indicator_id; + /** + * Whether this alert is currently enabled. + */ + bool enabled; + /** + * Alert notification frequency code. + */ + int32_t frequency; + /** + * Scope of the alert (e.g. per-symbol or global). + */ + int32_t scope; + /** + * Human-readable description text for the alert. + */ + const char *text; + /** + * Pointer to an array of state codes associated with this alert. + */ + const int32_t *state; + /** + * Number of elements in the `state` array. + */ + uintptr_t num_state; + /** + * JSON-serialized map of additional indicator parameter values. + */ + const char *value_map; +} lb_alert_item_t; + /** * HTTP Header */ @@ -6700,48 +6742,6 @@ typedef struct lb_exchange_rates_t { uintptr_t num_exchanges; } lb_exchange_rates_t; -/** - * A single alert indicator configuration for a symbol. - */ -typedef struct lb_alert_item_t { - /** - * Unique alert identifier. - */ - const char *id; - /** - * Identifier of the indicator that triggers this alert. - */ - const char *indicator_id; - /** - * Whether this alert is currently enabled. - */ - bool enabled; - /** - * Alert notification frequency code. - */ - int32_t frequency; - /** - * Scope of the alert (e.g. per-symbol or global). - */ - int32_t scope; - /** - * Human-readable description text for the alert. - */ - const char *text; - /** - * Pointer to an array of state codes associated with this alert. - */ - const int32_t *state; - /** - * Number of elements in the `state` array. - */ - uintptr_t num_state; - /** - * JSON-serialized map of additional indicator parameter values. - */ - const char *value_map; -} lb_alert_item_t; - /** * A symbol together with all of its associated alert indicators. */ @@ -7877,21 +7877,19 @@ void lb_alert_context_add(const struct lb_alert_context_t *ctx, void *userdata); /** - * Enable a price alert. - */ -void lb_alert_context_enable(const struct lb_alert_context_t *ctx, - const char *alert_id, + * Update (enable or disable) a price alert. + * + * `item` must point to a valid [`CAlertItem`] obtained from + * [`lb_alert_context_list`]. Set `enabled` to `true` to re-enable or + * `false` to disable. All fields of `item` are read before the function + * returns, so the pointer only needs to be valid for the duration of + * the call. + */ +void lb_alert_context_update(const struct lb_alert_context_t *ctx, + const struct lb_alert_item_t *item, lb_async_callback_t callback, void *userdata); -/** - * Disable a price alert. - */ -void lb_alert_context_disable(const struct lb_alert_context_t *ctx, - const char *alert_id, - lb_async_callback_t callback, - void *userdata); - /** * Delete price alerts. alert_ids: array of alert ID strings, num_ids: count. */ diff --git a/c/src/alert_context/context.rs b/c/src/alert_context/context.rs index a7f82de25..72b46eb2b 100644 --- a/c/src/alert_context/context.rs +++ b/c/src/alert_context/context.rs @@ -65,34 +65,24 @@ pub unsafe extern "C" fn lb_alert_context_add( }); } -/// Enable a price alert. +/// Update (enable or disable) a price alert. +/// +/// `item` must point to a valid [`CAlertItem`] obtained from +/// [`lb_alert_context_list`]. Set `enabled` to `true` to re-enable or +/// `false` to disable. All fields of `item` are read before the function +/// returns, so the pointer only needs to be valid for the duration of +/// the call. #[unsafe(no_mangle)] -pub unsafe extern "C" fn lb_alert_context_enable( +pub unsafe extern "C" fn lb_alert_context_update( ctx: *const CAlertContext, - alert_id: *const c_char, + item: *const CAlertItem, callback: CAsyncCallback, userdata: *mut c_void, ) { let ctx_inner = (*ctx).ctx.clone(); - let id = cstr_to_rust(alert_id); + let alert_item = (*item).to_alert_item(); execute_async(callback, ctx, userdata, async move { - ctx_inner.enable(id).await?; - Ok(()) - }); -} - -/// Disable a price alert. -#[unsafe(no_mangle)] -pub unsafe extern "C" fn lb_alert_context_disable( - ctx: *const CAlertContext, - alert_id: *const c_char, - callback: CAsyncCallback, - userdata: *mut c_void, -) { - let ctx_inner = (*ctx).ctx.clone(); - let id = cstr_to_rust(alert_id); - execute_async(callback, ctx, userdata, async move { - ctx_inner.disable(id).await?; + ctx_inner.update(&alert_item).await?; Ok(()) }); } diff --git a/c/src/alert_context/types.rs b/c/src/alert_context/types.rs index ef5457b31..338c1e79b 100644 --- a/c/src/alert_context/types.rs +++ b/c/src/alert_context/types.rs @@ -55,6 +55,30 @@ impl From for CAlertItemOwned { } } +impl CAlertItem { + /// Reconstruct a [`longbridge::alert::AlertItem`] from this C struct. + /// + /// # Safety + /// All pointer fields must be valid null-terminated C strings and the + /// `state` pointer must point to at least `num_state` valid `i32` values. + pub unsafe fn to_alert_item(&self) -> longbridge::alert::AlertItem { + use crate::types::cstr_to_rust; + let state = std::slice::from_raw_parts(self.state, self.num_state).to_vec(); + let value_map_str = cstr_to_rust(self.value_map); + let value_map = serde_json::from_str(&value_map_str).unwrap_or(serde_json::Value::Null); + longbridge::alert::AlertItem { + id: cstr_to_rust(self.id), + indicator_id: cstr_to_rust(self.indicator_id), + enabled: self.enabled, + frequency: self.frequency, + scope: self.scope, + text: cstr_to_rust(self.text), + state, + value_map, + } + } +} + impl ToFFI for CAlertItemOwned { type FFIType = CAlertItem; fn to_ffi_type(&self) -> Self::FFIType { diff --git a/cpp/include/alert_context.hpp b/cpp/include/alert_context.hpp index ca6ea3f29..a2b9f8ef4 100644 --- a/cpp/include/alert_context.hpp +++ b/cpp/include/alert_context.hpp @@ -45,12 +45,10 @@ class AlertContext { void add(const std::string& symbol, AlertCondition condition, const std::string& trigger_value, AlertFrequency frequency, AsyncCallback callback) const; - /// Enable a price alert by alert_id. - void enable(const std::string& alert_id, + /// Update (enable or disable) a price alert. + /// Set item.enabled before calling to choose the new state. + void update(const AlertItem& item, AsyncCallback callback) const; - /// Disable a price alert by alert_id. - void disable(const std::string& alert_id, - AsyncCallback callback) const; }; } // namespace alert diff --git a/cpp/src/alert_context.cpp b/cpp/src/alert_context.cpp index bc02f849b..2c890a94d 100644 --- a/cpp/src/alert_context.cpp +++ b/cpp/src/alert_context.cpp @@ -8,8 +8,7 @@ void lb_alert_context_retain(const lb_alert_context_t*); void lb_alert_context_release(const lb_alert_context_t*); void lb_alert_context_list(const lb_alert_context_t*, lb_async_callback_t, void*); void lb_alert_context_add(const lb_alert_context_t*, const char*, lb_alert_condition_t, const char*, lb_alert_frequency_t, lb_async_callback_t, void*); -void lb_alert_context_enable(const lb_alert_context_t*, const char*, lb_async_callback_t, void*); -void lb_alert_context_disable(const lb_alert_context_t*, const char*, lb_async_callback_t, void*); +void lb_alert_context_update(const lb_alert_context_t*, const lb_alert_item_t*, lb_async_callback_t, void*); } namespace longbridge { @@ -50,20 +49,21 @@ void AlertContext::add(const std::string& symbol, AlertCondition condition, }, new AsyncCallback(callback)); } -void AlertContext::enable(const std::string& alert_id, +void AlertContext::update(const AlertItem& item, AsyncCallback callback) const { - lb_alert_context_enable(ctx_, alert_id.c_str(), - [](auto res) { - auto cb = callback::get_async_callback(res->userdata); - AlertContext fctx((const lb_alert_context_t*)res->ctx); - Status status(res->error); - (*cb)(AsyncResult(fctx, std::move(status), nullptr)); - }, new AsyncCallback(callback)); -} - -void AlertContext::disable(const std::string& alert_id, - AsyncCallback callback) const { - lb_alert_context_disable(ctx_, alert_id.c_str(), + // Build a lb_alert_item_t from the C++ AlertItem to pass to the C layer. + std::vector state_copy = item.state; + lb_alert_item_t c_item{}; + c_item.id = item.id.c_str(); + c_item.indicator_id = item.indicator_id.c_str(); + c_item.enabled = item.enabled; + c_item.frequency = item.frequency; + c_item.scope = item.scope; + c_item.text = item.text.c_str(); + c_item.state = state_copy.data(); + c_item.num_state = state_copy.size(); + c_item.value_map = item.value_map.c_str(); + lb_alert_context_update(ctx_, &c_item, [](auto res) { auto cb = callback::get_async_callback(res->userdata); AlertContext fctx((const lb_alert_context_t*)res->ctx); diff --git a/java/src/alert_context.rs b/java/src/alert_context.rs index 418c0f03f..57eea137a 100644 --- a/java/src/alert_context.rs +++ b/java/src/alert_context.rs @@ -9,9 +9,47 @@ use longbridge::{AlertContext, Config, alert::types::*}; use crate::{ async_util, error::jni_result, - types::{FromJValue, ObjectArray, get_field}, + types::{ObjectArray, get_field}, }; +/// Read a Java `AlertItem` object into a Rust [`longbridge::alert::AlertItem`]. +fn read_alert_item( + env: &mut JNIEnv, + item: &JObject, +) -> jni::errors::Result { + let id: String = get_field(env, item, "id")?; + let indicator_id: String = get_field(env, item, "indicatorId")?; + let enabled: bool = get_field(env, item, "enabled")?; + let frequency: i32 = get_field(env, item, "frequency")?; + let scope: i32 = get_field(env, item, "scope")?; + let text: String = get_field(env, item, "text")?; + // state: int[] — read as a Java int array + let state = unsafe { + let state_obj = env.get_field(item, "state", "[I")?.l()?; + if state_obj.is_null() { + Vec::new() + } else { + let arr = jni::objects::JIntArray::from(state_obj); + let elements = env + .get_array_elements::(&arr, jni::objects::ReleaseMode::CopyBack)?; + std::slice::from_raw_parts(elements.as_ptr(), elements.len()).to_vec() + } + }; + // valueMap: JSON string + let value_map_str: String = get_field(env, item, "valueMap")?; + let value_map = serde_json::from_str(&value_map_str).unwrap_or(serde_json::Value::Null); + Ok(longbridge::alert::AlertItem { + id, + indicator_id, + enabled, + frequency, + scope, + text, + state, + value_map, + }) +} + struct ContextObj { ctx: AlertContext, } @@ -80,37 +118,18 @@ pub unsafe extern "system" fn Java_com_longbridge_SdkNative_alertContextAdd( } #[unsafe(no_mangle)] -pub unsafe extern "system" fn Java_com_longbridge_SdkNative_alertContextEnable( - mut env: JNIEnv, - _class: JClass, - context: i64, - alert_id: JObject, - callback: JObject, -) { - jni_result(&mut env, (), |env| { - let context = &*(context as *const ContextObj); - let id: String = FromJValue::from_jvalue(env, alert_id.into())?; - async_util::execute(env, callback, async move { - context.ctx.enable(id).await?; - Ok(()) - })?; - Ok(()) - }) -} - -#[unsafe(no_mangle)] -pub unsafe extern "system" fn Java_com_longbridge_SdkNative_alertContextDisable( +pub unsafe extern "system" fn Java_com_longbridge_SdkNative_alertContextUpdate( mut env: JNIEnv, _class: JClass, context: i64, - alert_id: JObject, + item: JObject, callback: JObject, ) { jni_result(&mut env, (), |env| { let context = &*(context as *const ContextObj); - let id: String = FromJValue::from_jvalue(env, alert_id.into())?; + let alert_item = read_alert_item(env, &item)?; async_util::execute(env, callback, async move { - context.ctx.disable(id).await?; + context.ctx.update(&alert_item).await?; Ok(()) })?; Ok(()) diff --git a/nodejs/src/alert/context.rs b/nodejs/src/alert/context.rs index 9b273aee3..a234df3c6 100644 --- a/nodejs/src/alert/context.rs +++ b/nodejs/src/alert/context.rs @@ -45,17 +45,14 @@ impl AlertContext { Ok(()) } - /// Enable a previously disabled price alert. - #[napi] - pub async fn enable(&self, alert_id: String) -> Result<()> { - self.ctx.enable(alert_id).await.map_err(ErrorNewType)?; - Ok(()) - } - - /// Disable a price alert without deleting it. + /// Update a price alert. + /// + /// Pass the [`AlertItem`] obtained from [`list`](Self::list). Set + /// `item.enabled` to `true` to re-enable or `false` to disable before + /// calling this method. #[napi] - pub async fn disable(&self, alert_id: String) -> Result<()> { - self.ctx.disable(alert_id).await.map_err(ErrorNewType)?; + pub async fn update(&self, item: AlertItem) -> Result<()> { + self.ctx.update(&item.into()).await.map_err(ErrorNewType)?; Ok(()) } diff --git a/nodejs/src/alert/types.rs b/nodejs/src/alert/types.rs index a80d943c3..d861c29e5 100644 --- a/nodejs/src/alert/types.rs +++ b/nodejs/src/alert/types.rs @@ -36,6 +36,21 @@ impl From for AlertItem { } } +impl From for lb::AlertItem { + fn from(v: AlertItem) -> Self { + Self { + id: v.id, + indicator_id: v.indicator_id, + enabled: v.enabled, + frequency: v.frequency, + scope: v.scope, + text: v.text, + state: v.state, + value_map: v.value_map, + } + } +} + /// Alert items for one security #[napi_derive::napi(object)] #[derive(Debug, Clone)] diff --git a/python/src/alert/context.rs b/python/src/alert/context.rs index 3d542d615..772e6f588 100644 --- a/python/src/alert/context.rs +++ b/python/src/alert/context.rs @@ -33,12 +33,8 @@ impl AlertContext { .map_err(ErrorNewType)?; Ok(()) } - fn enable(&self, alert_id: String) -> PyResult<()> { - self.ctx.enable(alert_id).map_err(ErrorNewType)?; - Ok(()) - } - fn disable(&self, alert_id: String) -> PyResult<()> { - self.ctx.disable(alert_id).map_err(ErrorNewType)?; + fn update(&self, item: AlertItem) -> PyResult<()> { + self.ctx.update(item.into()).map_err(ErrorNewType)?; Ok(()) } fn delete(&self, alert_ids: Vec) -> PyResult<()> { diff --git a/python/src/alert/types.rs b/python/src/alert/types.rs index 04f191de0..c30e1d1bb 100644 --- a/python/src/alert/types.rs +++ b/python/src/alert/types.rs @@ -26,6 +26,7 @@ impl<'py> IntoPyObject<'py> for &JsonValue { pub(crate) struct AlertItem { pub id: String, pub indicator_id: String, + #[pyo3(set)] pub enabled: bool, pub frequency: i32, pub scope: i32, @@ -49,6 +50,43 @@ impl From for AlertItem { } } +impl From for lb::AlertItem { + fn from(v: AlertItem) -> Self { + Self { + id: v.id, + indicator_id: v.indicator_id, + enabled: v.enabled, + frequency: v.frequency, + scope: v.scope, + text: v.text, + state: v.state, + value_map: v.value_map.0, + } + } +} + +impl<'a, 'py> FromPyObject<'a, 'py> for AlertItem { + type Error = PyErr; + + fn extract(ob: pyo3::Borrowed<'a, 'py, PyAny>) -> PyResult { + let value_map = ob + .getattr("value_map") + .ok() + .and_then(|v| pythonize::depythonize::(&v).ok()) + .unwrap_or(serde_json::Value::Null); + Ok(AlertItem { + id: ob.getattr("id")?.extract()?, + indicator_id: ob.getattr("indicator_id")?.extract()?, + enabled: ob.getattr("enabled")?.extract()?, + frequency: ob.getattr("frequency")?.extract()?, + scope: ob.getattr("scope")?.extract()?, + text: ob.getattr("text")?.extract()?, + state: ob.getattr("state")?.extract()?, + value_map: JsonValue(value_map), + }) + } +} + #[pyclass(get_all, skip_from_py_object)] #[derive(Debug, Clone)] pub(crate) struct AlertSymbolGroup { diff --git a/rust/src/alert/context.rs b/rust/src/alert/context.rs index 2cc8b5255..0ac3d8c98 100644 --- a/rust/src/alert/context.rs +++ b/rust/src/alert/context.rs @@ -142,24 +142,26 @@ impl AlertContext { .await } - /// Enable a price alert. + /// Update a price alert. /// - /// Path: `POST /v1/notify/reminders` (with `id` + `enabled=true`) - pub async fn enable(&self, alert_id: impl Into) -> Result { - self.post( - "/v1/notify/reminders", - serde_json::json!({ "id": alert_id.into(), "enabled": true }), - ) - .await - } - - /// Disable a price alert. + /// Requires the [`AlertItem`] from [`list`](Self::list). Set + /// `item.enabled` to `true` to re-enable or `false` to disable before + /// calling this method. All required fields are read from `item` directly + /// — no extra round-trip needed. /// - /// Path: `POST /v1/notify/reminders` (with `id` + `enabled=false`) - pub async fn disable(&self, alert_id: impl Into) -> Result { + /// Path: `POST /v1/notify/reminders` + pub async fn update(&self, item: &AlertItem) -> Result { self.post( "/v1/notify/reminders", - serde_json::json!({ "id": alert_id.into(), "enabled": false }), + serde_json::json!({ + "id": item.id, + "indicator_id": item.indicator_id, + "frequency": item.frequency, + "scope": item.scope, + "state": item.state, + "value_map": item.value_map, + "enabled": item.enabled, + }), ) .await } diff --git a/rust/src/blocking/alert.rs b/rust/src/blocking/alert.rs index f57107cc3..9d4967b9f 100644 --- a/rust/src/blocking/alert.rs +++ b/rust/src/blocking/alert.rs @@ -43,21 +43,10 @@ impl AlertContextSync { ctx.add(symbol, condition, trigger_value, frequency).await }) } - /// Enable a price alert. - pub fn enable( - &self, - alert_id: impl Into + Send + 'static, - ) -> Result { - self.rt - .call(move |ctx| async move { ctx.enable(alert_id).await }) - } - /// Disable a price alert. - pub fn disable( - &self, - alert_id: impl Into + Send + 'static, - ) -> Result { + /// Update (enable or disable) a price alert. + pub fn update(&self, item: AlertItem) -> Result { self.rt - .call(move |ctx| async move { ctx.disable(alert_id).await }) + .call(move |ctx| async move { ctx.update(&item).await }) } /// Delete price alerts. pub fn delete(&self, alert_ids: Vec) -> Result {