Browse Source

Fixed several websocket cache issues

tags/1.0-rc
RogueException 9 years ago
parent
commit
e76f1c64c4
9 changed files with 189 additions and 137 deletions
  1. +135
    -85
      src/Discord.Net/DiscordSocketClient.cs
  2. +6
    -6
      src/Discord.Net/Entities/WebSocket/CachedDMChannel.cs
  3. +27
    -26
      src/Discord.Net/Entities/WebSocket/CachedGuild.cs
  4. +2
    -2
      src/Discord.Net/Entities/WebSocket/CachedGuildUser.cs
  5. +3
    -2
      src/Discord.Net/Entities/WebSocket/CachedPublicUser.cs
  6. +8
    -8
      src/Discord.Net/Entities/WebSocket/CachedTextChannel.cs
  7. +3
    -3
      src/Discord.Net/Entities/WebSocket/CachedVoiceChannel.cs
  8. +4
    -4
      src/Discord.Net/Entities/WebSocket/ICachedMessageChannel.cs
  9. +1
    -1
      src/Discord.Net/Utilities/MessageCache.cs

+ 135
- 85
src/Discord.Net/DiscordSocketClient.cs View File

@@ -209,30 +209,21 @@ namespace Discord
{ {
return Task.FromResult<IGuild>(DataStore.GetGuild(id)); return Task.FromResult<IGuild>(DataStore.GetGuild(id));
} }
internal CachedGuild AddCachedGuild(API.Gateway.ExtendedGuild model, DataStore dataStore = null)
internal CachedGuild AddGuild(API.Gateway.ExtendedGuild model, DataStore dataStore)
{ {
dataStore = dataStore ?? DataStore;

var guild = new CachedGuild(this, model, dataStore); var guild = new CachedGuild(this, model, dataStore);
if (model.Unavailable != true)
{
for (int i = 0; i < model.Channels.Length; i++)
AddCachedChannel(guild, model.Channels[i], dataStore);
}
dataStore.AddGuild(guild); dataStore.AddGuild(guild);
if (model.Large) if (model.Large)
_largeGuilds.Enqueue(model.Id); _largeGuilds.Enqueue(model.Id);
return guild; return guild;
} }
internal CachedGuild RemoveCachedGuild(ulong id, DataStore dataStore = null)
internal CachedGuild RemoveGuild(ulong id)
{ {
dataStore = dataStore ?? DataStore;

var guild = dataStore.RemoveGuild(id);
var guild = DataStore.RemoveGuild(id);
foreach (var channel in guild.Channels) foreach (var channel in guild.Channels)
guild.RemoveCachedChannel(channel.Id);
guild.RemoveChannel(channel.Id);
foreach (var user in guild.Members) foreach (var user in guild.Members)
guild.RemoveCachedUser(user.Id);
guild.RemoveUser(user.Id);
return guild; return guild;
} }


@@ -241,46 +232,19 @@ namespace Discord
{ {
return Task.FromResult<IChannel>(DataStore.GetChannel(id)); return Task.FromResult<IChannel>(DataStore.GetChannel(id));
} }
internal ICachedGuildChannel AddCachedChannel(CachedGuild guild, API.Channel model, DataStore dataStore = null)
internal CachedDMChannel AddDMChannel(API.Channel model, DataStore dataStore)
{ {
dataStore = dataStore ?? DataStore;

var channel = guild.AddCachedChannel(model);
dataStore.AddChannel(channel);
return channel;
}
internal CachedDMChannel AddCachedDMChannel(API.Channel model, DataStore dataStore = null)
{
dataStore = dataStore ?? DataStore;

var recipient = GetOrAddCachedUser(model.Recipient, dataStore);
var recipient = GetOrAddUser(model.Recipient, dataStore);
var channel = recipient.AddDMChannel(model); var channel = recipient.AddDMChannel(model);
dataStore.AddChannel(channel); dataStore.AddChannel(channel);
return channel; return channel;
} }
internal ICachedChannel RemoveCachedChannel(ulong id, DataStore dataStore = null)
{
dataStore = dataStore ?? DataStore;

//TODO: C#7 Typeswitch Candidate
var channel = DataStore.RemoveChannel(id);

var guildChannel = channel as ICachedGuildChannel;
if (guildChannel != null)
{
guildChannel.Guild.RemoveCachedChannel(guildChannel.Id);
return channel;
}

var dmChannel = channel as CachedDMChannel;
if (dmChannel != null)
{
var recipient = dmChannel.Recipient;
recipient.RemoveDMChannel(id);
return channel;
}

return null;
internal CachedDMChannel RemoveDMChannel(ulong id)
{
var dmChannel = DataStore.RemoveChannel(id) as CachedDMChannel;
var recipient = dmChannel.Recipient;
recipient.RemoveDMChannel(id);
return dmChannel;
} }


/// <inheritdoc /> /// <inheritdoc />
@@ -293,20 +257,15 @@ namespace Discord
{ {
return Task.FromResult<IUser>(DataStore.Users.Where(x => x.Discriminator == discriminator && x.Username == username).FirstOrDefault()); return Task.FromResult<IUser>(DataStore.Users.Where(x => x.Discriminator == discriminator && x.Username == username).FirstOrDefault());
} }
internal CachedPublicUser GetOrAddCachedUser(API.User model, DataStore dataStore = null)
internal CachedPublicUser GetOrAddUser(API.User model, DataStore dataStore)
{ {
dataStore = dataStore ?? DataStore;

var user = dataStore.GetOrAddUser(model.Id, _ => new CachedPublicUser(this, model)); var user = dataStore.GetOrAddUser(model.Id, _ => new CachedPublicUser(this, model));
user.AddRef(); user.AddRef();
return user; return user;
} }
internal CachedPublicUser RemoveCachedUser(ulong id, DataStore dataStore = null)
internal CachedPublicUser RemoveUser(ulong id)
{ {
dataStore = dataStore ?? DataStore;

var user = dataStore.RemoveUser(id);
return user;
return DataStore.RemoveUser(id);
} }


/// <summary> Downloads the members list for all large guilds. </summary> /// <summary> Downloads the members list for all large guilds. </summary>
@@ -395,14 +354,16 @@ namespace Discord
var data = (payload as JToken).ToObject<ReadyEvent>(_serializer); var data = (payload as JToken).ToObject<ReadyEvent>(_serializer);
var dataStore = _dataStoreProvider(ShardId, _totalShards, data.Guilds.Length, data.PrivateChannels.Length); var dataStore = _dataStoreProvider(ShardId, _totalShards, data.Guilds.Length, data.PrivateChannels.Length);


