using System; using System.Collections.Generic; using System.Net.Http; using System.Net.Http.Headers; using System.Threading; using System.Threading.Tasks; using CompatApiClient; using Microsoft.Extensions.Caching.Memory; using Octokit; using ProductHeaderValue = Octokit.ProductHeaderValue; namespace GithubClient; public partial class Client { private readonly GitHubClient client = new(new ProductHeaderValue(ApiConfig.ProductName, ApiConfig.ProductVersion)); private const string OwnerId = "RPCS3"; private const string RepoId = "rpcs3"; private static readonly TimeSpan PrStatusCacheTime = TimeSpan.FromMinutes(3); private static readonly TimeSpan IssueStatusCacheTime = TimeSpan.FromMinutes(30); private static readonly MemoryCache StatusesCache = new(new MemoryCacheOptions { ExpirationScanFrequency = TimeSpan.FromMinutes(1) }); private static readonly MemoryCache IssuesCache = new(new MemoryCacheOptions { ExpirationScanFrequency = TimeSpan.FromMinutes(30) }); public static int RateLimit { get; private set; } public static int RateLimitRemaining { get; private set; } public static DateTime RateLimitResetTime { get; private set; } public Client(string? githubToken) { if (githubToken is {Length: >0}) client.Credentials = new(githubToken); } public async ValueTask GetPrInfoAsync(int pr, CancellationToken cancellationToken) { if (StatusesCache.TryGetValue(pr, out PullRequest? result)) { ApiConfig.Log.Debug($"Returned {nameof(PullRequest)} for {pr} from cache"); return result; } try { result = await client.PullRequest.Get(OwnerId, RepoId, pr).WaitAsync(cancellationToken).ConfigureAwait(false); UpdateRateLimitStats(); } catch (Exception e) { ApiConfig.Log.Error(e); } if (result == null) { ApiConfig.Log.Debug($"Failed to get {nameof(PullRequest)}, returning empty result"); return new(pr); } StatusesCache.Set(pr, result, PrStatusCacheTime); ApiConfig.Log.Debug($"Cached {nameof(PullRequest)} for {pr} for {PrStatusCacheTime}"); return result; } public async ValueTask GetIssueInfoAsync(int issue, CancellationToken cancellationToken) { if (IssuesCache.TryGetValue(issue, out Issue? result)) { ApiConfig.Log.Debug($"Returned {nameof(Issue)} for {issue} from cache"); return result; } try { result = await client.Issue.Get(OwnerId, RepoId, issue).WaitAsync(cancellationToken).ConfigureAwait(false); UpdateRateLimitStats(); IssuesCache.Set(issue, result, IssueStatusCacheTime); ApiConfig.Log.Debug($"Cached {nameof(Issue)} for {issue} for {IssueStatusCacheTime}"); return result; } catch (Exception e) { ApiConfig.Log.Error(e); } ApiConfig.Log.Debug($"Failed to get {nameof(Issue)}, returning empty result"); return new(); } public ValueTask?> GetOpenPrsAsync(CancellationToken cancellationToken) => GetPrsWithStatusAsync(new() { State = ItemStateFilter.Open }, cancellationToken); public ValueTask?> GetClosedPrsAsync(CancellationToken cancellationToken) => GetPrsWithStatusAsync(new() { State = ItemStateFilter.Closed, SortProperty = PullRequestSort.Updated, SortDirection = SortDirection.Descending }, cancellationToken); private async ValueTask?> GetPrsWithStatusAsync(PullRequestRequest filter, CancellationToken cancellationToken) { var statusUri = "https://api.github.com/repos/RPCS3/rpcs3/pulls?state=" + filter; if (StatusesCache.TryGetValue(statusUri, out IReadOnlyList? result)) { ApiConfig.Log.Debug("Returned list of opened PRs from cache"); return result; } try { result = await client.PullRequest.GetAllForRepository(OwnerId, RepoId, filter).WaitAsync(cancellationToken).ConfigureAwait(false); UpdateRateLimitStats(); StatusesCache.Set(statusUri, result, PrStatusCacheTime); foreach (var prInfo in result) StatusesCache.Set(prInfo.Number, prInfo, PrStatusCacheTime); ApiConfig.Log.Debug($"Cached list of open PRs for {PrStatusCacheTime}"); } catch (Exception e) { ApiConfig.Log.Error(e); } return result; } private void UpdateRateLimitStats() { var apiInfo = client.GetLastApiInfo(); if (apiInfo is null) return; RateLimit = apiInfo.RateLimit.Limit; RateLimitRemaining = apiInfo.RateLimit.Remaining; RateLimitResetTime = DateTimeOffset.FromUnixTimeSeconds(apiInfo.RateLimit.ResetAsUtcEpochSeconds).UtcDateTime; if (RateLimitRemaining < 10) ApiConfig.Log.Warn($"Github rate limit is low: {RateLimitRemaining} out of {RateLimit}, will be reset on {RateLimitResetTime:u}"); } }