1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-06 06:17:23 +08:00

Add back support for new API and private messages

This commit is contained in:
Dean Herbert 2018-11-13 15:20:40 +09:00
parent 8b9f7f6691
commit f241fcdba1
9 changed files with 135 additions and 234 deletions

View File

@ -87,15 +87,18 @@ namespace osu.Game.Tests.Visual
private void addRandomUser()
{
channelTabControl.AddChannel(new PrivateChannel
channelTabControl.AddChannel(new Channel
{
User = users?.Count > 0
Users =
{
users?.Count > 0
? users[RNG.Next(0, users.Count - 1)]
: new User
{
Id = RNG.Next(),
Username = "testuser" + RNG.Next(1000)
}
}
});
}

View File

@ -17,9 +17,19 @@ namespace osu.Game.Online.Chat
public readonly int MaxHistory = 300;
/// <summary>
/// Contains every joined user except the current logged in user.
/// Contains every joined user except the current logged in user. Currently only returned for PM channels.
/// </summary>
public readonly ObservableCollection<User> JoinedUsers = new ObservableCollection<User>();
public readonly ObservableCollection<User> Users = new ObservableCollection<User>();
[JsonProperty(@"users")]
private long[] userIds
{
set
{
foreach (var id in value)
Users.Add(new User { Id = id });
}
}
/// <summary>
/// Contains all the messages send in the channel.
@ -47,11 +57,6 @@ namespace osu.Game.Online.Chat
/// </summary>
public event Action<Message> MessageRemoved;
/// <summary>
/// Signalles whether the channels target is a private channel or public channel.
/// </summary>
public TargetType Target { get; protected set; }
public bool ReadOnly => false; //todo not yet used.
public override string ToString() => Name;

View File

@ -39,12 +39,12 @@ namespace osu.Game.Online.Chat
/// <summary>
/// The Channels the player has joined
/// </summary>
public ObservableCollection<Channel> JoinedChannels { get; } = new ObservableCollection<Channel>();
public ObservableCollection<Channel> JoinedChannels { get; } = new ObservableCollection<Channel>(); //todo: should be publicly readonly
/// <summary>
/// The channels available for the player to join
/// </summary>
public ObservableCollection<Channel> AvailableChannels { get; } = new ObservableCollection<Channel>();
public ObservableCollection<Channel> AvailableChannels { get; } = new ObservableCollection<Channel>(); //todo: should be publicly readonly
/*private readonly IncomingMessagesHandler privateMessagesHandler;*/
@ -54,12 +54,6 @@ namespace osu.Game.Online.Chat
public ChannelManager()
{
CurrentChannel.ValueChanged += currentChannelChanged;
/*channelMessagesHandler = new IncomingMessagesHandler(
lastId => new GetMessagesRequest(JoinedChannels.Where(c => c.Target == TargetType.Channel)), handleChannelMessages);
privateMessagesHandler = new IncomingMessagesHandler(
lastId => new GetPrivateMessagesRequest(lastId),handleUserMessages);*/
}
/// <summary>
@ -85,14 +79,13 @@ namespace osu.Game.Online.Chat
if (user == null)
throw new ArgumentNullException(nameof(user));
CurrentChannel.Value = JoinedChannels.FirstOrDefault(c => c.Target == TargetType.User && c.Id == user.Id)
?? new PrivateChannel { User = 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 } };
}
private void currentChannelChanged(Channel channel)
{
if (!JoinedChannels.Contains(channel))
JoinedChannels.Add(channel);
JoinChannel(channel);
}
/// <summary>
@ -169,71 +162,6 @@ namespace osu.Game.Online.Chat
}
}
private void fetchNewMessages()
{
/*if (channelMessagesHandler.CanRequestNewMessages)
channelMessagesHandler.RequestNewMessages(api);
if (privateMessagesHandler.CanRequestNewMessages)
privateMessagesHandler.RequestNewMessages(api);*/
}
private void handleUserMessages(IEnumerable<Message> messages)
{
var joinedPrivateChannels = JoinedChannels.Where(c => c.Target == TargetType.User).ToList();
Channel getChannelForUser(User user)
{
var channel = joinedPrivateChannels.FirstOrDefault(c => c.Id == user.Id);
if (channel == null)
{
channel = new PrivateChannel { User = user };
JoinedChannels.Add(channel);
joinedPrivateChannels.Add(channel);
}
return channel;
}
long localUserId = api.LocalUser.Value.Id;
var outgoingGroups = messages.Where(m => m.Sender.Id == localUserId).GroupBy(m => m.ChannelId);
var incomingGroups = messages.Where(m => m.Sender.Id != localUserId).GroupBy(m => m.UserId);
foreach (var group in incomingGroups)
{
var targetUser = group.First().Sender;
var channel = getChannelForUser(targetUser);
channel.AddNewMessages(group.ToArray());
var outgoingTargetMessages = outgoingGroups.FirstOrDefault(g => g.Key == targetUser.Id);
if (outgoingTargetMessages != null)
channel.AddNewMessages(outgoingTargetMessages.ToArray());
}
// Because of the way the API provides data right now, outgoing messages do not contain required
// user (or in the future, target channel) metadata. As such we need to do a second request
// to find out the specifics of the user.
var withoutReplyGroups = outgoingGroups.Where(g => joinedPrivateChannels.All(m => m.Id != g.Key));
foreach (var withoutReplyGroup in withoutReplyGroups)
{
var userReq = new GetUserRequest(withoutReplyGroup.First().ChannelId);
userReq.Failure += exception => Logger.Error(exception, "Failed to get user informations.");
userReq.Success += user =>
{
var channel = getChannelForUser(user);
channel.AddNewMessages(withoutReplyGroup.ToArray());
};
api.Queue(userReq);
}
}
private void handleChannelMessages(IEnumerable<Message> messages)
{
var channels = JoinedChannels.ToList();
@ -246,32 +174,24 @@ namespace osu.Game.Online.Chat
{
var req = new ListChannelsRequest();
//var joinDefaults = JoinedChannels.Count == 0;
req.Success += channels =>
{
foreach (var channel in channels)
{
if (JoinedChannels.Any(c => c.Id == channel.Id))
continue;
// add as available if not already
if (AvailableChannels.All(c => c.Id != channel.Id))
AvailableChannels.Add(channel);
// join any channels classified as "defaults"
if (defaultChannels.Any(c => c.Equals(channel.Name, StringComparison.OrdinalIgnoreCase)))
{
JoinedChannels.Add(channel);
FetchInitalMessages(channel);
}
/*if (joinDefaults && defaultChannels.Any(c => c.Equals(channel.Name, StringComparison.OrdinalIgnoreCase)))
JoinChannel(channel);*/
}
fetchNewMessages();
};
req.Failure += error =>
{
Logger.Error(error, "Fetching channel list failed");
initializeDefaultChannels();
};
@ -285,7 +205,7 @@ namespace osu.Game.Online.Chat
/// right now it caps out at 50 messages and therefore only returns one channel's worth of content.
/// </summary>
/// <param name="channel">The channel </param>
public void FetchInitalMessages(Channel channel)
private void fetchInitalMessages(Channel channel)
{
var fetchInitialMsgReq = new GetMessagesRequest(channel);
fetchInitialMsgReq.Success += handleChannelMessages;
@ -293,6 +213,62 @@ namespace osu.Game.Online.Chat
api.Queue(fetchInitialMsgReq);
}
public void JoinChannel(Channel channel)
{
if (channel == null) return;
// ReSharper disable once AccessToModifiedClosure
var existing = JoinedChannels.FirstOrDefault(c => c.Id == channel.Id);
if (existing != null)
{
// if we already have this channel loaded, we don't want to make a second one.
channel = existing;
}
else
{
var foundSelf = channel.Users.FirstOrDefault(u => u.Id == api.LocalUser.Value.Id);
if (foundSelf != null)
channel.Users.Remove(foundSelf);
JoinedChannels.Add(channel);
if (channel.Type == ChannelType.Public && !channel.Joined)
{
var req = new JoinChannelRequest(channel, api.LocalUser);
req.Success += () => JoinChannel(channel);
req.Failure += ex => LeaveChannel(channel);
api.Queue(req);
return;
}
}
if (CurrentChannel.Value == null)
CurrentChannel.Value = channel;
if (!channel.Joined.Value)
{
// let's fetch a small number of messages to bring us up-to-date with the backlog.
fetchInitalMessages(channel);
channel.Joined.Value = true;
}
}
public void LeaveChannel(Channel channel)
{
if (channel == null) return;
if (channel == CurrentChannel.Value) CurrentChannel.Value = null;
JoinedChannels.Remove(channel);
if (channel.Joined.Value)
{
api.Queue(new LeaveChannelRequest(channel, api.LocalUser));
channel.Joined.Value = false;
}
}
public void APIStateChanged(APIAccess api, APIState state)
{
switch (state)
@ -301,18 +277,53 @@ namespace osu.Game.Online.Chat
if (JoinedChannels.Count == 0)
initializeDefaultChannels();
fetchMessagesScheduleder = Scheduler.AddDelayed(fetchNewMessages, 1000, true);
fetchUpdates();
break;
default:
/*channelMessagesHandler.CancelOngoingRequests();
privateMessagesHandler.CancelOngoingRequests();*/
fetchMessagesScheduleder?.Cancel();
fetchMessagesScheduleder = null;
break;
}
}
private long lastMessageId;
private const int update_poll_interval = 1000;
private void fetchUpdates()
{
fetchMessagesScheduleder?.Cancel();
fetchMessagesScheduleder = Scheduler.AddDelayed(() =>
{
var fetchReq = new GetUpdatesRequest(lastMessageId);
fetchReq.Success += updates =>
{
if (updates?.Presence != null)
{
foreach (var channel in updates.Presence)
{
JoinChannel(AvailableChannels.FirstOrDefault(c => c.Id == channel.Id) ?? channel);
}
//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;
}
fetchUpdates();
};
fetchReq.Failure += delegate { fetchUpdates(); };
api.Queue(fetchReq);
}, update_poll_interval);
}
[BackgroundDependencyLoader]
private void load(IAPIProvider api)
{

View File

@ -1,72 +0,0 @@
// 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.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using osu.Framework.Logging;
using osu.Game.Online.API;
namespace osu.Game.Online.Chat
{
/// <summary>
/// Handles tracking and updating of a specific message type, allowing polling and requesting of only new messages on an ongoing basis.
/// </summary>
public class IncomingMessagesHandler
{
public delegate APIMessagesRequest CreateRequestDelegate(long? lastMessageId);
public long? LastMessageId { get; private set; }
private APIMessagesRequest getMessagesRequest;
private readonly CreateRequestDelegate createRequest;
private readonly Action<List<Message>> onNewMessages;
public bool CanRequestNewMessages => getMessagesRequest == null;
public IncomingMessagesHandler([NotNull] CreateRequestDelegate createRequest, [NotNull] Action<List<Message>> onNewMessages)
{
this.createRequest = createRequest ?? throw new ArgumentNullException(nameof(createRequest));
this.onNewMessages = onNewMessages ?? throw new ArgumentNullException(nameof(onNewMessages));
}
public void RequestNewMessages(IAPIProvider api)
{
if (!CanRequestNewMessages)
throw new InvalidOperationException("Requesting new messages is not possible yet, because the old request is still ongoing.");
getMessagesRequest = createRequest.Invoke(LastMessageId);
getMessagesRequest.Success += handleNewMessages;
getMessagesRequest.Failure += exception =>
{
Logger.Error(exception, "Fetching messages failed.");
// allowing new messages to be requested even after the fail.
getMessagesRequest = null;
};
api.Queue(getMessagesRequest);
}
private void handleNewMessages(List<Message> messages)
{
// allowing new messages to be requested.
getMessagesRequest = null;
// in case of no new messages we simply do nothing.
if (messages == null || messages.Count == 0)
return;
onNewMessages.Invoke(messages);
LastMessageId = messages.Max(m => m.Id) ?? LastMessageId;
}
public void CancelOngoingRequests()
{
getMessagesRequest?.Cancel();
}
}
}

View File

@ -3,7 +3,6 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using Newtonsoft.Json;
using osu.Game.Users;
@ -69,12 +68,4 @@ namespace osu.Game.Online.Chat
// ReSharper disable once ImpureMethodCallOnReadonlyValueField
public override int GetHashCode() => Id.GetHashCode();
}
public enum TargetType
{
[Description(@"channel")]
Channel,
[Description(@"user")]
User
}
}