_currentUser = new CachedSelfUser(this, data.User);
var currentUser = new CachedSelfUser(this, data.User);
//dataStore.GetOrAddUser(data.User.Id, _ => currentUser);


for (int i = 0; i < data.Guilds.Length; i++) for (int i = 0; i < data.Guilds.Length; i++)
AddCachedGuild(data.Guilds[i], dataStore);
AddGuild(data.Guilds[i], dataStore);
for (int i = 0; i < data.PrivateChannels.Length; i++) for (int i = 0; i < data.PrivateChannels.Length; i++)
AddCachedDMChannel(data.PrivateChannels[i], dataStore);
AddDMChannel(data.PrivateChannels[i], dataStore);


_sessionId = data.SessionId; _sessionId = data.SessionId;
_currentUser = currentUser;
DataStore = dataStore; DataStore = dataStore;


await Ready.RaiseAsync().ConfigureAwait(false); await Ready.RaiseAsync().ConfigureAwait(false);
@@ -423,8 +384,7 @@ namespace Discord
CachedGuild guild; CachedGuild guild;
if (data.Unavailable != false) if (data.Unavailable != false)
{ {
guild = new CachedGuild(this, data, DataStore);
DataStore.AddGuild(guild);
guild = AddGuild(data, DataStore);
await JoinedGuild.RaiseAsync(guild).ConfigureAwait(false); await JoinedGuild.RaiseAsync(guild).ConfigureAwait(false);
} }
else else
@@ -433,7 +393,10 @@ namespace Discord
if (guild != null) if (guild != null)
guild.Update(data, UpdateSource.WebSocket); guild.Update(data, UpdateSource.WebSocket);
else else
{
await _gatewayLogger.WarningAsync($"{type} referenced an unknown guild.").ConfigureAwait(false); await _gatewayLogger.WarningAsync($"{type} referenced an unknown guild.").ConfigureAwait(false);
return;
}
} }


await GuildAvailable.RaiseAsync(guild).ConfigureAwait(false); await GuildAvailable.RaiseAsync(guild).ConfigureAwait(false);
@@ -452,7 +415,10 @@ namespace Discord
await GuildUpdated.RaiseAsync(before, guild).ConfigureAwait(false); await GuildUpdated.RaiseAsync(before, guild).ConfigureAwait(false);
} }
else else
{
await _gatewayLogger.WarningAsync("GUILD_UPDATE referenced an unknown guild."); await _gatewayLogger.WarningAsync("GUILD_UPDATE referenced an unknown guild.");
return;
}
} }
break; break;
case "GUILD_DELETE": case "GUILD_DELETE":
@@ -462,7 +428,7 @@ namespace Discord
type = "GUILD_UNAVAILABLE"; type = "GUILD_UNAVAILABLE";
await _gatewayLogger.DebugAsync($"Received Dispatch ({type})").ConfigureAwait(false); await _gatewayLogger.DebugAsync($"Received Dispatch ({type})").ConfigureAwait(false);


var guild = DataStore.RemoveGuild(data.Id);
var guild = RemoveGuild(data.Id);
if (guild != null) if (guild != null)
{ {
await GuildUnavailable.RaiseAsync(guild).ConfigureAwait(false); await GuildUnavailable.RaiseAsync(guild).ConfigureAwait(false);
@@ -472,7 +438,10 @@ namespace Discord
member.User.RemoveRef(); member.User.RemoveRef();
} }
else else
{
await _gatewayLogger.WarningAsync($"{type} referenced an unknown guild.").ConfigureAwait(false); await _gatewayLogger.WarningAsync($"{type} referenced an unknown guild.").ConfigureAwait(false);
return;
}
} }
break; break;


@@ -487,15 +456,15 @@ namespace Discord
{ {
var guild = DataStore.GetGuild(data.GuildId.Value); var guild = DataStore.GetGuild(data.GuildId.Value);
if (guild != null) if (guild != null)
{
channel = guild.AddCachedChannel(data);
DataStore.AddChannel(channel);
}
guild.AddChannel(data, DataStore);
else else
{
await _gatewayLogger.WarningAsync("CHANNEL_CREATE referenced an unknown guild.").ConfigureAwait(false); await _gatewayLogger.WarningAsync("CHANNEL_CREATE referenced an unknown guild.").ConfigureAwait(false);
return;
}
} }
else else
channel = AddCachedDMChannel(data);
channel = AddDMChannel(data, DataStore);
if (channel != null) if (channel != null)
await ChannelCreated.RaiseAsync(channel).ConfigureAwait(false); await ChannelCreated.RaiseAsync(channel).ConfigureAwait(false);
} }
@@ -513,19 +482,38 @@ namespace Discord
await ChannelUpdated.RaiseAsync(before, channel).ConfigureAwait(false); await ChannelUpdated.RaiseAsync(before, channel).ConfigureAwait(false);
} }
else else
{
await _gatewayLogger.WarningAsync("CHANNEL_UPDATE referenced an unknown channel.").ConfigureAwait(false); await _gatewayLogger.WarningAsync("CHANNEL_UPDATE referenced an unknown channel.").ConfigureAwait(false);
return;
}
} }
break; break;
case "CHANNEL_DELETE": case "CHANNEL_DELETE":
{ {
await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_DELETE)").ConfigureAwait(false); await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_DELETE)").ConfigureAwait(false);


ICachedChannel channel = null;
var data = (payload as JToken).ToObject<API.Channel>(_serializer); var data = (payload as JToken).ToObject<API.Channel>(_serializer);
var channel = RemoveCachedChannel(data.Id);
if (data.GuildId != null)
{
var guild = DataStore.GetGuild(data.GuildId.Value);
if (guild != null)
channel = guild.RemoveChannel(data.Id);
else
{
await _gatewayLogger.WarningAsync("CHANNEL_DELETE referenced an unknown guild.").ConfigureAwait(false);
return;
}
}
else
channel = RemoveDMChannel(data.Id);
if (channel != null) if (channel != null)
await ChannelDestroyed.RaiseAsync(channel).ConfigureAwait(false); await ChannelDestroyed.RaiseAsync(channel).ConfigureAwait(false);
else else
{
await _gatewayLogger.WarningAsync("CHANNEL_DELETE referenced an unknown channel.").ConfigureAwait(false); await _gatewayLogger.WarningAsync("CHANNEL_DELETE referenced an unknown channel.").ConfigureAwait(false);
return;
}
} }
break; break;


