Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix various missing method or class loading due to ASF update #111

Merged
merged 10 commits into from
Dec 4, 2024
3 changes: 3 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ indent_style = tab
insert_final_newline = true
trim_trailing_whitespace = true

[*.{cs,vb}]
dotnet_diagnostic.CA1859.severity = none

###############################
# C# Coding Conventions #
###############################
Expand Down
13 changes: 7 additions & 6 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [macos-latest, ubuntu-latest, windows-latest]
os: [
macos-latest,
ubuntu-latest,
#windows-latest
]

runs-on: ${{ matrix.os }}

Expand Down Expand Up @@ -190,13 +194,10 @@ jobs:
- name: Checkout code
uses: actions/checkout@v4.2.2

# TODO: It'd be perfect if we could match final artifacts to the platform they target, so e.g. linux build comes from the linux machine
# However, that is currently impossible due to https://github.com/dotnet/msbuild/issues/3897
# Therefore, we'll (sadly) pull artifacts from Windows machine only for now
- name: Download generic artifact from windows-latest
- name: Download generic artifact from ubuntu-latest
uses: actions/download-artifact@v4.1.8
with:
name: windows-latest_${{ env.PLUGIN_NAME }}-generic
name: ubuntu-latest_${{ env.PLUGIN_NAME }}-generic
path: out

- name: Unzip and copy generic artifact
Expand Down
9 changes: 7 additions & 2 deletions ASFFreeGames/ASFFreeGamesPlugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ internal static PluginContext Context {
}

// ReSharper disable once InconsistentNaming
private static readonly AsyncLocal<PluginContext> _context = new();
private static readonly Utils.Workarounds.AsyncLocal<PluginContext> _context = new();
private static CancellationToken CancellationToken => Context.CancellationToken;

public string Name => StaticName;
Expand Down Expand Up @@ -141,7 +141,9 @@ public async void CollectGamesOnClock(object? source) {

if (!cts.IsCancellationRequested) {
string cmd = $"FREEGAMES {FreeGamesCommand.CollectInternalCommandString} " + string.Join(' ', reorderedBots.Select(static bot => bot.BotName));
#pragma warning disable CS1998
await OnBotCommand(null!, EAccess.None, cmd, cmd.Split()).ConfigureAwait(false);
#pragma warning restore CS1998
}
}
}
Expand Down Expand Up @@ -212,12 +214,15 @@ private async Task RemoveBot(Bot bot) {
private void StartTimerIfNeeded() => CollectIntervalManager.StartTimerIfNeeded();

~ASFFreeGamesPlugin() => CollectIntervalManager.Dispose();
public readonly GithubPluginUpdater Updater = new(new Lazy<Version>(GetVersion));

#region IGitHubPluginUpdates implementation
private readonly GithubPluginUpdater Updater = new(new Lazy<Version>(GetVersion));
string IGitHubPluginUpdates.RepositoryName => GithubPluginUpdater.RepositoryName;

bool IGitHubPluginUpdates.CanUpdate => Updater.CanUpdate;

Task<Uri?> IGitHubPluginUpdates.GetTargetReleaseURL(Version asfVersion, string asfVariant, bool asfUpdate, bool stable, bool forced) => Updater.GetTargetReleaseURL(asfVersion, asfVariant, asfUpdate, stable, forced);
#endregion
}

