Lots of bug fixes

This commit is contained in:
RogueException
2015-10-25 01:02:28 -03:00
parent 6bd8472127
commit 1fcbd36ead
17 changed files with 273 additions and 185 deletions

View File

@@ -36,18 +36,20 @@ namespace Discord
public Task Ban(User member) public Task Ban(User member)
{ {
if (member == null) throw new ArgumentNullException(nameof(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(); CheckReady();
return _api.Ban(member.ServerId, member.Id); return _api.Ban(member.Server.Id, member.Id);
} }
/// <summary> Unbans a user from the provided server. </summary> /// <summary> Unbans a user from the provided server. </summary>
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(); 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) { } catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { }
} }
} }

View File

@@ -10,6 +10,8 @@ namespace Discord
/// <remarks> Supported formats: inviteCode, xkcdCode, https://discord.gg/inviteCode, https://discord.gg/xkcdCode </remarks> /// <remarks> Supported formats: inviteCode, xkcdCode, https://discord.gg/inviteCode, https://discord.gg/xkcdCode </remarks>
public async Task<Invite> GetInvite(string inviteIdOrXkcd) public async Task<Invite> 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)); if (inviteIdOrXkcd == null) throw new ArgumentNullException(nameof(inviteIdOrXkcd));
CheckReady(); CheckReady();
@@ -22,8 +24,8 @@ namespace Discord
inviteIdOrXkcd = inviteIdOrXkcd.Substring(index + 1); inviteIdOrXkcd = inviteIdOrXkcd.Substring(index + 1);
var response = await _api.GetInvite(inviteIdOrXkcd).ConfigureAwait(false); var response = await _api.GetInvite(inviteIdOrXkcd).ConfigureAwait(false);
var invite = new Invite(this, response.Code, response.XkcdPass, response.Guild.Id); var invite = new Invite(this, response.Code, response.XkcdPass, response.Guild.Id, response.Inviter?.Id, response.Channel?.Id);
invite.Update(response); invite.Cache(); //Builds references
return invite; return invite;
} }
@@ -53,8 +55,8 @@ namespace Discord
var response = await _api.CreateInvite(channel.Id, maxAge: maxAge, maxUses: maxUses, var response = await _api.CreateInvite(channel.Id, maxAge: maxAge, maxUses: maxUses,
tempMembership: tempMembership, hasXkcd: hasXkcd).ConfigureAwait(false); tempMembership: tempMembership, hasXkcd: hasXkcd).ConfigureAwait(false);
var invite = new Invite(this, response.Code, response.XkcdPass, response.Guild.Id); var invite = new Invite(this, response.Code, response.XkcdPass, response.Guild.Id, response.Inviter?.Id, response.Channel?.Id);
invite.Update(response); invite.Cache(); //Builds references
return invite; return invite;
} }

View File

@@ -121,7 +121,7 @@ namespace Discord
if (member == null) throw new ArgumentNullException(nameof(member)); if (member == null) throw new ArgumentNullException(nameof(member));
CheckReady(); 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));
} }
} }
} }

View File

@@ -25,7 +25,7 @@ namespace Discord
else else
{ {
var msg = new Message(_client, id, channelId, userId); var msg = new Message(_client, id, channelId, userId);
msg.Cache(); //Creates references to channel/server msg.Cache(); //Builds references
return msg; return msg;
} }
} }

View File