@@ -538,11 +526,14 @@ namespace Discord
var guild = DataStore.GetGuild(data.GuildId); var guild = DataStore.GetGuild(data.GuildId);
if (guild != null) if (guild != null)
{ {
var user = guild.AddCachedUser(data);
var user = guild.AddUser(data, DataStore);
await UserJoined.RaiseAsync(user).ConfigureAwait(false); await UserJoined.RaiseAsync(user).ConfigureAwait(false);
} }
else else
{
await _gatewayLogger.WarningAsync("GUILD_MEMBER_ADD referenced an unknown guild.").ConfigureAwait(false); await _gatewayLogger.WarningAsync("GUILD_MEMBER_ADD referenced an unknown guild.").ConfigureAwait(false);
return;
}
} }
break; break;
case "GUILD_MEMBER_UPDATE": case "GUILD_MEMBER_UPDATE":
@@ -553,7 +544,7 @@ namespace Discord
var guild = DataStore.GetGuild(data.GuildId); var guild = DataStore.GetGuild(data.GuildId);
if (guild != null) if (guild != null)
{ {
var user = guild.GetCachedUser(data.User.Id);
var user = guild.GetUser(data.User.Id);
if (user != null) if (user != null)
{ {
var before = _enablePreUpdateEvents ? user.Clone() : null; var before = _enablePreUpdateEvents ? user.Clone() : null;
@@ -561,10 +552,16 @@ namespace Discord
await UserUpdated.RaiseAsync(before, user).ConfigureAwait(false); await UserUpdated.RaiseAsync(before, user).ConfigureAwait(false);
} }
else else
{
await _gatewayLogger.WarningAsync("GUILD_MEMBER_UPDATE referenced an unknown user.").ConfigureAwait(false); await _gatewayLogger.WarningAsync("GUILD_MEMBER_UPDATE referenced an unknown user.").ConfigureAwait(false);
return;
}
} }
else else
{
await _gatewayLogger.WarningAsync("GUILD_MEMBER_UPDATE referenced an unknown guild.").ConfigureAwait(false); await _gatewayLogger.WarningAsync("GUILD_MEMBER_UPDATE referenced an unknown guild.").ConfigureAwait(false);
return;
}
} }
break; break;
case "GUILD_MEMBER_REMOVE": case "GUILD_MEMBER_REMOVE":
@@ -575,17 +572,23 @@ namespace Discord
var guild = DataStore.GetGuild(data.GuildId); var guild = DataStore.GetGuild(data.GuildId);
if (guild != null) if (guild != null)
{ {
var user = guild.RemoveCachedUser(data.User.Id);
var user = guild.RemoveUser(data.User.Id);
if (user != null) if (user != null)
{ {
user.User.RemoveRef(); user.User.RemoveRef();
await UserLeft.RaiseAsync(user).ConfigureAwait(false); await UserLeft.RaiseAsync(user).ConfigureAwait(false);
} }
else else
{
await _gatewayLogger.WarningAsync("GUILD_MEMBER_REMOVE referenced an unknown user.").ConfigureAwait(false); await _gatewayLogger.WarningAsync("GUILD_MEMBER_REMOVE referenced an unknown user.").ConfigureAwait(false);
return;
}
} }
else else
{
await _gatewayLogger.WarningAsync("GUILD_MEMBER_REMOVE referenced an unknown guild.").ConfigureAwait(false); await _gatewayLogger.WarningAsync("GUILD_MEMBER_REMOVE referenced an unknown guild.").ConfigureAwait(false);
return;
}
} }
break; break;
case "GUILD_MEMBERS_CHUNK": case "GUILD_MEMBERS_CHUNK":
@@ -597,7 +600,7 @@ namespace Discord
if (guild != null) if (guild != null)
{ {
foreach (var memberModel in data.Members) foreach (var memberModel in data.Members)
guild.AddCachedUser(memberModel);
guild.AddUser(memberModel, DataStore);


if (guild.DownloadedMemberCount >= guild.MemberCount) //Finished downloading for there if (guild.DownloadedMemberCount >= guild.MemberCount) //Finished downloading for there
{ {
@@ -606,7 +609,10 @@ namespace Discord
} }
} }
else else
{
await _gatewayLogger.WarningAsync("GUILD_MEMBERS_CHUNK referenced an unknown guild.").ConfigureAwait(false); await _gatewayLogger.WarningAsync("GUILD_MEMBERS_CHUNK referenced an unknown guild.").ConfigureAwait(false);
return;
}
} }
break; break;


@@ -619,11 +625,14 @@ namespace Discord
var guild = DataStore.GetGuild(data.GuildId); var guild = DataStore.GetGuild(data.GuildId);
if (guild != null) if (guild != null)
{ {
var role = guild.AddCachedRole(data.Role);
var role = guild.AddRole(data.Role);
await RoleCreated.RaiseAsync(role).ConfigureAwait(false); await RoleCreated.RaiseAsync(role).ConfigureAwait(false);
} }
else else
{
await _gatewayLogger.WarningAsync("GUILD_ROLE_CREATE referenced an unknown guild.").ConfigureAwait(false); await _gatewayLogger.WarningAsync("GUILD_ROLE_CREATE referenced an unknown guild.").ConfigureAwait(false);
return;
}
} }
break; break;
case "GUILD_ROLE_UPDATE": case "GUILD_ROLE_UPDATE":
@@ -642,10 +651,16 @@ namespace Discord
await RoleUpdated.RaiseAsync(before, role).ConfigureAwait(false); await RoleUpdated.RaiseAsync(before, role).ConfigureAwait(false);
} }
else else
{
await _gatewayLogger.WarningAsync("GUILD_ROLE_UPDATE referenced an unknown role.").ConfigureAwait(false); await _gatewayLogger.WarningAsync("GUILD_ROLE_UPDATE referenced an unknown role.").ConfigureAwait(false);
return;
}
} }
else else
{
await _gatewayLogger.WarningAsync("GUILD_ROLE_UPDATE referenced an unknown guild.").ConfigureAwait(false); await _gatewayLogger.WarningAsync("GUILD_ROLE_UPDATE referenced an unknown guild.").ConfigureAwait(false);
return;
}
} }
break; break;
case "GUILD_ROLE_DELETE": case "GUILD_ROLE_DELETE":
@@ -656,14 +671,20 @@ namespace Discord
var guild = DataStore.GetGuild(data.GuildId); var guild = DataStore.GetGuild(data.GuildId);
if (guild != null) if (guild != null)
{ {
var role = guild.RemoveCachedRole(data.RoleId);
var role = guild.RemoveRole(data.RoleId);
if (role != null) if (role != null)
await RoleDeleted.RaiseAsync(role).ConfigureAwait(false); await RoleDeleted.RaiseAsync(role).ConfigureAwait(false);
else else
{
await _gatewayLogger.WarningAsync("GUILD_ROLE_DELETE referenced an unknown role.").ConfigureAwait(false); await _gatewayLogger.WarningAsync("GUILD_ROLE_DELETE referenced an unknown role.").ConfigureAwait(false);
return;
}
} }
else else
{
await _gatewayLogger.WarningAsync("GUILD_ROLE_DELETE referenced an unknown guild.").ConfigureAwait(false); await _gatewayLogger.WarningAsync("GUILD_ROLE_DELETE referenced an unknown guild.").ConfigureAwait(false);
return;
}
} }
break; break;


