feat: add preferences and better backlog ordering
This commit is contained in:
parent
98cdf7916e
commit
fa5980c61b
|
@ -1,9 +1,10 @@
|
|||
using Newtonsoft.Json;
|
||||
using Sodium;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
@ -100,7 +101,7 @@ namespace XIVChat_Desktop {
|
|||
}
|
||||
|
||||
[JsonObject]
|
||||
public class Tab : INotifyPropertyChanged {
|
||||
public class Tab : IEnumerable<ServerMessage>, INotifyCollectionChanged {
|
||||
private string name;
|
||||
|
||||
public Tab(string name) {
|
||||
|
@ -111,25 +112,53 @@ namespace XIVChat_Desktop {
|
|||
get => this.name;
|
||||
set {
|
||||
this.name = value;
|
||||
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(this.Name)));
|
||||
}
|
||||
}
|
||||
|
||||
public Filter Filter { get; set; } = new Filter();
|
||||
|
||||
[JsonIgnore]
|
||||
public ObservableCollection<ServerMessage> Messages { get; } = new ObservableCollection<ServerMessage>();
|
||||
public List<ServerMessage> Messages { get; } = new List<ServerMessage>();
|
||||
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
private void NotifyReset() {
|
||||
this.CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
|
||||
}
|
||||
|
||||
public void RepopulateMessages(ConcurrentStack<ServerMessage> mainMessages) {
|
||||
private void NotifyAdd(ServerMessage message) {
|
||||
this.CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, message));
|
||||
}
|
||||
|
||||
private void NotifyAddItemsAt(IList messages, int index) {
|
||||
this.CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, messages, index));
|
||||
}
|
||||
|
||||
public void RepopulateMessages(IEnumerable<ServerMessage> mainMessages) {
|
||||
this.Messages.Clear();
|
||||
|
||||
foreach (var message in mainMessages.Where(msg => this.Filter.Allowed(msg)).Reverse()) {
|
||||
// add messages from newest to oldest
|
||||
foreach (var message in mainMessages.Where(msg => this.Filter.Allowed(msg))) {
|
||||
this.Messages.Add(message);
|
||||
}
|
||||
|
||||
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(this.Messages)));
|
||||
this.NotifyReset();
|
||||
}
|
||||
|
||||
private int lastSequence = -1;
|
||||
private int insertAt;
|
||||
|
||||
public void AddReversedChunk(ServerMessage[] messages, int sequence) {
|
||||
if (sequence != this.lastSequence) {
|
||||
this.lastSequence = sequence;
|
||||
this.insertAt = this.Messages.Count;
|
||||
}
|
||||
|
||||
var filtered = messages
|
||||
.Where(msg => msg.Channel == 0 || this.Filter.Allowed(msg))
|
||||
.ToList();
|
||||
|
||||
this.Messages.InsertRange(this.insertAt, filtered);
|
||||
|
||||
this.NotifyAddItemsAt(filtered, this.insertAt);
|
||||
}
|
||||
|
||||
public void AddMessage(ServerMessage message) {
|
||||
|
@ -138,13 +167,12 @@ namespace XIVChat_Desktop {
|
|||
}
|
||||
|
||||
this.Messages.Add(message);
|
||||
|
||||
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(this.Messages)));
|
||||
this.NotifyAdd(message);
|
||||
}
|
||||
|
||||
public void ClearMessages() {
|
||||
this.Messages.Clear();
|
||||
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(this.Messages)));
|
||||
this.NotifyReset();
|
||||
}
|
||||
|
||||
public static Filter GeneralFilter() {
|
||||
|
@ -177,6 +205,16 @@ namespace XIVChat_Desktop {
|
|||
},
|
||||
};
|
||||
}
|
||||
|
||||
public IEnumerator<ServerMessage> GetEnumerator() {
|
||||
return this.Messages.GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() {
|
||||
return this.GetEnumerator();
|
||||
}
|
||||
|
||||
public event NotifyCollectionChangedEventHandler? CollectionChanged;
|
||||
}
|
||||
|
||||
[JsonObject]
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Net.Sockets;
|
||||
|
@ -7,6 +8,7 @@ using System.Threading.Channels;
|
|||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using XIVChatCommon;
|
||||
using DispatcherPriority = System.Windows.Threading.DispatcherPriority;
|
||||
|
||||
namespace XIVChat_Desktop {
|
||||
public class Connection : INotifyPropertyChanged {
|
||||
|
@ -85,6 +87,16 @@ namespace XIVChat_Desktop {
|
|||
this.app.Window.AddSystemMessage("Connected");
|
||||
});
|
||||
|
||||
// tell the server our preferences
|
||||
var preferences = new ClientPreferences {
|
||||
Preferences = new Dictionary<ClientPreference, object> {
|
||||
{
|
||||
ClientPreference.BacklogNewestMessagesFirst, true
|
||||
},
|
||||
},
|
||||
};
|
||||
await SecretMessage.SendSecretMessage(stream, handshake.Keys.tx, preferences, this.cancel.Token);
|
||||
|
||||
// check if backlog or catch-up is needed
|
||||
if (sameHost) {
|
||||
// catch-up
|
||||
|
@ -235,9 +247,14 @@ namespace XIVChat_Desktop {
|
|||
case ServerOperation.Backlog:
|
||||
var backlog = ServerBacklog.Decode(payload);
|
||||
|
||||
foreach (var msg in backlog.messages) {
|
||||
this.Dispatch(() => {
|
||||
this.app.Window.AddMessage(msg);
|
||||
this.backlogSequence += 1;
|
||||
|
||||
var seq = this.backlogSequence;
|
||||
foreach (var msg in backlog.messages.ToList().Chunks(100)) {
|
||||
msg.Reverse();
|
||||
var array = msg.ToArray();
|
||||
this.Dispatch(DispatcherPriority.Background, () => {
|
||||
this.app.Window.AddReversedChunk(array, seq);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -249,6 +266,8 @@ namespace XIVChat_Desktop {
|
|||
}
|
||||
}
|
||||
|
||||
private int backlogSequence = -1;
|
||||
|
||||
private void SetPlayerData(PlayerData? playerData) {
|
||||
var visibility = playerData == null ? Visibility.Collapsed : Visibility.Visible;
|
||||
|
||||
|
@ -271,7 +290,11 @@ namespace XIVChat_Desktop {
|
|||
}
|
||||
|
||||
private void Dispatch(Action action) {
|
||||
this.app.Dispatcher.BeginInvoke(action);
|
||||
this.Dispatch(DispatcherPriority.Normal, action);
|
||||
}
|
||||
|
||||
private void Dispatch(DispatcherPriority priority, Action action) {
|
||||
this.app.Dispatcher.BeginInvoke(priority, action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -63,7 +63,7 @@
|
|||
<ItemsControl Background="#333"
|
||||
x:Name="items"
|
||||
Padding="4"
|
||||
ItemsSource="{Binding Messages, UpdateSourceTrigger=PropertyChanged}">
|
||||
ItemsSource="{Binding .}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<VirtualizingStackPanel IsVirtualizing="True"
|
||||
|
@ -110,4 +110,4 @@
|
|||
Visibility="Collapsed" />
|
||||
</StatusBar>
|
||||
</Grid>
|
||||
</Window>
|
||||
</Window>
|
||||
|
|
|
@ -15,7 +15,7 @@ namespace XIVChat_Desktop {
|
|||
public partial class MainWindow {
|
||||
public App App => (App)Application.Current;
|
||||
|
||||
public ConcurrentStack<ServerMessage> Messages { get; } = new ConcurrentStack<ServerMessage>();
|
||||
public List<ServerMessage> Messages { get; } = new List<ServerMessage>();
|
||||
|
||||
public MainWindow() {
|
||||
this.InitializeComponent();
|
||||
|
@ -67,13 +67,39 @@ namespace XIVChat_Desktop {
|
|||
this.AddMessage(message);
|
||||
}
|
||||
|
||||
private int lastSequence = -1;
|
||||
private int insertAt;
|
||||
|
||||
public void AddReversedChunk(ServerMessage[] messages, int sequence) {
|
||||
if (sequence != this.lastSequence) {
|
||||
this.lastSequence = sequence;
|
||||
this.insertAt = this.Messages.Count;
|
||||
}
|
||||
|
||||
// detect if scroller is at the bottom
|
||||
var scroller = this.FindElementByName<ScrollViewer>(this.Tabs, "scroller");
|
||||
var wasAtBottom = Math.Abs(scroller!.VerticalOffset - scroller.ScrollableHeight) < .0001;
|
||||
|
||||
// add messages to main list
|
||||
this.Messages.InsertRange(this.insertAt, messages);
|
||||
// add message to each tab if the filter allows for it
|
||||
foreach (var tab in this.App.Config.Tabs) {
|
||||
tab.AddReversedChunk(messages, sequence);
|
||||
}
|
||||
|
||||
// scroll to the bottom if previously at the bottom
|
||||
if (wasAtBottom) {
|
||||
scroller.ScrollToBottom();
|
||||
}
|
||||
}
|
||||
|
||||
public void AddMessage(ServerMessage message) {
|
||||
// detect if scroller is at the bottom
|
||||
var scroller = this.FindElementByName<ScrollViewer>(this.Tabs, "scroller");
|
||||
var wasAtBottom = Math.Abs(scroller!.VerticalOffset - scroller.ScrollableHeight) < .0001;
|
||||
|
||||
// add message to main list
|
||||
this.Messages.Push(message);
|
||||
this.Messages.Add(message);
|
||||
// add message to each tab if the filter allows for it
|
||||
foreach (var tab in this.App.Config.Tabs) {
|
||||
tab.AddMessage(message);
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
using MessagePack.Formatters;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
|
||||
// ReSharper disable UnusedAutoPropertyAccessor.Global
|
||||
// ReSharper disable UnusedMember.Global
|
||||
// ReSharper disable ClassNeverInstantiated.Global
|
||||
|
@ -36,7 +38,7 @@ namespace XIVChatCommon {
|
|||
|
||||
[IgnoreMember]
|
||||
protected override byte Code => (byte)ServerOperation.Message;
|
||||
|
||||
|
||||
public ServerMessage(DateTime timestamp, ChatType channel, byte[] sender, byte[] content, List<Chunk> chunks) {
|
||||
this.Timestamp = timestamp;
|
||||
this.Channel = channel;
|
||||
|
@ -76,7 +78,7 @@ namespace XIVChatCommon {
|
|||
|
||||
[Key(4)]
|
||||
public string Content { get; set; }
|
||||
|
||||
|
||||
public TextChunk(string content) {
|
||||
this.Content = content;
|
||||
}
|
||||
|
@ -601,7 +603,7 @@ namespace XIVChatCommon {
|
|||
|
||||
[IgnoreMember]
|
||||
protected override byte Code => (byte)ClientOperation.Message;
|
||||
|
||||
|
||||
public ClientMessage(string content) {
|
||||
this.Content = content;
|
||||
}
|
||||
|
@ -839,7 +841,7 @@ namespace XIVChatCommon {
|
|||
public ClientCatchUp(DateTime after) {
|
||||
this.After = after;
|
||||
}
|
||||
|
||||
|
||||
public static ClientCatchUp Decode(byte[] bytes) {
|
||||
return MessagePackSerializer.Deserialize<ClientCatchUp>(bytes);
|
||||
}
|
||||
|
@ -858,7 +860,7 @@ namespace XIVChatCommon {
|
|||
public Player[] Players { get; set; }
|
||||
|
||||
protected override byte Code => (byte)ServerOperation.PlayerList;
|
||||
|
||||
|
||||
public ServerPlayerList(PlayerListType type, Player[] players) {
|
||||
this.Type = type;
|
||||
this.Players = players;
|
||||
|
@ -944,6 +946,57 @@ namespace XIVChatCommon {
|
|||
public byte MainLanguage { get; set; }
|
||||
}
|
||||
|
||||
[MessagePackObject]
|
||||
public class ClientPreferences : IEncodable {
|
||||
[Key(0)]
|
||||
public Dictionary<ClientPreference, object> Preferences { get; set; } = new Dictionary<ClientPreference, object>();
|
||||
|
||||
protected override byte Code => (byte)ClientOperation.Preferences;
|
||||
|
||||
protected override byte[] PayloadEncode() {
|
||||
return MessagePackSerializer.Serialize(this);
|
||||
}
|
||||
|
||||
public static ClientPreferences Decode(byte[] bytes) {
|
||||
return MessagePackSerializer.Deserialize<ClientPreferences>(bytes);
|
||||
}
|
||||
}
|
||||
|
||||
public enum ClientPreference {
|
||||
[Preference(typeof(bool))]
|
||||
BacklogNewestMessagesFirst,
|
||||
}
|
||||
|
||||
public class PreferenceAttribute : Attribute {
|
||||
public Type ValueType { get; }
|
||||
|
||||
public PreferenceAttribute(Type valueType) {
|
||||
this.ValueType = valueType;
|
||||
}
|
||||
}
|
||||
|
||||
public static class ClientPreferencesExtension {
|
||||
public static bool TryGetValue<T>(this ClientPreferences prefs, ClientPreference pref, out T value) {
|
||||
value = default!;
|
||||
|
||||
if (!prefs.Preferences.TryGetValue(pref, out var obj)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var attr = pref
|
||||
.GetType()
|
||||
.GetField(pref.ToString())
|
||||
?.GetCustomAttribute<PreferenceAttribute>(false);
|
||||
|
||||
if (obj.GetType() != typeof(T) || obj.GetType() != attr?.ValueType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
value = (T)obj;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class IEncodable {
|
||||
protected abstract byte Code { get; }
|
||||
protected abstract byte[] PayloadEncode();
|
||||
|
@ -981,6 +1034,7 @@ namespace XIVChatCommon {
|
|||
CatchUp = 5,
|
||||
PlayerList = 6,
|
||||
LinkshellList = 7,
|
||||
Preferences = 8,
|
||||
}
|
||||
|
||||
public class MillisecondsDateTimeFormatter : IMessagePackFormatter<DateTime> {
|
||||
|
|
|
@ -377,7 +377,10 @@ namespace XIVChatPlugin {
|
|||
node = node.Previous;
|
||||
}
|
||||
|
||||
backlogMessages.Reverse();
|
||||
bool bNewestFirst = false;
|
||||
if (client.Preferences?.TryGetValue(ClientPreference.BacklogNewestMessagesFirst, out bNewestFirst) == true && !bNewestFirst) {
|
||||
backlogMessages.Reverse();
|
||||
}
|
||||
|
||||
await SendBacklogs(backlogMessages.ToArray(), stream, client);
|
||||
break;
|
||||
|
@ -387,6 +390,11 @@ namespace XIVChatPlugin {
|
|||
var after = catchUp.After.AddMilliseconds(1);
|
||||
var msgs = this.MessagesAfter(after);
|
||||
|
||||
bool cNewestFirst = false;
|
||||
if (client.Preferences?.TryGetValue(ClientPreference.BacklogNewestMessagesFirst, out cNewestFirst) == true && cNewestFirst) {
|
||||
msgs = msgs.Reverse();
|
||||
}
|
||||
|
||||
await SendBacklogs(msgs, stream, client);
|
||||
break;
|
||||
case ClientOperation.PlayerList:
|
||||
|
@ -400,6 +408,11 @@ namespace XIVChatPlugin {
|
|||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case ClientOperation.Preferences:
|
||||
var preferences = ClientPreferences.Decode(payload);
|
||||
client.Preferences = preferences;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -789,6 +802,7 @@ namespace XIVChatPlugin {
|
|||
public bool Connected { get; set; }
|
||||
public TcpClient Conn { get; }
|
||||
public HandshakeInfo? Handshake { get; set; }
|
||||
public ClientPreferences? Preferences { get; set; }
|
||||
public CancellationTokenSource TokenSource { get; } = new CancellationTokenSource();
|
||||
public Channel<ServerMessage> Queue { get; } = Channel.CreateUnbounded<ServerMessage>();
|
||||
|
||||
|
|
Loading…
Reference in New Issue