@@ -81,9 +81,17 @@ namespace Discord
if (changed) if (changed)
{ {
if (targetType == PermissionTarget.Role) if (targetType == PermissionTarget.Role)
channel.InvalidatePermissionsCache(); {
var role = _roles[targetId];
if (role != null)
channel.InvalidatePermissionsCache(role);
}
else if (targetType == PermissionTarget.User) 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(); channel.PermissionOverwrites.Where(x => x.TargetType != targetType || x.TargetId != userOrRoleId).ToArray();
if (targetType == PermissionTarget.Role) if (targetType == PermissionTarget.Role)
channel.InvalidatePermissionsCache(); {
var role = _roles[userOrRoleId];
channel.InvalidatePermissionsCache(role);
}
else if (targetType == PermissionTarget.User) 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) { } catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { }

View File

@@ -7,11 +7,13 @@ namespace Discord
{ {
internal sealed class Roles : AsyncCollection<Role> internal sealed class Roles : AsyncCollection<Role>
{ {
private const string VirtualEveryoneId = "[Virtual]";
public Role VirtualEveryone { get; private set; } public Role VirtualEveryone { get; private set; }
public Roles(DiscordClient client, object writerLock) 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) public Role GetOrAdd(string id, string serverId)
=> GetOrAdd(id, () => new Role(_client, id, serverId)); => GetOrAdd(id, () => new Role(_client, id, serverId));

View File

@@ -57,7 +57,7 @@ namespace Discord
} }
/// <summary> Returns a collection of all servers this client is a member of. </summary> /// <summary> Returns a collection of all servers this client is a member of. </summary>
public IEnumerable<Server> AllServers => _servers.Where(x => !x.IsVirtual); public IEnumerable<Server> AllServers => _servers;
internal Servers Servers => _servers; internal Servers Servers => _servers;
private readonly Servers _servers; private readonly Servers _servers;

View File

@@ -59,12 +59,16 @@ namespace Discord
VoiceDisconnected += (s, e) => 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; if (member.IsSpeaking)
RaiseUserIsSpeaking(member, _channels[_voiceSocket.CurrentChannelId], false); {
member.IsSpeaking = false;
RaiseUserIsSpeaking(member, _channels[_voiceSocket.CurrentChannelId], false);
}
} }
} }
}; };
@@ -104,13 +108,13 @@ namespace Discord
BanRemoved += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, BanRemoved += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client,
$"Removed Ban: {e.Server?.Name ?? "[Private]"}/{e.UserId}"); $"Removed Ban: {e.Server?.Name ?? "[Private]"}/{e.UserId}");
UserAdded += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, 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, 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, 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, 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, ProfileUpdated += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client,
"Updated Profile"); "Updated Profile");
} }
@@ -281,13 +285,12 @@ namespace Discord
{ {
try try
{ {
await base.OnReceivedEvent(e);
switch (e.Type) switch (e.Type)
{ {
//Global //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<ReadyEvent>(_serializer); var data = e.Payload.ToObject<ReadyEvent>(_serializer);
_currentUser = _users.GetOrAdd(data.User.Id, null); _currentUser = _users.GetOrAdd(data.User.Id, null);
_currentUser.Update(data.User); _currentUser.Update(data.User);
@@ -576,10 +579,11 @@ namespace Discord
var member = _users[data.UserId, data.GuildId]; var member = _users[data.UserId, data.GuildId];
if (member != null) 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; member.IsSpeaking = false;
RaiseUserIsSpeaking(member, _channels[member.VoiceChannelId], false); RaiseUserIsSpeaking(member, _channels[voiceChannel.Id], false);
} }
member.Update(data); member.Update(data);
RaiseUserVoiceStateUpdated(member); RaiseUserVoiceStateUpdated(member);
@@ -611,6 +615,7 @@ namespace Discord
//Internal (handled in DiscordSimpleClient) //Internal (handled in DiscordSimpleClient)
case "VOICE_SERVER_UPDATE": case "VOICE_SERVER_UPDATE":
await base.OnReceivedEvent(e);
break; break;
//Others //Others

View File

@@ -90,7 +90,7 @@ namespace Discord
} }
} }
socket.ReceivedEvent += (s, e) => OnReceivedEvent(e); socket.ReceivedEvent += async (s, e) => await OnReceivedEvent(e);
return socket; return socket;
} }
internal virtual VoiceWebSocket CreateVoiceSocket() internal virtual VoiceWebSocket CreateVoiceSocket()
@@ -292,7 +292,7 @@ namespace Discord
} }
} }
internal virtual Task OnReceivedEvent(WebSocketEventEventArgs e) internal virtual async Task OnReceivedEvent(WebSocketEventEventArgs e)
{ {
try try
{ {
@@ -309,7 +309,7 @@ namespace Discord
{ {
string token = e.Payload.Value<string>("token"); string token = e.Payload.Value<string>("token");
_voiceSocket.Host = "wss://" + e.Payload.Value<string>("endpoint").Split(':')[0]; _voiceSocket.Host = "wss://" + e.Payload.Value<string>("endpoint").Split(':')[0];
return _voiceSocket.Login(_userId, _dataSocket.SessionId, token, CancelToken); await _voiceSocket.Login(_userId, _dataSocket.SessionId, token, CancelToken);
} }
} }
break; break;
@@ -319,7 +319,6 @@ namespace Discord
{ {
RaiseOnLog(LogMessageSeverity.Error, LogMessageSource.Client, $"Error handling {e.Type} event: {ex.GetBaseException().Message}"); RaiseOnLog(LogMessageSeverity.Error, LogMessageSource.Client, $"Error handling {e.Type} event: {ex.GetBaseException().Message}");
} }
return TaskHelper.CompletedTask;
} }
} }
} }

View File