@@ -677,7 +698,10 @@ namespace Discord
if (guild != null) if (guild != null)
await UserBanned.RaiseAsync(new User(this, data)).ConfigureAwait(false); await UserBanned.RaiseAsync(new User(this, data)).ConfigureAwait(false);
else else
{
await _gatewayLogger.WarningAsync("GUILD_BAN_ADD referenced an unknown guild.").ConfigureAwait(false); await _gatewayLogger.WarningAsync("GUILD_BAN_ADD referenced an unknown guild.").ConfigureAwait(false);
return;
}
} }
break; break;
case "GUILD_BAN_REMOVE": case "GUILD_BAN_REMOVE":
@@ -689,7 +713,10 @@ namespace Discord
if (guild != null) if (guild != null)
await UserUnbanned.RaiseAsync(new User(this, data)).ConfigureAwait(false); await UserUnbanned.RaiseAsync(new User(this, data)).ConfigureAwait(false);
else else
{
await _gatewayLogger.WarningAsync("GUILD_BAN_REMOVE referenced an unknown guild.").ConfigureAwait(false); await _gatewayLogger.WarningAsync("GUILD_BAN_REMOVE referenced an unknown guild.").ConfigureAwait(false);
return;
}
} }
break; break;


@@ -702,18 +729,24 @@ namespace Discord
var channel = DataStore.GetChannel(data.ChannelId) as ICachedMessageChannel; var channel = DataStore.GetChannel(data.ChannelId) as ICachedMessageChannel;
if (channel != null) if (channel != null)
{ {
var author = channel.GetCachedUser(data.Author.Id);
var author = channel.GetUser(data.Author.Id);


if (author != null) if (author != null)
{ {
var msg = channel.AddCachedMessage(author, data);
var msg = channel.AddMessage(author, data);
await MessageReceived.RaiseAsync(msg).ConfigureAwait(false); await MessageReceived.RaiseAsync(msg).ConfigureAwait(false);
} }
else else
{
await _gatewayLogger.WarningAsync("MESSAGE_CREATE referenced an unknown user.").ConfigureAwait(false); await _gatewayLogger.WarningAsync("MESSAGE_CREATE referenced an unknown user.").ConfigureAwait(false);
return;
}
} }
else else
{
await _gatewayLogger.WarningAsync("MESSAGE_CREATE referenced an unknown channel.").ConfigureAwait(false); await _gatewayLogger.WarningAsync("MESSAGE_CREATE referenced an unknown channel.").ConfigureAwait(false);
return;
}
} }
break; break;
case "MESSAGE_UPDATE": case "MESSAGE_UPDATE":
@@ -724,13 +757,16 @@ namespace Discord
var channel = DataStore.GetChannel(data.ChannelId) as ICachedMessageChannel; var channel = DataStore.GetChannel(data.ChannelId) as ICachedMessageChannel;
if (channel != null) if (channel != null)
{ {
var msg = channel.GetCachedMessage(data.Id);
var msg = channel.GetMessage(data.Id);
var before = _enablePreUpdateEvents ? msg.Clone() : null; var before = _enablePreUpdateEvents ? msg.Clone() : null;
msg.Update(data, UpdateSource.WebSocket); msg.Update(data, UpdateSource.WebSocket);
await MessageUpdated.RaiseAsync(before, msg).ConfigureAwait(false); await MessageUpdated.RaiseAsync(before, msg).ConfigureAwait(false);
} }
else else
{
await _gatewayLogger.WarningAsync("MESSAGE_UPDATE referenced an unknown channel.").ConfigureAwait(false); await _gatewayLogger.WarningAsync("MESSAGE_UPDATE referenced an unknown channel.").ConfigureAwait(false);
return;
}
} }
break; break;
case "MESSAGE_DELETE": case "MESSAGE_DELETE":
@@ -741,11 +777,14 @@ namespace Discord
var channel = DataStore.GetChannel(data.ChannelId) as ICachedMessageChannel; var channel = DataStore.GetChannel(data.ChannelId) as ICachedMessageChannel;
if (channel != null) if (channel != null)
{ {
var msg = channel.RemoveCachedMessage(data.Id);
var msg = channel.RemoveMessage(data.Id);
await MessageDeleted.RaiseAsync(msg).ConfigureAwait(false); await MessageDeleted.RaiseAsync(msg).ConfigureAwait(false);
} }
else else
{
await _gatewayLogger.WarningAsync("MESSAGE_DELETE referenced an unknown channel.").ConfigureAwait(false); await _gatewayLogger.WarningAsync("MESSAGE_DELETE referenced an unknown channel.").ConfigureAwait(false);
return;
}
} }
break; break;


