mirror of
https://github.com/osukey/osukey.git
synced 2025-08-05 23:53:51 +09:00
Merge branch 'master' into user-registration
This commit is contained in:
@ -234,6 +234,12 @@ namespace osu.Game.Online.API
|
||||
try
|
||||
{
|
||||
Logger.Log($@"Performing request {req}", LoggingTarget.Network);
|
||||
req.Failure += ex =>
|
||||
{
|
||||
if (ex is WebException we)
|
||||
handleWebException(we);
|
||||
};
|
||||
|
||||
req.Perform(this);
|
||||
|
||||
//we could still be in initialisation, at which point we don't want to say we're Online yet.
|
||||
@ -245,37 +251,12 @@ namespace osu.Game.Online.API
|
||||
}
|
||||
catch (WebException we)
|
||||
{
|
||||
HttpStatusCode statusCode = (we.Response as HttpWebResponse)?.StatusCode
|
||||
?? (we.Status == WebExceptionStatus.UnknownError ? HttpStatusCode.NotAcceptable : HttpStatusCode.RequestTimeout);
|
||||
var removeFromQueue = handleWebException(we);
|
||||
|
||||
// special cases for un-typed but useful message responses.
|
||||
switch (we.Message)
|
||||
{
|
||||
case "Unauthorized":
|
||||
statusCode = HttpStatusCode.Unauthorized;
|
||||
break;
|
||||
}
|
||||
if (removeFromQueue)
|
||||
req.Fail(we);
|
||||
|
||||
switch (statusCode)
|
||||
{
|
||||
case HttpStatusCode.Unauthorized:
|
||||
Logout(false);
|
||||
return true;
|
||||
case HttpStatusCode.RequestTimeout:
|
||||
failureCount++;
|
||||
log.Add($@"API failure count is now {failureCount}");
|
||||
|
||||
if (failureCount < 3)
|
||||
//we might try again at an api level.
|
||||
return false;
|
||||
|
||||
State = APIState.Failing;
|
||||
flushQueue();
|
||||
return true;
|
||||
}
|
||||
|
||||
req.Fail(we);
|
||||
return true;
|
||||
return removeFromQueue;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@ -311,6 +292,41 @@ namespace osu.Game.Online.API
|
||||
}
|
||||
}
|
||||
|
||||
private bool handleWebException(WebException we)
|
||||
{
|
||||
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:
|
||||
Logout(false);
|
||||
return true;
|
||||
case HttpStatusCode.RequestTimeout:
|
||||
failureCount++;
|
||||
log.Add($@"API failure count is now {failureCount}");
|
||||
|
||||
if (failureCount < 3)
|
||||
//we might try again at an api level.
|
||||
return false;
|
||||
|
||||
State = APIState.Failing;
|
||||
flushQueue();
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool IsLoggedIn => LocalUser.Value.Id > 1;
|
||||
|
||||
public void Queue(APIRequest request)
|
||||
|
@ -88,6 +88,17 @@ namespace osu.Game.Online.Chat
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a private messaging channel with the specified user.
|
||||
/// </summary>
|
||||
/// <param name="user">The user to create the private conversation with.</param>
|
||||
public Channel(User user)
|
||||
{
|
||||
Type = ChannelType.PM;
|
||||
Users.Add(user);
|
||||
Name = user.Username;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the argument message as a local echo. When this local echo is resolved <see cref="PendingMessageResolved"/> will get called.
|
||||
/// </summary>
|
||||
|
@ -4,11 +4,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Users;
|
||||
@ -18,7 +17,7 @@ namespace osu.Game.Online.Chat
|
||||
/// <summary>
|
||||
/// Manages everything channel related
|
||||
/// </summary>
|
||||
public class ChannelManager : Component, IOnlineComponent
|
||||
public class ChannelManager : PollingComponent
|
||||
{
|
||||
/// <summary>
|
||||
/// The channels the player joins on startup
|
||||
@ -49,11 +48,14 @@ namespace osu.Game.Online.Chat
|
||||
public IBindableCollection<Channel> AvailableChannels => availableChannels;
|
||||
|
||||
private IAPIProvider api;
|
||||
private ScheduledDelegate fetchMessagesScheduleder;
|
||||
|
||||
public readonly BindableBool HighPollRate = new BindableBool();
|
||||
|
||||
public ChannelManager()
|
||||
{
|
||||
CurrentChannel.ValueChanged += currentChannelChanged;
|
||||
|
||||
HighPollRate.BindValueChanged(high => TimeBetweenPolls = high ? 1000 : 6000, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -79,7 +81,7 @@ namespace osu.Game.Online.Chat
|
||||
throw new ArgumentNullException(nameof(user));
|
||||
|
||||
CurrentChannel.Value = JoinedChannels.FirstOrDefault(c => c.Type == ChannelType.PM && c.Users.Count == 1 && c.Users.Any(u => u.Id == user.Id))
|
||||
?? new Channel { Name = user.Username, Users = { user }, Type = ChannelType.PM };
|
||||
?? new Channel(user);
|
||||
}
|
||||
|
||||
private void currentChannelChanged(Channel channel) => JoinChannel(channel);
|
||||
@ -360,73 +362,60 @@ namespace osu.Game.Online.Chat
|
||||
}
|
||||
}
|
||||
|
||||
public void APIStateChanged(APIAccess api, APIState state)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case APIState.Online:
|
||||
fetchUpdates();
|
||||
break;
|
||||
default:
|
||||
fetchMessagesScheduleder?.Cancel();
|
||||
fetchMessagesScheduleder = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private long lastMessageId;
|
||||
private const int update_poll_interval = 1000;
|
||||
|
||||
private bool channelsInitialised;
|
||||
|
||||
private void fetchUpdates()
|
||||
protected override Task Poll()
|
||||
{
|
||||
fetchMessagesScheduleder?.Cancel();
|
||||
fetchMessagesScheduleder = Scheduler.AddDelayed(() =>
|
||||
if (!api.IsLoggedIn)
|
||||
return base.Poll();
|
||||
|
||||
var fetchReq = new GetUpdatesRequest(lastMessageId);
|
||||
|
||||
var tcs = new TaskCompletionSource<bool>();
|
||||
|
||||
fetchReq.Success += updates =>
|
||||
{
|
||||
var fetchReq = new GetUpdatesRequest(lastMessageId);
|
||||
|
||||
fetchReq.Success += updates =>
|
||||
if (updates?.Presence != null)
|
||||
{
|
||||
if (updates?.Presence != null)
|
||||
foreach (var channel in updates.Presence)
|
||||
{
|
||||
foreach (var channel in updates.Presence)
|
||||
{
|
||||
// we received this from the server so should mark the channel already joined.
|
||||
JoinChannel(channel, true);
|
||||
}
|
||||
|
||||
//todo: handle left channels
|
||||
|
||||
handleChannelMessages(updates.Messages);
|
||||
|
||||
foreach (var group in updates.Messages.GroupBy(m => m.ChannelId))
|
||||
JoinedChannels.FirstOrDefault(c => c.Id == group.Key)?.AddNewMessages(group.ToArray());
|
||||
|
||||
lastMessageId = updates.Messages.LastOrDefault()?.Id ?? lastMessageId;
|
||||
// we received this from the server so should mark the channel already joined.
|
||||
JoinChannel(channel, true);
|
||||
}
|
||||
|
||||
if (!channelsInitialised)
|
||||
{
|
||||
channelsInitialised = true;
|
||||
// we want this to run after the first presence so we can see if the user is in any channels already.
|
||||
initializeChannels();
|
||||
}
|
||||
//todo: handle left channels
|
||||
|
||||
fetchUpdates();
|
||||
};
|
||||
handleChannelMessages(updates.Messages);
|
||||
|
||||
fetchReq.Failure += delegate { fetchUpdates(); };
|
||||
foreach (var group in updates.Messages.GroupBy(m => m.ChannelId))
|
||||
JoinedChannels.FirstOrDefault(c => c.Id == group.Key)?.AddNewMessages(group.ToArray());
|
||||
|
||||
api.Queue(fetchReq);
|
||||
}, update_poll_interval);
|
||||
lastMessageId = updates.Messages.LastOrDefault()?.Id ?? lastMessageId;
|
||||
}
|
||||
|
||||
if (!channelsInitialised)
|
||||
{
|
||||
channelsInitialised = true;
|
||||
// we want this to run after the first presence so we can see if the user is in any channels already.
|
||||
initializeChannels();
|
||||
}
|
||||
|
||||
tcs.SetResult(true);
|
||||
};
|
||||
|
||||
fetchReq.Failure += _ => tcs.SetResult(false);
|
||||
|
||||
api.Queue(fetchReq);
|
||||
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(IAPIProvider api)
|
||||
{
|
||||
this.api = api;
|
||||
api.Register(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,6 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
@ -15,20 +14,20 @@ using osuTK;
|
||||
namespace osu.Game.Online.Chat
|
||||
{
|
||||
/// <summary>
|
||||
/// An invisible drawable that brings multiple <see cref="SpriteText"/> pieces together to form a consumable clickable link.
|
||||
/// An invisible drawable that brings multiple <see cref="Drawable"/> pieces together to form a consumable clickable link.
|
||||
/// </summary>
|
||||
public class DrawableLinkCompiler : OsuHoverContainer, IHasTooltip
|
||||
{
|
||||
/// <summary>
|
||||
/// Each word part of a chat link (split for word-wrap support).
|
||||
/// </summary>
|
||||
public List<SpriteText> Parts;
|
||||
public List<Drawable> Parts;
|
||||
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Parts.Any(d => d.ReceivePositionalInputAt(screenSpacePos));
|
||||
|
||||
protected override HoverClickSounds CreateHoverClickSounds(HoverSampleSet sampleSet) => new LinkHoverSounds(sampleSet, Parts);
|
||||
|
||||
public DrawableLinkCompiler(IEnumerable<SpriteText> parts)
|
||||
public DrawableLinkCompiler(IEnumerable<Drawable> parts)
|
||||
{
|
||||
Parts = parts.ToList();
|
||||
}
|
||||
@ -45,9 +44,9 @@ namespace osu.Game.Online.Chat
|
||||
|
||||
private class LinkHoverSounds : HoverClickSounds
|
||||
{
|
||||
private readonly List<SpriteText> parts;
|
||||
private readonly List<Drawable> parts;
|
||||
|
||||
public LinkHoverSounds(HoverSampleSet sampleSet, List<SpriteText> parts)
|
||||
public LinkHoverSounds(HoverSampleSet sampleSet, List<Drawable> parts)
|
||||
: base(sampleSet)
|
||||
{
|
||||
this.parts = parts;
|
||||
|
118
osu.Game/Online/PollingComponent.cs
Normal file
118
osu.Game/Online/PollingComponent.cs
Normal file
@ -0,0 +1,118 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Threading;
|
||||
|
||||
namespace osu.Game.Online
|
||||
{
|
||||
/// <summary>
|
||||
/// A component which requires a constant polling process.
|
||||
/// </summary>
|
||||
public abstract class PollingComponent : Component
|
||||
{
|
||||
private double? lastTimePolled;
|
||||
|
||||
private ScheduledDelegate scheduledPoll;
|
||||
|
||||
private bool pollingActive;
|
||||
|
||||
private double timeBetweenPolls;
|
||||
|
||||
/// <summary>
|
||||
/// The time in milliseconds to wait between polls.
|
||||
/// Setting to zero stops all polling.
|
||||
/// </summary>
|
||||
public double TimeBetweenPolls
|
||||
{
|
||||
get => timeBetweenPolls;
|
||||
set
|
||||
{
|
||||
timeBetweenPolls = value;
|
||||
scheduledPoll?.Cancel();
|
||||
pollIfNecessary();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="timeBetweenPolls">The initial time in milliseconds to wait between polls. Setting to zero stops al polling.</param>
|
||||
protected PollingComponent(double timeBetweenPolls = 0)
|
||||
{
|
||||
TimeBetweenPolls = timeBetweenPolls;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
pollIfNecessary();
|
||||
}
|
||||
|
||||
private bool pollIfNecessary()
|
||||
{
|
||||
// we must be loaded so we have access to clock.
|
||||
if (!IsLoaded) return false;
|
||||
|
||||
// there's already a poll process running.
|
||||
if (pollingActive) return false;
|
||||
|
||||
// don't try polling if the time between polls hasn't been set.
|
||||
if (timeBetweenPolls == 0) return false;
|
||||
|
||||
if (!lastTimePolled.HasValue)
|
||||
{
|
||||
doPoll();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Time.Current - lastTimePolled.Value > timeBetweenPolls)
|
||||
{
|
||||
doPoll();
|
||||
return true;
|
||||
}
|
||||
|
||||
// not ennough time has passed since the last poll. we do want to schedule a poll to happen, though.
|
||||
scheduleNextPoll();
|
||||
return false;
|
||||
}
|
||||
|
||||
private void doPoll()
|
||||
{
|
||||
scheduledPoll = null;
|
||||
pollingActive = true;
|
||||
Poll().ContinueWith(_ => pollComplete());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Perform the polling in this method. Call <see cref="pollComplete"/> when done.
|
||||
/// </summary>
|
||||
protected virtual Task Poll()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Call when a poll operation has completed.
|
||||
/// </summary>
|
||||
private void pollComplete()
|
||||
{
|
||||
lastTimePolled = Time.Current;
|
||||
pollingActive = false;
|
||||
|
||||
if (scheduledPoll == null)
|
||||
scheduleNextPoll();
|
||||
}
|
||||
|
||||
private void scheduleNextPoll()
|
||||
{
|
||||
scheduledPoll?.Cancel();
|
||||
|
||||
double lastPollDuration = lastTimePolled.HasValue ? Time.Current - lastTimePolled.Value : 0;
|
||||
|
||||
scheduledPoll = Scheduler.AddDelayed(doPoll, Math.Max(0, timeBetweenPolls - lastPollDuration));
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user