Browse Source

Added initial work for member downloading

tags/1.0-rc
RogueException 9 years ago
parent
commit
0c3b02f5a4
6 changed files with 84 additions and 15 deletions
  1. +4
    -0
      src/Discord.Net/API/DiscordAPIClient.cs
  2. +2
    -1
      src/Discord.Net/API/Gateway/RequestMembersParams.cs
  3. +55
    -14
      src/Discord.Net/DiscordSocketClient.cs
  4. +1
    -0
      src/Discord.Net/Entities/Guilds/Guild.cs
  5. +3
    -0
      src/Discord.Net/Entities/Guilds/IGuild.cs
  6. +19
    -0
      src/Discord.Net/Entities/WebSocket/CachedGuild.cs

+ 4
- 0
src/Discord.Net/API/DiscordAPIClient.cs View File

@@ -367,6 +367,10 @@ namespace Discord.API
{ {
await SendGateway(GatewayOpCode.Heartbeat, lastSeq, options: options).ConfigureAwait(false); await SendGateway(GatewayOpCode.Heartbeat, lastSeq, options: options).ConfigureAwait(false);
} }
public async Task SendRequestMembers(IEnumerable<ulong> guildIds, RequestOptions options = null)
{
await SendGateway(GatewayOpCode.RequestGuildMembers, new RequestMembersParams { GuildIds = guildIds, Query = "", Limit = 0 }, options: options).ConfigureAwait(false);
}


//Channels //Channels
public async Task<Channel> GetChannel(ulong channelId, RequestOptions options = null) public async Task<Channel> GetChannel(ulong channelId, RequestOptions options = null)


+ 2
- 1
src/Discord.Net/API/Gateway/RequestMembersParams.cs View File

@@ -1,11 +1,12 @@
using Newtonsoft.Json; using Newtonsoft.Json;
using System.Collections.Generic;


