Minor changes to IStore. Added another IStore interface for double keys. Added ChatterStore for chat's voices. Updated respective requests.
This commit is contained in:
parent
6a5c24dc2c
commit
3f3ba63554
@ -1,6 +1,7 @@
|
||||
using System.Text.Json;
|
||||
using HermesSocketLibrary.db;
|
||||
using HermesSocketLibrary.Requests;
|
||||
using HermesSocketServer.Store;
|
||||
using ILogger = Serilog.ILogger;
|
||||
|
||||
namespace HermesSocketServer.Requests
|
||||
@ -9,11 +10,13 @@ namespace HermesSocketServer.Requests
|
||||
{
|
||||
public string Name => "create_tts_user";
|
||||
private Database _database;
|
||||
private ChatterStore _chatters;
|
||||
private ILogger _logger;
|
||||
|
||||
public CreateTTSUser(Database database, ILogger logger)
|
||||
public CreateTTSUser(ChatterStore chatters, Database database, ILogger logger)
|
||||
{
|
||||
_database = database;
|
||||
_chatters = chatters;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
@ -25,8 +28,8 @@ namespace HermesSocketServer.Requests
|
||||
return new RequestResult(false, null);
|
||||
}
|
||||
|
||||
if (long.TryParse(data["chatter"].ToString(), out long chatter))
|
||||
data["chatter"] = chatter;
|
||||
if (long.TryParse(data["chatter"].ToString(), out long chatterId))
|
||||
data["chatter"] = chatterId;
|
||||
else
|
||||
return new RequestResult(false, "Invalid Twitch user id");
|
||||
|
||||
@ -41,13 +44,9 @@ namespace HermesSocketServer.Requests
|
||||
if (check is not bool state || !state)
|
||||
return new RequestResult(false, "Voice is disabled on this channel.");
|
||||
|
||||
string sql = "INSERT INTO \"TtsChatVoice\" (\"userId\", \"chatterId\", \"ttsVoiceId\") VALUES (@user, @chatter, @voice)";
|
||||
var result = await _database.Execute(sql, data);
|
||||
if (result == 0)
|
||||
return new RequestResult(false, "Could not insert the user's voice properly.");
|
||||
|
||||
_chatters.Set(sender, chatterId, data["voice"].ToString());
|
||||
_logger.Information($"Selected a tts voice [voice: {data["voice"]}] for user [chatter: {data["chatter"]}] in channel [channel: {data["user"]}]");
|
||||
return new RequestResult(result == 1, null);
|
||||
return new RequestResult(true, null);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
using System.Text.Json;
|
||||
using HermesSocketLibrary.db;
|
||||
using HermesSocketLibrary.Requests;
|
||||
using HermesSocketServer.Store;
|
||||
using ILogger = Serilog.ILogger;
|
||||
|
||||
namespace HermesSocketServer.Requests
|
||||
@ -11,15 +12,18 @@ namespace HermesSocketServer.Requests
|
||||
|
||||
private readonly ServerConfiguration _configuration;
|
||||
private readonly Database _database;
|
||||
private readonly ILogger _logger;
|
||||
private ChatterStore _chatters;
|
||||
private ILogger _logger;
|
||||
|
||||
public UpdateTTSUser(ServerConfiguration configuration, Database database, ILogger logger)
|
||||
public UpdateTTSUser(ChatterStore chatters, Database database, ServerConfiguration configuration, ILogger logger)
|
||||
{
|
||||
_configuration = configuration;
|
||||
_database = database;
|
||||
_chatters = chatters;
|
||||
_configuration = configuration;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
|
||||
public async Task<RequestResult> Grant(string sender, IDictionary<string, object>? data)
|
||||
{
|
||||
if (data == null)
|
||||
@ -40,10 +44,9 @@ namespace HermesSocketServer.Requests
|
||||
return new RequestResult(false, null);
|
||||
}
|
||||
|
||||
string sql = "UPDATE \"TtsChatVoice\" SET \"ttsVoiceId\" = @voice WHERE \"userId\" = @user AND \"chatterId\" = @chatter";
|
||||
var result = await _database.Execute(sql, data);
|
||||
_chatters.Set(sender, chatterId, data["voice"].ToString());
|
||||
_logger.Information($"Updated chatter's [chatter: {data["chatter"]}] selected tts voice [voice: {data["voice"]}] in channel [channel: {sender}]");
|
||||
return new RequestResult(result == 1, null);
|
||||
return new RequestResult(true, null);
|
||||
}
|
||||
}
|
||||
}
|
13
Server.cs
13
Server.cs
@ -52,6 +52,19 @@ namespace HermesSocketLibrary
|
||||
if (obj.OpCode != 0)
|
||||
_logger.Information($"rxm: {message} [ip: {socket.IPAddress}][id: {socket.Id}][name: {socket.Name}][token: {socket.ApiKey}][uid: {socket.UID}]");
|
||||
|
||||
int[] nonProtectedOps = { 0, 1 };
|
||||
if (string.IsNullOrEmpty(socket.Id) && !nonProtectedOps.Contains(obj.OpCode))
|
||||
{
|
||||
_logger.Warning($"An attempt was made to use protected routes while not logged in [ip: {socket.IPAddress}][id: {socket.Id}][name: {socket.Name}][token: {socket.ApiKey}][uid: {socket.UID}]");
|
||||
return;
|
||||
}
|
||||
int[] protectedOps = { 0, 3, 5, 6, 7, 8 };
|
||||
if (!string.IsNullOrEmpty(socket.Id) && !protectedOps.Contains(obj.OpCode))
|
||||
{
|
||||
_logger.Warning($"An attempt was made to use non-protected routes while logged in [ip: {socket.IPAddress}][id: {socket.Id}][name: {socket.Name}][token: {socket.ApiKey}][uid: {socket.UID}]");
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* 0: Heartbeat
|
||||
* 1: Login RX
|
||||
|
@ -18,5 +18,6 @@ namespace HermesSocketServer
|
||||
public class DatabaseConfiguration
|
||||
{
|
||||
public string ConnectionString;
|
||||
public int SaveDelayInSeconds;
|
||||
}
|
||||
}
|
@ -5,10 +5,14 @@ namespace HermesSocketServer.Services
|
||||
public class DatabaseService : BackgroundService
|
||||
{
|
||||
private readonly VoiceStore _voices;
|
||||
private readonly ChatterStore _chatters;
|
||||
private readonly ServerConfiguration _configuration;
|
||||
private readonly Serilog.ILogger _logger;
|
||||
|
||||
public DatabaseService(VoiceStore voices, Serilog.ILogger logger) {
|
||||
public DatabaseService(VoiceStore voices, ChatterStore chatters, ServerConfiguration configuration, Serilog.ILogger logger) {
|
||||
_voices = voices;
|
||||
_chatters = chatters;
|
||||
_configuration = configuration;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
@ -16,14 +20,16 @@ namespace HermesSocketServer.Services
|
||||
{
|
||||
_logger.Information("Loading TTS voices...");
|
||||
await _voices.Load();
|
||||
_logger.Information("Loaded TTS voices.");
|
||||
_logger.Information("Loading TTS chatters' voice.");
|
||||
await _chatters.Load();
|
||||
|
||||
await Task.Run(async () =>
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromMinutes(1));
|
||||
await Task.Delay(TimeSpan.FromSeconds(_configuration.Database.SaveDelayInSeconds));
|
||||
await _voices.Save();
|
||||
await _chatters.Save();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -81,6 +81,7 @@ s.AddSingleton<VoiceNameValidator>();
|
||||
|
||||
// Stores
|
||||
s.AddSingleton<VoiceStore>();
|
||||
s.AddSingleton<ChatterStore>();
|
||||
|
||||
// Request handlers
|
||||
s.AddSingleton<IRequest, GetTTSUsers>();
|
||||
|
283
Store/ChatterStore.cs
Normal file
283
Store/ChatterStore.cs
Normal file
@ -0,0 +1,283 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Text;
|
||||
using HermesSocketLibrary.db;
|
||||
|
||||
namespace HermesSocketServer.Store
|
||||
{
|
||||
public class ChatterStore : IStore<string, long, string>
|
||||
{
|
||||
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;
|
||||
|
||||
|
||||
public ChatterStore(Database database, Serilog.ILogger logger)
|
||||
{
|
||||
_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 chatterId = reader.GetInt64(0);
|
||||
var ttsVoiceId = reader.GetString(1);
|
||||
var userId = reader.GetString(2);
|
||||
if (!_chatters.TryGetValue(userId, out var chatters))
|
||||
{
|
||||
chatters = new Dictionary<long, string>();
|
||||
_chatters.Add(userId, chatters);
|
||||
}
|
||||
chatters.Add(chatterId, ttsVoiceId);
|
||||
});
|
||||
_logger.Information($"Loaded {_chatters.Count} TTS voices from database.");
|
||||
}
|
||||
|
||||
public void Remove(string user, long? key)
|
||||
{
|
||||
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)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public async Task<bool> Save()
|
||||
{
|
||||
var changes = false;
|
||||
var sb = new StringBuilder();
|
||||
var sql = "";
|
||||
|
||||
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();
|
||||
_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;
|
||||
}
|
||||
|
||||
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();
|
||||
_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;
|
||||
}
|
||||
|
||||
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();
|
||||
_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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
@ -3,10 +3,20 @@ namespace HermesSocketServer.Store
|
||||
public interface IStore<K, V>
|
||||
{
|
||||
V? Get(K key);
|
||||
IEnumerable<V> Get();
|
||||
IDictionary<K, V> Get();
|
||||
Task Load();
|
||||
void Remove(K? key);
|
||||
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);
|
||||
}
|
||||
}
|
@ -42,9 +42,9 @@ namespace HermesSocketServer.Store
|
||||
return null;
|
||||
}
|
||||
|
||||
public IEnumerable<string> Get()
|
||||
public IDictionary<string, string> Get()
|
||||
{
|
||||
return _voices.Values.ToImmutableList();
|
||||
return _voices.ToImmutableDictionary();
|
||||
}
|
||||
|
||||
public async Task Load()
|
||||
|
Loading…
Reference in New Issue
Block a user