1use std::net::SocketAddr;
4use std::ops::ControlFlow;
5use std::time::Duration;
6
7use pyo3::prelude::*;
8use pyo3::types::{PyDict, PyList};
9
10use spvirit_client::pva_client::PvaClient;
11use spvirit_client::search::{build_auto_broadcast_targets, discover_servers};
12
13use crate::convert::{decoded_to_py, py_to_json};
14use crate::errors::to_py_err;
15use crate::runtime::RUNTIME;
16
17#[pyclass(name = "GetResult")]
20pub struct PyGetResult {
21 #[pyo3(get)]
22 pub pv_name: String,
23 value: PyObject,
24 #[pyo3(get)]
25 pub raw_pva: Vec<u8>,
26 #[pyo3(get)]
27 pub raw_pvd: Vec<u8>,
28}
29
30impl PyGetResult {
31 pub(crate) fn new(
32 pv_name: String,
33 value: PyObject,
34 raw_pva: Vec<u8>,
35 raw_pvd: Vec<u8>,
36 ) -> Self {
37 Self {
38 pv_name,
39 value,
40 raw_pva,
41 raw_pvd,
42 }
43 }
44}
45
46#[pymethods]
47impl PyGetResult {
48 #[getter]
49 fn value(&self, py: Python<'_>) -> PyObject {
50 self.value.clone_ref(py)
51 }
52
53 fn __repr__(&self) -> String {
54 format!("GetResult(pv_name={:?})", self.pv_name)
55 }
56}
57
58#[pyclass(name = "MonitorEvent")]
61pub struct PyMonitorEvent {
62 #[pyo3(get)]
63 pub pv_name: String,
64 value: PyObject,
65}
66
67#[pymethods]
68impl PyMonitorEvent {
69 #[getter]
70 fn value(&self, py: Python<'_>) -> PyObject {
71 self.value.clone_ref(py)
72 }
73
74 fn __repr__(&self) -> String {
75 format!("MonitorEvent(pv_name={:?})", self.pv_name)
76 }
77}
78
79#[pyclass(name = "DiscoveredServer")]
82#[derive(Clone)]
83pub struct PyDiscoveredServer {
84 #[pyo3(get)]
85 pub guid: Vec<u8>,
86 #[pyo3(get)]
87 pub tcp_addr: String,
88}
89
90#[pymethods]
91impl PyDiscoveredServer {
92 fn __repr__(&self) -> String {
93 format!("DiscoveredServer(tcp_addr={:?})", self.tcp_addr)
94 }
95}
96
97#[pyclass(name = "ClientBuilder")]
100pub struct PyClientBuilder {
101 udp_port: u16,
102 tcp_port: u16,
103 timeout_secs: f64,
104 no_broadcast: bool,
105 name_servers: Vec<String>,
106 authnz_user: Option<String>,
107 authnz_host: Option<String>,
108 server_addr: Option<String>,
109 search_addr: Option<String>,
110 bind_addr: Option<String>,
111 debug: bool,
112}
113
114#[pymethods]
115impl PyClientBuilder {
116 #[new]
117 fn new() -> Self {
118 Self {
119 udp_port: 5076,
120 tcp_port: 5075,
121 timeout_secs: 5.0,
122 no_broadcast: false,
123 name_servers: Vec::new(),
124 authnz_user: None,
125 authnz_host: None,
126 server_addr: None,
127 search_addr: None,
128 bind_addr: None,
129 debug: false,
130 }
131 }
132
133 fn port(mut slf: PyRefMut<'_, Self>, port: u16) -> PyRefMut<'_, Self> {
134 slf.tcp_port = port;
135 slf
136 }
137
138 fn udp_port(mut slf: PyRefMut<'_, Self>, port: u16) -> PyRefMut<'_, Self> {
139 slf.udp_port = port;
140 slf
141 }
142
143 fn timeout(mut slf: PyRefMut<'_, Self>, secs: f64) -> PyRefMut<'_, Self> {
144 slf.timeout_secs = secs;
145 slf
146 }
147
148 fn no_broadcast(mut slf: PyRefMut<'_, Self>, enabled: bool) -> PyRefMut<'_, Self> {
149 slf.no_broadcast = enabled;
150 slf
151 }
152
153 fn name_server(mut slf: PyRefMut<'_, Self>, addr: String) -> PyResult<PyRefMut<'_, Self>> {
154 let _: SocketAddr = addr.parse().map_err(|e| {
155 pyo3::exceptions::PyValueError::new_err(format!("invalid address: {e}"))
156 })?;
157 slf.name_servers.push(addr);
158 Ok(slf)
159 }
160
161 fn authnz_user(mut slf: PyRefMut<'_, Self>, user: String) -> PyRefMut<'_, Self> {
162 slf.authnz_user = Some(user);
163 slf
164 }
165
166 fn authnz_host(mut slf: PyRefMut<'_, Self>, host: String) -> PyRefMut<'_, Self> {
167 slf.authnz_host = Some(host);
168 slf
169 }
170
171 fn server_addr(mut slf: PyRefMut<'_, Self>, addr: String) -> PyResult<PyRefMut<'_, Self>> {
172 let _: SocketAddr = addr.parse().map_err(|e| {
173 pyo3::exceptions::PyValueError::new_err(format!("invalid address: {e}"))
174 })?;
175 slf.server_addr = Some(addr);
176 Ok(slf)
177 }
178
179 fn search_addr(mut slf: PyRefMut<'_, Self>, addr: String) -> PyRefMut<'_, Self> {
180 slf.search_addr = Some(addr);
181 slf
182 }
183
184 fn bind_addr(mut slf: PyRefMut<'_, Self>, addr: String) -> PyRefMut<'_, Self> {
185 slf.bind_addr = Some(addr);
186 slf
187 }
188
189 fn debug(mut slf: PyRefMut<'_, Self>, enabled: bool) -> PyRefMut<'_, Self> {
190 slf.debug = enabled;
191 slf
192 }
193
194 fn build(&self) -> PyResult<PyClient> {
195 let mut b = PvaClient::builder()
196 .port(self.tcp_port)
197 .udp_port(self.udp_port)
198 .timeout(Duration::from_secs_f64(self.timeout_secs));
199 if self.no_broadcast {
200 b = b.no_broadcast();
201 }
202 for ns in &self.name_servers {
203 let addr: SocketAddr = ns.parse().map_err(|e| {
204 pyo3::exceptions::PyValueError::new_err(format!("invalid address: {e}"))
205 })?;
206 b = b.name_server(addr);
207 }
208 if let Some(ref user) = self.authnz_user {
209 b = b.authnz_user(user);
210 }
211 if let Some(ref host) = self.authnz_host {
212 b = b.authnz_host(host);
213 }
214 if let Some(ref addr) = self.server_addr {
215 let sa: SocketAddr = addr.parse().map_err(|e| {
216 pyo3::exceptions::PyValueError::new_err(format!("invalid address: {e}"))
217 })?;
218 b = b.server_addr(sa);
219 }
220 if let Some(ref addr) = self.search_addr {
221 let ip: std::net::IpAddr = addr
222 .parse()
223 .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("invalid IP: {e}")))?;
224 b = b.search_addr(ip);
225 }
226 if let Some(ref addr) = self.bind_addr {
227 let ip: std::net::IpAddr = addr
228 .parse()
229 .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("invalid IP: {e}")))?;
230 b = b.bind_addr(ip);
231 }
232 if self.debug {
233 b = b.debug();
234 }
235 Ok(PyClient { inner: b.build() })
236 }
237}
238
239#[pyclass(name = "Client")]
242pub struct PyClient {
243 inner: PvaClient,
244}
245
246#[pymethods]
247impl PyClient {
248 #[new]
249 fn new() -> Self {
250 Self {
251 inner: PvaClient::builder().build(),
252 }
253 }
254
255 #[staticmethod]
257 fn builder() -> PyClientBuilder {
258 PyClientBuilder::new()
259 }
260
261 #[pyo3(signature = (pv_name, fields=None))]
266 fn get(
267 &self,
268 py: Python<'_>,
269 pv_name: String,
270 fields: Option<Vec<String>>,
271 ) -> PyResult<PyGetResult> {
272 let client = self.inner.clone();
273 let result = py
274 .allow_threads(|| {
275 RUNTIME.block_on(async {
276 match fields {
277 None => client.pvget(&pv_name).await,
278 Some(ref f) => {
279 let refs: Vec<&str> = f.iter().map(String::as_str).collect();
280 client.pvget_fields(&pv_name, &refs).await
281 }
282 }
283 })
284 })
285 .map_err(to_py_err)?;
286 let value = decoded_to_py(py, &result.value);
287 Ok(PyGetResult {
288 pv_name: result.pv_name,
289 value,
290 raw_pva: result.raw_pva,
291 raw_pvd: result.raw_pvd,
292 })
293 }
294
295 #[pyo3(signature = (pv_name, value, fields=None))]
300 fn put(
301 &self,
302 py: Python<'_>,
303 pv_name: String,
304 value: PyObject,
305 fields: Option<Vec<String>>,
306 ) -> PyResult<()> {
307 let json_val = py_to_json(value.bind(py))?;
308 let client = self.inner.clone();
309 py.allow_threads(|| {
310 RUNTIME.block_on(async {
311 match fields {
312 None => client.pvput(&pv_name, json_val).await,
313 Some(ref f) => {
314 let refs: Vec<&str> = f.iter().map(String::as_str).collect();
315 client.pvput_fields(&pv_name, json_val, &refs).await
316 }
317 }
318 })
319 })
320 .map_err(to_py_err)
321 }
322
323 #[pyo3(signature = (pv_name, callback, fields=None))]
328 fn monitor(
329 &self,
330 _py: Python<'_>,
331 pv_name: String,
332 callback: PyObject,
333 fields: Option<Vec<String>>,
334 ) -> PyResult<()> {
335 let client = self.inner.clone();
336 let fields = fields.unwrap_or_default();
337 let result = RUNTIME.block_on(async {
341 let refs: Vec<&str> = fields.iter().map(String::as_str).collect();
342 client
343 .pvmonitor_fields(&pv_name, &refs, |decoded| {
344 let keep_going = Python::with_gil(|py| {
345 let py_val = decoded_to_py(py, decoded);
346 match callback.call1(py, (py_val,)) {
347 Ok(ret) => {
348 ret.extract::<bool>(py).unwrap_or(true)
350 }
351 Err(_) => false,
352 }
353 });
354 if keep_going {
355 ControlFlow::Continue(())
356 } else {
357 ControlFlow::Break(())
358 }
359 })
360 .await
361 });
362 result.map_err(to_py_err)
364 }
365
366 fn info(&self, py: Python<'_>, pv_name: String) -> PyResult<PyObject> {
368 let client = self.inner.clone();
369 let desc = py
370 .allow_threads(|| RUNTIME.block_on(client.pvinfo(&pv_name)))
371 .map_err(to_py_err)?;
372 let dict = PyDict::new(py);
374 dict.set_item("struct_id", &desc.struct_id)?;
375 let fields: Vec<PyObject> = desc
376 .fields
377 .iter()
378 .map(|f| {
379 let fd = PyDict::new(py);
380 fd.set_item("name", &f.name).expect("set");
381 fd.set_item("field_type", format!("{:?}", f.field_type))
382 .expect("set");
383 fd.into_any().unbind()
384 })
385 .collect();
386 dict.set_item("fields", PyList::new(py, &fields)?)?;
387 Ok(dict.into_any().unbind())
388 }
389
390 fn pvlist(&self, py: Python<'_>, server_addr: String) -> PyResult<Vec<String>> {
392 let addr: SocketAddr = server_addr.parse().map_err(|e| {
393 pyo3::exceptions::PyValueError::new_err(format!("invalid address: {e}"))
394 })?;
395 let client = self.inner.clone();
396 py.allow_threads(|| RUNTIME.block_on(client.pvlist(addr)))
397 .map_err(to_py_err)
398 }
399}
400
401#[pyfunction]
405#[pyo3(signature = (udp_port=5076, timeout=2.0, debug=false))]
406pub fn py_discover_servers(
407 py: Python<'_>,
408 udp_port: u16,
409 timeout: f64,
410 debug: bool,
411) -> PyResult<Vec<PyDiscoveredServer>> {
412 let targets = build_auto_broadcast_targets();
413 let dur = Duration::from_secs_f64(timeout);
414 let servers = py
415 .allow_threads(|| RUNTIME.block_on(discover_servers(udp_port, dur, &targets, debug)))
416 .map_err(to_py_err)?;
417 Ok(servers
418 .into_iter()
419 .map(|s| PyDiscoveredServer {
420 guid: s.guid.to_vec(),
421 tcp_addr: s.tcp_addr.to_string(),
422 })
423 .collect())
424}