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

Enhance configuration options with auto-blacklist, delay, and retry settings #126

Open
wants to merge 7 commits into
base: main
Choose a base branch
from

Conversation

peter9811
Copy link
Contributor

Introduce new configuration options to improve plugin behavior, including automatic blacklisting of forbidden packages, adjustable delays between requests, and retry mechanisms for transient errors.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello @peter9811, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

Summary of Changes

This pull request introduces new configuration options to the ASFFreeGames plugin, enhancing its behavior with automatic blacklisting of forbidden packages, adjustable delays between requests, and retry mechanisms for transient errors. It also adds commands to manage the blacklist via chat.

Highlights

  • Blacklist Management: Adds commands to show, clear, and remove entries from the blacklist via chat interface.
  • Automatic Blacklisting: Introduces an option to automatically blacklist packages that return 'Forbidden' errors.
  • Request Throttling: Adds configuration options for setting delays between requests and retrying failed requests due to transient errors.
  • Error Handling: Improves error handling by retrying transient errors and logging more detailed status messages.

Changelog

Click here to see the changelog
  • ASFFreeGames/Commands/FreeGamesCommand.cs
    • Adds 'SHOWBLACKLIST' command to display the current blacklist.
    • Implements 'CLEARBLACKLIST' command to clear all entries from the blacklist.
    • Introduces 'REMOVEBLACKLIST' command to remove a specific package from the blacklist.
    • Adds a cache to skip packages that have already failed in the current collection run.
    • Implements retry logic for transient errors with configurable delay and max attempts.
    • Adds automatic blacklisting of packages that return 'Forbidden' errors.
    • Adds a delay between requests to avoid rate limits.
    • Logs more detailed status messages for each game collection attempt.
  • ASFFreeGames/Configurations/ASFFreeGamesOptions.cs
    • Adds 'AutoBlacklistForbiddenPackages' option to automatically blacklist forbidden packages.
    • Adds 'DelayBetweenRequests' option to set a delay between license requests.
    • Adds 'MaxRetryAttempts' option to configure the number of retry attempts for transient errors.
    • Adds 'RetryDelayMilliseconds' option to set the delay between retries.
    • Adds methods to add, remove, and clear items from the blacklist.
  • README.md
    • Adds documentation for the new blacklist management commands.
    • Adds documentation for the new configuration options (autoBlacklistForbiddenPackages, delayBetweenRequests, maxRetryAttempts, retryDelayMilliseconds).
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in issue comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist is currently in preview and may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments to provide feedback.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.


Trivia time!

What is the standard delay that Thread.Sleep() uses if a negative timeout is provided?

Click here for the answer
If the value of the timeout argument is other than Timeout.Infinite, and less than or equal to zero, the thread is not suspended. If the value of timeout is Timeout.Infinite, the thread is suspended indefinitely.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces several new configuration options to enhance the plugin's behavior, including automatic blacklisting, adjustable delays, and retry mechanisms. The changes seem well-structured and address potential issues like rate limiting and transient errors. However, there are a few areas that could benefit from further refinement and clarification.

Summary of Findings

  • Inconsistent Blacklist Handling: The blacklist functionality is implemented in both FreeGamesCommand.cs and ASFFreeGamesOptions.cs. Consolidating this logic into a single location could improve maintainability.
  • Potential NullReferenceException: The code uses Options.MaxRetryAttempts ?? 1 and similar constructs. While this provides a default value, it might be better to ensure Options itself is not null before accessing its properties to prevent potential NullReferenceException.
  • Lack of Centralized Configuration Handling: The configuration options are spread across multiple files. Centralizing the configuration logic could improve readability and maintainability.

Merge Readiness

The pull request introduces valuable enhancements to the plugin. However, before merging, it's recommended to address the identified issues, particularly those related to blacklist handling and potential null reference exceptions. I am unable to approve this pull request, and recommend that others review and approve this code before merging.

