1
0
Fork 1
mirror of https://github.com/TeamOctolings/Octobot.git synced 2025-05-13 17:26:08 +03:00

fix: handle guild data load errors better

This commit is contained in:
Octol1ttle 2023-10-26 18:38:19 +05:00
parent 02707312f5
commit 67a289a2d6
Signed by: Octol1ttle
GPG key ID: B77C34313AEE1FFF
8 changed files with 140 additions and 27 deletions

View file

@ -570,4 +570,10 @@
<data name="MessagesClearedFiltered" xml:space="preserve">
<value>Cleared {0} messages from {1}</value>
</data>
<data name="DataLoadFailedTitle" xml:space="preserve">
<value>An error occurred during guild data load.</value>
</data>
<data name="DataLoadFailedDescription" xml:space="preserve">
<value>This will lead to unexpected behavior. Data will no longer be saved</value>
</data>
</root>

View file

@ -570,4 +570,10 @@
<data name="MessagesClearedFiltered" xml:space="preserve">
<value>Очищено {0} сообщений от {1}</value>
</data>
<data name="DataLoadFailedTitle" xml:space="preserve">
<value>Произошла ошибка при загрузке данных сервера.</value>
</data>
<data name="DataLoadFailedDescription" xml:space="preserve">
<value>Это может привести к неожиданному поведению. Данные больше не будут сохраняться.</value>
</data>
</root>

View file

@ -570,4 +570,10 @@
<data name="MessagesClearedFiltered" xml:space="preserve">
<value>вырезано {0} забавных сообщений от {1}</value>
</data>
<data name="DataLoadFailedTitle" xml:space="preserve">
<value>произошёл пиздец в гилддате.</value>
</data>
<data name="DataLoadFailedDescription" xml:space="preserve">
<value>возможно всё съедет с крыши, но знай, что я больше ничё не сохраню.</value>
</data>
</root>

View file

@ -17,10 +17,12 @@ public sealed class GuildData
public readonly JsonNode Settings;
public readonly string SettingsPath;
public readonly bool DataLoadFailed;
public GuildData(
JsonNode settings, string settingsPath,
Dictionary<ulong, ScheduledEventData> scheduledEvents, string scheduledEventsPath,
Dictionary<ulong, MemberData> memberData, string memberDataPath)
Dictionary<ulong, MemberData> memberData, string memberDataPath, bool dataLoadFailed)
{
Settings = settings;
SettingsPath = settingsPath;
@ -28,6 +30,7 @@ public sealed class GuildData
ScheduledEventsPath = scheduledEventsPath;
MemberData = memberData;
MemberDataPath = memberDataPath;
DataLoadFailed = dataLoadFailed;
}
public MemberData GetOrCreateMemberData(Snowflake memberId)

View file

@ -996,5 +996,21 @@ namespace Octobot {
return ResourceManager.GetString("MessagesClearedFiltered", resourceCulture);
}
}
internal static string DataLoadFailedTitle
{
get
{
return ResourceManager.GetString("DataLoadFailedTitle", resourceCulture);
}
}
internal static string DataLoadFailedDescription
{
get
{
return ResourceManager.GetString("DataLoadFailedDescription", resourceCulture);
}
}
}
}

View file

@ -8,6 +8,7 @@ using Remora.Discord.API.Abstractions.Rest;
using Remora.Discord.API.Gateway.Events;
using Remora.Discord.Extensions.Embeds;
using Remora.Discord.Gateway.Responders;
using Remora.Rest.Core;
using Remora.Results;
namespace Octobot.Responders;
@ -42,7 +43,6 @@ public class GuildLoadedResponder : IResponder<IGuildCreate>
}
var guild = gatewayEvent.Guild.AsT0;
_logger.LogInformation("Joined guild {ID} (\"{Name}\")", guild.ID, guild.Name);
var data = await _guildData.GetData(guild.ID, ct);
var cfg = data.Settings;
@ -51,6 +51,31 @@ public class GuildLoadedResponder : IResponder<IGuildCreate>
data.GetOrCreateMemberData(member.User.Value.ID);
}
var botResult = await _userApi.GetCurrentUserAsync(ct);
if (!botResult.IsDefined(out var bot))
{
return Result.FromError(botResult);
}
if (data.DataLoadFailed)
{
var errorEmbed = new EmbedBuilder()
.WithSmallTitle(Messages.DataLoadFailedTitle, bot)
.WithDescription(Messages.DataLoadFailedDescription)
.WithColour(ColorsList.Red)
.Build();
if (!errorEmbed.IsDefined(out var errorBuilt))
{
return Result.FromError(errorEmbed);
}
return (Result)await _channelApi.CreateMessageAsync(
GetEmergencyFeedbackChannel(guild, data), embeds: new[] { errorBuilt }, ct: ct);
}
_logger.LogInformation("Loaded guild {ID} (\"{Name}\")", guild.ID, guild.Name);
if (!GuildSettings.ReceiveStartupMessages.Get(cfg))
{
return Result.FromSuccess();
@ -61,12 +86,6 @@ public class GuildLoadedResponder : IResponder<IGuildCreate>
return Result.FromSuccess();
}
var botResult = await _userApi.GetCurrentUserAsync(ct);
if (!botResult.IsDefined(out var bot))
{
return Result.FromError(botResult);
}
Messages.Culture = GuildSettings.Language.Get(cfg);
var i = Random.Shared.Next(1, 4);
@ -84,4 +103,23 @@ public class GuildLoadedResponder : IResponder<IGuildCreate>
return (Result)await _channelApi.CreateMessageAsync(
GuildSettings.PrivateFeedbackChannel.Get(cfg), embeds: new[] { built }, ct: ct);
}
private static Snowflake GetEmergencyFeedbackChannel(IGuildCreate.IAvailableGuild guild, GuildData data)
{
var privateFeedback = GuildSettings.PrivateFeedbackChannel.Get(data.Settings);
if (!privateFeedback.Empty())
{
return privateFeedback;
}
var publicFeedback = GuildSettings.PublicFeedbackChannel.Get(data.Settings);
if (!publicFeedback.Empty())
{
return publicFeedback;
}
return guild.SystemChannelID.AsOptional().IsDefined(out var systemChannel)
? systemChannel
: guild.Channels[0].ID;
}
}

