@@ -167,12 +167,12 @@ namespace Discord | |||||
remove { _userPresenceUpdatedEvent.Remove(value); } | remove { _userPresenceUpdatedEvent.Remove(value); } | ||||
} | } | ||||
private readonly AsyncEvent<Func<IGuildUser, IPresence, IPresence, Task>> _userPresenceUpdatedEvent = new AsyncEvent<Func<IGuildUser, IPresence, IPresence, Task>>(); | private readonly AsyncEvent<Func<IGuildUser, IPresence, IPresence, Task>> _userPresenceUpdatedEvent = new AsyncEvent<Func<IGuildUser, IPresence, IPresence, Task>>(); | ||||
public event Func<IGuildUser, IVoiceState, IVoiceState, Task> UserVoiceStateUpdated | |||||
public event Func<IUser, IVoiceState, IVoiceState, Task> UserVoiceStateUpdated | |||||
{ | { | ||||
add { _userVoiceStateUpdatedEvent.Add(value); } | add { _userVoiceStateUpdatedEvent.Add(value); } | ||||
remove { _userVoiceStateUpdatedEvent.Remove(value); } | remove { _userVoiceStateUpdatedEvent.Remove(value); } | ||||
} | } | ||||
private readonly AsyncEvent<Func<IGuildUser, IVoiceState, IVoiceState, Task>> _userVoiceStateUpdatedEvent = new AsyncEvent<Func<IGuildUser, IVoiceState, IVoiceState, Task>>(); | |||||
private readonly AsyncEvent<Func<IUser, IVoiceState, IVoiceState, Task>> _userVoiceStateUpdatedEvent = new AsyncEvent<Func<IUser, IVoiceState, IVoiceState, Task>>(); | |||||
public event Func<ISelfUser, ISelfUser, Task> CurrentUserUpdated | public event Func<ISelfUser, ISelfUser, Task> CurrentUserUpdated | ||||
{ | { | ||||
add { _selfUpdatedEvent.Add(value); } | add { _selfUpdatedEvent.Add(value); } | ||||
@@ -1280,39 +1280,66 @@ namespace Discord | |||||
var data = (payload as JToken).ToObject<API.VoiceState>(_serializer); | var data = (payload as JToken).ToObject<API.VoiceState>(_serializer); | ||||
if (data.GuildId.HasValue) | if (data.GuildId.HasValue) | ||||
{ | { | ||||
var guild = DataStore.GetGuild(data.GuildId.Value); | |||||
if (guild != null) | |||||
ICachedUser user; | |||||
VoiceState before, after; | |||||
if (data.GuildId != null) | |||||
{ | { | ||||
if (!guild.IsSynced) | |||||
var guild = DataStore.GetGuild(data.GuildId.Value); | |||||
if (guild != null) | |||||
{ | { | ||||
await _gatewayLogger.DebugAsync("Ignored VOICE_STATE_UPDATE, guild is not synced yet.").ConfigureAwait(false); | |||||
return; | |||||
if (!guild.IsSynced) | |||||
{ | |||||
await _gatewayLogger.DebugAsync("Ignored VOICE_STATE_UPDATE, guild is not synced yet.").ConfigureAwait(false); | |||||
return; | |||||
} | |||||
if (data.ChannelId != null) | |||||
{ | |||||
before = guild.GetVoiceState(data.UserId)?.Clone() ?? new VoiceState(null, null, false, false, false); | |||||
after = guild.AddOrUpdateVoiceState(data, DataStore); | |||||
} | |||||
else | |||||
{ | |||||
before = guild.RemoveVoiceState(data.UserId) ?? new VoiceState(null, null, false, false, false); | |||||
after = new VoiceState(null, data); | |||||
} | |||||
user = guild.GetUser(data.UserId); | |||||
} | } | ||||
VoiceState before, after; | |||||
if (data.ChannelId != null) | |||||
else | |||||
{ | { | ||||
before = guild.GetVoiceState(data.UserId)?.Clone() ?? new VoiceState(null, null, false, false, false); | |||||
after = guild.AddOrUpdateVoiceState(data, DataStore); | |||||
await _gatewayLogger.WarningAsync("VOICE_STATE_UPDATE referenced an unknown guild.").ConfigureAwait(false); | |||||
return; | |||||
} | } | ||||
else | |||||
} | |||||
else | |||||
{ | |||||
var groupChannel = DataStore.GetChannel(data.ChannelId.Value) as CachedGroupChannel; | |||||
if (groupChannel != null) | |||||
{ | { | ||||
before = guild.RemoveVoiceState(data.UserId) ?? new VoiceState(null, null, false, false, false); | |||||
after = new VoiceState(null, data); | |||||
if (data.ChannelId != null) | |||||
{ | |||||
before = groupChannel.GetVoiceState(data.UserId)?.Clone() ?? new VoiceState(null, null, false, false, false); | |||||
after = groupChannel.AddOrUpdateVoiceState(data, DataStore); | |||||
} | |||||
else | |||||
{ | |||||
before = groupChannel.RemoveVoiceState(data.UserId) ?? new VoiceState(null, null, false, false, false); | |||||
after = new VoiceState(null, data); | |||||
} | |||||
user = groupChannel.GetUser(data.UserId); | |||||
} | } | ||||
var user = guild.GetUser(data.UserId); | |||||
if (user != null) | |||||
await _userVoiceStateUpdatedEvent.InvokeAsync(user, before, after).ConfigureAwait(false); | |||||
else | else | ||||
{ | { | ||||
await _gatewayLogger.WarningAsync("VOICE_STATE_UPDATE referenced an unknown user.").ConfigureAwait(false); | |||||
await _gatewayLogger.WarningAsync("VOICE_STATE_UPDATE referenced an unknown channel.").ConfigureAwait(false); | |||||
return; | return; | ||||
} | } | ||||
} | } | ||||
if (user != null) | |||||
await _userVoiceStateUpdatedEvent.InvokeAsync(user, before, after).ConfigureAwait(false); | |||||
else | else | ||||
{ | { | ||||
await _gatewayLogger.WarningAsync("VOICE_STATE_UPDATE referenced an unknown guild.").ConfigureAwait(false); | |||||
await _gatewayLogger.WarningAsync("VOICE_STATE_UPDATE referenced an unknown user.").ConfigureAwait(false); | |||||
return; | return; | ||||
} | } | ||||
} | } | ||||
@@ -30,10 +30,10 @@ namespace Discord | |||||
{ | { | ||||
Discord = discord; | Discord = discord; | ||||
_users = recipients; | _users = recipients; | ||||
Update(model, UpdateSource.Creation); | Update(model, UpdateSource.Creation); | ||||
} | } | ||||
public void Update(Model model, UpdateSource source) | |||||
public virtual void Update(Model model, UpdateSource source) | |||||
{ | { | ||||
if (source == UpdateSource.Rest && IsAttached) return; | if (source == UpdateSource.Rest && IsAttached) return; | ||||
@@ -41,7 +41,7 @@ namespace Discord | |||||
Name = model.Name.Value; | Name = model.Name.Value; | ||||
if (model.Icon.IsSpecified) | if (model.Icon.IsSpecified) | ||||
_iconId = model.Icon.Value; | _iconId = model.Icon.Value; | ||||
if (source != UpdateSource.Creation && model.Recipients.IsSpecified) | if (source != UpdateSource.Creation && model.Recipients.IsSpecified) | ||||
UpdateUsers(model.Recipients.Value, source); | UpdateUsers(model.Recipients.Value, source); | ||||
} | } | ||||
@@ -6,16 +6,18 @@ using System.Linq; | |||||
using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
using MessageModel = Discord.API.Message; | using MessageModel = Discord.API.Message; | ||||
using Model = Discord.API.Channel; | using Model = Discord.API.Channel; | ||||
using VoiceStateModel = Discord.API.VoiceState; | |||||
namespace Discord | namespace Discord | ||||
{ | { | ||||
internal class CachedGroupChannel : GroupChannel, IGroupChannel, ICachedChannel, ICachedMessageChannel, ICachedPrivateChannel | internal class CachedGroupChannel : GroupChannel, IGroupChannel, ICachedChannel, ICachedMessageChannel, ICachedPrivateChannel | ||||
{ | { | ||||
private readonly MessageManager _messages; | private readonly MessageManager _messages; | ||||
private ConcurrentDictionary<ulong, VoiceState> _voiceStates; | |||||
public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient; | public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient; | ||||
public IReadOnlyCollection<ICachedUser> Members | public IReadOnlyCollection<ICachedUser> Members | ||||
=> _users.Select(x => x.Value).Concat(ImmutableArray.Create(Discord.CurrentUser)).Cast<ICachedUser>().ToReadOnlyCollection(_users, 1); | |||||
=> _users.Select(x => x.Value).Concat(ImmutableArray.Create(Discord.CurrentUser)).Cast<ICachedUser>().ToReadOnlyCollection(() => _users.Count + 1); | |||||
public new IReadOnlyCollection<CachedPrivateUser> Recipients => _users.Cast<CachedPrivateUser>().ToReadOnlyCollection(_users); | public new IReadOnlyCollection<CachedPrivateUser> Recipients => _users.Cast<CachedPrivateUser>().ToReadOnlyCollection(_users); | ||||
public CachedGroupChannel(DiscordSocketClient discord, ConcurrentDictionary<ulong, IUser> recipients, Model model) | public CachedGroupChannel(DiscordSocketClient discord, ConcurrentDictionary<ulong, IUser> recipients, Model model) | ||||
@@ -25,6 +27,13 @@ namespace Discord | |||||
_messages = new MessageCache(Discord, this); | _messages = new MessageCache(Discord, this); | ||||
else | else | ||||
_messages = new MessageManager(Discord, this); | _messages = new MessageManager(Discord, this); | ||||
_voiceStates = new ConcurrentDictionary<ulong, VoiceState>(1, 5); | |||||
} | |||||
public override void Update(Model model, UpdateSource source) | |||||
{ | |||||
if (source == UpdateSource.Rest && IsAttached) return; | |||||
base.Update(model, source); | |||||
} | } | ||||
protected override void UpdateUsers(API.User[] models, UpdateSource source) | protected override void UpdateUsers(API.User[] models, UpdateSource source) | ||||
@@ -35,6 +44,38 @@ namespace Discord | |||||
_users = users; | _users = users; | ||||
} | } | ||||
public ICachedUser GetUser(ulong id) | |||||
{ | |||||
IUser user; | |||||
if (_users.TryGetValue(id, out user)) | |||||
return user as ICachedUser; | |||||
if (id == Discord.CurrentUser.Id) | |||||
return Discord.CurrentUser; | |||||
return null; | |||||
} | |||||
public VoiceState AddOrUpdateVoiceState(VoiceStateModel model, DataStore dataStore, ConcurrentDictionary<ulong, VoiceState> voiceStates = null) | |||||
{ | |||||
var voiceChannel = dataStore.GetChannel(model.ChannelId.Value) as CachedVoiceChannel; | |||||
var voiceState = new VoiceState(voiceChannel, model); | |||||
(voiceStates ?? _voiceStates)[model.UserId] = voiceState; | |||||
return voiceState; | |||||
} | |||||
public VoiceState? GetVoiceState(ulong id) | |||||
{ | |||||
VoiceState voiceState; | |||||
if (_voiceStates.TryGetValue(id, out voiceState)) | |||||
return voiceState; | |||||
return null; | |||||
} | |||||
public VoiceState? RemoveVoiceState(ulong id) | |||||
{ | |||||
VoiceState voiceState; | |||||
if (_voiceStates.TryRemove(id, out voiceState)) | |||||
return voiceState; | |||||
return null; | |||||
} | |||||
public override async Task<IMessage> GetMessageAsync(ulong id) | public override async Task<IMessage> GetMessageAsync(ulong id) | ||||
{ | { | ||||
return await _messages.DownloadAsync(id).ConfigureAwait(false); | return await _messages.DownloadAsync(id).ConfigureAwait(false); | ||||
@@ -10,8 +10,8 @@ namespace Discord.Extensions | |||||
{ | { | ||||
public static IReadOnlyCollection<TValue> ToReadOnlyCollection<TKey, TValue>(this IReadOnlyDictionary<TKey, TValue> source) | public static IReadOnlyCollection<TValue> ToReadOnlyCollection<TKey, TValue>(this IReadOnlyDictionary<TKey, TValue> source) | ||||
=> new ConcurrentDictionaryWrapper<TValue>(source.Select(x => x.Value), () => source.Count); | => new ConcurrentDictionaryWrapper<TValue>(source.Select(x => x.Value), () => source.Count); | ||||
public static IReadOnlyCollection<TValue> ToReadOnlyCollection<TValue, TSource>(this IEnumerable<TValue> query, IReadOnlyCollection<TSource> source, int countOffset = 0) | |||||
=> new ConcurrentDictionaryWrapper<TValue>(query, () => source.Count + countOffset); | |||||
public static IReadOnlyCollection<TValue> ToReadOnlyCollection<TValue, TSource>(this IEnumerable<TValue> query, IReadOnlyCollection<TSource> source) | |||||
=> new ConcurrentDictionaryWrapper<TValue>(query, () => source.Count); | |||||
public static IReadOnlyCollection<TValue> ToReadOnlyCollection<TValue>(this IEnumerable<TValue> query, Func<int> countFunc) | public static IReadOnlyCollection<TValue> ToReadOnlyCollection<TValue>(this IEnumerable<TValue> query, Func<int> countFunc) | ||||
=> new ConcurrentDictionaryWrapper<TValue>(query, countFunc); | => new ConcurrentDictionaryWrapper<TValue>(query, countFunc); | ||||
} | } | ||||