@@ -770,9 +809,9 @@ namespace Discord
break; break;
} }
if (data.Status == UserStatus.Offline) if (data.Status == UserStatus.Offline)
guild.RemoveCachedPresence(data.User.Id);
guild.RemovePresence(data.User.Id);
else else
guild.AddOrUpdateCachedPresence(data);
guild.AddOrUpdatePresence(data);
} }
} }
break; break;
@@ -784,12 +823,15 @@ namespace Discord
var channel = DataStore.GetChannel(data.ChannelId) as ICachedMessageChannel; var channel = DataStore.GetChannel(data.ChannelId) as ICachedMessageChannel;
if (channel != null) if (channel != null)
{ {
var user = channel.GetCachedUser(data.UserId);
var user = channel.GetUser(data.UserId);
if (user != null) if (user != null)
await UserIsTyping.RaiseAsync(channel, user).ConfigureAwait(false); await UserIsTyping.RaiseAsync(channel, user).ConfigureAwait(false);
} }
else else
{
await _gatewayLogger.WarningAsync("TYPING_START referenced an unknown channel.").ConfigureAwait(false); await _gatewayLogger.WarningAsync("TYPING_START referenced an unknown channel.").ConfigureAwait(false);
return;
}
} }
break; break;


@@ -805,15 +847,18 @@ namespace Discord
if (guild != null) if (guild != null)
{ {
if (data.ChannelId == null) if (data.ChannelId == null)
guild.RemoveCachedVoiceState(data.UserId);
guild.RemoveVoiceState(data.UserId);
else else
guild.AddOrUpdateCachedVoiceState(data);
guild.AddOrUpdateVoiceState(data);


var user = guild.GetCachedUser(data.UserId);
var user = guild.GetUser(data.UserId);
user.Update(data, UpdateSource.WebSocket); user.Update(data, UpdateSource.WebSocket);
} }
else else
{
await _gatewayLogger.WarningAsync("VOICE_STATE_UPDATE referenced an unknown guild.").ConfigureAwait(false); await _gatewayLogger.WarningAsync("VOICE_STATE_UPDATE referenced an unknown guild.").ConfigureAwait(false);
return;
}
} }
} }
break; break;
@@ -830,6 +875,11 @@ namespace Discord
CurrentUser.Update(data, UpdateSource.WebSocket); CurrentUser.Update(data, UpdateSource.WebSocket);
await CurrentUserUpdated.RaiseAsync(before, CurrentUser).ConfigureAwait(false); await CurrentUserUpdated.RaiseAsync(before, CurrentUser).ConfigureAwait(false);
} }
else
{
await _gatewayLogger.WarningAsync("Received USER_UPDATE for wrong user.").ConfigureAwait(false);
return;
}
} }
break; break;




+ 6
- 6
src/Discord.Net/Entities/WebSocket/CachedDMChannel.cs View File

@@ -21,11 +21,11 @@ namespace Discord
_messages = new MessageCache(Discord, this); _messages = new MessageCache(Discord, this);
} }


public override Task<IUser> GetUserAsync(ulong id) => Task.FromResult<IUser>(GetCachedUser(id));
public override Task<IUser> GetUserAsync(ulong id) => Task.FromResult<IUser>(GetUser(id));
public override Task<IReadOnlyCollection<IUser>> GetUsersAsync() => Task.FromResult<IReadOnlyCollection<IUser>>(Members); public override Task<IReadOnlyCollection<IUser>> GetUsersAsync() => Task.FromResult<IReadOnlyCollection<IUser>>(Members);
public override Task<IReadOnlyCollection<IUser>> GetUsersAsync(int limit, int offset) public override Task<IReadOnlyCollection<IUser>> GetUsersAsync(int limit, int offset)
=> Task.FromResult<IReadOnlyCollection<IUser>>(Members.Skip(offset).Take(limit).ToImmutableArray()); => Task.FromResult<IReadOnlyCollection<IUser>>(Members.Skip(offset).Take(limit).ToImmutableArray());
public ICachedUser GetCachedUser(ulong id)
public ICachedUser GetUser(ulong id)
{ {
var currentUser = Discord.CurrentUser; var currentUser = Discord.CurrentUser;
if (id == Recipient.Id) if (id == Recipient.Id)
@@ -48,24 +48,24 @@ namespace Discord
{ {
return await _messages.DownloadAsync(fromMessageId, dir, limit).ConfigureAwait(false); return await _messages.DownloadAsync(fromMessageId, dir, limit).ConfigureAwait(false);
} }
public CachedMessage AddCachedMessage(ICachedUser author, MessageModel model)
public CachedMessage AddMessage(ICachedUser author, MessageModel model)
{ {
var msg = new CachedMessage(this, author, model); var msg = new CachedMessage(this, author, model);
_messages.Add(msg); _messages.Add(msg);
return msg; return msg;
} }
public CachedMessage GetCachedMessage(ulong id)
public CachedMessage GetMessage(ulong id)
{ {
return _messages.Get(id); return _messages.Get(id);
} }
public CachedMessage RemoveCachedMessage(ulong id)
public CachedMessage RemoveMessage(ulong id)
{ {
return _messages.Remove(id); return _messages.Remove(id);
} }


public CachedDMChannel Clone() => MemberwiseClone() as CachedDMChannel; public CachedDMChannel Clone() => MemberwiseClone() as CachedDMChannel;


IMessage IMessageChannel.GetCachedMessage(ulong id) => GetCachedMessage(id);
IMessage IMessageChannel.GetCachedMessage(ulong id) => GetMessage(id);
ICachedChannel ICachedChannel.Clone() => Clone(); ICachedChannel ICachedChannel.Clone() => Clone();
} }
} }

+ 27
- 26
src/Discord.Net/Entities/WebSocket/CachedGuild.cs View File

@@ -32,8 +32,8 @@ namespace Discord
public Task DownloaderPromise => _downloaderPromise.Task; public Task DownloaderPromise => _downloaderPromise.Task;


public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient; public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient;
public CachedGuildUser CurrentUser => GetCachedUser(Discord.CurrentUser.Id);
public IReadOnlyCollection<ICachedGuildChannel> Channels => _channels.Select(x => GetCachedChannel(x)).ToReadOnlyCollection(_channels);
public CachedGuildUser CurrentUser => GetUser(Discord.CurrentUser.Id);
public IReadOnlyCollection<ICachedGuildChannel> Channels => _channels.Select(x => GetChannel(x)).ToReadOnlyCollection(_channels);
public IReadOnlyCollection<CachedGuildUser> Members => _members.ToReadOnlyCollection(); public IReadOnlyCollection<CachedGuildUser> Members => _members.ToReadOnlyCollection();


