Added database table data into configuration. Store saves is auto-handled. Added Action & Redemption stores.

This commit is contained in:
Tom
2024-12-31 18:31:21 +00:00
parent 538bf07454
commit 3429c8f8dc
29 changed files with 657 additions and 374 deletions

View File

@ -1,8 +0,0 @@
namespace HermesSocketServer.Models
{
public class Voice
{
public required string Id { get; set; }
public required string Name { get; set; }
}
}

View File

@ -0,0 +1,53 @@
using System.Text.Json;
using HermesSocketLibrary.Requests.Messages;
using HermesSocketServer.Models;
using ILogger = Serilog.ILogger;
namespace HermesSocketServer.Requests
{
public class CreateRedeemableAction : IRequest
{
public string Name => "create_redeemable_action";
public string[] RequiredKeys => ["name", "data", "type"];
private ILogger _logger;
public CreateRedeemableAction(ILogger logger)
{
_logger = logger;
}
public Task<RequestResult> Grant(Channel channel, IDictionary<string, object> data)
{
string name = data["name"].ToString()!;
string d = data["data"].ToString()!;
string type = data["type"].ToString()!;
IDictionary<string, string> dict = new Dictionary<string, string>();
try
{
dict = JsonSerializer.Deserialize<IDictionary<string, string>>(d)!;
}
catch (Exception ex)
{
_logger.Error(ex, $"Failed to parse data on redeemable action while creating action [name: {name}][type: {type}][data: {d}]");
return Task.FromResult(RequestResult.Failed("Could not parse the data on this action."));
}
var action = new RedeemableAction()
{
UserId = channel.Id,
Name = name,
Data = dict,
Type = type,
};
bool result = channel.Actions.Set(name, action);
if (result)
{
_logger.Information($"Added redeemable action to channel [name: {name}][type: {type}][channel: {channel.Id}]");
return Task.FromResult(RequestResult.Successful(action));
}
return Task.FromResult(RequestResult.Failed("Something went wrong when updating the cache."));
}
}
}

View File

@ -0,0 +1,47 @@
using HermesSocketLibrary.Requests.Messages;
using HermesSocketServer.Models;
using ILogger = Serilog.ILogger;
namespace HermesSocketServer.Requests
{
public class CreateRedemption : IRequest
{
public string Name => "create_redemption";
public string[] RequiredKeys => ["redemption", "action", "order"];
private ILogger _logger;
public CreateRedemption(ILogger logger)
{
_logger = logger;
}
public Task<RequestResult> Grant(Channel channel, IDictionary<string, object> data)
{
var id = Guid.NewGuid();
string redemptionId = data["redemption"].ToString()!;
string actionName = data["action"].ToString()!;
if (channel.Actions.Get(actionName) == null)
return Task.FromResult(RequestResult.Failed("Action Name must be an existing action."));
if (!int.TryParse(data["order"].ToString()!, out var order))
return Task.FromResult(RequestResult.Failed("Order must be an integer."));
var redemption = new Redemption()
{
Id = id.ToString(),
UserId = channel.Id,
RedemptionId = redemptionId,
ActionName = actionName,
Order = order,
State = true,
};
bool result = channel.Redemptions.Set(id.ToString(), redemption);
if (result)
{
_logger.Information($"Added redemption to channel [id: {id}][redemption id: {redemptionId}][action: {actionName}][order: {order}][channel: {channel.Id}]");
return Task.FromResult(RequestResult.Successful(redemption));
}
return Task.FromResult(RequestResult.Failed("Something went wrong when updating the cache."));
}
}
}

View File

@ -1,3 +1,4 @@
using HermesSocketLibrary.Requests.Messages;
using HermesSocketServer.Models; using HermesSocketServer.Models;
using HermesSocketServer.Store; using HermesSocketServer.Store;
using ILogger = Serilog.ILogger; using ILogger = Serilog.ILogger;
@ -8,7 +9,7 @@ namespace HermesSocketServer.Requests
{ {
public string Name => "create_tts_voice"; public string Name => "create_tts_voice";
public string[] RequiredKeys => ["voice"]; public string[] RequiredKeys => ["voice"];
private IStore<string, Voice> _voices; private IStore<string, TTSVoice> _voices;
private ILogger _logger; private ILogger _logger;
private Random _random; private Random _random;
@ -24,7 +25,7 @@ namespace HermesSocketServer.Requests
string voice = data["voice"].ToString()!; string voice = data["voice"].ToString()!;
string id = RandomString(25); string id = RandomString(25);
var result = _voices.Set(id, new Voice() var result = _voices.Set(id, new TTSVoice()
{ {
Id = id, Id = id,
Name = voice Name = voice

View File

@ -25,7 +25,7 @@ namespace HermesSocketServer.Requests
} }
_logger.Warning($"Failed to find policy by id [id: {policyId}]"); _logger.Warning($"Failed to find policy by id [id: {policyId}]");
return Task.FromResult(RequestResult.Failed("Cannot find the policy by id.")); return Task.FromResult(RequestResult.Failed("Policy ID does not exist."));
} }
} }
} }

View File

@ -0,0 +1,32 @@
using HermesSocketServer.Models;
using ILogger = Serilog.ILogger;
namespace HermesSocketServer.Requests
{
public class DeleteRedeemableAction : IRequest
{
public string Name => "delete_redeemable_action";
public string[] RequiredKeys => ["name"];
private ILogger _logger;
public DeleteRedeemableAction(ILogger logger)
{
_logger = logger;
}
public Task<RequestResult> Grant(Channel channel, IDictionary<string, object> data)
{
string name = data["name"].ToString()!;
var result = channel.Actions.Remove(name);
if (result)
{
_logger.Information($"Deleted a redeemable action by name [name: {name}]");
return Task.FromResult(RequestResult.Successful(null));
}
_logger.Warning($"Action name does not exist [id: {name}]");
return Task.FromResult(RequestResult.Failed("Action name does not exist."));
}
}
}

View File

@ -0,0 +1,32 @@
using HermesSocketServer.Models;
using ILogger = Serilog.ILogger;
namespace HermesSocketServer.Requests
{
public class DeleteRedemption : IRequest
{
public string Name => "delete_redemption";
public string[] RequiredKeys => ["id"];
private ILogger _logger;
public DeleteRedemption(ILogger logger)
{
_logger = logger;
}
public Task<RequestResult> Grant(Channel channel, IDictionary<string, object> data)
{
string id = data["id"].ToString()!;
var result = channel.Redemptions.Remove(id);
if (result)
{
_logger.Information($"Deleted a redemption by id [id: {id}]");
return Task.FromResult(RequestResult.Successful(null));
}
_logger.Warning($"Redemption ID does not exist [id: {id}]");
return Task.FromResult(RequestResult.Failed("Redemption ID does not exist."));
}
}
}

View File

@ -17,10 +17,16 @@ namespace HermesSocketServer.Requests
public Task<RequestResult> Grant(Channel channel, IDictionary<string, object> data) public Task<RequestResult> Grant(Channel channel, IDictionary<string, object> data)
{ {
string filterId = data["id"].ToString()!; string filterId = data["id"].ToString()!;
channel.Filters.Remove(filterId); var result = channel.Filters.Remove(filterId);
_logger.Information($"Deleted a TTS filter by id [tts filter id: {filterId}]"); if (result)
return Task.FromResult(RequestResult.Successful(null)); {
_logger.Information($"Deleted a TTS filter by id [tts filter id: {filterId}]");
return Task.FromResult(RequestResult.Successful(null));
}
_logger.Warning($"Filter ID does not exist [id: {filterId}]");
return Task.FromResult(RequestResult.Failed("Filter ID does not exist."));
} }
} }
} }

