rstango 0.4.0

Implementation of the Tango distributed control system
Documentation
//! Implementation of the classes of a server.

use std::sync::{Arc, Mutex, Weak};
use std::collections::HashMap;
use std::fmt::{self, Debug};
use std::future::Future;
use std::any::Any;

use crate::dev::{All, Named, ToNames, ToNamesNotAll};
use crate::database::{GetPropSpec, SetPropSpec};
use crate::error::{Error, ErrorKind, Result};
use crate::name::{self, ArcName, Name};
use crate::value::Spectrum;

use super::{reserved_name, Model, RESERVED_ATTRS};
use super::dev::{AnyDev, Dev};
use super::server::Server;
use super::def::ClassDef;


/// A class of any model, which is how the server holds its classes.
pub(super) trait AnyClass: Debug + Send + Sync {

    /// The name of that class, shared so that it is cheaply copied out.
    fn name(&self) -> &Arc<str>;

    /// The rank of that class among the classes of its server, which is the order they
    /// were made in and the order their devices are initialized in, see [`Server::sync_async`].
    fn index(&self) -> usize;

    /// Add a device of that class to the server, see [`Class::add_dev`].
    fn add_dev(&self, name: &str) -> Result<Arc<dyn AnyDev>>;

    /// That class as [`Any`], for it to be downcast to the [`Class`] it is.
    fn into_any(self: Arc<Self>) -> Arc<dyn Any + Send + Sync>;

}

/// A class of a server, through which its devices reach its definition and the
/// properties the database holds for it.
pub struct Class<M: Model> {
    /// The name of that class, as its definition gave it, shared with the key it is
    /// registered under in the server.
    name: Arc<str>,
    /// See [`AnyClass::index`].
    index: usize,
    /// Self reference, for what a class hands its devices of itself.
    this: Weak<Self>,
    server: Arc<Server>,
    def: Arc<ClassDef<M>>,
    /// The properties the database holds for the attributes of this class, by attribute
    /// name, which every device of the class is configured from, see [`Self::attr_props`].
    attr_props: Mutex<HashMap<ArcName, Arc<[Named<Spectrum<str>>]>>>,
}

impl<M: Model> Class<M> {

    /// A new class of that server, from its definition, at that rank among the classes of
    /// the server, see [`AnyClass::index`].
    pub(super) fn new(server: Arc<Server>, def: Arc<ClassDef<M>>, index: usize) -> Arc<Self> {
        Arc::new_cyclic(|this| Self {
            name: Arc::from(&*def.name),
            index,
            this: this.clone(),
            server,
            def,
            attr_props: Mutex::new(HashMap::new()),
        })
    }

    /// The name of that class.
    #[inline]
    pub fn name(&self) -> &str {
        &self.name
    }

    /// The server this class is defined in.
    #[inline]
    pub fn server(&self) -> &Server {
        &self.server
    }

    // The properties of a class are the database's: everything below asks the database 
    // of the server for what it holds of this class, see the very same methods on
    // [`crate::database::DatabaseProxy`]. The blocking forms are for synchronous handlers, 
    // which run on the pool, and must never run in an asynchronous one.

    /// The names of the properties of this class that match the given filter.
    pub async fn list_prop_async(&self, filter: &str) -> Result<Spectrum<str>> {
        self.server().database().list_class_prop_async(&self.name, filter).await
    }

    /// The names of the properties of this class, see [`Self::list_prop_async`].
    pub fn list_prop(&self, filter: &str) -> Result<Spectrum<str>> {
        self.server().database().list_class_prop(&self.name, filter)
    }

    /// The properties of this class, parsed into the shape the given names ask for, see
    /// [`crate::database::DatabaseProxy::get_class_prop`].
    pub fn get_prop_async<Spec, Names: ToNames>(&self, names: Names) -> impl Future<Output = Result<Spec::Output>> + Send
    where
        Spec: GetPropSpec<Names>,
    {
        self.server().database().get_class_prop_async::<Spec, Names>(&self.name, names)
    }

    /// The properties of this class, see [`Self::get_prop_async`].
    pub fn get_prop<Spec, Names: ToNames>(&self, names: Names) -> Result<Spec::Output>
    where
        Spec: GetPropSpec<Names>,
    {
        self.server().database().get_class_prop::<Spec, Names>(&self.name, names)
    }

