use std::hash::{BuildHasherDefault, DefaultHasher, Hash, Hasher};
use std::sync::{Arc, OnceLock, Weak, Mutex};
use std::time::{Duration, SystemTime};
use std::collections::{HashMap, hash_map};
use std::marker::PhantomData;
use std::fmt::{self, Debug};
use std::str::FromStr;
use std::borrow::Cow;
use std::env;
use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
use tracing::{debug, instrument};
use crate::backend::{self, AttrReadyChannelBackend, AttrValueChannelBackend, Backend, DevBackend, DevCommandBackend, DevGetAttrInfoBackend, DevGetCommandInfoBackend, DevReadAttrBackend, DevWriteAttrBackend};
use crate::database::{DatabaseProxy, GetManyProp, GetPropSpec, SetPropBuilder, SetPropSpec, get_prop_spec};
use crate::data::{Data, DataKind, DataLayoutKind, FromData, IntoData};
use crate::error::{Error, ErrorKind, Result, reason};
use crate::trl::{IntoTrl, Trl, TrlObject};
static PROXY_CACHE: Mutex<ProxyCache> = Mutex::new(ProxyCache::with_hasher(BuildHasherDefault::new()));
#[derive(Debug, Clone)]
pub struct DevProxy {
shared: Arc<ProxyShared>,
}
#[derive(Debug)]
pub struct ProxyShared {
target: ProxyTarget,
backend: RwLock<Option<ProxyBackend>>,
}
#[derive(Debug)]
pub(crate) struct ProxyBackend {
inner: Arc<dyn DevBackend>,
database_host_and_port: Option<(Arc<str>, u16)>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub(crate) enum ProxyTarget {
Local {
name: Arc<str>,
},
DatabaseFromEnv,
DatabaseAt {
host: Arc<str>,
port: u16,
},
RegularFromDatabase {
database: Box<DatabaseProxy>, name: Arc<str>,
},
RegularAt {
host: Arc<str>,
port: u16,
name: Arc<str>,
},
}
impl DevProxy {
#[inline]
pub fn new(trl: impl IntoTrl) -> Self {
Self::try_new(trl).expect("The given TRL does not target a device")
}
pub fn try_new(trl: impl IntoTrl) -> Option<Self> {
let trl = trl.into_trl().ok()?;
if trl.property().is_some() {
return None;
}
let name = match trl.object() {
TrlObject::Alias(name) => name,
TrlObject::Dev(name) => name,
TrlObject::Attr(_, _) => return None,
};
Some(Self::new_with_target(match (trl.db(), trl.host_and_port()) {
(true, Some((host, port))) => ProxyTarget::RegularFromDatabase {
database: Box::new(DatabaseProxy::new(host, port)),
name: Arc::from(name),
},
(true, None) => ProxyTarget::RegularFromDatabase {
database: Box::new(DatabaseProxy::new_from_env()),
name: Arc::from(name),
},
(false, Some((host, port))) => ProxyTarget::RegularAt {
host: Arc::from(host),
port,
name: Arc::from(name),
},
(false, None) => ProxyTarget::Local {
name: Arc::from(name),
}
}))
}
pub fn new_from_database(database: &DatabaseProxy, name: &str) -> Self {
Self::new_with_target(ProxyTarget::RegularFromDatabase {
database: Box::new(database.clone()),
name: Arc::from(name),
})
}
pub(crate) fn new_with_target(target: ProxyTarget) -> Self {
let shared = match PROXY_CACHE.lock().unwrap().entry(target) {
hash_map::Entry::Occupied(mut o) => {
if let Some(shared) = o.get_mut().upgrade() {
shared
} else {
let shared = Arc::new(ProxyShared {
target: o.key().clone(),
backend: RwLock::const_new(None),
});
o.insert(Arc::downgrade(&shared));
shared
}
}
hash_map::Entry::Vacant(v) => {
let shared = Arc::new(ProxyShared {
target: v.key().clone(),
backend: RwLock::const_new(None),
});
v.insert(Arc::downgrade(&shared));
shared
}
};
Self {
shared,
}
}
async fn try_backends<'a, A, F>(backends: &'a [Arc<dyn Backend>], async_func: A) -> Result<Option<Arc<dyn DevBackend>>>
where
A: Fn(&'a dyn Backend) -> F,
F: Future<Output = Result<Arc<dyn DevBackend>>> + Send + 'a,
{
for backend in backends.iter() {
match async_func(&**backend).await {
Err(err) => match err.kind() {
ErrorKind::NotHandled => continue,
ErrorKind::DevNotFound => continue,
ErrorKind::ServerNotConnected => continue,
ErrorKind::DevNotConnected => continue,
_ => return Err(err),
},
Ok(dev) => return Ok(Some(dev)),
};
}
Ok(None)
}
async fn ensure_backend(&self) -> Result<RwLockReadGuard<'_, ProxyBackend>> {
let backend = self.shared.backend.read().await;
let backend = match RwLockReadGuard::try_map(backend, |b| b.as_ref()) {
Ok(ret) => return Ok(ret),
Err(backend) => backend,
};
debug!("No backend, initializing");
drop(backend);
let backend = self.shared.backend.write().await;
let backend = match RwLockWriteGuard::try_downgrade_map(backend, |b| b.as_ref()) {
Ok(ret) => return Ok(ret),
Err(backend) => backend,
};
let Some(backends) = backend::read_backends() else {
return Err(Error::new(ErrorKind::DevNotConnected)
.with_append_description("There are no backends, see backend::register_backend"));
};
let mut database_host_and_port = None;
let new_backend = match self.shared.target {
ProxyTarget::Local { .. } => {
todo!() }
ProxyTarget::DatabaseFromEnv => {
debug!("Initializing database backend from env");
let mut backend = None;
for (host, port) in env_databases() {
backend = Self::try_backends(&backends[..], |backend| {
backend.connect_database(host, *port)
}).await?;
if backend.is_some() {
database_host_and_port = Some((host, *port));
break;
}
}
backend
}
ProxyTarget::DatabaseAt { ref host, port } => {
debug!("Initializing database backend for {host}:{port}");
database_host_and_port = Some((host, port));
Self::try_backends(&backends[..], |backend| {
backend.connect_database(host, port)
}).await?
}
ProxyTarget::RegularFromDatabase { ref database, ref name } => {
debug!("Initializing backend for {name} from database");
let info = match Box::pin(database.import_dev(name)).await {
Ok(info) => info,
Err(err) if err.kind() == ErrorKind::DevNotConnected =>
return Err(Error::new(ErrorKind::DatabaseNotConnected)
.with_parent(err)),
Err(err) => return Err(err),
};
Self::try_backends(&backends[..], |backend| {
backend.connect_device(&info.itr)
}).await?
}
ProxyTarget::RegularAt { ref host, port, ref name } => {
debug!("Initializing backend for {name} at {host}:{port}");
Self::try_backends(&backends[..], |backend| {
backend.connect_device_at(name, host, port)
}).await?
}
};
match new_backend {
None => Err(Error::new(ErrorKind::DevNotConnected)),
Some(new_backend) => {
let mut backend = backend;
*backend = Some(ProxyBackend {
inner: new_backend,
database_host_and_port: database_host_and_port.map(|(host, port)| (host.clone(), port)),
});
Ok(RwLockWriteGuard::downgrade_map(backend, |backend| backend.as_ref().unwrap()))
}
}
}
async fn reset_backend(&self) {
debug!("Reset backend");
let mut backend = self.shared.backend.write().await;
*backend = None;
}
#[inline]
async fn with_backend<'a, A, F, R>(&self, async_func: A) -> Result<R>
where
A: FnOnce(Arc<dyn DevBackend>) -> F,
F: Future<Output = Result<R>> + Send + 'a,
{
let backend = self.ensure_backend().await?;
let backend_inner = backend.inner.clone();
drop(backend);
match async_func(backend_inner).await {
Ok(ret) => Ok(ret),
Err(err) => {
if err.kind() == ErrorKind::DevNotConnected {
self.reset_backend().await;
}
Err(err)
}
}
}
pub fn database(&self) -> Result<&DatabaseProxy> {
match self.shared.target {
ProxyTarget::DatabaseFromEnv |
ProxyTarget::DatabaseAt { .. } =>
Ok(DatabaseProxy::from_dev(self)),
ProxyTarget::RegularFromDatabase { ref database, .. } =>
Ok(&**database),
ProxyTarget::Local { .. } |
ProxyTarget::RegularAt { .. } =>
Err(Error::new(ErrorKind::DatabaseNeeded)),
}
}
async fn name_shared(&self) -> Result<Arc<str>> {
self.with_backend(async move |backend| {
backend.name().await
}).await
}
pub async fn trl(&self) -> Result<Trl> {
match self.shared.target {
ProxyTarget::Local { ref name } => {
Ok(format!("tango://{name}#dbase=no").parse::<Trl>().unwrap())
}
ProxyTarget::DatabaseFromEnv => {
let backend = self.ensure_backend().await?;
let (host, port) = backend.database_host_and_port.as_ref()
.expect("This should be defined if the target is a database");
let name = self.name_shared().await?;
Ok(format!("tango://{host}:{port}/{name}").parse::<Trl>().unwrap())
}
ProxyTarget::DatabaseAt { ref host, port } => {
let name = self.name_shared().await?;
Ok(format!("tango://{host}:{port}/{name}").parse::<Trl>().unwrap())
}
ProxyTarget::RegularFromDatabase { ref database, ref name } => {
let db_backend = database.ensure_backend().await?;
let (host, port) = db_backend.database_host_and_port.as_ref()
.expect("This should be defined if the target is a database");
Ok(format!("tango://{host}:{port}/{name}").parse::<Trl>().unwrap())
}
ProxyTarget::RegularAt { ref host, port, ref name } => {
Ok(format!("tango://{host}:{port}/{name}#dbase=no").parse::<Trl>().unwrap())
}
}
}
#[instrument(skip(self), fields(target = %self.shared.target))]
pub async fn name(&self) -> Result<String> {
self.name_shared().await.map(|s| s.to_string())
}
#[instrument(skip(self), fields(target = %self.shared.target))]
pub async fn admin_dev(&self) -> Result<DevProxy> {
let name = self.with_backend(async move |backend| {
backend.admin_dev_name().await
}).await?;
Ok(DevProxy::new_with_target(match &self.shared.target {
ProxyTarget::Local { .. } => todo!(),
ProxyTarget::DatabaseFromEnv => todo!(),
ProxyTarget::DatabaseAt { .. } => {
ProxyTarget::RegularFromDatabase {
database: Box::new(DatabaseProxy::with_dev(self.clone())),
name,
}
}
ProxyTarget::RegularFromDatabase { database, .. } => {
ProxyTarget::RegularFromDatabase {
database: database.clone(),
name,
}
}
ProxyTarget::RegularAt { host, port, .. } => {
ProxyTarget::RegularAt {
host: Arc::clone(host),
port: *port,
name,
}
}
}))
}
#[instrument(skip(self), fields(target = %self.shared.target))]
pub async fn ping(&self) -> Result<()> {
self.with_backend(async move |backend| {
backend.ping().await
}).await
}
#[instrument(skip(self), fields(target = %self.shared.target))]
pub async fn state(&self) -> Result<DevState> {
self.with_backend(async move |backend| {
backend.state().await
}).await
}
#[instrument(skip(self), fields(target = %self.shared.target))]
pub async fn status(&self) -> Result<String> {
self.with_backend(async move |backend| {
backend.status().await
}).await
}
#[instrument(skip(self), fields(target = %self.shared.target))]
pub async fn black_box(&self, max: Option<usize>) -> Result<Vec<String>> {
self.with_backend(async |backend| {
backend.black_box(max).await
}).await
}
#[instrument(skip(self, data), fields(target = %self.shared.target))]
pub async fn command_raw<'i, I>(&self, name: &str, data: I) -> Result<CommandOutput>
where
I: IntoData<'i>,
{
let data_in = data.into_data();
let res = self.with_backend(async |backend| {
backend.command(name, data_in).await
}).await;
let backend = match res {
Ok(backend) => backend,
Err(err) => match err.kind() {
ErrorKind::Reason(reason::API_CommandNotFound) => {
return Err(err.with_kind(ErrorKind::CommandNotFound));
}
ErrorKind::Reason(reason::API_IncompatibleCmdArgumentType) => {
let expected_kind = err.description().split_ascii_whitespace()
.find_map(|word| word.strip_prefix("Tango::"))
.and_then(data_kind_from_cpp_tango)
.unwrap_or(DataKind::Void);
return Err(
err.with_kind(ErrorKind::CommandArgumentKindInvalid)
.with_append_description(format_args!("Expected {expected_kind:?}")));
}
_ => return Err(err),
}
};
Ok(CommandOutput {
backend,
})
}
pub async fn command<'i, I, O>(&self, name: &str, data: I) -> Result<O>
where
I: IntoData<'i>,
O: for<'o> FromData<'o>,
{
let output = self.command_raw(name, data).await?;
output.get::<O>()
}
#[instrument(skip(self), fields(target = %self.shared.target))]
pub async fn read_all_attr(&self) -> Result<ReadManyAttr> {
let backend = self.with_backend(async |backend| {
backend.read_attr(None).await
}).await?;
Ok(ReadManyAttr {
len: backend.len(),
backend,
})
}
#[instrument(skip(self, names), fields(target = %self.shared.target))]
pub async fn read_many_attr<Names: AsNames>(&self, names: Names) -> Result<ReadManyAttr> {
let names = names.as_names();
let names = names.as_ref();
let backend = self.with_backend(async |backend| {
backend.read_attr(Some(names)).await
}).await?;
let len = {
if backend.len() != names.len() {
return Err(Error::new(ErrorKind::Internal)
.with_description(format!("Expected {} attributes, got {}", names.len(), backend.len())));
}
names.len()
};
Ok(ReadManyAttr {
len,
backend,
})
}
pub async fn read_attr<Spec, Names: AsNames>(&self, names: Names) -> Result<Spec::Output>
where
Spec: ReadAttrSpec<Names>,
{
self.read_many_attr(names).await.and_then(|many| Spec::output(many, read_attr_spec::Token))
}
#[inline]
pub fn write_attr_builder<'data>(&self) -> WriteAttrBuilder<'_, 'data> {
WriteAttrBuilder {
dev: self,
array: Vec::new(),
}
}
pub async fn write_attr<Spec>(&self, attrs: Spec) -> Result<WriteManyAttr>
where
for<'data> Spec: WriteAttrSpec<'data>,
{
let mut builder = self.write_attr_builder();
attrs.push_to_builder(&mut builder);
builder.write().await
}
#[instrument(skip(self), fields(target = %self.shared.target))]
pub async fn get_all_command_info(&self) -> Result<GetManyCommandInfo> {
let backend = self.with_backend(async |backend| {
backend.get_command_info(None).await
}).await?;
Ok(GetManyCommandInfo {
len: backend.len(),
backend,
})
}
#[instrument(skip(self, names), fields(target = %self.shared.target))]
pub async fn get_many_command_info<Names: AsNames>(&self, names: Names) -> Result<GetManyCommandInfo> {
let names = names.as_names();
let names = names.as_ref();
let backend = self.with_backend(async |backend| {
backend.get_command_info(Some(names)).await
}).await?;
let len = {
if backend.len() != names.len() {
return Err(Error::new(ErrorKind::Internal)
.with_description(format!("Expected {} commands info, got {}", names.len(), backend.len())));
}
names.len()
};
Ok(GetManyCommandInfo {
backend,
len,
})
}
#[instrument(skip(self), fields(target = %self.shared.target))]
pub async fn get_all_attr_info(&self) -> Result<GetManyAttrInfo> {
let backend = self.with_backend(async |backend| {
backend.get_attr_info(None).await
}).await?;
Ok(GetManyAttrInfo {
len: backend.len(),
backend,
})
}
#[instrument(skip(self, names), fields(target = %self.shared.target))]
pub async fn get_many_attr_info<Names: AsNames>(&self, names: Names) -> Result<GetManyAttrInfo> {
let names = names.as_names();
let names = names.as_ref();
let backend = self.with_backend(async |backend| {
backend.get_attr_info(Some(names)).await
}).await?;
let len = {
if backend.len() != names.len() {
return Err(Error::new(ErrorKind::Internal)
.with_description(format!("Expected {} attributes info, got {}", names.len(), backend.len())));
}
names.len()
};
Ok(GetManyAttrInfo {
backend,
len,
})
}
#[instrument(skip(self), fields(target = %self.shared.target))]
pub async fn subscribe_attr_value<T>(&self, name: &str, event: AttrValueEvent) -> Result<AttrChannel<T>>
where
T: for<'d> FromData<'d>,
{
let Some(backends) = backend::read_event_backends() else {
return Err(Error::new(ErrorKind::EventNotConnected)
.with_append_description("There are no event backends, see backend::register_event_backend"));
};
for backend in &backends[..] {
let backend = match backend.subscribe_attr_value(self, name, event).await {
Ok(channel) => channel,
Err(err) if err.kind() == ErrorKind::NotHandled => continue,
Err(err) => return Err(err),
};
return Ok(AttrChannel {
backend,
name: name.to_string(),
_phantom: PhantomData,
});
}
Err(Error::new(ErrorKind::EventNotConnected)
.with_append_description("None of the event backend handled the subscription"))
}
#[instrument(skip(self), fields(target = %self.shared.target))]
pub async fn subscribe_attr_ready(&self, name: &str) -> Result<AttrReadyChannel> {
let Some(backends) = backend::read_event_backends() else {
return Err(Error::new(ErrorKind::EventNotConnected)
.with_append_description("There are no event backends, see backend::register_event_backend"));
};
for backend in &backends[..] {
let backend = match backend.subscribe_attr_ready(self, name).await {
Ok(channel) => channel,
Err(err) if err.kind() == ErrorKind::NotHandled => continue,
Err(err) => return Err(err),
};
return Ok(AttrReadyChannel {
backend,
name: name.to_string(),
});
}
Err(Error::new(ErrorKind::EventNotConnected)
.with_append_description("None of the event backend handled the subscription"))
}
pub async fn subscribe_attr_conf(&self, name: &str) -> Result<()> {
let _ = name;
todo!()
}
pub async fn list_prop(&self, filter: &str) -> Result<Vec<String>> {
let dev_name = self.name_shared().await?;
self.database()?.list_dev_prop(&dev_name, filter).await
}
pub async fn get_all_prop(&self) -> Result<GetManyProp> {
let dev_name = self.name_shared().await?;
self.database()?.get_all_dev_prop(&dev_name).await
}
pub async fn get_many_prop<Names: AsNames>(&self, names: Names) -> Result<GetManyProp> {
let dev_name = self.name_shared().await?;
self.database()?.get_many_dev_prop::<Names>(&dev_name, names).await
}
pub async fn get_prop<Spec, Names: AsNames>(&self, names: Names) -> Result<Spec::Output>
where
Spec: GetPropSpec<Names>,
{
self.get_many_prop(names).await.and_then(|many| Spec::output(many, get_prop_spec::Token))
}
pub fn set_prop_builder<'data>(&self) -> SetPropBuilder<'_, 'data> {
SetPropBuilder::new(self, "DbPutDeviceProperty", Cow::Borrowed(""))
}
pub async fn set_prop<Spec>(&self, props: Spec) -> Result<()>
where
for<'data> Spec: SetPropSpec<'data>,
{
let mut builder = self.set_prop_builder();
props.push_to_builder(&mut builder);
builder.set().await
}
pub async fn del_prop<Names: AsNames>(&self, names: Names) -> Result<()> {
let dev_name = self.name_shared().await?;
self.database()?.del_dev_prop(&dev_name, names).await
}
}
impl PartialEq for DevProxy {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.shared.target == other.shared.target
}
}
impl Eq for DevProxy { }
impl Hash for DevProxy {
#[inline]
fn hash<H: Hasher>(&self, state: &mut H) {
self.shared.target.hash(state);
}
}
impl fmt::Display for ProxyTarget {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ProxyTarget::Local { name } =>
write!(f, "{name}@local"),
ProxyTarget::DatabaseFromEnv =>
write!(f, "db"),
ProxyTarget::DatabaseAt { host, port } =>
write!(f, "db@{host}:{port}"),
ProxyTarget::RegularFromDatabase { name, .. } =>
write!(f, "{name}"),
ProxyTarget::RegularAt { host, port, name } =>
write!(f, "{name}@{host}:{port}"),
}
}
}
type CacheBuildHasher = BuildHasherDefault<DefaultHasher>;
type ProxyCache = HashMap<ProxyTarget, Weak<ProxyShared>, CacheBuildHasher>;
mod as_names {
pub trait Internal {
fn as_names(&self) -> impl AsRef<[&'_ str]>;
}
}
pub trait AsNames: as_names::Internal { }
impl<T: as_names::Internal + ?Sized> AsNames for T { }
impl as_names::Internal for &'_ str {
fn as_names(&self) -> impl AsRef<[&'_ str]> {
[*self]
}
}
impl as_names::Internal for &'_ [&'_ str] {
fn as_names(&self) -> impl AsRef<[&'_ str]> {
self
}
}
impl<const N: usize> as_names::Internal for [&'_ str; N] {
fn as_names(&self) -> impl AsRef<[&'_ str]> {
&self[..]
}
}
macro_rules! impl_as_names_tuple {
( @name: $comp:ident ) => { &'_ str };
( @one: $comp:ident ) => { 1 };
( $( $comp:ident )* ) => {
#[allow(unused_variables)]
impl as_names::Internal for ( $(impl_as_names_tuple!(@name: $comp),)* ) {
fn as_names(&self) -> impl AsRef<[&'_ str]> {
let ( $($comp,)* ) = *self;
[ $($comp,)* ]
}
}
};
}
impl_as_names_tuple!();
impl_as_names_tuple!(a);
impl_as_names_tuple!(a b);
impl_as_names_tuple!(a b c);
impl_as_names_tuple!(a b c d);
impl_as_names_tuple!(a b c d e);
impl_as_names_tuple!(a b c d e f);
#[derive(Debug)]
pub struct CommandOutput {
backend: Box<dyn DevCommandBackend>,
}
impl CommandOutput {
pub fn get<'d, T>(&'d self) -> Result<T>
where
T: FromData<'d>,
{
let data = self.backend.get()?;
T::from_data(data)
.map_err(|err| Error::new(ErrorKind::InvalidData)
.with_append_description("Failed to parse command output value")
.with_append_description(err))
}
}
pub struct ReadManyAttr {
backend: Box<dyn DevReadAttrBackend>,
len: usize,
}
impl ReadManyAttr {
#[inline]
pub fn len(&self) -> usize {
self.len
}
pub fn get<'d, T>(&'d self, index: usize) -> Result<AttrRead<'d, T>>
where
T: FromData<'d>,
{
let attr_read = self.backend.get(index)?;
let value = match attr_read.value {
Some(val) => match T::from_data(val) {
Ok(val) => Some(val),
Err(err) => return Err(Error::new(ErrorKind::InvalidData)
.with_append_description("Failed to parse read value")
.with_append_description(err)),
}
None => None,
};
let w_value = match attr_read.w_value {
Some(val) => match T::from_data(val) {
Ok(val) => Some(val),
Err(err) => return Err(Error::new(ErrorKind::InvalidData)
.with_append_description("Failed to parse write value")
.with_append_description(err)),
}
None => None,
};
Ok(AttrRead {
name: attr_read.name,
value,
w_value,
quality: attr_read.quality,
time: attr_read.time,
})
}
}
impl Debug for ReadManyAttr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "ReadManyAttr(")?;
f.debug_list().entries((0..self.len()).map(|i| self.get::<Data>(i))).finish()?;
write!(f, ")")
}
}
pub(crate) mod read_attr_spec {
use super::{ReadManyAttr, Result};
pub struct Token;
pub trait Internal<Names> {
type Output;
fn output(array: ReadManyAttr, _: Token) -> Result<Self::Output>;
}
}
pub trait ReadAttrSpec<Names>: read_attr_spec::Internal<Names> { }
impl<Names, T: read_attr_spec::Internal<Names>> ReadAttrSpec<Names> for T { }
impl<T> read_attr_spec::Internal<&'_ str> for T
where
T: for<'d> FromData<'d>,
{
type Output = AttrRead<'static, T>;
#[inline]
fn output(array: ReadManyAttr, _: read_attr_spec::Token) -> Result<Self::Output> {
array.get(0).map(AttrRead::into_owned)
}
}
impl<T> read_attr_spec::Internal<&'_ [&'_ str]> for T
where
T: for<'d> FromData<'d>,
{
type Output = Vec<Result<AttrRead<'static, T>>>;
#[inline]
fn output(array: ReadManyAttr, _: read_attr_spec::Token) -> Result<Self::Output> {
Ok((0..array.len()).map(|i| array.get::<T>(i).map(AttrRead::into_owned)).collect())
}
}
impl<T, const N: usize> read_attr_spec::Internal<[&'_ str; N]> for T
where
T: for<'d> FromData<'d>,
{
type Output = [Result<AttrRead<'static, T>>; N];
#[inline]
fn output(array: ReadManyAttr, _: read_attr_spec::Token) -> Result<Self::Output> {
Ok(std::array::from_fn::<_, N, _>(|i| {
array.get(i).map(AttrRead::into_owned)
}))
}
}
macro_rules! impl_read_attr_spec_tuple {
( @name: $comp:ident ) => { &'_ str };
( $( $comp:ident )* ) => {
#[allow(non_snake_case, unused_assignments)]
impl<$($comp,)* > read_attr_spec::Internal<( $( impl_read_attr_spec_tuple!(@name: $comp), )* )> for ( $($comp,)* )
where
$(
$comp: for<'d> FromData<'d>,
)*
{
type Output = ( $( Result<AttrRead<'static, $comp>>,)* );
#[inline]
fn output(array: ReadManyAttr, _: read_attr_spec::Token) -> Result<Self::Output> {
let mut i = 0;
$(
let $comp = array.get(i).map(AttrRead::into_owned);
i += 1;
)*
Ok( ( $($comp,)* ) )
}
}
};
}
impl_read_attr_spec_tuple!(A);
impl_read_attr_spec_tuple!(A B);
impl_read_attr_spec_tuple!(A B C);
impl_read_attr_spec_tuple!(A B C D);
impl_read_attr_spec_tuple!(A B C D E);
impl_read_attr_spec_tuple!(A B C D E F);
#[derive(Debug)]
pub struct WriteAttrBuilder<'dev, 'd> {
dev: &'dev DevProxy,
array: Vec<AttrWrite<'d, Data<'d>>>,
}
impl<'dev, 'd> WriteAttrBuilder<'dev, 'd> {
#[inline]
pub fn len(&self) -> usize {
self.array.len()
}
pub fn push<'dd: 'd, T>(&mut self, attr: AttrWrite<'dd, T>)
where
T: IntoData<'dd>,
{
self.array.push(AttrWrite {
name: attr.name,
value: attr.value.map(|v| v.into_data()),
quality: attr.quality,
time: attr.time,
});
}
#[instrument(name = "write_attr_raw", skip(self), fields(target = %self.dev.shared.target))]
pub async fn write(self) -> Result<WriteManyAttr> {
let len = self.array.len();
let backend = self.dev.with_backend(async |backend| {
backend.write_attr(&self.array).await
}).await?;
Ok(WriteManyAttr {
backend,
len,
})
}
}
pub struct WriteManyAttr {
backend: Box<dyn DevWriteAttrBackend>,
len: usize,
}
impl WriteManyAttr {
#[inline]
pub fn len(&self) -> usize {
self.len
}
pub fn check(&self, index: usize) -> Result<()> {
let err = match self.backend.check(index) {
Ok(()) => return Ok(()),
Err(err) => err,
};
Err(match err.kind() {
ErrorKind::Reason(reason::API_AttrNotFound) => {
err.with_kind(ErrorKind::AttrNotFound)
}
ErrorKind::Reason(reason::API_AttrNotWritable) => {
err.with_kind(ErrorKind::AttrNotWritable)
}
ErrorKind::Reason(reason::API_IncompatibleAttrDataType) => {
let expected_kind = err.description().split_ascii_whitespace()
.find_map(|word| word.strip_prefix("Tango::"))
.and_then(data_kind_from_cpp_tango)
.unwrap_or(DataKind::Void);
err.with_kind(ErrorKind::AttrKindInvalid)
.with_append_description(format_args!("Expected {expected_kind:?}"))
}
_ => err
})
}
}
impl Debug for WriteManyAttr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "WriteManyAttr(")?;
f.debug_list().entries((0..self.len()).map(|i| self.check(i))).finish()?;
write!(f, ")")
}
}
pub(crate) mod write_attr_spec {
use super::WriteAttrBuilder;
pub trait Internal<'d>: Sized {
fn push_to_builder(self, builder: &mut WriteAttrBuilder<'_, 'd>);
}
pub trait IntoIteratorFilter {}
impl<T, const N: usize> IntoIteratorFilter for [T; N] {}
impl<T> IntoIteratorFilter for Vec<T> {}
}
pub trait WriteAttrSpec<'d>: write_attr_spec::Internal<'d> { }
impl<'d, T: write_attr_spec::Internal<'d>> WriteAttrSpec<'d> for T { }
impl<'d, 'dd: 'd, T> write_attr_spec::Internal<'d> for AttrWrite<'dd, T>
where
T: IntoData<'dd>,
{
#[inline]
fn push_to_builder(self, builder: &mut WriteAttrBuilder<'_, 'd>) {
builder.push(self);
}
}
impl<'d, 'dd: 'd, T> write_attr_spec::Internal<'d> for (&'dd str, T)
where
T: IntoData<'dd>,
{
#[inline]
fn push_to_builder(self, builder: &mut WriteAttrBuilder<'_, 'd>) {
builder.push(AttrWrite {
name: Cow::Borrowed(self.0),
value: Some(self.1),
quality: AttrQuality::Valid,
time: SystemTime::now(),
});
}
}
impl<'d, T, I> write_attr_spec::Internal<'d> for I
where
T: WriteAttrSpec<'d>,
I: IntoIterator<Item = T>,
I: write_attr_spec::IntoIteratorFilter,
{
#[inline]
fn push_to_builder(self, builder: &mut WriteAttrBuilder<'_, 'd>) {
for attr in self {
attr.push_to_builder(&mut *builder);
}
}
}
macro_rules! impl_write_attr_spec_tuple {
( @name: $comp:ident ) => { &'name str };
( $( $comp:ident )* ) => {
#[allow(non_snake_case, unused_assignments)]
impl<'name, 'd, $($comp,)*> write_attr_spec::Internal<'d> for ( $( $comp, )* )
where
$(
$comp: WriteAttrSpec<'d>,
)*
{
#[inline]
fn push_to_builder(self, builder: &mut WriteAttrBuilder<'_, 'd>) {
let ( $($comp,)* ) = self;
$(
$comp.push_to_builder(&mut *builder);
)*
}
}
};
}
impl_write_attr_spec_tuple!(A);
impl_write_attr_spec_tuple!(A B);
impl_write_attr_spec_tuple!(A B C);
impl_write_attr_spec_tuple!(A B C D);
impl_write_attr_spec_tuple!(A B C D E);
impl_write_attr_spec_tuple!(A B C D E F);
pub struct GetManyCommandInfo {
backend: Box<dyn DevGetCommandInfoBackend>,
len: usize,
}
impl GetManyCommandInfo {
#[inline]
pub fn len(&self) -> usize {
self.len
}
#[inline]
pub fn get(&self, index: usize) -> Result<CommandInfo<'_>> {
self.backend.get(index)
}
}
impl Debug for GetManyCommandInfo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "GetManyCommandInfo(")?;
f.debug_list().entries((0..self.len()).map(|i| self.get(i))).finish()?;
write!(f, ")")
}
}
pub struct GetManyAttrInfo {
backend: Box<dyn DevGetAttrInfoBackend>,
len: usize,
}
impl GetManyAttrInfo {
#[inline]
pub fn len(&self) -> usize {
self.len
}
#[inline]
pub fn get(&self, index: usize) -> Result<AttrInfo<'_>> {
self.backend.get(index)
}
}
impl Debug for GetManyAttrInfo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "GetManyAttrInfo(")?;
f.debug_list().entries((0..self.len()).map(|i| self.get(i))).finish()?;
write!(f, ")")
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CommandInfo<'name> {
pub name: Cow<'name, str>,
pub display_level: DisplayLevel,
pub in_kind: DataKind,
pub out_kind: DataKind,
pub in_description: Option<String>,
pub out_description: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AttrRead<'name, T> {
pub name: Cow<'name, str>,
pub value: Option<T>,
pub w_value: Option<T>,
pub quality: AttrQuality,
pub time: SystemTime,
}
impl<'name, T> AttrRead<'name, T> {
#[inline]
fn into_owned(self) -> AttrRead<'static, T> {
AttrRead {
name: Cow::Owned(self.name.into_owned()),
value: self.value,
w_value: self.w_value,
quality: self.quality,
time: self.time,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AttrWrite<'name, T> {
pub name: Cow<'name, str>,
pub value: Option<T>,
pub quality: AttrQuality,
pub time: SystemTime,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum AttrQuality {
Valid,
Invalid,
Alarm,
Changing,
Warning,
}
#[derive(Debug, Clone, PartialEq)]
pub struct AttrInfo<'name> {
pub config: AttrConfig<'name>,
pub mode: AttrMode<'name>,
pub data_kind: DataKind,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AttrMode<'name> {
Read,
ReadWithWrite {
write_attr_name: Cow<'name, str>,
},
Write,
ReadWrite,
}
#[derive(Debug, Clone, PartialEq)]
pub struct AttrConfig<'name> {
pub name: Cow<'name, str>,
pub description: Option<String>,
pub label: Option<String>,
pub unit: Option<String>,
pub standard_unit: Option<f32>,
pub display_unit: Option<String>,
pub display_level: DisplayLevel,
pub display_format: Option<String>,
pub range: AttrRangeConfig,
pub alarm: AttrRangeConfig,
pub warning: AttrRangeConfig,
pub delta: Option<AttrDeltaConfig>,
pub change_event: AttrChangeEventConfig,
pub periodic_event: AttrPeriodicEventConfig,
pub archive_event: AttrArchiveEventConfig,
pub memorized: Option<AttrMemorizedMode>,
pub extensions: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct AttrRangeConfig {
pub min: Option<String>,
pub max: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AttrDeltaConfig {
pub value: String,
pub time: Option<Duration>,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct AttrChangeEventConfig {
pub rel_change: Option<String>,
pub abs_change: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct AttrPeriodicEventConfig {
pub period: Option<Duration>,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct AttrArchiveEventConfig {
pub rel_change: Option<String>,
pub abs_change: Option<String>,
pub period: Option<Duration>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum AttrMemorizedMode {
Light,
WriteAtInit,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum DevState {
On,
Off,
Close,
Open,
Insert,
Extract,
Moving,
Standby,
Fault,
Init,
Running,
Alarm,
Disable,
Unknown,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum DevSource {
Dev,
Cache,
CacheDev,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum DisplayLevel {
Operator,
Expert,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum AttrValueEvent {
Change,
Periodic,
Archive,
User,
Alarm,
}
pub struct AttrChannel<T> {
backend: Box<dyn AttrValueChannelBackend>,
name: String,
_phantom: PhantomData<fn() -> T>,
}
impl<T> AttrChannel<T> {
pub async fn wait(&mut self) -> Result<AttrRead<'_, T>>
where
T: for<'d> FromData<'d>,
{
let data = self.backend.wait().await?;
let attr_read = data.get()?;
if attr_read.name != self.name {
return Err(Error::new(ErrorKind::InvalidData)
.with_append_description(format_args!("Incoherent name read ({:?}) from what was subscribed to ({:?})",
attr_read.name, self.name)));
}
let value = match attr_read.value {
Some(val) => match T::from_data(val) {
Ok(val) => Some(val),
Err(err) => return Err(Error::new(ErrorKind::InvalidData)
.with_append_description("Failed to parse read value")
.with_append_description(err)),
}
None => None,
};
let w_value = match attr_read.w_value {
Some(val) => match T::from_data(val) {
Ok(val) => Some(val),
Err(err) => return Err(Error::new(ErrorKind::InvalidData)
.with_append_description("Failed to parse write value")
.with_append_description(err)),
}
None => None,
};
Ok(AttrRead {
name: Cow::Borrowed(&self.name[..]),
value,
w_value,
quality: attr_read.quality,
time: attr_read.time,
})
}
}
pub struct AttrReadyChannel {
backend: Box<dyn AttrReadyChannelBackend>,
name: String,
}
impl AttrReadyChannel {
pub async fn wait(&mut self) -> Result<&'_ str> {
self.backend.wait().await?;
Ok(&self.name)
}
}
fn data_kind_from_cpp_tango(cpp_type: &str) -> Option<DataKind> {
Some(match cpp_type {
"DevState" => DataKind::State(DataLayoutKind::Scalar),
"DevBoolean" => DataKind::Bool(DataLayoutKind::Scalar),
"DevShort" => DataKind::Short(DataLayoutKind::Scalar),
"DevLong" => DataKind::Long(DataLayoutKind::Scalar),
"DevLong64" => DataKind::Long64(DataLayoutKind::Scalar),
"DevFloat" => DataKind::Float(DataLayoutKind::Scalar),
"DevDouble" => DataKind::Double(DataLayoutKind::Scalar),
"DevUShort" => DataKind::UShort(DataLayoutKind::Scalar),
"DevULong" => DataKind::ULong(DataLayoutKind::Scalar),
"DevULong64" => DataKind::ULong64(DataLayoutKind::Scalar),
"DevString" |
"ConstDevString" => DataKind::String(DataLayoutKind::Scalar),
"DevVarStateArray" => DataKind::State(DataLayoutKind::Spectrum(None)),
"DevVarBooleanArray" => DataKind::Bool(DataLayoutKind::Spectrum(None)),
"DevVarShortArray" => DataKind::Short(DataLayoutKind::Spectrum(None)),
"DevVarLongArray" => DataKind::Long(DataLayoutKind::Spectrum(None)),
"DevVarLong64Array" => DataKind::Long64(DataLayoutKind::Spectrum(None)),
"DevVarFloatArray" => DataKind::Float(DataLayoutKind::Spectrum(None)),
"DevVarDoubleArray" => DataKind::Double(DataLayoutKind::Spectrum(None)),
"DevVarUShortArray" => DataKind::UShort(DataLayoutKind::Spectrum(None)),
"DevVarULongArray" => DataKind::ULong(DataLayoutKind::Spectrum(None)),
"DevVarULong64Array" => DataKind::ULong64(DataLayoutKind::Spectrum(None)),
"DevVarStringArray" => DataKind::String(DataLayoutKind::Spectrum(None)),
"DevVarLongStringArray" => DataKind::Struct(Cow::Borrowed(&[
DataKind::Long(DataLayoutKind::Spectrum(None)),
DataKind::String(DataLayoutKind::Spectrum(None)),
])),
"DevVarDoubleStringArray" => DataKind::Struct(Cow::Borrowed(&[
DataKind::Double(DataLayoutKind::Spectrum(None)),
DataKind::String(DataLayoutKind::Spectrum(None)),
])),
_ => return None,
})
}
fn env_databases() -> &'static [(Arc<str>, u16)] {
static CACHE: OnceLock<Box<[(Arc<str>, u16)]>> = OnceLock::new();
&**CACHE.get_or_init(|| {
let mut ret = Vec::new();
if let Ok(var) = env::var("TANGO_HOST") {
for part in var.split(',') {
if let Some((host, port_raw)) = part.rsplit_once(':')
&& let Ok(port) = u16::from_str(port_raw) {
ret.push((host.into(), port));
}
}
}
ret.into_boxed_slice()
})
}