Browse Source

Fixed race condition, better handled serialization errors, and added singleton store

tags/docs-0.9
RogueException 9 years ago
parent
commit
91b3c17fb3
21 changed files with 187 additions and 113 deletions
  1. +3
    -3
      src/Discord.Net.Net45/Discord.Net.csproj
  2. +11
    -0
      src/Discord.Net/API/Converters/StringEnumConverter.cs
  3. +2
    -9
      src/Discord.Net/DiscordAPIClientConfig.cs
  4. +3
    -3
      src/Discord.Net/DiscordClient.Messages.cs
  5. +10
    -3
      src/Discord.Net/DiscordClient.Servers.cs
  6. +52
    -47
      src/Discord.Net/DiscordClient.cs
  7. +32
    -1
      src/Discord.Net/DiscordWSClient.cs
  8. +19
    -9
      src/Discord.Net/Helpers/AsyncCollection.cs
  9. +8
    -4
      src/Discord.Net/Helpers/CachedObject.cs
  10. +2
    -2
      src/Discord.Net/Helpers/Reference.cs
  11. +3
    -3
      src/Discord.Net/Models/Channel.cs
  12. +1
    -1
      src/Discord.Net/Models/GlobalUser.cs
  13. +1
    -1
      src/Discord.Net/Models/Invite.cs
  14. +16
    -14
      src/Discord.Net/Models/Message.cs
  15. +10
    -0
      src/Discord.Net/Models/Region.cs
  16. +2
    -2
      src/Discord.Net/Models/Role.cs
  17. +2
    -1
      src/Discord.Net/Models/Server.cs
  18. +3
    -3
      src/Discord.Net/Models/User.cs
  19. +1
    -1
      src/Discord.Net/Net/Rest/SharpRestEngine.cs
  20. +3
    -3
      src/Discord.Net/Net/WebSockets/DataWebSocket.cs
  21. +3
    -3
      src/Discord.Net/Net/WebSockets/VoiceWebSocket.cs

+ 3
- 3
src/Discord.Net.Net45/Discord.Net.csproj View File

@@ -94,9 +94,6 @@
<Compile Include="..\Discord.Net\API\Enums\PermissionTarget.cs"> <Compile Include="..\Discord.Net\API\Enums\PermissionTarget.cs">
<Link>API\Enums\PermissionTarget.cs</Link> <Link>API\Enums\PermissionTarget.cs</Link>
</Compile> </Compile>
<Compile Include="..\Discord.Net\API\Enums\Region.cs">
<Link>API\Enums\Region.cs</Link>
</Compile>
<Compile Include="..\Discord.Net\API\Enums\StringEnum.cs"> <Compile Include="..\Discord.Net\API\Enums\StringEnum.cs">
<Link>API\Enums\StringEnum.cs</Link> <Link>API\Enums\StringEnum.cs</Link>
</Compile> </Compile>
@@ -259,6 +256,9 @@
<Compile Include="..\Discord.Net\Models\Permissions.cs"> <Compile Include="..\Discord.Net\Models\Permissions.cs">
<Link>Models\Permissions.cs</Link> <Link>Models\Permissions.cs</Link>
</Compile> </Compile>
<Compile Include="..\Discord.Net\Models\Region.cs">
<Link>Models\Region.cs</Link>
</Compile>
<Compile Include="..\Discord.Net\Models\Role.cs"> <Compile Include="..\Discord.Net\Models\Role.cs">
<Link>Models\Role.cs</Link> <Link>Models\Role.cs</Link>
</Compile> </Compile>


+ 11
- 0
src/Discord.Net/API/Converters/StringEnumConverter.cs View File

@@ -0,0 +1,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Discord.API.Converters
{
public class StringEnumConverter
{
}
}

+ 2
- 9
src/Discord.Net/DiscordAPIClientConfig.cs View File