View File

@ -1,3 +1,4 @@
using HermesSocketLibrary.Requests.Messages;
using HermesSocketServer.Models; using HermesSocketServer.Models;
using HermesSocketServer.Store; using HermesSocketServer.Store;
using ILogger = Serilog.ILogger; using ILogger = Serilog.ILogger;
@ -8,7 +9,7 @@ namespace HermesSocketServer.Requests
{ {
public string Name => "delete_tts_voice"; public string Name => "delete_tts_voice";
public string[] RequiredKeys => ["voice"]; public string[] RequiredKeys => ["voice"];
private IStore<string, Voice> _voices; private IStore<string, TTSVoice> _voices;
private ILogger _logger; private ILogger _logger;
public DeleteTTSVoice(VoiceStore voices, ILogger logger) public DeleteTTSVoice(VoiceStore voices, ILogger logger)
@ -20,9 +21,16 @@ namespace HermesSocketServer.Requests
public Task<RequestResult> Grant(Channel channel, IDictionary<string, object> data) public Task<RequestResult> Grant(Channel channel, IDictionary<string, object> data)
{ {
string voiceId = data["voice"].ToString()!; string voiceId = data["voice"].ToString()!;
_voices.Remove(voiceId); var result = _voices.Remove(voiceId);
_logger.Information($"Deleted a voice by id [voice id: {voiceId}]");
return Task.FromResult(RequestResult.Successful(null)); if (result)
{
_logger.Information($"Deleted a voice by id [voice id: {voiceId}]");
return Task.FromResult(RequestResult.Successful(null));
}
_logger.Warning($"Voice ID does not exist [id: {voiceId}]");
return Task.FromResult(RequestResult.Failed("Voice ID does not exist."));
} }
} }
} }

View File

