blueprints.guild.roles: add basic comms between roles bp and lazy guilds

- pubsub.lazy_guild: add LazyGuildDispatcher.dispatch
 - pubsub.lazy_guild: add GuildMemberList.role_delete
This commit is contained in:
Luna Mendes 2018-11-09 00:47:22 -03:00
parent 6245f08289
commit 6c63e014ae
2 changed files with 149 additions and 12 deletions

View File

@ -29,6 +29,23 @@ async def get_guild_roles(guild_id):
)
async def _maybe_lg(guild_id: int, event: str,
role, force: bool = False):
# sometimes we want to dispatch an event
# even if the role isn't hoisted
# an example of such a case is when a role loses
# its hoist status.
# check if is a dict first because role_delete
# only receives the role id.
if isinstance(role, dict) and not role['hoist'] and not force:
return
await app.dispatcher.dispatch(
'lazy_guild', guild_id, event, role)
async def create_role(guild_id, name: str, **kwargs):
"""Create a role in a guild."""
new_role_id = get_snowflake()
@ -64,6 +81,10 @@ async def create_role(guild_id, name: str, **kwargs):
)
role = await app.storage.get_role(new_role_id, guild_id)
# we need to update the lazy guild handlers for the newly created group
await _maybe_lg(guild_id, 'new_role', role)
await app.dispatcher.dispatch_guild(
guild_id, 'GUILD_ROLE_CREATE', {
'guild_id': str(guild_id),
@ -96,6 +117,8 @@ async def _role_update_dispatch(role_id: int, guild_id: int):
"""Dispatch a GUILD_ROLE_UPDATE with updated information on a role."""
role = await app.storage.get_role(role_id, guild_id)
await _maybe_lg(guild_id, 'role_pos_upd', role)
await app.dispatcher.dispatch_guild(guild_id, 'GUILD_ROLE_UPDATE', {
'guild_id': str(guild_id),
'role': role,
@ -285,6 +308,7 @@ async def update_guild_role(guild_id, role_id):
""", j[field], role_id, guild_id)
role = await _role_update_dispatch(role_id, guild_id)
await _maybe_lg(guild_id, 'role_update', role, True)
return jsonify(role)
@ -307,6 +331,8 @@ async def delete_guild_role(guild_id, role_id):
if res == 'DELETE 0':
return '', 204
await _maybe_lg(guild_id, 'role_delete', role_id, True)
await app.dispatcher.dispatch_guild(guild_id, 'GUILD_ROLE_DELETE', {
'guild_id': str(guild_id),
'role_id': str(role_id),

View File

@ -31,10 +31,30 @@ class GroupInfo:
@dataclass
class MemberList:
"""Total information on the guild's member list."""
"""Total information on the guild's member list.
Attributes
----------
groups: List[:class:`GroupInfo`]
List with all group information, sorted
by their actual position in the member list.
data:
Actual dictionary holding a list of presences
that are connected to the given group.
overwrites:
Holds the channel overwrite information
for the list (a list is tied to a single
channel, and since only roles with Read Messages
can be in the list, we need to store that information)
"""
groups: List[GroupInfo] = None
#: this attribute is not actively used
# but i'm keeping it here to future-proof
# in the case where we need to fetch info
# by the group id.
group_info: Dict[GroupID, GroupInfo] = None
data: Dict[GroupID, Presence] = None
data: Dict[GroupID, List[Presence]] = None
overwrites: Dict[int, Dict[str, Any]] = None
def __bool__(self):
@ -139,6 +159,11 @@ class GuildMemberList:
# type is{session_id: set[list]}
self.state = defaultdict(set)
@property
def loop(self):
"""Get the main asyncio loop instance."""
return self.main.app.loop
@property
def storage(self):
"""Get the global :class:`Storage` instance."""
@ -385,6 +410,7 @@ class GuildMemberList:
self._set_empty_list()
def get_state(self, session_id: str):
"""Get the state for a session id."""
try:
state = self.state_man.fetch_raw(session_id)
return state
@ -668,20 +694,74 @@ class GuildMemberList:
return await self._pres_update_complex(
user_id, old_group, old_index, new_group)
async def dispatch(self, event: str, data: Any):
"""Modify the member list and dispatch the respective
events to subscribed shards.
async def role_delete(self, role_id: int):
"""Called when a role is deleted, so we should
delete it off the list."""
GuildMemberList stores the current guilds' list
in its :attr:`GuildMemberList.list` attribute,
with that attribute being modified via different
calls to :meth:`GuildMemberList.dispatch`
"""
# if no subscribers, drop event
if not self.list:
return
# before we delete anything, we need to find the
# states we'll resend the list info to.
# find the item id for the group info
role_item_index = index_by_func(
lambda d: d.get('group', {}).get('id') == role_id,
self.items
)
sess_ids_resync = self.get_subs(role_item_index)
# remove the group info off the list
groups_index = index_by_func(
lambda group: group.gid == role_id,
self.list.groups
)
if groups_index is not None:
del self.list.groups[groups_index]
else:
log.warning('list unstable: {} not on group list', role_id)
# then the state of it in the group_info list
try:
self.list.group_info.pop(role_id)
except KeyError:
log.warning('list unstable: {} not on group info', role_id)
# now the data info
try:
self.list.data.pop(role_id)
except KeyError:
log.warning('list unstable: {} not in data dict', role_id)
# and then overwrites.
try:
self.list.overwrites.pop(role_id)
except KeyError:
log.warning('list unstable: {} not in overwrites dict', role_id)
# after removing, we do a resync with the
# shards that had the group.
for session_id in sess_ids_resync:
# find the list range that the group was on
# so we resync only the given range, instead
# of the whole list state.
ranges = self.state[session_id]
try:
# get the only range where the group is in
role_range = next((r_min, r_max) for r_min, r_max in ranges
if r_min < role_item_index < r_max)
except StopIteration:
continue
# do resync-ing in the background
self.loop.create_task(
self.shard_query(session_id, role_range)
)
class LazyGuildDispatcher(Dispatcher):
"""Main class holding the member lists for lazy guilds."""
@ -734,3 +814,34 @@ class LazyGuildDispatcher(Dispatcher):
async def unsub(self, chan_id, session_id):
gml = await self.get_gml(chan_id)
gml.unsub(session_id)
async def dispatch(self, guild_id, event: str, *args, **kwargs):
"""Call a function specialized in handling the given event"""
try:
handler = getattr(self, f'_handle_{event}')
await handler(guild_id, *args, **kwargs)
except AttributeError:
log.warning('unknown event: {}', event)
return
async def _call_all_lists(self, guild_id, method: str, *args):
lists = self.get_gml_guild(guild_id)
for lazy_list in lists:
method = getattr(lazy_list, method)
await method(*args)
async def _handle_new_role(self, guild_id: int, new_role: dict):
"""Handle the addition of a new group by dispatching it to
the member lists."""
await self._call_all_lists(guild_id, 'new_role', new_role)
async def _handle_role_pos_upd(self, guild_id, role: dict):
await self._call_all_lists(guild_id, 'role_pos_update', role)
async def _handle_role_update(self, guild_id, role: dict):
# handle name and hoist changes
await self._call_all_lists(guild_id, 'role_update', role)
async def _handle_role_delete(self, guild_id, role_id: int):
await self._call_all_lists(guild_id, 'role_delete', role_id)