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.Bans;
|
||||
using SharpChat.Channels;
|
||||
using SharpChat.Configuration;
|
||||
using SharpChat.SockChat.S2CPackets;
|
||||
|
||||
|
@ -8,14 +9,10 @@ namespace SharpChat.C2SPacketHandlers;
|
|||
public class AuthC2SPacketHandler(
|
||||
AuthClient authClient,
|
||||
BansClient bansClient,
|
||||
Channel defaultChannel,
|
||||
ChannelsContext channelsCtx,
|
||||
CachedValue<int> maxMsgLength,
|
||||
CachedValue<int> maxConns
|
||||
) : 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) {
|
||||
return ctx.CheckPacketId("1");
|
||||
}
|
||||
|
@ -75,7 +72,7 @@ public class AuthC2SPacketHandler(
|
|||
);
|
||||
|
||||
// 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));
|
||||
ctx.Connection.Dispose();
|
||||
return;
|
||||
|
@ -93,7 +90,7 @@ public class AuthC2SPacketHandler(
|
|||
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 {
|
||||
ctx.Chat.ContextAccess.Release();
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
using SharpChat.Channels;
|
||||
using SharpChat.Configuration;
|
||||
using SharpChat.Events;
|
||||
using SharpChat.Snowflake;
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
namespace SharpChat;
|
||||
using SharpChat.Channels;
|
||||
|
||||
namespace SharpChat;
|
||||
|
||||
public class ClientCommandContext {
|
||||
public string Name { get; }
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
using SharpChat.Channels;
|
||||
using SharpChat.SockChat.S2CPackets;
|
||||
|
||||
namespace SharpChat.ClientCommands;
|
||||
|
@ -35,28 +36,23 @@ public class CreateChannelClientCommand : ClientCommand {
|
|||
|
||||
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));
|
||||
return;
|
||||
}
|
||||
|
||||
if(ctx.Chat.Channels.Any(c => c.NameEquals(createChanName))) {
|
||||
} catch(ChannelExistsException) {
|
||||
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;
|
||||
|
||||
namespace SharpChat.ClientCommands;
|
||||
|
@ -19,7 +20,7 @@ public class DeleteChannelClientCommand : ClientCommand {
|
|||
}
|
||||
|
||||
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) {
|
||||
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;
|
||||
|
||||
namespace SharpChat.ClientCommands;
|
||||
|
@ -10,7 +11,7 @@ public class JoinChannelClientCommand : ClientCommand {
|
|||
public async Task Dispatch(ClientCommandContext ctx) {
|
||||
long msgId = ctx.Chat.RandomSnowflake.Next();
|
||||
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) {
|
||||
await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.CHANNEL_NOT_FOUND, true, joinChanStr));
|
||||
|
|
|
@ -22,7 +22,7 @@ public class RankChannelClientCommand : ClientCommand {
|
|||
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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
using SharpChat.Channels;
|
||||
using SharpChat.SockChat.S2CPackets;
|
||||
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));
|
||||
} else {
|
||||
Channel? whoChan = ctx.Chat.Channels.FirstOrDefault(c => c.NameEquals(whoChanStr));
|
||||
Channel? whoChan = ctx.Chat.Channels.GetChannel(whoChanStr);
|
||||
|
||||
if(whoChan is null) {
|
||||
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.Messages;
|
||||
using SharpChat.Snowflake;
|
||||
|
@ -15,7 +16,7 @@ public class Context {
|
|||
public SnowflakeGenerator SnowflakeGenerator { get; } = new();
|
||||
public RandomSnowflake RandomSnowflake { get; }
|
||||
|
||||
public HashSet<Channel> Channels { get; } = [];
|
||||
public ChannelsContext Channels { get; } = new();
|
||||
public HashSet<Connection> Connections { get; } = [];
|
||||
public HashSet<User> Users { get; } = [];
|
||||
public MessageStorage Messages { get; }
|
||||
|
@ -59,7 +60,7 @@ public class Context {
|
|||
true
|
||||
));
|
||||
} else {
|
||||
Channel? channel = Channels.FirstOrDefault(c => c.NameEquals(mce.ChannelName));
|
||||
Channel? channel = Channels.GetChannel(mce.ChannelName);
|
||||
if(channel is not null)
|
||||
await SendTo(channel, new ChatMessageAddS2CPacket(
|
||||
mce.MessageId,
|
||||
|
@ -118,8 +119,7 @@ public class Context {
|
|||
}
|
||||
|
||||
public Channel[] GetUserChannels(User user) {
|
||||
string[] names = GetUserChannelNames(user);
|
||||
return [.. Channels.Where(c => names.Any(n => c.NameEquals(n)))];
|
||||
return [.. Channels.GetChannels(GetUserChannelNames(user))];
|
||||
}
|
||||
|
||||
public string[] GetChannelUserIds(Channel channel) {
|
||||
|
@ -241,7 +241,7 @@ public class Context {
|
|||
await conn.Send(new ContextMessageS2CPacket(msg));
|
||||
|
||||
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))
|
||||
));
|
||||
|
||||
|
@ -294,9 +294,6 @@ public class Context {
|
|||
}
|
||||
|
||||
public async Task ForceChannelSwitch(User user, Channel chan) {
|
||||
if(!Channels.Any(c => c.NameEquals(chan.Name)))
|
||||
return;
|
||||
|
||||
Channel oldChan = UserLastChannel[user.UserId];
|
||||
|
||||
long leaveId = RandomSnowflake.Next();
|
||||
|
@ -354,7 +351,7 @@ public class Context {
|
|||
}
|
||||
|
||||
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))));
|
||||
foreach(Connection conn in conns)
|
||||
await conn.Send(packet);
|
||||
|
@ -373,18 +370,18 @@ public class Context {
|
|||
await SendTo(user, new UserChannelForceJoinS2CPacket(chan.Name));
|
||||
}
|
||||
|
||||
public async Task UpdateChannel(Channel channel, bool? temporary = null, int? hierarchy = null, string? password = null) {
|
||||
if(!Channels.Any(c => c.NameEquals(channel.Name)))
|
||||
throw new ArgumentException("Provided channel is not registered with this manager.", nameof(channel));
|
||||
|
||||
if(temporary.HasValue)
|
||||
channel.IsTemporary = temporary.Value;
|
||||
|
||||
if(hierarchy.HasValue)
|
||||
channel.Rank = hierarchy.Value;
|
||||
|
||||
if(password != null)
|
||||
channel.Password = password;
|
||||
public async Task UpdateChannel(
|
||||
Channel channel,
|
||||
bool? temporary = null,
|
||||
int? rank = null,
|
||||
string? password = null
|
||||
) {
|
||||
Channels.UpdateChannel(
|
||||
channel,
|
||||
temporary: temporary,
|
||||
rank: rank,
|
||||
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
|
||||
foreach(User user in Users.Where(u => u.Rank >= channel.Rank))
|
||||
|
@ -392,20 +389,13 @@ public class Context {
|
|||
}
|
||||
|
||||
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
|
||||
Channels.Remove(channel);
|
||||
Channels.RemoveChannel(channel.Name);
|
||||
|
||||
// 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.
|
||||
foreach(User user in GetChannelUsers(channel))
|
||||
await SwitchChannel(user, defaultChannel, string.Empty);
|
||||
await SwitchChannel(user, Channels.DefaultChannel, string.Empty);
|
||||
|
||||
// Broadcast deletion of channel
|
||||
foreach(User user in Users.Where(u => u.Rank >= channel.Rank))
|
||||
|
|
|
@ -2,6 +2,7 @@ using Fleck;
|
|||
using SharpChat.Auth;
|
||||
using SharpChat.Bans;
|
||||
using SharpChat.C2SPacketHandlers;
|
||||
using SharpChat.Channels;
|
||||
using SharpChat.ClientCommands;
|
||||
using SharpChat.Configuration;
|
||||
using SharpChat.Messages;
|
||||
|
@ -35,8 +36,6 @@ public class SockChatServer : IDisposable {
|
|||
|
||||
private static readonly string[] DEFAULT_CHANNELS = ["lounge"];
|
||||
|
||||
private Channel DefaultChannel { get; set; }
|
||||
|
||||
public SockChatServer(
|
||||
AuthClient authClient,
|
||||
BansClient bansClient,
|
||||
|
@ -63,20 +62,14 @@ public class SockChatServer : IDisposable {
|
|||
if(string.IsNullOrWhiteSpace(name))
|
||||
name = channelName;
|
||||
|
||||
Channel channelInfo = new(
|
||||
Context.Channels.CreateChannel(
|
||||
name,
|
||||
channelCfg.SafeReadValue("password", string.Empty)!,
|
||||
rank: channelCfg.SafeReadValue("minRank", 0)
|
||||
);
|
||||
|
||||
Context.Channels.Add(channelInfo);
|
||||
DefaultChannel ??= channelInfo;
|
||||
}
|
||||
|
||||
if(DefaultChannel is null)
|
||||
throw new Exception("The default channel could not be determined.");
|
||||
|
||||
GuestHandlers.Add(new AuthC2SPacketHandler(authClient, bansClient, DefaultChannel, MaxMessageLength, MaxConnections));
|
||||
GuestHandlers.Add(new AuthC2SPacketHandler(authClient, bansClient, Context.Channels, MaxMessageLength, MaxConnections));
|
||||
|
||||
AuthedHandlers.AddRange([
|
||||
new PingC2SPacketHandler(authClient),
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
namespace SharpChat;
|
||||
namespace SharpChat.Channels;
|
||||
|
||||
public class Channel(
|
||||
string name,
|
||||
|
@ -7,15 +7,18 @@ public class Channel(
|
|||
int rank = 0,
|
||||
string ownerId = ""
|
||||
) {
|
||||
public string Name { get; } = name ?? throw new ArgumentNullException(nameof(name));
|
||||
public string Password { get; set; } = password ?? string.Empty;
|
||||
public bool IsTemporary { get; set; } = isTemporary;
|
||||
public int Rank { get; set; } = rank;
|
||||
public string OwnerId { get; set; } = ownerId;
|
||||
public string Name { get; internal set; } = name;
|
||||
public string Password { get; internal set; } = password ?? string.Empty;
|
||||
public bool IsTemporary { get; internal set; } = isTemporary;
|
||||
public int Rank { get; internal set; } = rank;
|
||||
public string OwnerId { get; internal set; } = ownerId;
|
||||
|
||||
public bool HasPassword
|
||||
=> !string.IsNullOrWhiteSpace(Password);
|
||||
|
||||
public bool IsPublic
|
||||
=> !HasPassword && Rank < 1;
|
||||
|
||||
public bool NameEquals(string name) {
|
||||
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