@ -20,10 +20,10 @@ namespace HermesSocketServer.Requests
public Task<RequestResult> Grant(Channel channel, IDictionary<string, object> data) public Task<RequestResult> Grant(Channel channel, IDictionary<string, object> data)
{ {
IEnumerable<VoiceDetails> voices = _voices.Get().Select(v => new VoiceDetails() IEnumerable<TTSVoice> voices = _voices.Get().Select(v => new TTSVoice()
{ {
Id = v.Value.Id, Id = v.Value.Id,
Name = v.Value.Name Name = v.Value.Name,
}); });
_logger.Information($"Fetched all TTS voices for channel [channel: {channel.Id}]"); _logger.Information($"Fetched all TTS voices for channel [channel: {channel.Id}]");

View File

@ -0,0 +1,59 @@
using System.Text.Json;
using HermesSocketLibrary.Requests.Messages;
using HermesSocketServer.Models;
using ILogger = Serilog.ILogger;
namespace HermesSocketServer.Requests
{
public class UpdateRedeemableAction : IRequest
{
public string Name => "update_redeemable_action";
public string[] RequiredKeys => ["name", "data", "type"];
private ILogger _logger;
public UpdateRedeemableAction(ILogger logger)
{
_logger = logger;
}
public Task<RequestResult> Grant(Channel channel, IDictionary<string, object> data)
{
string name = data["name"].ToString()!;
string d = data["data"].ToString()!;
string type = data["type"].ToString()!;
IDictionary<string, string> dict = new Dictionary<string, string>();
try
{
dict = JsonSerializer.Deserialize<IDictionary<string, string>>(d)!;
}
catch (Exception ex)
{
_logger.Error(ex, $"Failed to parse data on redeemable action while updating action [name: {name}][type: {type}][data: {d}]");
return Task.FromResult(RequestResult.Failed("Could not parse the data on this action."));
}
var action = new RedeemableAction()
{
UserId = channel.Id,
Name = name,
Data = dict,
Type = type,
};
bool result = channel.Actions.Modify(name, action =>
{
action.Type = type;
action.Data = dict;
});
if (result)
{
_logger.Information($"Added redeemable action to channel [name: {name}][type: {type}][channel: {channel.Id}]");
return Task.FromResult(RequestResult.Successful(action));
}
if (channel.Actions.Get(name) == null)
return Task.FromResult(RequestResult.Failed("Action does not exist."));
return Task.FromResult(RequestResult.Failed("Something went wrong when updating the cache."));
}
}
}

View File

@ -0,0 +1,62 @@
using HermesSocketLibrary.Requests.Messages;
using HermesSocketServer.Models;
using ILogger = Serilog.ILogger;
namespace HermesSocketServer.Requests
{
public class UpdateRedemption : IRequest
{
public string Name => "update_redemption";
public string[] RequiredKeys => ["id", "redemption", "action", "order"];
private ILogger _logger;
public UpdateRedemption(ILogger logger)
{
_logger = logger;
}
public Task<RequestResult> Grant(Channel channel, IDictionary<string, object> data)
{
var id = data["id"].ToString()!;
string redemptionId = data["redemption"].ToString()!;
string actionName = data["action"].ToString()!;
if (channel.Actions.Get(actionName) == null)
return Task.FromResult(RequestResult.Failed("Action Name must be an existing action."));
if (!int.TryParse(data["order"].ToString()!, out var order))
return Task.FromResult(RequestResult.Failed("Order must be an integer."));
bool state = data["state"].ToString()?.ToLower() == "true";
var redemption = new Redemption()
{
Id = id,
UserId = channel.Id,
RedemptionId = redemptionId,
ActionName = actionName,
Order = order,
State = true,
};
bool result = channel.Redemptions.Modify(id.ToString(), r =>
{
if (r.UserId != channel.Id)
return;
r.RedemptionId = redemptionId;
r.ActionName = actionName;
r.Order = order;
r.State = state;
});
var r = channel.Redemptions.Get(id);
if (result)
{
_logger.Information($"Added redemption to channel [id: {id}][redemption id: {redemptionId}][action: {actionName}][order: {order}][channel: {channel.Id}]");
return Task.FromResult(RequestResult.Successful(r));
}
if (r == null || r.UserId != channel.Id)
return Task.FromResult(RequestResult.Failed("Redemption does not exist."));
return Task.FromResult(RequestResult.Failed("Something went wrong when updating the cache."));
}
}
}

View File

@ -1,3 +1,4 @@
using HermesSocketLibrary.Requests.Messages;
using HermesSocketServer.Models; using HermesSocketServer.Models;
using HermesSocketServer.Store; using HermesSocketServer.Store;
using ILogger = Serilog.ILogger; using ILogger = Serilog.ILogger;
@ -8,7 +9,7 @@ namespace HermesSocketServer.Requests
{ {
public string Name => "update_tts_voice"; public string Name => "update_tts_voice";
public string[] RequiredKeys => ["voice", "voiceId"]; public string[] RequiredKeys => ["voice", "voiceId"];
private IStore<string, Voice> _voices; private IStore<string, TTSVoice> _voices;
private ILogger _logger; private ILogger _logger;
public UpdateTTSVoice(VoiceStore voices, ILogger logger) public UpdateTTSVoice(VoiceStore voices, ILogger logger)
@ -22,7 +23,7 @@ namespace HermesSocketServer.Requests
string voiceName = data["voice"].ToString()!; string voiceName = data["voice"].ToString()!;
string voiceId = data["voiceid"].ToString()!; string voiceId = data["voiceid"].ToString()!;
var result = _voices.Set(voiceId, new Voice() var result = _voices.Set(voiceId, new TTSVoice()
{ {
Id = voiceId, Id = voiceId,
Name = voiceName Name = voiceName

View File

@ -1,3 +1,5 @@
using HermesSocketServer.Store.Internal;
namespace HermesSocketServer namespace HermesSocketServer
{ {
public class ServerConfiguration public class ServerConfiguration
@ -19,6 +21,7 @@ namespace HermesSocketServer
{ {
public required string ConnectionString; public required string ConnectionString;
public int SaveDelayInSeconds; public int SaveDelayInSeconds;
public required IDictionary<string, DatabaseTable> Tables;
} }
public class TTSConfiguration public class TTSConfiguration

View File

@ -9,14 +9,16 @@ namespace HermesSocketServer.Services
{ {
private readonly UserStore _users; private readonly UserStore _users;
private readonly Database _database; private readonly Database _database;
private readonly ServerConfiguration _configuration;
private readonly Serilog.ILogger _logger; private readonly Serilog.ILogger _logger;
private readonly IDictionary<string, Channel> _channels; private readonly IDictionary<string, Channel> _channels;
private readonly object _lock; private readonly object _lock;
public ChannelManager(UserStore users, Database database, Serilog.ILogger logger) public ChannelManager(UserStore users, Database database, ServerConfiguration configuration, Serilog.ILogger logger)
{ {
_users = users; _users = users;
_database = database; _database = database;
_configuration = configuration;
_logger = logger; _logger = logger;
_channels = new ConcurrentDictionary<string, Channel>(); _channels = new ConcurrentDictionary<string, Channel>();
_lock = new object(); _lock = new object();
@ -27,21 +29,24 @@ namespace HermesSocketServer.Services
{ {
var user = _users.Get(userId); var user = _users.Get(userId);
if (user == null) if (user == null)
{
return Task.FromResult<Channel?>(null); return Task.FromResult<Channel?>(null);
}
lock (_lock) lock (_lock)
{ {
if (_channels.ContainsKey(userId)) if (_channels.ContainsKey(userId))
{
return Task.FromResult<Channel?>(null); return Task.FromResult<Channel?>(null);
}
var chatters = new ChatterStore(userId, _database, _logger); var actionTable = _configuration.Database.Tables["Action"];
var policies = new PolicyStore(userId, _database, _logger); var chatterTable = _configuration.Database.Tables["Chatter"];
var filters = new TTSFilterStore(userId, _database, _logger); var policyTable = _configuration.Database.Tables["Policy"];
var actions = new ActionStore(userId, _database, _logger); var redemptionTable = _configuration.Database.Tables["Redemption"];
var redemptions = new RedemptionStore(userId, _database, _logger); var ttsFilterTable = _configuration.Database.Tables["TtsFilter"];
var chatters = new ChatterStore(userId, chatterTable, _database, _logger);
var policies = new PolicyStore(userId, policyTable, _database, _logger);
var filters = new TTSFilterStore(userId, ttsFilterTable, _database, _logger);
var actions = new ActionStore(userId, actionTable, _database, _logger);
var redemptions = new RedemptionStore(userId, redemptionTable, _database, _logger);
Task.WaitAll([ Task.WaitAll([
chatters.Load(), chatters.Load(),

View File

@ -15,6 +15,8 @@ using Microsoft.AspNetCore.Connections;
using HermesSocketServer.Validators; using HermesSocketServer.Validators;
using HermesSocketServer.Store; using HermesSocketServer.Store;
using HermesSocketServer.Services; using HermesSocketServer.Services;
using HermesSocketServer.Store.Internal;
using Microsoft.Extensions.DependencyInjection;
var yamlDeserializer = new DeserializerBuilder() var yamlDeserializer = new DeserializerBuilder()
@ -78,15 +80,26 @@ s.AddSingleton<ISocketHandler, EmoteUsageHandler>();
s.AddSingleton<VoiceIdValidator>(); s.AddSingleton<VoiceIdValidator>();
s.AddSingleton<VoiceNameValidator>(); s.AddSingleton<VoiceNameValidator>();
// Database Tables
if (configuration.Database.Tables != null)
{
foreach (var table in configuration.Database.Tables)
s.AddKeyedTransient<DatabaseTable>(table.Key, (sp, _) => table.Value);
}
// Stores // Stores
s.AddSingleton<VoiceStore>(); s.AddSingleton<VoiceStore>();
s.AddSingleton<UserStore>(); s.AddSingleton<UserStore>();
// Request handlers // Request handlers
s.AddSingleton<IRequest, CreatePolicy>(); s.AddSingleton<IRequest, CreatePolicy>();
s.AddSingleton<IRequest, CreateRedeemableAction>();
s.AddSingleton<IRequest, CreateRedemption>();
s.AddSingleton<IRequest, CreateTTSFilter>(); s.AddSingleton<IRequest, CreateTTSFilter>();
s.AddSingleton<IRequest, CreateTTSUser>(); s.AddSingleton<IRequest, CreateTTSUser>();
s.AddSingleton<IRequest, CreateTTSVoice>(); s.AddSingleton<IRequest, CreateTTSVoice>();
s.AddSingleton<IRequest, DeleteRedeemableAction>();
s.AddSingleton<IRequest, DeleteRedemption>();
s.AddSingleton<IRequest, DeletePolicy>(); s.AddSingleton<IRequest, DeletePolicy>();
s.AddSingleton<IRequest, DeleteTTSFilter>(); s.AddSingleton<IRequest, DeleteTTSFilter>();
s.AddSingleton<IRequest, DeleteTTSVoice>(); s.AddSingleton<IRequest, DeleteTTSVoice>();
@ -96,18 +109,20 @@ s.AddSingleton<IRequest, GetDefaultTTSVoice>();
s.AddSingleton<IRequest, GetEmotes>(); s.AddSingleton<IRequest, GetEmotes>();
s.AddSingleton<IRequest, GetEnabledTTSVoices>(); s.AddSingleton<IRequest, GetEnabledTTSVoices>();
s.AddSingleton<IRequest, GetPermissions>(); s.AddSingleton<IRequest, GetPermissions>();
s.AddSingleton<IRequest, GetRedemptions>();
s.AddSingleton<IRequest, GetRedeemableActions>(); s.AddSingleton<IRequest, GetRedeemableActions>();
s.AddSingleton<IRequest, GetRedemptions>();
s.AddSingleton<IRequest, GetPolicies>(); s.AddSingleton<IRequest, GetPolicies>();
s.AddSingleton<IRequest, GetTTSUsers>(); s.AddSingleton<IRequest, GetTTSUsers>();
s.AddSingleton<IRequest, GetTTSVoices>(); s.AddSingleton<IRequest, GetTTSVoices>();
s.AddSingleton<IRequest, GetTTSWordFilters>(); s.AddSingleton<IRequest, GetTTSWordFilters>();
s.AddSingleton<IRequest, UpdateDefaultTTSVoice>();
s.AddSingleton<IRequest, UpdatePolicy>();
s.AddSingleton<IRequest, UpdateRedeemableAction>();
s.AddSingleton<IRequest, UpdateRedemption>();
s.AddSingleton<IRequest, UpdateTTSFilter>(); s.AddSingleton<IRequest, UpdateTTSFilter>();
s.AddSingleton<IRequest, UpdateTTSUser>(); s.AddSingleton<IRequest, UpdateTTSUser>();
s.AddSingleton<IRequest, UpdateTTSVoice>(); s.AddSingleton<IRequest, UpdateTTSVoice>();
s.AddSingleton<IRequest, UpdateDefaultTTSVoice>();
s.AddSingleton<IRequest, UpdateTTSVoiceState>(); s.AddSingleton<IRequest, UpdateTTSVoiceState>();
s.AddSingleton<IRequest, UpdatePolicy>();
// Managers // Managers
s.AddSingleton<ChannelManager>(); s.AddSingleton<ChannelManager>();

53
Store/ActionStore.cs Normal file
View File

@ -0,0 +1,53 @@
using System.Text.Json;
using HermesSocketLibrary.db;
using HermesSocketLibrary.Requests.Messages;
using HermesSocketServer.Store.Internal;
namespace HermesSocketServer.Store
{
public class ActionStore : AutoSavedStore<string, RedeemableAction>
{
private readonly string _userId;
private readonly Database _database;
private readonly Serilog.ILogger _logger;
public ActionStore(string userId, DatabaseTable table, Database database, Serilog.ILogger logger)
: base(table, database, logger)
{
_userId = userId;
_database = database;
_logger = logger;
}
public override async Task Load()
{
var data = new Dictionary<string, object>() { { "user", _userId } };
string sql = $"SELECT name, type, data FROM \"Action\" WHERE \"userId\" = @user";
await _database.Execute(sql, data, (reader) =>
{
var name = reader.GetString(0);
_store.Add(name.ToString(), new RedeemableAction()
{
UserId = _userId,
Name = name,
Type = reader.GetString(1),
Data = JsonSerializer.Deserialize<IDictionary<string, string>>(reader.GetString(2))!
});
});
_logger.Information($"Loaded {_store.Count} TTS chatter voices from database.");
}
protected override void OnInitialAdd(string key, RedeemableAction value)
{
}
protected override void OnInitialModify(string key, RedeemableAction value)
{
}
protected override void OnInitialRemove(string key)
{
}
}
}

View File

@ -1,30 +1,22 @@
using System.Collections.Immutable;
using HermesSocketLibrary.db; using HermesSocketLibrary.db;
using HermesSocketServer.Models; using HermesSocketServer.Models;
using HermesSocketServer.Store.Internal;
namespace HermesSocketServer.Store namespace HermesSocketServer.Store
{ {
public class ChatterStore : GroupSaveStore<string, ChatterVoice> public class ChatterStore : AutoSavedStore<string, ChatterVoice>
{ {
private readonly string _userId; private readonly string _userId;
private readonly Database _database; private readonly Database _database;
private readonly Serilog.ILogger _logger; private readonly Serilog.ILogger _logger;
private readonly GroupSaveSqlGenerator<ChatterVoice> _generator;
public ChatterStore(string userId, Database database, Serilog.ILogger logger) : base(logger) public ChatterStore(string userId, DatabaseTable table, Database database, Serilog.ILogger logger)
: base(table, database, logger)
{ {
_userId = userId; _userId = userId;
_database = database; _database = database;
_logger = logger; _logger = logger;
var ctp = new Dictionary<string, string>
{
{ "chatterId", "ChatterId" },
{ "ttsVoiceId", "VoiceId" },
{ "userId", "UserId" },
};
_generator = new GroupSaveSqlGenerator<ChatterVoice>(ctp, _logger);
} }
public override async Task Load() public override async Task Load()
@ -55,54 +47,5 @@ namespace HermesSocketServer.Store
protected override void OnInitialRemove(string key) protected override void OnInitialRemove(string key)
{ {
} }
public override async Task Save()
{
int count = 0;
string sql = string.Empty;
ImmutableList<string>? list = null;
if (_added.Any())
{
lock (_lock)
{
list = _added.ToImmutableList();
_added.Clear();
}
count = list.Count;
sql = _generator.GeneratePreparedInsertSql("TtsChatVoice", count, ["userId", "chatterId", "ttsVoiceId"]);
_logger.Debug($"Chatter - Adding {count} rows to database: {sql}");
var values = list.Select(id => _store[id]).Where(v => v != null);
await _generator.DoPreparedStatement(_database, sql, values, ["userId", "chatterId", "ttsVoiceId"]);
}
if (_modified.Any())
{
lock (_lock)
{
list = _modified.ToImmutableList();
_modified.Clear();
}
count = list.Count;
sql = _generator.GeneratePreparedUpdateSql("TtsChatVoice", count, ["userId", "chatterId"], ["ttsVoiceId"]);
_logger.Debug($"Chatter - Modifying {count} rows in database: {sql}");
var values = list.Select(id => _store[id]).Where(v => v != null);
await _generator.DoPreparedStatement(_database, sql, values, ["userId", "chatterId", "ttsVoiceId"]);
}
if (_deleted.Any())
{
lock (_lock)
{
list = _deleted.ToImmutableList();
_deleted.Clear();
}
count = list.Count;
sql = _generator.GeneratePreparedDeleteSql("TtsChatVoice", count, ["userId", "chatterId"]);
_logger.Debug($"Chatter - Deleting {count} rows from database: {sql}");
await _generator.DoPreparedStatementRaw(_database, sql, list, ["userId", "chatterId"]);
}
}
} }
} }

View File

@ -6,7 +6,7 @@ namespace HermesSocketServer.Store
IDictionary<K, V> Get(); IDictionary<K, V> Get();
Task Load(); Task Load();
bool Modify(K? key, Action<V> action); bool Modify(K? key, Action<V> action);
void Remove(K? key); bool Remove(K? key);
Task Save(); Task Save();
bool Set(K? key, V? value); bool Set(K? key, V? value);
} }

View File

@ -0,0 +1,11 @@
namespace HermesSocketServer.Store.Internal
{
public class DatabaseTable
{
public required string TableName { get; set; }
public required string[] KeyColumns { get; set; }
public required string[] DataColumns { get; set; }
public required IDictionary<string, string> PropertyMapping { get; set; }
public IDictionary<string, string>? TypeMapping { get; set; }
}
}

View File

@ -1,20 +1,29 @@
using System.Reflection; using System.Reflection;
using System.Text; using System.Text;
using System.Text.Json;
using HermesSocketLibrary.db; using HermesSocketLibrary.db;
using NpgsqlTypes;
namespace HermesSocketServer.Store namespace HermesSocketServer.Store.Internal
{ {
public class GroupSaveSqlGenerator<T> public class GroupSaveSqlGenerator<T>
{ {
private readonly IDictionary<string, PropertyInfo?> columnPropertyRelations; private readonly IDictionary<string, PropertyInfo?> _columnPropertyRelations;
private readonly IDictionary<string, NpgsqlDbType> _columnTypes;
private readonly Serilog.ILogger _logger; private readonly Serilog.ILogger _logger;
public GroupSaveSqlGenerator(IDictionary<string, string> columnsToProperties, Serilog.ILogger logger) public GroupSaveSqlGenerator(IDictionary<string, string> columnsToProperties, Serilog.ILogger logger)
: this(columnsToProperties, new Dictionary<string, NpgsqlDbType>(), logger)
{ {
columnPropertyRelations = columnsToProperties.ToDictionary(p => p.Key, p => typeof(T).GetProperty(p.Value)); }
public GroupSaveSqlGenerator(IDictionary<string, string> columnsToProperties, IDictionary<string, NpgsqlDbType> columnTypes, Serilog.ILogger logger)
{
_columnPropertyRelations = columnsToProperties.ToDictionary(p => p.Key, p => typeof(T).GetProperty(p.Value));
_columnTypes = columnTypes;
_logger = logger; _logger = logger;
var nullProperties = columnPropertyRelations.Where(p => p.Value == null) var nullProperties = _columnPropertyRelations.Where(p => p.Value == null)
.Select(p => columnsToProperties[p.Key]); .Select(p => columnsToProperties[p.Key]);
if (nullProperties.Any()) if (nullProperties.Any())
throw new ArgumentException("Some properties do not exist on the values given: " + string.Join(", ", nullProperties)); throw new ArgumentException("Some properties do not exist on the values given: " + string.Join(", ", nullProperties));
@ -31,8 +40,15 @@ namespace HermesSocketServer.Store
{ {
foreach (var column in columns) foreach (var column in columns)
{ {
var propValue = columnPropertyRelations[column]!.GetValue(value); var propValue = _columnPropertyRelations[column]!.GetValue(value);
c.Parameters.AddWithValue(column.ToLower() + valueCounter, propValue ?? DBNull.Value); if (_columnTypes.Any() && _columnTypes.TryGetValue(column, out var type))
{
if (type == NpgsqlDbType.Jsonb)
propValue = JsonSerializer.Serialize(propValue);
c.Parameters.AddWithValue(column.ToLower() + valueCounter, type, propValue ?? DBNull.Value);
}
else
c.Parameters.AddWithValue(column.ToLower() + valueCounter, propValue ?? DBNull.Value);
} }
valueCounter++; valueCounter++;
} }
@ -81,7 +97,7 @@ namespace HermesSocketServer.Store
if (!columns.Any()) if (!columns.Any())
throw new ArgumentException("Empty list given.", nameof(columns)); throw new ArgumentException("Empty list given.", nameof(columns));
var ctp = columns.ToDictionary(c => c, c => columnPropertyRelations[c]); var ctp = columns.ToDictionary(c => c, c => _columnPropertyRelations[c]);
var sb = new StringBuilder(); var sb = new StringBuilder();
sb.Append($"INSERT INTO \"{table}\" (\"{string.Join("\", \"", columns)}\") VALUES "); sb.Append($"INSERT INTO \"{table}\" (\"{string.Join("\", \"", columns)}\") VALUES ");
foreach (var value in values) foreach (var value in values)
@ -89,8 +105,8 @@ namespace HermesSocketServer.Store
sb.Append("("); sb.Append("(");
foreach (var column in columns) foreach (var column in columns)
{ {
var propValue = columnPropertyRelations[column]!.GetValue(value); var propValue = _columnPropertyRelations[column]!.GetValue(value);
var propType = columnPropertyRelations[column]!.PropertyType; var propType = _columnPropertyRelations[column]!.PropertyType;
WriteValue(sb, propValue ?? DBNull.Value, propType); WriteValue(sb, propValue ?? DBNull.Value, propType);
sb.Append(","); sb.Append(",");
} }
@ -111,7 +127,7 @@ namespace HermesSocketServer.Store
if (!columns.Any()) if (!columns.Any())
throw new ArgumentException("Empty list given.", nameof(columns)); throw new ArgumentException("Empty list given.", nameof(columns));
var ctp = columns.ToDictionary(c => c, c => columnPropertyRelations[c]); var ctp = columns.ToDictionary(c => c, c => _columnPropertyRelations[c]);
var sb = new StringBuilder(); var sb = new StringBuilder();
var columnsLower = columns.Select(c => c.ToLower()); var columnsLower = columns.Select(c => c.ToLower());
sb.Append($"INSERT INTO \"{table}\" (\"{string.Join("\", \"", columns)}\") VALUES "); sb.Append($"INSERT INTO \"{table}\" (\"{string.Join("\", \"", columns)}\") VALUES ");
@ -151,7 +167,7 @@ namespace HermesSocketServer.Store
throw new ArgumentException("Empty list given.", nameof(updateColumns)); throw new ArgumentException("Empty list given.", nameof(updateColumns));
var columns = keyColumns.Union(updateColumns); var columns = keyColumns.Union(updateColumns);
var ctp = columns.ToDictionary(c => c, c => columnPropertyRelations[c]); var ctp = columns.ToDictionary(c => c, c => _columnPropertyRelations[c]);
var sb = new StringBuilder(); var sb = new StringBuilder();
sb.Append($"UPDATE \"{table}\" as t SET {string.Join(", ", updateColumns.Select(c => "\"" + c + "\" = c.\"" + c + "\""))} FROM (VALUES "); sb.Append($"UPDATE \"{table}\" as t SET {string.Join(", ", updateColumns.Select(c => "\"" + c + "\" = c.\"" + c + "\""))} FROM (VALUES ");
foreach (var value in values) foreach (var value in values)
@ -159,8 +175,8 @@ namespace HermesSocketServer.Store
sb.Append("("); sb.Append("(");
foreach (var column in columns) foreach (var column in columns)
{ {
var propValue = columnPropertyRelations[column]!.GetValue(value); var propValue = _columnPropertyRelations[column]!.GetValue(value);
var propType = columnPropertyRelations[column]!.PropertyType; var propType = _columnPropertyRelations[column]!.PropertyType;
WriteValue(sb, propValue, propType); WriteValue(sb, propValue, propType);
sb.Append(","); sb.Append(",");
} }
@ -189,7 +205,7 @@ namespace HermesSocketServer.Store
throw new ArgumentException("Empty list given.", nameof(updateColumns)); throw new ArgumentException("Empty list given.", nameof(updateColumns));
var columns = keyColumns.Union(updateColumns); var columns = keyColumns.Union(updateColumns);
var ctp = columns.ToDictionary(c => c, c => columnPropertyRelations[c]); var ctp = columns.ToDictionary(c => c, c => _columnPropertyRelations[c]);
var sb = new StringBuilder(); var sb = new StringBuilder();
sb.Append($"UPDATE \"{table}\" as t SET {string.Join(", ", updateColumns.Select(c => "\"" + c + "\" = c.\"" + c + "\""))} FROM (VALUES "); sb.Append($"UPDATE \"{table}\" as t SET {string.Join(", ", updateColumns.Select(c => "\"" + c + "\" = c.\"" + c + "\""))} FROM (VALUES ");
for (var row = 0; row < rows; row++) for (var row = 0; row < rows; row++)
@ -226,7 +242,7 @@ namespace HermesSocketServer.Store
if (!keyColumns.Any()) if (!keyColumns.Any())
throw new ArgumentException("Empty list given.", nameof(keyColumns)); throw new ArgumentException("Empty list given.", nameof(keyColumns));
var ctp = keyColumns.ToDictionary(c => c, c => columnPropertyRelations[c]); var ctp = keyColumns.ToDictionary(c => c, c => _columnPropertyRelations[c]);
var sb = new StringBuilder(); var sb = new StringBuilder();
sb.Append($"DELETE FROM \"{table}\" WHERE (\"{string.Join("\", \"", keyColumns)}\") IN ("); sb.Append($"DELETE FROM \"{table}\" WHERE (\"{string.Join("\", \"", keyColumns)}\") IN (");
foreach (var k in keys) foreach (var k in keys)
@ -234,7 +250,7 @@ namespace HermesSocketServer.Store
sb.Append("("); sb.Append("(");
foreach (var column in keyColumns) foreach (var column in keyColumns)
{ {
var propType = columnPropertyRelations[column]!.PropertyType; var propType = _columnPropertyRelations[column]!.PropertyType;
WriteValue(sb, k, propType); WriteValue(sb, k, propType);
sb.Append(","); sb.Append(",");
} }
@ -256,7 +272,7 @@ namespace HermesSocketServer.Store
if (!keyColumns.Any()) if (!keyColumns.Any())
throw new ArgumentException("Empty list given.", nameof(keyColumns)); throw new ArgumentException("Empty list given.", nameof(keyColumns));
var ctp = keyColumns.ToDictionary(c => c, c => columnPropertyRelations[c]); var ctp = keyColumns.ToDictionary(c => c, c => _columnPropertyRelations[c]);
var sb = new StringBuilder(); var sb = new StringBuilder();
sb.Append($"DELETE FROM \"{table}\" WHERE (\"{string.Join("\", \"", keyColumns)}\") IN ("); sb.Append($"DELETE FROM \"{table}\" WHERE (\"{string.Join("\", \"", keyColumns)}\") IN (");
for (var row = 0; row < rows; row++) for (var row = 0; row < rows; row++)
@ -289,7 +305,12 @@ namespace HermesSocketServer.Store
.Append(value?.ToString()) .Append(value?.ToString())
.Append("')"); .Append("')");
else if (type == typeof(TimeSpan)) else if (type == typeof(TimeSpan))
sb.Append(((TimeSpan)value).TotalMilliseconds); {
if (value == null)
sb.Append("0");
else
sb.Append(((TimeSpan)value).TotalMilliseconds);
}
else else
sb.Append(value); sb.Append(value);
} }

View File

@ -2,7 +2,7 @@
using System.Collections.Immutable; using System.Collections.Immutable;
namespace HermesSocketServer.Store namespace HermesSocketServer.Store.Internal
{ {
public abstract class GroupSaveStore<K, V> : IStore<K, V> where K : class where V : class public abstract class GroupSaveStore<K, V> : IStore<K, V> where K : class where V : class
{ {
@ -60,30 +60,30 @@ namespace HermesSocketServer.Store
if (value == null) if (value == null)
return false; return false;
OnInitialModify(key, value);
action(value); action(value);
if (!_added.Contains(key) && !_modified.Contains(key)) if (!_added.Contains(key) && !_modified.Contains(key))
{ {
_modified.Add(key); _modified.Add(key);
_logger.Information($"added key to _modified {key}"); _logger.Information($"added key to _modified {key}");
} }
OnInitialModify(key, value);
return true; return true;
} }
} }
return false; return false;
} }
public void Remove(K? key) public bool Remove(K? key)
{ {
if (key == null) if (key == null)
return; return false;
lock (_lock) lock (_lock)
{ {
OnInitialRemove(key);
if (_store.Remove(key)) if (_store.Remove(key))
{ {
_logger.Information($"removed key from _deleted {key}"); _logger.Information($"removed key from _deleted {key}");
OnInitialRemove(key);
if (!_added.Remove(key)) if (!_added.Remove(key))
{ {
_modified.Remove(key); _modified.Remove(key);
@ -94,8 +94,10 @@ namespace HermesSocketServer.Store
_logger.Information($"added key to _deleted {key}"); _logger.Information($"added key to _deleted {key}");
} }
} }
return true;
} }
} }
return false;
} }
public bool Set(K? key, V? value) public bool Set(K? key, V? value)