Comment on lines 134 to 151
case "REMOVEBLACKLIST":
if (args.Length >= 4) {
string identifier = args[3];
if (GameIdentifier.TryParse(identifier, out GameIdentifier gid)) {
bool removed = Options.RemoveFromBlacklist(in gid);
await SaveOptions(cancellationToken).ConfigureAwait(false);

if (removed) {
return FormatBotResponse(bot, $"{ASFFreeGamesPlugin.StaticName} removed {gid} from blacklist");
} else {
return FormatBotResponse(bot, $"{ASFFreeGamesPlugin.StaticName} could not find {gid} in blacklist");
}
} else {
return FormatBotResponse(bot, $"Invalid game identifier format: {identifier}");
}
} else {
return FormatBotResponse(bot, "Please provide a game identifier to remove from blacklist");
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This block handles the REMOVEBLACKLIST command. Consider adding a check to ensure the identifier is not null or empty before attempting to parse it. This can prevent unexpected behavior or errors.

					case "REMOVEBLACKLIST":
						if (args.Length >= 4) {
							string identifier = args[3];
							if (string.IsNullOrEmpty(identifier)) {
								return FormatBotResponse(bot, "Please provide a valid game identifier to remove from blacklist");
							}
							if (GameIdentifier.TryParse(identifier, out GameIdentifier gid)) {

Comment on lines 370 to 371
int retryAttempts = 0;
int maxRetries = Options.MaxRetryAttempts ?? 1;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Consider adding a check to ensure that Options is not null before accessing MaxRetryAttempts. While the null-coalescing operator provides a default value, explicitly checking for null can prevent potential NullReferenceException if Options itself is null.

						int maxRetries = Options?.MaxRetryAttempts ?? 1;

Comment on lines 453 to 470
if (Options.AutoBlacklistForbiddenPackages ?? true) {
Options.AddToBlacklist(in gid);

if (VerboseLog || requestSource is ECollectGameRequestSource.RequestedByUser) {
bot.ArchiLogger.LogGenericInfo($"[FreeGames] Adding {gid} to blacklist due to Forbidden response", nameof(CollectGames));
}

// Save the updated options to persist the blacklist
_ = Task.Run(async () => {
try {
await SaveOptions(cancellationToken).ConfigureAwait(false);
} catch (Exception ex) {
if (VerboseLog || requestSource is ECollectGameRequestSource.RequestedByUser) {
bot.ArchiLogger.LogGenericWarning($"Failed to save options after blacklisting: {ex.Message}", nameof(CollectGames));
}
}
});
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The code saves options within a Task.Run. While this prevents blocking the main thread, it's generally better to use Task.ConfigureAwait(false) to avoid potential deadlocks or context-switching issues. Also, consider using ArchiSteamFarm.Core.ASF.ArchiLogger.LogGenericError instead of bot.ArchiLogger.LogGenericWarning to be consistent with the logging style.

                                                        _ = Task.Run(async () => {
                                                                try {
                                                                        await SaveOptions(cancellationToken).ConfigureAwait(false);
                                                                } catch (Exception ex) {
                                                                        if (VerboseLog || requestSource is ECollectGameRequestSource.RequestedByUser) {
                                                                                ArchiSteamFarm.Core.ASF.ArchiLogger.LogGenericError($"Failed to save options after blacklisting: {ex.Message}");
                                                                        }
                                                                }
                                                        }).ConfigureAwait(false);

Comment on lines 59 to 64
public void AddToBlacklist(in GameIdentifier gid) {
if (Blacklist is HashSet<string> blacklist) {
blacklist.Add(gid.ToString());
} else {
Blacklist = new HashSet<string>(Blacklist) { gid.ToString() };
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The logic for adding to the blacklist checks if Blacklist is a HashSet<string>. If not, it creates a new HashSet<string> from the existing Blacklist. This could be simplified by always using a HashSet<string> for Blacklist and ensuring it's properly initialized.

        public void AddToBlacklist(in GameIdentifier gid) {
            if (Blacklist is not HashSet<string> blacklist) {
                Blacklist = new HashSet<string>(Blacklist);
                blacklist = (HashSet<string>)Blacklist;
            }
            ((HashSet<string>)Blacklist).Add(gid.ToString());
        }

Comment on lines 67 to 77
public bool RemoveFromBlacklist(in GameIdentifier gid) {
if (Blacklist is HashSet<string> blacklist) {
return blacklist.Remove(gid.ToString()) || blacklist.Remove(gid.Id.ToString(CultureInfo.InvariantCulture));
} else {
HashSet<string> newBlacklist = new(Blacklist);
bool removed = newBlacklist.Remove(gid.ToString()) || newBlacklist.Remove(gid.Id.ToString(CultureInfo.InvariantCulture));
if (removed) {
Blacklist = newBlacklist;
}
return removed;
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Similar to AddToBlacklist, the RemoveFromBlacklist method also checks the type of Blacklist and creates a new HashSet<string> if it's not already one. This logic can be simplified by ensuring Blacklist is always a HashSet<string>. Also, consider using StringComparer.InvariantCultureIgnoreCase for case-insensitive comparisons.

        public bool RemoveFromBlacklist(in GameIdentifier gid) {
            if (Blacklist is not HashSet<string> blacklist) {
                Blacklist = new HashSet<string>(Blacklist);
                blacklist = (HashSet<string>)Blacklist;
            }
            return ((HashSet<string>)Blacklist).Remove(gid.ToString());
        }

Comment on lines 80 to 85
public void ClearBlacklist() {
if (Blacklist is HashSet<string> blacklist) {
blacklist.Clear();
} else {
Blacklist = new HashSet<string>();
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The ClearBlacklist method also has similar logic to AddToBlacklist and RemoveFromBlacklist. Consider ensuring Blacklist is always a HashSet<string> to simplify this method.

        public void ClearBlacklist() {
            if (Blacklist is not HashSet<string> blacklist) {
                Blacklist = new HashSet<string>();
            }
            ((HashSet<string>)Blacklist).Clear();
        }

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces several new configuration options to enhance the plugin's behavior, including automatic blacklisting, adjustable delays, and retry mechanisms. The changes generally look good, but there are a few areas where improvements can be made to enhance clarity and maintainability.

Summary of Findings

  • Missing documentation for new configuration options: The new configuration options (autoBlacklistForbiddenPackages, delayBetweenRequests, maxRetryAttempts, retryDelayMilliseconds) are not documented in the README.md file. This makes it difficult for users to understand how to use these new features.
  • Inconsistent use of default values: The AutoBlacklistForbiddenPackages option defaults to true in the ASFFreeGamesOptions class, but this default is not explicitly mentioned in the README.md file. This inconsistency could lead to confusion.
  • Potential for excessive retries: The CollectGames method includes retry logic with configurable delays. While this is beneficial for transient errors, it's important to ensure that the retry mechanism doesn't lead to excessive delays or resource consumption in cases where errors are persistent.

Merge Readiness

The pull request is almost ready for merging. However, I recommend addressing the missing documentation and considering the potential for excessive retries before merging. I am unable to approve this pull request, and others should review and approve this code before merging.

Comment on lines 474 to 483
_ = Task.Run(async () => {
try {
await SaveOptions(cancellationToken).ConfigureAwait(false);
}
catch (Exception ex) {
if (VerboseLog || requestSource is ECollectGameRequestSource.RequestedByUser) {
ArchiSteamFarm.Core.ASF.ArchiLogger.LogGenericError($"Failed to save options after blacklisting: {ex.Message}");
}
}
}).ConfigureAwait(false);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Consider using ArchiSteamFarm.Core.ASF.ArchiLogger.LogGenericError directly instead of wrapping it in a Task.Run. This simplifies the code and avoids potential issues with task scheduling.

try {
									await SaveOptions(cancellationToken).ConfigureAwait(false);
								}
								catch (Exception ex) {
									if (VerboseLog || requestSource is ECollectGameRequestSource.RequestedByUser) {
										ArchiSteamFarm.Core.ASF.ArchiLogger.LogGenericError($"Failed to save options after blacklisting: {ex.Message}");
									}
								}

Copy link

It looks like you're trying to invoke Gemini Code Assist with the /gemini-code-assist command. However, the correct way to invoke Gemini is by using either /gemini <command> or @gemini-code-assist <command>. If you want a list of available commands, you can use /gemini help.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

The pull request introduces new configuration options to enhance the plugin's behavior, including automatic blacklisting, adjustable delays, and retry mechanisms. The changes touch various files, mainly focusing on workflow configurations and core plugin logic. Overall, the changes seem well-structured and aim to improve the plugin's robustness and user experience.

Summary of Findings

  • Missing Documentation for New Configuration Options: The pull request introduces new configuration options such as autoBlacklistForbiddenPackages, delayBetweenRequests, maxRetryAttempts, and retryDelayMilliseconds. However, there is no documentation in the README or elsewhere explaining these options to the user. This could lead to confusion and make it difficult for users to effectively utilize the new features.
  • Potential Infinite Retry Loop: The retry mechanism introduced in ASFFreeGames/Commands/FreeGamesCommand.cs might lead to an infinite loop if isTransientError remains true indefinitely. While a maxRetries limit is in place, it's crucial to ensure that the conditions for transient errors are correctly evaluated to prevent the plugin from getting stuck in a retry loop.
  • Lack of Input Validation for Configuration Options: The new configuration options such as delayBetweenRequests and maxRetryAttempts lack input validation. This could lead to unexpected behavior or errors if users provide invalid values (e.g., negative numbers, excessively large values). Adding validation would improve the robustness of the plugin.

Merge Readiness

The pull request introduces valuable enhancements to the plugin. However, the missing documentation and potential for an infinite retry loop need to be addressed before merging. I am unable to directly approve this pull request, and recommend that others review and approve this code before merging. At a minimum, the high severity issue should be addressed before merging.

Comment on lines +566 to +570
int maxRetries = Options?.MaxRetryAttempts ?? 1;

bool isTransientError = false;
int retryAttempts = 0;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Consider adding a check to ensure that maxRetries is not zero or negative to prevent potential issues.

int maxRetries = (Options?.MaxRetryAttempts > 0) ? Options.MaxRetryAttempts.Value : 1;

save |= context.RegisterInvalidApp(in gid);
}
// Add a delay between requests to avoid hitting rate limits
int delay = Options.DelayBetweenRequests ?? 500;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Consider adding a check to ensure that delay is not negative to prevent potential issues.

int delay = (Options.DelayBetweenRequests > 0) ? Options.DelayBetweenRequests.Value : 500;

Comment on lines +623 to +640
// Check if this is a transient error that should be retried
isTransientError =
!success
&& (
resp.Contains(
"timeout",
StringComparison.InvariantCultureIgnoreCase
)
|| resp.Contains(
"connection error",
StringComparison.InvariantCultureIgnoreCase
)
|| resp.Contains(
"service unavailable",
StringComparison.InvariantCultureIgnoreCase
)
);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

It's good to check for transient errors, but consider adding a condition to prevent infinite retries if the error persists even after multiple attempts. Perhaps a check for a specific error code or a maximum number of consecutive failures could be implemented.

isTransientError = !success && (resp.Contains("timeout", StringComparison.InvariantCultureIgnoreCase) || resp.Contains("connection error", StringComparison.InvariantCultureIgnoreCase) || resp.Contains("service unavailable", StringComparison.InvariantCultureIgnoreCase)) && (retryAttempts < maxRetries);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant