class Registry(QtCore.QThread):

    """
    A thread that handles attribute subscriptions.

    The main purpose of this thread is to offload the setting up and
    tearing down of subscriptions from the main UI thread, to make the
    operation "asynchronous". We don't want the UI to lock up briefly
    every time we do this (which may be very often in the case of a
    large, zoomable synoptic).
    """

    def __init__(self, event_callback, unsubscribe_callback, period=0.5):
        QtCore.QThread.__init__(self)
        self.listeners = CaselessDict()
        self.inverse_listeners = {}
        self.event_callback = event_callback
        self.unsubscribe_callback = unsubscribe_callback
        self.period = period
        self._attributes = CaselessList()
        self._config = CaselessDict()
        self._last_event = TTLDict(default_ttl=10)
        self.attribute_validator = TangoAttributeNameValidator()
        self.device_validator = TangoDeviceNameValidator()
        self.eval_validator = EvaluationAttributeNameValidator()
        self.lock = Lock()
        self.stopped = Event()

    def run(self):
        "A simple loop checking for changes to the attribute list"
        # "batching" the updates should be more efficient than
        # reacting immediately, especially when listeners can come and
        # go quite frequently.
        while not self.stopped.is_set():
            sleep(self.period)
            if self._attributes:
                attributes, self._attributes = self._attributes, CaselessList()
                with self.lock:
                    self._update(attributes)

    def subscribe(self, models=[]):
        """Set the currently subscribed list of models."""
        attrs = CaselessDict()
        for model in models:
            if self.device_validator.isValid(model):
                # for convenience, we subscribe to State for any devices
                attrs[model + "/State"] = True
            elif self.attribute_validator.isValid(model) or self.eval_validator.isValid(model):
                attrs[model] = True
            else:
                print "Invalid Taurus model %s!?" % model
        self._attributes = attrs

    def get_value(self, model):
        evt = self._last_event.get(model)
        if evt:
            return evt.attr_value

    def get_config(self, model):
        return self._config.get(model)

    def handle_event(self, evt_src, *args):
        # lookup the model(s) for this listener and notify them
        models = self.inverse_listeners.get(evt_src, [])
        for model in models:
            self.event_callback(model, evt_src, *args)

    def _update(self, attributes=CaselessDict()):

        "Update the subscriptions; add new ones, remove old ones"

        listeners = set(k.lower() for k in self.listeners.keys())
        new_attrs = set(attributes) - set(listeners)
        old_attrs = set(listeners) - set(attributes)

        for attr in old_attrs:
            if self._attributes:
                # meaning we got a new list of subscriptions, so
                # no point in continuing with this one.
                return
            self._remove_listener(attr)
            self._last_event.pop(attr, None)

        for attr in new_attrs:
            if self._attributes:
                return
            try:
                self._add_listener(attr)
            except PyTango.DevFailed as e:
                print "Failed to setup listener for", attr, e

        self.unsubscribe_callback(old_attrs)

    def _add_listener(self, model):
        try:
            listener = self.listeners[model] = Attribute(model)
            # to make loopkups more efficient, we also keep an "inverted"
            # mapping of listeners->models. But since some models may map
            # to the *same* listener (e.g. eval expressions), this must
            # be a one-to-many map.
            if listener in self.inverse_listeners:
                self.inverse_listeners[listener][model] = True
            else:
                self.inverse_listeners[listener] = CaselessDict([(model, True)])
            listener.addListener(self.handle_event)
            return listener
        except AttributeError:
            print "Failed to subscribe to model %s!" % model

    def _remove_listener(self, model):
        listener = self.listeners.pop(model)
        models = self.inverse_listeners[listener]
        print models, model
        models.pop(model)
        if not models:
            self.inverse_listeners.pop(listener)
        listener.removeListener(self.handle_event)

    def get_listener(self, model):
        "return the listener for a given model"
        if model in self.listeners:
            return self.listeners[model]
        for attr in self.listeners.values():
            if attr.getNormalName().lower() == model.lower():
                return attr

    def stop(self):
        self.stopped.set()

    def clear(self):
        self._attributes.clear()
        self._last_event.clear()
        self._update()