View File

@ -0,0 +1,61 @@
using System.Collections.Immutable;
using HermesSocketLibrary.db;
namespace HermesSocketServer.Store.Internal
{
public abstract class AutoSavedStore<K, V> : GroupSaveStore<K, V> where K : class where V : class
{
private readonly GroupSaveSqlGenerator<V> _generator;
private readonly DatabaseTable _table;
private readonly Database _database;
private readonly Serilog.ILogger _logger;
public AutoSavedStore(DatabaseTable table, Database database, Serilog.ILogger logger)
: base(logger)
{
_generator = new GroupSaveSqlGenerator<V>(table.PropertyMapping, logger);
_table = table;
_database = database;
_logger = logger;
}
public override async Task Save()
{
var allColumns = _table.KeyColumns.Union(_table.DataColumns).ToArray();
await GenerateQuery(_added,
(size) => _generator.GeneratePreparedInsertSql(_table.TableName, size, allColumns),
async (query, _, values) => await _generator.DoPreparedStatement(_database, query, values, allColumns));
await GenerateQuery(_modified,
(size) => _generator.GeneratePreparedUpdateSql(_table.TableName, size, _table.KeyColumns, _table.DataColumns),
async (query, _, values) => await _generator.DoPreparedStatement(_database, query, values, allColumns));
await GenerateQuery(_deleted,
(size) => _generator.GeneratePreparedDeleteSql(_table.TableName, size, _table.KeyColumns),
async (query, keys, _) => await _generator.DoPreparedStatementRaw(_database, query, keys, _table.KeyColumns));
}
private async Task GenerateQuery(IList<K> keys, Func<int, string> generate, Func<string, IEnumerable<K>, IEnumerable<V>, Task> execute)
{
ImmutableList<K>? list = null;
lock (_lock)
{
if (!keys.Any())
return;
list = keys.ToImmutableList();
keys.Clear();
}
var query = generate(list.Count);
_logger.Debug($"{_table.TableName} - Adding {list.Count} rows to database: {query}");
var values = list.Select(id => _store[id]).Where(v => v != null);
await execute(query, list, values);
}
}
}

