From 6076c8c671446d4c83a95d679f8ba512c682599c Mon Sep 17 00:00:00 2001
From: Rapptz <rapptz@gmail.com>
Date: Mon, 11 Apr 2016 14:24:15 -0400
Subject: [PATCH] Better chunking behaviour and add members on PRESENCE_UPDATE.

This should hopefully cover all cases where members are added.
There was a bug where an array of chunks received would get entirely
processed if only a single chunk was received. This was fixed by
explicitly bailing early if we're requesting for chunks.
---
 discord/state.py | 51 ++++++++++++++++++++++++++----------------------
 1 file changed, 28 insertions(+), 23 deletions(-)

diff --git a/discord/state.py b/discord/state.py
index 3b36225bd..7536f6596 100644
--- a/discord/state.py
+++ b/discord/state.py
@@ -46,7 +46,7 @@ class ListenerType(enum.Enum):
 
 Listener = namedtuple('Listener', ('type', 'future', 'predicate'))
 log = logging.getLogger(__name__)
-ReadyState = namedtuple('ReadyState', ('launch', 'chunks', 'servers'))
+ReadyState = namedtuple('ReadyState', ('launch', 'servers'))
 
 class ConnectionState:
     def __init__(self, dispatch, chunker, max_messages, *, loop):
@@ -85,6 +85,8 @@ class ConnectionState:
                 if passed:
                     future.set_result(result)
                     removed.append(i)
+                    if listener.type == ListenerType.chunk:
+                        break
 
         for index in reversed(removed):
             del self._listeners[index]
@@ -134,16 +136,7 @@ class ConnectionState:
             yield self.receive_chunk(server.id)
 
     @asyncio.coroutine
-    def _delay_ready(self, large_servers):
-        if len(large_servers):
-            # for regular accounts with < 100 guilds you will
-            # get a regular READY packet without the unavailable
-            # streaming so if this is non-empty then it's a regular
-            # account and it needs chunking.
-            yield from self.chunker(large_servers)
-            for server in large_servers:
-                self._ready_state.chunks.extend(self.chunks_needed(server))
-
+    def _delay_ready(self):
         launch = self._ready_state.launch
         while not launch.is_set():
             # this snippet of code is basically waiting 2 seconds
@@ -152,9 +145,18 @@ class ConnectionState:
             yield from asyncio.sleep(2)
 
         # get all the chunks
-        chunks = [f for f in self._ready_state.chunks if not f.done()]
+        servers = self._ready_state.servers
+        chunks = []
+        for server in servers:
+            chunks.extend(self.chunks_needed(server))
+
+        # we only want to request ~75 guilds per chunk request.
+        splits = [servers[i:i + 75] for i in range(0, len(servers), 75)]
+        for split in splits:
+            yield from self.chunker(split)
+
+        # wait for the chunks
         if chunks:
-            yield from self.chunker(self._ready_state.servers)
             yield from asyncio.wait(chunks)
 
         # remove the state
@@ -164,21 +166,21 @@ class ConnectionState:
         self.dispatch('ready')
 
     def parse_ready(self, data):
-        self._ready_state = ReadyState(launch=asyncio.Event(), chunks=[], servers=[])
+        self._ready_state = ReadyState(launch=asyncio.Event(), servers=[])
         self.user = User(**data['user'])
         guilds = data.get('guilds')
 
-        large_servers = []
+        servers = self._ready_state.servers
         for guild in guilds:
             server = self._add_server_from_data(guild)
             if server.large:
-                large_servers.append(server)
+                servers.append(server)
 
         for pm in data.get('private_channels'):
             self._add_private_channel(PrivateChannel(id=pm['id'],
                                      user=User(**pm['recipient'])))
 
-        utils.create_task(self._delay_ready(large_servers), loop=self.loop)
+        utils.create_task(self._delay_ready(), loop=self.loop)
 
     def parse_message_create(self, data):
         channel = self.get_channel(data.get('channel_id'))
@@ -215,7 +217,13 @@ class ConnectionState:
         member_id = user['id']
         member = server.get_member(member_id)
         if member is None:
-            return
+            if 'name' not in user:
+                # sometimes we receive 'incomplete' member data post-removal.
+                # skip these useless cases.
+                return
+
+            member = self._make_member(server, data)
+            server._add_member(member)
 
         old_member = copy.copy(member)
         member.status = data.get('status')
@@ -338,8 +346,6 @@ class ConnectionState:
 
         # check if it requires chunking
         if server.large:
-            chunks = list(self.chunks_needed(server))
-
             if unavailable == False:
                 # check if we're waiting for 'useful' READY
                 # and if we are, we don't want to dispatch any
@@ -349,9 +355,7 @@ class ConnectionState:
                 try:
                     state = self._ready_state
                     state.launch.clear()
-                    if chunks:
-                        state.servers.append(server)
-                        state.chunks.extend(chunks)
+                    state.servers.append(server)
                 except AttributeError:
                     # the _ready_state attribute is only there during
                     # processing of useful READY.
@@ -362,6 +366,7 @@ class ConnectionState:
             # since we're not waiting for 'useful' READY we'll just
             # do the chunk request here
             yield from self.chunker(server)
+            chunks = list(self.chunks_needed(server))
             if chunks:
                 yield from asyncio.wait(chunks)