diff --git a/Store/IStore.cs b/Store/IStore.cs new file mode 100644 index 0000000..6c5d906 --- /dev/null +++ b/Store/IStore.cs @@ -0,0 +1,12 @@ +namespace HermesSocketServer.Store +{ + public interface IStore + { + V? Get(K key); + IEnumerable Get(); + Task Load(); + void Remove(K? key); + Task Save(); + bool Set(K? key, V? value); + } +} \ No newline at end of file diff --git a/Store/VoiceStore.cs b/Store/VoiceStore.cs new file mode 100644 index 0000000..5c15223 --- /dev/null +++ b/Store/VoiceStore.cs @@ -0,0 +1,212 @@ +using System.Collections.Immutable; +using System.Text; +using HermesSocketLibrary.db; +using HermesSocketServer.Validators; + +namespace HermesSocketServer.Store +{ + public class VoiceStore : IStore + { + private readonly Database _database; + private readonly IValidator _voiceIdValidator; + private readonly IValidator _voiceNameValidator; + private readonly Serilog.ILogger _logger; + private readonly IDictionary _voices; + private readonly IList _added; + private readonly IList _modified; + private readonly IList _deleted; + private readonly object _lock; + + public DateTime PreviousSave; + + + public VoiceStore(Database database, IValidator voiceIdValidator, IValidator voiceNameValidator, Serilog.ILogger logger) + { + _database = database; + _voiceIdValidator = voiceIdValidator; + _voiceNameValidator = voiceNameValidator; + _logger = logger; + _voices = new Dictionary(); + _added = new List(); + _modified = new List(); + _deleted = new List(); + _lock = new object(); + + PreviousSave = DateTime.UtcNow; + } + + public string? Get(string key) + { + if (_voices.TryGetValue(key, out var voice)) + return voice; + return null; + } + + public IEnumerable Get() + { + return _voices.Values.ToImmutableList(); + } + + public async Task Load() + { + string sql = "SELECT id, name FROM \"TtsVoice\";"; + await _database.Execute(sql, new Dictionary(), (reader) => + { + var id = reader.GetString(0); + var name = reader.GetString(1); + _voices.Add(id, name); + }); + } + + public void Remove(string? key) + { + if (key == null) + return; + + lock (_lock) + { + if (_voices.ContainsKey(key)) + { + _voices.Remove(key); + if (!_added.Remove(key)) + { + _modified.Remove(key); + if (!_deleted.Contains(key)) + _deleted.Add(key); + } + } + } + } + + public async Task Save() + { + var sb = new StringBuilder(); + var sql = ""; + + if (_added.Any()) + { + sb.Append("INSERT INTO \"TtsVoice\" (id, name) VALUES "); + lock (_lock) + { + foreach (var voiceId in _added) + { + string voice = _voices[voiceId]; + sb.Append("('") + .Append(voiceId) + .Append("','") + .Append(voice) + .Append("'),"); + } + sb.Remove(sb.Length - 1, 1) + .Append(';'); + + sql = sb.ToString(); + sb.Clear(); + _added.Clear(); + } + + try + { + await _database.ExecuteScalar(sql); + } + catch (Exception ex) + { + _logger.Error(ex, "Failed to save TTS voices on database: " + sql); + } + } + + if (_modified.Any()) + { + sb.Append("UPDATE \"TtsVoice\" as t SET name = c.name FROM (VALUES "); + lock (_lock) + { + foreach (var voiceId in _modified) + { + string voice = _voices[voiceId]; + sb.Append("('") + .Append(voiceId) + .Append("','") + .Append(voice) + .Append("'),"); + } + sb.Remove(sb.Length - 1, 1) + .Append(") AS c(id, name) WHERE id = c.id;"); + + + sql = sb.ToString(); + sb.Clear(); + _modified.Clear(); + } + + try + { + await _database.ExecuteScalar(sql); + } + catch (Exception ex) + { + _logger.Error(ex, "Failed to modify TTS voices on database: " + sql); + } + } + + if (_deleted.Any()) + { + sb.Append("DELETE FROM \"TtsVoice\" WHERE id IN ("); + lock (_lock) + { + foreach (var voiceId in _deleted) + { + sb.Append("'") + .Append(voiceId) + .Append("',"); + } + sb.Remove(sb.Length - 1, 1) + .Append(");"); + + + sql = sb.ToString(); + sb.Clear(); + _deleted.Clear(); + } + + try + { + await _database.ExecuteScalar(sql); + } + catch (Exception ex) + { + _logger.Error(ex, "Failed to modify TTS voices on database: " + sql); + } + } + } + + public bool Set(string? key, string? value) + { + if (key == null || value == null) + return false; + _voiceNameValidator.Check(value); + + lock (_lock) + { + if (_voices.TryGetValue(key, out var voice)) + { + + if (voice != value) + { + _voices[key] = value; + if (!_added.Contains(key) && !_modified.Contains(key)) + _modified.Add(key); + } + } + else + { + _voiceIdValidator.Check(key); + _voices.Add(key, value); + if (!_deleted.Remove(key) && !_modified.Contains(key)) + _added.Add(key); + } + } + + return true; + } + } +} \ No newline at end of file