Skip to content

Commit

Permalink
Updated SteamAPIService to be easier to abstract.
Browse files Browse the repository at this point in the history
  • Loading branch information
DanielWillett committed Oct 14, 2024
1 parent 60a58c6 commit 8993c17
Show file tree
Hide file tree
Showing 17 changed files with 345 additions and 325 deletions.
Binary file modified Libraries/Assembly-CSharp.dll
Binary file not shown.
Binary file modified Libraries/SDG.NetPak.Runtime.dll
Binary file not shown.
Binary file modified Libraries/SDG.NetTransport.dll
Binary file not shown.
Binary file modified Libraries/UnityEx.dll
Binary file not shown.
Binary file modified Libraries/UnturnedDat.dll
Binary file not shown.
Binary file modified Libraries/com.rlabrecque.steamworks.net.dll
Binary file not shown.
5 changes: 3 additions & 2 deletions UncreatedWarfare/Moderation/Actors.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Globalization;
using System.Text.Json;
using System.Text.Json.Serialization;
using Uncreated.Warfare.Steam;
using Uncreated.Warfare.Steam.Models;

namespace Uncreated.Warfare.Moderation;
Expand Down Expand Up @@ -75,7 +76,7 @@ public virtual async ValueTask<string> GetDisplayName(DatabaseInterface database
// todo if (UCPlayer.FromID(Id.m_SteamID) is { } pl)
// return await (pl as IModerationActor).GetProfilePictureURL(database, size, token).ConfigureAwait(false);

PlayerSummary? summary = await database.SteamAPI.GetPlayerSummary(Id, token);
PlayerSummary? summary = await database.SteamAPI.GetPlayerSummaryAsync(Id, token);
if (summary == null)
return null;
url = size switch
Expand Down Expand Up @@ -126,7 +127,7 @@ public virtual async ValueTask<string> GetDisplayName(DatabaseInterface database
// todo if (UCPlayer.FromID(steam64) is { } pl)
// todo return await (pl as IModerationActor).GetProfilePictureURL(database, size, token).ConfigureAwait(false);

PlayerSummary? summary = await database.SteamAPI.GetPlayerSummary(steam64, token);
PlayerSummary? summary = await database.SteamAPI.GetPlayerSummaryAsync(steam64, token);
if (summary == null)
return null;
url = size switch
Expand Down
46 changes: 35 additions & 11 deletions UncreatedWarfare/Moderation/DatabaseInterface.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
namespace Uncreated.Warfare.Moderation;
public class DatabaseInterface
{
private readonly object _cacheSync = new object();
public readonly TimeSpan DefaultInvalidateDuration = TimeSpan.FromSeconds(3);
private readonly ILogger<DatabaseInterface> _logger;
private readonly Dictionary<ulong, string> _iconUrlCacheSmall = new Dictionary<ulong, string>(128);
Expand Down Expand Up @@ -70,9 +71,9 @@ public class DatabaseInterface

public event Action<ModerationEntry>? OnNewModerationEntryAdded;
public event Action<ModerationEntry>? OnModerationEntryUpdated;
public ModerationCache Cache { get; } = new ModerationCache(64);
internal SteamApiService SteamAPI { get; }
public DatabaseInterface(IManualMySqlProvider mySqlProvider, ILogger<DatabaseInterface> logger, SteamApiService steamApi)
public ModerationCache Cache { get; } = new ModerationCache();
internal ISteamApiService SteamAPI { get; }
public DatabaseInterface(IManualMySqlProvider mySqlProvider, ILogger<DatabaseInterface> logger, ISteamApiService steamApi)
{
SteamAPI = steamApi;
Sql = mySqlProvider;
Expand All @@ -88,6 +89,7 @@ public bool TryGetAvatar(IModerationActor actor, AvatarSize size, out string ava
}
return TryGetAvatar(actor.Id, size, out avatar);
}

public bool TryGetAvatar(ulong steam64, AvatarSize size, out string avatar)
{
Dictionary<ulong, string> dict = size switch
Expand All @@ -96,8 +98,11 @@ public bool TryGetAvatar(ulong steam64, AvatarSize size, out string avatar)
AvatarSize.Medium => _iconUrlCacheMedium,
_ => _iconUrlCacheSmall
};
return dict.TryGetValue(steam64, out avatar);

lock (_cacheSync)
return dict.TryGetValue(steam64, out avatar);
}

public void UpdateAvatar(ulong steam64, AvatarSize size, string value)
{
Dictionary<ulong, string> dict = size switch
Expand All @@ -106,8 +111,11 @@ public void UpdateAvatar(ulong steam64, AvatarSize size, string value)
AvatarSize.Medium => _iconUrlCacheMedium,
_ => _iconUrlCacheSmall
};
dict[steam64] = value;

lock (_cacheSync)
dict[steam64] = value;
}

public bool TryGetUsernames(IModerationActor actor, out PlayerNames names)
{
CSteamID steam64 = new CSteamID(actor.Id);
Expand All @@ -119,14 +127,19 @@ public bool TryGetUsernames(IModerationActor actor, out PlayerNames names)

return TryGetUsernames(steam64, out names);
}

public bool TryGetUsernames(CSteamID steam64, out PlayerNames names)
{
return _usernameCache.TryGetValue(steam64.m_SteamID, out names);
lock (_cacheSync)
return _usernameCache.TryGetValue(steam64.m_SteamID, out names);
}

public void UpdateUsernames(CSteamID steam64, PlayerNames names)
{
_usernameCache[steam64.m_SteamID] = names;
lock (_cacheSync)
_usernameCache[steam64.m_SteamID] = names;
}

// todo public Task VerifyTables(CancellationToken token = default) => Sql.VerifyTables(Schema, token);
public async Task<PlayerNames> GetUsernames(CSteamID id, bool useCache, CancellationToken token = default)
{
Expand All @@ -141,6 +154,7 @@ public async Task<PlayerNames> GetUsernames(CSteamID id, bool useCache, Cancella
// return names;
return PlayerNames.Nil;
}

public async Task<T?> ReadOne<T>(uint id, bool tryGetFromCache, bool detail = true, bool baseOnly = false, CancellationToken token = default) where T : class, IModerationEntry
{
if (tryGetFromCache && Cache.TryGet(id, out T val, DefaultInvalidateDuration))
Expand All @@ -161,7 +175,7 @@ await Sql.QueryAsync(sb.ToString(), pkArgs, token, reader =>

if (entry == null)
{
Cache.Remove(id);
Cache.TryRemove(id, out _);
return null;
}

Expand Down Expand Up @@ -1377,15 +1391,25 @@ await Sql.QueryAsync(builder.ToString(), args, token, reader =>

Cache.AddOrUpdate(mod);

UniTask.Create(async () =>
if (WarfareModule.IsActive)
{
await UniTask.SwitchToMainThread(token);
UniTask.Create(async () =>
{
await UniTask.SwitchToMainThread(token);

if (isNew)
OnNewModerationEntryAdded?.Invoke(mod);
else
OnModerationEntryUpdated?.Invoke(mod);
});
}
else
{
if (isNew)
OnNewModerationEntryAdded?.Invoke(mod);
else
OnModerationEntryUpdated?.Invoke(mod);
});
}
}
public async Task<int> GetNextPresetLevel(ulong player, PresetType type, CancellationToken token = default)
{
Expand Down
9 changes: 6 additions & 3 deletions UncreatedWarfare/Moderation/ModerationEntry.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using DanielWillett.SpeedBytes;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
Expand Down Expand Up @@ -639,15 +640,14 @@ public interface IDurationModerationEntry : IModerationEntry
bool IsPermanent { get; set; }
}

public class ModerationCache : Dictionary<uint, ModerationEntryCacheEntry>
public class ModerationCache : ConcurrentDictionary<uint, ModerationEntryCacheEntry>
{
public ModerationCache() { }
public ModerationCache(int capacity) : base(capacity) { }
public new IModerationEntry this[uint key]
{
get => base[key].Entry;
set => base[key] = new ModerationEntryCacheEntry(value);
}

public void AddOrUpdate(IModerationEntry entry)
{
if (entry.Id != 0u)
Expand All @@ -665,6 +665,7 @@ public bool TryGet<T>(uint key, out T value) where T : ModerationEntry
value = null!;
return false;
}

public bool TryGet<T>(uint key, out T value, TimeSpan timeout) where T : class, IModerationEntry
{
if (key != 0u && timeout.Ticks > 0 && TryGetValue(key, out ModerationEntryCacheEntry entry))
Expand All @@ -677,6 +678,7 @@ public bool TryGet<T>(uint key, out T value, TimeSpan timeout) where T : class,
return false;
}
}

public readonly struct ModerationEntryCacheEntry
{
public IModerationEntry Entry { get; }
Expand All @@ -688,6 +690,7 @@ public ModerationEntryCacheEntry(IModerationEntry entry, DateTime lastRefreshed)
LastRefreshed = lastRefreshed;
}
}

public sealed class ModerationEntryConverter : JsonConverter<ModerationEntry>
{
public override ModerationEntry Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
Expand Down
6 changes: 3 additions & 3 deletions UncreatedWarfare/Moderation/ModerationUI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public partial class ModerationUI : UnturnedUI

private readonly string _discordInviteCode;

private readonly SteamApiService _steamAPI;
private readonly ISteamApiService _steamAPI;

public const int ModerationHistoryLength = 30;
public const string PositiveReputationColor = "00cc00";
Expand Down Expand Up @@ -191,7 +191,7 @@ public ModerationUI(IServiceProvider serviceProvider)
{
_valueFormatter = serviceProvider.GetRequiredService<ITranslationValueFormatter>();
_playerService = serviceProvider.GetRequiredService<IPlayerService>();
_steamAPI = serviceProvider.GetRequiredService<SteamApiService>();
_steamAPI = serviceProvider.GetRequiredService<ISteamApiService>();
_moderationSql = serviceProvider.GetRequiredService<DatabaseInterface>();
_itemIconProvider = serviceProvider.GetRequiredService<ItemIconProvider>();

Expand Down Expand Up @@ -567,7 +567,7 @@ private async UniTask PrepareModerationPage(WarfarePlayer player, CancellationTo

UpdateSelectedPlayer(player);

await _steamAPI.TryDownloadAllPlayerSummaries(token: token);
// todo await _steamAPI.TryDownloadAllPlayerSummaries(token: token);
await UniTask.SwitchToMainThread(token);

ModerationData data = GetOrAddModerationData(player);
Expand Down
8 changes: 4 additions & 4 deletions UncreatedWarfare/Players/PendingTasks/SteamApiSummaryTask.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,19 @@ namespace Uncreated.Warfare.Players.PendingTasks;
/// </summary>
public class SteamApiSummaryTask : IPlayerPendingTask
{
private readonly SteamApiService _apiService;
private readonly ISteamApiService _apiService;

private PlayerSummary? _summary;
private ulong[]? _friends;
public SteamApiSummaryTask(SteamApiService apiService)
public SteamApiSummaryTask(ISteamApiService apiService)
{
_apiService = apiService;
}

async Task<bool> IPlayerPendingTask.RunAsync(PlayerPending e, CancellationToken token)
{
UniTask<PlayerSummary?> summaryTask = _apiService.GetPlayerSummary(e.Steam64.m_SteamID, token);
PlayerFriendsList friendsList = await _apiService.GetPlayerFriends(e.Steam64.m_SteamID, token);
Task<PlayerSummary> summaryTask = _apiService.GetPlayerSummaryAsync(e.Steam64.m_SteamID, token);
PlayerFriendsList friendsList = await _apiService.GetPlayerFriendsAsync(e.Steam64.m_SteamID, token);
_summary = await summaryTask;

friendsList.Friends.Sort((a, b) => a.FriendsSince.CompareTo(b.FriendsSince));
Expand Down
99 changes: 99 additions & 0 deletions UncreatedWarfare/Steam/ISteamApiService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
using System;
using System.Globalization;
using Uncreated.Warfare.Util;

namespace Uncreated.Warfare.Steam;
public interface ISteamApiService
{
/// <exception cref="SteamApiRequestException"/>
Task<TResponse> ExecuteQueryAsync<TResponse>(SteamApiQuery query, CancellationToken token) where TResponse : notnull;
}

public class SteamApiRequestException : Exception
{
public SteamApiRequestException(string message) : base(message) { }
public SteamApiRequestException(string message, Exception innerException) : base(message, innerException) { }
}

public readonly struct SteamApiQuery
{
public readonly string Interface;
public readonly string Method;
public readonly int Version;
public readonly string? QueryString;
public readonly float StartTimeout = 1f;

public SteamApiQuery(string @interface, string method, int version, string? queryString)
{
Interface = @interface;
Method = method;
Version = version;
QueryString = queryString;
}

public string CreateUrl(string apiKey)
{
int vLen = MathUtility.CountDigits(Version);
int stringLen = SteamApiServiceExtensions.BaseSteamApiUrl.Length + Interface.Length + 1 + Method.Length + 2 + vLen + 5 + apiKey.Length;
if (QueryString != null)
stringLen += 1 + QueryString.Length;

CreateUrlState state = default;
state.APIKey = apiKey;
state.Version = Version;
state.Method = Method;
state.Interface = Interface;
state.VersionLength = vLen;
state.Query = QueryString;

return string.Create(stringLen, state, (span, state) =>
{
int index = 0;
SteamApiServiceExtensions.BaseSteamApiUrl.AsSpan().CopyTo(span);
index += SteamApiServiceExtensions.BaseSteamApiUrl.Length;

state.Interface.AsSpan().CopyTo(span[index..]);
index += state.Interface.Length;

span[index++] = '/';

state.Method.AsSpan().CopyTo(span[index..]);
index += state.Method.Length;

span[index++] = '/';
span[index++] = 'v';

state.Version.TryFormat(span[index..], out _, "D", CultureInfo.InvariantCulture);
index += state.VersionLength;

span[index++] = '?';
span[index++] = 'k';
span[index++] = 'e';
span[index++] = 'y';
span[index++] = '=';

state.APIKey.AsSpan().CopyTo(span[index..]);
index += state.APIKey.Length;

if (state.Query == null)
return;

span[index++] = '&';
state.Query.AsSpan().CopyTo(span[index..]);
});
}
private struct CreateUrlState
{
public string Interface;
public string Method;
public string? Query;
public string APIKey;
public int Version;
public int VersionLength;
}

public override string ToString()
{
return $"steam://{Interface}/{Method}/v{Version.ToString(CultureInfo.InvariantCulture)}";
}
}
Loading

0 comments on commit 8993c17

Please sign in to comment.