namespace Discord.API.Gateway namespace Discord.API.Gateway
{ {
public class RequestMembersParams public class RequestMembersParams
{ {
[JsonProperty("guild_id")] [JsonProperty("guild_id")]
public ulong[] GuildId { get; set; }
public IEnumerable<ulong> GuildIds { get; set; }
[JsonProperty("query")] [JsonProperty("query")]
public string Query { get; set; } public string Query { get; set; }
[JsonProperty("limit")] [JsonProperty("limit")]


+ 55
- 14
src/Discord.Net/DiscordSocketClient.cs View File

@@ -17,9 +17,8 @@ using System.Threading.Tasks;


namespace Discord namespace Discord
{ {
//TODO: Remove unnecessary `as` casts
//TODO: Add event docstrings //TODO: Add event docstrings
//TODO: Add reconnect logic (+ensure the heartbeat task shuts down)
//TODO: Add reconnect logic (+ensure the heartbeat task to shut down)
//TODO: Add resume logic //TODO: Add resume logic
public class DiscordSocketClient : DiscordClient, IDiscordClient public class DiscordSocketClient : DiscordClient, IDiscordClient
{ {
@@ -32,7 +31,7 @@ namespace Discord
public event Func<IMessage, IMessage, Task> MessageUpdated; public event Func<IMessage, IMessage, Task> MessageUpdated;
public event Func<IRole, Task> RoleCreated, RoleDeleted; public event Func<IRole, Task> RoleCreated, RoleDeleted;
public event Func<IRole, IRole, Task> RoleUpdated; public event Func<IRole, IRole, Task> RoleUpdated;
public event Func<IGuild, Task> JoinedGuild, LeftGuild, GuildAvailable, GuildUnavailable;
public event Func<IGuild, Task> JoinedGuild, LeftGuild, GuildAvailable, GuildUnavailable, GuildDownloadedMembers;
public event Func<IGuild, IGuild, Task> GuildUpdated; public event Func<IGuild, IGuild, Task> GuildUpdated;
public event Func<IUser, Task> UserJoined, UserLeft, UserBanned, UserUnbanned; public event Func<IUser, Task> UserJoined, UserLeft, UserBanned, UserUnbanned;
public event Func<IUser, IUser, Task> UserUpdated; public event Func<IUser, IUser, Task> UserUpdated;
@@ -305,6 +304,47 @@ namespace Discord
return user; return user;
} }


/// <summary> Downloads the members list for all large guilds. </summary>
public Task DownloadAllMembers()
=> DownloadMembers(DataStore.Guilds.Where(x => !x.HasAllMembers));
/// <summary> Downloads the members list for the provided guilds, if they don't have a complete list. </summary>
public async Task DownloadMembers(IEnumerable<IGuild> guilds)
{
const short batchSize = 50;
var cachedGuilds = guilds.Select(x => x as CachedGuild).ToArray();
if (cachedGuilds.Length == 0)
return;
else if (cachedGuilds.Length == 1)
{
await cachedGuilds[0].DownloadMembers().ConfigureAwait(false);
return;
}

ulong[] batchIds = new ulong[Math.Min(batchSize, cachedGuilds.Length)];
Task[] batchTasks = new Task[batchIds.Length];
int batchCount = (cachedGuilds.Length + (batchSize - 1)) / batchSize;

for (int i = 0, k = 0; i < batchCount; i++)
{
bool isLast = i == batchCount - 1;
int count = isLast ? (batchIds.Length - (batchCount - 1) * batchSize) : batchSize;

for (int j = 0; j < count; j++, k++)
{
var guild = cachedGuilds[k];
batchIds[j] = guild.Id;
batchTasks[j] = guild.DownloaderPromise;
}

ApiClient.SendRequestMembers(batchIds);

if (isLast && batchCount > 1)
await Task.WhenAll(batchTasks.Take(count)).ConfigureAwait(false);
else
await Task.WhenAll(batchTasks).ConfigureAwait(false);
}
}

private async Task ProcessMessage(GatewayOpCode opCode, int? seq, string type, object payload) private async Task ProcessMessage(GatewayOpCode opCode, int? seq, string type, object payload)
{ {
if (seq != null) if (seq != null)
@@ -367,11 +407,8 @@ namespace Discord
type = "GUILD_AVAILABLE"; type = "GUILD_AVAILABLE";
else else
await JoinedGuild.Raise(guild).ConfigureAwait(false); await JoinedGuild.Raise(guild).ConfigureAwait(false);

if (!data.Large)
await GuildAvailable.Raise(guild);
else
_largeGuilds.Enqueue(data.Id);
await GuildAvailable.Raise(guild);
} }
break; break;
case "GUILD_UPDATE": case "GUILD_UPDATE":
@@ -781,15 +818,19 @@ namespace Discord
} }
private async Task RunHeartbeat(int intervalMillis, CancellationToken cancelToken) private async Task RunHeartbeat(int intervalMillis, CancellationToken cancelToken)
{ {
var state = ConnectionState;
while (state == ConnectionState.Connecting || state == ConnectionState.Connected)
try
{ {
//if (_heartbeatTime != 0) //TODO: Connection lost, reconnect
var state = ConnectionState;
while (state == ConnectionState.Connecting || state == ConnectionState.Connected)
{
//if (_heartbeatTime != 0) //TODO: Connection lost, reconnect


_heartbeatTime = Environment.TickCount;
await ApiClient.SendHeartbeat(_lastSeq).ConfigureAwait(false);
await Task.Delay(intervalMillis, cancelToken).ConfigureAwait(false);
_heartbeatTime = Environment.TickCount;
await ApiClient.SendHeartbeat(_lastSeq).ConfigureAwait(false);
await Task.Delay(intervalMillis, cancelToken).ConfigureAwait(false);
}
} }
catch (OperationCanceledException) { }
} }
} }
} }

+ 1
- 0
src/Discord.Net/Entities/Guilds/Guild.cs View File

@@ -306,6 +306,7 @@ namespace Discord
IRole IGuild.EveryoneRole => EveryoneRole; IRole IGuild.EveryoneRole => EveryoneRole;
IReadOnlyCollection<Emoji> IGuild.Emojis => Emojis; IReadOnlyCollection<Emoji> IGuild.Emojis => Emojis;
IReadOnlyCollection<string> IGuild.Features => Features; IReadOnlyCollection<string> IGuild.Features => Features;
Task IGuild.DownloadUsers() { throw new NotSupportedException(); }