@@ -53,11 +53,8 @@ namespace Discord
{ {
get get
{ {
if (!_areMembersStale) if (_areMembersStale)
return _members.Select(x => x.Value); UpdateMembersCache();
_members = Server.Members.Where(x => x.GetPermissions(this)?.ReadMessages ?? false).ToDictionary(x => x.Id, x => x);
_areMembersStale = false;
return _members.Select(x => x.Value); 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 RemoveMessage(Message message) => _messages.TryRemove(message.Id, out message);
internal void InvalidMembersCache() internal void InvalidateMembersCache()
{ {
_areMembersStale = true; _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; _areMembersStale = true;
foreach (var member in Members) foreach (var member in role.Members)
member.UpdateChannelPermissions(this); member.UpdateChannelPermissions(this);
} }
internal void InvalidatePermissionsCache(string userId) internal void InvalidatePermissionsCache(User user)
{ {
_areMembersStale = true; _areMembersStale = true;
var user = _members[userId] user.UpdateChannelPermissions(this);
if (user != null)
user.UpdateChannelPermissions(this);
} }
} }
} }

View File

@@ -34,7 +34,10 @@ namespace Discord
_users = new ConcurrentDictionary<string, User>(); _users = new ConcurrentDictionary<string, User>();
} }
internal override void OnCached() { } 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) internal void Update(UserInfo model)
{ {
@@ -44,10 +47,10 @@ namespace Discord
IsVerified = model.IsVerified; 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) internal void RemoveUser(User user)
{ {
if (_users.TryRemove(user.Id, out user)) if (_users.TryRemove(user.UniqueId, out user))
{ {
if (_users.Count == 0) if (_users.Count == 0)
_client.GlobalUsers.TryRemove(Id); _client.GlobalUsers.TryRemove(Id);

View File

@@ -5,10 +5,11 @@ using Newtonsoft.Json;
namespace Discord namespace Discord
{ {
public sealed class Invite : CachedObject public sealed class Invite : CachedObject
{ {
private readonly string _serverId; /// <summary> Returns the unique code for this invite. </summary>
private string _inviterId, _channelId; public string Code { get; private set; }
/// <summary> Returns, if enabled, an alternative human-readable code for URLs. </summary>
public string XkcdCode { get; }
/// <summary> Time (in seconds) until the invite expires. Set to 0 to never expire. </summary> /// <summary> Time (in seconds) until the invite expires. Set to 0 to never expire. </summary>
public int MaxAge { get; private set; } public int MaxAge { get; private set; }
/// <summary> The amount of times this invite has been used. </summary> /// <summary> The amount of times this invite has been used. </summary>
@@ -19,47 +20,72 @@ namespace Discord
public bool IsRevoked { get; private set; } public bool IsRevoked { get; private set; }
/// <summary> If true, a user accepting this invite will be kicked from the server after closing their client. </summary> /// <summary> If true, a user accepting this invite will be kicked from the server after closing their client. </summary>
public bool IsTemporary { get; private set; } public bool IsTemporary { get; private set; }
/// <summary> Returns, if enabled, an alternative human-readable code for URLs. </summary>
public string XkcdPass { get; }
/// <summary> Returns a URL for this invite using XkcdPass if available or Id if not. </summary> /// <summary> Returns a URL for this invite using XkcdCode if available or Id if not. </summary>
public string Url => API.Endpoints.InviteUrl(XkcdPass ?? Id); public string Url => API.Endpoints.InviteUrl(XkcdCode ?? Code);
/// <summary> Returns the user that created this invite. </summary> /// <summary> Returns the user that created this invite. </summary>
[JsonIgnore] [JsonIgnore]
public User Inviter => _client.Users[_inviterId, _serverId]; public User Inviter { get; private set; }
[JsonProperty("InviterId")]
private readonly string _inviterId;
/// <summary> Returns the server this invite is to. </summary> /// <summary> Returns the server this invite is to. </summary>
[JsonIgnore] [JsonIgnore]
public Server Server => _client.Servers[_serverId]; public Server Server { get; private set; }
[JsonProperty("ServerId")]
private readonly string _serverId;
/// <summary> Returns the channel this invite is to. </summary> /// <summary> Returns the channel this invite is to. </summary>
[JsonIgnore] [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) : base(client, code)
{ {
XkcdPass = xkcdPass; XkcdCode = xkcdPass;
_serverId = serverId; _serverId = serverId;
_inviterId = inviterId;
_channelId = channelId;
} }
internal override void OnCached() { }
internal override void OnUncached() { }
public override string ToString() => XkcdPass ?? Id; internal override void OnCached()
internal void Update(InviteReference model)
{ {
if (model.Channel != null) var server = _client.Servers[_serverId];
_channelId = model.Channel.Id; if (server == null)
if (model.Inviter != null) server = new Server(_client, _serverId);
_inviterId = model.Inviter.Id; Server = server;
if (_inviterId != null)
{
var inviter = _client.Users[_inviterId, _serverId];
if (inviter == null)
inviter = new User(_client, _inviterId, _serverId);
Inviter = inviter;
}
if (_channelId != null)
{
var channel = _client.Channels[_channelId];
if (channel == null)
channel = new Channel(_client, _channelId, _serverId, null);
Channel = channel;
}
} }
internal override void OnUncached()
{
Server = null;
Inviter = null;
Channel = null;
}
public override string ToString() => XkcdCode ?? Id;
internal void Update(InviteInfo model) internal void Update(InviteInfo model)
{ {
Update(model as InviteReference);
if (model.IsRevoked != null) if (model.IsRevoked != null)
IsRevoked = model.IsRevoked.Value; IsRevoked = model.IsRevoked.Value;
if (model.IsTemporary != null) if (model.IsTemporary != null)

View File

@@ -157,15 +157,18 @@ namespace Discord
} }
internal override void OnCached() internal override void OnCached()
{ {
//References
var channel = _client.Channels[_channelId]; var channel = _client.Channels[_channelId];
channel.AddMessage(this); channel.AddMessage(this);
Channel = channel; Channel = channel;
} }
internal override void OnUncached() internal override void OnUncached()
{ {
//References
var channel = Channel; var channel = Channel;
if (channel != null) if (channel != null)
channel.RemoveMessage(this); channel.RemoveMessage(this);
Channel = null;
} }
internal void Update(MessageInfo model) internal void Update(MessageInfo model)

View File

@@ -28,10 +28,11 @@ namespace Discord
public Server Server { get; private set; } public Server Server { get; private set; }
/// <summary> Returns true if this is the role representing all users in a server. </summary> /// <summary> Returns true if this is the role representing all users in a server. </summary>
public bool IsEveryone => Id == _serverId; public bool IsEveryone => _serverId == null || Id == _serverId;
/// <summary> Returns a list of all members in this role. </summary> /// <summary> Returns a list of all members in this role. </summary>
[JsonIgnore] [JsonIgnore]
public IEnumerable<User> Members => IsEveryone ? Server.Members : Server.Members.Where(x => x.HasRole(this)); public IEnumerable<User> Members => IsEveryone ? Server.Members : Server.Members.Where(x => x.HasRole(this));
//TODO: Add local members cache
internal Role(DiscordClient client, string id, string serverId) internal Role(DiscordClient client, string id, string serverId)
: base(client, id) : base(client, id)
@@ -41,18 +42,17 @@ namespace Discord
Permissions.Lock(); Permissions.Lock();
Color = new Color(0); Color = new Color(0);
Color.Lock(); Color.Lock();
if (IsEveryone)
Position = int.MinValue;
} }
internal override void OnCached() internal override void OnCached()
{ {
//References
var server = _client.Servers[_serverId]; var server = _client.Servers[_serverId];
server.AddRole(this); server.AddRole(this);
Server = server; Server = server;
} }
internal override void OnUncached() internal override void OnUncached()
{ {
//References
var server = Server; var server = Server;
if (server != null) if (server != null)
server.RemoveRole(this); server.RemoveRole(this);

View File

@@ -8,21 +8,11 @@ using System.Linq;
namespace Discord namespace Discord
{ {
public sealed class Server : CachedObject public sealed class Server : CachedObject
{ {
private readonly ConcurrentDictionary<string, bool> _bans;
private readonly ConcurrentDictionary<string, Channel> _channels;
private readonly ConcurrentDictionary<string, User> _members;
private readonly ConcurrentDictionary<string, Role> _roles;
private readonly ConcurrentDictionary<string, Invite> _invites;
private string _ownerId;
/// <summary> Returns the name of this channel. </summary> /// <summary> Returns the name of this channel. </summary>
public string Name { get; private set; } public string Name { get; private set; }
/// <summary> Returns the current logged-in user's data for this server. </summary> /// <summary> Returns the current logged-in user's data for this server. </summary>
public User CurrentMember { get; internal set; } public User CurrentMember { get; internal set; }
/// <summary> Returns true if this is a virtual server used by Discord.Net and not a real Discord server. </summary>
public bool IsVirtual { get; internal set; }
/// <summary> Returns the amount of time (in seconds) a user must be inactive for until they are automatically moved to the AFK channel (see AFKChannel). </summary> /// <summary> Returns the amount of time (in seconds) a user must be inactive for until they are automatically moved to the AFK channel (see AFKChannel). </summary>
public int AFKTimeout { get; private set; } public int AFKTimeout { get; private set; }
@@ -38,49 +28,49 @@ namespace Discord
/// <summary> Returns the user that first created this server. </summary> /// <summary> Returns the user that first created this server. </summary>
[JsonIgnore] [JsonIgnore]
public User Owner { get; private set; } public User Owner { get; private set; }
private string _ownerId;
/// <summary> Returns the id of the AFK voice channel for this server (see AFKTimeout). </summary>
public string AFKChannelId { get; private set; }
/// <summary> Returns the AFK voice channel for this server (see AFKTimeout). </summary> /// <summary> Returns the AFK voice channel for this server (see AFKTimeout). </summary>
[JsonIgnore] [JsonIgnore]
public Channel AFKChannel => _client.Channels[AFKChannelId]; public Channel AFKChannel { get; private set; }
/// <summary> Returns the id of the default channel for this server. </summary>
public string DefaultChannelId => Id;
/// <summary> Returns the default channel for this server. </summary> /// <summary> Returns the default channel for this server. </summary>
[JsonIgnore] [JsonIgnore]
public Channel DefaultChannel => _client.Channels[DefaultChannelId]; public Channel DefaultChannel { get; private set; }
/// <summary> Returns a collection of the ids of all users banned on this server. </summary> /// <summary> Returns a collection of the ids of all users banned on this server. </summary>
[JsonIgnore] [JsonIgnore]
public IEnumerable<string> Bans => _bans.Select(x => x.Key); public IEnumerable<string> Bans => _bans.Select(x => x.Key);
private ConcurrentDictionary<string, bool> _bans;
/// <summary> Returns a collection of all channels within this server. </summary> /// <summary> Returns a collection of all channels within this server. </summary>
[JsonIgnore] [JsonIgnore]
public IEnumerable<Channel> Channels => _channels.Select(x => _client.Channels[x.Key]); public IEnumerable<Channel> Channels => _channels.Select(x => x.Value);
/// <summary> Returns a collection of all channels within this server. </summary> /// <summary> Returns a collection of all channels within this server. </summary>
[JsonIgnore] [JsonIgnore]
public IEnumerable<Channel> TextChannels => _channels.Select(x => _client.Channels[x.Key]).Where(x => x.Type == ChannelType.Text); public IEnumerable<Channel> TextChannels => _channels.Select(x => x.Value).Where(x => x.Type == ChannelType.Text);
/// <summary> Returns a collection of all channels within this server. </summary> /// <summary> Returns a collection of all channels within this server. </summary>
[JsonIgnore] [JsonIgnore]
public IEnumerable<Channel> VoiceChannels => _channels.Select(x => _client.Channels[x.Key]).Where(x => x.Type == ChannelType.Voice); public IEnumerable<Channel> VoiceChannels => _channels.Select(x => x.Value).Where(x => x.Type == ChannelType.Voice);
private ConcurrentDictionary<string, Channel> _channels;
/// <summary> Returns a collection of all invites to this server. </summary> /// <summary> Returns a collection of all invites to this server. </summary>
[JsonIgnore] [JsonIgnore]
public IEnumerable<Invite> Invites => _invites.Values; public IEnumerable<Invite> Invites => _invites.Values;
private ConcurrentDictionary<string, Invite> _invites;
/// <summary> Returns a collection of all users within this server with their server-specific data. </summary> /// <summary> Returns a collection of all users within this server with their server-specific data. </summary>
[JsonIgnore] [JsonIgnore]
public IEnumerable<User> Members => _members.Select(x => _client.Users[x.Key, Id]); public IEnumerable<User> Members => _members.Select(x => x.Value);
private ConcurrentDictionary<string, User> _members;
/// <summary> Return the id of the role representing all users in a server. </summary>
public string EveryoneRoleId => Id;
/// <summary> Return the the role representing all users in a server. </summary> /// <summary> Return the the role representing all users in a server. </summary>
[JsonIgnore] [JsonIgnore]
public Role EveryoneRole => _client.Roles[EveryoneRoleId]; public Role EveryoneRole { get; private set; }
/// <summary> Returns a collection of all roles within this server. </summary> /// <summary> Returns a collection of all roles within this server. </summary>
[JsonIgnore] [JsonIgnore]
public IEnumerable<Role> Roles => _roles.Select(x => _client.Roles[x.Key]); public IEnumerable<Role> Roles => _roles.Select(x => x.Value);
private ConcurrentDictionary<string, Role> _roles;
internal Server(DiscordClient client, string id) internal Server(DiscordClient client, string id)
: base(client, id) : base(client, id)
@@ -98,30 +88,37 @@ namespace Discord
internal override void OnUncached() internal override void OnUncached()
{ {
//Global Cache //Global Cache
var channels = _client.Channels; var globalChannels = _client.Channels;
foreach (var channel in _channels) var channels = _channels;
channels.TryRemove(channel.Key); foreach (var channel in channels)
globalChannels.TryRemove(channel.Key);
channels.Clear();
var members = _client.Users; var globalMembers = _client.Users;
foreach (var user in _members) var members = _members;
members.TryRemove(user.Key, Id); foreach (var user in members)
globalMembers.TryRemove(user.Key, Id);
members.Clear();
var roles = _client.Roles; var globalRoles = _client.Roles;
foreach (var role in _roles) var roles = _roles;
roles.TryRemove(role.Key); foreach (var role in roles)
globalRoles.TryRemove(role.Key);
roles.Clear();
//Local Cache //Local Cache
foreach (var invite in _invites) var invites = _invites;
foreach (var invite in invites)
invite.Value.Uncache(); invite.Value.Uncache();
_invites.Clear(); invites.Clear();
_bans.Clear(); _bans.Clear();
} }
internal void Update(GuildInfo model) internal void Update(GuildInfo model)
{ {
//Can be null //Can be null
AFKChannelId = model.AFKChannelId; AFKChannel = _client.Channels[model.AFKChannelId];
if (model.AFKTimeout != null) if (model.AFKTimeout != null)
AFKTimeout = model.AFKTimeout.Value; AFKTimeout = model.AFKTimeout.Value;
@@ -139,18 +136,18 @@ namespace Discord
if (model.Roles != null) if (model.Roles != null)
{ {
var roles = _client.Roles; var roleCache = _client.Roles;
foreach (var subModel in model.Roles) foreach (var x in model.Roles)
{ {
var role = roles.GetOrAdd(subModel.Id, Id); var role = roleCache.GetOrAdd(x.Id, Id);
role.Update(subModel); role.Update(x);
} }
} }
} }
internal void Update(ExtendedGuildInfo model) internal void Update(ExtendedGuildInfo model)
{ {
Update(model as GuildInfo); Update(model as GuildInfo);
var channels = _client.Channels; var channels = _client.Channels;
foreach (var subModel in model.Channels) foreach (var subModel in model.Channels)
{ {
@@ -158,22 +155,22 @@ namespace Discord
channel.Update(subModel); channel.Update(subModel);
} }
var users = _client.GlobalUsers; var usersCache = _client.GlobalUsers;
var members = _client.Users; var membersCache = _client.Users;
foreach (var subModel in model.Members) 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); member.Update(subModel);
} }
foreach (var subModel in model.VoiceStates) foreach (var subModel in model.VoiceStates)
{ {
var member = members[subModel.UserId, Id]; var member = membersCache[subModel.UserId, Id];
if (member != null) if (member != null)
member.Update(subModel); member.Update(subModel);
} }
foreach (var subModel in model.Presences) foreach (var subModel in model.Presences)
{ {
var member = members[subModel.User.Id, Id]; var member = membersCache[subModel.User.Id, Id];
if (member != null) if (member != null)
member.Update(subModel); member.Update(subModel);
} }
@@ -193,9 +190,13 @@ namespace Discord
internal void AddChannel(Channel channel) internal void AddChannel(Channel channel)
{ {
_channels.TryAdd(channel.Id, channel); if (_channels.TryAdd(channel.Id, channel))
foreach (var member in Members) {
member.AddChannel(channel); if (channel.Id == Id)
DefaultChannel = channel;
foreach (var member in Members)
member.AddChannel(channel);
}
} }
internal void RemoveChannel(Channel channel) internal void RemoveChannel(Channel channel)
{ {
@@ -213,7 +214,7 @@ namespace Discord
foreach (var channel in Channels) foreach (var channel in Channels)
{ {
member.AddChannel(channel); member.AddChannel(channel);
channel.InvalidatePermissionsCache(member.Id); channel.InvalidatePermissionsCache(member);
} }
} }
internal void RemoveMember(User member) internal void RemoveMember(User member)
@@ -221,13 +222,27 @@ namespace Discord
foreach (var channel in Channels) foreach (var channel in Channels)
{ {
member.RemoveChannel(channel); member.RemoveChannel(channel);
channel.InvalidatePermissionsCache(member.Id); channel.InvalidatePermissionsCache(member);
} }
_members.TryRemove(member.Id, out member); _members.TryRemove(member.Id, out member);
} }
internal void HasMember(User user) => _members.ContainsKey(user.Id); internal void HasMember(User user) => _members.ContainsKey(user.Id);
internal void AddRole(Role role) => _roles.TryAdd(role.Id, role); internal void AddRole(Role role)
internal void RemoveRole(Role role) => _roles.TryRemove(role.Id, out 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;
}
}
} }
} }

View File

@@ -9,17 +9,14 @@ namespace Discord
{ {
public class User : CachedObject public class User : CachedObject
{ {
private static readonly string[] _initialRoleIds = new string[0];
internal static string GetId(string userId, string serverId) => (serverId ?? "Private") + '_' + userId; internal static string GetId(string userId, string serverId) => (serverId ?? "Private") + '_' + userId;
private ConcurrentDictionary<string, Channel> _channels; private ConcurrentDictionary<string, Channel> _channels;
private ConcurrentDictionary<string, ChannelPermissions> _permissions; private ConcurrentDictionary<string, ChannelPermissions> _permissions;
private ServerPermissions _serverPermissions; private ServerPermissions _serverPermissions;
private string[] _roleIds;
/// <summary> Returns a unique identifier combining this user's id with its server's. </summary> /// <summary> Returns a unique identifier combining this user's id with its server's. </summary>
internal string UniqueId => GetId(Id, ServerId); internal string UniqueId => GetId(Id, _serverId);
/// <summary> Returns the name of this user on this server. </summary> /// <summary> Returns the name of this user on this server. </summary>
public string Name { get; private set; } public string Name { get; private set; }
/// <summary> Returns a by-name unique identifier separating this user from others with the same name. </summary> /// <summary> Returns a by-name unique identifier separating this user from others with the same name. </summary>
@@ -52,48 +49,55 @@ namespace Discord
private DateTime _lastOnline; private DateTime _lastOnline;
[JsonIgnore] [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 string VoiceChannelId { get; private set; }
[JsonIgnore]
public Channel VoiceChannel => _client.Channels[VoiceChannelId];
[JsonIgnore] [JsonIgnore]
public IEnumerable<Role> Roles => _roleIds.Select(x => _client.Roles[x]); public Server Server { get; private set; }
private string _serverId;
[JsonIgnore]
public Channel VoiceChannel { get; private set; }
[JsonIgnore]
public IEnumerable<Role> Roles => _roles.Select(x => x.Value);
private Dictionary<string, Role> _roles;
/// <summary> Returns a collection of all messages this user has sent on this server that are still in cache. </summary> /// <summary> Returns a collection of all messages this user has sent on this server that are still in cache. </summary>
[JsonIgnore] [JsonIgnore]
public IEnumerable<Message> Messages => _client.Messages.Where(x => x.UserId == Id && x.Server.Id == ServerId); public IEnumerable<Message> Messages => _client.Messages.Where(x => x.UserId == Id && x.Server.Id == _serverId);
/// <summary> Returns a collection of all channels this user is a member of. </summary> /// <summary> Returns a collection of all channels this user is a member of. </summary>
[JsonIgnore] [JsonIgnore]
public IEnumerable<Channel> Channels => _client.Channels.Where(x => x.Server.Id == ServerId && x.Members == this); public IEnumerable<Channel> Channels => _channels.Select(x => x.Value);
internal User(DiscordClient client, string id, string serverId) internal User(DiscordClient client, string id, string serverId)
: base(client, id) : base(client, id)
{ {
ServerId = serverId; _serverId = serverId;
Status = UserStatus.Offline; Status = UserStatus.Offline;
_roleIds = _initialRoleIds; //_roles = new Dictionary<string, Role>();
_channels = new ConcurrentDictionary<string, Channel>(); _channels = new ConcurrentDictionary<string, Channel>();
_permissions = new ConcurrentDictionary<string, ChannelPermissions>(); _permissions = new ConcurrentDictionary<string, ChannelPermissions>();
_serverPermissions = new ServerPermissions(); _serverPermissions = new ServerPermissions();
} }
internal override void OnCached() internal override void OnCached()
{ {
var server = Server; var server = _client.Servers[_serverId];
server.AddMember(this); if (server != null)
if (Id == _client.CurrentUserId) {
server.CurrentMember = this; server.AddMember(this);
if (Id == _client.CurrentUserId)
server.CurrentMember = this;
Server = server;
}
var user = GlobalUser; var user = _client.GlobalUsers.GetOrAdd(Id);
if (server == null || !server.IsVirtual) user.AddUser(this);
user.AddUser(this); GlobalUser = user;
} }
internal override void OnUncached() internal override void OnUncached()
{ {
//References
var server = Server; var server = Server;
if (server != null) if (server != null)
{ {
@@ -101,9 +105,12 @@ namespace Discord
if (Id == _client.CurrentUserId) if (Id == _client.CurrentUserId)
server.CurrentMember = null; server.CurrentMember = null;
} }
Server = null;
var globalUser = GlobalUser; var globalUser = GlobalUser;
if (globalUser != null) if (globalUser != null)
globalUser.RemoveUser(this); globalUser.RemoveUser(this);
GlobalUser = null;
} }
public override string ToString() => Id; public override string ToString() => Id;
@@ -124,7 +131,7 @@ namespace Discord
if (model.JoinedAt.HasValue) if (model.JoinedAt.HasValue)
JoinedAt = model.JoinedAt.Value; JoinedAt = model.JoinedAt.Value;
if (model.Roles != null) if (model.Roles != null)
UpdateRoles(model.Roles); UpdateRoles(model.Roles.Select(x => _client.Roles[x]));
UpdateServerPermissions(); UpdateServerPermissions();
} }
@@ -142,7 +149,7 @@ namespace Discord
Update(model.User as UserReference); Update(model.User as UserReference);
if (model.Roles != null) if (model.Roles != null)
UpdateRoles(model.Roles); UpdateRoles(model.Roles.Select(x => _client.Roles[x]));
if (model.Status != null && Status != model.Status) if (model.Status != null && Status != model.Status)
{ {
Status = UserStatus.FromString(model.Status); Status = UserStatus.FromString(model.Status);
@@ -165,7 +172,7 @@ namespace Discord
Token = model.Token; Token = model.Token;
if (model.ChannelId != null) if (model.ChannelId != null)
VoiceChannelId = model.ChannelId; VoiceChannel = _client.Channels[model.ChannelId];
if (model.IsSelfDeafened != null) if (model.IsSelfDeafened != null)
IsSelfDeafened = model.IsSelfDeafened.Value; IsSelfDeafened = model.IsSelfDeafened.Value;
if (model.IsSelfMuted != null) if (model.IsSelfMuted != null)
@@ -173,14 +180,16 @@ namespace Discord
if (model.IsServerSuppressed != null) if (model.IsServerSuppressed != null)
IsServerSuppressed = model.IsServerSuppressed.Value; IsServerSuppressed = model.IsServerSuppressed.Value;
} }
private void UpdateRoles(string[] roleIds) private void UpdateRoles(IEnumerable<Role> roles)
{ {
//Set roles, with the everyone role added too var newRoles = roles.ToDictionary(x => x.Id, x => x);
string[] newRoles = new string[roleIds.Length + 1]; Role everyone;
newRoles[0] = ServerId; //Everyone if (_serverId != null)
for (int i = 0; i < roleIds.Length; i++) everyone = Server.EveryoneRole;
newRoles[i + 1] = roleIds[i]; else
_roleIds = newRoles; everyone = _client.Roles.VirtualEveryone;
newRoles.Add(everyone.Id, everyone);
_roles = newRoles;
} }
internal void UpdateActivity(DateTime? activity = null) internal void UpdateActivity(DateTime? activity = null)
@@ -191,7 +200,7 @@ namespace Discord
internal void UpdateChannelPermissions(Channel channel) 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; var server = Server;
if (server == null || channel.Server != server) return; if (server == null || channel.Server != server) return;
@@ -225,14 +234,14 @@ namespace Discord
if (newPermissions != oldPermissions) if (newPermissions != oldPermissions)
{ {
permissions.SetRawValueInternal(newPermissions); permissions.SetRawValueInternal(newPermissions);
channel.InvalidMembersCache(); channel.InvalidateMembersCache();
} }
permissions.SetRawValueInternal(newPermissions); permissions.SetRawValueInternal(newPermissions);
} }
internal void UpdateServerPermissions() 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; var server = Server;
if (server == null) return; if (server == null) return;
@@ -290,7 +299,7 @@ namespace Discord
{ {
if (role == null) throw new ArgumentNullException(nameof(role)); if (role == null) throw new ArgumentNullException(nameof(role));
return _roleIds.Contains(role.Id); return _roles.ContainsKey(role.Id);
} }
} }
} }

View File

@@ -53,21 +53,21 @@ namespace Discord.Net.WebSockets
_connectedEvent = new ManualResetEventSlim(false); _connectedEvent = new ManualResetEventSlim(false);
_engine = new WebSocketSharpEngine(this, client.Config); _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 compressed = new MemoryStream(e.Data, 2, e.Data.Length - 2))
using (var decompressed = new MemoryStream()) using (var decompressed = new MemoryStream())
{ {
using (var zlib = new DeflateStream(compressed, CompressionMode.Decompress)) using (var zlib = new DeflateStream(compressed, CompressionMode.Decompress))
await zlib.CopyToAsync(decompressed); zlib.CopyTo(decompressed);
decompressed.Position = 0; decompressed.Position = 0;
using (var reader = new StreamReader(decompressed)) 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();
}; };
} }