Redid stores. Added user store. Added Channel Manager, to manage data from a single channel.

This commit is contained in:
Tom
2024-10-19 01:50:46 +00:00
parent 3f3ba63554
commit 4d0b38babd
22 changed files with 654 additions and 475 deletions

View File

@ -1,282 +1,99 @@
using System.Collections.Immutable;
using System.Text;
using HermesSocketLibrary.db;
using HermesSocketServer.Models;
namespace HermesSocketServer.Store
{
public class ChatterStore : IStore<string, long, string>
public class ChatterStore : GroupSaveStore<string, ChatterVoice>
{
private readonly string _userId;
private readonly Database _database;
private readonly Serilog.ILogger _logger;
private readonly IDictionary<string, IDictionary<long, string>> _chatters;
private readonly IDictionary<string, IList<long>> _added;
private readonly IDictionary<string, IList<long>> _modified;
private readonly IDictionary<string, IList<long>> _deleted;
private readonly object _lock;
private readonly GroupSaveSqlGenerator<ChatterVoice> _generator;
public ChatterStore(Database database, Serilog.ILogger logger)
public ChatterStore(string userId, Database database, Serilog.ILogger logger) : base(logger)
{
_userId = userId;
_database = database;
_logger = logger;
_chatters = new Dictionary<string, IDictionary<long, string>>();
_added = new Dictionary<string, IList<long>>();
_modified = new Dictionary<string, IList<long>>();
_deleted = new Dictionary<string, IList<long>>();
_lock = new object();
}
public string? Get(string user, long key)
{
if (!_chatters.TryGetValue(user, out var broadcaster))
return null;
if (broadcaster.TryGetValue(key, out var chatter))
return chatter;
return null;
}
public IEnumerable<string> Get()
{
return _chatters.Select(c => c.Value).SelectMany(c => c.Values).ToImmutableList();
}
public IDictionary<long, string> Get(string user)
{
if (_chatters.TryGetValue(user, out var chatters))
return chatters.ToImmutableDictionary();
return new Dictionary<long, string>();
}
public async Task Load()
{
string sql = "SELECT \"chatterId\", \"ttsVoiceId\", \"userId\" FROM \"TtsChatVoice\";";
await _database.Execute(sql, new Dictionary<string, object>(), (reader) =>
var ctp = new Dictionary<string, string>
{
var chatterId = reader.GetInt64(0);
var ttsVoiceId = reader.GetString(1);
var userId = reader.GetString(2);
if (!_chatters.TryGetValue(userId, out var chatters))
{ "chatterId", "ChatterId" },
{ "ttsVoiceId", "VoiceId" },
{ "userId", "UserId" },
};
_generator = new GroupSaveSqlGenerator<ChatterVoice>(ctp);
}
public override async Task Load()
{
var data = new Dictionary<string, object>() { { "user", _userId } };
string sql = $"SELECT \"chatterId\", \"ttsVoiceId\" FROM \"TtsChatVoice\" WHERE \"userId\" = @user";
await _database.Execute(sql, data, (reader) =>
{
string chatterId = reader.GetInt64(0).ToString();
_store.Add(chatterId, new ChatterVoice()
{
chatters = new Dictionary<long, string>();
_chatters.Add(userId, chatters);
}
chatters.Add(chatterId, ttsVoiceId);
UserId = _userId,
ChatterId = chatterId,
VoiceId = reader.GetString(1)
});
});
_logger.Information($"Loaded {_chatters.Count} TTS voices from database.");
_logger.Information($"Loaded {_store.Count} TTS chatter voices from database.");
}
public void Remove(string user, long? key)
public override void OnInitialAdd(string key, ChatterVoice value)
{
if (key == null)
return;
lock (_lock)
{
if (_chatters.TryGetValue(user, out var chatters) && chatters.Remove(key.Value))
{
if (!_added.TryGetValue(user, out var added) || !added.Remove(key.Value))
{
if (_modified.TryGetValue(user, out var modified))
modified.Remove(key.Value);
if (!_deleted.TryGetValue(user, out var deleted))
{
deleted = new List<long>();
_deleted.Add(user, deleted);
deleted.Add(key.Value);
}
else if (!deleted.Contains(key.Value))
deleted.Add(key.Value);
}
}
}
}
public void Remove(string? leftKey, long rightKey)
public override void OnInitialModify(string key, ChatterVoice value)
{
throw new NotImplementedException();
}
public async Task<bool> Save()
public override void OnInitialRemove(string key)
{
var changes = false;
var sb = new StringBuilder();
var sql = "";
}
public override async Task<bool> Save()
{
int count = 0;
string sql = string.Empty;
if (_added.Any())
{
int count = _added.Count;
sb.Append("INSERT INTO \"TtsChatVoice\" (\"chatterId\", \"ttsVoiceId\", \"userId\") VALUES ");
lock (_lock)
{
foreach (var broadcaster in _added)
{
var userId = broadcaster.Key;
var user = _chatters[userId];
foreach (var chatterId in broadcaster.Value)
{
var voiceId = user[chatterId];
sb.Append("(")
.Append(chatterId)
.Append(",'")
.Append(voiceId)
.Append("','")
.Append(userId)
.Append("'),");
}
}
sb.Remove(sb.Length - 1, 1)
.Append(';');
sql = sb.ToString();
sb.Clear();
count = _added.Count;
sql = _generator.GenerateInsertSql("TtsChatVoice", _added.Select(a => _store[a]), ["userId", "chatterId", "ttsVoiceId"]);
_added.Clear();
}
try
{
_logger.Debug($"About to save {count} voices to database.");
await _database.ExecuteScalar(sql);
}
catch (Exception ex)
{
_logger.Error(ex, "Failed to save TTS voices on database: " + sql);
}
changes = true;
_logger.Debug($"ADD {count} " + sql);
await _database.ExecuteScalar(sql);
}
if (_modified.Any())
{
int count = _modified.Count;
sb.Append("UPDATE \"TtsChatVoice\" as t SET \"ttsVoiceId\" = c.\"ttsVoiceId\" FROM (VALUES ");
lock (_lock)
{
foreach (var broadcaster in _modified)
{
var userId = broadcaster.Key;
var user = _chatters[userId];
foreach (var chatterId in broadcaster.Value)
{
var voiceId = user[chatterId];
sb.Append("(")
.Append(chatterId)
.Append(",'")
.Append(voiceId)
.Append("','")
.Append(userId)
.Append("'),");
}
}
sb.Remove(sb.Length - 1, 1)
.Append(") AS c(\"chatterId\", \"ttsVoiceId\", \"userId\") WHERE \"userId\" = c.\"userId\" AND \"chatterId\" = c.\"chatterId\";");
sql = sb.ToString();
sb.Clear();
count = _modified.Count;
sql = _generator.GenerateUpdateSql("TtsChatVoice", _modified.Select(m => _store[m]), ["userId", "chatterId"], ["ttsVoiceId"]);
_modified.Clear();
}
try
{
_logger.Debug($"About to update {count} voices on the database.");
await _database.ExecuteScalar(sql);
}
catch (Exception ex)
{
_logger.Error(ex, "Failed to modify TTS voices on database: " + sql);
}
changes = true;
_logger.Debug($"MOD {count} " + sql);
await _database.ExecuteScalar(sql);
}
if (_deleted.Any())
{
int count = _deleted.Count;
sb.Append("DELETE FROM \"TtsChatVoice\" WHERE (\"chatterId\", \"userId\") IN (");
lock (_lock)
{
foreach (var broadcaster in _deleted)
{
var userId = broadcaster.Key;
var user = _chatters[userId];
foreach (var chatterId in broadcaster.Value)
{
sb.Append("(")
.Append(chatterId)
.Append(",'")
.Append(userId)
.Append("'),");
}
}
sb.Remove(sb.Length - 1, 1)
.Append(");");
sql = sb.ToString();
sb.Clear();
count = _deleted.Count;
sql = _generator.GenerateDeleteSql("TtsChatVoice", _deleted, ["userId", "chatterId"]);
_deleted.Clear();
}
try
{
_logger.Debug($"About to delete {count} voices from the database.");
await _database.ExecuteScalar(sql);
}
catch (Exception ex)
{
_logger.Error(ex, "Failed to modify TTS voices on database: " + sql);
}
changes = true;
_logger.Debug($"DEL {count} " + sql);
await _database.ExecuteScalar(sql);
}
return changes;
}
public bool Set(string? user, long key, string? value)
{
if (user == null || value == null)
return false;
lock (_lock)
{
if (!_chatters.TryGetValue(user, out var broadcaster))
{
broadcaster = new Dictionary<long, string>();
_chatters.Add(user, broadcaster);
}
if (broadcaster.TryGetValue(key, out var chatter))
{
if (chatter != value)
{
broadcaster[key] = value;
if (!_added.TryGetValue(user, out var added) || !added.Contains(key))
{
if (!_modified.TryGetValue(user, out var modified))
{
modified = new List<long>();
_modified.Add(user, modified);
modified.Add(key);
}
else if (!modified.Contains(key))
modified.Add(key);
}
}
}
else
{
broadcaster.Add(key, value);
_added.TryAdd(user, new List<long>());
if (!_deleted.TryGetValue(user, out var deleted) || !deleted.Remove(key))
{
if (!_added.TryGetValue(user, out var added))
{
added = new List<long>();
_added.Add(user, added);
added.Add(key);
}
else if (!added.Contains(key))
added.Add(key);
}
}
}
return true;
}
}

View File

@ -0,0 +1,138 @@
using System.Reflection;
using System.Text;
namespace HermesSocketServer.Store
{
public class GroupSaveSqlGenerator<T>
{
private readonly IDictionary<string, PropertyInfo?> columnPropertyRelations;
public GroupSaveSqlGenerator(IDictionary<string, string> columnsToProperties)
{
columnPropertyRelations = columnsToProperties.ToDictionary(p => p.Key, p => typeof(T).GetProperty(p.Value));
var nullProperties = columnPropertyRelations.Where(p => p.Value == null)
.Select(p => columnsToProperties[p.Key]);
if (nullProperties.Any())
throw new ArgumentException("Some properties do not exist on the values given: " + string.Join(", ", nullProperties));
}
public string GenerateInsertSql(string table, IEnumerable<T> values, IEnumerable<string> columns)
{
if (string.IsNullOrWhiteSpace(table))
throw new ArgumentException("Value is either null or whitespace-filled.", nameof(table));
if (values == null)
throw new ArgumentNullException(nameof(values));
if (!values.Any())
throw new ArgumentException("Empty list given.", nameof(values));
if (columns == null)
throw new ArgumentNullException(nameof(columns));
if (!columns.Any())
throw new ArgumentException("Empty list given.", nameof(columns));
var ctp = columns.ToDictionary(c => c, c => columnPropertyRelations[c]);
var sb = new StringBuilder();
sb.Append($"INSERT INTO \"{table}\" (\"{string.Join("\", \"", columns)}\") VALUES ");
foreach (var value in values)
{
sb.Append("(");
foreach (var column in columns)
{
var propValue = columnPropertyRelations[column]!.GetValue(value);
var propType = columnPropertyRelations[column]!.PropertyType;
WriteValue(sb, propValue, propType);
sb.Append(",");
}
sb.Remove(sb.Length - 1, 1)
.Append("),");
}
sb.Remove(sb.Length - 1, 1)
.Append(';');
return sb.ToString();
}
public string GenerateUpdateSql(string table, IEnumerable<T> values, IEnumerable<string> keyColumns, IEnumerable<string> updateColumns)
{
if (string.IsNullOrWhiteSpace(table))
throw new ArgumentException("Value is either null or whitespace-filled.", nameof(table));
if (values == null)
throw new ArgumentNullException(nameof(values));
if (!values.Any())
throw new ArgumentException("Empty list given.", nameof(values));
if (keyColumns == null)
throw new ArgumentNullException(nameof(keyColumns));
if (!keyColumns.Any())
throw new ArgumentException("Empty list given.", nameof(keyColumns));
if (updateColumns == null)
throw new ArgumentNullException(nameof(updateColumns));
if (!updateColumns.Any())
throw new ArgumentException("Empty list given.", nameof(updateColumns));
var columns = keyColumns.Union(updateColumns);
var ctp = columns.ToDictionary(c => c, c => columnPropertyRelations[c]);
var sb = new StringBuilder();
sb.Append($"UPDATE \"{table}\" as t SET {string.Join(", ", updateColumns.Select(c => "\"" + c + "\" = c.\"" + c + "\""))} FROM (VALUES ");
foreach (var value in values)
{
sb.Append("(");
foreach (var column in columns)
{
var propValue = columnPropertyRelations[column]!.GetValue(value);
var propType = columnPropertyRelations[column]!.PropertyType;
WriteValue(sb, propValue, propType);
sb.Append(",");
}
sb.Remove(sb.Length - 1, 1)
.Append("),");
}
sb.Remove(sb.Length - 1, 1)
.Append($") AS c(\"{string.Join("\", \"", columns)}\") WHERE id = c.id;");
return sb.ToString();
}
public string GenerateDeleteSql(string table, IEnumerable<string> keys, IEnumerable<string> keyColumns)
{
if (string.IsNullOrWhiteSpace(table))
throw new ArgumentException("Value is either null or whitespace-filled.", nameof(table));
if (keys == null)
throw new ArgumentNullException(nameof(keys));
if (!keys.Any())
throw new ArgumentException("Empty list given.", nameof(keys));
if (keyColumns == null)
throw new ArgumentNullException(nameof(keyColumns));
if (!keyColumns.Any())
throw new ArgumentException("Empty list given.", nameof(keyColumns));
var ctp = keyColumns.ToDictionary(c => c, c => columnPropertyRelations[c]);
var sb = new StringBuilder();
sb.Append($"DELETE FROM \"{table}\" WHERE (\"{string.Join("\", \"", keyColumns)}\") IN (");
foreach (var k in keys)
{
sb.Append("(");
foreach (var column in keyColumns)
{
var propType = columnPropertyRelations[column]!.PropertyType;
WriteValue(sb, k, propType);
sb.Append(",");
}
sb.Remove(sb.Length - 1, 1)
.Append("),");
}
sb.Remove(sb.Length - 1, 1)
.Append(");");
return sb.ToString();
}
private void WriteValue(StringBuilder sb, object? value, Type type)
{
if (type == typeof(string))
sb.Append("'")
.Append(value)
.Append("'");
else
sb.Append(value);
}
}
}