View File

@ -1,33 +1,22 @@
using System.Collections.Immutable;
using HermesSocketLibrary.db; using HermesSocketLibrary.db;
using HermesSocketServer.Models; using HermesSocketServer.Models;
using HermesSocketServer.Store.Internal;
namespace HermesSocketServer.Store namespace HermesSocketServer.Store
{ {
public class PolicyStore : GroupSaveStore<string, PolicyMessage> public class PolicyStore : AutoSavedStore<string, PolicyMessage>
{ {
private readonly string _userId; private readonly string _userId;
private readonly Database _database; private readonly Database _database;
private readonly Serilog.ILogger _logger; private readonly Serilog.ILogger _logger;
private readonly GroupSaveSqlGenerator<PolicyMessage> _generator;
public PolicyStore(string userId, Database database, Serilog.ILogger logger) : base(logger) public PolicyStore(string userId, DatabaseTable table, Database database, Serilog.ILogger logger)
: base(table, database, logger)
{ {
_userId = userId; _userId = userId;
_database = database; _database = database;
_logger = logger; _logger = logger;
var ctp = new Dictionary<string, string>
{
{ "id", "Id" },
{ "userId", "UserId" },
{ "groupId", "GroupId" },
{ "path", "Path" },
{ "count", "Usage" },
{ "timespan", "Span" },
};
_generator = new GroupSaveSqlGenerator<PolicyMessage>(ctp, _logger);
} }
public override async Task Load() public override async Task Load()
@ -61,54 +50,5 @@ namespace HermesSocketServer.Store
protected override void OnInitialRemove(string key) protected override void OnInitialRemove(string key)
{ {
} }
public override async Task Save()
{
int count = 0;
string sql = string.Empty;
ImmutableList<string>? list = null;
if (_added.Any())
{
lock (_lock)
{
list = _added.ToImmutableList();
_added.Clear();
}
count = list.Count;
sql = _generator.GeneratePreparedInsertSql("GroupPermissionPolicy", count, ["id", "userId", "groupId", "path", "count", "timespan"]);
_logger.Debug($"GroupPermissionPolicy - Adding {count} rows to database: {sql}");
var values = list.Select(id => _store[id]).Where(v => v != null);
await _generator.DoPreparedStatement(_database, sql, values, ["id", "userId", "groupId", "path", "count", "timespan"]);
}
if (_modified.Any())
{
lock (_lock)
{
list = _modified.ToImmutableList();
_modified.Clear();
}
count = list.Count;
sql = _generator.GeneratePreparedUpdateSql("GroupPermissionPolicy", count, ["id"], ["userId", "groupId", "path", "count", "timespan"]);
_logger.Debug($"GroupPermissionPolicy - Modifying {count} rows in database: {sql}");
var values = list.Select(id => _store[id]).Where(v => v != null);
await _generator.DoPreparedStatement(_database, sql, values, ["id", "userId", "groupId", "path", "count", "timespan"]);
}
if (_deleted.Any())
{
lock (_lock)
{
list = _deleted.ToImmutableList();
_deleted.Clear();
}
count = list.Count;
sql = _generator.GeneratePreparedDeleteSql("GroupPermissionPolicy", count, ["id"]);
_logger.Debug($"GroupPermissionPolicy - Deleting {count} rows from database: {sql}");
await _generator.DoPreparedStatementRaw(_database, sql, list.Select(id => new Guid(id)), ["id"]);
}
}
} }
} }

