Line data Source code
1 : /*
2 : * Package : mqtt_client
3 : * Author : S. Hamblett <steve.hamblett@linux.com>
4 : * Date : 30/06/2017
5 : * Copyright : S.Hamblett
6 : */
7 :
8 : part of mqtt_client;
9 :
10 : /// A class that can manage the topic subscription process.
11 : class SubscriptionsManager extends events.EventDetector {
12 : /// Dispenser used for keeping track of subscription ids
13 : MessageIdentifierDispenser messageIdentifierDispenser =
14 : new MessageIdentifierDispenser();
15 :
16 : /// List of confirmed subscriptions, keyed on the topic name.
17 : Map<String, Subscription> subscriptions = new Map<String, Subscription>();
18 :
19 : /// A list of subscriptions that are pending acknowledgement, keyed on the message identifier.
20 : Map<int, Subscription> pendingSubscriptions = new Map<int, Subscription>();
21 :
22 : /// The connection handler that we use to subscribe to subscription acknowledgements.
23 : IMqttConnectionHandler connectionHandler;
24 :
25 : /// Publishing manager used for passing on published messages to subscribers.
26 : PublishingManager publishingManager;
27 :
28 : /// Observable publish messages received map, indexed by topic
29 : Map<String, ChangeNotifier<MqttReceivedMessage>> messagesReceived =
30 : new Map<String, ChangeNotifier<MqttReceivedMessage>>();
31 :
32 : /// Creates a new instance of a SubscriptionsManager that uses the specified connection to manage subscriptions.
33 : SubscriptionsManager(IMqttConnectionHandler connectionHandler,
34 2 : IPublishingManager publishingManager) {
35 2 : this.connectionHandler = connectionHandler;
36 2 : this.publishingManager = publishingManager;
37 : this
38 2 : .connectionHandler
39 4 : .registerForMessage(MqttMessageType.subscribeAck, confirmSubscription);
40 : this
41 2 : .connectionHandler
42 4 : .registerForMessage(MqttMessageType.unsubscribeAck, confirmUnsubscribe);
43 : // Start listening for published messages
44 2 : this.listen(
45 4 : this.publishingManager, MessageReceived, publishMessageReceived);
46 : }
47 :
48 : /// Registers a new subscription with the subscription manager.
49 : ChangeNotifier<MqttReceivedMessage> registerSubscription(String topic,
50 : MqttQos qos) {
51 1 : ChangeNotifier<MqttReceivedMessage> cn = tryGetExistingSubscription(topic);
52 : if (cn == null) {
53 1 : cn = createNewSubscription(topic, qos);
54 : }
55 : return cn;
56 : }
57 :
58 : /// Gets a view on the existing observable, if the subscription already exists.
59 : ChangeNotifier<MqttReceivedMessage> tryGetExistingSubscription(String topic) {
60 2 : Subscription retSub = subscriptions[topic];
61 : if (retSub == null) {
62 : // Search the pending subscriptions
63 2 : for (Subscription sub in pendingSubscriptions.values) {
64 0 : if (sub.topic.rawTopic == topic) {
65 : retSub = sub;
66 : }
67 : }
68 : }
69 0 : return retSub != null ? retSub.observable : null;
70 : }
71 :
72 : /// Creates a new subscription for the specified topic.
73 : ChangeNotifier<MqttReceivedMessage> createNewSubscription(String topic,
74 : MqttQos qos) {
75 : try {
76 1 : final SubscriptionTopic subscriptionTopic = new SubscriptionTopic(topic);
77 : // Get an ID that represents the subscription. We will use this same ID for unsubscribe as well.
78 : final int msgId =
79 2 : messageIdentifierDispenser.getNextMessageIdentifier("subscriptions");
80 : // Create a new observable that is used to yield messages
81 : // that arrive for the topic.
82 : final ChangeNotifier<MqttReceivedMessage> observable =
83 1 : createObservableForSubscription(subscriptionTopic, msgId);
84 1 : final Subscription sub = new Subscription();
85 1 : sub.topic = subscriptionTopic;
86 1 : sub.qos = qos;
87 1 : sub.messageIdentifier = msgId;
88 2 : sub.createdTime = new DateTime.now();
89 1 : sub.observable = observable;
90 3 : pendingSubscriptions[sub.messageIdentifier] = sub;
91 : // Build a subscribe message for the caller and send it off to the broker.
92 1 : final MqttSubscribeMessage msg = new MqttSubscribeMessage()
93 2 : .withMessageIdentifier(sub.messageIdentifier)
94 3 : .toTopic(sub.topic.rawTopic)
95 2 : .atQos(sub.qos);
96 2 : connectionHandler.sendMessage(msg);
97 1 : return sub.observable;
98 : } catch (Exception) {
99 0 : throw new InvalidTopicException(
100 : "from SubscriptionManager::createNewSubscription", topic);
101 : }
102 : }
103 :
104 : /// Publish message received
105 : void publishMessageReceived(events.Event<MessageReceived> event) {
106 3 : final String topic = event.data.topic.rawTopic;
107 2 : if (messagesReceived.containsKey(topic)) {
108 : final MqttReceivedMessage<MqttMessage> msg =
109 3 : new MqttReceivedMessage<MqttMessage>(topic, event.data.message);
110 3 : messagesReceived[topic].notifyChange(msg);
111 : }
112 : }
113 :
114 : /// Creates an observable for a subscription.
115 : ChangeNotifier<MqttReceivedMessage> createObservableForSubscription(
116 : SubscriptionTopic subscriptionTopic, int msgId) {
117 1 : final String topic = subscriptionTopic.rawTopic;
118 : final ChangeNotifier<MqttReceivedMessage> cn =
119 1 : new ChangeNotifier<MqttReceivedMessage>();
120 2 : messagesReceived[topic] = cn;
121 : return cn;
122 : }
123 :
124 : /// Unsubscribe from a topic
125 : void unsubscribe(String topic) {
126 1 : final MqttUnsubscribeMessage unsubscribeMsg = new MqttUnsubscribeMessage()
127 2 : .withMessageIdentifier(messageIdentifierDispenser
128 1 : .getNextMessageIdentifier("unsubscriptions"))
129 1 : .fromTopic(topic);
130 2 : connectionHandler.sendMessage(unsubscribeMsg);
131 : }
132 :
133 : /// Confirms a subscription has been made with the broker. Marks the sub as confirmed in the subs storage.
134 : /// Returns true, always.
135 : bool confirmSubscription(MqttMessage msg) {
136 1 : final MqttSubscribeAckMessage subAck = msg as MqttSubscribeAckMessage;
137 1 : if (pendingSubscriptions
138 3 : .containsKey(subAck.variableHeader.messageIdentifier)) {
139 : final String topic =
140 4 : pendingSubscriptions[subAck.variableHeader.messageIdentifier]
141 1 : .topic
142 1 : .rawTopic;
143 2 : subscriptions[topic] =
144 4 : pendingSubscriptions[subAck.variableHeader.messageIdentifier];
145 4 : pendingSubscriptions.remove(subAck.variableHeader.messageIdentifier);
146 : }
147 : return true;
148 : }
149 :
150 : /// Cleans up after an unsubscribe message is received from the broker.
151 : /// returns true, always
152 : bool confirmUnsubscribe(MqttMessage msg) {
153 1 : final MqttUnsubscribeAckMessage unSubAck = msg as MqttUnsubscribeAckMessage;
154 : String subKey;
155 : Subscription sub;
156 2 : subscriptions.forEach((String key, Subscription value) {
157 2 : if (value.messageIdentifier ==
158 2 : unSubAck.variableHeader.messageIdentifier) {
159 : sub = value;
160 : subKey = key;
161 : }
162 : });
163 : // If we have the subscription remove it
164 : if (sub != null) {
165 2 : subscriptions.remove(subKey);
166 2 : messagesReceived.remove(subKey);
167 : }
168 : return true;
169 : }
170 :
171 : /// Gets the current status of a subscription.
172 : SubscriptionStatus getSubscriptionsStatus(String topic) {
173 : SubscriptionStatus status = SubscriptionStatus.doesNotExist;
174 2 : if (subscriptions.containsKey(topic)) {
175 : status = SubscriptionStatus.active;
176 : }
177 2 : pendingSubscriptions.forEach((int key, Subscription value) {
178 3 : if (value.topic.rawTopic == topic) {
179 : status = SubscriptionStatus.pending;
180 : }
181 : });
182 : return status;
183 : }
184 : }
|