112
Store/GroupedSaveStore.cs Normal file
View File

@ -0,0 +1,112 @@
using System.Collections.Immutable;
namespace HermesSocketServer.Store
{
public abstract class GroupSaveStore<K, V> : IStore<K, V> where K : class where V : class
{
private readonly Serilog.ILogger _logger;
protected readonly IDictionary<K, V> _store;
protected readonly IList<K> _added;
protected readonly IList<K> _modified;
protected readonly IList<K> _deleted;
protected readonly object _lock;
public GroupSaveStore(Serilog.ILogger logger)
{
_logger = logger;
_store = new Dictionary<K, V>();
_added = new List<K>();
_modified = new List<K>();
_deleted = new List<K>();
_lock = new object();
}
public abstract Task Load();
public abstract void OnInitialAdd(K key, V value);
public abstract void OnInitialModify(K key, V value);
public abstract void OnInitialRemove(K key);
public abstract Task<bool> Save();
public V? Get(K key)
{
lock (_lock)
{
if (_store.TryGetValue(key, out var value))
return value;
}
return null;
}
public IDictionary<K, V> Get()
{
lock (_lock)
{
return _store.ToImmutableDictionary();
}
}
public void Remove(K? key)
{
if (key == null)
return;
lock (_lock)
{
if (_store.Remove(key))
{
_logger.Information($"removed key from _deleted {key}");
OnInitialRemove(key);
if (!_added.Remove(key))
{
_modified.Remove(key);
_logger.Information($"removed key from _added & _modified {key}");
if (!_deleted.Contains(key))
{
_deleted.Add(key);
_logger.Information($"added key to _deleted {key}");
}
}
}
}
}
public bool Set(K? key, V? value)
{
if (key == null || value == null)
return false;
lock (_lock)
{
if (_store.TryGetValue(key, out V? fetched))
{
if (fetched != value)
{
OnInitialModify(key, value);
_store[key] = value;
if (!_added.Contains(key) && !_modified.Contains(key))
{
_modified.Add(key);
_logger.Information($"added key to _modified {key}");
}
return true;
}
}
else
{
OnInitialAdd(key, value);
_store.Add(key, value);
if (!_deleted.Remove(key) && !_added.Contains(key))
{
_added.Add(key);
_logger.Information($"added key to _added {key}");
}
return true;
}
}
return false;
}
}
}