@@ -6,6 +6,8 @@ namespace Discord
{ {
public class DiscordAPIClientConfig public class DiscordAPIClientConfig
{ {
internal static readonly string UserAgent = $"Discord.Net/{DiscordClient.Version} (https://github.com/RogueException/Discord.Net)";

/// <summary> Specifies the minimum log level severity that will be sent to the LogMessage event. Warning: setting this to debug will really hurt performance but should help investigate any internal issues. </summary> /// <summary> Specifies the minimum log level severity that will be sent to the LogMessage event. Warning: setting this to debug will really hurt performance but should help investigate any internal issues. </summary>
public LogMessageSeverity LogLevel { get { return _logLevel; } set { SetValue(ref _logLevel, value); } } public LogMessageSeverity LogLevel { get { return _logLevel; } set { SetValue(ref _logLevel, value); } }
private LogMessageSeverity _logLevel = LogMessageSeverity.Info; private LogMessageSeverity _logLevel = LogMessageSeverity.Info;
@@ -21,15 +23,6 @@ namespace Discord
public NetworkCredential ProxyCredentials { get { return _proxyCredentials; } set { SetValue(ref _proxyCredentials, value); } } public NetworkCredential ProxyCredentials { get { return _proxyCredentials; } set { SetValue(ref _proxyCredentials, value); } }
private NetworkCredential _proxyCredentials = null; private NetworkCredential _proxyCredentials = null;


internal string UserAgent
{
get
{
string version = typeof(DiscordClientConfig).GetTypeInfo().Assembly.GetName().Version.ToString(2);
return $"Discord.Net/{version} (https://github.com/RogueException/Discord.Net)";
}
}

//Lock //Lock
protected bool _isLocked; protected bool _isLocked;
internal void Lock() { _isLocked = true; } internal void Lock() { _isLocked = true; }


+ 3
- 3
src/Discord.Net/DiscordClient.Messages.cs View File

@@ -261,7 +261,7 @@ namespace Discord
} }
/// <summary> Deserializes messages from JSON format and imports them into the message cache.</summary> /// <summary> Deserializes messages from JSON format and imports them into the message cache.</summary>
public IEnumerable<Message> ImportMessages(string json)
public IEnumerable<Message> ImportMessages(Channel channel, string json)
{ {
if (json == null) throw new ArgumentNullException(nameof(json)); if (json == null) throw new ArgumentNullException(nameof(json));
@@ -269,8 +269,8 @@ namespace Discord
.Select(x => .Select(x =>
{ {
var msg = new Message(this, var msg = new Message(this,
x["Id"].Value<long>(),
x["ChannelId"].Value<long>(),
x["Id"].Value<long>(),
channel.Id,
x["UserId"].Value<long>()); x["UserId"].Value<long>());


var reader = x.CreateReader(); var reader = x.CreateReader();


+ 10
- 3
src/Discord.Net/DiscordClient.Servers.cs View File

@@ -86,19 +86,19 @@ namespace Discord
if (region == null) throw new ArgumentNullException(nameof(region)); if (region == null) throw new ArgumentNullException(nameof(region));
CheckReady(); CheckReady();


var response = await _api.CreateServer(name, region.Value).ConfigureAwait(false);
var response = await _api.CreateServer(name, region.Id).ConfigureAwait(false);
var server = _servers.GetOrAdd(response.Id); var server = _servers.GetOrAdd(response.Id);
server.Update(response); server.Update(response);
return server; return server;
} }
/// <summary> Edits the provided server, changing only non-null attributes. </summary> /// <summary> Edits the provided server, changing only non-null attributes. </summary>
public async Task EditServer(Server server, string name = null, Region region = null, ImageType iconType = ImageType.Png, byte[] icon = null)
public async Task EditServer(Server server, string name = null, string region = null, ImageType iconType = ImageType.Png, byte[] icon = null)
{ {
if (server == null) throw new ArgumentNullException(nameof(server)); if (server == null) throw new ArgumentNullException(nameof(server));
CheckReady(); CheckReady();


var response = await _api.EditServer(server.Id, name: name ?? server.Name, region: region.Value, iconType: iconType, icon: icon).ConfigureAwait(false);
var response = await _api.EditServer(server.Id, name: name ?? server.Name, region: region, iconType: iconType, icon: icon).ConfigureAwait(false);
server.Update(response); server.Update(response);
} }
@@ -112,5 +112,12 @@ namespace Discord
catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { }
//return _servers.TryRemove(server.Id); //return _servers.TryRemove(server.Id);
} }

public async Task<IEnumerable<Region>> GetVoiceRegions()
{
CheckReady();

return (await _api.GetVoiceRegions()).Select(x => new Region { Id = x.Id, Name = x.Name, Hostname = x.Hostname, Port = x.Port });
}
} }
} }

+ 52
- 47
src/Discord.Net/DiscordClient.cs View File

@@ -5,6 +5,7 @@ using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;


