Rewrite BGR to new OrderedDictionary<T,V> type

This commit is contained in:
Łukasz Domeradzki
2025-11-11 19:27:10 +01:00
parent 1ac4dfd6c8
commit 970998fb4b
4 changed files with 31 additions and 50 deletions

View File

@@ -24,7 +24,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Collections.Specialized;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Text.Json; using System.Text.Json;
@@ -227,7 +226,7 @@ public sealed class BotController : ArchiController {
[EndpointSummary("Adds keys to redeem using BGR to given bot")] [EndpointSummary("Adds keys to redeem using BGR to given bot")]
[HttpPost("{botNames:required}/GamesToRedeemInBackground")] [HttpPost("{botNames:required}/GamesToRedeemInBackground")]
[ProducesResponseType<GenericResponse<IReadOnlyDictionary<string, IOrderedDictionary>>>((int) HttpStatusCode.OK)] [ProducesResponseType<GenericResponse<IReadOnlyDictionary<string, OrderedDictionary<string, string>>>>((int) HttpStatusCode.OK)]
[ProducesResponseType<GenericResponse>((int) HttpStatusCode.BadRequest)] [ProducesResponseType<GenericResponse>((int) HttpStatusCode.BadRequest)]
public async Task<ActionResult<GenericResponse>> GamesToRedeemInBackgroundPost(string botNames, [FromBody] BotGamesToRedeemInBackgroundRequest request) { public async Task<ActionResult<GenericResponse>> GamesToRedeemInBackgroundPost(string botNames, [FromBody] BotGamesToRedeemInBackgroundRequest request) {
ArgumentException.ThrowIfNullOrEmpty(botNames); ArgumentException.ThrowIfNullOrEmpty(botNames);
@@ -243,7 +242,7 @@ public sealed class BotController : ArchiController {
return BadRequest(new GenericResponse(false, Strings.FormatBotNotFound(botNames))); return BadRequest(new GenericResponse(false, Strings.FormatBotNotFound(botNames)));
} }
IOrderedDictionary validGamesToRedeemInBackground = Bot.ValidateGamesToRedeemInBackground(request.GamesToRedeemInBackground); OrderedDictionary<string, string> validGamesToRedeemInBackground = Bot.ValidateGamesToRedeemInBackground(request.GamesToRedeemInBackground);
if (validGamesToRedeemInBackground.Count == 0) { if (validGamesToRedeemInBackground.Count == 0) {
return BadRequest(new GenericResponse(false, Strings.FormatErrorIsEmpty(nameof(validGamesToRedeemInBackground)))); return BadRequest(new GenericResponse(false, Strings.FormatErrorIsEmpty(nameof(validGamesToRedeemInBackground))));
@@ -251,13 +250,13 @@ public sealed class BotController : ArchiController {
await Utilities.InParallel(bots.Select(bot => Task.Run(() => bot.AddGamesToRedeemInBackground(validGamesToRedeemInBackground)))).ConfigureAwait(false); await Utilities.InParallel(bots.Select(bot => Task.Run(() => bot.AddGamesToRedeemInBackground(validGamesToRedeemInBackground)))).ConfigureAwait(false);
Dictionary<string, IOrderedDictionary> result = new(bots.Count, Bot.BotsComparer); Dictionary<string, OrderedDictionary<string, string>> result = new(bots.Count, Bot.BotsComparer);
foreach (Bot bot in bots) { foreach (Bot bot in bots) {
result[bot.BotName] = validGamesToRedeemInBackground; result[bot.BotName] = validGamesToRedeemInBackground;
} }
return Ok(new GenericResponse<IReadOnlyDictionary<string, IOrderedDictionary>>(result)); return Ok(new GenericResponse<IReadOnlyDictionary<string, OrderedDictionary<string, string>>>(result));
} }
[EndpointSummary("Provides input value to given bot for next usage")] [EndpointSummary("Provides input value to given bot for next usage")]

View File

@@ -21,7 +21,8 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
using System.Collections.Specialized; using System;
using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
@@ -35,7 +36,7 @@ public sealed class BotGamesToRedeemInBackgroundRequest {
[JsonInclude] [JsonInclude]
[JsonRequired] [JsonRequired]
[Required] [Required]
public OrderedDictionary GamesToRedeemInBackground { get; private init; } = new(); public OrderedDictionary<string, string> GamesToRedeemInBackground { get; private init; } = new(StringComparer.OrdinalIgnoreCase);
[JsonConstructor] [JsonConstructor]
private BotGamesToRedeemInBackgroundRequest() { } private BotGamesToRedeemInBackgroundRequest() { }

View File

@@ -22,12 +22,10 @@
// limitations under the License. // limitations under the License.
using System; using System;
using System.Collections;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Frozen; using System.Collections.Frozen;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Collections.Specialized;
using System.ComponentModel; using System.ComponentModel;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
@@ -1020,7 +1018,7 @@ public sealed class Bot : IAsyncDisposable, IDisposable {
return true; return true;
} }
internal void AddGamesToRedeemInBackground(IOrderedDictionary gamesToRedeemInBackground) { internal void AddGamesToRedeemInBackground(IReadOnlyDictionary<string, string> gamesToRedeemInBackground) {
if ((gamesToRedeemInBackground == null) || (gamesToRedeemInBackground.Count == 0)) { if ((gamesToRedeemInBackground == null) || (gamesToRedeemInBackground.Count == 0)) {
throw new ArgumentNullException(nameof(gamesToRedeemInBackground)); throw new ArgumentNullException(nameof(gamesToRedeemInBackground));
} }
@@ -1461,7 +1459,7 @@ public sealed class Bot : IAsyncDisposable, IDisposable {
} }
try { try {
OrderedDictionary gamesToRedeemInBackground = new(); OrderedDictionary<string, string> gamesToRedeemInBackground = new(StringComparer.OrdinalIgnoreCase);
using (StreamReader reader = new(filePath)) { using (StreamReader reader = new(filePath)) {
while (await reader.ReadLineAsync().ConfigureAwait(false) is { } line) { while (await reader.ReadLineAsync().ConfigureAwait(false) is { } line) {
@@ -1489,7 +1487,7 @@ public sealed class Bot : IAsyncDisposable, IDisposable {
} }
if (gamesToRedeemInBackground.Count > 0) { if (gamesToRedeemInBackground.Count > 0) {
IOrderedDictionary validGamesToRedeemInBackground = ValidateGamesToRedeemInBackground(gamesToRedeemInBackground); OrderedDictionary<string, string> validGamesToRedeemInBackground = ValidateGamesToRedeemInBackground(gamesToRedeemInBackground);
if (validGamesToRedeemInBackground.Count > 0) { if (validGamesToRedeemInBackground.Count > 0) {
AddGamesToRedeemInBackground(validGamesToRedeemInBackground); AddGamesToRedeemInBackground(validGamesToRedeemInBackground);
@@ -2009,18 +2007,22 @@ public sealed class Bot : IAsyncDisposable, IDisposable {
return true; return true;
} }
internal static IOrderedDictionary ValidateGamesToRedeemInBackground(IOrderedDictionary gamesToRedeemInBackground) { internal static OrderedDictionary<string, string> ValidateGamesToRedeemInBackground(IReadOnlyDictionary<string, string> gamesToRedeemInBackground) {
if ((gamesToRedeemInBackground == null) || (gamesToRedeemInBackground.Count == 0)) { if ((gamesToRedeemInBackground == null) || (gamesToRedeemInBackground.Count == 0)) {
throw new ArgumentNullException(nameof(gamesToRedeemInBackground)); throw new ArgumentNullException(nameof(gamesToRedeemInBackground));
} }
HashSet<object> invalidKeys = gamesToRedeemInBackground.Cast<DictionaryEntry>().Where(static game => !BotDatabase.IsValidGameToRedeemInBackground(game)).Select(static game => game.Key).ToHashSet(); OrderedDictionary<string, string> result = new(StringComparer.OrdinalIgnoreCase);
foreach (object invalidKey in invalidKeys) { foreach ((string key, string name) in gamesToRedeemInBackground) {
gamesToRedeemInBackground.Remove(invalidKey); if (!BotDatabase.IsValidGameToRedeemInBackground(key, name)) {
continue;
}
result[key] = name;
} }
return gamesToRedeemInBackground; return result;
} }
private async Task Connect() { private async Task Connect() {

View File

@@ -22,8 +22,7 @@
// limitations under the License. // limitations under the License.
using System; using System;
using System.Collections; using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text.Json; using System.Text.Json;
@@ -192,7 +191,7 @@ public sealed class BotDatabase : GenericDatabase {
[JsonDisallowNull] [JsonDisallowNull]
[JsonInclude] [JsonInclude]
private OrderedDictionary GamesToRedeemInBackground { get; init; } = new(); private OrderedDictionary<string, string> GamesToRedeemInBackground { get; init; } = new(StringComparer.OrdinalIgnoreCase);
private BotDatabase(string filePath) : this() { private BotDatabase(string filePath) : this() {
ArgumentException.ThrowIfNullOrEmpty(filePath); ArgumentException.ThrowIfNullOrEmpty(filePath);
@@ -303,18 +302,18 @@ public sealed class BotDatabase : GenericDatabase {
protected override Task Save() => Save(this); protected override Task Save() => Save(this);
internal void AddGamesToRedeemInBackground(IOrderedDictionary games) { internal void AddGamesToRedeemInBackground(IReadOnlyDictionary<string, string> games) {
if ((games == null) || (games.Count == 0)) { if ((games == null) || (games.Count == 0)) {
throw new ArgumentNullException(nameof(games)); throw new ArgumentNullException(nameof(games));
} }
lock (GamesToRedeemInBackground) { lock (GamesToRedeemInBackground) {
foreach (DictionaryEntry game in games) { foreach ((string key, string name) in games) {
if (!IsValidGameToRedeemInBackground(game)) { if (!IsValidGameToRedeemInBackground(key, name)) {
throw new InvalidOperationException(nameof(game)); throw new InvalidOperationException(nameof(IsValidGameToRedeemInBackground));
} }
GamesToRedeemInBackground[game.Key] = game.Value; GamesToRedeemInBackground[key] = name;
} }
} }
@@ -381,33 +380,15 @@ public sealed class BotDatabase : GenericDatabase {
internal (string? Key, string? Name) GetGameToRedeemInBackground() { internal (string? Key, string? Name) GetGameToRedeemInBackground() {
lock (GamesToRedeemInBackground) { lock (GamesToRedeemInBackground) {
foreach (DictionaryEntry game in GamesToRedeemInBackground) { foreach ((string key, string name) in GamesToRedeemInBackground) {
return game.Value switch { return (key, name);
string name => (game.Key as string, name),
JsonElement { ValueKind: JsonValueKind.String } jsonElement => (game.Key as string, jsonElement.GetString()),
_ => throw new InvalidOperationException(nameof(game.Value))
};
} }
} }
return (null, null); return (null, null);
} }
internal static bool IsValidGameToRedeemInBackground(DictionaryEntry game) { internal static bool IsValidGameToRedeemInBackground(string key, string name) => !string.IsNullOrEmpty(key) && !string.IsNullOrEmpty(name) && Utilities.IsValidCdKey(key);
string? key = game.Key as string;
if (string.IsNullOrEmpty(key) || !Utilities.IsValidCdKey(key)) {
return false;
}
switch (game.Value) {
case string name when !string.IsNullOrEmpty(name):
case JsonElement { ValueKind: JsonValueKind.String } jsonElement when !string.IsNullOrEmpty(jsonElement.GetString()):
return true;
default:
return false;
}
}
internal void PerformMaintenance() { internal void PerformMaintenance() {
DateTime now = DateTime.UtcNow; DateTime now = DateTime.UtcNow;
@@ -421,17 +402,15 @@ public sealed class BotDatabase : GenericDatabase {
ArgumentException.ThrowIfNullOrEmpty(key); ArgumentException.ThrowIfNullOrEmpty(key);
lock (GamesToRedeemInBackground) { lock (GamesToRedeemInBackground) {
if (!GamesToRedeemInBackground.Contains(key)) { if (!GamesToRedeemInBackground.Remove(key)) {
return; return;
} }
GamesToRedeemInBackground.Remove(key);
} }
Utilities.InBackground(Save); Utilities.InBackground(Save);
} }
private (bool Valid, string? ErrorMessage) CheckValidation() => GamesToRedeemInBackground.Cast<DictionaryEntry>().Any(static game => !IsValidGameToRedeemInBackground(game)) ? (false, Strings.FormatErrorConfigPropertyInvalid(nameof(GamesToRedeemInBackground), string.Join("", GamesToRedeemInBackground))) : (true, null); private (bool Valid, string? ErrorMessage) CheckValidation() => GamesToRedeemInBackground.Any(static entry => !IsValidGameToRedeemInBackground(entry.Key, entry.Value)) ? (false, Strings.FormatErrorConfigPropertyInvalid(nameof(GamesToRedeemInBackground), string.Join("", GamesToRedeemInBackground))) : (true, null);
private async void OnObjectModified(object? sender, EventArgs e) { private async void OnObjectModified(object? sender, EventArgs e) {
if (string.IsNullOrEmpty(FilePath)) { if (string.IsNullOrEmpty(FilePath)) {