using System; using System.Collections.Generic; using System.IO; using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using MQTTnet.Server.Configuration; using Newtonsoft.Json; namespace MQTTnet.Server.Mqtt { public class MqttServerStorage : IMqttServerStorage { private readonly List _messages = new List(); private readonly SettingsModel _settings; private readonly ILogger _logger; private string _filename; private bool _messagesHaveChanged; public MqttServerStorage(SettingsModel settings, ILogger logger) { _settings = settings ?? throw new ArgumentNullException(nameof(settings)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } public void Configure() { if (_settings.RetainedApplicationMessages?.Persist != true) { _logger.LogInformation("Persisting of retained application messages is disabled."); return; } _filename = ExpandFilename(); // The retained application messages are stored in a separate thread. // This is mandatory because writing them to a slow storage (like RaspberryPi SD card) // will slow down the whole message processing speed. Task.Run(SaveRetainedMessagesInternalAsync, CancellationToken.None); } public Task SaveRetainedMessagesAsync(IList messages) { lock (_messages) { _messages.Clear(); _messages.AddRange(messages); _messagesHaveChanged = true; } return Task.CompletedTask; } private async Task SaveRetainedMessagesInternalAsync() { while (true) { try { await Task.Delay(TimeSpan.FromSeconds(_settings.RetainedApplicationMessages.WriteInterval)).ConfigureAwait(false); List messages; lock (_messages) { if (!_messagesHaveChanged) { continue; } messages = new List(_messages); _messagesHaveChanged = false; } var json = JsonConvert.SerializeObject(messages); await File.WriteAllTextAsync(_filename, json, Encoding.UTF8).ConfigureAwait(false); _logger.LogInformation($"{messages.Count} retained MQTT messages written."); } catch (Exception exception) { _logger.LogError(exception, "Error while writing retained MQTT messages."); } } } public async Task> LoadRetainedMessagesAsync() { if (_settings.RetainedApplicationMessages?.Persist != true) { return null; } if (!File.Exists(_filename)) { return null; } try { var json = await File.ReadAllTextAsync(_filename).ConfigureAwait(false); var applicationMessages = JsonConvert.DeserializeObject>(json); _logger.LogInformation($"{applicationMessages.Count} retained MQTT messages loaded."); return applicationMessages; } catch (Exception exception) { _logger.LogWarning(exception, "Error while loading persisted retained application messages."); return null; } } private string ExpandFilename() { var filename = _settings.RetainedApplicationMessages.Filename; var uri = new Uri(filename, UriKind.RelativeOrAbsolute); if (!uri.IsAbsoluteUri) { filename = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, filename); } return filename; } } }