Skip to content

Commit decafba

Browse files
committed
feat: setup http resiliency
1 parent 6a09f1e commit decafba

File tree

3 files changed

+54
-27
lines changed

3 files changed

+54
-27
lines changed

Crimson/App.xaml.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88
using H.NotifyIcon;
99
using Microsoft.Extensions.DependencyInjection;
1010
using Microsoft.Extensions.Hosting;
11+
using Microsoft.Extensions.Http.Resilience;
1112
using Microsoft.UI.Xaml;
13+
using Polly;
1214
using Serilog;
1315

1416
namespace Crimson
@@ -66,6 +68,24 @@ public App()
6668
services.AddSingleton<LibraryManager>();
6769
services.AddSingleton<InstallManager>();
6870

71+
services.AddHttpClient<IStoreRepository, EpicGamesRepository>().AddResilienceHandler(
72+
"CustomPipeline",
73+
static builder =>
74+
{
75+
// See: https://www.pollydocs.org/strategies/retry.html
76+
builder.AddRetry(new HttpRetryStrategyOptions
77+
{
78+
// Customize and configure the retry logic.
79+
BackoffType = DelayBackoffType.Exponential,
80+
MaxRetryAttempts = 5,
81+
UseJitter = true
82+
});
83+
84+
85+
// See: https://www.pollydocs.org/strategies/timeout.html
86+
builder.AddTimeout(TimeSpan.FromSeconds(5));
87+
});
88+
6989
services.AddTransient<SettingsViewModel>();
7090
}).
7191
Build();

Crimson/Crimson.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
<PackageReference Include="H.NotifyIcon.WinUI" Version="2.1.3" />
3232
<PackageReference Include="Ionic.Zlib.Core" Version="1.0.0" />
3333
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
34+
<PackageReference Include="Microsoft.Extensions.Http.Resilience" Version="8.10.0" />
3435
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.6.240923002" />
3536
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.1742" />
3637
<PackageReference Include="Serilog" Version="4.0.2" />

Crimson/Repository/EpicGamesRepository.cs

Lines changed: 33 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -19,34 +19,32 @@ internal class EpicGamesRepository : IStoreRepository
1919
private const string OAuthHost = "account-public-service-prod03.ol.epicgames.com";
2020
private const string UserAgent = "UELauncher/11.0.1-14907503+++Portal+Release-Live Windows/10.0.19041.1.256.64bit";
2121

22-
private static readonly HttpClient HttpClient;
22+
private readonly HttpClient _httpClient;
2323
private readonly ILogger _log;
2424
private readonly AuthManager _authManager;
2525