Example #2
0
def get_changes(data, calls):
    """Combine a list of database calls into "changes" that can
    be more easily turned into a readable representation"""

    devices = get_devices_from_dict(data["servers"])
    device_mapping = CaselessDict(
        (device, (server, instance, clss))
        for server, instance, clss, device in devices)
    classes = data.get("classes", {})

    # The idea is to first go through all database calls and collect
    # the changes per device. Then we can print it all out in a more
    # readable format since it will all be collected per device.
    #
    # Example of resulting dict:
    # {
    #     "sys/tg_test/1": {
    #         "server": "TangoTest",
    #         "instance": "test",
    #         "device_class": "TangoTest",
    #         "properties": {
    #             # added property has no old_value
    #             "hej": {
    #                 "value": ["a", "b", "c"]
    #             },
    #             # removed property has no value
    #             "svej": {
    #                 "old_value": ["dsaodkasokd"]
    #             },
    #             # changed property has both
    #             "flepp": {
    #                 "old_value": ["1", "3"],
    #                 "value": ["1", "2", "3"]
    #             }
    #         }
    #     }
    # }

    changes = {"devices": SetterDict(), "classes": SetterDict()}

    # Go through all database calls and store the changes
    for method, args, kwargs in calls:

        if method == "put_device_alias":
            device, alias = args
            if device in device_mapping:
                old_alias = get_device_data(device, device_mapping,
                                            data).get("alias")
            else:
                old_alias = None
            changes["devices"][device].update(
                {"alias": {
                    "old_value": old_alias,
                    "value": alias
                }})

        elif method == "add_device":
            info, = args
            changes["devices"][info.name].update(added=True,
                                                 server=info.server,
                                                 device_class=info._class)
            if info.name in device_mapping:
                old_server, old_instance, old_class = device_mapping[info.name]
                changes["devices"][info.name]["old_server"] = "{}/{}".format(
                    old_server, old_instance)
                changes["devices"][info.name]["old_class"] = old_class

        elif method == "delete_device":
            device, = args
            server, instance, clss = device_mapping[device]
            device_data = data["servers"][server][instance][clss]
            properties = device_data.get("properties", {})
            changes["devices"][device].update(deleted=True,
                                              server="{}/{}".format(
                                                  server, instance),
                                              instance=instance,
                                              device_class=clss,
                                              properties=properties)

        elif method == "put_device_property":
            device, properties = args
            old_data = get_device_data(device, device_mapping, data)
            caseless_props = CaselessDict(old_data.get("properties", {}))
            if "properties" not in changes["devices"][device]:
                changes["devices"][device]["properties"] = {}
            for name, value in properties.items():
                old_value = caseless_props.get(name)
                if value != old_value:
                    changes["devices"][device]["properties"].update(
                        {name: {
                            "value": value,
                            "old_value": old_value
                        }})

        elif method == "delete_device_property":
            device, properties = args
            old_data = get_device_data(device, device_mapping, data)
            caseless_props = CaselessDict(old_data.get("properties", {}))
            prop_changes = changes["devices"][device].setdefault(
                "properties", {})
            for prop in properties:
                old_value = caseless_props[prop]
                prop_changes[prop] = {"old_value": old_value}

        elif method == "put_device_attribute_property":
            device, properties = args
            attr_props = changes["devices"][device].setdefault(
                "attribute_properties", {})
            old_data = get_device_data(device, device_mapping, data)
            caseless_attrs = CaselessDict(
                old_data.get("attribute_properties", {}))
            for attr, props in properties.items():
                caseless_props = CaselessDict(caseless_attrs.get(attr, {}))
                for name, value in props.items():
                    old_value = caseless_props.get(name)
                    if value != old_value:
                        attr_props[attr] = {
                            name: {
                                "old_value": old_value,
                                "value": value
                            }
                        }

        elif method == "delete_device_attribute_property":
            device, attributes = args
            prop_changes = attr_props = changes["devices"][device].setdefault(
                "attribute_properties", {})
            old_data = get_device_data(device, device_mapping, data)
            caseless_attrs = CaselessDict(
                old_data.get("attribute_properties", {}))
            for attr, props in attributes.items():
                caseless_props = CaselessDict(caseless_attrs[attr])
                for prop in props:
                    old_value = caseless_props.get(prop)
                    attr_props[attr] = {prop: {"old_value": old_value}}

        elif method == "put_class_property":
            clss, properties = args
            old_data = classes.get(clss, {})
            caseless_props = CaselessDict(old_data.get("properties", {}))
            prop_changes = changes["classes"][clss].setdefault(
                "properties", {})
            for name, value in properties.items():
                old_value = caseless_props.get(name)
                if value != old_value:
                    prop_changes.update(
                        {name: {
                            "value": value,
                            "old_value": old_value
                        }})

        elif method == "delete_class_property":
            clss, properties = args
            old_data = classes.get(clss, {})
            caseless_props = CaselessDict(old_data.get("properties", {}))
            prop_changes = changes["classes"][clss].setdefault(
                "properties", {})
            for prop in properties:
                old_value = caseless_props.get(prop)
                prop_changes[prop] = {"old_value": old_value}

        elif method == "put_class_attribute_property":
            clss, properties = args
            attr_props = changes["classes"][clss].setdefault(
                "attribute_properties", {})
            old_data = classes.get(clss, {})
            caseless_attrs = CaselessDict(
                old_data.get("attribute_properties", {}))
            for attr, props in properties.items():
                caseless_props = CaselessDict(caseless_attrs.get(attr, {}))
                for name, value in props.items():
                    old_value = caseless_props.get(name)
                    if value != old_value:
                        attr_props[attr] = {
                            name: {
                                "old_value": old_value,
                                "value": value
                            }
                        }

        elif method == "delete_class_attribute_property":
            clss, attributes = args
            attr_props = changes["classes"][clss].setdefault(
                "attribute_properties", {})
            old_data = classes.get(clss, {})
            caseless_attrs = CaselessDict(old_data.get("properties", {}))
            for attr, props in attributes.items():
                caseless_props = CaselessDict(caseless_attrs.get(attr, {}))
                for prop in props:
                    old_value = caseless_props.get(prop)
                    attr_props[attr] = {prop: {"old_value": old_value}}

    return {
        "devices": changes["devices"].to_dict(),
        "classes": changes["classes"].to_dict(),
    }