54
Store/RedemptionStore.cs Normal file
View File

@ -0,0 +1,54 @@
using HermesSocketLibrary.db;
using HermesSocketLibrary.Requests.Messages;
using HermesSocketServer.Store.Internal;
namespace HermesSocketServer.Store
{
public class RedemptionStore : AutoSavedStore<string, Redemption>
{
private readonly string _userId;
private readonly Database _database;
private readonly Serilog.ILogger _logger;
public RedemptionStore(string userId, DatabaseTable table, Database database, Serilog.ILogger logger)
: base(table, database, logger)
{
_userId = userId;
_database = database;
_logger = logger;
}
public override async Task Load()
{
var data = new Dictionary<string, object>() { { "user", _userId } };
string sql = $"SELECT id, \"redemptionId\", \"order\", \"state\", \"actionName\" FROM \"Redemption\" WHERE \"userId\" = @user";
await _database.Execute(sql, data, (reader) =>
{
var id = reader.GetGuid(0);
_store.Add(id.ToString(), new Redemption()
{
Id = id.ToString(),
UserId = _userId,
RedemptionId = reader.GetString(1),
Order = reader.GetInt32(2),
State = reader.GetBoolean(3),
ActionName = reader.GetString(4),
});
});
_logger.Information($"Loaded {_store.Count} TTS chatter voices from database.");
}
protected override void OnInitialAdd(string key, Redemption value)
{
}
protected override void OnInitialModify(string key, Redemption value)
{
}
protected override void OnInitialRemove(string key)
{
}
}
}

