Redid stores. Added user store. Added Channel Manager, to manage data from a single channel.
This commit is contained in:
parent
3f3ba63554
commit
4d0b38babd
11
Models/Channel.cs
Normal file
11
Models/Channel.cs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
using HermesSocketServer.Store;
|
||||||
|
|
||||||
|
namespace HermesSocketServer.Models
|
||||||
|
{
|
||||||
|
public class Channel
|
||||||
|
{
|
||||||
|
public string Id { get; set; }
|
||||||
|
public User User { get; set; }
|
||||||
|
public ChatterStore Chatters { get; set; }
|
||||||
|
}
|
||||||
|
}
|
9
Models/ChatterVoice.cs
Normal file
9
Models/ChatterVoice.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
namespace HermesSocketServer.Models
|
||||||
|
{
|
||||||
|
public class ChatterVoice
|
||||||
|
{
|
||||||
|
public string ChatterId { get; set; }
|
||||||
|
public string UserId { get; set; }
|
||||||
|
public string VoiceId { get; set; }
|
||||||
|
}
|
||||||
|
}
|
11
Models/User.cs
Normal file
11
Models/User.cs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
namespace HermesSocketServer.Models
|
||||||
|
{
|
||||||
|
public class User
|
||||||
|
{
|
||||||
|
public string Id { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string Email { get; set; }
|
||||||
|
public string Role { get; set; }
|
||||||
|
public string DefaultVoice { get; set; }
|
||||||
|
}
|
||||||
|
}
|
8
Models/Voice.cs
Normal file
8
Models/Voice.cs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
namespace HermesSocketServer.Models
|
||||||
|
{
|
||||||
|
public class Voice
|
||||||
|
{
|
||||||
|
public string Id { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,8 @@
|
|||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using HermesSocketLibrary.db;
|
using HermesSocketLibrary.db;
|
||||||
using HermesSocketLibrary.Requests;
|
using HermesSocketLibrary.Requests;
|
||||||
|
using HermesSocketServer.Models;
|
||||||
|
using HermesSocketServer.Services;
|
||||||
using HermesSocketServer.Store;
|
using HermesSocketServer.Store;
|
||||||
using ILogger = Serilog.ILogger;
|
using ILogger = Serilog.ILogger;
|
||||||
|
|
||||||
@ -9,14 +11,16 @@ namespace HermesSocketServer.Requests
|
|||||||
public class CreateTTSUser : IRequest
|
public class CreateTTSUser : IRequest
|
||||||
{
|
{
|
||||||
public string Name => "create_tts_user";
|
public string Name => "create_tts_user";
|
||||||
|
private ChannelManager _channels;
|
||||||
private Database _database;
|
private Database _database;
|
||||||
private ChatterStore _chatters;
|
private readonly ServerConfiguration _configuration;
|
||||||
private ILogger _logger;
|
private ILogger _logger;
|
||||||
|
|
||||||
public CreateTTSUser(ChatterStore chatters, Database database, ILogger logger)
|
public CreateTTSUser(ChannelManager channels, Database database, ServerConfiguration configuration, ILogger logger)
|
||||||
{
|
{
|
||||||
_database = database;
|
_database = database;
|
||||||
_chatters = chatters;
|
_channels = channels;
|
||||||
|
_configuration = configuration;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,10 +45,19 @@ namespace HermesSocketServer.Requests
|
|||||||
data["user"] = sender;
|
data["user"] = sender;
|
||||||
|
|
||||||
var check = await _database.ExecuteScalar("SELECT state FROM \"TtsVoiceState\" WHERE \"userId\" = @user AND \"ttsVoiceId\" = @voice", data) ?? false;
|
var check = await _database.ExecuteScalar("SELECT state FROM \"TtsVoiceState\" WHERE \"userId\" = @user AND \"ttsVoiceId\" = @voice", data) ?? false;
|
||||||
if (check is not bool state || !state)
|
if ((check is not bool state || !state) && chatterId != _configuration.Tts.OwnerId)
|
||||||
return new RequestResult(false, "Voice is disabled on this channel.");
|
return new RequestResult(false, "Voice is disabled on this channel.");
|
||||||
|
|
||||||
_chatters.Set(sender, chatterId, data["voice"].ToString());
|
var channel = _channels.Get(sender);
|
||||||
|
if (channel == null)
|
||||||
|
return new RequestResult(false, null);
|
||||||
|
|
||||||
|
channel.Chatters.Set(chatterId.ToString(), new ChatterVoice()
|
||||||
|
{
|
||||||
|
UserId = sender,
|
||||||
|
ChatterId = chatterId.ToString(),
|
||||||
|
VoiceId = data["voice"].ToString()!
|
||||||
|
});
|
||||||
_logger.Information($"Selected a tts voice [voice: {data["voice"]}] for user [chatter: {data["chatter"]}] in channel [channel: {data["user"]}]");
|
_logger.Information($"Selected a tts voice [voice: {data["voice"]}] for user [chatter: {data["chatter"]}] in channel [channel: {data["user"]}]");
|
||||||
return new RequestResult(true, null);
|
return new RequestResult(true, null);
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using HermesSocketLibrary.Requests;
|
using HermesSocketLibrary.Requests;
|
||||||
|
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 class CreateTTSVoice : IRequest
|
public class CreateTTSVoice : IRequest
|
||||||
{
|
{
|
||||||
public string Name => "create_tts_voice";
|
public string Name => "create_tts_voice";
|
||||||
private IStore<string, string> _voices;
|
private IStore<string, Voice> _voices;
|
||||||
private ILogger _logger;
|
private ILogger _logger;
|
||||||
private Random _random;
|
private Random _random;
|
||||||
|
|
||||||
@ -35,7 +36,11 @@ namespace HermesSocketServer.Requests
|
|||||||
|
|
||||||
string id = RandomString(25);
|
string id = RandomString(25);
|
||||||
|
|
||||||
_voices.Set(id, data["voice"].ToString());
|
_voices.Set(id, new Voice()
|
||||||
|
{
|
||||||
|
Id = id,
|
||||||
|
Name = data["voice"].ToString()!
|
||||||
|
});
|
||||||
_logger.Information($"Added a new voice [voice: {data["voice"]}][voice id: {id}]");
|
_logger.Information($"Added a new voice [voice: {data["voice"]}][voice id: {id}]");
|
||||||
|
|
||||||
return new RequestResult(true, id);
|
return new RequestResult(true, id);
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using HermesSocketLibrary.Requests;
|
using HermesSocketLibrary.Requests;
|
||||||
|
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 class DeleteTTSVoice : IRequest
|
public class DeleteTTSVoice : IRequest
|
||||||
{
|
{
|
||||||
public string Name => "delete_tts_voice";
|
public string Name => "delete_tts_voice";
|
||||||
private IStore<string, string> _voices;
|
private IStore<string, Voice> _voices;
|
||||||
private ILogger _logger;
|
private ILogger _logger;
|
||||||
|
|
||||||
public DeleteTTSVoice(VoiceStore voices, ILogger logger)
|
public DeleteTTSVoice(VoiceStore voices, ILogger logger)
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
using HermesSocketLibrary.Requests;
|
using HermesSocketLibrary.Requests;
|
||||||
using HermesSocketServer.Store;
|
using HermesSocketServer.Services;
|
||||||
using ILogger = Serilog.ILogger;
|
using ILogger = Serilog.ILogger;
|
||||||
|
|
||||||
namespace HermesSocketServer.Requests
|
namespace HermesSocketServer.Requests
|
||||||
@ -7,18 +7,22 @@ namespace HermesSocketServer.Requests
|
|||||||
public class GetTTSUsers : IRequest
|
public class GetTTSUsers : IRequest
|
||||||
{
|
{
|
||||||
public string Name => "get_tts_users";
|
public string Name => "get_tts_users";
|
||||||
private ChatterStore _chatters;
|
private ChannelManager _channels;
|
||||||
private ILogger _logger;
|
private ILogger _logger;
|
||||||
|
|
||||||
public GetTTSUsers(ChatterStore chatters, ILogger logger)
|
public GetTTSUsers(ChannelManager channels, ILogger logger)
|
||||||
{
|
{
|
||||||
_chatters = chatters;
|
_channels = channels;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<RequestResult> Grant(string sender, IDictionary<string, object>? data)
|
public async Task<RequestResult> Grant(string sender, IDictionary<string, object>? data)
|
||||||
{
|
{
|
||||||
var temp = _chatters.Get(sender);
|
var channel = _channels.Get(sender);
|
||||||
|
if (channel == null)
|
||||||
|
return new RequestResult(false, null, notifyClientsOnAccount: false);
|
||||||
|
|
||||||
|
var temp = channel.Chatters.Get().ToDictionary(p => p.Key, p => p.Value.VoiceId);
|
||||||
_logger.Information($"Fetched all chatters' selected tts voice for channel [channel: {sender}]");
|
_logger.Information($"Fetched all chatters' selected tts voice for channel [channel: {sender}]");
|
||||||
return new RequestResult(true, temp, notifyClientsOnAccount: false);
|
return new RequestResult(true, temp, notifyClientsOnAccount: false);
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using HermesSocketLibrary.db;
|
using HermesSocketLibrary.db;
|
||||||
using HermesSocketLibrary.Requests;
|
using HermesSocketLibrary.Requests;
|
||||||
using HermesSocketLibrary.Requests.Messages;
|
using HermesSocketLibrary.Requests.Messages;
|
||||||
|
using HermesSocketServer.Store;
|
||||||
using ILogger = Serilog.ILogger;
|
using ILogger = Serilog.ILogger;
|
||||||
|
|
||||||
namespace HermesSocketServer.Requests
|
namespace HermesSocketServer.Requests
|
||||||
@ -8,24 +9,23 @@ namespace HermesSocketServer.Requests
|
|||||||
public class GetTTSVoices : IRequest
|
public class GetTTSVoices : IRequest
|
||||||
{
|
{
|
||||||
public string Name => "get_tts_voices";
|
public string Name => "get_tts_voices";
|
||||||
private Database _database;
|
private VoiceStore _voices;
|
||||||
private ILogger _logger;
|
private ILogger _logger;
|
||||||
|
|
||||||
public GetTTSVoices(Database database, ILogger logger)
|
public GetTTSVoices(VoiceStore voices, ILogger logger)
|
||||||
{
|
{
|
||||||
_database = database;
|
_voices = voices;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<RequestResult> Grant(string sender, IDictionary<string, object>? data)
|
public async Task<RequestResult> Grant(string sender, IDictionary<string, object>? data)
|
||||||
{
|
{
|
||||||
IList<VoiceDetails> voices = new List<VoiceDetails>();
|
IEnumerable<VoiceDetails> voices = _voices.Get().Select(v => new VoiceDetails()
|
||||||
string sql = "SELECT id, name FROM \"TtsVoice\"";
|
|
||||||
await _database.Execute(sql, (IDictionary<string, object>?) null, (r) => voices.Add(new VoiceDetails()
|
|
||||||
{
|
{
|
||||||
Id = r.GetString(0),
|
Id = v.Value.Id,
|
||||||
Name = r.GetString(1)
|
Name = v.Value.Name
|
||||||
}));
|
});
|
||||||
|
|
||||||
_logger.Information($"Fetched all TTS voices for channel [channel: {sender}]");
|
_logger.Information($"Fetched all TTS voices for channel [channel: {sender}]");
|
||||||
return new RequestResult(true, voices, notifyClientsOnAccount: false);
|
return new RequestResult(true, voices, notifyClientsOnAccount: false);
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
using System.Threading.Channels;
|
||||||
using HermesSocketLibrary.db;
|
using HermesSocketLibrary.db;
|
||||||
using HermesSocketLibrary.Requests;
|
using HermesSocketLibrary.Requests;
|
||||||
|
using HermesSocketServer.Models;
|
||||||
|
using HermesSocketServer.Services;
|
||||||
using HermesSocketServer.Store;
|
using HermesSocketServer.Store;
|
||||||
using ILogger = Serilog.ILogger;
|
using ILogger = Serilog.ILogger;
|
||||||
|
|
||||||
@ -10,15 +13,15 @@ namespace HermesSocketServer.Requests
|
|||||||
{
|
{
|
||||||
public string Name => "update_tts_user";
|
public string Name => "update_tts_user";
|
||||||
|
|
||||||
|
private ChannelManager _channels;
|
||||||
|
private Database _database;
|
||||||
private readonly ServerConfiguration _configuration;
|
private readonly ServerConfiguration _configuration;
|
||||||
private readonly Database _database;
|
|
||||||
private ChatterStore _chatters;
|
|
||||||
private ILogger _logger;
|
private ILogger _logger;
|
||||||
|
|
||||||
public UpdateTTSUser(ChatterStore chatters, Database database, ServerConfiguration configuration, ILogger logger)
|
public UpdateTTSUser(ChannelManager channels, Database database, ServerConfiguration configuration, ILogger logger)
|
||||||
{
|
{
|
||||||
_database = database;
|
_database = database;
|
||||||
_chatters = chatters;
|
_channels = channels;
|
||||||
_configuration = configuration;
|
_configuration = configuration;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
@ -39,12 +42,19 @@ namespace HermesSocketServer.Requests
|
|||||||
data["user"] = sender;
|
data["user"] = sender;
|
||||||
|
|
||||||
var check = await _database.ExecuteScalar("SELECT state FROM \"TtsVoiceState\" WHERE \"userId\" = @user AND \"ttsVoiceId\" = @voice", data) ?? false;
|
var check = await _database.ExecuteScalar("SELECT state FROM \"TtsVoiceState\" WHERE \"userId\" = @user AND \"ttsVoiceId\" = @voice", data) ?? false;
|
||||||
if ((check is not bool state || !state) && chatterId != _configuration.OwnerId)
|
if ((check is not bool state || !state) && chatterId != _configuration.Tts.OwnerId)
|
||||||
{
|
|
||||||
return new RequestResult(false, null);
|
return new RequestResult(false, null);
|
||||||
}
|
|
||||||
|
|
||||||
_chatters.Set(sender, chatterId, data["voice"].ToString());
|
var channel = _channels.Get(sender);
|
||||||
|
if (channel == null)
|
||||||
|
return new RequestResult(false, null);
|
||||||
|
|
||||||
|
channel.Chatters.Set(chatterId.ToString(), new ChatterVoice()
|
||||||
|
{
|
||||||
|
UserId = sender,
|
||||||
|
ChatterId = chatterId.ToString(),
|
||||||
|
VoiceId = data["voice"].ToString()!
|
||||||
|
});
|
||||||
_logger.Information($"Updated chatter's [chatter: {data["chatter"]}] selected tts voice [voice: {data["voice"]}] in channel [channel: {sender}]");
|
_logger.Information($"Updated chatter's [chatter: {data["chatter"]}] selected tts voice [voice: {data["voice"]}] in channel [channel: {sender}]");
|
||||||
return new RequestResult(true, null);
|
return new RequestResult(true, null);
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using HermesSocketLibrary.Requests;
|
using HermesSocketLibrary.Requests;
|
||||||
|
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 class UpdateTTSVoice : IRequest
|
public class UpdateTTSVoice : IRequest
|
||||||
{
|
{
|
||||||
public string Name => "update_tts_voice";
|
public string Name => "update_tts_voice";
|
||||||
private IStore<string, string> _voices;
|
private IStore<string, Voice> _voices;
|
||||||
private ILogger _logger;
|
private ILogger _logger;
|
||||||
|
|
||||||
public UpdateTTSVoice(VoiceStore voices, ILogger logger)
|
public UpdateTTSVoice(VoiceStore voices, ILogger logger)
|
||||||
@ -30,7 +31,11 @@ namespace HermesSocketServer.Requests
|
|||||||
if (data["voiceid"] is JsonElement id)
|
if (data["voiceid"] is JsonElement id)
|
||||||
data["voiceid"] = id.ToString();
|
data["voiceid"] = id.ToString();
|
||||||
|
|
||||||
_voices.Set(data["voiceid"].ToString(), data["voice"].ToString());
|
_voices.Set(data["voiceid"].ToString(), new Voice()
|
||||||
|
{
|
||||||
|
Id = data["voiceid"].ToString()!,
|
||||||
|
Name = data["voice"].ToString()!
|
||||||
|
});
|
||||||
_logger.Information($"Updated voice's [voice id: {data["voiceid"]}] name [new name: {data["voice"]}]");
|
_logger.Information($"Updated voice's [voice id: {data["voiceid"]}] name [new name: {data["voice"]}]");
|
||||||
return new RequestResult(true, null);
|
return new RequestResult(true, null);
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ namespace HermesSocketServer
|
|||||||
public string Environment;
|
public string Environment;
|
||||||
public WebsocketServerConfiguration WebsocketServer;
|
public WebsocketServerConfiguration WebsocketServer;
|
||||||
public DatabaseConfiguration Database;
|
public DatabaseConfiguration Database;
|
||||||
public long OwnerId;
|
public TTSConfiguration Tts;
|
||||||
public string AdminPassword;
|
public string AdminPassword;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -20,4 +20,10 @@ namespace HermesSocketServer
|
|||||||
public string ConnectionString;
|
public string ConnectionString;
|
||||||
public int SaveDelayInSeconds;
|
public int SaveDelayInSeconds;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class TTSConfiguration
|
||||||
|
{
|
||||||
|
public long OwnerId;
|
||||||
|
public string DefaultTtsVoice;
|
||||||
|
}
|
||||||
}
|
}
|
56
Services/ChannelManager.cs
Normal file
56
Services/ChannelManager.cs
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
using System.Collections.Concurrent;
|
||||||
|
using HermesSocketLibrary.db;
|
||||||
|
using HermesSocketServer.Models;
|
||||||
|
using HermesSocketServer.Store;
|
||||||
|
|
||||||
|
namespace HermesSocketServer.Services
|
||||||
|
{
|
||||||
|
public class ChannelManager
|
||||||
|
{
|
||||||
|
private readonly UserStore _users;
|
||||||
|
private readonly Database _database;
|
||||||
|
private readonly Serilog.ILogger _logger;
|
||||||
|
private readonly IDictionary<string, Channel> _channels;
|
||||||
|
|
||||||
|
public ChannelManager(UserStore users, Database database, Serilog.ILogger logger)
|
||||||
|
{
|
||||||
|
_users = users;
|
||||||
|
_database = database;
|
||||||
|
_logger = logger;
|
||||||
|
_channels = new ConcurrentDictionary<string, Channel>();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public async Task Add(string userId)
|
||||||
|
{
|
||||||
|
var user = _users.Get(userId);
|
||||||
|
if (user == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (_channels.ContainsKey(userId))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var chatters = new ChatterStore(userId, _database, _logger);
|
||||||
|
await chatters.Load();
|
||||||
|
|
||||||
|
var channel = new Channel()
|
||||||
|
{
|
||||||
|
Id = userId,
|
||||||
|
User = user,
|
||||||
|
Chatters = chatters
|
||||||
|
};
|
||||||
|
|
||||||
|
_channels.Add(userId, channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Channel? Get(string channelId)
|
||||||
|
{
|
||||||
|
if (_channels.TryGetValue(channelId, out var channel))
|
||||||
|
return channel;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -5,13 +5,14 @@ namespace HermesSocketServer.Services
|
|||||||
public class DatabaseService : BackgroundService
|
public class DatabaseService : BackgroundService
|
||||||
{
|
{
|
||||||
private readonly VoiceStore _voices;
|
private readonly VoiceStore _voices;
|
||||||
private readonly ChatterStore _chatters;
|
private readonly UserStore _users;
|
||||||
private readonly ServerConfiguration _configuration;
|
private readonly ServerConfiguration _configuration;
|
||||||
private readonly Serilog.ILogger _logger;
|
private readonly Serilog.ILogger _logger;
|
||||||
|
|
||||||
public DatabaseService(VoiceStore voices, ChatterStore chatters, ServerConfiguration configuration, Serilog.ILogger logger) {
|
public DatabaseService(VoiceStore voices, UserStore users, ServerConfiguration configuration, Serilog.ILogger logger)
|
||||||
|
{
|
||||||
_voices = voices;
|
_voices = voices;
|
||||||
_chatters = chatters;
|
_users = users;
|
||||||
_configuration = configuration;
|
_configuration = configuration;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
@ -20,16 +21,19 @@ namespace HermesSocketServer.Services
|
|||||||
{
|
{
|
||||||
_logger.Information("Loading TTS voices...");
|
_logger.Information("Loading TTS voices...");
|
||||||
await _voices.Load();
|
await _voices.Load();
|
||||||
_logger.Information("Loading TTS chatters' voice.");
|
_logger.Information("Loading users...");
|
||||||
await _chatters.Load();
|
await _users.Load();
|
||||||
|
|
||||||
await Task.Run(async () =>
|
await Task.Run(async () =>
|
||||||
{
|
{
|
||||||
|
var tasks = new List<Task>();
|
||||||
|
await Task.Delay(TimeSpan.FromSeconds(_configuration.Database.SaveDelayInSeconds));
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
await Task.Delay(TimeSpan.FromSeconds(_configuration.Database.SaveDelayInSeconds));
|
tasks.Add(_voices.Save());
|
||||||
await _voices.Save();
|
tasks.Add(_users.Save());
|
||||||
await _chatters.Save();
|
tasks.Add(Task.Delay(TimeSpan.FromSeconds(_configuration.Database.SaveDelayInSeconds)));
|
||||||
|
await Task.WhenAll(tasks);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
using HermesSocketLibrary.db;
|
using HermesSocketLibrary.db;
|
||||||
using HermesSocketLibrary.Requests.Messages;
|
using HermesSocketLibrary.Requests.Messages;
|
||||||
using HermesSocketLibrary.Socket.Data;
|
using HermesSocketLibrary.Socket.Data;
|
||||||
|
using HermesSocketServer.Services;
|
||||||
|
using HermesSocketServer.Store;
|
||||||
using ILogger = Serilog.ILogger;
|
using ILogger = Serilog.ILogger;
|
||||||
|
|
||||||
namespace HermesSocketServer.Socket.Handlers
|
namespace HermesSocketServer.Socket.Handlers
|
||||||
@ -9,14 +11,18 @@ namespace HermesSocketServer.Socket.Handlers
|
|||||||
{
|
{
|
||||||
public int OperationCode { get; } = 1;
|
public int OperationCode { get; } = 1;
|
||||||
|
|
||||||
|
private readonly ChannelManager _manager;
|
||||||
|
private readonly VoiceStore _voices;
|
||||||
private readonly ServerConfiguration _configuration;
|
private readonly ServerConfiguration _configuration;
|
||||||
private readonly Database _database;
|
private readonly Database _database;
|
||||||
private readonly HermesSocketManager _sockets;
|
private readonly HermesSocketManager _sockets;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly object _lock;
|
private readonly object _lock;
|
||||||
|
|
||||||
public HermesLoginHandler(ServerConfiguration configuration, Database database, HermesSocketManager sockets, ILogger logger)
|
public HermesLoginHandler(ChannelManager manager, VoiceStore voices, ServerConfiguration configuration, Database database, HermesSocketManager sockets, ILogger logger)
|
||||||
{
|
{
|
||||||
|
_manager = manager;
|
||||||
|
_voices = voices;
|
||||||
_configuration = configuration;
|
_configuration = configuration;
|
||||||
_database = database;
|
_database = database;
|
||||||
_sockets = sockets;
|
_sockets = sockets;
|
||||||
@ -49,30 +55,31 @@ namespace HermesSocketServer.Socket.Handlers
|
|||||||
sender.WebLogin = data.WebLogin;
|
sender.WebLogin = data.WebLogin;
|
||||||
}
|
}
|
||||||
|
|
||||||
var userIdDict = new Dictionary<string, object>() { { "user", userId } };
|
await _manager.Add(userId);
|
||||||
string? ttsDefaultVoice = null;
|
var channel = _manager.Get(userId);
|
||||||
string sql2 = "select name, role, \"ttsDefaultVoice\" from \"User\" where id = @user";
|
if (channel == null)
|
||||||
await _database.Execute(sql2, userIdDict, sql =>
|
return;
|
||||||
{
|
|
||||||
sender.Name = sql.GetString(0);
|
sender.Name = channel.User.Name;
|
||||||
sender.Admin = sql.GetString(1) == "ADMIN";
|
sender.Admin = channel.User.Role == "ADMIN";
|
||||||
ttsDefaultVoice = sql.GetString(2);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(sender.Name))
|
if (string.IsNullOrEmpty(sender.Name))
|
||||||
{
|
{
|
||||||
_logger.Error($"Could not find username using the user id [user id: {userId}][api key: {data.ApiKey}]");
|
_logger.Error($"Could not find username for a certain user [user id: {userId}][api key: {data.ApiKey}]");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (string.IsNullOrEmpty(channel.User.DefaultVoice))
|
||||||
|
_logger.Warning($"No default voice was set for an user [user id: {userId}][api key: {data.ApiKey}]");
|
||||||
|
|
||||||
var ack = new LoginAckMessage()
|
var ack = new LoginAckMessage()
|
||||||
{
|
{
|
||||||
UserId = userId,
|
UserId = userId,
|
||||||
OwnerId = _configuration.OwnerId,
|
OwnerId = _configuration.Tts.OwnerId,
|
||||||
Admin = sender.Admin,
|
Admin = sender.Admin,
|
||||||
WebLogin = data.WebLogin,
|
WebLogin = data.WebLogin,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var userIdDict = new Dictionary<string, object>() { { "user", userId } };
|
||||||
ack.Connections = new List<Connection>();
|
ack.Connections = new List<Connection>();
|
||||||
string sql3 = "select \"name\", \"type\", \"clientId\", \"accessToken\", \"grantType\", \"scope\", \"expiresAt\", \"default\" from \"Connection\" where \"userId\" = @user";
|
string sql3 = "select \"name\", \"type\", \"clientId\", \"accessToken\", \"grantType\", \"scope\", \"expiresAt\", \"default\" from \"Connection\" where \"userId\" = @user";
|
||||||
await _database.Execute(sql3, userIdDict, sql =>
|
await _database.Execute(sql3, userIdDict, sql =>
|
||||||
@ -89,9 +96,7 @@ namespace HermesSocketServer.Socket.Handlers
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
ack.TTSVoicesAvailable = new Dictionary<string, string>();
|
ack.TTSVoicesAvailable = _voices.Get().ToDictionary(v => v.Key, v => v.Value.Name);
|
||||||
string sql4 = "SELECT id, name FROM \"TtsVoice\"";
|
|
||||||
await _database.Execute(sql4, (IDictionary<string, object>?) null, (r) => ack.TTSVoicesAvailable.Add(r.GetString(0), r.GetString(1)));
|
|
||||||
|
|
||||||
ack.EnabledTTSVoices = new List<string>();
|
ack.EnabledTTSVoices = new List<string>();
|
||||||
string sql5 = $"SELECT v.name FROM \"TtsVoiceState\" s "
|
string sql5 = $"SELECT v.name FROM \"TtsVoiceState\" s "
|
||||||
@ -108,8 +113,7 @@ namespace HermesSocketServer.Socket.Handlers
|
|||||||
Replace = r.GetString(2)
|
Replace = r.GetString(2)
|
||||||
}));
|
}));
|
||||||
|
|
||||||
if (ttsDefaultVoice != null)
|
ack.DefaultTTSVoice = channel.User.DefaultVoice ?? _configuration.Tts.DefaultTtsVoice;
|
||||||
ack.DefaultTTSVoice = ttsDefaultVoice;
|
|
||||||
|
|
||||||
await sender.Send(2, ack);
|
await sender.Send(2, ack);
|
||||||
|
|
||||||
@ -120,7 +124,7 @@ namespace HermesSocketServer.Socket.Handlers
|
|||||||
{
|
{
|
||||||
AnotherClient = true,
|
AnotherClient = true,
|
||||||
UserId = userId,
|
UserId = userId,
|
||||||
OwnerId = _configuration.OwnerId,
|
OwnerId = _configuration.Tts.OwnerId,
|
||||||
WebLogin = data.WebLogin
|
WebLogin = data.WebLogin
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -81,7 +81,7 @@ s.AddSingleton<VoiceNameValidator>();
|
|||||||
|
|
||||||
// Stores
|
// Stores
|
||||||
s.AddSingleton<VoiceStore>();
|
s.AddSingleton<VoiceStore>();
|
||||||
s.AddSingleton<ChatterStore>();
|
s.AddSingleton<UserStore>();
|
||||||
|
|
||||||
// Request handlers
|
// Request handlers
|
||||||
s.AddSingleton<IRequest, GetTTSUsers>();
|
s.AddSingleton<IRequest, GetTTSUsers>();
|
||||||
@ -103,6 +103,8 @@ s.AddSingleton<IRequest, GetRedeemableActions>();
|
|||||||
s.AddSingleton<IRequest, UpdateTTSVoiceState>();
|
s.AddSingleton<IRequest, UpdateTTSVoiceState>();
|
||||||
s.AddSingleton<IRequest, UpdateDefaultTTSVoice>();
|
s.AddSingleton<IRequest, UpdateDefaultTTSVoice>();
|
||||||
|
|
||||||
|
// Managers
|
||||||
|
s.AddSingleton<ChannelManager>();
|
||||||
s.AddSingleton<HermesSocketManager>();
|
s.AddSingleton<HermesSocketManager>();
|
||||||
s.AddSingleton<SocketHandlerManager>();
|
s.AddSingleton<SocketHandlerManager>();
|
||||||
s.AddSingleton<IRequestManager, RequestManager>();
|
s.AddSingleton<IRequestManager, RequestManager>();
|
||||||
@ -113,7 +115,7 @@ s.AddSingleton(new JsonSerializerOptions()
|
|||||||
});
|
});
|
||||||
s.AddSingleton<Server>();
|
s.AddSingleton<Server>();
|
||||||
|
|
||||||
|
// Background services
|
||||||
s.AddHostedService<DatabaseService>();
|
s.AddHostedService<DatabaseService>();
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
@ -1,282 +1,99 @@
|
|||||||
using System.Collections.Immutable;
|
|
||||||
using System.Text;
|
|
||||||
using HermesSocketLibrary.db;
|
using HermesSocketLibrary.db;
|
||||||
|
using HermesSocketServer.Models;
|
||||||
|
|
||||||
namespace HermesSocketServer.Store
|
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 Database _database;
|
||||||
private readonly Serilog.ILogger _logger;
|
private readonly Serilog.ILogger _logger;
|
||||||
private readonly IDictionary<string, IDictionary<long, string>> _chatters;
|
private readonly GroupSaveSqlGenerator<ChatterVoice> _generator;
|
||||||
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)
|
public ChatterStore(string userId, Database database, Serilog.ILogger logger) : base(logger)
|
||||||
{
|
{
|
||||||
|
_userId = userId;
|
||||||
_database = database;
|
_database = database;
|
||||||
_logger = logger;
|
_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)
|
var ctp = new Dictionary<string, string>
|
||||||
{
|
|
||||||
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);
|
{ "chatterId", "ChatterId" },
|
||||||
var ttsVoiceId = reader.GetString(1);
|
{ "ttsVoiceId", "VoiceId" },
|
||||||
var userId = reader.GetString(2);
|
{ "userId", "UserId" },
|
||||||
if (!_chatters.TryGetValue(userId, out var chatters))
|
};
|
||||||
|
_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>();
|
UserId = _userId,
|
||||||
_chatters.Add(userId, chatters);
|
ChatterId = chatterId,
|
||||||
}
|
VoiceId = reader.GetString(1)
|
||||||
chatters.Add(chatterId, ttsVoiceId);
|
});
|
||||||
});
|
});
|
||||||
_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())
|
if (_added.Any())
|
||||||
{
|
{
|
||||||
int count = _added.Count;
|
|
||||||
sb.Append("INSERT INTO \"TtsChatVoice\" (\"chatterId\", \"ttsVoiceId\", \"userId\") VALUES ");
|
|
||||||
lock (_lock)
|
lock (_lock)
|
||||||
{
|
{
|
||||||
foreach (var broadcaster in _added)
|
count = _added.Count;
|
||||||
{
|
sql = _generator.GenerateInsertSql("TtsChatVoice", _added.Select(a => _store[a]), ["userId", "chatterId", "ttsVoiceId"]);
|
||||||
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();
|
_added.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
_logger.Debug($"ADD {count} " + sql);
|
||||||
{
|
await _database.ExecuteScalar(sql);
|
||||||
_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())
|
if (_modified.Any())
|
||||||
{
|
{
|
||||||
int count = _modified.Count;
|
|
||||||
sb.Append("UPDATE \"TtsChatVoice\" as t SET \"ttsVoiceId\" = c.\"ttsVoiceId\" FROM (VALUES ");
|
|
||||||
lock (_lock)
|
lock (_lock)
|
||||||
{
|
{
|
||||||
foreach (var broadcaster in _modified)
|
count = _modified.Count;
|
||||||
{
|
sql = _generator.GenerateUpdateSql("TtsChatVoice", _modified.Select(m => _store[m]), ["userId", "chatterId"], ["ttsVoiceId"]);
|
||||||
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();
|
_modified.Clear();
|
||||||
}
|
}
|
||||||
|
_logger.Debug($"MOD {count} " + sql);
|
||||||
try
|
await _database.ExecuteScalar(sql);
|
||||||
{
|
|
||||||
_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())
|
if (_deleted.Any())
|
||||||
{
|
{
|
||||||
int count = _deleted.Count;
|
|
||||||
sb.Append("DELETE FROM \"TtsChatVoice\" WHERE (\"chatterId\", \"userId\") IN (");
|
|
||||||
lock (_lock)
|
lock (_lock)
|
||||||
{
|
{
|
||||||
foreach (var broadcaster in _deleted)
|
count = _deleted.Count;
|
||||||
{
|
sql = _generator.GenerateDeleteSql("TtsChatVoice", _deleted, ["userId", "chatterId"]);
|
||||||
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();
|
_deleted.Clear();
|
||||||
}
|
}
|
||||||
|
_logger.Debug($"DEL {count} " + sql);
|
||||||
try
|
await _database.ExecuteScalar(sql);
|
||||||
{
|
|
||||||
_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;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
138
Store/GroupSaveSqlGenerator.cs
Normal file
138
Store/GroupSaveSqlGenerator.cs
Normal 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
112
Store/GroupedSaveStore.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -9,14 +9,4 @@ namespace HermesSocketServer.Store
|
|||||||
Task<bool> Save();
|
Task<bool> Save();
|
||||||
bool Set(K? key, V? value);
|
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
94
Store/UserStore.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,223 +1,102 @@
|
|||||||
using System.Collections.Immutable;
|
|
||||||
using System.Text;
|
|
||||||
using HermesSocketLibrary.db;
|
using HermesSocketLibrary.db;
|
||||||
|
using HermesSocketServer.Models;
|
||||||
using HermesSocketServer.Validators;
|
using HermesSocketServer.Validators;
|
||||||
|
|
||||||
namespace HermesSocketServer.Store
|
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 Database _database;
|
||||||
private readonly IValidator _voiceIdValidator;
|
|
||||||
private readonly IValidator _voiceNameValidator;
|
|
||||||
private readonly Serilog.ILogger _logger;
|
private readonly Serilog.ILogger _logger;
|
||||||
private readonly IDictionary<string, string> _voices;
|
private readonly GroupSaveSqlGenerator<Voice> _generator;
|
||||||
private readonly IList<string> _added;
|
|
||||||
private readonly IList<string> _modified;
|
|
||||||
private readonly IList<string> _deleted;
|
|
||||||
private readonly object _lock;
|
|
||||||
|
|
||||||
public DateTime PreviousSave;
|
|
||||||
|
|
||||||
|
|
||||||
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;
|
_database = database;
|
||||||
_voiceIdValidator = voiceIdValidator;
|
|
||||||
_voiceNameValidator = voiceNameValidator;
|
|
||||||
_logger = logger;
|
_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)
|
public override async Task Load()
|
||||||
{
|
|
||||||
if (_voices.TryGetValue(key, out var voice))
|
|
||||||
return voice;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IDictionary<string, string> Get()
|
|
||||||
{
|
|
||||||
return _voices.ToImmutableDictionary();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task Load()
|
|
||||||
{
|
{
|
||||||
string sql = "SELECT id, name FROM \"TtsVoice\";";
|
string sql = "SELECT id, name FROM \"TtsVoice\";";
|
||||||
await _database.Execute(sql, new Dictionary<string, object>(), (reader) =>
|
await _database.Execute(sql, new Dictionary<string, object>(), (reader) =>
|
||||||
{
|
{
|
||||||
var id = reader.GetString(0);
|
string id = reader.GetString(0);
|
||||||
var name = reader.GetString(1);
|
_store.Add(id, new Voice()
|
||||||
_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))
|
|
||||||
{
|
{
|
||||||
_voices.Remove(key);
|
Id = id,
|
||||||
if (!_added.Remove(key))
|
Name = reader.GetString(1),
|
||||||
{
|
});
|
||||||
_modified.Remove(key);
|
});
|
||||||
if (!_deleted.Contains(key))
|
_logger.Information($"Loaded {_store.Count} TTS voices from database.");
|
||||||
_deleted.Add(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> Save()
|
public override void OnInitialAdd(string key, Voice value)
|
||||||
{
|
{
|
||||||
var changes = false;
|
_idValidator.Check(value.Id);
|
||||||
var sb = new StringBuilder();
|
_nameValidator.Check(value.Name);
|
||||||
var sql = "";
|
}
|
||||||
|
|
||||||
|
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())
|
if (_added.Any())
|
||||||
{
|
{
|
||||||
int count = _added.Count;
|
|
||||||
sb.Append("INSERT INTO \"TtsVoice\" (id, name) VALUES ");
|
|
||||||
lock (_lock)
|
lock (_lock)
|
||||||
{
|
{
|
||||||
foreach (var voiceId in _added)
|
count = _added.Count;
|
||||||
{
|
sql = _generator.GenerateInsertSql("TtsVoice", _added.Select(a => _store[a]), ["id", "name"]);
|
||||||
string voice = _voices[voiceId];
|
|
||||||
sb.Append("('")
|
|
||||||
.Append(voiceId)
|
|
||||||
.Append("','")
|
|
||||||
.Append(voice)
|
|
||||||
.Append("'),");
|
|
||||||
}
|
|
||||||
sb.Remove(sb.Length - 1, 1)
|
|
||||||
.Append(';');
|
|
||||||
|
|
||||||
sql = sb.ToString();
|
|
||||||
sb.Clear();
|
|
||||||
_added.Clear();
|
_added.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
_logger.Debug($"ADD {count} " + sql);
|
||||||
{
|
await _database.ExecuteScalar(sql);
|
||||||
_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())
|
if (_modified.Any())
|
||||||
{
|
{
|
||||||
int count = _modified.Count;
|
|
||||||
sb.Append("UPDATE \"TtsVoice\" as t SET name = c.name FROM (VALUES ");
|
|
||||||
lock (_lock)
|
lock (_lock)
|
||||||
{
|
{
|
||||||
foreach (var voiceId in _modified)
|
count = _modified.Count;
|
||||||
{
|
sql = _generator.GenerateUpdateSql("TtsVoice", _modified.Select(m => _store[m]), ["id"], ["name"]);
|
||||||
string voice = _voices[voiceId];
|
|
||||||
sb.Append("('")
|
|
||||||
.Append(voiceId)
|
|
||||||
.Append("','")
|
|
||||||
.Append(voice)
|
|
||||||
.Append("'),");
|
|
||||||
}
|
|
||||||
sb.Remove(sb.Length - 1, 1)
|
|
||||||
.Append(") AS c(id, name) WHERE id = c.id;");
|
|
||||||
|
|
||||||
|
|
||||||
sql = sb.ToString();
|
|
||||||
sb.Clear();
|
|
||||||
_modified.Clear();
|
_modified.Clear();
|
||||||
}
|
}
|
||||||
|
_logger.Debug($"MOD {count} " + sql);
|
||||||
try
|
await _database.ExecuteScalar(sql);
|
||||||
{
|
|
||||||
_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())
|
if (_deleted.Any())
|
||||||
{
|
{
|
||||||
int count = _deleted.Count;
|
|
||||||
sb.Append("DELETE FROM \"TtsVoice\" WHERE id IN (");
|
|
||||||
lock (_lock)
|
lock (_lock)
|
||||||
{
|
{
|
||||||
foreach (var voiceId in _deleted)
|
count = _deleted.Count;
|
||||||
{
|
sql = _generator.GenerateDeleteSql("TtsVoice", _deleted, ["id"]);
|
||||||
sb.Append("'")
|
|
||||||
.Append(voiceId)
|
|
||||||
.Append("',");
|
|
||||||
}
|
|
||||||
sb.Remove(sb.Length - 1, 1)
|
|
||||||
.Append(");");
|
|
||||||
|
|
||||||
|
|
||||||
sql = sb.ToString();
|
|
||||||
sb.Clear();
|
|
||||||
_deleted.Clear();
|
_deleted.Clear();
|
||||||
}
|
}
|
||||||
|
_logger.Debug($"DEL {count} " + sql);
|
||||||
try
|
await _database.ExecuteScalar(sql);
|
||||||
{
|
|
||||||
_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? 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;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user