You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

147 lines
5.3 KiB

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Threading;
  5. using System.Threading.Tasks;
  6. using MQTTnet.Core.Packets;
  7. using Microsoft.Extensions.Logging;
  8. using Microsoft.Extensions.Options;
  9. namespace MQTTnet.Core.Server
  10. {
  11. public sealed class MqttClientRetainedMessagesManager : IMqttClientRetainedMessageManager
  12. {
  13. private readonly Dictionary<string, MqttApplicationMessage> _retainedMessages = new Dictionary<string, MqttApplicationMessage>();
  14. private readonly SemaphoreSlim _gate = new SemaphoreSlim(1, 1);
  15. private readonly ILogger<MqttClientRetainedMessagesManager> _logger;
  16. private readonly MqttServerOptions _options;
  17. public MqttClientRetainedMessagesManager(IOptions<MqttServerOptions> options, ILogger<MqttClientRetainedMessagesManager> logger)
  18. {
  19. _logger = logger ?? throw new ArgumentNullException(nameof(logger));
  20. _options = options.Value ?? throw new ArgumentNullException(nameof(options));
  21. }
  22. public async Task LoadMessagesAsync()
  23. {
  24. if (_options.Storage == null)
  25. {
  26. return;
  27. }
  28. await _gate.WaitAsync();
  29. try
  30. {
  31. var retainedMessages = await _options.Storage.LoadRetainedMessagesAsync();
  32. _retainedMessages.Clear();
  33. foreach (var retainedMessage in retainedMessages)
  34. {
  35. _retainedMessages[retainedMessage.Topic] = retainedMessage;
  36. }
  37. }
  38. catch (Exception exception)
  39. {
  40. _logger.LogError(new EventId(), exception, "Unhandled exception while loading retained messages.");
  41. }
  42. finally
  43. {
  44. _gate.Release();
  45. }
  46. }
  47. public async Task HandleMessageAsync(string clientId, MqttApplicationMessage applicationMessage)
  48. {
  49. if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage));
  50. await _gate.WaitAsync().ConfigureAwait(false);
  51. try
  52. {
  53. await HandleMessageInternalAsync(clientId, applicationMessage);
  54. }
  55. catch (Exception exception)
  56. {
  57. _logger.LogError(new EventId(), exception, "Unhandled exception while handling retained messages.");
  58. }
  59. finally
  60. {
  61. _gate.Release();
  62. }
  63. }
  64. public async Task<List<MqttApplicationMessage>> GetSubscribedMessagesAsync(MqttSubscribePacket subscribePacket)
  65. {
  66. var retainedMessages = new List<MqttApplicationMessage>();
  67. await _gate.WaitAsync().ConfigureAwait(false);
  68. try
  69. {
  70. foreach (var retainedMessage in _retainedMessages.Values)
  71. {
  72. foreach (var topicFilter in subscribePacket.TopicFilters)
  73. {
  74. if (retainedMessage.QualityOfServiceLevel < topicFilter.QualityOfServiceLevel)
  75. {
  76. continue;
  77. }
  78. if (!MqttTopicFilterComparer.IsMatch(retainedMessage.Topic, topicFilter.Topic))
  79. {
  80. continue;
  81. }
  82. retainedMessages.Add(retainedMessage);
  83. break;
  84. }
  85. }
  86. }
  87. finally
  88. {
  89. _gate.Release();
  90. }
  91. return retainedMessages;
  92. }
  93. private async Task HandleMessageInternalAsync(string clientId, MqttApplicationMessage applicationMessage)
  94. {
  95. var saveIsRequired = false;
  96. if (applicationMessage.Payload?.Any() == false)
  97. {
  98. saveIsRequired = _retainedMessages.Remove(applicationMessage.Topic);
  99. _logger.LogInformation("Client '{0}' cleared retained message for topic '{1}'.", clientId, applicationMessage.Topic);
  100. }
  101. else
  102. {
  103. if (!_retainedMessages.ContainsKey(applicationMessage.Topic))
  104. {
  105. _retainedMessages[applicationMessage.Topic] = applicationMessage;
  106. saveIsRequired = true;
  107. }
  108. else
  109. {
  110. var existingMessage = _retainedMessages[applicationMessage.Topic];
  111. if (existingMessage.QualityOfServiceLevel != applicationMessage.QualityOfServiceLevel || !existingMessage.Payload.SequenceEqual(applicationMessage.Payload ?? new byte[0]))
  112. {
  113. _retainedMessages[applicationMessage.Topic] = applicationMessage;
  114. saveIsRequired = true;
  115. }
  116. }
  117. _logger.LogInformation("Client '{0}' set retained message for topic '{1}'.", clientId, applicationMessage.Topic);
  118. }
  119. if (!saveIsRequired)
  120. {
  121. _logger.LogTrace("Skipped saving retained messages because no changes were detected.");
  122. }
  123. if (saveIsRequired && _options.Storage != null)
  124. {
  125. await _options.Storage.SaveRetainedMessagesAsync(_retainedMessages.Values.ToList());
  126. }
  127. }
  128. }
  129. }