View File

@ -9,14 +9,4 @@ namespace HermesSocketServer.Store
Task<bool> Save();
bool Set(K? key, V? value);
}
public interface IStore<L, R, V>
{
V? Get(L leftKey, R rightKey);
IDictionary<R, V> Get(L leftKey);
Task Load();
void Remove(L? leftKey, R? rightKey);
Task<bool> Save();
bool Set(L? leftKey, R? rightKey, V? value);
}
}

94
Store/UserStore.cs Normal file
View File

@ -0,0 +1,94 @@
using HermesSocketLibrary.db;
using HermesSocketServer.Models;
namespace HermesSocketServer.Store
{
public class UserStore : GroupSaveStore<string, User>
{
private readonly Database _database;
private readonly Serilog.ILogger _logger;
private readonly GroupSaveSqlGenerator<User> _generator;
public UserStore(Database database, Serilog.ILogger logger) : base(logger)
{
_database = database;
_logger = logger;
var ctp = new Dictionary<string, string>
{
{ "id", "Id" },
{ "name", "Name" },
{ "email", "Email" },
{ "role", "Role" },
{ "ttsDefaultVoice", "DefaultVoice" }
};
_generator = new GroupSaveSqlGenerator<User>(ctp);
}
public override async Task Load()
{
string sql = "SELECT id, name, email, role, \"ttsDefaultVoice\" FROM \"User\";";
await _database.Execute(sql, new Dictionary<string, object>(), (reader) =>
{
string id = reader.GetString(0);
_store.Add(id, new User()
{
Id = id,
Name = reader.GetString(1),
Email = reader.GetString(2),
Role = reader.GetString(3),
DefaultVoice = reader.GetString(4),
});
});
_logger.Information($"Loaded {_store.Count} users from database.");
}
public override void OnInitialAdd(string key, User value)
{
}
public override void OnInitialModify(string key, User value)
{
}
public override void OnInitialRemove(string key)
{
}
public override async Task<bool> Save()
{
if (_added.Any())
{
string sql = string.Empty;
lock (_lock)
{
sql = _generator.GenerateInsertSql("User", _added.Select(a => _store[a]), ["id", "name", "email", "role", "ttsDefaultVoice"]);
_added.Clear();
}
await _database.ExecuteScalar(sql);
}
if (_modified.Any())
{
string sql = string.Empty;
lock (_lock)
{
sql = _generator.GenerateUpdateSql("User", _modified.Select(m => _store[m]), ["id"], ["name", "email", "role", "ttsDefaultVoice"]);
_modified.Clear();
}
await _database.ExecuteScalar(sql);
}
if (_deleted.Any())
{
string sql = string.Empty;
lock (_lock)
{
sql = _generator.GenerateDeleteSql("User", _deleted, ["id"]);
_deleted.Clear();
}
await _database.ExecuteScalar(sql);
}
return true;
}
}
}