public CachedGuild(DiscordSocketClient discord, ExtendedModel model, DataStore dataStore) : base(discord, model) public CachedGuild(DiscordSocketClient discord, ExtendedModel model, DataStore dataStore) : base(discord, model)
@@ -74,7 +74,7 @@ namespace Discord
if (model.Channels != null) if (model.Channels != null)
{ {
for (int i = 0; i < model.Channels.Length; i++) for (int i = 0; i < model.Channels.Length; i++)
AddCachedChannel(model.Channels[i], channels);
AddChannel(model.Channels[i], dataStore, channels);
} }
_channels = channels; _channels = channels;


@@ -82,7 +82,7 @@ namespace Discord
if (model.Presences != null) if (model.Presences != null)
{ {
for (int i = 0; i < model.Presences.Length; i++) for (int i = 0; i < model.Presences.Length; i++)
AddOrUpdateCachedPresence(model.Presences[i], presences);
AddOrUpdatePresence(model.Presences[i], presences);
} }
_presences = presences; _presences = presences;


@@ -90,7 +90,7 @@ namespace Discord
if (model.Members != null) if (model.Members != null)
{ {
for (int i = 0; i < model.Members.Length; i++) for (int i = 0; i < model.Members.Length; i++)
AddCachedUser(model.Members[i], members, dataStore);
AddUser(model.Members[i], dataStore, members);
_downloaderPromise = new TaskCompletionSource<bool>(); _downloaderPromise = new TaskCompletionSource<bool>();
DownloadedMemberCount = model.Members.Length; DownloadedMemberCount = model.Members.Length;
if (!model.Large) if (!model.Large)
@@ -102,43 +102,44 @@ namespace Discord
if (model.VoiceStates != null) if (model.VoiceStates != null)
{ {
for (int i = 0; i < model.VoiceStates.Length; i++) for (int i = 0; i < model.VoiceStates.Length; i++)
AddOrUpdateCachedVoiceState(model.VoiceStates[i], voiceStates);
AddOrUpdateVoiceState(model.VoiceStates[i], voiceStates);
} }
_voiceStates = voiceStates; _voiceStates = voiceStates;
} }


public override Task<IGuildChannel> GetChannelAsync(ulong id) => Task.FromResult<IGuildChannel>(GetCachedChannel(id));
public override Task<IGuildChannel> GetChannelAsync(ulong id) => Task.FromResult<IGuildChannel>(GetChannel(id));
public override Task<IReadOnlyCollection<IGuildChannel>> GetChannelsAsync() => Task.FromResult<IReadOnlyCollection<IGuildChannel>>(Channels); public override Task<IReadOnlyCollection<IGuildChannel>> GetChannelsAsync() => Task.FromResult<IReadOnlyCollection<IGuildChannel>>(Channels);
public ICachedGuildChannel AddCachedChannel(ChannelModel model, ConcurrentHashSet<ulong> channels = null)
public void AddChannel(ChannelModel model, DataStore dataStore, ConcurrentHashSet<ulong> channels = null)
{ {
var channel = ToChannel(model); var channel = ToChannel(model);
(channels ?? _channels).TryAdd(model.Id); (channels ?? _channels).TryAdd(model.Id);
return channel;
dataStore.AddChannel(channel);
} }
public ICachedGuildChannel GetCachedChannel(ulong id)
public ICachedGuildChannel GetChannel(ulong id)
{ {
return Discord.DataStore.GetChannel(id) as ICachedGuildChannel; return Discord.DataStore.GetChannel(id) as ICachedGuildChannel;
} }
public void RemoveCachedChannel(ulong id, ConcurrentHashSet<ulong> channels = null)
public ICachedGuildChannel RemoveChannel(ulong id)
{ {
(channels ?? _channels).TryRemove(id);
_channels.TryRemove(id);
return Discord.DataStore.RemoveChannel(id) as ICachedGuildChannel;
} }


public Presence AddOrUpdateCachedPresence(PresenceModel model, ConcurrentDictionary<ulong, Presence> presences = null)
public Presence AddOrUpdatePresence(PresenceModel model, ConcurrentDictionary<ulong, Presence> presences = null)
{ {
var game = model.Game != null ? new Game(model.Game) : (Game?)null; var game = model.Game != null ? new Game(model.Game) : (Game?)null;
var presence = new Presence(model.Status, game); var presence = new Presence(model.Status, game);
(presences ?? _presences)[model.User.Id] = presence; (presences ?? _presences)[model.User.Id] = presence;
return presence; return presence;
} }
public Presence? GetCachedPresence(ulong id)
public Presence? GetPresence(ulong id)
{ {
Presence presence; Presence presence;
if (_presences.TryGetValue(id, out presence)) if (_presences.TryGetValue(id, out presence))
return presence; return presence;
return null; return null;
} }
public Presence? RemoveCachedPresence(ulong id)
public Presence? RemovePresence(ulong id)
{ {
Presence presence; Presence presence;
if (_presences.TryRemove(id, out presence)) if (_presences.TryRemove(id, out presence))
@@ -146,13 +147,13 @@ namespace Discord
return null; return null;
} }


public Role AddCachedRole(RoleModel model, ConcurrentDictionary<ulong, Role> roles = null)
public Role AddRole(RoleModel model, ConcurrentDictionary<ulong, Role> roles = null)
{ {
var role = new Role(this, model); var role = new Role(this, model);
(roles ?? _roles)[model.Id] = role; (roles ?? _roles)[model.Id] = role;
return role; return role;
} }
public Role RemoveCachedRole(ulong id)
public Role RemoveRole(ulong id)
{ {
Role role; Role role;
if (_roles.TryRemove(id, out role)) if (_roles.TryRemove(id, out role))
@@ -160,21 +161,21 @@ namespace Discord
return null; return null;
} }


