2019-01-24 16:43:03 +08:00
|
|
|
|
// 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 17:19:50 +08:00
|
|
|
|
|
2022-06-17 15:37:17 +08:00
|
|
|
|
#nullable disable
|
|
|
|
|
|
2016-08-31 18:49:34 +08:00
|
|
|
|
using System;
|
2016-11-30 14:15:07 +08:00
|
|
|
|
using System.Collections.Generic;
|
2016-11-30 15:54:15 +08:00
|
|
|
|
using System.Diagnostics;
|
2016-08-31 18:49:34 +08:00
|
|
|
|
using System.Net;
|
2018-12-05 16:13:22 +08:00
|
|
|
|
using System.Net.Http;
|
2020-12-29 14:27:22 +08:00
|
|
|
|
using System.Net.Sockets;
|
2016-08-31 18:49:34 +08:00
|
|
|
|
using System.Threading;
|
2019-11-29 19:03:14 +08:00
|
|
|
|
using System.Threading.Tasks;
|
2018-12-05 16:13:22 +08:00
|
|
|
|
using Newtonsoft.Json.Linq;
|
2019-02-21 18:04:31 +08:00
|
|
|
|
using osu.Framework.Bindables;
|
2019-12-03 19:20:49 +08:00
|
|
|
|
using osu.Framework.Extensions.ExceptionExtensions;
|
2020-06-09 21:13:48 +08:00
|
|
|
|
using osu.Framework.Extensions.ObjectExtensions;
|
2018-03-14 09:42:58 +08:00
|
|
|
|
using osu.Framework.Graphics;
|
2016-08-31 18:49:34 +08:00
|
|
|
|
using osu.Framework.Logging;
|
2018-03-14 08:48:03 +08:00
|
|
|
|
using osu.Game.Configuration;
|
2016-08-31 18:49:34 +08:00
|
|
|
|
using osu.Game.Online.API.Requests;
|
2021-11-04 17:02:44 +08:00
|
|
|
|
using osu.Game.Online.API.Requests.Responses;
|
2017-03-27 23:04:07 +08:00
|
|
|
|
using osu.Game.Users;
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2016-08-31 18:49:34 +08:00
|
|
|
|
namespace osu.Game.Online.API
|
|
|
|
|
{
|
2018-03-14 09:42:58 +08:00
|
|
|
|
public class APIAccess : Component, IAPIProvider
|
2016-08-31 18:49:34 +08:00
|
|
|
|
{
|
2020-02-14 23:18:56 +08:00
|
|
|
|
private readonly OsuConfigManager config;
|
2020-02-14 21:27:21 +08:00
|
|
|
|
|
2021-02-14 22:31:57 +08:00
|
|
|
|
private readonly string versionHash;
|
|
|
|
|
|
2017-03-23 12:41:50 +08:00
|
|
|
|
private readonly OAuth authentication;
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2018-09-01 11:55:11 +08:00
|
|
|
|
private readonly Queue<APIRequest> queue = new Queue<APIRequest>();
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2020-12-24 17:11:40 +08:00
|
|
|
|
public string APIEndpointUrl { get; }
|
|
|
|
|
|
|
|
|
|
public string WebsiteRootUrl { get; }
|
2020-12-24 16:58:38 +08:00
|
|
|
|
|
2022-07-12 03:04:21 +08:00
|
|
|
|
public int APIVersion => 20220705; // We may want to pull this from the game version eventually.
|
2022-02-17 17:33:27 +08:00
|
|
|
|
|
2021-10-04 14:40:24 +08:00
|
|
|
|
public Exception LastLoginError { get; private set; }
|
|
|
|
|
|
2018-03-14 08:48:03 +08:00
|
|
|
|
public string ProvidedUsername { get; private set; }
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2018-03-14 08:48:03 +08:00
|
|
|
|
private string password;
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2021-11-04 17:02:44 +08:00
|
|
|
|
public IBindable<APIUser> LocalUser => localUser;
|
|
|
|
|
public IBindableList<APIUser> Friends => friends;
|
2020-12-18 14:16:36 +08:00
|
|
|
|
public IBindable<UserActivity> Activity => activity;
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2021-11-04 17:02:44 +08:00
|
|
|
|
private Bindable<APIUser> localUser { get; } = new Bindable<APIUser>(createGuestUser());
|
2020-12-17 18:30:55 +08:00
|
|
|
|
|
2021-11-04 17:02:44 +08:00
|
|
|
|
private BindableList<APIUser> friends { get; } = new BindableList<APIUser>();
|
2020-12-18 14:16:36 +08:00
|
|
|
|
|
|
|
|
|
private Bindable<UserActivity> activity { get; } = new Bindable<UserActivity>();
|
2019-06-12 17:04:57 +08:00
|
|
|
|
|
2019-06-11 16:28:16 +08:00
|
|
|
|
protected bool HasLogin => authentication.Token.Value != null || (!string.IsNullOrEmpty(ProvidedUsername) && !string.IsNullOrEmpty(password));
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2018-03-23 19:57:04 +08:00
|
|
|
|
private readonly CancellationTokenSource cancellationToken = new CancellationTokenSource();
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2017-03-23 12:41:50 +08:00
|
|
|
|
private readonly Logger log;
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2021-02-14 22:31:57 +08:00
|
|
|
|
public APIAccess(OsuConfigManager config, EndpointConfiguration endpointConfiguration, string versionHash)
|
2016-08-31 18:49:34 +08:00
|
|
|
|
{
|
2018-03-14 08:48:03 +08:00
|
|
|
|
this.config = config;
|
2021-02-14 22:31:57 +08:00
|
|
|
|
this.versionHash = versionHash;
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2020-12-24 17:11:40 +08:00
|
|
|
|
APIEndpointUrl = endpointConfiguration.APIEndpointUrl;
|
|
|
|
|
WebsiteRootUrl = endpointConfiguration.WebsiteRootUrl;
|
2020-12-24 16:58:38 +08:00
|
|
|
|
|
2020-12-24 17:11:40 +08:00
|
|
|
|
authentication = new OAuth(endpointConfiguration.APIClientID, endpointConfiguration.APIClientSecret, APIEndpointUrl);
|
2016-08-31 18:49:34 +08:00
|
|
|
|
log = Logger.GetLogger(LoggingTarget.Network);
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2018-03-14 08:48:03 +08:00
|
|
|
|
ProvidedUsername = config.Get<string>(OsuSetting.Username);
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2018-04-12 13:30:28 +08:00
|
|
|
|
authentication.TokenString = config.Get<string>(OsuSetting.Token);
|
2018-04-12 12:31:06 +08:00
|
|
|
|
authentication.Token.ValueChanged += onTokenChanged;
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2020-12-18 14:16:36 +08:00
|
|
|
|
localUser.BindValueChanged(u =>
|
2019-06-12 17:04:57 +08:00
|
|
|
|
{
|
2020-12-18 14:16:36 +08:00
|
|
|
|
u.OldValue?.Activity.UnbindFrom(activity);
|
|
|
|
|
u.NewValue.Activity.BindTo(activity);
|
2019-06-12 17:04:57 +08:00
|
|
|
|
}, true);
|
|
|
|
|
|
2018-09-01 12:00:55 +08:00
|
|
|
|
var thread = new Thread(run)
|
|
|
|
|
{
|
|
|
|
|
Name = "APIAccess",
|
|
|
|
|
IsBackground = true
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
thread.Start();
|
2016-08-31 18:49:34 +08:00
|
|
|
|
}
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2021-03-17 15:10:16 +08:00
|
|
|
|
private void onTokenChanged(ValueChangedEvent<OAuthToken> e) => config.SetValue(OsuSetting.Token, config.Get<bool>(OsuSetting.SavePassword) ? authentication.TokenString : string.Empty);
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2018-03-24 17:22:55 +08:00
|
|
|
|
internal new void Schedule(Action action) => base.Schedule(action);
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2016-09-27 17:31:36 +08:00
|
|
|
|
public string AccessToken => authentication.RequestAccessToken();
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2016-08-31 18:49:34 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Number of consecutive requests which failed due to network issues.
|
|
|
|
|
/// </summary>
|
2017-03-07 09:59:19 +08:00
|
|
|
|
private int failureCount;
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2016-08-31 18:49:34 +08:00
|
|
|
|
private void run()
|
|
|
|
|
{
|
2018-03-23 19:57:04 +08:00
|
|
|
|
while (!cancellationToken.IsCancellationRequested)
|
2016-08-31 18:49:34 +08:00
|
|
|
|
{
|
2020-10-22 13:19:12 +08:00
|
|
|
|
switch (State.Value)
|
2016-08-31 18:49:34 +08:00
|
|
|
|
{
|
|
|
|
|
case APIState.Failing:
|
|
|
|
|
//todo: replace this with a ping request.
|
2017-03-07 09:59:19 +08:00
|
|
|
|
log.Add(@"In a failing state, waiting a bit before we try again...");
|
2016-08-31 18:49:34 +08:00
|
|
|
|
Thread.Sleep(5000);
|
2019-01-09 14:29:27 +08:00
|
|
|
|
|
|
|
|
|
if (!IsLoggedIn) goto case APIState.Connecting;
|
|
|
|
|
|
2016-08-31 18:49:34 +08:00
|
|
|
|
if (queue.Count == 0)
|
|
|
|
|
{
|
2017-03-07 09:59:19 +08:00
|
|
|
|
log.Add(@"Queueing a ping request");
|
2018-12-18 19:19:40 +08:00
|
|
|
|
Queue(new GetUserRequest());
|
2016-08-31 18:49:34 +08:00
|
|
|
|
}
|
2018-09-01 11:55:11 +08:00
|
|
|
|
|
2016-08-31 18:49:34 +08:00
|
|
|
|
break;
|
2019-04-01 11:16:05 +08:00
|
|
|
|
|
2016-08-31 18:49:34 +08:00
|
|
|
|
case APIState.Offline:
|
2017-09-27 12:18:24 +08:00
|
|
|
|
case APIState.Connecting:
|
2020-05-05 09:31:11 +08:00
|
|
|
|
// work to restore a connection...
|
2016-08-31 18:49:34 +08:00
|
|
|
|
if (!HasLogin)
|
|
|
|
|
{
|
2020-10-22 13:19:12 +08:00
|
|
|
|
state.Value = APIState.Offline;
|
2017-09-27 12:18:24 +08:00
|
|
|
|
Thread.Sleep(50);
|
2016-08-31 18:49:34 +08:00
|
|
|
|
continue;
|
|
|
|
|
}
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2020-10-22 13:19:12 +08:00
|
|
|
|
state.Value = APIState.Connecting;
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2022-08-09 15:40:20 +08:00
|
|
|
|
if (localUser.IsDefault)
|
|
|
|
|
{
|
|
|
|
|
// Show a placeholder user if saved credentials are available.
|
|
|
|
|
// This is useful for storing local scores and showing a placeholder username after starting the game,
|
|
|
|
|
// until a valid connection has been established.
|
|
|
|
|
localUser.Value = new APIUser
|
|
|
|
|
{
|
|
|
|
|
Username = ProvidedUsername,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-14 08:48:03 +08:00
|
|
|
|
// save the username at this point, if the user requested for it to be.
|
2021-03-17 15:10:16 +08:00
|
|
|
|
config.SetValue(OsuSetting.Username, config.Get<bool>(OsuSetting.SaveUsername) ? ProvidedUsername : string.Empty);
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2021-10-04 14:40:24 +08:00
|
|
|
|
if (!authentication.HasValidAccessToken)
|
2016-08-31 18:49:34 +08:00
|
|
|
|
{
|
2021-10-04 14:40:24 +08:00
|
|
|
|
LastLoginError = null;
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
authentication.AuthenticateWithLogin(ProvidedUsername, password);
|
|
|
|
|
}
|
|
|
|
|
catch (Exception e)
|
|
|
|
|
{
|
|
|
|
|
//todo: this fails even on network-related issues. we should probably handle those differently.
|
|
|
|
|
LastLoginError = e;
|
|
|
|
|
log.Add(@"Login failed!");
|
|
|
|
|
password = null;
|
|
|
|
|
authentication.Clear();
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2016-08-31 18:49:34 +08:00
|
|
|
|
}
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2016-12-01 15:44:24 +08:00
|
|
|
|
var userReq = new GetUserRequest();
|
2020-12-18 14:16:48 +08:00
|
|
|
|
|
2021-07-23 17:58:22 +08:00
|
|
|
|
userReq.Failure += ex =>
|
|
|
|
|
{
|
2022-07-16 05:34:21 +08:00
|
|
|
|
if (ex is APIException)
|
|
|
|
|
{
|
|
|
|
|
LastLoginError = ex;
|
|
|
|
|
log.Add("Login failed on local user retrieval!");
|
|
|
|
|
Logout();
|
|
|
|
|
}
|
|
|
|
|
else if (ex is WebException webException && webException.Message == @"Unauthorized")
|
2021-07-23 17:58:22 +08:00
|
|
|
|
{
|
|
|
|
|
log.Add(@"Login no longer valid");
|
|
|
|
|
Logout();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
failConnectionProcess();
|
|
|
|
|
};
|
2017-09-27 12:18:24 +08:00
|
|
|
|
userReq.Success += u =>
|
|
|
|
|
{
|
2020-12-18 14:16:36 +08:00
|
|
|
|
localUser.Value = u;
|
2019-12-18 13:07:03 +08:00
|
|
|
|
|
|
|
|
|
// todo: save/pull from settings
|
2020-12-18 14:16:36 +08:00
|
|
|
|
localUser.Value.Status.Value = new UserStatusOnline();
|
2019-12-18 13:07:03 +08:00
|
|
|
|
|
2017-10-24 16:13:59 +08:00
|
|
|
|
failureCount = 0;
|
2016-12-01 15:44:24 +08:00
|
|
|
|
};
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2016-12-01 15:44:24 +08:00
|
|
|
|
if (!handleRequest(userReq))
|
2017-10-24 16:13:59 +08:00
|
|
|
|
{
|
2020-12-18 14:19:38 +08:00
|
|
|
|
failConnectionProcess();
|
2016-12-01 15:44:24 +08:00
|
|
|
|
continue;
|
2017-10-24 16:13:59 +08:00
|
|
|
|
}
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2020-12-18 14:16:48 +08:00
|
|
|
|
// getting user's friends is considered part of the connection process.
|
|
|
|
|
var friendsReq = new GetFriendsRequest();
|
|
|
|
|
|
2021-07-23 17:58:22 +08:00
|
|
|
|
friendsReq.Failure += _ => failConnectionProcess();
|
2020-12-18 14:16:48 +08:00
|
|
|
|
friendsReq.Success += res =>
|
|
|
|
|
{
|
|
|
|
|
friends.AddRange(res);
|
|
|
|
|
|
|
|
|
|
//we're connected!
|
|
|
|
|
state.Value = APIState.Online;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (!handleRequest(friendsReq))
|
2020-12-18 14:19:38 +08:00
|
|
|
|
{
|
|
|
|
|
failConnectionProcess();
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-24 16:13:59 +08:00
|
|
|
|
// The Success callback event is fired on the main thread, so we should wait for that to run before proceeding.
|
|
|
|
|
// Without this, we will end up circulating this Connecting loop multiple times and queueing up many web requests
|
|
|
|
|
// before actually going online.
|
2020-10-22 13:19:12 +08:00
|
|
|
|
while (State.Value > APIState.Offline && State.Value < APIState.Online)
|
2017-10-24 16:13:59 +08:00
|
|
|
|
Thread.Sleep(500);
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2016-08-31 18:49:34 +08:00
|
|
|
|
break;
|
|
|
|
|
}
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2020-05-05 09:31:11 +08:00
|
|
|
|
// hard bail if we can't get a valid access token.
|
2016-08-31 18:49:34 +08:00
|
|
|
|
if (authentication.RequestAccessToken() == null)
|
|
|
|
|
{
|
2018-12-22 16:54:19 +08:00
|
|
|
|
Logout();
|
2016-08-31 18:49:34 +08:00
|
|
|
|
continue;
|
|
|
|
|
}
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2018-09-06 16:38:15 +08:00
|
|
|
|
while (true)
|
|
|
|
|
{
|
|
|
|
|
APIRequest req;
|
2018-09-01 11:55:11 +08:00
|
|
|
|
|
2018-09-06 16:38:15 +08:00
|
|
|
|
lock (queue)
|
|
|
|
|
{
|
|
|
|
|
if (queue.Count == 0) break;
|
2019-02-28 12:31:40 +08:00
|
|
|
|
|
2018-09-01 11:55:11 +08:00
|
|
|
|
req = queue.Dequeue();
|
2018-09-06 16:38:15 +08:00
|
|
|
|
}
|
2018-09-01 11:55:11 +08:00
|
|
|
|
|
|
|
|
|
handleRequest(req);
|
2016-08-31 18:49:34 +08:00
|
|
|
|
}
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2018-09-01 11:55:11 +08:00
|
|
|
|
Thread.Sleep(50);
|
2016-08-31 18:49:34 +08:00
|
|
|
|
}
|
2020-12-18 14:19:38 +08:00
|
|
|
|
|
|
|
|
|
void failConnectionProcess()
|
|
|
|
|
{
|
|
|
|
|
// if something went wrong during the connection process, we want to reset the state (but only if still connecting).
|
|
|
|
|
if (State.Value == APIState.Connecting)
|
|
|
|
|
state.Value = APIState.Failing;
|
|
|
|
|
}
|
2016-08-31 18:49:34 +08:00
|
|
|
|
}
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2019-11-29 19:03:14 +08:00
|
|
|
|
public void Perform(APIRequest request)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
request.Perform(this);
|
|
|
|
|
}
|
|
|
|
|
catch (Exception e)
|
|
|
|
|
{
|
|
|
|
|
// todo: fix exception handling
|
|
|
|
|
request.Fail(e);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Task PerformAsync(APIRequest request) =>
|
|
|
|
|
Task.Factory.StartNew(() => Perform(request), TaskCreationOptions.LongRunning);
|
|
|
|
|
|
2016-11-30 15:54:15 +08:00
|
|
|
|
public void Login(string username, string password)
|
|
|
|
|
{
|
2020-10-22 13:19:12 +08:00
|
|
|
|
Debug.Assert(State.Value == APIState.Offline);
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2018-03-14 08:48:03 +08:00
|
|
|
|
ProvidedUsername = username;
|
|
|
|
|
this.password = password;
|
2016-11-30 15:54:15 +08:00
|
|
|
|
}
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2021-08-02 13:44:51 +08:00
|
|
|
|
public IHubClientConnector GetHubConnector(string clientName, string endpoint, bool preferMessagePack) =>
|
|
|
|
|
new HubClientConnector(clientName, endpoint, this, versionHash, preferMessagePack);
|
2021-02-15 15:31:00 +08:00
|
|
|
|
|
2018-12-05 16:13:22 +08:00
|
|
|
|
public RegistrationRequest.RegistrationRequestErrors CreateAccount(string email, string username, string password)
|
2018-12-04 19:33:29 +08:00
|
|
|
|
{
|
2020-10-22 13:19:12 +08:00
|
|
|
|
Debug.Assert(State.Value == APIState.Offline);
|
2018-12-05 12:08:35 +08:00
|
|
|
|
|
2018-12-05 16:13:22 +08:00
|
|
|
|
var req = new RegistrationRequest
|
|
|
|
|
{
|
2020-12-24 17:11:40 +08:00
|
|
|
|
Url = $@"{APIEndpointUrl}/users",
|
2018-12-05 16:13:22 +08:00
|
|
|
|
Method = HttpMethod.Post,
|
|
|
|
|
Username = username,
|
|
|
|
|
Email = email,
|
|
|
|
|
Password = password
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
req.Perform();
|
|
|
|
|
}
|
|
|
|
|
catch (Exception e)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
2021-05-15 05:47:35 +08:00
|
|
|
|
return JObject.Parse(req.GetResponseString().AsNonNull()).SelectToken("form_error", true).AsNonNull().ToObject<RegistrationRequest.RegistrationRequestErrors>();
|
2018-12-05 16:13:22 +08:00
|
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
|
// if we couldn't deserialize the error message let's throw the original exception outwards.
|
2019-12-03 19:20:49 +08:00
|
|
|
|
e.Rethrow();
|
2018-12-05 16:13:22 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
2018-12-05 12:08:35 +08:00
|
|
|
|
|
2018-12-05 16:13:22 +08:00
|
|
|
|
return null;
|
2018-12-04 19:33:29 +08:00
|
|
|
|
}
|
|
|
|
|
|
2016-08-31 18:49:34 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Handle a single API request.
|
2018-12-18 19:19:40 +08:00
|
|
|
|
/// Ensures all exceptions are caught and dealt with correctly.
|
2016-08-31 18:49:34 +08:00
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="req">The request.</param>
|
2018-12-17 13:29:11 +08:00
|
|
|
|
/// <returns>true if the request succeeded.</returns>
|
2016-08-31 18:49:34 +08:00
|
|
|
|
private bool handleRequest(APIRequest req)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
req.Perform(this);
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2021-08-20 11:11:41 +08:00
|
|
|
|
if (req.CompletionState != APIRequestCompletionState.Completed)
|
|
|
|
|
return false;
|
|
|
|
|
|
2020-05-05 09:31:11 +08:00
|
|
|
|
// we could still be in initialisation, at which point we don't want to say we're Online yet.
|
2020-10-22 13:19:12 +08:00
|
|
|
|
if (IsLoggedIn) state.Value = APIState.Online;
|
2016-08-31 18:49:34 +08:00
|
|
|
|
failureCount = 0;
|
2021-08-20 11:11:41 +08:00
|
|
|
|
return true;
|
2016-08-31 18:49:34 +08:00
|
|
|
|
}
|
2020-12-29 14:27:22 +08:00
|
|
|
|
catch (HttpRequestException re)
|
|
|
|
|
{
|
|
|
|
|
log.Add($"{nameof(HttpRequestException)} while performing request {req}: {re.Message}");
|
|
|
|
|
handleFailure();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
catch (SocketException se)
|
|
|
|
|
{
|
|
|
|
|
log.Add($"{nameof(SocketException)} while performing request {req}: {se.Message}");
|
|
|
|
|
handleFailure();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2016-08-31 18:49:34 +08:00
|
|
|
|
catch (WebException we)
|
|
|
|
|
{
|
2020-12-29 14:27:22 +08:00
|
|
|
|
log.Add($"{nameof(WebException)} while performing request {req}: {we.Message}");
|
2018-12-18 19:19:40 +08:00
|
|
|
|
handleWebException(we);
|
2018-12-17 13:29:11 +08:00
|
|
|
|
return false;
|
2016-08-31 18:49:34 +08:00
|
|
|
|
}
|
2019-06-15 23:31:14 +08:00
|
|
|
|
catch (Exception ex)
|
2016-08-31 18:49:34 +08:00
|
|
|
|
{
|
2019-06-15 23:31:14 +08:00
|
|
|
|
Logger.Error(ex, "Error occurred while handling an API request.");
|
2018-12-17 13:29:11 +08:00
|
|
|
|
return false;
|
2016-08-31 18:49:34 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2020-10-22 13:19:12 +08:00
|
|
|
|
private readonly Bindable<APIState> state = new Bindable<APIState>();
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2020-10-22 13:19:12 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// The current connectivity state of the API.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public IBindable<APIState> State => state;
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2020-12-29 14:27:22 +08:00
|
|
|
|
private void handleWebException(WebException we)
|
2018-12-14 14:48:34 +08:00
|
|
|
|
{
|
|
|
|
|
HttpStatusCode statusCode = (we.Response as HttpWebResponse)?.StatusCode
|
|
|
|
|
?? (we.Status == WebExceptionStatus.UnknownError ? HttpStatusCode.NotAcceptable : HttpStatusCode.RequestTimeout);
|
|
|
|
|
|
|
|
|
|
// special cases for un-typed but useful message responses.
|
|
|
|
|
switch (we.Message)
|
|
|
|
|
{
|
|
|
|
|
case "Unauthorized":
|
|
|
|
|
case "Forbidden":
|
|
|
|
|
statusCode = HttpStatusCode.Unauthorized;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch (statusCode)
|
|
|
|
|
{
|
|
|
|
|
case HttpStatusCode.Unauthorized:
|
2018-12-22 16:54:19 +08:00
|
|
|
|
Logout();
|
2020-12-29 14:27:22 +08:00
|
|
|
|
break;
|
2019-04-01 11:16:05 +08:00
|
|
|
|
|
2018-12-14 14:48:34 +08:00
|
|
|
|
case HttpStatusCode.RequestTimeout:
|
2020-12-29 14:27:22 +08:00
|
|
|
|
handleFailure();
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-12-14 14:48:34 +08:00
|
|
|
|
|
2020-12-29 14:27:22 +08:00
|
|
|
|
private void handleFailure()
|
|
|
|
|
{
|
|
|
|
|
failureCount++;
|
|
|
|
|
log.Add($@"API failure count is now {failureCount}");
|
2018-12-19 13:32:43 +08:00
|
|
|
|
|
2020-12-29 14:27:22 +08:00
|
|
|
|
if (failureCount >= 3 && State.Value == APIState.Online)
|
|
|
|
|
{
|
|
|
|
|
state.Value = APIState.Failing;
|
|
|
|
|
flushQueue();
|
2018-12-14 14:48:34 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-08-09 15:38:59 +08:00
|
|
|
|
public bool IsLoggedIn => State.Value > APIState.Offline;
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2018-09-01 11:55:11 +08:00
|
|
|
|
public void Queue(APIRequest request)
|
|
|
|
|
{
|
2021-02-24 18:57:42 +08:00
|
|
|
|
lock (queue)
|
|
|
|
|
{
|
|
|
|
|
if (state.Value == APIState.Offline)
|
2022-02-03 12:16:54 +08:00
|
|
|
|
{
|
2022-02-03 13:09:27 +08:00
|
|
|
|
request.Fail(new WebException(@"User not logged in"));
|
2021-02-24 18:57:42 +08:00
|
|
|
|
return;
|
2022-02-03 12:16:54 +08:00
|
|
|
|
}
|
2021-02-24 18:57:42 +08:00
|
|
|
|
|
|
|
|
|
queue.Enqueue(request);
|
|
|
|
|
}
|
2018-09-01 11:55:11 +08:00
|
|
|
|
}
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2016-08-31 18:49:34 +08:00
|
|
|
|
private void flushQueue(bool failOldRequests = true)
|
|
|
|
|
{
|
2018-09-01 11:55:11 +08:00
|
|
|
|
lock (queue)
|
|
|
|
|
{
|
|
|
|
|
var oldQueueRequests = queue.ToArray();
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2018-09-01 11:55:11 +08:00
|
|
|
|
queue.Clear();
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2018-09-01 11:55:11 +08:00
|
|
|
|
if (failOldRequests)
|
|
|
|
|
{
|
|
|
|
|
foreach (var req in oldQueueRequests)
|
2022-02-03 13:09:27 +08:00
|
|
|
|
req.Fail(new WebException($@"Request failed from flush operation (state {state.Value})"));
|
2018-09-01 11:55:11 +08:00
|
|
|
|
}
|
2016-08-31 18:49:34 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2018-12-22 16:54:19 +08:00
|
|
|
|
public void Logout()
|
2016-08-31 18:49:34 +08:00
|
|
|
|
{
|
2018-03-14 08:48:03 +08:00
|
|
|
|
password = null;
|
2016-08-31 18:49:34 +08:00
|
|
|
|
authentication.Clear();
|
2019-05-09 12:32:18 +08:00
|
|
|
|
|
2020-12-17 18:30:55 +08:00
|
|
|
|
// Scheduled prior to state change such that the state changed event is invoked with the correct user and their friends present
|
|
|
|
|
Schedule(() =>
|
|
|
|
|
{
|
2020-12-18 14:16:36 +08:00
|
|
|
|
localUser.Value = createGuestUser();
|
|
|
|
|
friends.Clear();
|
2020-12-17 18:30:55 +08:00
|
|
|
|
});
|
2019-05-09 12:42:04 +08:00
|
|
|
|
|
2020-10-22 13:19:12 +08:00
|
|
|
|
state.Value = APIState.Offline;
|
2021-02-24 18:57:42 +08:00
|
|
|
|
flushQueue();
|
2016-08-31 18:49:34 +08:00
|
|
|
|
}
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2021-11-04 17:02:44 +08:00
|
|
|
|
private static APIUser createGuestUser() => new GuestUser();
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2018-03-14 09:42:58 +08:00
|
|
|
|
protected override void Dispose(bool isDisposing)
|
2016-09-27 18:22:02 +08:00
|
|
|
|
{
|
2018-03-14 09:42:58 +08:00
|
|
|
|
base.Dispose(isDisposing);
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2018-03-23 14:20:19 +08:00
|
|
|
|
flushQueue();
|
2018-03-23 19:57:04 +08:00
|
|
|
|
cancellationToken.Cancel();
|
2018-03-14 09:07:16 +08:00
|
|
|
|
}
|
2016-08-31 18:49:34 +08:00
|
|
|
|
}
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2021-11-04 17:02:44 +08:00
|
|
|
|
internal class GuestUser : APIUser
|
2019-01-23 10:06:29 +08:00
|
|
|
|
{
|
|
|
|
|
public GuestUser()
|
|
|
|
|
{
|
|
|
|
|
Username = @"Guest";
|
2022-05-06 16:32:55 +08:00
|
|
|
|
Id = SYSTEM_USER_ID;
|
2019-01-23 10:06:29 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-11-30 14:15:07 +08:00
|
|
|
|
public enum APIState
|
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// We cannot login (not enough credentials).
|
|
|
|
|
/// </summary>
|
|
|
|
|
Offline,
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2016-11-30 14:15:07 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// We are having connectivity issues.
|
|
|
|
|
/// </summary>
|
|
|
|
|
Failing,
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2016-11-30 14:15:07 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// We are in the process of (re-)connecting.
|
|
|
|
|
/// </summary>
|
|
|
|
|
Connecting,
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2016-11-30 14:15:07 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// We are online.
|
|
|
|
|
/// </summary>
|
|
|
|
|
Online
|
|
|
|
|
}
|
2016-08-31 18:49:34 +08:00
|
|
|
|
}
|