IRole IGuild.GetRole(ulong id) => GetRole(id); IRole IGuild.GetRole(ulong id) => GetRole(id);
} }


+ 3
- 0
src/Discord.Net/Entities/Guilds/IGuild.cs View File

@@ -90,6 +90,9 @@ namespace Discord
Task<IGuildUser> GetUser(ulong id); Task<IGuildUser> GetUser(ulong id);
/// <summary> Gets the current user for this guild. </summary> /// <summary> Gets the current user for this guild. </summary>
Task<IGuildUser> GetCurrentUser(); Task<IGuildUser> GetCurrentUser();
/// <summary> Downloads all users for this guild if the current list is incomplete. </summary>
Task DownloadUsers();
/// <summary> Removes all users from this guild if they have not logged on in a provided number of days or, if simulate is true, returns the number of users that would be removed. </summary>
Task<int> PruneUsers(int days = 30, bool simulate = false); Task<int> PruneUsers(int days = 30, bool simulate = false);
} }
} }

+ 19
- 0
src/Discord.Net/Entities/WebSocket/CachedGuild.cs View File

@@ -16,6 +16,7 @@ namespace Discord
{ {
internal class CachedGuild : Guild, ICachedEntity<ulong> internal class CachedGuild : Guild, ICachedEntity<ulong>
{ {
private TaskCompletionSource<bool> _downloaderPromise;
private ConcurrentHashSet<ulong> _channels; private ConcurrentHashSet<ulong> _channels;
private ConcurrentDictionary<ulong, CachedGuildUser> _members; private ConcurrentDictionary<ulong, CachedGuildUser> _members;
private ConcurrentDictionary<ulong, Presence> _presences; private ConcurrentDictionary<ulong, Presence> _presences;
@@ -23,6 +24,9 @@ namespace Discord


public bool Available { get; private set; } //TODO: Add to IGuild public bool Available { get; private set; } //TODO: Add to IGuild


public bool HasAllMembers => _downloaderPromise.Task.IsCompleted;
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 CachedGuildUser CurrentUser => GetCachedUser(Discord.CurrentUser.Id);
public IReadOnlyCollection<ICachedGuildChannel> Channels => _channels.Select(x => GetCachedChannel(x)).ToReadOnlyCollection(_channels); public IReadOnlyCollection<ICachedGuildChannel> Channels => _channels.Select(x => GetCachedChannel(x)).ToReadOnlyCollection(_channels);
@@ -30,6 +34,7 @@ namespace Discord


public CachedGuild(DiscordSocketClient discord, Model model) : base(discord, model) public CachedGuild(DiscordSocketClient discord, Model model) : base(discord, model)
{ {
_downloaderPromise = new TaskCompletionSource<bool>();
} }


public void Update(ExtendedModel model, UpdateSource source, DataStore dataStore) public void Update(ExtendedModel model, UpdateSource source, DataStore dataStore)
@@ -79,6 +84,9 @@ namespace Discord
{ {
for (int i = 0; i < model.Members.Length; i++) for (int i = 0; i < model.Members.Length; i++)
AddCachedUser(model.Members[i], members, dataStore); AddCachedUser(model.Members[i], members, dataStore);
_downloaderPromise = new TaskCompletionSource<bool>();
if (!model.Large)
_downloaderPromise.SetResult(true);
} }
_members = members; _members = members;
} }
@@ -153,6 +161,17 @@ namespace Discord
return null; return null;
} }


public async Task DownloadMembers()
{
if (!HasAllMembers)
await Discord.ApiClient.SendRequestMembers(new ulong[] { Id }).ConfigureAwait(false);
await _downloaderPromise.Task.ConfigureAwait(false);
}
public void CompleteDownloadMembers()
{
_downloaderPromise.SetResult(true);
}

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


new internal ICachedGuildChannel ToChannel(ChannelModel model) new internal ICachedGuildChannel ToChannel(ChannelModel model)


Loading…
Cancel
Save