View File

@ -1,29 +0,0 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Users;
namespace osu.Game.Online.Chat
{
public class PrivateChannel : Channel
{
public User User
{
set
{
Name = value.Username;
Id = value.Id;
JoinedUsers.Add(value);
}
}
/// <summary>
/// Contructs a private channel
/// </summary>
/// <param name="user">The user</param>
public PrivateChannel()
{
Target = TargetType.User;
}
}
}

View File

@ -54,11 +54,11 @@ namespace osu.Game.Overlays.Chat.Tabs
protected override TabItem<Channel> CreateTabItem(Channel value)
{
switch (value.Target)
switch (value.Type)
{
case TargetType.Channel:
case ChannelType.Public:
return new ChannelTabItem(value) { OnRequestClose = tabCloseRequested };
case TargetType.User:
case ChannelType.PM:
return new PrivateChannelTabItem(value) { OnRequestClose = tabCloseRequested };
default:
throw new InvalidOperationException("Only TargetType User and Channel are supported.");

View File

@ -25,7 +25,7 @@ namespace osu.Game.Overlays.Chat.Tabs
public PrivateChannelTabItem(Channel value)
: base(value)
{
if (value.Target != TargetType.User)
if (value.Type != ChannelType.PM)
throw new ArgumentException("Argument value needs to have the targettype user!");
AddRange(new Drawable[]
@ -49,7 +49,7 @@ namespace osu.Game.Overlays.Chat.Tabs
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Masking = true,
Child = new DelayedLoadWrapper(new Avatar(value.JoinedUsers.First())
Child = new DelayedLoadWrapper(new Avatar(value.Users.First())
{
RelativeSizeAxes = Axes.Both,
OnLoadComplete = d => d.FadeInFromZero(300, Easing.OutQuint),
@ -88,7 +88,7 @@ namespace osu.Game.Overlays.Chat.Tabs
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
var user = Value.JoinedUsers.First();
var user = Value.Users.First();
BackgroundActive = user.Colour != null ? OsuColour.FromHex(user.Colour) : colours.BlueDark;
BackgroundInactive = BackgroundActive.Darken(0.5f);

View File

@ -153,7 +153,7 @@ namespace osu.Game.Overlays
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
RelativeSizeAxes = Axes.Both,
OnRequestLeave = channel => channelManager.JoinedChannels.Remove(channel)
OnRequestLeave = channel => channelManager.LeaveChannel(channel)
},
}
},
@ -176,15 +176,9 @@ namespace osu.Game.Overlays
else
textbox.HoldFocus = true;
};
channelSelection.OnRequestJoin = channel =>
{
if (!channelManager.JoinedChannels.Contains(channel))
{
channelManager.JoinedChannels.Add(channel);
channelManager.FetchInitalMessages(channel);
}
};
channelSelection.OnRequestLeave = channel => channelManager.JoinedChannels.Remove(channel);
channelSelection.OnRequestJoin = channel => channelManager.JoinChannel(channel);
channelSelection.OnRequestLeave = channel => channelManager.LeaveChannel(channel);
}
private void joinedChannelsChanged(object sender, NotifyCollectionChangedEventArgs args)
@ -195,18 +189,16 @@ namespace osu.Game.Overlays
foreach (Channel newChannel in args.NewItems)
{
channelTabControl.AddChannel(newChannel);
newChannel.Joined.Value = true;
}
break;
case NotifyCollectionChangedAction.Remove:
foreach (Channel removedChannel in args.OldItems)
{
channelTabControl.RemoveChannel(removedChannel);
loadedChannels.Remove(loadedChannels.Find(c => c.Channel == removedChannel ));
removedChannel.Joined.Value = false;
loadedChannels.Remove(loadedChannels.Find(c => c.Channel == removedChannel));
}
break;
}
}