26-
static EpicGamesRepository()
27-
{
28-
HttpClient = new HttpClient();
29-
HttpClient.DefaultRequestHeaders.Add("User-Agent", UserAgent);
30-
}
31-
public EpicGamesRepository(AuthManager authManager, ILogger logger)
26+
public EpicGamesRepository(AuthManager authManager, ILogger logger, HttpClient httpClient)
3227
{
3328
_log = logger;
3429
_authManager = authManager;
30+
_httpClient = httpClient;
31+
_httpClient.DefaultRequestHeaders.Add("User-Agent", UserAgent);
3532
}
33+
3634
public async Task<Metadata> FetchGameMetaData(string nameSpace, string catalogItemId)
3735
{
3836

3937
_log.Information("FetchGameMetaData: Fetching game metadata");
4038
var accessToken = await _authManager.GetAccessToken();
41-
HttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
39+
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
4240

4341
// API requests parameters to be in query instead of body
4442
var qs = $"?id={catalogItemId}&includeDLCDetails=true&includeMainGameDetails=true&country=US&locale=en";
4543

4644
try
4745
{
4846
// Make the API call with the form data
49-
var httpResponse = await HttpClient.GetAsync($"https://{CatalogHost}/catalog/api/shared/namespace/{nameSpace}/bulk/items{qs}");
47+
var httpResponse = await _httpClient.GetAsync($"https://{CatalogHost}/catalog/api/shared/namespace/{nameSpace}/bulk/items{qs}");
5048
// Check if the request was successful (status code 200)
5149
if (httpResponse.IsSuccessStatusCode)
5250
{
@@ -85,8 +83,8 @@ public async Task<IEnumerable<Asset>> FetchGameAssets(string platform = "Windows
8583
_log.Information("FetchGameAssets: Fetching game assets");
8684
var accessToken = await _authManager.GetAccessToken();
8785

88-
HttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
89-
var httpResponse = await HttpClient.GetAsync($"https://{LauncherHost}/launcher/api/public/assets/{platform}?label={label}");
86+
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
87+
var httpResponse = await _httpClient.GetAsync($"https://{LauncherHost}/launcher/api/public/assets/{platform}?label={label}");
9088

9189
if (httpResponse.IsSuccessStatusCode)
9290
{
@@ -130,7 +128,7 @@ public async Task<GetGameManifest> GetGameManifest(string nameSpace, string cata
130128

131129
try
132130
{
133-
var httpResponse = await HttpClient.GetAsync(url);
131+
var httpResponse = await _httpClient.GetAsync(url);
134132
if (!httpResponse.IsSuccessStatusCode)
135133
{
136134
_log.Error($"Failed to fetch manifests from {url}, trying next url");
@@ -159,30 +157,38 @@ public async Task<GetGameManifest> GetGameManifest(string nameSpace, string cata
159157

160158
public async Task DownloadFileAsync(string url, string destinationPath)
161159
{
162-
var accessToken = await _authManager.GetAccessToken();
163-
//HttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
160+
try
161+
{
162+
var accessToken = await _authManager.GetAccessToken();
163+
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
164164

165-
using var response = await HttpClient.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
166-
response.EnsureSuccessStatusCode();
165+
using var response = await _httpClient.GetAsync(url);
166+
if (!response.IsSuccessStatusCode)
167+
throw new Exception($"File {url} failed to download");
167168

168-
// Create the directory if it doesn't exist
169-
var directoryPath = Path.GetDirectoryName(destinationPath);
170-
if (!string.IsNullOrEmpty(directoryPath))
169+
// Create the directory if it doesn't exist
170+
var directoryPath = Path.GetDirectoryName(destinationPath);
171+
if (!string.IsNullOrEmpty(directoryPath))
172+
{
173+
Directory.CreateDirectory(directoryPath);
174+
}
175+
await using var stream = await response.Content.ReadAsStreamAsync();
176+
await using var fileStream = new FileStream(destinationPath, FileMode.Create, FileAccess.Write, FileShare.None);
177+
await stream.CopyToAsync(fileStream);
178+
}
179+
catch (Exception ex)
171180
{
172-
Directory.CreateDirectory(directoryPath);
181+
_log.Error("Failed to download file}", url);
173182
}
174-
await using var stream = await response.Content.ReadAsStreamAsync();
175-
await using var fileStream = new FileStream(destinationPath, FileMode.Create, FileAccess.Write, FileShare.None);
176-
await stream.CopyToAsync(fileStream);
177183
}
178184

179185
public async Task<string> GetGameToken()
180186
{
181187
try
182188
{
183189
var accessToken = await _authManager.GetAccessToken();
184-
HttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
185-
using var response = await HttpClient.GetAsync($"https://{OAuthHost}/account/api/oauth/exchange");
190+
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
191+
using var response = await _httpClient.GetAsync($"https://{OAuthHost}/account/api/oauth/exchange");
186192
response.EnsureSuccessStatusCode();
187193

188194
await using var stream = await response.Content.ReadAsStreamAsync();
@@ -204,8 +210,8 @@ private async Task<GetManifestUrlData> GetManifestUrls(string nameSpace, string
204210
_log.Information("GetGameManifest: Fetching game assets");
205211
var accessToken = await _authManager.GetAccessToken();
206212

207-
HttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
208-
var httpResponse = await HttpClient.GetAsync($"https://{LauncherHost}/launcher/api/public/assets/v2/platform/{platform}/namespace/{nameSpace}/catalogItem/{catalogItem}/app/{appName}/label/{label}");
213+
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
214+
var httpResponse = await _httpClient.GetAsync($"https://{LauncherHost}/launcher/api/public/assets/v2/platform/{platform}/namespace/{nameSpace}/catalogItem/{catalogItem}/app/{appName}/label/{label}");
209215

210216
if (httpResponse.IsSuccessStatusCode)
211217
{

0 commit comments

Comments
 (0)