#pragma warning restore CA1812 // ASF uses this class during runtime
15 changes: 12 additions & 3 deletions ASFFreeGames/Commands/FreeGamesCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -156,9 +156,18 @@ public void Dispose() {
private async ValueTask<string?> HandleInternalCollectCommand(Bot? bot, string[] args, CancellationToken cancellationToken) {
Dictionary<string, Bot> botMap = Context.Bots.ToDictionary(static b => b.BotName.Trim(), static b => b, StringComparer.InvariantCultureIgnoreCase);

Bot[] bots = args.Skip(2).Select(botName => botMap.GetValueOrDefault(botName.Trim())).Where(static b => b is not null).ToArray()!;
List<Bot> bots = [];

if (bots.Length == 0) {
for (int i = 2; i < args.Length; i++) {
string botName = args[i].Trim();

// ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
if (botMap.TryGetValue(botName, out Bot? savedBot) && savedBot is not null) {
bots.Add(savedBot);
}
}

if (bots.Count == 0) {
if (bot is null) {
return null;
}
Expand All @@ -168,7 +177,7 @@ public void Dispose() {

int collected = await CollectGames(bots, ECollectGameRequestSource.Scheduled, cancellationToken).ConfigureAwait(false);

return FormatBotResponse(bot, $"Collected a total of {collected} free game(s)" + (bots.Length > 1 ? $" on {bots.Length} bots" : $" on {bots.FirstOrDefault()?.BotName}"));
return FormatBotResponse(bot, $"Collected a total of {collected} free game(s)" + (bots.Count > 1 ? $" on {bots.Count} bots" : $" on {bots.FirstOrDefault()?.BotName}"));
}

private async Task SaveOptions(CancellationToken cancellationToken) {
Expand Down
12 changes: 6 additions & 6 deletions ASFFreeGames/Configurations/ASFFreeGamesOptionsLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,12 @@ public static async Task Save(ASFFreeGamesOptions options, CancellationToken can
try {
#pragma warning disable CAC001
#pragma warning disable CA2007

// Use FileOptions.Asynchronous when creating a file stream for async operations
await using FileStream fs = new(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite, 4096, FileOptions.Asynchronous);
await using FileStream fs = new(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite);
#pragma warning restore CA2007
#pragma warning restore CAC001
using IMemoryOwner<byte> buffer = MemoryPool<byte>.Shared.Rent(checked(fs.Length > 0 ? (int) fs.Length + 1 : 1 << 15));
int read = await fs.ReadAsync(buffer.Memory, cancellationToken).ConfigureAwait(false);
byte[] buffer = new byte[fs.Length > 0 ? (int) fs.Length + 1 : 1 << 15];

int read = await fs.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);

try {
fs.Position = 0;
Expand All @@ -76,7 +75,8 @@ public static async Task Save(ASFFreeGamesOptions options, CancellationToken can

catch (Exception) {
fs.Position = 0;
await fs.WriteAsync(buffer.Memory[..read], cancellationToken).ConfigureAwait(false);

await fs.WriteAsync(((ReadOnlyMemory<byte>) buffer)[..read], cancellationToken).ConfigureAwait(false);
fs.SetLength(read);

throw;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,14 @@ private async Task<IReadOnlyCollection<RedditGameEntry>> DoDownloadUsingInstance

long dateMillis = date.ToUnixTimeMilliseconds();

return entries.Select(entry => entry.ToRedditGameEntry(dateMillis)).ToArray();
List<RedditGameEntry> redditGameEntries = [];

// ReSharper disable once LoopCanBeConvertedToQuery
foreach (RedlibGameEntry entry in entries) {
redditGameEntries.Add(entry.ToRedditGameEntry(dateMillis));
}

return redditGameEntries;
}

private async Task<IReadOnlyCollection<RedditGameEntry>> DownloadUsingInstance(SimpleHttpClient client, Uri uri, uint retry, CancellationToken cancellationToken) {
Expand Down
37 changes: 29 additions & 8 deletions ASFFreeGames/Github/GithubPluginUpdater.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,32 +14,55 @@ public class GithubPluginUpdater(Lazy<Version> version) {

private Version CurrentVersion => version.Value;

private static void LogGenericError(string message) {
if (string.IsNullOrEmpty(message)) {
return;
}

ArchiSteamFarm.Core.ASF.ArchiLogger.LogGenericError($"{nameof(GithubPluginUpdater)}: {message}");
}

private static void LogGenericDebug(string message) {
if (string.IsNullOrEmpty(message)) {
return;
}

ArchiSteamFarm.Core.ASF.ArchiLogger.LogGenericDebug($"{nameof(GithubPluginUpdater)}: {message}");
}

public async Task<Uri?> GetTargetReleaseURL(Version asfVersion, string asfVariant, bool asfUpdate, bool stable, bool forced) {
ArgumentNullException.ThrowIfNull(asfVersion);
ArgumentException.ThrowIfNullOrEmpty(asfVariant);

if (!CanUpdate) {
LogGenericDebug("CanUpdate is false");

return null;
}

if (string.IsNullOrEmpty(RepositoryName)) {
//ArchiSteamFarm.Core.ASF.ArchiLogger.LogGenericError(Strings.FormatWarningFailedWithError(nameof(RepositoryName)));
LogGenericError("RepositoryName is null or empty");

return null;
}

ReleaseResponse? releaseResponse = await GitHubService.GetLatestRelease(RepositoryName).ConfigureAwait(false);

if (releaseResponse == null) {
LogGenericError("GetLatestRelease returned null");

return null;
}

if (releaseResponse.IsPreRelease) {
LogGenericError("GetLatestRelease returned pre-release");

return null;
}

if (stable && !((releaseResponse.PublishedAt - DateTime.UtcNow).Duration() > TimeSpan.FromHours(3))) {
// Skip updates that are too recent
if (stable && ((releaseResponse.PublishedAt - DateTime.UtcNow).Duration() < TimeSpan.FromHours(3))) {
LogGenericDebug("GetLatestRelease returned too recent");

return null;
}

Expand All @@ -48,27 +71,25 @@ public class GithubPluginUpdater(Lazy<Version> version) {
if (!forced && (CurrentVersion >= newVersion)) {
// Allow same version to be re-updated when we're updating ASF release and more than one asset is found - potential compatibility difference
if ((CurrentVersion > newVersion) || !asfUpdate || (releaseResponse.Assets.Count(static asset => asset.Name.EndsWith(".zip", StringComparison.OrdinalIgnoreCase)) < 2)) {
//ASF.ArchiLogger.LogGenericInfo(Strings.FormatPluginUpdateNotFound(Name, Version, newVersion));

return null;
}
}

if (releaseResponse.Assets.Count == 0) {
//ASF.ArchiLogger.LogGenericWarning(Strings.FormatPluginUpdateNoAssetFound(Name, Version, newVersion));
LogGenericError($"GetLatestRelease for version {newVersion} returned no assets");

return null;
}

ReleaseAsset? asset = releaseResponse.Assets.FirstOrDefault(static asset => asset.Name.EndsWith(".zip", StringComparison.OrdinalIgnoreCase) && (asset.Size > (1 << 18)));

if ((asset == null) || !releaseResponse.Assets.Contains(asset)) {
//ASF.ArchiLogger.LogGenericWarning(Strings.FormatPluginUpdateNoAssetFound(Name, Version, newVersion));
LogGenericError($"GetLatestRelease for version {newVersion} returned no valid assets");

return null;
}

//.ArchiLogger.LogGenericInfo(Strings.FormatPluginUpdateFound(Name, Version, newVersion));
LogGenericDebug($"GetLatestRelease for version {newVersion} returned asset {asset.Name} with url {asset.DownloadURL}");

return asset.DownloadURL;
}
Expand Down
2 changes: 1 addition & 1 deletion ASFFreeGames/Reddit/RedditHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ IReadOnlyCollection<RedditGameEntry> returnValue() {
}

// ReSharper disable once LoopCanBePartlyConvertedToQuery
foreach (JsonNode? comment in children.AsArray()) {
foreach (JsonNode? comment in (JsonArray) children) {
JsonNode? commentData = comment?["data"];

if (commentData is null) {
Expand Down
4 changes: 2 additions & 2 deletions ASFFreeGames/Redlib/Instances/RedlibInstanceList.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,10 @@ internal static List<Uri> ParseUrls(JsonNode json) {
return [];
}

List<Uri> uris = new(instances.AsArray().Count);
List<Uri> uris = new(((JsonArray) instances).Count);

// ReSharper disable once LoopCanBePartlyConvertedToQuery
foreach (JsonNode? instance in instances.AsArray()) {
foreach (JsonNode? instance in (JsonArray) instances) {
JsonNode? url = instance?["url"];

if (Uri.TryCreate(url?.GetValue<string>() ?? "", UriKind.Absolute, out Uri? instanceUri) && instanceUri.Scheme is "http" or "https") {
Expand Down
84 changes: 84 additions & 0 deletions ASFFreeGames/Utils/Workarounds/AsyncLocal.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
using System;
using System.Reflection;

namespace Maxisoft.ASF.Utils.Workarounds;

public sealed class AsyncLocal<T> {
// ReSharper disable once StaticMemberInGenericType
private static readonly Type? AsyncLocalType;

#pragma warning disable CA1810
static AsyncLocal() {
#pragma warning restore CA1810
try {
AsyncLocalType = Type.GetType("System.Threading.AsyncLocal`1")
?.MakeGenericType(typeof(T));
}
catch (InvalidOperationException) {
// ignore
}

try {
AsyncLocalType ??= Type.GetType("System.Threading.AsyncLocal")
?.MakeGenericType(typeof(T));
}

catch (InvalidOperationException) {
// ignore
}
}

private readonly object? Delegate;
private T? NonSafeValue;

/// <summary>Instantiates an <see cref="AsyncLocal{T}"/> instance that does not receive change notifications.</summary>
public AsyncLocal() {
if (AsyncLocalType is not null) {
try {
Delegate = Activator.CreateInstance(AsyncLocalType)!;
}
catch (Exception) {
// ignored
}
}
}

/// <summary>Gets or sets the value of the ambient data.</summary>
/// <value>The value of the ambient data. If no value has been set, the returned value is default(T).</value>
public T? Value {
get {
if (Delegate is not null) {
try {
PropertyInfo? property = Delegate.GetType().GetProperty("Value");

if (property is not null) {
return (T) property.GetValue(Delegate)!;
}
}
catch (Exception) {
// ignored
}
}

return (T) NonSafeValue!;
}
set {
if (Delegate is not null) {
try {
PropertyInfo? property = Delegate.GetType().GetProperty("Value");

if (property is not null) {
property.SetValue(Delegate, value);

return;
}
}
catch (Exception) {
// ignored
}
}

NonSafeValue = value;
}
}
}
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

<PropertyGroup>
<PluginName>ASFFreeGames</PluginName>
<Version>1.8.0.0</Version>
<Version>1.8.1.0</Version>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>

Expand Down
Loading