public VoiceState AddOrUpdateCachedVoiceState(VoiceStateModel model, ConcurrentDictionary<ulong, VoiceState> voiceStates = null)
public VoiceState AddOrUpdateVoiceState(VoiceStateModel model, ConcurrentDictionary<ulong, VoiceState> voiceStates = null)
{ {
var voiceChannel = GetCachedChannel(model.ChannelId.Value) as CachedVoiceChannel;
var voiceChannel = GetChannel(model.ChannelId.Value) as CachedVoiceChannel;
var voiceState = new VoiceState(voiceChannel, model.SessionId, model.SelfMute, model.SelfDeaf, model.Suppress); var voiceState = new VoiceState(voiceChannel, model.SessionId, model.SelfMute, model.SelfDeaf, model.Suppress);
(voiceStates ?? _voiceStates)[model.UserId] = voiceState; (voiceStates ?? _voiceStates)[model.UserId] = voiceState;
return voiceState; return voiceState;
} }
public VoiceState? GetCachedVoiceState(ulong id)
public VoiceState? GetVoiceState(ulong id)
{ {
VoiceState voiceState; VoiceState voiceState;
if (_voiceStates.TryGetValue(id, out voiceState)) if (_voiceStates.TryGetValue(id, out voiceState))
return voiceState; return voiceState;
return null; return null;
} }
public VoiceState? RemoveCachedVoiceState(ulong id)
public VoiceState? RemoveVoiceState(ulong id)
{ {
VoiceState voiceState; VoiceState voiceState;
if (_voiceStates.TryRemove(id, out voiceState)) if (_voiceStates.TryRemove(id, out voiceState))
@@ -182,7 +183,7 @@ namespace Discord
return null; return null;
} }


public override Task<IGuildUser> GetUserAsync(ulong id) => Task.FromResult<IGuildUser>(GetCachedUser(id));
public override Task<IGuildUser> GetUserAsync(ulong id) => Task.FromResult<IGuildUser>(GetUser(id));
public override Task<IGuildUser> GetCurrentUserAsync() public override Task<IGuildUser> GetCurrentUserAsync()
=> Task.FromResult<IGuildUser>(CurrentUser); => Task.FromResult<IGuildUser>(CurrentUser);
public override Task<IReadOnlyCollection<IGuildUser>> GetUsersAsync() public override Task<IReadOnlyCollection<IGuildUser>> GetUsersAsync()
@@ -190,23 +191,23 @@ namespace Discord
//TODO: Is there a better way of exposing pagination? //TODO: Is there a better way of exposing pagination?
public override Task<IReadOnlyCollection<IGuildUser>> GetUsersAsync(int limit, int offset) public override Task<IReadOnlyCollection<IGuildUser>> GetUsersAsync(int limit, int offset)
=> Task.FromResult<IReadOnlyCollection<IGuildUser>>(Members.OrderBy(x => x.Id).Skip(offset).Take(limit).ToImmutableArray()); => Task.FromResult<IReadOnlyCollection<IGuildUser>>(Members.OrderBy(x => x.Id).Skip(offset).Take(limit).ToImmutableArray());
public CachedGuildUser AddCachedUser(MemberModel model, ConcurrentDictionary<ulong, CachedGuildUser> members = null, DataStore dataStore = null)
public CachedGuildUser AddUser(MemberModel model, DataStore dataStore, ConcurrentDictionary<ulong, CachedGuildUser> members = null)
{ {
var user = Discord.GetOrAddCachedUser(model.User);
var user = Discord.GetOrAddUser(model.User, dataStore);
var member = new CachedGuildUser(this, user, model); var member = new CachedGuildUser(this, user, model);
(members ?? _members)[user.Id] = member; (members ?? _members)[user.Id] = member;
user.AddRef(); user.AddRef();
DownloadedMemberCount++; DownloadedMemberCount++;
return member; return member;
} }
public CachedGuildUser GetCachedUser(ulong id)
public CachedGuildUser GetUser(ulong id)
{ {
CachedGuildUser member; CachedGuildUser member;
if (_members.TryGetValue(id, out member)) if (_members.TryGetValue(id, out member))
return member; return member;
return null; return null;
} }
public CachedGuildUser RemoveCachedUser(ulong id)
public CachedGuildUser RemoveUser(ulong id)
{ {
CachedGuildUser member; CachedGuildUser member;
if (_members.TryRemove(id, out member)) if (_members.TryRemove(id, out member))


+ 2
- 2
src/Discord.Net/Entities/WebSocket/CachedGuildUser.cs View File

@@ -8,11 +8,11 @@ namespace Discord
public new CachedGuild Guild => base.Guild as CachedGuild; public new CachedGuild Guild => base.Guild as CachedGuild;
public new CachedPublicUser User => base.User as CachedPublicUser; public new CachedPublicUser User => base.User as CachedPublicUser;


public Presence? Presence => Guild.GetCachedPresence(Id);
public Presence? Presence => Guild.GetPresence(Id);
public override Game? Game => Presence?.Game; public override Game? Game => Presence?.Game;
public override UserStatus Status => Presence?.Status ?? UserStatus.Offline; public override UserStatus Status => Presence?.Status ?? UserStatus.Offline;


public VoiceState? VoiceState => Guild.GetCachedVoiceState(Id);
public VoiceState? VoiceState => Guild.GetVoiceState(Id);
public bool IsSelfDeafened => VoiceState?.IsSelfDeafened ?? false; public bool IsSelfDeafened => VoiceState?.IsSelfDeafened ?? false;
public bool IsSelfMuted => VoiceState?.IsSelfMuted ?? false; public bool IsSelfMuted => VoiceState?.IsSelfMuted ?? false;
public bool IsSuppressed => VoiceState?.IsSuppressed ?? false; public bool IsSuppressed => VoiceState?.IsSuppressed ?? false;


+ 3
- 2
src/Discord.Net/Entities/WebSocket/CachedPublicUser.cs View File

@@ -1,4 +1,5 @@
using ChannelModel = Discord.API.Channel;
using Discord.Data;
using ChannelModel = Discord.API.Channel;
using Model = Discord.API.User; using Model = Discord.API.User;
using PresenceModel = Discord.API.Presence; using PresenceModel = Discord.API.Presence;


@@ -64,7 +65,7 @@ namespace Discord
lock (this) lock (this)
{ {
if (--_references == 0 && DMChannel == null) if (--_references == 0 && DMChannel == null)
Discord.RemoveCachedUser(Id);
Discord.RemoveUser(Id);
} }
} }




+ 8
- 8
src/Discord.Net/Entities/WebSocket/CachedTextChannel.cs View File

@@ -23,13 +23,13 @@ namespace Discord
_messages = new MessageCache(Discord, this); _messages = new MessageCache(Discord, this);
} }