View File

@ -1,223 +1,102 @@
using System.Collections.Immutable;
using System.Text;
using HermesSocketLibrary.db;
using HermesSocketServer.Models;
using HermesSocketServer.Validators;
namespace HermesSocketServer.Store
{
public class VoiceStore : IStore<string, string>
public class VoiceStore : GroupSaveStore<string, Voice>
{
private readonly VoiceIdValidator _idValidator;
private readonly VoiceNameValidator _nameValidator;
private readonly Database _database;
private readonly IValidator _voiceIdValidator;
private readonly IValidator _voiceNameValidator;
private readonly Serilog.ILogger _logger;
private readonly IDictionary<string, string> _voices;
private readonly IList<string> _added;
private readonly IList<string> _modified;
private readonly IList<string> _deleted;
private readonly object _lock;
public DateTime PreviousSave;
private readonly GroupSaveSqlGenerator<Voice> _generator;
public VoiceStore(Database database, VoiceIdValidator voiceIdValidator, VoiceNameValidator voiceNameValidator, Serilog.ILogger logger)
public VoiceStore(VoiceIdValidator voiceIdValidator, VoiceNameValidator voiceNameValidator, Database database, Serilog.ILogger logger) : base(logger)
{
_idValidator = voiceIdValidator;
_nameValidator = voiceNameValidator;
_database = database;
_voiceIdValidator = voiceIdValidator;
_voiceNameValidator = voiceNameValidator;
_logger = logger;
_voices = new Dictionary<string, string>();
_added = new List<string>();
_modified = new List<string>();
_deleted = new List<string>();
_lock = new object();
PreviousSave = DateTime.UtcNow;
var ctp = new Dictionary<string, string>
{
{ "id", "Id" },
{ "name", "Name" }
};
_generator = new GroupSaveSqlGenerator<Voice>(ctp);
}
public string? Get(string key)
{
if (_voices.TryGetValue(key, out var voice))
return voice;
return null;
}
public IDictionary<string, string> Get()
{
return _voices.ToImmutableDictionary();
}
public async Task Load()
public override async Task Load()
{
string sql = "SELECT id, name FROM \"TtsVoice\";";
await _database.Execute(sql, new Dictionary<string, object>(), (reader) =>
{
var id = reader.GetString(0);
var name = reader.GetString(1);
_voices.Add(id, name);
});
_logger.Information($"Loaded {_voices.Count} TTS voices from database.");
}
public void Remove(string? key)
{
if (key == null)
return;
lock (_lock)
{
if (_voices.ContainsKey(key))
string id = reader.GetString(0);
_store.Add(id, new Voice()
{
_voices.Remove(key);
if (!_added.Remove(key))
{
_modified.Remove(key);
if (!_deleted.Contains(key))
_deleted.Add(key);
}
}
}
Id = id,
Name = reader.GetString(1),
});
});
_logger.Information($"Loaded {_store.Count} TTS voices from database.");
}
public async Task<bool> Save()
public override void OnInitialAdd(string key, Voice value)
{
var changes = false;
var sb = new StringBuilder();
var sql = "";
_idValidator.Check(value.Id);
_nameValidator.Check(value.Name);
}
public override void OnInitialModify(string key, Voice value)
{
_nameValidator.Check(value.Name);
}
public override void OnInitialRemove(string key)
{
}
public override async Task<bool> Save()
{
int count = 0;
string sql = string.Empty;
if (_added.Any())
{
int count = _added.Count;
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();
count = _added.Count;
sql = _generator.GenerateInsertSql("TtsVoice", _added.Select(a => _store[a]), ["id", "name"]);
_added.Clear();
}
try
{
_logger.Debug($"About to save {count} voices to database.");
await _database.ExecuteScalar(sql);
}
catch (Exception ex)
{
_logger.Error(ex, "Failed to save TTS voices on database: " + sql);
}
changes = true;
_logger.Debug($"ADD {count} " + sql);
await _database.ExecuteScalar(sql);
}
if (_modified.Any())
{
int count = _modified.Count;
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();
count = _modified.Count;
sql = _generator.GenerateUpdateSql("TtsVoice", _modified.Select(m => _store[m]), ["id"], ["name"]);
_modified.Clear();
}
try
{
_logger.Debug($"About to update {count} voices on the database.");
await _database.ExecuteScalar(sql);
}
catch (Exception ex)
{
_logger.Error(ex, "Failed to modify TTS voices on database: " + sql);
}
changes = true;
_logger.Debug($"MOD {count} " + sql);
await _database.ExecuteScalar(sql);
}
if (_deleted.Any())
{
int count = _deleted.Count;
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();
count = _deleted.Count;
sql = _generator.GenerateDeleteSql("TtsVoice", _deleted, ["id"]);
_deleted.Clear();
}
try
{
_logger.Debug($"About to delete {count} voices from the database.");
await _database.ExecuteScalar(sql);
}
catch (Exception ex)
{
_logger.Error(ex, "Failed to modify TTS voices on database: " + sql);
}
changes = true;
_logger.Debug($"DEL {count} " + sql);
await _database.ExecuteScalar(sql);
}
return changes;
}
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) && !_added.Contains(key))
_added.Add(key);
}
}
return true;
}
}