View File

@ -1,31 +1,22 @@
using System.Collections.Immutable;
using HermesSocketLibrary.db; using HermesSocketLibrary.db;
using HermesSocketLibrary.Requests.Messages; using HermesSocketLibrary.Requests.Messages;
using HermesSocketServer.Store.Internal;
namespace HermesSocketServer.Store namespace HermesSocketServer.Store
{ {
public class TTSFilterStore : GroupSaveStore<string, TTSWordFilter> public class TTSFilterStore : AutoSavedStore<string, TTSWordFilter>
{ {
private readonly string _userId; private readonly string _userId;
private readonly Database _database; private readonly Database _database;
private readonly Serilog.ILogger _logger; private readonly Serilog.ILogger _logger;
private readonly GroupSaveSqlGenerator<TTSWordFilter> _generator;
public TTSFilterStore(string userId, Database database, Serilog.ILogger logger) : base(logger) public TTSFilterStore(string userId, DatabaseTable table, Database database, Serilog.ILogger logger)
: base(table, database, logger)
{ {
_userId = userId; _userId = userId;
_database = database; _database = database;
_logger = logger; _logger = logger;
var ctp = new Dictionary<string, string>
{
{ "id", "Id" },
{ "userId", "UserId" },
{ "search", "Search" },
{ "replace", "Replace" },
};
_generator = new GroupSaveSqlGenerator<TTSWordFilter>(ctp, _logger);
} }
public override async Task Load() public override async Task Load()
@ -56,54 +47,5 @@ namespace HermesSocketServer.Store
protected override void OnInitialRemove(string key) protected override void OnInitialRemove(string key)
{ {
} }
public override async Task Save()
{
int count = 0;
string sql = string.Empty;
ImmutableList<string>? list = null;
if (_added.Any())
{
lock (_lock)
{
list = _added.ToImmutableList();
_added.Clear();
}
count = list.Count;
sql = _generator.GeneratePreparedInsertSql("TtsWordFilter", count, ["id", "userId", "search", "replace"]);
_logger.Debug($"TTS Filter - Adding {count} rows to database: {sql}");
var values = list.Select(id => _store[id]).Where(v => v != null);
await _generator.DoPreparedStatement(_database, sql, values, ["id", "userId", "search", "replace"]);
}
if (_modified.Any())
{
lock (_lock)
{
list = _modified.ToImmutableList();
_modified.Clear();
}
count = list.Count;
sql = _generator.GeneratePreparedUpdateSql("TtsWordFilter", count, ["id"], ["userId", "search", "replace"]);
_logger.Debug($"TTS Filter - Modifying {count} rows in database: {sql}");
var values = list.Select(id => _store[id]).Where(v => v != null);
await _generator.DoPreparedStatement(_database, sql, values, ["id", "userId", "search", "replace"]);
}
if (_deleted.Any())
{
lock (_lock)
{
list = _deleted.ToImmutableList();
_deleted.Clear();
}
count = list.Count;
sql = _generator.GeneratePreparedDeleteSql("TtsWordFilter", count, ["id"]);
_logger.Debug($"TTS Filter - Deleting {count} rows from database: {sql}");
await _generator.DoPreparedStatementRaw(_database, sql, list, ["id"]);
}
}
} }
} }

View File

@ -1,30 +1,20 @@
using System.Collections.Immutable;
using HermesSocketLibrary.db; using HermesSocketLibrary.db;
using HermesSocketServer.Models; using HermesSocketServer.Models;
using HermesSocketServer.Store.Internal;
namespace HermesSocketServer.Store namespace HermesSocketServer.Store
{ {
public class UserStore : GroupSaveStore<string, User> public class UserStore : AutoSavedStore<string, User>
{ {
private readonly Database _database; private readonly Database _database;
private readonly Serilog.ILogger _logger; private readonly Serilog.ILogger _logger;
private readonly GroupSaveSqlGenerator<User> _generator;
public UserStore(Database database, Serilog.ILogger logger) : base(logger) public UserStore([FromKeyedServices("User")] DatabaseTable table, Database database, Serilog.ILogger logger)
: base(table, database, logger)
{ {
_database = database; _database = database;
_logger = logger; _logger = logger;
var ctp = new Dictionary<string, string>
{
{ "id", "Id" },
{ "name", "Name" },
{ "email", "Email" },
{ "role", "Role" },
{ "ttsDefaultVoice", "DefaultVoice" }
};
_generator = new GroupSaveSqlGenerator<User>(ctp, _logger);
} }
public override async Task Load() public override async Task Load()
@ -56,54 +46,5 @@ namespace HermesSocketServer.Store
protected override void OnInitialRemove(string key) protected override void OnInitialRemove(string key)
{ {
} }
public override async Task Save()
{
int count = 0;
string sql = string.Empty;
ImmutableList<string>? list = null;
if (_added.Any())
{
lock (_lock)
{
list = _added.ToImmutableList();
_added.Clear();
}
count = list.Count;
sql = _generator.GeneratePreparedInsertSql("User", count, ["id", "name", "email", "role", "ttsDefaultVoice"]);
_logger.Debug($"User - Adding {count} rows to database: {sql}");
var values = list.Select(id => _store[id]).Where(v => v != null);
await _generator.DoPreparedStatement(_database, sql, values, ["id", "name", "email", "role", "ttsDefaultVoice"]);
}
if (_modified.Any())
{
lock (_lock)
{
list = _modified.ToImmutableList();
_modified.Clear();
}
count = list.Count;
sql = _generator.GeneratePreparedUpdateSql("User", count, ["id"], ["name", "email", "role", "ttsDefaultVoice"]);
_logger.Debug($"User - Modifying {count} rows in database: {sql}");
var values = list.Select(id => _store[id]).Where(v => v != null);
await _generator.DoPreparedStatement(_database, sql, values, ["id", "name", "email", "role", "ttsDefaultVoice"]);
}
if (_deleted.Any())
{
lock (_lock)
{
list = _deleted.ToImmutableList();
_deleted.Clear();
}
count = list.Count;
sql = _generator.GeneratePreparedDeleteSql("User", count, ["id"]);
_logger.Debug($"User - Deleting {count} rows from database: {sql}");
await _generator.DoPreparedStatementRaw(_database, sql, list, ["id"]);
}
}
} }
} }