public override Task<IGuildUser> GetUserAsync(ulong id) => Task.FromResult<IGuildUser>(GetCachedUser(id));
public override Task<IGuildUser> GetUserAsync(ulong id) => Task.FromResult<IGuildUser>(GetUser(id));
public override Task<IReadOnlyCollection<IGuildUser>> GetUsersAsync() => Task.FromResult<IReadOnlyCollection<IGuildUser>>(Members); public override Task<IReadOnlyCollection<IGuildUser>> GetUsersAsync() => Task.FromResult<IReadOnlyCollection<IGuildUser>>(Members);
public override Task<IReadOnlyCollection<IGuildUser>> GetUsersAsync(int limit, int offset) public override Task<IReadOnlyCollection<IGuildUser>> GetUsersAsync(int limit, int offset)
=> Task.FromResult<IReadOnlyCollection<IGuildUser>>(Members.Skip(offset).Take(limit).ToImmutableArray()); => Task.FromResult<IReadOnlyCollection<IGuildUser>>(Members.Skip(offset).Take(limit).ToImmutableArray());
public CachedGuildUser GetCachedUser(ulong id)
public CachedGuildUser GetUser(ulong id)
{ {
var user = Guild.GetCachedUser(id);
var user = Guild.GetUser(id);
if (user != null && Permissions.GetValue(Permissions.ResolveChannel(user, this, user.GuildPermissions.RawValue), ChannelPermission.ReadMessages)) if (user != null && Permissions.GetValue(Permissions.ResolveChannel(user, this, user.GuildPermissions.RawValue), ChannelPermission.ReadMessages))
return user; return user;
return null; return null;
@@ -48,17 +48,17 @@ namespace Discord
return await _messages.DownloadAsync(fromMessageId, dir, limit).ConfigureAwait(false); return await _messages.DownloadAsync(fromMessageId, dir, limit).ConfigureAwait(false);
} }


public CachedMessage AddCachedMessage(ICachedUser author, MessageModel model)
public CachedMessage AddMessage(ICachedUser author, MessageModel model)
{ {
var msg = new CachedMessage(this, author, model); var msg = new CachedMessage(this, author, model);
_messages.Add(msg); _messages.Add(msg);
return msg; return msg;
} }
public CachedMessage GetCachedMessage(ulong id)
public CachedMessage GetMessage(ulong id)
{ {
return _messages.Get(id); return _messages.Get(id);
} }
public CachedMessage RemoveCachedMessage(ulong id)
public CachedMessage RemoveMessage(ulong id)
{ {
return _messages.Remove(id); return _messages.Remove(id);
} }
@@ -67,8 +67,8 @@ namespace Discord


IReadOnlyCollection<ICachedUser> ICachedMessageChannel.Members => Members; IReadOnlyCollection<ICachedUser> ICachedMessageChannel.Members => Members;


IMessage IMessageChannel.GetCachedMessage(ulong id) => GetCachedMessage(id);
ICachedUser ICachedMessageChannel.GetCachedUser(ulong id) => GetCachedUser(id);
IMessage IMessageChannel.GetCachedMessage(ulong id) => GetMessage(id);
ICachedUser ICachedMessageChannel.GetUser(ulong id) => GetUser(id);
ICachedChannel ICachedChannel.Clone() => Clone(); ICachedChannel ICachedChannel.Clone() => Clone();
} }
} }

+ 3
- 3
src/Discord.Net/Entities/WebSocket/CachedVoiceChannel.cs View File

@@ -20,14 +20,14 @@ namespace Discord
} }


public override Task<IGuildUser> GetUserAsync(ulong id) public override Task<IGuildUser> GetUserAsync(ulong id)
=> Task.FromResult(GetCachedUser(id));
=> Task.FromResult(GetUser(id));
public override Task<IReadOnlyCollection<IGuildUser>> GetUsersAsync() public override Task<IReadOnlyCollection<IGuildUser>> GetUsersAsync()
=> Task.FromResult(Members); => Task.FromResult(Members);
public override Task<IReadOnlyCollection<IGuildUser>> GetUsersAsync(int limit, int offset) public override Task<IReadOnlyCollection<IGuildUser>> GetUsersAsync(int limit, int offset)
=> Task.FromResult<IReadOnlyCollection<IGuildUser>>(Members.OrderBy(x => x.Id).Skip(offset).Take(limit).ToImmutableArray()); => Task.FromResult<IReadOnlyCollection<IGuildUser>>(Members.OrderBy(x => x.Id).Skip(offset).Take(limit).ToImmutableArray());
public IGuildUser GetCachedUser(ulong id)
public IGuildUser GetUser(ulong id)
{ {
var user = Guild.GetCachedUser(id);
var user = Guild.GetUser(id);
if (user != null && user.VoiceChannel.Id == Id) if (user != null && user.VoiceChannel.Id == Id)
return user; return user;
return null; return null;


+ 4
- 4
src/Discord.Net/Entities/WebSocket/ICachedMessageChannel.cs View File

@@ -7,10 +7,10 @@ namespace Discord
{ {
IReadOnlyCollection<ICachedUser> Members { get; } IReadOnlyCollection<ICachedUser> Members { get; }


CachedMessage AddCachedMessage(ICachedUser author, MessageModel model);
new CachedMessage GetCachedMessage(ulong id);
CachedMessage RemoveCachedMessage(ulong id);
CachedMessage AddMessage(ICachedUser author, MessageModel model);
CachedMessage GetMessage(ulong id);
CachedMessage RemoveMessage(ulong id);


ICachedUser GetCachedUser(ulong id);
ICachedUser GetUser(ulong id);
} }
} }

+ 1
- 1
src/Discord.Net/Utilities/MessageCache.cs View File

@@ -111,7 +111,7 @@ namespace Discord
RelativeMessageId = dir == Direction.Before ? cachedMessages[0].Id : cachedMessages[cachedMessages.Count - 1].Id RelativeMessageId = dir == Direction.Before ? cachedMessages[0].Id : cachedMessages[cachedMessages.Count - 1].Id
}; };
var downloadedMessages = await _discord.ApiClient.GetChannelMessagesAsync(_channel.Id, args).ConfigureAwait(false); var downloadedMessages = await _discord.ApiClient.GetChannelMessagesAsync(_channel.Id, args).ConfigureAwait(false);
return cachedMessages.Concat(downloadedMessages.Select(x => new CachedMessage(_channel, _channel.GetCachedUser(x.Id), x))).ToImmutableArray();
return cachedMessages.Concat(downloadedMessages.Select(x => new CachedMessage(_channel, _channel.GetUser(x.Id), x))).ToImmutableArray();
} }
} }
} }


Loading…
Cancel
Save