Example #3
0
class Registry(QtCore.QThread):

    """
    A thread that handles attribute subscriptions.

    The main purpose of this thread is to offload the setting up and
    tearing down of subscriptions from the main UI thread, to make the
    operation "asynchronous". We don't want the UI to lock up briefly
    every time we do this (which may be very often in the case of a
    large, zoomable synoptic).
    """

    def __init__(self, event_callback, unsubscribe_callback, period=0.5):
        QtCore.QThread.__init__(self)
        self.listeners = CaselessDict()
        self.inverse_listeners = {}
        self.event_callback = event_callback
        self.unsubscribe_callback = unsubscribe_callback
        self.period = period
        self._attributes = CaselessList()
        self._config = CaselessDict()
        self._last_event = TTLDict(default_ttl=10)
        self.attribute_validator = TangoAttributeNameValidator()
        self.device_validator = TangoDeviceNameValidator()
        self.eval_validator = EvaluationAttributeNameValidator()
        self.lock = Lock()
        self.stopped = Event()

    def run(self):
        "A simple loop checking for changes to the attribute list"
        # "batching" the updates should be more efficient than
        # reacting immediately, especially when listeners can come and
        # go quite frequently.
        while not self.stopped.is_set():
            sleep(self.period)
            if self._attributes:
                attributes, self._attributes = self._attributes, CaselessList()
                with self.lock:
                    self._update(attributes)

    def subscribe(self, models=[]):
        """Set the currently subscribed list of models."""
        attrs = CaselessDict()
        for model in models:
            if self.device_validator.isValid(model):
                # for convenience, we subscribe to State for any devices
                attrs[model + "/State"] = True
            elif (self.attribute_validator.isValid(model) or
                    self.eval_validator.isValid(model)):
                attrs[model] = True
            else:
                print "Invalid Taurus model %s!?" % model
        self._attributes = attrs

    def get_value(self, model):
        evt = self._last_event.get(model)
        if evt:
            return evt.attr_value

    def get_config(self, model):
        return self._config.get(model)

    def handle_event(self, evt_src, *args):
        # lookup the model(s) for this listener and notify them
        models = self.inverse_listeners.get(evt_src, [])
        for model in models:
            self.event_callback(model, evt_src, *args)

    def _update(self, attributes=CaselessDict()):

        "Update the subscriptions; add new ones, remove old ones"

        listeners = set(k.lower() for k in self.listeners.keys())
        new_attrs = set(attributes) - set(listeners)
        old_attrs = set(listeners) - set(attributes)

        for attr in old_attrs:
            if self._attributes:
                # meaning we got a new list of subscriptions, so
                # no point in continuing with this one.
                return
            self._remove_listener(attr)
            self._last_event.pop(attr, None)

        for attr in new_attrs:
            if self._attributes:
                return
            try:
                self._add_listener(attr)
            except PyTango.DevFailed as e:
                print "Failed to setup listener for", attr, e

        self.unsubscribe_callback(old_attrs)

    def _add_listener(self, model):
        try:
            listener = self.listeners[model] = Attribute(model)
            # to make loopkups more efficient, we also keep an "inverted"
            # mapping of listeners->models. But since some models may map
            # to the *same* listener (e.g. eval expressions), this must
            # be a one-to-many map.
            if listener in self.inverse_listeners:
                self.inverse_listeners[listener][model] = True
            else:
                self.inverse_listeners[listener] = CaselessDict([(model, True)])
            listener.addListener(self.handle_event)
            return listener
        except AttributeError:
            print "Failed to subscribe to model %s!" % model

    def _remove_listener(self, model):
        listener = self.listeners.pop(model)
        models = self.inverse_listeners[listener]
        print models, model
        models.pop(model)
        if not models:
            self.inverse_listeners.pop(listener)
        listener.removeListener(self.handle_event)

    def get_listener(self, model):
        "return the listener for a given model"
        if model in self.listeners:
            return self.listeners[model]
        for attr in self.listeners.values():
            if attr.getNormalName().lower() == model.lower():
                return attr

    def stop(self):
        self.stopped.set()

    def clear(self):
        self._attributes.clear()
        self._last_event.clear()
        self._update()