diff --git a/osu.Desktop.VisualTests/Tests/TestCaseChatDisplay.cs b/osu.Desktop.VisualTests/Tests/TestCaseChatDisplay.cs new file mode 100644 index 0000000000..3305cdcf85 --- /dev/null +++ b/osu.Desktop.VisualTests/Tests/TestCaseChatDisplay.cs @@ -0,0 +1,128 @@ +//Copyright (c) 2007-2016 ppy Pty Ltd . +//Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using osu.Framework.GameModes.Testing; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Transformations; +using osu.Framework.Threading; +using osu.Game; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Online.Chat; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using osu.Framework.Graphics.Sprites; +using osu.Game.Online.Chat.Display.osu.Online.Social; + +namespace osu.Desktop.Tests +{ + class TestCaseChatDisplay : TestCase + { + private ScheduledDelegate messageRequest; + + public override string Name => @"Chat"; + public override string Description => @"Testing API polling"; + + private List channels = new List(); + private FlowContainer flow; + + private Scheduler scheduler = new Scheduler(); + + private APIAccess api => ((OsuGameBase)Game).API; + + private long? lastMessageId; + + public override void Reset() + { + base.Reset(); + + lastMessageId = null; + + if (api.State != APIAccess.APIState.Online) + api.OnStateChange += delegate { initializeChannels(); }; + else + initializeChannels(); + + Add(new ScrollContainer() + { + Size = new Vector2(1, 0.5f), + Children = new Drawable[] + { + flow = new FlowContainer + { + Direction = FlowDirection.VerticalOnly, + SizeMode = InheritMode.X, + LayoutDuration = 100, + LayoutEasing = EasingTypes.Out, + Padding = new Vector2(1, 1) + } + } + }); + } + + protected override void Update() + { + scheduler.Update(); + base.Update(); + } + + private void initializeChannels() + { + if (api.State != APIAccess.APIState.Online) + return; + + messageRequest?.Cancel(); + + ListChannelsRequest req = new ListChannelsRequest(); + req.Success += delegate (List channels) + { + this.channels = channels; + messageRequest = scheduler.AddDelayed(requestNewMessages, 1000, true); + }; + api.Queue(req); + } + + private void requestNewMessages() + { + messageRequest.Wait(); + + Channel channel = channels.Find(c => c.Name == "#osu"); + + GetMessagesRequest gm = new GetMessagesRequest(new List { channel }, lastMessageId); + gm.Success += delegate (List messages) + { + foreach (Message m in messages) + { + //m.LineWidth = this.Size.X; //this is kinda ugly. + //m.Drawable.Depth = m.Id; + //m.Drawable.FadeInFromZero(800); + + //flow.Add(m.Drawable); + + //if (osu.Messages.Count > 50) + //{ + // osu.Messages[0].Drawable.Expire(); + // osu.Messages.RemoveAt(0); + //} + flow.Add(new ChatLine(m)); + channel.Messages.Add(m); + } + + lastMessageId = messages.LastOrDefault()?.Id ?? lastMessageId; + + Debug.Write("success!"); + messageRequest.Continue(); + }; + gm.Failure += delegate + { + Debug.Write("failure!"); + messageRequest.Continue(); + }; + + api.Queue(gm); + } + } +} diff --git a/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj b/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj index f27abf9cc7..b731d53b4b 100644 --- a/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj +++ b/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj @@ -150,6 +150,7 @@ + diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index 59b3d91d51..bb72bdf02e 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -5,21 +5,25 @@ using System; using System.Collections.Concurrent; using System.Net; using System.Threading; +using osu.Framework; using osu.Framework.Logging; +using osu.Framework.Threading; using osu.Game.Online.API.Requests; namespace osu.Game.Online.API { - internal class APIAccess + public class APIAccess : IUpdateable { private OAuth authentication; - internal string Endpoint = @"https://new.ppy.sh"; + public string Endpoint = @"https://new.ppy.sh"; const string ClientId = @"daNBnfdv7SppRVc61z0XuOI13y6Hroiz"; const string ClientSecret = @"d6fgZuZeQ0eSXkEj5igdqQX6ztdtS6Ow"; ConcurrentQueue queue = new ConcurrentQueue(); + public Scheduler Scheduler = new Scheduler(); + public string Username; private SecurePassword password; @@ -52,7 +56,7 @@ namespace osu.Game.Online.API Logger log; - internal APIAccess() + public APIAccess() { authentication = new OAuth(ClientId, ClientSecret, Endpoint); log = Logger.GetLogger(LoggingTarget.Network); @@ -61,7 +65,7 @@ namespace osu.Game.Online.API thread.Start(); } - internal string AccessToken => authentication.RequestAccessToken(); + public string AccessToken => authentication.RequestAccessToken(); /// /// Number of consecutive requests which failed due to network issues. @@ -221,16 +225,16 @@ namespace osu.Game.Online.API } } - internal void Queue(APIRequest request) + public void Queue(APIRequest request) { queue.Enqueue(request); } - internal event StateChangeDelegate OnStateChange; + public event StateChangeDelegate OnStateChange; - internal delegate void StateChangeDelegate(APIState oldState, APIState newState); + public delegate void StateChangeDelegate(APIState oldState, APIState newState); - internal enum APIState + public enum APIState { /// /// We cannot login (not enough credentials). @@ -268,10 +272,15 @@ namespace osu.Game.Online.API } } - internal void Logout() + public void Logout() { authentication.Clear(); State = APIState.Offline; } + + public void Update() + { + Scheduler.Update(); + } } } diff --git a/osu.Game/Online/API/APIRequest.cs b/osu.Game/Online/API/APIRequest.cs index 3de942b998..a63530ab79 100644 --- a/osu.Game/Online/API/APIRequest.cs +++ b/osu.Game/Online/API/APIRequest.cs @@ -11,7 +11,7 @@ namespace osu.Game.Online.API /// An API request with a well-defined response type. /// /// Type of the response (used for deserialisation). - internal class APIRequest : APIRequest + public class APIRequest : APIRequest { protected override WebRequest CreateWebRequest() => new JsonWebRequest(Uri); @@ -36,7 +36,7 @@ namespace osu.Game.Online.API /// /// The maximum amount of time before this request will fail. /// - internal int Timeout = WebRequest.DEFAULT_TIMEOUT; + public int Timeout = WebRequest.DEFAULT_TIMEOUT; protected virtual string Target => string.Empty; @@ -46,11 +46,11 @@ namespace osu.Game.Online.API private double remainingTime => Math.Max(0, Timeout - (DateTime.Now.TotalMilliseconds() - (startTime ?? 0))); - internal bool ExceededTimeout => remainingTime == 0; + public bool ExceededTimeout => remainingTime == 0; private double? startTime; - internal double StartTime => startTime ?? -1; + public double StartTime => startTime ?? -1; private APIAccess api; protected WebRequest WebRequest; @@ -58,7 +58,7 @@ namespace osu.Game.Online.API public event APISuccessHandler Success; public event APIFailureHandler Failure; - internal void Perform(APIAccess api) + public void Perform(APIAccess api) { if (startTime == null) startTime = DateTime.Now.TotalMilliseconds(); @@ -74,17 +74,16 @@ namespace osu.Game.Online.API WebRequest.BlockingPerform(); - //OsuGame.Scheduler.Add(delegate { - Success?.Invoke(); - //}); + api.Scheduler.Add(delegate { Success?.Invoke(); }); } - internal void Fail(Exception e) + public void Fail(Exception e) { WebRequest?.Abort(); - //OsuGame.Scheduler.Add(delegate { + api.Scheduler.Add(delegate + { Failure?.Invoke(e); - //}); + }); } } diff --git a/osu.Game/Online/API/Requests/GetMessagesRequest.cs b/osu.Game/Online/API/Requests/GetMessagesRequest.cs index 4de206e8c0..96ded02d55 100644 --- a/osu.Game/Online/API/Requests/GetMessagesRequest.cs +++ b/osu.Game/Online/API/Requests/GetMessagesRequest.cs @@ -7,7 +7,7 @@ using osu.Game.Online.Chat; namespace osu.Game.Online.API.Requests { - internal class GetMessagesRequest : APIRequest> + public class GetMessagesRequest : APIRequest> { List channels; long? since; diff --git a/osu.Game/Online/API/Requests/ListChannels.cs b/osu.Game/Online/API/Requests/ListChannels.cs index 5592f8aa04..ecee48a938 100644 --- a/osu.Game/Online/API/Requests/ListChannels.cs +++ b/osu.Game/Online/API/Requests/ListChannels.cs @@ -6,7 +6,7 @@ using osu.Game.Online.Chat; namespace osu.Game.Online.API.Requests { - internal class ListChannelsRequest : APIRequest> + public class ListChannelsRequest : APIRequest> { protected override string Target => @"chat/channels"; } diff --git a/osu.Game/Online/Chat/Display/ChatLine.cs b/osu.Game/Online/Chat/Display/ChatLine.cs new file mode 100644 index 0000000000..7e11aeb068 --- /dev/null +++ b/osu.Game/Online/Chat/Display/ChatLine.cs @@ -0,0 +1,62 @@ +//Copyright (c) 2007-2016 ppy Pty Ltd . +//Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Drawables; +using osu.Framework.Graphics.Sprites; +using OpenTK; +using OpenTK.Graphics; + +namespace osu.Game.Online.Chat.Display +{ + namespace osu.Online.Social + { + public class ChatLine : AutoSizeContainer + { + private readonly Message msg; + + public ChatLine(Message msg) + { + this.msg = msg; + } + + public override void Load() + { + base.Load(); + + SizeMode = InheritMode.X; + + Add(new Box + { + SizeMode = InheritMode.XY, + Colour = Color4.Aqua, + Alpha = 0.2f + }); + + Add(new SpriteText + { + Text = msg.Timestamp.ToLocalTime().ToLongTimeString(), + Colour = new Color4(128, 128, 128, 255) + }); + + Add(new SpriteText + { + Text = msg.User.Name, + Origin = Anchor.TopRight, + PositionMode = InheritMode.X, + Position = new Vector2(0.14f,0), + }); + + Add(new SpriteText + { + Text = msg.Content, + PositionMode = InheritMode.X, + Position = new Vector2(0.15f, 0), + SizeMode = InheritMode.X, + Size = new Vector2(0.85f, 1), + }); + } + } + } +} diff --git a/osu.Game/Online/Chat/Message.cs b/osu.Game/Online/Chat/Message.cs index 1c3db48f4e..7f9f6ece2c 100644 --- a/osu.Game/Online/Chat/Message.cs +++ b/osu.Game/Online/Chat/Message.cs @@ -24,7 +24,7 @@ namespace osu.Game.Online.Chat public string Content; [JsonProperty(@"sender")] - public string User; + public User User; [JsonConstructor] public Message() diff --git a/osu.Game/Online/User.cs b/osu.Game/Online/User.cs new file mode 100644 index 0000000000..27de5d8dc2 --- /dev/null +++ b/osu.Game/Online/User.cs @@ -0,0 +1,16 @@ +//Copyright (c) 2007-2016 ppy Pty Ltd . +//Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using Newtonsoft.Json; + +namespace osu.Game.Online +{ + public class User + { + [JsonProperty(@"username")] + public string Name; + + [JsonProperty(@"colour")] + public string Colour; + } +} diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 65e200ea55..475adb22e2 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -26,14 +26,6 @@ namespace osu.Game Add(new MainMenu()); } - protected override void Dispose(bool isDisposing) - { - //refresh token may have changed. - Config.Set(OsuConfig.Token, API.Token); - - base.Dispose(isDisposing); - } - public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true) { if (!base.Invalidate(invalidation, source, shallPropagate)) return false; diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 20e7153575..f57e86763a 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -16,7 +16,7 @@ namespace osu.Game protected override string MainResourceFile => @"osu.Game.Resources.dll"; - internal APIAccess API; + public APIAccess API; protected override Container AddTarget => ratioContainer?.IsLoaded == true ? ratioContainer : base.AddTarget; @@ -50,5 +50,20 @@ namespace osu.Game } }); } + + protected override void Update() + { + base.Update(); + API.Update(); + } + + protected override void Dispose(bool isDisposing) + { + //refresh token may have changed. + Config.Set(OsuConfig.Token, API.Token); + Config.Save(); + + base.Dispose(isDisposing); + } } } diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 888350f780..7950774c23 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -99,7 +99,9 @@ + +