View file

@ -30,7 +30,7 @@ public class GuildUnloadedResponder : IResponder<IGuildDelete>
var isDataRemoved = _guildData.UnloadGuildData(guildId);
if (isDataRemoved)
{
_logger.LogInformation("Left guild {GuildId}", guildId);
_logger.LogInformation("Unloaded guild {GuildId}", guildId);
}
return Task.FromResult(Result.FromSuccess());

View file

@ -43,25 +43,31 @@ public sealed class GuildDataService : IHostedService
{
var tasks = new List<Task>();
var datas = _datas.Values.ToArray();
foreach (var data in datas)
foreach (var data in datas.Where(data => !data.DataLoadFailed))
{
await using var settingsStream = File.Create(data.SettingsPath);
tasks.Add(JsonSerializer.SerializeAsync(settingsStream, data.Settings, cancellationToken: ct));
await using var eventsStream = File.Create(data.ScheduledEventsPath);
tasks.Add(JsonSerializer.SerializeAsync(eventsStream, data.ScheduledEvents, cancellationToken: ct));
tasks.Add(SerializeObjectSafelyAsync(data.Settings, data.SettingsPath, ct));
tasks.Add(SerializeObjectSafelyAsync(data.ScheduledEvents, data.ScheduledEventsPath, ct));
var memberDatas = data.MemberData.Values.ToArray();
foreach (var memberData in memberDatas)
{
await using var memberDataStream = File.Create($"{data.MemberDataPath}/{memberData.Id}.json");
tasks.Add(JsonSerializer.SerializeAsync(memberDataStream, memberData, cancellationToken: ct));
}
tasks.AddRange(memberDatas.Select(memberData =>
SerializeObjectSafelyAsync(memberData, $"{data.MemberDataPath}/{memberData.Id}.json", ct)));
}
await Task.WhenAll(tasks);
}
private static async Task SerializeObjectSafelyAsync<T>(T obj, string path, CancellationToken ct)
{
var tempFilePath = path + ".tmp";
await using (var tempFileStream = File.Create(tempFilePath))
{
await JsonSerializer.SerializeAsync(tempFileStream, obj, cancellationToken: ct);
}
File.Copy(tempFilePath, path, true);
File.Delete(tempFilePath);
}
public async Task<GuildData> GetData(Snowflake guildId, CancellationToken ct = default)
{
return _datas.TryGetValue(guildId, out var data) ? data : await InitializeData(guildId, ct);
@ -88,20 +94,50 @@ public sealed class GuildDataService : IHostedService
await File.WriteAllTextAsync(scheduledEventsPath, "{}", ct);
}
var dataLoadFailed = false;
await using var settingsStream = File.OpenRead(settingsPath);
var jsonSettings
= JsonNode.Parse(settingsStream);
JsonNode? jsonSettings = null;
try
{
jsonSettings = JsonNode.Parse(settingsStream);
}
catch (Exception e)
{
_logger.LogError(e, "Guild settings load failed: {Path}", settingsPath);
dataLoadFailed = true;
}
await using var eventsStream = File.OpenRead(scheduledEventsPath);
var events
= await JsonSerializer.DeserializeAsync<Dictionary<ulong, ScheduledEventData>>(
Dictionary<ulong, ScheduledEventData>? events = null;
try
{
events = await JsonSerializer.DeserializeAsync<Dictionary<ulong, ScheduledEventData>>(
eventsStream, cancellationToken: ct);
}
catch (Exception e)
{
_logger.LogError(e, "Guild scheduled events load failed: {Path}", scheduledEventsPath);
dataLoadFailed = true;
}
var memberData = new Dictionary<ulong, MemberData>();
foreach (var dataFileInfo in Directory.CreateDirectory(memberDataPath).GetFiles())
{
await using var dataStream = dataFileInfo.OpenRead();
var data = await JsonSerializer.DeserializeAsync<MemberData>(dataStream, cancellationToken: ct);
MemberData? data;
try
{
data = await JsonSerializer.DeserializeAsync<MemberData>(dataStream, cancellationToken: ct);
}
catch (Exception e)
{
_logger.LogError(e, "Member data load failed: {MemberDataPath}/{FileName}", memberDataPath,
dataFileInfo.Name);
dataLoadFailed = true;
continue;
}
if (data is null)
{
continue;
@ -113,7 +149,8 @@ public sealed class GuildDataService : IHostedService
var finalData = new GuildData(
jsonSettings ?? new JsonObject(), settingsPath,
events ?? new Dictionary<ulong, ScheduledEventData>(), scheduledEventsPath,
memberData, memberDataPath);
memberData, memberDataPath,
dataLoadFailed);
_datas.TryAdd(guildId, finalData);
@ -129,7 +166,8 @@ public sealed class GuildDataService : IHostedService
Directory.CreateDirectory($"{newPath}/..");
Directory.Move(oldPath, newPath);
_logger.LogInformation("Moved guild data to separate folder: \"{OldPath}\" -> \"{NewPath}\"", oldPath, newPath);
_logger.LogInformation("Moved guild data to separate folder: \"{OldPath}\" -> \"{NewPath}\"", oldPath,
newPath);
}
}