    /// Write the given properties of this class.
    pub fn set_prop_async<'a, Spec>(&'a self, spec: Spec) -> impl Future<Output = Result<()>> + Send
    where
        Spec: SetPropSpec<'a>,
    {
        self.server().database().set_class_prop_async(&self.name, spec)
    }

    /// Write the given properties of this class, see [`Self::set_prop_async`].
    pub fn set_prop<'a, Spec>(&'a self, spec: Spec) -> Result<()>
    where
        Spec: SetPropSpec<'a>,
    {
        self.server().database().set_class_prop(&self.name, spec)
    }

    /// Delete the given properties of this class.
    pub fn delete_prop_async<Names: ToNamesNotAll>(&self, names: Names) -> impl Future<Output = Result<()>> + Send {
        self.server().database().delete_class_prop_async(&self.name, names)
    }

    /// Delete the given properties of this class, see [`Self::delete_prop_async`].
    pub fn delete_prop<Names: ToNamesNotAll>(&self, names: Names) -> Result<()> {
        self.server().database().delete_class_prop(&self.name, names)
    }

    /// The properties the database holds for the attributes of this class of those names,
    /// in the same order, an attribute it holds nothing for having none.
    /// 
    /// They are the same for every device of the class, so the database is only asked the
    /// first time a device has an attribute of that name, and never again: what a device
    /// is configured with only changes at the level of the device itself, which is the
    /// one a client changes.
    ///
    /// The State and the Status have none, whatever the database holds: they are the
    /// server's own, which only the device configures, the way cppTango does.
    pub(super) async fn attr_props(&self, names: &[&str]) -> Result<Vec<Arc<[Named<Spectrum<str>>]>>> {

        // Only refetch the missing attribute names.
        let missing = {
            let attr_props = self.attr_props.lock().unwrap();
            names.iter()
                .copied()
                .filter(|name| !attr_props.contains_key(Name::new(name)))
                .filter(|name| reserved_name(name, RESERVED_ATTRS).is_none())
                .collect::<Vec<_>>()
        };

        if !missing.is_empty() {
            
            let fetched = self.server()
                .database()
                .get_class_attr_prop_async::<Spectrum<str>, _, _>(&self.name, missing.as_slice(), All).await?;

            let mut attr_props = self.attr_props.lock().unwrap();
            for (name, props) in missing.into_iter().zip(fetched) {
                attr_props.insert(ArcName::from(name), Arc::from(props?));
            }

        }

        let attr_props = self.attr_props.lock().unwrap();
        Ok(names
            .iter()
            .map(|name| attr_props.get(Name::new(name))
                .map_or_else(|| Arc::from([]), Arc::clone))
            .collect())

    }

    /// The definition of that class.
    #[inline]
    pub fn def(&self) -> &ClassDef<M> {
        &self.def
    }

    /// Add a device of that class to the server, whose name has to be a valid
    /// `domain/family/member` one, see [`name::check_tango_dev_name`], and unique
    /// server-wide.
    /// 
    /// The device is only registered: it is initialized, served and exported by the next
    /// [`Server::sync_async`].
    pub fn add_dev(&self, name: &str) -> Result<Arc<Dev<M>>> {

        if !name::check_tango_dev_name(name) {
            return Err(Error::new(ErrorKind::DevNameInvalid)
                .with_description(format!("Device {name:?} has an invalid name, it should be \
                    domain/family/member, each part made of letters, digits, '_' and '-', at most 85 long")));
        }

        let core = self.server.core_checked()?;
        let mut server_devs = core.devs.lock().unwrap();
        if server_devs.contains_key(Name::new(name)) {
            return Err(Error::new(ErrorKind::DevAlreadyDefined)
                .with_description(format!("Device {name:?} is already defined on the server")));
        }

        let name = Arc::<str>::from(name);
        // A class is only ever reached through a reference to it, so it is alive.
        let dev = Dev::new(self.this.upgrade().unwrap(), Arc::clone(&name));
        server_devs.insert(ArcName::from(name), Arc::clone(&dev) as Arc<dyn AnyDev>);
        drop(server_devs);

        core.pending.lock().unwrap().added.push(Arc::clone(&dev) as Arc<dyn AnyDev>);
        Ok(dev)

    }

}

impl<M: Model> AnyClass for Class<M> {

    #[inline]
    fn name(&self) -> &Arc<str> {
        &self.name
    }

    #[inline]
    fn index(&self) -> usize {
        self.index
    }

    fn add_dev(&self, name: &str) -> Result<Arc<dyn AnyDev>> {
        Class::add_dev(self, name).map(|dev| dev as Arc<dyn AnyDev>)
    }

    #[inline]
    fn into_any(self: Arc<Self>) -> Arc<dyn Any + Send + Sync> {
        self
    }

}

impl<M: Model> Debug for Class<M> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("Class").field("name", &self.name).finish_non_exhaustive()
    }
}