@@ -223,6 +223,9 @@ | |||
<Compile Include="..\Discord.Net\Helpers\Mention.cs"> | |||
<Link>Helpers\Mention.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\Helpers\Reference.cs"> | |||
<Link>Helpers\Reference.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\Helpers\TaskHelper.cs"> | |||
<Link>Helpers\TaskHelper.cs</Link> | |||
</Compile> | |||
@@ -0,0 +1,68 @@ | |||
using System; | |||
namespace Discord | |||
{ | |||
internal class Reference<T> | |||
where T : CachedObject | |||
{ | |||
private Action<T> _onCache, _onUncache; | |||
private Func<string, T> _getItem; | |||
private string _id; | |||
public string Id | |||
{ | |||
get { return _id; } | |||
set | |||
{ | |||
_id = value; | |||
_value = null; | |||
} | |||
} | |||
private T _value; | |||
public T Value | |||
{ | |||
get | |||
{ | |||
var v = _value; //A little trickery to make this threadsafe | |||
if (v != null && !_value.IsCached) | |||
{ | |||
v = null; | |||
_value = null; | |||
} | |||
if (v == null && _id != null) | |||
{ | |||
v = _getItem(_id); | |||
if (v != null) | |||
_onCache(v); | |||
_value = v; | |||
} | |||
return v; | |||
} | |||
} | |||
public T Load() | |||
{ | |||
return Value; //Used for precaching | |||
} | |||
public void Unload() | |||
{ | |||
if (_onUncache != null) | |||
{ | |||
var v = _value; | |||
if (v != null && _onUncache != null) | |||
_onUncache(v); | |||
} | |||
} | |||
public Reference(Func<string, T> onUpdate, Action<T> onCache = null, Action<T> onUncache = null) | |||
: this(null, onUpdate, onCache, onUncache) { } | |||
public Reference(string id, Func<string, T> getItem, Action<T> onCache = null, Action<T> onUncache = null) | |||
{ | |||
_id = id; | |||
_getItem = getItem; | |||
_onCache = onCache; | |||
_onUncache = onUncache; | |||
} | |||
} | |||
} |
@@ -5,6 +5,8 @@ | |||
protected readonly DiscordClient _client; | |||
private bool _isCached; | |||
internal bool IsCached => _isCached; | |||
internal CachedObject(DiscordClient client, string id) | |||
{ | |||
_client = client; | |||
@@ -18,18 +20,18 @@ | |||
internal void Cache() | |||
{ | |||
OnCached(); | |||
LoadReferences(); | |||
_isCached = true; | |||
} | |||
internal void Uncache() | |||
{ | |||
if (_isCached) | |||
{ | |||
OnUncached(); | |||
UnloadReferences(); | |||
_isCached = false; | |||
} | |||
} | |||
internal abstract void OnCached(); | |||
internal abstract void OnUncached(); | |||
internal abstract void LoadReferences(); | |||
internal abstract void UnloadReferences(); | |||
} | |||
} |
@@ -33,19 +33,19 @@ namespace Discord | |||
/// <summary> Returns the position of this channel in the channel list for this server. </summary> | |||
public int Position { get; private set; } | |||
/// <summary> Returns false is this is a public chat and true if this is a private chat with another user (see Recipient). </summary> | |||
public bool IsPrivate => _recipientId != null; | |||
public bool IsPrivate => _recipient.Id != null; | |||
/// <summary> Returns the type of this channel (see ChannelTypes). </summary> | |||
public string Type { get; private set; } | |||
/// <summary> Returns the server containing this channel. </summary> | |||
[JsonIgnore] | |||
public Server Server { get; private set; } | |||
private readonly string _serverId; | |||
public Server Server => _server.Value; | |||
private readonly Reference<Server> _server; | |||
/// For private chats, returns the target user, otherwise null. | |||
[JsonIgnore] | |||
public User Recipient { get; private set; } | |||
private readonly string _recipientId; | |||
public User Recipient => _recipient.Value; | |||
private readonly Reference<User> _recipient; | |||
/// <summary> Returns a collection of all users with read access to this channel. </summary> | |||
[JsonIgnore] | |||
@@ -74,39 +74,35 @@ namespace Discord | |||
internal Channel(DiscordClient client, string id, string serverId, string recipientId) | |||
: base(client, id) | |||
{ | |||
_serverId = serverId; | |||
_recipientId = recipientId; | |||
_server = new Reference<Server>(serverId, | |||
x => _client.Servers[x], | |||
x => x.AddChannel(this), | |||
x => x.RemoveChannel(this)); | |||
_recipient = new Reference<User>(recipientId, | |||
x => _client.Users[x, _server.Id], | |||
x => | |||
{ | |||
Name = "@" + x.Name; | |||
x.GlobalUser.PrivateChannel = this; | |||
}, | |||
x => x.GlobalUser.PrivateChannel = null); | |||
_permissionOverwrites = _initialPermissionsOverwrites; | |||
_areMembersStale = true; | |||
//Local Cache | |||
_messages = new ConcurrentDictionary<string, Message>(); | |||
} | |||
internal override void OnCached() | |||
internal override void LoadReferences() | |||
{ | |||
if (IsPrivate) | |||
{ | |||
var recipient = _client.Users[_recipientId, null]; | |||
Name = "@" + recipient.Name; | |||
recipient.GlobalUser.PrivateChannel = this; | |||
Recipient = recipient; | |||
} | |||
_recipient.Load(); | |||
else | |||
{ | |||
var server = _client.Servers[_serverId]; | |||
server.AddChannel(this); | |||
Server = server; | |||
} | |||
_server.Load(); | |||
} | |||
internal override void OnUncached() | |||
internal override void UnloadReferences() | |||
{ | |||
var server = Server; | |||
if (server != null) | |||
server.RemoveChannel(this); | |||
var recipient = Recipient; | |||
if (recipient != null) | |||
recipient.GlobalUser.PrivateChannel = null; | |||
_server.Unload(); | |||
_recipient.Unload(); | |||
var globalMessages = _client.Messages; | |||
var messages = _messages; | |||
@@ -167,7 +163,10 @@ namespace Discord | |||
} | |||
private void UpdateMembersCache() | |||
{ | |||
_members = Server.Members.Where(x => x.GetPermissions(this)?.ReadMessages ?? false).ToDictionary(x => x.Id, x => x); | |||
if (_server.Id != null) | |||
_members = Server.Members.Where(x => x.GetPermissions(this)?.ReadMessages ?? false).ToDictionary(x => x.Id, x => x); | |||
else | |||
_members = new Dictionary<string, User>(); | |||
_areMembersStale = false; | |||
} | |||
@@ -43,8 +43,8 @@ namespace Discord | |||
{ | |||
_users = new ConcurrentDictionary<string, User>(); | |||
} | |||
internal override void OnCached() { } | |||
internal override void OnUncached() | |||
internal override void LoadReferences() { } | |||
internal override void UnloadReferences() | |||
{ | |||
//Don't need to clean _users - they're considered owned by server | |||
} | |||
@@ -21,58 +21,37 @@ namespace Discord | |||
/// <summary> Returns a URL for this invite using XkcdCode if available or Id if not. </summary> | |||
public string Url => API.Endpoints.InviteUrl(XkcdCode ?? Id); | |||
/// <summary> Returns the user that created this invite. </summary> | |||
[JsonIgnore] | |||
public User Inviter { get; private set; } | |||
[JsonProperty("InviterId")] | |||
private readonly string _inviterId; | |||
public User Inviter => _inviter.Value; | |||
private readonly Reference<User> _inviter; | |||
/// <summary> Returns the server this invite is to. </summary> | |||
[JsonIgnore] | |||
public Server Server { get; private set; } | |||
[JsonProperty("ServerId")] | |||
private readonly string _serverId; | |||
public Server Server => _server.Value; | |||
private readonly Reference<Server> _server; | |||
/// <summary> Returns the channel this invite is to. </summary> | |||
[JsonIgnore] | |||
public Channel Channel { get; private set; } | |||
[JsonProperty("ChannelId")] | |||
private readonly string _channelId; | |||
public Channel Channel => _channel.Value; | |||
private readonly Reference<Channel> _channel; | |||
internal Invite(DiscordClient client, string code, string xkcdPass, string serverId, string inviterId, string channelId) | |||
: base(client, code) | |||
{ | |||
XkcdCode = xkcdPass; | |||
_serverId = serverId; | |||
_inviterId = inviterId; | |||
_channelId = channelId; | |||
_server = new Reference<Server>(serverId, x => _client.Servers[x] ?? new Server(client, x)); | |||
_inviter = new Reference<User>(serverId, x => _client.Users[x, _server.Id] ?? new User(client, x, _server.Id)); | |||
_channel = new Reference<Channel>(serverId, x => _client.Channels[x] ?? new Channel(client, x, _server.Id, null)); | |||
} | |||
internal override void OnCached() | |||
internal override void LoadReferences() | |||
{ | |||
var server = _client.Servers[_serverId]; | |||
if (server == null) | |||
server = new Server(_client, _serverId); | |||
Server = server; | |||
if (_inviterId != null) | |||
{ | |||
var inviter = _client.Users[_inviterId, _serverId]; | |||
if (inviter == null) | |||
inviter = new User(_client, _inviterId, _serverId); | |||
Inviter = inviter; | |||
} | |||
if (_channelId != null) | |||
{ | |||
var channel = _client.Channels[_channelId]; | |||
if (channel == null) | |||
channel = new Channel(_client, _channelId, _serverId, null); | |||
Channel = channel; | |||
} | |||
_server.Load(); | |||
_inviter.Load(); | |||
_channel.Load(); | |||
} | |||
internal override void OnUncached() { } | |||
internal override void UnloadReferences() { } | |||
public override string ToString() => XkcdCode ?? Id; | |||
@@ -129,48 +129,36 @@ namespace Discord | |||
/// <summary> Returns the server containing the channel this message was sent to. </summary> | |||
[JsonIgnore] | |||
public Server Server => Channel.Server; | |||
public Server Server => _channel.Value.Server; | |||
/// <summary> Returns the channel this message was sent to. </summary> | |||
[JsonIgnore] | |||
public Channel Channel { get; private set; } | |||
private readonly string _channelId; | |||
public Channel Channel => _channel.Value; | |||
private readonly Reference<Channel> _channel; | |||
/// <summary> Returns true if the current user created this message. </summary> | |||
public bool IsAuthor => _client.CurrentUserId == _userId; | |||
public bool IsAuthor => _client.CurrentUserId == _user.Id; | |||
/// <summary> Returns the author of this message. </summary> | |||
[JsonIgnore] | |||
public User User { get; private set; } | |||
private readonly string _userId; | |||
public User User => _user.Value; | |||
private readonly Reference<User> _user; | |||
internal Message(DiscordClient client, string id, string channelId, string userId) | |||
: base(client, id) | |||
{ | |||
_channelId = channelId; | |||
_userId = userId; | |||
_channel = new Reference<Channel>(channelId, x => _client.Channels[x], x => x.AddMessage(this), x => x.RemoveMessage(this)); | |||
_user = new Reference<User>(userId, x => _client.Users[x]); | |||
Attachments = _initialAttachments; | |||
Embeds = _initialEmbeds; | |||
} | |||
internal override void OnCached() | |||
internal override void LoadReferences() | |||
{ | |||
//References | |||
var channel = _client.Channels[_channelId]; | |||
channel.AddMessage(this); | |||
Channel = channel; | |||
var user = _client.Users[_userId, channel.Server?.Id]; | |||
//user.AddMessage(this); | |||
User = user; | |||
_channel.Load(); | |||
_user.Load(); | |||
} | |||
internal override void OnUncached() | |||
internal override void UnloadReferences() | |||
{ | |||
//References | |||
var channel = Channel; | |||
if (channel != null) | |||
channel.RemoveMessage(this); | |||
/*var user = User; | |||
if (user != null) | |||
user.RemoveMessage(this);*/ | |||
_channel.Unload(); | |||
_user.Unload(); | |||
} | |||
internal void Update(MessageInfo model) | |||
@@ -6,9 +6,7 @@ using System.Linq; | |||
namespace Discord | |||
{ | |||
public sealed class Role : CachedObject | |||
{ | |||
private readonly string _serverId; | |||
{ | |||
/// <summary> Returns the name of this role. </summary> | |||
public string Name { get; private set; } | |||
/// <summary> If true, this role is displayed isolated from other users. </summary> | |||
@@ -22,40 +20,35 @@ namespace Discord | |||
/// <summary> Returns the the permissions contained by this role. </summary> | |||
public ServerPermissions Permissions { get; } | |||
/// <summary> Returns the server this role is a member of. </summary> | |||
[JsonIgnore] | |||
public Server Server { get; private set; } | |||
public Server Server => _server.Value; | |||
private readonly Reference<Server> _server; | |||
/// <summary> Returns true if this is the role representing all users in a server. </summary> | |||
public bool IsEveryone => _serverId == null || Id == _serverId; | |||
public bool IsEveryone => _server.Id == null || Id == _server.Id; | |||
/// <summary> Returns a list of all members in this role. </summary> | |||
[JsonIgnore] | |||
public IEnumerable<User> Members => IsEveryone ? Server.Members : Server.Members.Where(x => x.HasRole(this)); | |||
public IEnumerable<User> Members => _server.Id != null ? (IsEveryone ? Server.Members : Server.Members.Where(x => x.HasRole(this))) : new User[0]; | |||
//TODO: Add local members cache | |||
internal Role(DiscordClient client, string id, string serverId) | |||
: base(client, id) | |||
{ | |||
_serverId = serverId; | |||
_server = new Reference<Server>(serverId, x => _client.Servers[x], x => x.AddRole(this), x => x.RemoveRole(this)); | |||
Permissions = new ServerPermissions(0); | |||
Permissions.Lock(); | |||
Color = new Color(0); | |||
Color.Lock(); | |||
} | |||
internal override void OnCached() | |||
internal override void LoadReferences() | |||
{ | |||
//References | |||
var server = _client.Servers[_serverId]; | |||
server.AddRole(this); | |||
Server = server; | |||
_server.Load(); | |||
} | |||
internal override void OnUncached() | |||
internal override void UnloadReferences() | |||
{ | |||
//References | |||
var server = Server; | |||
if (server != null) | |||
server.RemoveRole(this); | |||
_server.Unload(); | |||
} | |||
internal void Update(RoleInfo model) | |||
@@ -84,8 +84,8 @@ namespace Discord | |||
_bans = new ConcurrentDictionary<string, bool>(); | |||
_invites = new ConcurrentDictionary<string, Invite>(); | |||
} | |||
internal override void OnCached() { } | |||
internal override void OnUncached() | |||
internal override void LoadReferences() { } | |||
internal override void UnloadReferences() | |||
{ | |||
//Global Cache | |||
var globalChannels = _client.Channels; | |||
@@ -210,21 +210,31 @@ namespace Discord | |||
internal void AddMember(User member) | |||
{ | |||
_members.TryAdd(member.Id, member); | |||
foreach (var channel in Channels) | |||
if (_members.TryAdd(member.Id, member)) | |||
{ | |||
member.AddChannel(channel); | |||
channel.InvalidatePermissionsCache(member); | |||
if (member.Id == _ownerId) | |||
Owner = member; | |||
foreach (var channel in Channels) | |||
{ | |||
member.AddChannel(channel); | |||
channel.InvalidatePermissionsCache(member); | |||
} | |||
} | |||
} | |||
internal void RemoveMember(User member) | |||
{ | |||
foreach (var channel in Channels) | |||
if (_members.TryRemove(member.Id, out member)) | |||
{ | |||
member.RemoveChannel(channel); | |||
channel.InvalidatePermissionsCache(member); | |||
if (member.Id == _ownerId) | |||
Owner = null; | |||
foreach (var channel in Channels) | |||
{ | |||
member.RemoveChannel(channel); | |||
channel.InvalidatePermissionsCache(member); | |||
} | |||
} | |||
_members.TryRemove(member.Id, out member); | |||
} | |||
internal void HasMember(User user) => _members.ContainsKey(user.Id); | |||
@@ -16,7 +16,7 @@ namespace Discord | |||
private ServerPermissions _serverPermissions; | |||
/// <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, _server.Id); | |||
/// <summary> Returns the name of this user on this server. </summary> | |||
public string Name { get; private set; } | |||
/// <summary> Returns a by-name unique identifier separating this user from others with the same name. </summary> | |||
@@ -49,11 +49,12 @@ namespace Discord | |||
private DateTime _lastOnline; | |||
[JsonIgnore] | |||
internal GlobalUser GlobalUser { get; private set; } | |||
internal GlobalUser GlobalUser => _globalUser.Value; | |||
private readonly Reference<GlobalUser> _globalUser; | |||
[JsonIgnore] | |||
public Server Server { get; private set; } | |||
private string _serverId; | |||
public Server Server => _server.Value; | |||
private readonly Reference<Server> _server; | |||
[JsonIgnore] | |||
public Channel VoiceChannel { get; private set; } | |||
@@ -64,7 +65,7 @@ namespace Discord | |||
/// <summary> Returns a collection of all messages this user has sent on this server that are still in cache. </summary> | |||
[JsonIgnore] | |||
public IEnumerable<Message> Messages => _client.Messages.Where(x => x.User.Id == Id && x.Server.Id == _serverId); | |||
public IEnumerable<Message> Messages => _client.Messages.Where(x => x.User.Id == Id && x.Server.Id == _server.Id); | |||
/// <summary> Returns a collection of all channels this user is a member of. </summary> | |||
[JsonIgnore] | |||
@@ -73,44 +74,41 @@ namespace Discord | |||
internal User(DiscordClient client, string id, string serverId) | |||
: base(client, id) | |||
{ | |||
_serverId = serverId; | |||
_globalUser = new Reference<GlobalUser>(id, | |||
x => _client.GlobalUsers.GetOrAdd(x), | |||
x => x.AddUser(this), | |||
x => x.RemoveUser(this)); | |||
_server = new Reference<Server>(serverId, | |||
x => _client.Servers[x], | |||
x => | |||
{ | |||
x.AddMember(this); | |||
if (x.Id == _client.CurrentUserId) | |||
x.CurrentMember = this; | |||
}, | |||
x => | |||
{ | |||
x.RemoveMember(this); | |||
if (x.Id == _client.CurrentUserId) | |||
x.CurrentMember = null; | |||
}); | |||
Status = UserStatus.Offline; | |||
//_roles = new Dictionary<string, Role>(); | |||
_channels = new ConcurrentDictionary<string, Channel>(); | |||
_permissions = new ConcurrentDictionary<string, ChannelPermissions>(); | |||
_serverPermissions = new ServerPermissions(); | |||
} | |||
internal override void OnCached() | |||
{ | |||
if (_serverId != null) | |||
{ | |||
var server = _client.Servers[_serverId]; | |||
server.AddMember(this); | |||
if (Id == _client.CurrentUserId) | |||
server.CurrentMember = this; | |||
Server = server; | |||
} | |||
else | |||
UpdateRoles(null); | |||
var user = _client.GlobalUsers.GetOrAdd(Id); | |||
user.AddUser(this); | |||
GlobalUser = user; | |||
if (serverId == null) | |||
UpdateRoles(null); | |||
} | |||
internal override void OnUncached() | |||
internal override void LoadReferences() | |||
{ | |||
//References | |||
var server = Server; | |||
if (server != null) | |||
{ | |||
server.RemoveMember(this); | |||
if (Id == _client.CurrentUserId) | |||
server.CurrentMember = null; | |||
} | |||
var globalUser = GlobalUser; | |||
if (globalUser != null) | |||
globalUser.RemoveUser(this); | |||
_globalUser.Load(); | |||
_server.Load(); | |||
} | |||
internal override void UnloadReferences() | |||
{ | |||
_globalUser.Unload(); | |||
_server.Unload(); | |||
} | |||
public override string ToString() => Id; | |||
@@ -128,6 +126,7 @@ namespace Discord | |||
{ | |||
if (model.User != null) | |||
Update(model.User); | |||
if (model.JoinedAt.HasValue) | |||
JoinedAt = model.JoinedAt.Value; | |||
if (model.Roles != null) | |||
@@ -138,6 +137,7 @@ namespace Discord | |||
internal void Update(ExtendedMemberInfo model) | |||
{ | |||
Update(model as API.MemberInfo); | |||
if (model.IsServerDeafened != null) | |||
IsServerDeafened = model.IsServerDeafened.Value; | |||
if (model.IsServerMuted != null) | |||
@@ -156,9 +156,8 @@ namespace Discord | |||
if (Status == UserStatus.Offline) | |||
_lastOnline = DateTime.UtcNow; | |||
} | |||
//Allows null | |||
GameId = model.GameId; | |||
GameId = model.GameId; //Allows null | |||
} | |||
internal void Update(VoiceMemberInfo model) | |||
{ | |||
@@ -188,7 +187,7 @@ namespace Discord | |||
else | |||
newRoles = new Dictionary<string, Role>(); | |||
Role everyone; | |||
if (_serverId != null) | |||
if (_server.Id != null) | |||
everyone = Server.EveryoneRole; | |||
else | |||
everyone = _client.Roles.VirtualEveryone; | |||