55
Store/VoiceStateStore.cs Normal file
View File

@ -0,0 +1,55 @@
using HermesSocketLibrary.db;
using HermesSocketLibrary.Requests.Messages;
using HermesSocketServer.Store.Internal;
using HermesSocketServer.Validators;
namespace HermesSocketServer.Store
{
public class VoiceStateStore : AutoSavedStore<string, TTSVoiceState>
{
private readonly string _userId;
private readonly VoiceIdValidator _idValidator;
private readonly Database _database;
private readonly Serilog.ILogger _logger;
public VoiceStateStore(string userId, VoiceIdValidator voiceIdValidator, DatabaseTable table, Database database, Serilog.ILogger logger)
: base(table, database, logger)
{
_userId = userId;
_idValidator = voiceIdValidator;
_database = database;
_logger = logger;
}
public override async Task Load()
{
var data = new Dictionary<string, object>() { { "user", _userId } };
string sql = "SELECT \"ttsVoiceId\", state FROM \"TtsVoiceState\" WHERE \"userId\" = @user";
await _database.Execute(sql, data, (reader) =>
{
string id = reader.GetString(0);
_store.Add(id, new TTSVoiceState()
{
Id = id,
Enabled = reader.GetBoolean(1),
UserId = _userId,
});
});
_logger.Information($"Loaded {_store.Count} TTS voice states from database.");
}
protected override void OnInitialAdd(string key, TTSVoiceState value)
{
_idValidator.Check(value.Id);
}
protected override void OnInitialModify(string key, TTSVoiceState value)
{
}
protected override void OnInitialRemove(string key)
{
}
}
}

View File

@ -1,32 +1,25 @@
using System.Collections.Immutable;
using HermesSocketLibrary.db; using HermesSocketLibrary.db;
using HermesSocketServer.Models; using HermesSocketLibrary.Requests.Messages;
using HermesSocketServer.Store.Internal;
using HermesSocketServer.Validators; using HermesSocketServer.Validators;
namespace HermesSocketServer.Store namespace HermesSocketServer.Store
{ {
public class VoiceStore : GroupSaveStore<string, Voice> public class VoiceStore : AutoSavedStore<string, TTSVoice>
{ {
private readonly VoiceIdValidator _idValidator; private readonly VoiceIdValidator _idValidator;
private readonly VoiceNameValidator _nameValidator; private readonly VoiceNameValidator _nameValidator;
private readonly Database _database; private readonly Database _database;
private readonly Serilog.ILogger _logger; private readonly Serilog.ILogger _logger;
private readonly GroupSaveSqlGenerator<Voice> _generator;
public VoiceStore(VoiceIdValidator voiceIdValidator, VoiceNameValidator voiceNameValidator, Database database, Serilog.ILogger logger) : base(logger) public VoiceStore(VoiceIdValidator voiceIdValidator, VoiceNameValidator voiceNameValidator, [FromKeyedServices("Voice")] DatabaseTable table, Database database, Serilog.ILogger logger)
: base(table, database, logger)
{ {
_idValidator = voiceIdValidator; _idValidator = voiceIdValidator;
_nameValidator = voiceNameValidator; _nameValidator = voiceNameValidator;
_database = database; _database = database;
_logger = logger; _logger = logger;
var ctp = new Dictionary<string, string>
{
{ "id", "Id" },
{ "name", "Name" }
};
_generator = new GroupSaveSqlGenerator<Voice>(ctp, _logger);
} }
public override async Task Load() public override async Task Load()
@ -35,7 +28,7 @@ namespace HermesSocketServer.Store
await _database.Execute(sql, new Dictionary<string, object>(), (reader) => await _database.Execute(sql, new Dictionary<string, object>(), (reader) =>
{ {
string id = reader.GetString(0); string id = reader.GetString(0);
_store.Add(id, new Voice() _store.Add(id, new TTSVoice()
{ {
Id = id, Id = id,
Name = reader.GetString(1), Name = reader.GetString(1),
@ -44,13 +37,13 @@ namespace HermesSocketServer.Store
_logger.Information($"Loaded {_store.Count} TTS voices from database."); _logger.Information($"Loaded {_store.Count} TTS voices from database.");
} }
protected override void OnInitialAdd(string key, Voice value) protected override void OnInitialAdd(string key, TTSVoice value)
{ {
_idValidator.Check(value.Id); _idValidator.Check(value.Id);
_nameValidator.Check(value.Name); _nameValidator.Check(value.Name);
} }
protected override void OnInitialModify(string key, Voice value) protected override void OnInitialModify(string key, TTSVoice value)
{ {
_nameValidator.Check(value.Name); _nameValidator.Check(value.Name);
} }
@ -58,54 +51,5 @@ namespace HermesSocketServer.Store
protected override void OnInitialRemove(string key) protected override void OnInitialRemove(string key)
{ {
} }
public override async Task Save()
{
int count = 0;
string sql = string.Empty;
ImmutableList<string>? list = null;
if (_added.Any())
{
lock (_lock)
{
list = _added.ToImmutableList();
_added.Clear();
}
count = list.Count;
sql = _generator.GeneratePreparedInsertSql("TtsVoice", count, ["id", "name"]);
_logger.Debug($"Voice - Adding {count} rows to database: {sql}");
var values = list.Select(id => _store[id]).Where(v => v != null);
await _generator.DoPreparedStatement(_database, sql, values, ["id", "name", "email", "role", "ttsDefaultVoice"]);
}
if (_modified.Any())
{
lock (_lock)
{
list = _modified.ToImmutableList();
_modified.Clear();
}
count = list.Count;
sql = _generator.GeneratePreparedUpdateSql("TtsVoice", count, ["id"], ["name"]);
_logger.Debug($"Voice - Modifying {count} rows in database: {sql}");
var values = list.Select(id => _store[id]).Where(v => v != null);
await _generator.DoPreparedStatement(_database, sql, values, ["id", "name", "email", "role", "ttsDefaultVoice"]);
}
if (_deleted.Any())
{
lock (_lock)
{
list = _deleted.ToImmutableList();
_deleted.Clear();
}
count = list.Count;
sql = _generator.GeneratePreparedDeleteSql("TtsVoice", count, ["id"]);
_logger.Debug($"Voice - Deleting {count} rows from database: {sql}");
await _generator.DoPreparedStatementRaw(_database, sql, list, ["id"]);
}
}
} }
} }