namespace Discord namespace Discord
@@ -12,12 +13,14 @@ namespace Discord
/// <summary> Provides a connection to the DiscordApp service. </summary> /// <summary> Provides a connection to the DiscordApp service. </summary>
public sealed partial class DiscordClient : DiscordWSClient public sealed partial class DiscordClient : DiscordWSClient
{ {
public static readonly string Version = typeof(DiscordClientConfig).GetTypeInfo().Assembly.GetName().Version.ToString(3);

private readonly DiscordAPIClient _api; private readonly DiscordAPIClient _api;
private readonly Random _rand; private readonly Random _rand;
private readonly JsonSerializer _socketSerializer, _messageImporter;
private readonly JsonSerializer _messageImporter;
private readonly ConcurrentQueue<Message> _pendingMessages; private readonly ConcurrentQueue<Message> _pendingMessages;
private readonly ConcurrentDictionary<long, DiscordWSClient> _voiceClients; private readonly ConcurrentDictionary<long, DiscordWSClient> _voiceClients;
private readonly Dictionary<Type, IService> _services;
private readonly Dictionary<Type, object> _singletons;
private bool _sentInitialLog; private bool _sentInitialLog;
private uint _nextVoiceClientId; private uint _nextVoiceClientId;
private UserStatus _status; private UserStatus _status;
@@ -47,7 +50,7 @@ namespace Discord
_roles = new Roles(this, cacheLock); _roles = new Roles(this, cacheLock);
_servers = new Servers(this, cacheLock); _servers = new Servers(this, cacheLock);
_globalUsers = new GlobalUsers(this, cacheLock); _globalUsers = new GlobalUsers(this, cacheLock);
_services = new Dictionary<Type, IService>();
_singletons = new Dictionary<Type, object>();


_status = UserStatus.Online; _status = UserStatus.Online;


@@ -162,16 +165,9 @@ namespace Discord


if (Config.UseMessageQueue) if (Config.UseMessageQueue)
_pendingMessages = new ConcurrentQueue<Message>(); _pendingMessages = new ConcurrentQueue<Message>();

_socketSerializer = new JsonSerializer();
_socketSerializer.DateTimeZoneHandling = DateTimeZoneHandling.Utc;
#if TEST_RESPONSES
_serializer.CheckAdditionalContent = true;
_serializer.MissingMemberHandling = MissingMemberHandling.Error;
#endif

_messageImporter = new JsonSerializer(); _messageImporter = new JsonSerializer();
_messageImporter.ContractResolver = new MessageImporterResolver();
_messageImporter.ContractResolver = new Message.ImportResolver();
} }
internal override VoiceWebSocket CreateVoiceSocket() internal override VoiceWebSocket CreateVoiceSocket()
{ {
@@ -276,25 +272,34 @@ namespace Discord
_privateUser = null; _privateUser = null;
} }


public T AddSingleton<T>(T obj)
where T : class
{
_singletons.Add(typeof(T), obj);
return obj;
}
public T GetSingleton<T>(bool required = true)
where T : class
{
object singleton;
T singletonT = null;
if (_singletons.TryGetValue(typeof(T), out singleton))
singletonT = singleton as T;

if (singletonT == null && required)
throw new InvalidOperationException($"This operation requires {nameof(T)} to be added to {nameof(DiscordClient)}.");
return singletonT;
}
public T AddService<T>(T obj) public T AddService<T>(T obj)
where T : class, IService where T : class, IService
{ {
_services.Add(typeof(T), obj);
AddSingleton(obj);
obj.Install(this); obj.Install(this);
return obj; return obj;
} }
public T GetService<T>(bool required = true) public T GetService<T>(bool required = true)
where T : class, IService where T : class, IService
{
IService service;
T serviceT = null;
if (_services.TryGetValue(typeof(T), out service))
serviceT = service as T;

if (serviceT == null && required)
throw new InvalidOperationException($"This operation requires {nameof(T)} to be added to {nameof(DiscordClient)}.");
return serviceT;
}
=> GetSingleton<T>(required);


