osu/osu.Game/Online/API/APIRequest.cs

173 lines
5.2 KiB
C#
Raw Normal View History

// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
2018-04-13 09:19:50 +00:00
using System;
2019-11-12 10:34:20 +00:00
using Newtonsoft.Json;
2018-04-13 09:19:50 +00:00
using osu.Framework.IO.Network;
using osu.Framework.Logging;
2018-04-13 09:19:50 +00:00
namespace osu.Game.Online.API
{
/// <summary>
/// An API request with a well-defined response type.
/// </summary>
/// <typeparam name="T">Type of the response (used for deserialisation).</typeparam>
public abstract class APIRequest<T> : APIRequest
{
2019-12-28 15:07:55 +00:00
protected override WebRequest CreateWebRequest() => new OsuJsonWebRequest<T>(Uri);
2018-04-13 09:19:50 +00:00
public T Result => ((OsuJsonWebRequest<T>)WebRequest).ResponseObject;
2018-04-13 09:19:50 +00:00
protected APIRequest()
{
base.Success += onSuccess;
}
private void onSuccess() => Success?.Invoke(Result);
2018-04-13 09:19:50 +00:00
2018-06-08 05:37:27 +00:00
/// <summary>
/// Invoked on successful completion of an API request.
/// This will be scheduled to the API's internal scheduler (run on update thread automatically).
/// </summary>
2018-04-13 09:19:50 +00:00
public new event APISuccessHandler<T> Success;
}
/// <summary>
/// AN API request with no specified response type.
/// </summary>
public abstract class APIRequest
{
protected abstract string Target { get; }
2018-04-13 09:19:50 +00:00
2019-12-28 15:07:55 +00:00
protected virtual WebRequest CreateWebRequest() => new OsuWebRequest(Uri);
2018-04-13 09:19:50 +00:00
protected virtual string Uri => $@"{API.Endpoint}/api/v2/{Target}";
protected APIAccess API;
protected WebRequest WebRequest;
2018-06-08 05:37:27 +00:00
/// <summary>
/// Invoked on successful completion of an API request.
/// This will be scheduled to the API's internal scheduler (run on update thread automatically).
/// </summary>
2018-04-13 09:19:50 +00:00
public event APISuccessHandler Success;
2018-06-08 05:37:27 +00:00
/// <summary>
/// Invoked on failure to complete an API request.
/// This will be scheduled to the API's internal scheduler (run on update thread automatically).
/// </summary>
2018-04-13 09:19:50 +00:00
public event APIFailureHandler Failure;
private bool cancelled;
private Action pendingFailure;
public void Perform(IAPIProvider api)
2018-04-13 09:19:50 +00:00
{
if (!(api is APIAccess apiAccess))
2019-09-25 06:00:08 +00:00
{
Fail(new NotSupportedException($"A {nameof(APIAccess)} is required to perform requests."));
return;
}
API = apiAccess;
2018-04-13 09:19:50 +00:00
if (checkAndScheduleFailure())
2018-04-13 09:19:50 +00:00
return;
WebRequest = CreateWebRequest();
WebRequest.Failed += Fail;
2018-04-13 09:19:50 +00:00
WebRequest.AllowRetryOnTimeout = false;
WebRequest.AddHeader("Authorization", $"Bearer {API.AccessToken}");
2018-04-13 09:19:50 +00:00
if (checkAndScheduleFailure())
2018-04-13 09:19:50 +00:00
return;
if (!WebRequest.Aborted) //could have been aborted by a Cancel() call
{
Logger.Log($@"Performing request {this}", LoggingTarget.Network);
2018-04-13 09:19:50 +00:00
WebRequest.Perform();
}
2018-04-13 09:19:50 +00:00
if (checkAndScheduleFailure())
2018-04-13 09:19:50 +00:00
return;
API.Schedule(delegate
{
if (cancelled) return;
Success?.Invoke();
});
2018-04-13 09:19:50 +00:00
}
public void Cancel() => Fail(new OperationCanceledException(@"Request cancelled"));
public void Fail(Exception e)
{
if (WebRequest?.Completed == true)
return;
if (cancelled)
return;
2018-04-13 09:19:50 +00:00
cancelled = true;
2018-04-13 09:19:50 +00:00
WebRequest?.Abort();
2019-11-21 01:55:31 +00:00
string responseString = WebRequest?.GetResponseString();
2019-11-12 10:34:20 +00:00
if (!string.IsNullOrEmpty(responseString))
{
try
{
// attempt to decode a displayable error string.
var error = JsonConvert.DeserializeObject<DisplayableError>(responseString);
if (error != null)
2019-11-28 13:52:05 +00:00
e = new APIException(error.ErrorMessage, e);
2019-11-12 10:34:20 +00:00
}
catch
{
}
}
Logger.Log($@"Failing request {this} ({e})", LoggingTarget.Network);
2018-04-13 09:19:50 +00:00
pendingFailure = () => Failure?.Invoke(e);
checkAndScheduleFailure();
2018-04-13 09:19:50 +00:00
}
/// <summary>
/// Checked for cancellation or error. Also queues up the Failed event if we can.
/// </summary>
/// <returns>Whether we are in a failed or cancelled state.</returns>
private bool checkAndScheduleFailure()
2018-04-13 09:19:50 +00:00
{
if (API == null || pendingFailure == null) return cancelled;
API.Schedule(pendingFailure);
pendingFailure = null;
return true;
}
2019-11-12 10:34:20 +00:00
private class DisplayableError
{
[JsonProperty("error")]
2019-11-25 02:30:55 +00:00
public string ErrorMessage { get; set; }
2019-11-12 10:34:20 +00:00
}
2018-04-13 09:19:50 +00:00
}
2019-11-28 13:52:05 +00:00
public class APIException : InvalidOperationException
{
public APIException(string messsage, Exception innerException)
: base(messsage, innerException)
{
}
}
2018-04-13 09:19:50 +00:00
public delegate void APIFailureHandler(Exception e);
2019-02-28 04:31:40 +00:00
2018-04-13 09:19:50 +00:00
public delegate void APISuccessHandler();
2019-02-28 04:31:40 +00:00
2018-04-13 09:19:50 +00:00
public delegate void APIProgressHandler(long current, long total);
2019-02-28 04:31:40 +00:00
2018-04-13 09:19:50 +00:00
public delegate void APISuccessHandler<in T>(T content);
}