| @@ -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; | |||