protected override IEnumerable<Task> GetTasks() protected override IEnumerable<Task> GetTasks()
{ {
@@ -314,7 +319,7 @@ namespace Discord
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 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>(_socketSerializer);
var data = e.Payload.ToObject<ReadyEvent>(_dataSocketSerializer);
_privateUser = _users.GetOrAdd(data.User.Id, null); _privateUser = _users.GetOrAdd(data.User.Id, null);
_privateUser.Update(data.User); _privateUser.Update(data.User);
_privateUser.Global.Update(data.User); _privateUser.Global.Update(data.User);
@@ -339,7 +344,7 @@ namespace Discord
//Servers //Servers
case "GUILD_CREATE": case "GUILD_CREATE":
{ {
var data = e.Payload.ToObject<GuildCreateEvent>(_socketSerializer);
var data = e.Payload.ToObject<GuildCreateEvent>(_dataSocketSerializer);
if (data.Unavailable != true) if (data.Unavailable != true)
{ {
var server = _servers.GetOrAdd(data.Id); var server = _servers.GetOrAdd(data.Id);
@@ -353,7 +358,7 @@ namespace Discord
break; break;
case "GUILD_UPDATE": case "GUILD_UPDATE":
{ {
var data = e.Payload.ToObject<GuildUpdateEvent>(_socketSerializer);
var data = e.Payload.ToObject<GuildUpdateEvent>(_dataSocketSerializer);
var server = _servers[data.Id]; var server = _servers[data.Id];
if (server != null) if (server != null)
{ {
@@ -364,7 +369,7 @@ namespace Discord
break; break;
case "GUILD_DELETE": case "GUILD_DELETE":
{ {
var data = e.Payload.ToObject<GuildDeleteEvent>(_socketSerializer);
var data = e.Payload.ToObject<GuildDeleteEvent>(_dataSocketSerializer);
var server = _servers.TryRemove(data.Id); var server = _servers.TryRemove(data.Id);
if (server != null) if (server != null)
{ {
@@ -379,7 +384,7 @@ namespace Discord
//Channels //Channels
case "CHANNEL_CREATE": case "CHANNEL_CREATE":
{ {
var data = e.Payload.ToObject<ChannelCreateEvent>(_socketSerializer);
var data = e.Payload.ToObject<ChannelCreateEvent>(_dataSocketSerializer);
Channel channel; Channel channel;
if (data.IsPrivate) if (data.IsPrivate)
{ {
@@ -395,7 +400,7 @@ namespace Discord
break; break;
case "CHANNEL_UPDATE": case "CHANNEL_UPDATE":
{ {
var data = e.Payload.ToObject<ChannelUpdateEvent>(_socketSerializer);
var data = e.Payload.ToObject<ChannelUpdateEvent>(_dataSocketSerializer);
var channel = _channels[data.Id]; var channel = _channels[data.Id];
if (channel != null) if (channel != null)
{ {
@@ -406,7 +411,7 @@ namespace Discord
break; break;
case "CHANNEL_DELETE": case "CHANNEL_DELETE":
{ {
var data = e.Payload.ToObject<ChannelDeleteEvent>(_socketSerializer);
var data = e.Payload.ToObject<ChannelDeleteEvent>(_dataSocketSerializer);
var channel = _channels.TryRemove(data.Id); var channel = _channels.TryRemove(data.Id);
if (channel != null) if (channel != null)
RaiseChannelDestroyed(channel); RaiseChannelDestroyed(channel);
@@ -416,7 +421,7 @@ namespace Discord
//Members //Members
case "GUILD_MEMBER_ADD": case "GUILD_MEMBER_ADD":
{ {
var data = e.Payload.ToObject<MemberAddEvent>(_socketSerializer);
var data = e.Payload.ToObject<MemberAddEvent>(_dataSocketSerializer);
var user = _users.GetOrAdd(data.User.Id, data.GuildId); var user = _users.GetOrAdd(data.User.Id, data.GuildId);
user.Update(data); user.Update(data);
if (Config.TrackActivity) if (Config.TrackActivity)
@@ -426,7 +431,7 @@ namespace Discord
break; break;
case "GUILD_MEMBER_UPDATE": case "GUILD_MEMBER_UPDATE":
{ {
var data = e.Payload.ToObject<MemberUpdateEvent>(_socketSerializer);
var data = e.Payload.ToObject<MemberUpdateEvent>(_dataSocketSerializer);
var user = _users[data.User.Id, data.GuildId]; var user = _users[data.User.Id, data.GuildId];
if (user != null) if (user != null)
{ {
@@ -437,7 +442,7 @@ namespace Discord
break; break;
case "GUILD_MEMBER_REMOVE": case "GUILD_MEMBER_REMOVE":
{ {
var data = e.Payload.ToObject<MemberRemoveEvent>(_socketSerializer);
var data = e.Payload.ToObject<MemberRemoveEvent>(_dataSocketSerializer);
var user = _users.TryRemove(data.UserId, data.GuildId); var user = _users.TryRemove(data.UserId, data.GuildId);
if (user != null) if (user != null)
RaiseUserLeft(user); RaiseUserLeft(user);
@@ -445,7 +450,7 @@ namespace Discord
break; break;
case "GUILD_MEMBERS_CHUNK": case "GUILD_MEMBERS_CHUNK":
{ {
var data = e.Payload.ToObject<MembersChunkEvent>(_socketSerializer);
var data = e.Payload.ToObject<MembersChunkEvent>(_dataSocketSerializer);
foreach (var memberData in data.Members) foreach (var memberData in data.Members)
{ {
var user = _users.GetOrAdd(memberData.User.Id, memberData.GuildId); var user = _users.GetOrAdd(memberData.User.Id, memberData.GuildId);
@@ -458,7 +463,7 @@ namespace Discord
//Roles //Roles
case "GUILD_ROLE_CREATE": case "GUILD_ROLE_CREATE":
{ {
var data = e.Payload.ToObject<RoleCreateEvent>(_socketSerializer);
var data = e.Payload.ToObject<RoleCreateEvent>(_dataSocketSerializer);
var role = _roles.GetOrAdd(data.Data.Id, data.GuildId); var role = _roles.GetOrAdd(data.Data.Id, data.GuildId);
role.Update(data.Data); role.Update(data.Data);
var server = _servers[data.GuildId]; var server = _servers[data.GuildId];
@@ -469,7 +474,7 @@ namespace Discord
break; break;
case "GUILD_ROLE_UPDATE": case "GUILD_ROLE_UPDATE":
{ {
var data = e.Payload.ToObject<RoleUpdateEvent>(_socketSerializer);
var data = e.Payload.ToObject<RoleUpdateEvent>(_dataSocketSerializer);
var role = _roles[data.Data.Id]; var role = _roles[data.Data.Id];
if (role != null) if (role != null)
{ {
@@ -480,7 +485,7 @@ namespace Discord
break; break;
case "GUILD_ROLE_DELETE": case "GUILD_ROLE_DELETE":
{ {
var data = e.Payload.ToObject<RoleDeleteEvent>(_socketSerializer);
var data = e.Payload.ToObject<RoleDeleteEvent>(_dataSocketSerializer);
var role = _roles.TryRemove(data.RoleId); var role = _roles.TryRemove(data.RoleId);
if (role != null) if (role != null)
{ {
@@ -495,7 +500,7 @@ namespace Discord
//Bans //Bans
case "GUILD_BAN_ADD": case "GUILD_BAN_ADD":
{ {
var data = e.Payload.ToObject<BanAddEvent>(_socketSerializer);
var data = e.Payload.ToObject<BanAddEvent>(_dataSocketSerializer);
var server = _servers[data.GuildId]; var server = _servers[data.GuildId];
if (server != null) if (server != null)
{ {
@@ -507,7 +512,7 @@ namespace Discord
break; break;
case "GUILD_BAN_REMOVE": case "GUILD_BAN_REMOVE":
{ {
var data = e.Payload.ToObject<BanRemoveEvent>(_socketSerializer);
var data = e.Payload.ToObject<BanRemoveEvent>(_dataSocketSerializer);
var server = _servers[data.GuildId]; var server = _servers[data.GuildId];
if (server != null) if (server != null)
{ {
@@ -521,7 +526,7 @@ namespace Discord
//Messages //Messages
case "MESSAGE_CREATE": case "MESSAGE_CREATE":
{ {
var data = e.Payload.ToObject<MessageCreateEvent>(_socketSerializer);
var data = e.Payload.ToObject<MessageCreateEvent>(_dataSocketSerializer);
Message msg = null; Message msg = null;


bool isAuthor = data.Author.Id == _userId; bool isAuthor = data.Author.Id == _userId;
@@ -548,7 +553,7 @@ namespace Discord
break; break;
case "MESSAGE_UPDATE": case "MESSAGE_UPDATE":
{ {
var data = e.Payload.ToObject<MessageUpdateEvent>(_socketSerializer);
var data = e.Payload.ToObject<MessageUpdateEvent>(_dataSocketSerializer);
var msg = _messages[data.Id]; var msg = _messages[data.Id];
if (msg != null) if (msg != null)
{ {
@@ -559,7 +564,7 @@ namespace Discord
break; break;
case "MESSAGE_DELETE": case "MESSAGE_DELETE":
{ {
var data = e.Payload.ToObject<MessageDeleteEvent>(_socketSerializer);
var data = e.Payload.ToObject<MessageDeleteEvent>(_dataSocketSerializer);
var msg = _messages.TryRemove(data.Id); var msg = _messages.TryRemove(data.Id);
if (msg != null) if (msg != null)
RaiseMessageDeleted(msg); RaiseMessageDeleted(msg);
@@ -567,7 +572,7 @@ namespace Discord
break; break;
case "MESSAGE_ACK": case "MESSAGE_ACK":
{ {
var data = e.Payload.ToObject<MessageAckEvent>(_socketSerializer);
var data = e.Payload.ToObject<MessageAckEvent>(_dataSocketSerializer);
var msg = GetMessage(data.MessageId); var msg = GetMessage(data.MessageId);
if (msg != null) if (msg != null)
RaiseMessageReadRemotely(msg); RaiseMessageReadRemotely(msg);
@@ -577,7 +582,7 @@ namespace Discord
//Statuses //Statuses
case "PRESENCE_UPDATE": case "PRESENCE_UPDATE":
{ {
var data = e.Payload.ToObject<PresenceUpdateEvent>(_socketSerializer);
var data = e.Payload.ToObject<PresenceUpdateEvent>(_dataSocketSerializer);
var user = _users.GetOrAdd(data.User.Id, data.GuildId); var user = _users.GetOrAdd(data.User.Id, data.GuildId);
if (user != null) if (user != null)
{ {
@@ -588,7 +593,7 @@ namespace Discord
break; break;
case "TYPING_START": case "TYPING_START":
{ {
var data = e.Payload.ToObject<TypingStartEvent>(_socketSerializer);
var data = e.Payload.ToObject<TypingStartEvent>(_dataSocketSerializer);
var channel = _channels[data.ChannelId]; var channel = _channels[data.ChannelId];
if (channel != null) if (channel != null)
{ {
@@ -614,7 +619,7 @@ namespace Discord
//Voice //Voice
case "VOICE_STATE_UPDATE": case "VOICE_STATE_UPDATE":
{ {
var data = e.Payload.ToObject<MemberVoiceStateUpdateEvent>(_socketSerializer);
var data = e.Payload.ToObject<MemberVoiceStateUpdateEvent>(_dataSocketSerializer);
var user = _users[data.UserId, data.GuildId]; var user = _users[data.UserId, data.GuildId];
if (user != null) if (user != null)
{ {
@@ -633,7 +638,7 @@ namespace Discord
//Settings //Settings
case "USER_UPDATE": case "USER_UPDATE":
{ {
var data = e.Payload.ToObject<UserUpdateEvent>(_socketSerializer);
var data = e.Payload.ToObject<UserUpdateEvent>(_dataSocketSerializer);
var user = _globalUsers[data.Id]; var user = _globalUsers[data.Id];
if (user != null) if (user != null)
{ {


+ 32
- 1
src/Discord.Net/DiscordWSClient.cs View File

@@ -1,5 +1,6 @@
using Discord.Net; using Discord.Net;
using Discord.Net.WebSockets; using Discord.Net.WebSockets;
using Newtonsoft.Json;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@@ -23,9 +24,9 @@ namespace Discord
protected readonly DiscordWSClientConfig _config; protected readonly DiscordWSClientConfig _config;
protected readonly ManualResetEvent _disconnectedEvent; protected readonly ManualResetEvent _disconnectedEvent;
protected readonly ManualResetEventSlim _connectedEvent; protected readonly ManualResetEventSlim _connectedEvent;
protected ExceptionDispatchInfo _disconnectReason;
internal readonly DataWebSocket _dataSocket; internal readonly DataWebSocket _dataSocket;
internal readonly VoiceWebSocket _voiceSocket; internal readonly VoiceWebSocket _voiceSocket;
protected ExceptionDispatchInfo _disconnectReason;
protected string _gateway, _token; protected string _gateway, _token;
protected long? _userId, _voiceServerId; protected long? _userId, _voiceServerId;
private Task _runTask; private Task _runTask;
@@ -44,6 +45,10 @@ namespace Discord
private CancellationTokenSource _cancelTokenSource; private CancellationTokenSource _cancelTokenSource;
protected CancellationToken _cancelToken; protected CancellationToken _cancelToken;


internal JsonSerializer DataSocketSerializer => _dataSocketSerializer;
internal JsonSerializer VoiceSocketSerializer => _voiceSocketSerializer;
protected readonly JsonSerializer _dataSocketSerializer, _voiceSocketSerializer;

/// <summary> Initializes a new instance of the DiscordClient class. </summary> /// <summary> Initializes a new instance of the DiscordClient class. </summary>
public DiscordWSClient(DiscordWSClientConfig config = null) public DiscordWSClient(DiscordWSClientConfig config = null)
{ {
@@ -55,6 +60,32 @@ namespace Discord
_disconnectedEvent = new ManualResetEvent(true); _disconnectedEvent = new ManualResetEvent(true);
_connectedEvent = new ManualResetEventSlim(false); _connectedEvent = new ManualResetEventSlim(false);


_dataSocketSerializer = new JsonSerializer();
_dataSocketSerializer.DateTimeZoneHandling = DateTimeZoneHandling.Utc;
#if TEST_RESPONSES
_dataSocketSerializer.CheckAdditionalContent = true;
_dataSocketSerializer.MissingMemberHandling = MissingMemberHandling.Error;
#else
_dataSocketSerializer.Error += (s, e) =>
{
e.ErrorContext.Handled = true;
RaiseOnLog(LogMessageSeverity.Error, LogMessageSource.DataWebSocket, "Serialization Failed", e.ErrorContext.Error);
};
#endif

_voiceSocketSerializer = new JsonSerializer();
_voiceSocketSerializer.DateTimeZoneHandling = DateTimeZoneHandling.Utc;
#if TEST_RESPONSES
_voiceSocketSerializer.CheckAdditionalContent = true;
_voiceSocketSerializer.MissingMemberHandling = MissingMemberHandling.Error;
#else
_voiceSocketSerializer.Error += (s, e) =>
{
e.ErrorContext.Handled = true;
RaiseOnLog(LogMessageSeverity.Error, LogMessageSource.VoiceWebSocket, "Serialization Failed", e.ErrorContext.Error);
};
#endif

_dataSocket = CreateDataSocket(); _dataSocket = CreateDataSocket();
if (_config.EnableVoice) if (_config.EnableVoice)
_voiceSocket = CreateVoiceSocket(); _voiceSocket = CreateVoiceSocket();


+ 19
- 9
src/Discord.Net/Helpers/AsyncCollection.cs View File

@@ -84,15 +84,21 @@ namespace Discord


lock (_writerLock) lock (_writerLock)
{ {
TValue newItem = createFunc();
result = _dictionary.GetOrAdd(key, newItem);
if (result == newItem)
if (!_dictionary.ContainsKey(key))
{ {
result.Cache();
RaiseItemCreated(result);
result = createFunc();
if (result.Cache())
{
_dictionary.TryAdd(key, result);
RaiseItemCreated(result);
}
else
result.Uncache();
return result;
} }
else
return _dictionary[key];
} }
return result;
} }
protected void Import(IEnumerable<KeyValuePair<TKey, TValue>> items) protected void Import(IEnumerable<KeyValuePair<TKey, TValue>> items)
{ {
@@ -101,9 +107,13 @@ namespace Discord
foreach (var pair in items) foreach (var pair in items)
{ {
var value = pair.Value; var value = pair.Value;
_dictionary.TryAdd(pair.Key, value);
value.Cache();
RaiseItemCreated(value);
if (value.Cache())
{
_dictionary.TryAdd(pair.Key, value);
RaiseItemCreated(value);
}
else
value.Uncache();
} }
} }
} }


+ 8
- 4
src/Discord.Net/Helpers/CachedObject.cs View File

@@ -31,10 +31,14 @@ namespace Discord
_client = client; _client = client;
} }


internal void Cache()
internal bool Cache()
{ {
LoadReferences();
_isCached = true;
if (LoadReferences())
{
_isCached = true;
return true;
}
return false;
} }
internal void Uncache() internal void Uncache()
{ {
@@ -44,7 +48,7 @@ namespace Discord
_isCached = false; _isCached = false;
} }
} }
internal abstract void LoadReferences();
internal abstract bool LoadReferences();
internal abstract void UnloadReferences(); internal abstract void UnloadReferences();
} }
} }

+ 2
- 2
src/Discord.Net/Helpers/Reference.cs View File

@@ -41,9 +41,9 @@ namespace Discord
} }
} }


public T Load()
public bool Load()
{ {
return Value; //Used for precaching
return Value != null; //Used for precaching
} }


public void Unload() public void Unload()


+ 3
- 3
src/Discord.Net/Models/Channel.cs View File

@@ -124,12 +124,12 @@ namespace Discord
if (client.Config.MessageCacheLength > 0) if (client.Config.MessageCacheLength > 0)
_messages = new ConcurrentDictionary<long, Message>(); _messages = new ConcurrentDictionary<long, Message>();
} }
internal override void LoadReferences()
internal override bool LoadReferences()
{ {
if (IsPrivate) if (IsPrivate)
_recipient.Load();
return _recipient.Load();
else else
_server.Load();
return _server.Load();
} }
internal override void UnloadReferences() internal override void UnloadReferences()
{ {


+ 1
- 1
src/Discord.Net/Models/GlobalUser.cs View File

@@ -44,7 +44,7 @@ namespace Discord
{ {
_users = new ConcurrentDictionary<long, User>(); _users = new ConcurrentDictionary<long, User>();
} }
internal override void LoadReferences() { }
internal override bool LoadReferences() { return true; }
internal override void UnloadReferences() internal override void UnloadReferences()
{ {
//Don't need to clean _users - they're considered owned by server //Don't need to clean _users - they're considered owned by server


+ 1
- 1
src/Discord.Net/Models/Invite.cs View File

@@ -82,7 +82,7 @@ namespace Discord
{ {
XkcdCode = xkcdPass; XkcdCode = xkcdPass;
} }
internal override void LoadReferences() { }
internal override bool LoadReferences() { return true; }
internal override void UnloadReferences() { } internal override void UnloadReferences() { }


internal void Update(InviteReference model) internal void Update(InviteReference model)


+ 16
- 14
src/Discord.Net/Models/Message.cs View File

@@ -15,24 +15,25 @@ namespace Discord
Queued, Queued,
Failed Failed
} }
internal class MessageImporterResolver : DefaultContractResolver

public sealed class Message : CachedObject<long>
{ {
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
internal class ImportResolver : DefaultContractResolver
{ {
var property = base.CreateProperty(member, memberSerialization);
if (member is PropertyInfo)
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{ {
if (!(member as PropertyInfo).CanWrite)
return null;
var property = base.CreateProperty(member, memberSerialization);
if (member is PropertyInfo)
{
if (member.Name == nameof(ChannelId) || !(member as PropertyInfo).CanWrite)
return null;


property.Writable = true; //Handles private setters
property.Writable = true; //Handles private setters
}
return property;
} }
return property;
} }
}


public sealed class Message : CachedObject<long>
{
public sealed class Attachment : File public sealed class Attachment : File
{ {
/// <summary> Unique identifier for this file. </summary> /// <summary> Unique identifier for this file. </summary>
@@ -173,6 +174,8 @@ namespace Discord
x => x =>
{ {
var channel = Channel; var channel = Channel;
if (channel == null) return null;

if (!channel.IsPrivate) if (!channel.IsPrivate)
return _client.Users[x, channel.Server.Id]; return _client.Users[x, channel.Server.Id];
else else
@@ -181,10 +184,9 @@ namespace Discord
Attachments = _initialAttachments; Attachments = _initialAttachments;
Embeds = _initialEmbeds; Embeds = _initialEmbeds;
} }
internal override void LoadReferences()
internal override bool LoadReferences()
{ {
_channel.Load();
_user.Load();
return _channel.Load() && _user.Load();
} }
internal override void UnloadReferences() internal override void UnloadReferences()
{ {


+ 10
- 0
src/Discord.Net/Models/Region.cs View File

@@ -0,0 +1,10 @@
namespace Discord
{
public sealed class Region
{
public string Hostname;
public int Port;
public string Id;
public string Name;
}
}

+ 2
- 2
src/Discord.Net/Models/Role.cs View File

@@ -47,9 +47,9 @@ namespace Discord
Color = new Color(0); Color = new Color(0);
Color.Lock(); Color.Lock();
} }
internal override void LoadReferences()
internal override bool LoadReferences()
{ {
_server.Load();
return _server.Load();
} }
internal override void UnloadReferences() internal override void UnloadReferences()
{ {


+ 2
- 1
src/Discord.Net/Models/Server.cs View File

@@ -103,9 +103,10 @@ namespace Discord
//Local Cache //Local Cache
_bans = new ConcurrentDictionary<long, bool>(); _bans = new ConcurrentDictionary<long, bool>();
} }
internal override void LoadReferences()
internal override bool LoadReferences()
{ {
_afkChannel.Load(); _afkChannel.Load();
return true;
} }
internal override void UnloadReferences() internal override void UnloadReferences()
{ {


+ 3
- 3
src/Discord.Net/Models/User.cs View File

@@ -148,10 +148,10 @@ namespace Discord
if (serverId == null) if (serverId == null)
UpdateRoles(null); UpdateRoles(null);
} }
internal override void LoadReferences()
internal override bool LoadReferences()
{ {
_globalUser.Load();
_server.Load();
return _globalUser.Load() &&
(IsPrivate || _server.Load());
} }
internal override void UnloadReferences() internal override void UnloadReferences()
{ {


+ 1
- 1
src/Discord.Net/Net/Rest/SharpRestEngine.cs View File

@@ -20,7 +20,7 @@ namespace Discord.Net.Rest
{ {
PreAuthenticate = false, PreAuthenticate = false,
ReadWriteTimeout = _config.APITimeout, ReadWriteTimeout = _config.APITimeout,
UserAgent = _config.UserAgent
UserAgent = DiscordAPIClientConfig.UserAgent
}; };
if (_config.ProxyUrl != null) if (_config.ProxyUrl != null)
_client.Proxy = new WebProxy(_config.ProxyUrl, true, new string[0], _config.ProxyCredentials); _client.Proxy = new WebProxy(_config.ProxyUrl, true, new string[0], _config.ProxyCredentials);


+ 3
- 3
src/Discord.Net/Net/WebSockets/DataWebSocket.cs View File

@@ -82,13 +82,13 @@ namespace Discord.Net.WebSockets
JToken token = msg.Payload as JToken; JToken token = msg.Payload as JToken;
if (msg.Type == "READY") if (msg.Type == "READY")
{ {
var payload = token.ToObject<ReadyEvent>();
var payload = token.ToObject<ReadyEvent>(_client.DataSocketSerializer);
_sessionId = payload.SessionId; _sessionId = payload.SessionId;
_heartbeatInterval = payload.HeartbeatInterval; _heartbeatInterval = payload.HeartbeatInterval;
} }
else if (msg.Type == "RESUMED") else if (msg.Type == "RESUMED")
{ {
var payload = token.ToObject<ResumedEvent>();
var payload = token.ToObject<ResumedEvent>(_client.DataSocketSerializer);
_heartbeatInterval = payload.HeartbeatInterval; _heartbeatInterval = payload.HeartbeatInterval;
} }
RaiseReceivedEvent(msg.Type, token); RaiseReceivedEvent(msg.Type, token);
@@ -98,7 +98,7 @@ namespace Discord.Net.WebSockets
break; break;
case 7: //Redirect case 7: //Redirect
{ {
var payload = (msg.Payload as JToken).ToObject<RedirectEvent>();
var payload = (msg.Payload as JToken).ToObject<RedirectEvent>(_client.DataSocketSerializer);
if (payload.Url != null) if (payload.Url != null)
{ {
Host = payload.Url; Host = payload.Url;


+ 3
- 3
src/Discord.Net/Net/WebSockets/VoiceWebSocket.cs View File

@@ -443,7 +443,7 @@ namespace Discord.Net.WebSockets
{ {
if (_state != (int)WebSocketState.Connected) if (_state != (int)WebSocketState.Connected)
{ {
var payload = (msg.Payload as JToken).ToObject<VoiceReadyEvent>();
var payload = (msg.Payload as JToken).ToObject<VoiceReadyEvent>(_client.VoiceSocketSerializer);
_heartbeatInterval = payload.HeartbeatInterval; _heartbeatInterval = payload.HeartbeatInterval;
_ssrc = payload.SSRC; _ssrc = payload.SSRC;
_endpoint = new IPEndPoint((await Dns.GetHostAddressesAsync(Host.Replace("wss://", "")).ConfigureAwait(false)).FirstOrDefault(), payload.Port); _endpoint = new IPEndPoint((await Dns.GetHostAddressesAsync(Host.Replace("wss://", "")).ConfigureAwait(false)).FirstOrDefault(), payload.Port);
@@ -486,7 +486,7 @@ namespace Discord.Net.WebSockets
break; break;
case 4: //SESSION_DESCRIPTION case 4: //SESSION_DESCRIPTION
{ {
var payload = (msg.Payload as JToken).ToObject<JoinServerEvent>();
var payload = (msg.Payload as JToken).ToObject<JoinServerEvent>(_client.VoiceSocketSerializer);
_secretKey = payload.SecretKey; _secretKey = payload.SecretKey;
SendIsTalking(true); SendIsTalking(true);
EndConnect(); EndConnect();
@@ -494,7 +494,7 @@ namespace Discord.Net.WebSockets
break; break;
case 5: case 5:
{ {
var payload = (msg.Payload as JToken).ToObject<IsTalkingEvent>();
var payload = (msg.Payload as JToken).ToObject<IsTalkingEvent>(_client.VoiceSocketSerializer);
RaiseIsSpeaking(payload.UserId, payload.IsSpeaking); RaiseIsSpeaking(payload.UserId, payload.IsSpeaking);
} }
break; break;


Loading…
Cancel
Save