Skip to content

Commit

Permalink
consolidate http request handling, and use api urls fresh every time
Browse files Browse the repository at this point in the history
api urls were loaded into each manager's httpclient in the constructors. this resulted in tweaks to the urls not actually changing which url was used, until the plugin is reloaded. this also introduced a problem where if the url is invalid, then the plugin will fail to load outright, and the user will have to manually edit save data to fix the url. so obviously both of those things suck XD
  • Loading branch information
dit-zy committed Aug 15, 2024
1 parent b19c7bf commit 90cdb5c
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 97 deletions.
77 changes: 26 additions & 51 deletions ScoutHelper/Managers/BearManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,43 +4,34 @@
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using CSharpFunctionalExtensions;
using Dalamud.Plugin.Services;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using ScoutHelper.Config;
using ScoutHelper.Models;
using ScoutHelper.Models.Http;
using ScoutHelper.Utils;

namespace ScoutHelper.Managers;

public class BearManager : IDisposable {
private readonly IPluginLog _log;
private readonly Configuration _conf;

private static readonly JsonSerializerSettings JsonSerializerSettings = new JsonSerializerSettings() {
DateFormatString = "yyyy'-'MM'-'dd'T'HH':'mm':'ssK",
DateTimeZoneHandling = DateTimeZoneHandling.Utc
};

private static HttpClient HttpClient { get; } = new();
private readonly HttpClient _httpClient = new();

private IDictionary<uint, (Patch patch, string name)> MobIdToBearName { get; init; }

public BearManager(IPluginLog log, Configuration conf, ScoutHelperOptions options) {
_log = log;
_conf = conf;

HttpClient.BaseAddress = new Uri(_conf.BearApiBaseUrl);
HttpClient.DefaultRequestHeaders.UserAgent.Add(Constants.UserAgent);
HttpClient.Timeout = _conf.BearApiTimeout;

MobIdToBearName = LoadData(options.BearDataFile);
}

public void Dispose() {
HttpClient.Dispose();
_httpClient.Dispose();

GC.SuppressFinalize(this);
}
Expand All @@ -51,52 +42,36 @@ IEnumerable<TrainMob> trainMobs
) {
var bearSupportedMobs = trainMobs.Where(mob => MobIdToBearName.ContainsKey(mob.MobId)).ToList();
if (bearSupportedMobs.Count == 0)
return "No mobs supported by Bear Toolkit were found in the Hunt Helper train recorder ;-;";
return "no mobs supported by bear toolkit were found in the hunt helper train recorder ;-;";

var spawnPoints = bearSupportedMobs.Select(CreateRequestSpawnPoint).ToList();
var highestPatch = bearSupportedMobs
.Select(mob => MobIdToBearName[mob.MobId].patch)
.Distinct()
.Max();

var requestPayload = JsonConvert.SerializeObject(
new BearApiTrainRequest(worldName, _conf.BearTrainName, highestPatch.BearName(), spawnPoints),
JsonSerializerSettings
);
_log.Debug("Request payload: {0:l}", requestPayload);
var requestContent = new StringContent(requestPayload, Encoding.UTF8, Constants.MediaTypeJson);

try {
var response = await HttpClient.PostAsync(_conf.BearApiTrainPath, requestContent);
_log.Debug(
"Request: {0}\n\nResponse: {1}",
response.RequestMessage!.ToString(),
response.ToString()
return await HttpUtils.DoRequest<BearApiTrainRequest, BearApiTrainResponse, BearLinkData>(
_log,
_httpClient,
_conf.BearApiBaseUrl,
new BearApiTrainRequest(worldName, _conf.BearTrainName, highestPatch.BearName(), spawnPoints),
(client, content) => {
client.Timeout = _conf.BearApiTimeout;
return client.PostAsync(_conf.BearApiTrainPath, content);
},
bearResponse => new BearLinkData(
$"{_conf.BearSiteTrainUrl}/{bearResponse.Trains.First().TrainId}",
bearResponse.Trains.First().Password,
highestPatch
)
)
.HandleHttpError(
_log,
"timed out posting the train to bear ;-;",
"generating the bear link was canceled >_>",
"something failed when communicating with bear :T",
"an unknown error happened while generating the bear link D:"
);

response.EnsureSuccessStatusCode();

var responseJson = await response.Content.ReadAsStringAsync();
var trainInfo = JsonConvert.DeserializeObject<BearApiTrainResponse>(responseJson).Trains.First();

var url = $"{_conf.BearSiteTrainUrl}/{trainInfo.TrainId}";
return new BearLinkData(url, trainInfo.Password, highestPatch);
} catch (TimeoutException) {
const string message = "Timed out posting the train to Bear ;-;";
_log.Error(message);
return message;
} catch (OperationCanceledException e) {
const string message = "Generating the Bear link was canceled >_>";
_log.Warning(e, message);
return message;
} catch (HttpRequestException e) {
_log.Error(e, "Posting the train to Bear failed.");
return "Something failed when communicating with Bear :T";
} catch (Exception e) {
const string message = "An unknown error happened while generating the Bear link D:";
_log.Error(e, message);
return message;
}
}

private BearApiSpawnPoint CreateRequestSpawnPoint(TrainMob mob) {
Expand Down
71 changes: 32 additions & 39 deletions ScoutHelper/Managers/TurtleManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,14 @@ namespace ScoutHelper.Managers;
using MobDict = IDictionary<uint, (Patch patch, uint turtleMobId)>;
using TerritoryDict = IDictionary<uint, TurtleMapData>;

public partial class TurtleManager {
public partial class TurtleManager : IDisposable {
[GeneratedRegex(@"(?:/scout)?/?(?<session>\w+)/(?<password>\w+)/?\s*$")]
private static partial Regex CollabLinkRegex();
private static HttpClient HttpClient { get; } = new();

private readonly IPluginLog _log;
private readonly Configuration _conf;
private readonly IClientState _clientState;
private readonly HttpClient _httpClient = new();

private MobDict MobIdToTurtleId { get; }
private TerritoryDict TerritoryIdToTurtleData { get; }
Expand All @@ -52,14 +52,16 @@ MobManager mobManager
_conf = conf;
_clientState = clientState;

HttpClient.BaseAddress = new Uri(_conf.TurtleApiBaseUrl);
HttpClient.DefaultRequestHeaders.UserAgent.Add(Constants.UserAgent);
HttpClient.Timeout = _conf.TurtleApiTimeout;

(MobIdToTurtleId, TerritoryIdToTurtleData)
= LoadData(options.TurtleDataFile, territoryManager, mobManager);
}

public void Dispose() {
_httpClient.Dispose();

GC.SuppressFinalize(this);
}

public Maybe<(string slug, string password)> JoinCollabSession(string sessionLink) {
var match = CollabLinkRegex().Match(sessionLink);
if (!match.Success) return Maybe.None;
Expand All @@ -86,6 +88,8 @@ public async Task<TurtleHttpStatus> UpdateCurrentSession(IList<TrainMob> train)
var httpResult = await
HttpUtils.DoRequest(
_log,
_httpClient,
_conf.TurtleApiBaseUrl,
new TurtleTrainUpdateRequest(
_currentCollabPassword,
_clientState.PlayerTag().Where(_ => _conf.IncludeNameInTurtleSession),
Expand All @@ -97,7 +101,10 @@ public async Task<TurtleHttpStatus> UpdateCurrentSession(IList<TrainMob> train)
mob.Position)
)
),
requestContent => HttpClient.PatchAsync($"{_conf.TurtleApiTrainPath}/{_currentCollabSession}", requestContent)
(client, content) => {
client.Timeout = _conf.TurtleApiTimeout;
return client.PatchAsync($"{_conf.TurtleApiTrainPath}/{_currentCollabSession}", content);
}
).TapError(
error => {
if (error.ErrorType == HttpErrorType.Timeout) {
Expand Down Expand Up @@ -130,37 +137,23 @@ public async Task<Result<TurtleLinkData, string>> GenerateTurtleLink(
.Select(mob => MobIdToTurtleId[mob.MobId].patch)
.Max();

var trainResult = await HttpUtils.DoRequest<TurtleTrainRequest, TurtleTrainResponse>(
_log,
TurtleTrainRequest.CreateRequest(spawnPoints),
requestContent => HttpClient.PostAsync(_conf.TurtleApiTrainPath, requestContent)
);

return trainResult
.Map(trainInfo => TurtleLinkData.From(trainInfo, highestPatch))
.MapError<TurtleLinkData, HttpError, string>(
error => {
string message;
switch (error.ErrorType) {
case HttpErrorType.Timeout: {
message = "Timed out posting the train to Turtle ;-;";
_log.Error(message);
return message;
}
case HttpErrorType.Canceled: {
message = "Generating the Turtle link was canceled >_>";
_log.Warning(message);
return message;
}
case HttpErrorType.HttpException:
_log.Error(error.Exception, "Posting the train to Turtle failed.");
return "Something failed when communicating with Turtle :T";
default:
message = "An unknown error happened while generating the Turtle link D:";
_log.Error(error.Exception, message);
return message;
}
}
return await HttpUtils.DoRequest<TurtleTrainRequest, TurtleTrainResponse, TurtleLinkData>(
_log,
_httpClient,
_conf.TurtleApiBaseUrl,
TurtleTrainRequest.CreateRequest(spawnPoints),
(client, content) => {
client.Timeout = _conf.TurtleApiTimeout;
return client.PostAsync(_conf.TurtleApiTrainPath, content);
},
trainResponse => TurtleLinkData.From(trainResponse, highestPatch)
)
.HandleHttpError(
_log,
"timed out posting the train to turtle ;-;",
"generating the turtle link was canceled >_>",
"something failed when communicating with turtle :T",
"an unknown error happened while generating the turtle link D:"
);
}

Expand Down Expand Up @@ -292,6 +285,6 @@ public static TurtleLinkData From(TurtleTrainResponse response, Patch highestPat

public static class TurtleExtensions {
public static uint AsTurtleInstance(this uint? instance) {
return instance is null or 0 ? 1 : (uint)instance!;
return instance is null or 0 ? 1 : (uint)instance;
}
}
33 changes: 33 additions & 0 deletions ScoutHelper/Models/Http/HttpError.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using System;
using System.Threading.Tasks;
using CSharpFunctionalExtensions;
using Dalamud.Plugin.Services;

namespace ScoutHelper.Models.Http;

Expand All @@ -16,3 +18,34 @@ public enum HttpErrorType {
Canceled,
HttpException,
}

public static class HttpErrorExtensions {
public static Task<Result<T, string>> HandleHttpError<T>(
this Task<Result<T, HttpError>> apiOperation,
IPluginLog log,
string timeoutMsg,
string cancelMsg,
string httpExceptionMsg,
string unknownErrorMsg
) =>
apiOperation.MapError<T, HttpError, string>(
error => {
switch (error.ErrorType) {
case HttpErrorType.Timeout: {
log.Error(timeoutMsg);
return timeoutMsg;
}
case HttpErrorType.Canceled: {
log.Warning(cancelMsg);
return cancelMsg;
}
case HttpErrorType.HttpException:
log.Error(error.Exception, httpExceptionMsg);
return httpExceptionMsg;
default:
log.Error(error.Exception, unknownErrorMsg);
return unknownErrorMsg;
}
}
);
}
22 changes: 15 additions & 7 deletions ScoutHelper/Utils/HttpUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,32 +16,40 @@ public static class HttpUtils {
NullValueHandling = NullValueHandling.Ignore,
};

public static Task<Result<U, HttpError>> DoRequest<T, U>(
public static Task<Result<R, HttpError>> DoRequest<T, U, R>(
IPluginLog log,
HttpClient client,
string baseUrl,
T requestObject,
Func<HttpContent, Task<HttpResponseMessage>> requestAction
Func<HttpClient, HttpContent, Task<HttpResponseMessage>> requestAction,
Func<U, R> responseTransform
) =>
DoRequest(log, requestObject, requestAction)
DoRequest(log, client, baseUrl, requestObject, requestAction)
.Then(
result => result.Bind<string, U, HttpError>(
result => result.Bind<string, R, HttpError>(
responseJson => Utils.Try(
() => JsonConvert.DeserializeObject<U>(responseJson)!,
() => responseTransform(JsonConvert.DeserializeObject<U>(responseJson)!),
e => new HttpError(HttpErrorType.Unknown, e)
)
)
);

public static async Task<Result<string, HttpError>> DoRequest<T>(
IPluginLog log,
HttpClient client,
string baseUrl,
T requestObject,
Func<HttpContent, Task<HttpResponseMessage>> requestAction
Func<HttpClient, HttpContent, Task<HttpResponseMessage>> requestAction
) {
try {
var requestPayload = JsonConvert.SerializeObject(requestObject, JsonSerializerSettings);
log.Debug("Request body: {0:l}", requestPayload);
var requestContent = new StringContent(requestPayload, Encoding.UTF8, Constants.MediaTypeJson);

var response = await requestAction(requestContent);
client.BaseAddress = new Uri(baseUrl);
client.DefaultRequestHeaders.UserAgent.Add(Constants.UserAgent);
client.DefaultRequestHeaders.Accept.Add(Constants.MediaTypeJson);
var response = await requestAction(client, requestContent);
log.Debug(
"Request: {0:l}\n\nResponse: {1:l}",
response.RequestMessage!.ToString(),
Expand Down

0 comments on commit 90cdb5c

Please sign in to comment.