LCOV - code coverage report
Current view: top level - src - mqtt_client_subscriptions_manager.dart (source / functions) Hit Total Coverage
Test: coverage.lcov Lines: 59 62 95.2 %
Date: 2017-10-09 Functions: 0 0 -

          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             : }

Generated by: LCOV version 1.10