1import logging
2import threading
3
4from acts.event import event_bus
5from acts.event.event_subscription import EventSubscription
6from acts.event.subscription_handle import InstanceSubscriptionHandle
7from acts.event.subscription_handle import SubscriptionHandle
8from acts.event.subscription_handle import StaticSubscriptionHandle
9
10
11class SubscriptionBundle(object):
12    """A class for maintaining a set of EventSubscriptions in the event bus.
13
14    Attributes:
15        subscriptions: A dictionary of {EventSubscription: RegistrationID}
16    """
17
18    def __init__(self):
19        self.subscriptions = {}
20        self._subscription_lock = threading.Lock()
21        self._registered = False
22
23    @property
24    def registered(self):
25        """True if this SubscriptionBundle has been registered."""
26        return self._registered
27
28    def add(self, event_type, func, event_filter=None,
29            order=0):
30        """Adds a new Subscription to this SubscriptionBundle.
31
32        If this SubscriptionBundle is registered, the added Subscription will
33        also be registered.
34
35        Returns:
36            the EventSubscription object created.
37        """
38        subscription = EventSubscription(event_type, func,
39                                         event_filter=event_filter,
40                                         order=order)
41        return self.add_subscription(subscription)
42
43    def add_subscription(self, subscription):
44        """Adds an existing Subscription to the subscription bundle.
45
46        If this SubscriptionBundle is registered, the added subscription will
47        also be registered.
48
49        Returns:
50            the subscription object.
51        """
52        registration_id = None
53        with self._subscription_lock:
54            if self.registered:
55                registration_id = event_bus.register_subscription(subscription)
56
57            self.subscriptions[subscription] = registration_id
58        return subscription
59
60    def remove_subscription(self, subscription):
61        """Removes a subscription from the SubscriptionBundle.
62
63        If the SubscriptionBundle is registered, removing the subscription will
64        also unregister it.
65        """
66        if subscription not in self.subscriptions.keys():
67            return False
68        with self._subscription_lock:
69            if self.registered:
70                event_bus.unregister(self.subscriptions[subscription])
71            del self.subscriptions[subscription]
72        return True
73
74    def register(self):
75        """Registers all subscriptions found within this object."""
76        if self.registered:
77            return
78        with self._subscription_lock:
79            self._registered = True
80            for subscription, registration_id in self.subscriptions.items():
81                if registration_id is not None:
82                    logging.warning('Registered subscription found in '
83                                    'unregistered SubscriptionBundle: %s, %s' %
84                                    (subscription, registration_id))
85                self.subscriptions[subscription] = (
86                    event_bus.register_subscription(subscription))
87
88    def unregister(self):
89        """Unregisters all subscriptions managed by this SubscriptionBundle."""
90        if not self.registered:
91            return
92        with self._subscription_lock:
93            self._registered = False
94            for subscription, registration_id in self.subscriptions.items():
95                if registration_id is None:
96                    logging.warning('Unregistered subscription found in '
97                                    'registered SubscriptionBundle: %s, %s' %
98                                    (subscription, registration_id))
99                event_bus.unregister(subscription)
100                self.subscriptions[subscription] = None
101
102
103def create_from_static(obj):
104    """Generates a SubscriptionBundle from @subscribe_static functions on obj.
105
106    Args:
107        obj: The object that contains @subscribe_static functions. Can either
108             be a module or a class.
109
110    Returns:
111        An unregistered SubscriptionBundle.
112    """
113    return _create_from_object(obj, obj, StaticSubscriptionHandle)
114
115
116def create_from_instance(instance):
117    """Generates a SubscriptionBundle from an instance's @subscribe functions.
118
119    Args:
120        instance: The instance object that contains @subscribe functions.
121
122    Returns:
123        An unregistered SubscriptionBundle.
124    """
125    return _create_from_object(instance, instance.__class__,
126                               InstanceSubscriptionHandle)
127
128
129def _create_from_object(obj, obj_to_search, subscription_handle_type):
130    """Generates a SubscriptionBundle from an object's SubscriptionHandles.
131
132    Note that instance variables do not have the class's functions as direct
133    attributes. The attributes are resolved from the type of the object. Here,
134    we need to search through the instance's class to find the correct types,
135    and subscribe the instance-specific subscriptions.
136
137    Args:
138        obj: The object that contains SubscriptionHandles.
139        obj_to_search: The class to search for SubscriptionHandles from.
140        subscription_handle_type: The type of the SubscriptionHandles to
141                                  capture.
142
143    Returns:
144        An unregistered SubscriptionBundle.
145    """
146    bundle = SubscriptionBundle()
147    for attr_name, attr_value in obj_to_search.__dict__.items():
148        if isinstance(attr_value, subscription_handle_type):
149            bundle.add_subscription(getattr(obj, attr_name).subscription)
150        if isinstance(attr_value, staticmethod):
151            if isinstance(getattr(obj, attr_name), subscription_handle_type):
152                bundle.add_subscription(getattr(obj, attr_name).subscription)
153    return bundle
154