From 1fcbd36ead2fab42513f3ab46e168bfd6b2a33c2 Mon Sep 17 00:00:00 2001 From: RogueException Date: Sun, 25 Oct 2015 01:02:28 -0300 Subject: [PATCH] Lots of bug fixes --- src/Discord.Net/DiscordClient.Bans.cs | 10 +- src/Discord.Net/DiscordClient.Invites.cs | 10 +- src/Discord.Net/DiscordClient.Members.cs | 2 +- src/Discord.Net/DiscordClient.Messages.cs | 2 +- src/Discord.Net/DiscordClient.Permissions.cs | 23 ++- src/Discord.Net/DiscordClient.Roles.cs | 6 +- src/Discord.Net/DiscordClient.Servers.cs | 2 +- src/Discord.Net/DiscordClient.cs | 31 ++-- src/Discord.Net/DiscordWSClient.cs | 7 +- src/Discord.Net/Models/Channel.cs | 31 ++-- src/Discord.Net/Models/GlobalUser.cs | 9 +- src/Discord.Net/Models/Invite.cs | 74 ++++++---- src/Discord.Net/Models/Message.cs | 3 + src/Discord.Net/Models/Role.cs | 8 +- src/Discord.Net/Models/Server.cs | 143 ++++++++++--------- src/Discord.Net/Models/User.cs | 79 +++++----- src/Discord.Net/Net/WebSockets/WebSocket.cs | 10 +- 17 files changed, 269 insertions(+), 181 deletions(-) diff --git a/src/Discord.Net/DiscordClient.Bans.cs b/src/Discord.Net/DiscordClient.Bans.cs index 2fd5df60a..9d99b0909 100644 --- a/src/Discord.Net/DiscordClient.Bans.cs +++ b/src/Discord.Net/DiscordClient.Bans.cs @@ -36,18 +36,20 @@ namespace Discord public Task Ban(User member) { if (member == null) throw new ArgumentNullException(nameof(member)); + if (member.Server == null) throw new ArgumentException("Unable to ban a user in a private chat."); CheckReady(); - return _api.Ban(member.ServerId, member.Id); + return _api.Ban(member.Server.Id, member.Id); } /// Unbans a user from the provided server. - public async Task Unban(User member) + public async Task Unban(Server server, string userId) { - if (member == null) throw new ArgumentNullException(nameof(member)); + if (server == null) throw new ArgumentNullException(nameof(server)); + if (userId == null) throw new ArgumentNullException(nameof(userId)); CheckReady(); - try { await _api.Unban(member.ServerId, member.Id).ConfigureAwait(false); } + try { await _api.Unban(server.Id, userId).ConfigureAwait(false); } catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } } } diff --git a/src/Discord.Net/DiscordClient.Invites.cs b/src/Discord.Net/DiscordClient.Invites.cs index 8637243f1..4e61992e9 100644 --- a/src/Discord.Net/DiscordClient.Invites.cs +++ b/src/Discord.Net/DiscordClient.Invites.cs @@ -10,6 +10,8 @@ namespace Discord /// Supported formats: inviteCode, xkcdCode, https://discord.gg/inviteCode, https://discord.gg/xkcdCode public async Task GetInvite(string inviteIdOrXkcd) { + //This doesn't work well if it's an invite to a different server! + if (inviteIdOrXkcd == null) throw new ArgumentNullException(nameof(inviteIdOrXkcd)); CheckReady(); @@ -22,8 +24,8 @@ namespace Discord inviteIdOrXkcd = inviteIdOrXkcd.Substring(index + 1); var response = await _api.GetInvite(inviteIdOrXkcd).ConfigureAwait(false); - var invite = new Invite(this, response.Code, response.XkcdPass, response.Guild.Id); - invite.Update(response); + var invite = new Invite(this, response.Code, response.XkcdPass, response.Guild.Id, response.Inviter?.Id, response.Channel?.Id); + invite.Cache(); //Builds references return invite; } @@ -53,8 +55,8 @@ namespace Discord var response = await _api.CreateInvite(channel.Id, maxAge: maxAge, maxUses: maxUses, tempMembership: tempMembership, hasXkcd: hasXkcd).ConfigureAwait(false); - var invite = new Invite(this, response.Code, response.XkcdPass, response.Guild.Id); - invite.Update(response); + var invite = new Invite(this, response.Code, response.XkcdPass, response.Guild.Id, response.Inviter?.Id, response.Channel?.Id); + invite.Cache(); //Builds references return invite; } diff --git a/src/Discord.Net/DiscordClient.Members.cs b/src/Discord.Net/DiscordClient.Members.cs index 119bbec95..d30b50679 100644 --- a/src/Discord.Net/DiscordClient.Members.cs +++ b/src/Discord.Net/DiscordClient.Members.cs @@ -121,7 +121,7 @@ namespace Discord if (member == null) throw new ArgumentNullException(nameof(member)); CheckReady(); - return _api.EditMember(member.ServerId, member.Id, mute: mute, deaf: deaf, roles: roles.Select(x => x.Id)); + return _api.EditMember(member.Server?.Id, member.Id, mute: mute, deaf: deaf, roles: roles.Select(x => x.Id)); } } } \ No newline at end of file diff --git a/src/Discord.Net/DiscordClient.Messages.cs b/src/Discord.Net/DiscordClient.Messages.cs index 9796e9318..7b7311cdb 100644 --- a/src/Discord.Net/DiscordClient.Messages.cs +++ b/src/Discord.Net/DiscordClient.Messages.cs @@ -25,7 +25,7 @@ namespace Discord else { var msg = new Message(_client, id, channelId, userId); - msg.Cache(); //Creates references to channel/server + msg.Cache(); //Builds references return msg; } } diff --git a/src/Discord.Net/DiscordClient.Permissions.cs b/src/Discord.Net/DiscordClient.Permissions.cs index 291ef1881..23e963a13 100644 --- a/src/Discord.Net/DiscordClient.Permissions.cs +++ b/src/Discord.Net/DiscordClient.Permissions.cs @@ -81,9 +81,17 @@ namespace Discord if (changed) { if (targetType == PermissionTarget.Role) - channel.InvalidatePermissionsCache(); + { + var role = _roles[targetId]; + if (role != null) + channel.InvalidatePermissionsCache(role); + } else if (targetType == PermissionTarget.User) - channel.InvalidatePermissionsCache(targetId); + { + var user = _users[targetId, channel.Server?.Id]; + if (user != null) + channel.InvalidatePermissionsCache(user); + } } } @@ -114,9 +122,16 @@ namespace Discord channel.PermissionOverwrites.Where(x => x.TargetType != targetType || x.TargetId != userOrRoleId).ToArray(); if (targetType == PermissionTarget.Role) - channel.InvalidatePermissionsCache(); + { + var role = _roles[userOrRoleId]; + channel.InvalidatePermissionsCache(role); + } else if (targetType == PermissionTarget.User) - channel.InvalidatePermissionsCache(userOrRoleId); + { + var user = _users[userOrRoleId, channel.Server?.Id]; + if (user != null) + channel.InvalidatePermissionsCache(user); + } } } catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } diff --git a/src/Discord.Net/DiscordClient.Roles.cs b/src/Discord.Net/DiscordClient.Roles.cs index 10fc46ff5..f98005cde 100644 --- a/src/Discord.Net/DiscordClient.Roles.cs +++ b/src/Discord.Net/DiscordClient.Roles.cs @@ -7,11 +7,13 @@ namespace Discord { internal sealed class Roles : AsyncCollection { - private const string VirtualEveryoneId = "[Virtual]"; public Role VirtualEveryone { get; private set; } public Roles(DiscordClient client, object writerLock) - : base(client, writerLock, x => x.OnCached(), x => x.OnUncached()) { } + : base(client, writerLock, x => x.OnCached(), x => x.OnUncached()) + { + VirtualEveryone = new Role(client, "Private", null); + } public Role GetOrAdd(string id, string serverId) => GetOrAdd(id, () => new Role(_client, id, serverId)); diff --git a/src/Discord.Net/DiscordClient.Servers.cs b/src/Discord.Net/DiscordClient.Servers.cs index afebc9c02..0db0ba1f3 100644 --- a/src/Discord.Net/DiscordClient.Servers.cs +++ b/src/Discord.Net/DiscordClient.Servers.cs @@ -57,7 +57,7 @@ namespace Discord } /// Returns a collection of all servers this client is a member of. - public IEnumerable AllServers => _servers.Where(x => !x.IsVirtual); + public IEnumerable AllServers => _servers; internal Servers Servers => _servers; private readonly Servers _servers; diff --git a/src/Discord.Net/DiscordClient.cs b/src/Discord.Net/DiscordClient.cs index 6e05a6259..56b6bcb18 100644 --- a/src/Discord.Net/DiscordClient.cs +++ b/src/Discord.Net/DiscordClient.cs @@ -59,12 +59,16 @@ namespace Discord VoiceDisconnected += (s, e) => { - foreach (var member in _users) + var server = _servers[e.ServerId]; + if (server != null) { - if (member.ServerId == e.ServerId && member.IsSpeaking) + foreach (var member in server.Members) { - member.IsSpeaking = false; - RaiseUserIsSpeaking(member, _channels[_voiceSocket.CurrentChannelId], false); + if (member.IsSpeaking) + { + member.IsSpeaking = false; + RaiseUserIsSpeaking(member, _channels[_voiceSocket.CurrentChannelId], false); + } } } }; @@ -104,13 +108,13 @@ namespace Discord BanRemoved += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, $"Removed Ban: {e.Server?.Name ?? "[Private]"}/{e.UserId}"); UserAdded += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, - $"Added Member: {e.Server?.Name ?? "[Private]"}/{e.UserId}"); + $"Added Member: {e.Server?.Name ?? "[Private]"}/{e.User.Id}"); UserRemoved += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, - $"Removed Member: {e.Server?.Name ?? "[Private]"}/{e.UserId}"); + $"Removed Member: {e.Server?.Name ?? "[Private]"}/{e.User.Id}"); MemberUpdated += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, - $"Updated Member: {e.Server?.Name ?? "[Private]"}/{e.UserId}"); + $"Updated Member: {e.Server?.Name ?? "[Private]"}/{e.User.Id}"); UserVoiceStateUpdated += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, - $"Updated Member (Voice State): {e.Server?.Name ?? "[Private]"}/{e.UserId}"); + $"Updated Member (Voice State): {e.Server?.Name ?? "[Private]"}/{e.User.Id}"); ProfileUpdated += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, "Updated Profile"); } @@ -281,13 +285,12 @@ namespace Discord { try { - await base.OnReceivedEvent(e); - switch (e.Type) { //Global - case "READY": //Resync + case "READY": //Resync { + base.OnReceivedEvent(e).Wait(); //This cannot be an await, or we'll get later messages before we're ready var data = e.Payload.ToObject(_serializer); _currentUser = _users.GetOrAdd(data.User.Id, null); _currentUser.Update(data.User); @@ -576,10 +579,11 @@ namespace Discord var member = _users[data.UserId, data.GuildId]; if (member != null) { - if (data.ChannelId != member.VoiceChannelId && member.IsSpeaking) + var voiceChannel = member.VoiceChannel; + if (voiceChannel != null && data.ChannelId != voiceChannel.Id && member.IsSpeaking) { member.IsSpeaking = false; - RaiseUserIsSpeaking(member, _channels[member.VoiceChannelId], false); + RaiseUserIsSpeaking(member, _channels[voiceChannel.Id], false); } member.Update(data); RaiseUserVoiceStateUpdated(member); @@ -611,6 +615,7 @@ namespace Discord //Internal (handled in DiscordSimpleClient) case "VOICE_SERVER_UPDATE": + await base.OnReceivedEvent(e); break; //Others diff --git a/src/Discord.Net/DiscordWSClient.cs b/src/Discord.Net/DiscordWSClient.cs index f9f6d621e..6d3549c19 100644 --- a/src/Discord.Net/DiscordWSClient.cs +++ b/src/Discord.Net/DiscordWSClient.cs @@ -90,7 +90,7 @@ namespace Discord } } - socket.ReceivedEvent += (s, e) => OnReceivedEvent(e); + socket.ReceivedEvent += async (s, e) => await OnReceivedEvent(e); return socket; } internal virtual VoiceWebSocket CreateVoiceSocket() @@ -292,7 +292,7 @@ namespace Discord } } - internal virtual Task OnReceivedEvent(WebSocketEventEventArgs e) + internal virtual async Task OnReceivedEvent(WebSocketEventEventArgs e) { try { @@ -309,7 +309,7 @@ namespace Discord { string token = e.Payload.Value("token"); _voiceSocket.Host = "wss://" + e.Payload.Value("endpoint").Split(':')[0]; - return _voiceSocket.Login(_userId, _dataSocket.SessionId, token, CancelToken); + await _voiceSocket.Login(_userId, _dataSocket.SessionId, token, CancelToken); } } break; @@ -319,7 +319,6 @@ namespace Discord { RaiseOnLog(LogMessageSeverity.Error, LogMessageSource.Client, $"Error handling {e.Type} event: {ex.GetBaseException().Message}"); } - return TaskHelper.CompletedTask; } } } \ No newline at end of file diff --git a/src/Discord.Net/Models/Channel.cs b/src/Discord.Net/Models/Channel.cs index 2a11b03db..1eda52a87 100644 --- a/src/Discord.Net/Models/Channel.cs +++ b/src/Discord.Net/Models/Channel.cs @@ -53,11 +53,8 @@ namespace Discord { get { - if (!_areMembersStale) - return _members.Select(x => x.Value); - - _members = Server.Members.Where(x => x.GetPermissions(this)?.ReadMessages ?? false).ToDictionary(x => x.Id, x => x); - _areMembersStale = false; + if (_areMembersStale) + UpdateMembersCache(); return _members.Select(x => x.Value); } } @@ -157,22 +154,32 @@ namespace Discord } internal void RemoveMessage(Message message) => _messages.TryRemove(message.Id, out message); - internal void InvalidMembersCache() + internal void InvalidateMembersCache() { _areMembersStale = true; } - internal void InvalidatePermissionsCache() + private void UpdateMembersCache() + { + _members = Server.Members.Where(x => x.GetPermissions(this)?.ReadMessages ?? false).ToDictionary(x => x.Id, x => x); + _areMembersStale = false; + } + + internal void InvalidatePermissionsCache() + { + UpdateMembersCache(); + foreach (var member in _members) + member.Value.UpdateChannelPermissions(this); + } + internal void InvalidatePermissionsCache(Role role) { _areMembersStale = true; - foreach (var member in Members) + foreach (var member in role.Members) member.UpdateChannelPermissions(this); } - internal void InvalidatePermissionsCache(string userId) + internal void InvalidatePermissionsCache(User user) { _areMembersStale = true; - var user = _members[userId] - if (user != null) - user.UpdateChannelPermissions(this); + user.UpdateChannelPermissions(this); } } } diff --git a/src/Discord.Net/Models/GlobalUser.cs b/src/Discord.Net/Models/GlobalUser.cs index fd44d5bb9..38dc96a05 100644 --- a/src/Discord.Net/Models/GlobalUser.cs +++ b/src/Discord.Net/Models/GlobalUser.cs @@ -34,7 +34,10 @@ namespace Discord _users = new ConcurrentDictionary(); } internal override void OnCached() { } - internal override void OnUncached() { } + internal override void OnUncached() + { + //Don't need to clean _users - they're considered owned by server + } internal void Update(UserInfo model) { @@ -44,10 +47,10 @@ namespace Discord IsVerified = model.IsVerified; } - internal void AddUser(User user) => _users.TryAdd(user.Id, user); + internal void AddUser(User user) => _users.TryAdd(user.UniqueId, user); internal void RemoveUser(User user) { - if (_users.TryRemove(user.Id, out user)) + if (_users.TryRemove(user.UniqueId, out user)) { if (_users.Count == 0) _client.GlobalUsers.TryRemove(Id); diff --git a/src/Discord.Net/Models/Invite.cs b/src/Discord.Net/Models/Invite.cs index e6b122239..0a7c1aad6 100644 --- a/src/Discord.Net/Models/Invite.cs +++ b/src/Discord.Net/Models/Invite.cs @@ -5,10 +5,11 @@ using Newtonsoft.Json; namespace Discord { public sealed class Invite : CachedObject - { - private readonly string _serverId; - private string _inviterId, _channelId; - + { + /// Returns the unique code for this invite. + public string Code { get; private set; } + /// Returns, if enabled, an alternative human-readable code for URLs. + public string XkcdCode { get; } /// Time (in seconds) until the invite expires. Set to 0 to never expire. public int MaxAge { get; private set; } /// The amount of times this invite has been used. @@ -19,47 +20,72 @@ namespace Discord public bool IsRevoked { get; private set; } /// If true, a user accepting this invite will be kicked from the server after closing their client. public bool IsTemporary { get; private set; } - /// Returns, if enabled, an alternative human-readable code for URLs. - public string XkcdPass { get; } - /// Returns a URL for this invite using XkcdPass if available or Id if not. - public string Url => API.Endpoints.InviteUrl(XkcdPass ?? Id); + /// Returns a URL for this invite using XkcdCode if available or Id if not. + public string Url => API.Endpoints.InviteUrl(XkcdCode ?? Code); /// Returns the user that created this invite. [JsonIgnore] - public User Inviter => _client.Users[_inviterId, _serverId]; - + public User Inviter { get; private set; } + [JsonProperty("InviterId")] + private readonly string _inviterId; + /// Returns the server this invite is to. [JsonIgnore] - public Server Server => _client.Servers[_serverId]; - + public Server Server { get; private set; } + [JsonProperty("ServerId")] + private readonly string _serverId; + /// Returns the channel this invite is to. [JsonIgnore] - public Channel Channel => _client.Channels[_channelId]; + public Channel Channel { get; private set; } + [JsonProperty("ChannelId")] + private readonly string _channelId; - internal Invite(DiscordClient client, string code, string xkcdPass, string serverId) + internal Invite(DiscordClient client, string code, string xkcdPass, string serverId, string inviterId, string channelId) : base(client, code) { - XkcdPass = xkcdPass; + XkcdCode = xkcdPass; _serverId = serverId; + _inviterId = inviterId; + _channelId = channelId; } - internal override void OnCached() { } - internal override void OnUncached() { } - public override string ToString() => XkcdPass ?? Id; + internal override void OnCached() + { + var server = _client.Servers[_serverId]; + if (server == null) + server = new Server(_client, _serverId); + Server = server; + if (_inviterId != null) + { + var inviter = _client.Users[_inviterId, _serverId]; + if (inviter == null) + inviter = new User(_client, _inviterId, _serverId); + Inviter = inviter; + } - internal void Update(InviteReference model) + if (_channelId != null) + { + var channel = _client.Channels[_channelId]; + if (channel == null) + channel = new Channel(_client, _channelId, _serverId, null); + Channel = channel; + } + } + internal override void OnUncached() { - if (model.Channel != null) - _channelId = model.Channel.Id; - if (model.Inviter != null) - _inviterId = model.Inviter.Id; + Server = null; + Inviter = null; + Channel = null; } + public override string ToString() => XkcdCode ?? Id; + + internal void Update(InviteInfo model) { - Update(model as InviteReference); if (model.IsRevoked != null) IsRevoked = model.IsRevoked.Value; if (model.IsTemporary != null) diff --git a/src/Discord.Net/Models/Message.cs b/src/Discord.Net/Models/Message.cs index 193ab87e4..f195ef771 100644 --- a/src/Discord.Net/Models/Message.cs +++ b/src/Discord.Net/Models/Message.cs @@ -157,15 +157,18 @@ namespace Discord } internal override void OnCached() { + //References var channel = _client.Channels[_channelId]; channel.AddMessage(this); Channel = channel; } internal override void OnUncached() { + //References var channel = Channel; if (channel != null) channel.RemoveMessage(this); + Channel = null; } internal void Update(MessageInfo model) diff --git a/src/Discord.Net/Models/Role.cs b/src/Discord.Net/Models/Role.cs index 0d0ff9835..2473e8448 100644 --- a/src/Discord.Net/Models/Role.cs +++ b/src/Discord.Net/Models/Role.cs @@ -28,10 +28,11 @@ namespace Discord public Server Server { get; private set; } /// Returns true if this is the role representing all users in a server. - public bool IsEveryone => Id == _serverId; + public bool IsEveryone => _serverId == null || Id == _serverId; /// Returns a list of all members in this role. [JsonIgnore] public IEnumerable Members => IsEveryone ? Server.Members : Server.Members.Where(x => x.HasRole(this)); + //TODO: Add local members cache internal Role(DiscordClient client, string id, string serverId) : base(client, id) @@ -41,18 +42,17 @@ namespace Discord Permissions.Lock(); Color = new Color(0); Color.Lock(); - - if (IsEveryone) - Position = int.MinValue; } internal override void OnCached() { + //References var server = _client.Servers[_serverId]; server.AddRole(this); Server = server; } internal override void OnUncached() { + //References var server = Server; if (server != null) server.RemoveRole(this); diff --git a/src/Discord.Net/Models/Server.cs b/src/Discord.Net/Models/Server.cs index 8cdf95925..20ea50fe8 100644 --- a/src/Discord.Net/Models/Server.cs +++ b/src/Discord.Net/Models/Server.cs @@ -8,21 +8,11 @@ using System.Linq; namespace Discord { public sealed class Server : CachedObject - { - private readonly ConcurrentDictionary _bans; - private readonly ConcurrentDictionary _channels; - private readonly ConcurrentDictionary _members; - private readonly ConcurrentDictionary _roles; - private readonly ConcurrentDictionary _invites; - - private string _ownerId; - + { /// Returns the name of this channel. public string Name { get; private set; } /// Returns the current logged-in user's data for this server. public User CurrentMember { get; internal set; } - /// Returns true if this is a virtual server used by Discord.Net and not a real Discord server. - public bool IsVirtual { get; internal set; } /// Returns the amount of time (in seconds) a user must be inactive for until they are automatically moved to the AFK channel (see AFKChannel). public int AFKTimeout { get; private set; } @@ -38,49 +28,49 @@ namespace Discord /// Returns the user that first created this server. [JsonIgnore] public User Owner { get; private set; } - - /// Returns the id of the AFK voice channel for this server (see AFKTimeout). - public string AFKChannelId { get; private set; } + private string _ownerId; + /// Returns the AFK voice channel for this server (see AFKTimeout). [JsonIgnore] - public Channel AFKChannel => _client.Channels[AFKChannelId]; - - /// Returns the id of the default channel for this server. - public string DefaultChannelId => Id; + public Channel AFKChannel { get; private set; } + /// Returns the default channel for this server. [JsonIgnore] - public Channel DefaultChannel => _client.Channels[DefaultChannelId]; - + public Channel DefaultChannel { get; private set; } + /// Returns a collection of the ids of all users banned on this server. [JsonIgnore] public IEnumerable Bans => _bans.Select(x => x.Key); + private ConcurrentDictionary _bans; /// Returns a collection of all channels within this server. [JsonIgnore] - public IEnumerable Channels => _channels.Select(x => _client.Channels[x.Key]); + public IEnumerable Channels => _channels.Select(x => x.Value); /// Returns a collection of all channels within this server. [JsonIgnore] - public IEnumerable TextChannels => _channels.Select(x => _client.Channels[x.Key]).Where(x => x.Type == ChannelType.Text); + public IEnumerable TextChannels => _channels.Select(x => x.Value).Where(x => x.Type == ChannelType.Text); /// Returns a collection of all channels within this server. [JsonIgnore] - public IEnumerable VoiceChannels => _channels.Select(x => _client.Channels[x.Key]).Where(x => x.Type == ChannelType.Voice); - + public IEnumerable VoiceChannels => _channels.Select(x => x.Value).Where(x => x.Type == ChannelType.Voice); + private ConcurrentDictionary _channels; + /// Returns a collection of all invites to this server. [JsonIgnore] public IEnumerable Invites => _invites.Values; - + private ConcurrentDictionary _invites; + /// Returns a collection of all users within this server with their server-specific data. [JsonIgnore] - public IEnumerable Members => _members.Select(x => _client.Users[x.Key, Id]); + public IEnumerable Members => _members.Select(x => x.Value); + private ConcurrentDictionary _members; - /// Return the id of the role representing all users in a server. - public string EveryoneRoleId => Id; /// Return the the role representing all users in a server. [JsonIgnore] - public Role EveryoneRole => _client.Roles[EveryoneRoleId]; + public Role EveryoneRole { get; private set; } /// Returns a collection of all roles within this server. [JsonIgnore] - public IEnumerable Roles => _roles.Select(x => _client.Roles[x.Key]); + public IEnumerable Roles => _roles.Select(x => x.Value); + private ConcurrentDictionary _roles; internal Server(DiscordClient client, string id) : base(client, id) @@ -98,30 +88,37 @@ namespace Discord internal override void OnUncached() { //Global Cache - var channels = _client.Channels; - foreach (var channel in _channels) - channels.TryRemove(channel.Key); - - var members = _client.Users; - foreach (var user in _members) - members.TryRemove(user.Key, Id); - - var roles = _client.Roles; - foreach (var role in _roles) - roles.TryRemove(role.Key); + var globalChannels = _client.Channels; + var channels = _channels; + foreach (var channel in channels) + globalChannels.TryRemove(channel.Key); + channels.Clear(); + + var globalMembers = _client.Users; + var members = _members; + foreach (var user in members) + globalMembers.TryRemove(user.Key, Id); + members.Clear(); + + var globalRoles = _client.Roles; + var roles = _roles; + foreach (var role in roles) + globalRoles.TryRemove(role.Key); + roles.Clear(); //Local Cache - foreach (var invite in _invites) + var invites = _invites; + foreach (var invite in invites) invite.Value.Uncache(); - _invites.Clear(); + invites.Clear(); _bans.Clear(); - } + } internal void Update(GuildInfo model) { //Can be null - AFKChannelId = model.AFKChannelId; + AFKChannel = _client.Channels[model.AFKChannelId]; if (model.AFKTimeout != null) AFKTimeout = model.AFKTimeout.Value; @@ -139,18 +136,18 @@ namespace Discord if (model.Roles != null) { - var roles = _client.Roles; - foreach (var subModel in model.Roles) + var roleCache = _client.Roles; + foreach (var x in model.Roles) { - var role = roles.GetOrAdd(subModel.Id, Id); - role.Update(subModel); - } - } + var role = roleCache.GetOrAdd(x.Id, Id); + role.Update(x); + } + } } internal void Update(ExtendedGuildInfo model) { Update(model as GuildInfo); - + var channels = _client.Channels; foreach (var subModel in model.Channels) { @@ -158,22 +155,22 @@ namespace Discord channel.Update(subModel); } - var users = _client.GlobalUsers; - var members = _client.Users; + var usersCache = _client.GlobalUsers; + var membersCache = _client.Users; foreach (var subModel in model.Members) { - var member = members.GetOrAdd(subModel.User.Id, Id); + var member = membersCache.GetOrAdd(subModel.User.Id, Id); member.Update(subModel); } foreach (var subModel in model.VoiceStates) { - var member = members[subModel.UserId, Id]; + var member = membersCache[subModel.UserId, Id]; if (member != null) member.Update(subModel); } foreach (var subModel in model.Presences) { - var member = members[subModel.User.Id, Id]; + var member = membersCache[subModel.User.Id, Id]; if (member != null) member.Update(subModel); } @@ -193,9 +190,13 @@ namespace Discord internal void AddChannel(Channel channel) { - _channels.TryAdd(channel.Id, channel); - foreach (var member in Members) - member.AddChannel(channel); + if (_channels.TryAdd(channel.Id, channel)) + { + if (channel.Id == Id) + DefaultChannel = channel; + foreach (var member in Members) + member.AddChannel(channel); + } } internal void RemoveChannel(Channel channel) { @@ -213,7 +214,7 @@ namespace Discord foreach (var channel in Channels) { member.AddChannel(channel); - channel.InvalidatePermissionsCache(member.Id); + channel.InvalidatePermissionsCache(member); } } internal void RemoveMember(User member) @@ -221,13 +222,27 @@ namespace Discord foreach (var channel in Channels) { member.RemoveChannel(channel); - channel.InvalidatePermissionsCache(member.Id); + channel.InvalidatePermissionsCache(member); } _members.TryRemove(member.Id, out member); } internal void HasMember(User user) => _members.ContainsKey(user.Id); - internal void AddRole(Role role) => _roles.TryAdd(role.Id, role); - internal void RemoveRole(Role role) => _roles.TryRemove(role.Id, out role); + internal void AddRole(Role role) + { + if (_roles.TryAdd(role.Id, role)) + { + if (role.Id == Id) + EveryoneRole = role; + } + } + internal void RemoveRole(Role role) + { + if (_roles.TryRemove(role.Id, out role)) + { + if (role.Id == Id) + EveryoneRole = null; + } + } } } diff --git a/src/Discord.Net/Models/User.cs b/src/Discord.Net/Models/User.cs index 72af1165b..2e4021ac7 100644 --- a/src/Discord.Net/Models/User.cs +++ b/src/Discord.Net/Models/User.cs @@ -9,17 +9,14 @@ namespace Discord { public class User : CachedObject { - private static readonly string[] _initialRoleIds = new string[0]; - internal static string GetId(string userId, string serverId) => (serverId ?? "Private") + '_' + userId; private ConcurrentDictionary _channels; private ConcurrentDictionary _permissions; private ServerPermissions _serverPermissions; - private string[] _roleIds; /// Returns a unique identifier combining this user's id with its server's. - internal string UniqueId => GetId(Id, ServerId); + internal string UniqueId => GetId(Id, _serverId); /// Returns the name of this user on this server. public string Name { get; private set; } /// Returns a by-name unique identifier separating this user from others with the same name. @@ -52,48 +49,55 @@ namespace Discord private DateTime _lastOnline; [JsonIgnore] - internal GlobalUser GlobalUser => _client.GlobalUsers[Id]; + internal GlobalUser GlobalUser { get; private set; } - public string ServerId { get; } [JsonIgnore] - public Server Server => _client.Servers[ServerId]; + public Server Server { get; private set; } + private string _serverId; - public string VoiceChannelId { get; private set; } [JsonIgnore] - public Channel VoiceChannel => _client.Channels[VoiceChannelId]; + public Channel VoiceChannel { get; private set; } [JsonIgnore] - public IEnumerable Roles => _roleIds.Select(x => _client.Roles[x]); + public IEnumerable Roles => _roles.Select(x => x.Value); + private Dictionary _roles; + /// Returns a collection of all messages this user has sent on this server that are still in cache. [JsonIgnore] - public IEnumerable Messages => _client.Messages.Where(x => x.UserId == Id && x.Server.Id == ServerId); + public IEnumerable Messages => _client.Messages.Where(x => x.UserId == Id && x.Server.Id == _serverId); + /// Returns a collection of all channels this user is a member of. [JsonIgnore] - public IEnumerable Channels => _client.Channels.Where(x => x.Server.Id == ServerId && x.Members == this); + public IEnumerable Channels => _channels.Select(x => x.Value); internal User(DiscordClient client, string id, string serverId) : base(client, id) { - ServerId = serverId; + _serverId = serverId; Status = UserStatus.Offline; - _roleIds = _initialRoleIds; + //_roles = new Dictionary(); _channels = new ConcurrentDictionary(); _permissions = new ConcurrentDictionary(); _serverPermissions = new ServerPermissions(); } internal override void OnCached() { - var server = Server; - server.AddMember(this); - if (Id == _client.CurrentUserId) - server.CurrentMember = this; + var server = _client.Servers[_serverId]; + if (server != null) + { + server.AddMember(this); + if (Id == _client.CurrentUserId) + server.CurrentMember = this; + Server = server; + } - var user = GlobalUser; - if (server == null || !server.IsVirtual) - user.AddUser(this); + var user = _client.GlobalUsers.GetOrAdd(Id); + user.AddUser(this); + GlobalUser = user; } internal override void OnUncached() { + //References var server = Server; if (server != null) { @@ -101,9 +105,12 @@ namespace Discord if (Id == _client.CurrentUserId) server.CurrentMember = null; } + Server = null; + var globalUser = GlobalUser; if (globalUser != null) globalUser.RemoveUser(this); + GlobalUser = null; } public override string ToString() => Id; @@ -124,7 +131,7 @@ namespace Discord if (model.JoinedAt.HasValue) JoinedAt = model.JoinedAt.Value; if (model.Roles != null) - UpdateRoles(model.Roles); + UpdateRoles(model.Roles.Select(x => _client.Roles[x])); UpdateServerPermissions(); } @@ -142,7 +149,7 @@ namespace Discord Update(model.User as UserReference); if (model.Roles != null) - UpdateRoles(model.Roles); + UpdateRoles(model.Roles.Select(x => _client.Roles[x])); if (model.Status != null && Status != model.Status) { Status = UserStatus.FromString(model.Status); @@ -165,7 +172,7 @@ namespace Discord Token = model.Token; if (model.ChannelId != null) - VoiceChannelId = model.ChannelId; + VoiceChannel = _client.Channels[model.ChannelId]; if (model.IsSelfDeafened != null) IsSelfDeafened = model.IsSelfDeafened.Value; if (model.IsSelfMuted != null) @@ -173,14 +180,16 @@ namespace Discord if (model.IsServerSuppressed != null) IsServerSuppressed = model.IsServerSuppressed.Value; } - private void UpdateRoles(string[] roleIds) + private void UpdateRoles(IEnumerable roles) { - //Set roles, with the everyone role added too - string[] newRoles = new string[roleIds.Length + 1]; - newRoles[0] = ServerId; //Everyone - for (int i = 0; i < roleIds.Length; i++) - newRoles[i + 1] = roleIds[i]; - _roleIds = newRoles; + var newRoles = roles.ToDictionary(x => x.Id, x => x); + Role everyone; + if (_serverId != null) + everyone = Server.EveryoneRole; + else + everyone = _client.Roles.VirtualEveryone; + newRoles.Add(everyone.Id, everyone); + _roles = newRoles; } internal void UpdateActivity(DateTime? activity = null) @@ -191,7 +200,7 @@ namespace Discord internal void UpdateChannelPermissions(Channel channel) { - if (_roleIds == null) return; // We don't have all our data processed yet, this will be called again soon + if (_roles == null) return; // We don't have all our data processed yet, this will be called again soon var server = Server; if (server == null || channel.Server != server) return; @@ -225,14 +234,14 @@ namespace Discord if (newPermissions != oldPermissions) { permissions.SetRawValueInternal(newPermissions); - channel.InvalidMembersCache(); + channel.InvalidateMembersCache(); } permissions.SetRawValueInternal(newPermissions); } internal void UpdateServerPermissions() { - if (_roleIds == null) return; // We don't have all our data processed yet, this will be called again soon + if (_roles == null) return; // We don't have all our data processed yet, this will be called again soon var server = Server; if (server == null) return; @@ -290,7 +299,7 @@ namespace Discord { if (role == null) throw new ArgumentNullException(nameof(role)); - return _roleIds.Contains(role.Id); + return _roles.ContainsKey(role.Id); } } } \ No newline at end of file diff --git a/src/Discord.Net/Net/WebSockets/WebSocket.cs b/src/Discord.Net/Net/WebSockets/WebSocket.cs index 0b72cc064..157923c3f 100644 --- a/src/Discord.Net/Net/WebSockets/WebSocket.cs +++ b/src/Discord.Net/Net/WebSockets/WebSocket.cs @@ -53,21 +53,21 @@ namespace Discord.Net.WebSockets _connectedEvent = new ManualResetEventSlim(false); _engine = new WebSocketSharpEngine(this, client.Config); - _engine.BinaryMessage += async (s, e) => + _engine.BinaryMessage += (s, e) => { using (var compressed = new MemoryStream(e.Data, 2, e.Data.Length - 2)) using (var decompressed = new MemoryStream()) { using (var zlib = new DeflateStream(compressed, CompressionMode.Decompress)) - await zlib.CopyToAsync(decompressed); + zlib.CopyTo(decompressed); decompressed.Position = 0; using (var reader = new StreamReader(decompressed)) - await ProcessMessage(await reader.ReadToEndAsync()); + ProcessMessage(reader.ReadToEnd()).Wait(); } }; - _engine.TextMessage += async (s, e) => + _engine.TextMessage += (s, e) => { - await ProcessMessage(e.Message); + /*await*/ ProcessMessage(e.Message).Wait(); }; }