feat: Manage joined channels in AppOrchestrator and update channel history handling

This commit is contained in:
HueByte
2026-02-19 08:03:30 +01:00
parent eef9059b6a
commit 96b31ec568
4 changed files with 62 additions and 22 deletions
+17 -3
View File
@@ -28,6 +28,7 @@ public sealed class AppOrchestrator : IDisposable
private UserStatus _currentStatus = UserStatus.Online;
private string? _currentStatusMessage;
private string _currentUsername = string.Empty;
private readonly HashSet<string> _joinedChannels = [];
private bool IsConnected => _connection is not null && _connection.IsConnected;
private bool IsAuthenticated => _apiClient is not null;
@@ -161,6 +162,7 @@ public sealed class AppOrchestrator : IDisposable
try
{
_joinedChannels.Add(channelName);
var history = await _connection!.JoinChannelAsync(channelName);
InvokeUI(() =>
{
@@ -185,6 +187,7 @@ public sealed class AppOrchestrator : IDisposable
try
{
await _connection!.LeaveChannelAsync(channel);
_joinedChannels.Remove(channel);
InvokeUI(() => _mainWindow.AddSystemMessage(channel, $"You left #{channel}"));
}
catch (Exception ex)
@@ -294,6 +297,8 @@ public sealed class AppOrchestrator : IDisposable
_mainWindow.UpdateStatusBar("Connected");
});
_joinedChannels.Clear();
_joinedChannels.Add(HubConstants.DefaultChannel);
await _connection.JoinChannelAsync(HubConstants.DefaultChannel);
InvokeUI(() => _mainWindow.SwitchToChannel(HubConstants.DefaultChannel));
@@ -330,6 +335,7 @@ public sealed class AppOrchestrator : IDisposable
_apiClient?.Dispose();
_apiClient = null;
_joinedChannels.Clear();
InvokeUI(() =>
{
@@ -377,11 +383,12 @@ public sealed class AppOrchestrator : IDisposable
RunAsync(async () =>
{
await _connection!.JoinChannelAsync(channelName);
if (_joinedChannels.Add(channelName))
await _connection!.JoinChannelAsync(channelName);
try
{
var history = await _connection.GetHistoryAsync(channelName);
var history = await _connection!.GetHistoryAsync(channelName);
InvokeUI(() => _mainWindow.LoadHistory(channelName, history));
}
catch
@@ -530,6 +537,7 @@ public sealed class AppOrchestrator : IDisposable
var channel = await _apiClient!.CreateChannelAsync(result.Name, result.Topic);
if (channel is null) return;
_joinedChannels.Add(channel.Name);
var history = await _connection!.JoinChannelAsync(channel.Name);
// Refresh the channel list
@@ -579,13 +587,19 @@ public sealed class AppOrchestrator : IDisposable
connection.OnReconnected += () =>
{
var channels = _mainWindow.GetChannelNames();
// Server-side state is lost on reconnect — rejoin all channels
var channels = _joinedChannels.ToList();
if (channels.Count == 0) return;
_joinedChannels.Clear();
RunAsync(async () =>
{
foreach (var channel in channels)
{
_joinedChannels.Add(channel);
await _connection!.JoinChannelAsync(channel);
}
Log.Information("Rejoined {Count} channel(s) after reconnect", channels.Count);
}, "Failed to rejoin channels after reconnect");
+2 -8
View File
@@ -544,18 +544,12 @@ public sealed class MainWindow : Runnable
}
/// <summary>
/// Load historical messages into a channel (prepend).
/// Load historical messages into a channel, replacing any existing messages.
/// </summary>
public void LoadHistory(string channelName, List<MessageDto> messages)
{
if (!_channelMessages.TryGetValue(channelName, out var existing))
{
existing = [];
_channelMessages[channelName] = existing;
}
var formatted = messages.SelectMany(FormatMessage).ToList();
existing.InsertRange(0, formatted);
_channelMessages[channelName] = formatted;
if (channelName == _currentChannel)
{
+10 -9
View File
@@ -112,12 +112,14 @@ public class ChatHub(EchoHubDbContext db, ILogger<ChatHub> logger, PresenceTrack
return [];
}
presenceTracker.JoinChannel(CurrentUsername, channelName);
var isNewJoin = presenceTracker.JoinChannel(CurrentUsername, channelName);
await Groups.AddToGroupAsync(Context.ConnectionId, channelName);
await Clients.OthersInGroup(channelName).UserJoined(channelName, CurrentUsername);
logger.LogInformation("{User} joined channel '{Channel}'", CurrentUsername, channelName);
if (isNewJoin)
{
await Groups.AddToGroupAsync(Context.ConnectionId, channelName);
await Clients.OthersInGroup(channelName).UserJoined(channelName, CurrentUsername);
logger.LogInformation("{User} joined channel '{Channel}'", CurrentUsername, channelName);
}
var history = await GetChannelHistory(channelName, HubConstants.DefaultHistoryCount);
return history;
@@ -293,11 +295,10 @@ public class ChatHub(EchoHubDbContext db, ILogger<ChatHub> logger, PresenceTrack
statusMessage);
var channels = presenceTracker.GetChannelsForUser(CurrentUsername);
var connections = presenceTracker.GetConnectionsInChannels(channels);
foreach (var channel in channels)
{
await Clients.Group(channel).UserStatusChanged(presence);
}
if (connections.Count > 0)
await Clients.Clients(connections).UserStatusChanged(presence);
}
catch (Exception ex)
{
+33 -2
View File
@@ -50,7 +50,10 @@ public class PresenceTracker
return username;
}
public void JoinChannel(string username, string channelName)
/// <summary>
/// Returns true if this is a new join, false if the user was already in the channel.
/// </summary>
public bool JoinChannel(string username, string channelName)
{
lock (_lock)
{
@@ -60,7 +63,7 @@ public class PresenceTracker
_userChannels[username] = channels;
}
channels.Add(channelName);
return channels.Add(channelName);
}
}
@@ -106,6 +109,34 @@ public class PresenceTracker
return [];
}
/// <summary>
/// Get all unique connection IDs for users who share any of the given channels.
/// </summary>
public List<string> GetConnectionsInChannels(List<string> channels)
{
lock (_lock)
{
var usernames = new HashSet<string>();
foreach (var channel in channels)
{
foreach (var (username, userChannels) in _userChannels)
{
if (userChannels.Contains(channel))
usernames.Add(username);
}
}
var connections = new List<string>();
foreach (var username in usernames)
{
if (_userConnections.TryGetValue(username, out var conns))
connections.AddRange(conns);
}
return connections;
}
}
public bool IsOnline(string username)
{
return _userConnections.TryGetValue(username, out var connections) && connections.Count > 0;