Beginnings of moving channel handling out of the main context or random assignments.
This commit is contained in:
parent
f1d4051fb5
commit
80475a9180
17 changed files with 209 additions and 78 deletions
SharpChat
C2SPacketHandlers
ClientCommandContext.csClientCommands
CreateChannelClientCommand.csDeleteChannelClientCommand.csJoinChannelClientCommand.csRankChannelClientCommand.csWhoClientCommand.cs
Context.csSockChatServer.csSharpChatCommon/Channels
|
@ -1,5 +1,6 @@
|
||||||
using SharpChat.Auth;
|
using SharpChat.Auth;
|
||||||
using SharpChat.Bans;
|
using SharpChat.Bans;
|
||||||
|
using SharpChat.Channels;
|
||||||
using SharpChat.Configuration;
|
using SharpChat.Configuration;
|
||||||
using SharpChat.SockChat.S2CPackets;
|
using SharpChat.SockChat.S2CPackets;
|
||||||
|
|
||||||
|
@ -8,14 +9,10 @@ namespace SharpChat.C2SPacketHandlers;
|
||||||
public class AuthC2SPacketHandler(
|
public class AuthC2SPacketHandler(
|
||||||
AuthClient authClient,
|
AuthClient authClient,
|
||||||
BansClient bansClient,
|
BansClient bansClient,
|
||||||
Channel defaultChannel,
|
ChannelsContext channelsCtx,
|
||||||
CachedValue<int> maxMsgLength,
|
CachedValue<int> maxMsgLength,
|
||||||
CachedValue<int> maxConns
|
CachedValue<int> maxConns
|
||||||
) : C2SPacketHandler {
|
) : C2SPacketHandler {
|
||||||
private readonly Channel DefaultChannel = defaultChannel ?? throw new ArgumentNullException(nameof(defaultChannel));
|
|
||||||
private readonly CachedValue<int> MaxMessageLength = maxMsgLength ?? throw new ArgumentNullException(nameof(maxMsgLength));
|
|
||||||
private readonly CachedValue<int> MaxConnections = maxConns ?? throw new ArgumentNullException(nameof(maxConns));
|
|
||||||
|
|
||||||
public bool IsMatch(C2SPacketHandlerContext ctx) {
|
public bool IsMatch(C2SPacketHandlerContext ctx) {
|
||||||
return ctx.CheckPacketId("1");
|
return ctx.CheckPacketId("1");
|
||||||
}
|
}
|
||||||
|
@ -75,7 +72,7 @@ public class AuthC2SPacketHandler(
|
||||||
);
|
);
|
||||||
|
|
||||||
// Enforce a maximum amount of connections per user
|
// Enforce a maximum amount of connections per user
|
||||||
if(ctx.Chat.Connections.Count(conn => conn.User == user) >= MaxConnections) {
|
if(ctx.Chat.Connections.Count(conn => conn.User == user) >= maxConns) {
|
||||||
await ctx.Connection.Send(new AuthFailS2CPacket(AuthFailS2CPacket.Reason.MaxSessions));
|
await ctx.Connection.Send(new AuthFailS2CPacket(AuthFailS2CPacket.Reason.MaxSessions));
|
||||||
ctx.Connection.Dispose();
|
ctx.Connection.Dispose();
|
||||||
return;
|
return;
|
||||||
|
@ -93,7 +90,7 @@ public class AuthC2SPacketHandler(
|
||||||
await ctx.Connection.Send(new CommandResponseS2CPacket(0, LCR.WELCOME, false, line));
|
await ctx.Connection.Send(new CommandResponseS2CPacket(0, LCR.WELCOME, false, line));
|
||||||
}
|
}
|
||||||
|
|
||||||
await ctx.Chat.HandleJoin(user, DefaultChannel, ctx.Connection, MaxMessageLength);
|
await ctx.Chat.HandleJoin(user, channelsCtx.DefaultChannel, ctx.Connection, maxMsgLength);
|
||||||
} finally {
|
} finally {
|
||||||
ctx.Chat.ContextAccess.Release();
|
ctx.Chat.ContextAccess.Release();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
using SharpChat.Channels;
|
||||||
using SharpChat.Configuration;
|
using SharpChat.Configuration;
|
||||||
using SharpChat.Events;
|
using SharpChat.Events;
|
||||||
using SharpChat.Snowflake;
|
using SharpChat.Snowflake;
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
namespace SharpChat;
|
using SharpChat.Channels;
|
||||||
|
|
||||||
|
namespace SharpChat;
|
||||||
|
|
||||||
public class ClientCommandContext {
|
public class ClientCommandContext {
|
||||||
public string Name { get; }
|
public string Name { get; }
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
using SharpChat.Channels;
|
||||||
using SharpChat.SockChat.S2CPackets;
|
using SharpChat.SockChat.S2CPackets;
|
||||||
|
|
||||||
namespace SharpChat.ClientCommands;
|
namespace SharpChat.ClientCommands;
|
||||||
|
@ -35,28 +36,23 @@ public class CreateChannelClientCommand : ClientCommand {
|
||||||
|
|
||||||
string createChanName = string.Join('_', ctx.Args.Skip(createChanHasHierarchy ? 1 : 0));
|
string createChanName = string.Join('_', ctx.Args.Skip(createChanHasHierarchy ? 1 : 0));
|
||||||
|
|
||||||
if(!Channel.CheckName(createChanName)) {
|
try {
|
||||||
|
Channel channel = ctx.Chat.Channels.CreateChannel(
|
||||||
|
createChanName,
|
||||||
|
temporary: !ctx.User.Permissions.HasFlag(UserPermissions.SetChannelPermanent),
|
||||||
|
rank: createChanHierarchy,
|
||||||
|
ownerId: ctx.User.UserId
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach(User ccu in ctx.Chat.Users.Where(u => u.Rank >= ctx.Channel.Rank))
|
||||||
|
await ctx.Chat.SendTo(ccu, new ChannelCreateS2CPacket(channel.Name, channel.HasPassword, channel.IsTemporary));
|
||||||
|
|
||||||
|
await ctx.Chat.SwitchChannel(ctx.User, channel, channel.Password);
|
||||||
|
await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.CHANNEL_CREATED, false, channel.Name));
|
||||||
|
} catch(ChannelNameFormatException) {
|
||||||
await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.CHANNEL_NAME_INVALID));
|
await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.CHANNEL_NAME_INVALID));
|
||||||
return;
|
} catch(ChannelExistsException) {
|
||||||
}
|
|
||||||
|
|
||||||
if(ctx.Chat.Channels.Any(c => c.NameEquals(createChanName))) {
|
|
||||||
await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.CHANNEL_ALREADY_EXISTS, true, createChanName));
|
await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.CHANNEL_ALREADY_EXISTS, true, createChanName));
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Channel createChan = new(
|
|
||||||
createChanName,
|
|
||||||
isTemporary: !ctx.User.Permissions.HasFlag(UserPermissions.SetChannelPermanent),
|
|
||||||
rank: createChanHierarchy,
|
|
||||||
ownerId: ctx.User.UserId
|
|
||||||
);
|
|
||||||
|
|
||||||
ctx.Chat.Channels.Add(createChan);
|
|
||||||
foreach(User ccu in ctx.Chat.Users.Where(u => u.Rank >= ctx.Channel.Rank))
|
|
||||||
await ctx.Chat.SendTo(ccu, new ChannelCreateS2CPacket(createChan.Name, createChan.HasPassword, createChan.IsTemporary));
|
|
||||||
|
|
||||||
await ctx.Chat.SwitchChannel(ctx.User, createChan, createChan.Password);
|
|
||||||
await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.CHANNEL_CREATED, false, createChan.Name));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
using SharpChat.Channels;
|
||||||
using SharpChat.SockChat.S2CPackets;
|
using SharpChat.SockChat.S2CPackets;
|
||||||
|
|
||||||
namespace SharpChat.ClientCommands;
|
namespace SharpChat.ClientCommands;
|
||||||
|
@ -19,7 +20,7 @@ public class DeleteChannelClientCommand : ClientCommand {
|
||||||
}
|
}
|
||||||
|
|
||||||
string delChanName = string.Join('_', ctx.Args);
|
string delChanName = string.Join('_', ctx.Args);
|
||||||
Channel? delChan = ctx.Chat.Channels.FirstOrDefault(c => c.NameEquals(delChanName));
|
Channel? delChan = ctx.Chat.Channels.GetChannel(delChanName);
|
||||||
|
|
||||||
if(delChan == null) {
|
if(delChan == null) {
|
||||||
await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.CHANNEL_NOT_FOUND, true, delChanName));
|
await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.CHANNEL_NOT_FOUND, true, delChanName));
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
using SharpChat.Channels;
|
||||||
using SharpChat.SockChat.S2CPackets;
|
using SharpChat.SockChat.S2CPackets;
|
||||||
|
|
||||||
namespace SharpChat.ClientCommands;
|
namespace SharpChat.ClientCommands;
|
||||||
|
@ -10,7 +11,7 @@ public class JoinChannelClientCommand : ClientCommand {
|
||||||
public async Task Dispatch(ClientCommandContext ctx) {
|
public async Task Dispatch(ClientCommandContext ctx) {
|
||||||
long msgId = ctx.Chat.RandomSnowflake.Next();
|
long msgId = ctx.Chat.RandomSnowflake.Next();
|
||||||
string joinChanStr = ctx.Args.FirstOrDefault() ?? "Channel";
|
string joinChanStr = ctx.Args.FirstOrDefault() ?? "Channel";
|
||||||
Channel? joinChan = ctx.Chat.Channels.FirstOrDefault(c => c.NameEquals(joinChanStr));
|
Channel? joinChan = ctx.Chat.Channels.GetChannel(joinChanStr);
|
||||||
|
|
||||||
if(joinChan is null) {
|
if(joinChan is null) {
|
||||||
await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.CHANNEL_NOT_FOUND, true, joinChanStr));
|
await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.CHANNEL_NOT_FOUND, true, joinChanStr));
|
||||||
|
|
|
@ -22,7 +22,7 @@ public class RankChannelClientCommand : ClientCommand {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await ctx.Chat.UpdateChannel(ctx.Channel, hierarchy: chanHierarchy);
|
await ctx.Chat.UpdateChannel(ctx.Channel, rank: chanHierarchy);
|
||||||
await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.CHANNEL_HIERARCHY_CHANGED, false));
|
await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.CHANNEL_HIERARCHY_CHANGED, false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
using SharpChat.Channels;
|
||||||
using SharpChat.SockChat.S2CPackets;
|
using SharpChat.SockChat.S2CPackets;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
|
@ -30,7 +31,7 @@ public class WhoClientCommand : ClientCommand {
|
||||||
|
|
||||||
await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.USERS_LISTING_SERVER, false, whoChanSB));
|
await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.USERS_LISTING_SERVER, false, whoChanSB));
|
||||||
} else {
|
} else {
|
||||||
Channel? whoChan = ctx.Chat.Channels.FirstOrDefault(c => c.NameEquals(whoChanStr));
|
Channel? whoChan = ctx.Chat.Channels.GetChannel(whoChanStr);
|
||||||
|
|
||||||
if(whoChan is null) {
|
if(whoChan is null) {
|
||||||
await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.CHANNEL_NOT_FOUND, true, whoChanStr));
|
await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.CHANNEL_NOT_FOUND, true, whoChanStr));
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
using SharpChat.Channels;
|
||||||
using SharpChat.Events;
|
using SharpChat.Events;
|
||||||
using SharpChat.Messages;
|
using SharpChat.Messages;
|
||||||
using SharpChat.Snowflake;
|
using SharpChat.Snowflake;
|
||||||
|
@ -15,7 +16,7 @@ public class Context {
|
||||||
public SnowflakeGenerator SnowflakeGenerator { get; } = new();
|
public SnowflakeGenerator SnowflakeGenerator { get; } = new();
|
||||||
public RandomSnowflake RandomSnowflake { get; }
|
public RandomSnowflake RandomSnowflake { get; }
|
||||||
|
|
||||||
public HashSet<Channel> Channels { get; } = [];
|
public ChannelsContext Channels { get; } = new();
|
||||||
public HashSet<Connection> Connections { get; } = [];
|
public HashSet<Connection> Connections { get; } = [];
|
||||||
public HashSet<User> Users { get; } = [];
|
public HashSet<User> Users { get; } = [];
|
||||||
public MessageStorage Messages { get; }
|
public MessageStorage Messages { get; }
|
||||||
|
@ -59,7 +60,7 @@ public class Context {
|
||||||
true
|
true
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
Channel? channel = Channels.FirstOrDefault(c => c.NameEquals(mce.ChannelName));
|
Channel? channel = Channels.GetChannel(mce.ChannelName);
|
||||||
if(channel is not null)
|
if(channel is not null)
|
||||||
await SendTo(channel, new ChatMessageAddS2CPacket(
|
await SendTo(channel, new ChatMessageAddS2CPacket(
|
||||||
mce.MessageId,
|
mce.MessageId,
|
||||||
|
@ -118,8 +119,7 @@ public class Context {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Channel[] GetUserChannels(User user) {
|
public Channel[] GetUserChannels(User user) {
|
||||||
string[] names = GetUserChannelNames(user);
|
return [.. Channels.GetChannels(GetUserChannelNames(user))];
|
||||||
return [.. Channels.Where(c => names.Any(n => c.NameEquals(n)))];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public string[] GetChannelUserIds(Channel channel) {
|
public string[] GetChannelUserIds(Channel channel) {
|
||||||
|
@ -241,7 +241,7 @@ public class Context {
|
||||||
await conn.Send(new ContextMessageS2CPacket(msg));
|
await conn.Send(new ContextMessageS2CPacket(msg));
|
||||||
|
|
||||||
await conn.Send(new ContextChannelsS2CPacket(
|
await conn.Send(new ContextChannelsS2CPacket(
|
||||||
Channels.Where(c => c.Rank <= user.Rank)
|
Channels.GetChannels(user.Rank)
|
||||||
.Select(c => new ContextChannelsS2CPacket.Entry(c.Name, c.HasPassword, c.IsTemporary))
|
.Select(c => new ContextChannelsS2CPacket.Entry(c.Name, c.HasPassword, c.IsTemporary))
|
||||||
));
|
));
|
||||||
|
|
||||||
|
@ -294,9 +294,6 @@ public class Context {
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ForceChannelSwitch(User user, Channel chan) {
|
public async Task ForceChannelSwitch(User user, Channel chan) {
|
||||||
if(!Channels.Any(c => c.NameEquals(chan.Name)))
|
|
||||||
return;
|
|
||||||
|
|
||||||
Channel oldChan = UserLastChannel[user.UserId];
|
Channel oldChan = UserLastChannel[user.UserId];
|
||||||
|
|
||||||
long leaveId = RandomSnowflake.Next();
|
long leaveId = RandomSnowflake.Next();
|
||||||
|
@ -354,7 +351,7 @@ public class Context {
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SendToUserChannels(User user, S2CPacket packet) {
|
public async Task SendToUserChannels(User user, S2CPacket packet) {
|
||||||
IEnumerable<Channel> chans = Channels.Where(c => IsInChannel(user, c));
|
IEnumerable<Channel> chans = Channels.GetChannels(c => IsInChannel(user, c));
|
||||||
IEnumerable<Connection> conns = Connections.Where(conn => conn.IsAlive && conn.User is not null && ChannelUsers.Any(cu => cu.UserId == conn.User.UserId && chans.Any(chan => chan.NameEquals(cu.ChannelName))));
|
IEnumerable<Connection> conns = Connections.Where(conn => conn.IsAlive && conn.User is not null && ChannelUsers.Any(cu => cu.UserId == conn.User.UserId && chans.Any(chan => chan.NameEquals(cu.ChannelName))));
|
||||||
foreach(Connection conn in conns)
|
foreach(Connection conn in conns)
|
||||||
await conn.Send(packet);
|
await conn.Send(packet);
|
||||||
|
@ -373,18 +370,18 @@ public class Context {
|
||||||
await SendTo(user, new UserChannelForceJoinS2CPacket(chan.Name));
|
await SendTo(user, new UserChannelForceJoinS2CPacket(chan.Name));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task UpdateChannel(Channel channel, bool? temporary = null, int? hierarchy = null, string? password = null) {
|
public async Task UpdateChannel(
|
||||||
if(!Channels.Any(c => c.NameEquals(channel.Name)))
|
Channel channel,
|
||||||
throw new ArgumentException("Provided channel is not registered with this manager.", nameof(channel));
|
bool? temporary = null,
|
||||||
|
int? rank = null,
|
||||||
if(temporary.HasValue)
|
string? password = null
|
||||||
channel.IsTemporary = temporary.Value;
|
) {
|
||||||
|
Channels.UpdateChannel(
|
||||||
if(hierarchy.HasValue)
|
channel,
|
||||||
channel.Rank = hierarchy.Value;
|
temporary: temporary,
|
||||||
|
rank: rank,
|
||||||
if(password != null)
|
password: password
|
||||||
channel.Password = password;
|
);
|
||||||
|
|
||||||
// TODO: Users that no longer have access to the channel/gained access to the channel by the hierarchy change should receive delete and create packets respectively
|
// TODO: Users that no longer have access to the channel/gained access to the channel by the hierarchy change should receive delete and create packets respectively
|
||||||
foreach(User user in Users.Where(u => u.Rank >= channel.Rank))
|
foreach(User user in Users.Where(u => u.Rank >= channel.Rank))
|
||||||
|
@ -392,20 +389,13 @@ public class Context {
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task RemoveChannel(Channel channel) {
|
public async Task RemoveChannel(Channel channel) {
|
||||||
if(channel == null || Channels.Count < 1)
|
|
||||||
return;
|
|
||||||
|
|
||||||
Channel? defaultChannel = Channels.FirstOrDefault();
|
|
||||||
if(defaultChannel is null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Remove channel from the listing
|
// Remove channel from the listing
|
||||||
Channels.Remove(channel);
|
Channels.RemoveChannel(channel.Name);
|
||||||
|
|
||||||
// Move all users back to the main channel
|
// Move all users back to the main channel
|
||||||
// TODO: Replace this with a kick. SCv2 supports being in 0 channels, SCv1 should force the user back to DefaultChannel.
|
// TODO: Replace this with a kick. SCv2 supports being in 0 channels, SCv1 should force the user back to DefaultChannel.
|
||||||
foreach(User user in GetChannelUsers(channel))
|
foreach(User user in GetChannelUsers(channel))
|
||||||
await SwitchChannel(user, defaultChannel, string.Empty);
|
await SwitchChannel(user, Channels.DefaultChannel, string.Empty);
|
||||||
|
|
||||||
// Broadcast deletion of channel
|
// Broadcast deletion of channel
|
||||||
foreach(User user in Users.Where(u => u.Rank >= channel.Rank))
|
foreach(User user in Users.Where(u => u.Rank >= channel.Rank))
|
||||||
|
|
|
@ -2,6 +2,7 @@ using Fleck;
|
||||||
using SharpChat.Auth;
|
using SharpChat.Auth;
|
||||||
using SharpChat.Bans;
|
using SharpChat.Bans;
|
||||||
using SharpChat.C2SPacketHandlers;
|
using SharpChat.C2SPacketHandlers;
|
||||||
|
using SharpChat.Channels;
|
||||||
using SharpChat.ClientCommands;
|
using SharpChat.ClientCommands;
|
||||||
using SharpChat.Configuration;
|
using SharpChat.Configuration;
|
||||||
using SharpChat.Messages;
|
using SharpChat.Messages;
|
||||||
|
@ -35,8 +36,6 @@ public class SockChatServer : IDisposable {
|
||||||
|
|
||||||
private static readonly string[] DEFAULT_CHANNELS = ["lounge"];
|
private static readonly string[] DEFAULT_CHANNELS = ["lounge"];
|
||||||
|
|
||||||
private Channel DefaultChannel { get; set; }
|
|
||||||
|
|
||||||
public SockChatServer(
|
public SockChatServer(
|
||||||
AuthClient authClient,
|
AuthClient authClient,
|
||||||
BansClient bansClient,
|
BansClient bansClient,
|
||||||
|
@ -63,20 +62,14 @@ public class SockChatServer : IDisposable {
|
||||||
if(string.IsNullOrWhiteSpace(name))
|
if(string.IsNullOrWhiteSpace(name))
|
||||||
name = channelName;
|
name = channelName;
|
||||||
|
|
||||||
Channel channelInfo = new(
|
Context.Channels.CreateChannel(
|
||||||
name,
|
name,
|
||||||
channelCfg.SafeReadValue("password", string.Empty)!,
|
channelCfg.SafeReadValue("password", string.Empty)!,
|
||||||
rank: channelCfg.SafeReadValue("minRank", 0)
|
rank: channelCfg.SafeReadValue("minRank", 0)
|
||||||
);
|
);
|
||||||
|
|
||||||
Context.Channels.Add(channelInfo);
|
|
||||||
DefaultChannel ??= channelInfo;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(DefaultChannel is null)
|
GuestHandlers.Add(new AuthC2SPacketHandler(authClient, bansClient, Context.Channels, MaxMessageLength, MaxConnections));
|
||||||
throw new Exception("The default channel could not be determined.");
|
|
||||||
|
|
||||||
GuestHandlers.Add(new AuthC2SPacketHandler(authClient, bansClient, DefaultChannel, MaxMessageLength, MaxConnections));
|
|
||||||
|
|
||||||
AuthedHandlers.AddRange([
|
AuthedHandlers.AddRange([
|
||||||
new PingC2SPacketHandler(authClient),
|
new PingC2SPacketHandler(authClient),
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
namespace SharpChat;
|
namespace SharpChat.Channels;
|
||||||
|
|
||||||
public class Channel(
|
public class Channel(
|
||||||
string name,
|
string name,
|
||||||
|
@ -7,15 +7,18 @@ public class Channel(
|
||||||
int rank = 0,
|
int rank = 0,
|
||||||
string ownerId = ""
|
string ownerId = ""
|
||||||
) {
|
) {
|
||||||
public string Name { get; } = name ?? throw new ArgumentNullException(nameof(name));
|
public string Name { get; internal set; } = name;
|
||||||
public string Password { get; set; } = password ?? string.Empty;
|
public string Password { get; internal set; } = password ?? string.Empty;
|
||||||
public bool IsTemporary { get; set; } = isTemporary;
|
public bool IsTemporary { get; internal set; } = isTemporary;
|
||||||
public int Rank { get; set; } = rank;
|
public int Rank { get; internal set; } = rank;
|
||||||
public string OwnerId { get; set; } = ownerId;
|
public string OwnerId { get; internal set; } = ownerId;
|
||||||
|
|
||||||
public bool HasPassword
|
public bool HasPassword
|
||||||
=> !string.IsNullOrWhiteSpace(Password);
|
=> !string.IsNullOrWhiteSpace(Password);
|
||||||
|
|
||||||
|
public bool IsPublic
|
||||||
|
=> !HasPassword && Rank < 1;
|
||||||
|
|
||||||
public bool NameEquals(string name) {
|
public bool NameEquals(string name) {
|
||||||
return string.Equals(name, Name, StringComparison.InvariantCultureIgnoreCase);
|
return string.Equals(name, Name, StringComparison.InvariantCultureIgnoreCase);
|
||||||
}
|
}
|
4
SharpChatCommon/Channels/ChannelExistsException.cs
Normal file
4
SharpChatCommon/Channels/ChannelExistsException.cs
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
namespace SharpChat.Channels;
|
||||||
|
|
||||||
|
public class ChannelExistsException(string argName)
|
||||||
|
: ArgumentException("A channel with that name already exists.", argName) {}
|
4
SharpChatCommon/Channels/ChannelIsDefaultException.cs
Normal file
4
SharpChatCommon/Channels/ChannelIsDefaultException.cs
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
namespace SharpChat.Channels;
|
||||||
|
|
||||||
|
public class ChannelIsDefaultException(string argName)
|
||||||
|
: ArgumentException("You cannot delete the default channel.", argName) {}
|
4
SharpChatCommon/Channels/ChannelNameFormatException.cs
Normal file
4
SharpChatCommon/Channels/ChannelNameFormatException.cs
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
namespace SharpChat.Channels;
|
||||||
|
|
||||||
|
public class ChannelNameFormatException(string argName)
|
||||||
|
: ArgumentException("Channel name contains unsupported characters.", argName) { }
|
4
SharpChatCommon/Channels/ChannelNotFoundException.cs
Normal file
4
SharpChatCommon/Channels/ChannelNotFoundException.cs
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
namespace SharpChat.Channels;
|
||||||
|
|
||||||
|
public class ChannelNotFoundException(string argName)
|
||||||
|
: ArgumentException("No channel with that name exists.", argName) {}
|
126
SharpChatCommon/Channels/ChannelsContext.cs
Normal file
126
SharpChatCommon/Channels/ChannelsContext.cs
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
namespace SharpChat.Channels;
|
||||||
|
|
||||||
|
public class ChannelsContext {
|
||||||
|
private readonly List<Channel> Channels = [];
|
||||||
|
|
||||||
|
public int Count => Channels.Count;
|
||||||
|
|
||||||
|
private Channel? DefaultChannelValue;
|
||||||
|
public Channel DefaultChannel {
|
||||||
|
get {
|
||||||
|
if(DefaultChannelValue is not null) {
|
||||||
|
if(Channels.Contains(DefaultChannelValue))
|
||||||
|
return DefaultChannelValue;
|
||||||
|
|
||||||
|
DefaultChannelValue = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetChannel(c => c.IsPublic && !c.IsTemporary) ?? throw new NoDefaultChannelException();
|
||||||
|
}
|
||||||
|
|
||||||
|
set => DefaultChannelValue = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ChannelExists(Func<Channel, bool> predicate) {
|
||||||
|
return Channels.Any(predicate);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ChannelExists(string name) {
|
||||||
|
return ChannelExists(c => c.NameEquals(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Channel? GetChannel(Func<Channel, bool> predicate) {
|
||||||
|
return Channels.FirstOrDefault(predicate);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Channel? GetChannel(string name) {
|
||||||
|
return GetChannel(c => c.NameEquals(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<Channel> GetChannels(Func<Channel, bool> predicate) {
|
||||||
|
return Channels.Where(predicate);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<Channel> GetChannels(IEnumerable<string> names) {
|
||||||
|
return GetChannels(c => names.Any(n => c.NameEquals(n)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<Channel> GetChannels(int maxRank) {
|
||||||
|
return GetChannels(c => c.Rank <= maxRank);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Channel CreateChannel(
|
||||||
|
string name,
|
||||||
|
string password = "",
|
||||||
|
bool temporary = false,
|
||||||
|
int rank = 0,
|
||||||
|
string ownerId = ""
|
||||||
|
) {
|
||||||
|
if(!Channel.CheckName(name))
|
||||||
|
throw new ChannelNameFormatException(nameof(name));
|
||||||
|
if(ChannelExists(name))
|
||||||
|
throw new ChannelExistsException(nameof(name));
|
||||||
|
|
||||||
|
Channel channel = new(
|
||||||
|
name,
|
||||||
|
password ?? string.Empty,
|
||||||
|
temporary,
|
||||||
|
rank,
|
||||||
|
ownerId ?? string.Empty
|
||||||
|
);
|
||||||
|
|
||||||
|
Channels.Add(channel);
|
||||||
|
|
||||||
|
return channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Channel UpdateChannel(
|
||||||
|
Channel channel,
|
||||||
|
string? name = null,
|
||||||
|
string? password = null,
|
||||||
|
bool? temporary = null,
|
||||||
|
int? rank = null,
|
||||||
|
string? ownerId = null
|
||||||
|
) => UpdateChannel(channel.Name, name, password, temporary, rank, ownerId);
|
||||||
|
|
||||||
|
public Channel UpdateChannel(
|
||||||
|
string currentName,
|
||||||
|
string? name = null,
|
||||||
|
string? password = null,
|
||||||
|
bool? temporary = null,
|
||||||
|
int? rank = null,
|
||||||
|
string? ownerId = null
|
||||||
|
) {
|
||||||
|
Channel channel = GetChannel(currentName) ?? throw new ChannelNotFoundException(nameof(currentName));
|
||||||
|
|
||||||
|
if(name is not null && currentName != name) {
|
||||||
|
if(!Channel.CheckName(name))
|
||||||
|
throw new ChannelNameFormatException(nameof(name));
|
||||||
|
if(ChannelExists(name))
|
||||||
|
throw new ChannelExistsException(nameof(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(name is not null)
|
||||||
|
channel.Name = name;
|
||||||
|
if(password is not null)
|
||||||
|
channel.Password = password;
|
||||||
|
if(temporary.HasValue)
|
||||||
|
channel.IsTemporary = temporary.Value;
|
||||||
|
if(rank.HasValue)
|
||||||
|
channel.Rank = rank.Value;
|
||||||
|
if(ownerId is not null)
|
||||||
|
channel.OwnerId = ownerId;
|
||||||
|
|
||||||
|
return channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveChannel(string name) {
|
||||||
|
Channel channel = GetChannel(name) ?? throw new ChannelNotFoundException(nameof(name));
|
||||||
|
|
||||||
|
Channel defaultChannel = DefaultChannel;
|
||||||
|
if(channel == defaultChannel || defaultChannel.NameEquals(channel.Name))
|
||||||
|
throw new ChannelIsDefaultException(nameof(name));
|
||||||
|
|
||||||
|
Channels.Remove(channel);
|
||||||
|
}
|
||||||
|
}
|
4
SharpChatCommon/Channels/NoDefaultChannelException.cs
Normal file
4
SharpChatCommon/Channels/NoDefaultChannelException.cs
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
namespace SharpChat.Channels;
|
||||||
|
|
||||||
|
public class NoDefaultChannelException()
|
||||||
|
: NullReferenceException("A public, non-temporary default channel could not be determined.") {}
|
Loading…
Add table
Add a link
Reference in a new issue