mirror of https://gitlab.com/litecord/litecord.git
blueprints.guild: use EventDispatcher
- dispatcher: add sub_guild, unsub_guild, remove_guild, dispatch_guild - gateway.state_manager: fix fetch_states - gateway.websocket: add current_shard, shard_count to atributes - schema: add ON DELETE CASCADE to channel tables
This commit is contained in:
parent
b085df207d
commit
de98d0f609
|
|
@ -70,7 +70,11 @@ async def create_guild():
|
||||||
guild_json = await app.storage.get_guild(guild_id, user_id)
|
guild_json = await app.storage.get_guild(guild_id, user_id)
|
||||||
guild_extra = await app.storage.get_guild_extra(guild_id, user_id, 250)
|
guild_extra = await app.storage.get_guild_extra(guild_id, user_id, 250)
|
||||||
|
|
||||||
return jsonify({**guild_json, **guild_extra})
|
guild_total = {**guild_json, **guild_extra}
|
||||||
|
|
||||||
|
app.dispatcher.sub_guild(guild_id, user_id)
|
||||||
|
await app.dispatcher.dispatch_guild(guild_id, 'GUILD_CREATE', guild_total)
|
||||||
|
return jsonify(guild_total)
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/<int:guild_id>', methods=['GET'])
|
@bp.route('/<int:guild_id>', methods=['GET'])
|
||||||
|
|
@ -84,6 +88,7 @@ async def get_guild(guild_id):
|
||||||
|
|
||||||
@bp.route('/<int:guild_id>', methods=['DELETE'])
|
@bp.route('/<int:guild_id>', methods=['DELETE'])
|
||||||
async def delete_guild(guild_id):
|
async def delete_guild(guild_id):
|
||||||
|
"""Delete a guild."""
|
||||||
user_id = await token_check()
|
user_id = await token_check()
|
||||||
|
|
||||||
owner_id = await app.db.fetchval("""
|
owner_id = await app.db.fetchval("""
|
||||||
|
|
@ -98,7 +103,20 @@ async def delete_guild(guild_id):
|
||||||
if user_id != owner_id:
|
if user_id != owner_id:
|
||||||
raise Forbidden('You are not the owner of the guild')
|
raise Forbidden('You are not the owner of the guild')
|
||||||
|
|
||||||
# TODO: delete guild, fire GUILD_DELETE to guild
|
await app.db.execute("""
|
||||||
|
DELETE FROM guild
|
||||||
|
WHERE guilds.id = $1
|
||||||
|
""", guild_id)
|
||||||
|
|
||||||
|
await app.dispatcher.dispatch_guild(guild_id, 'GUILD_DELETE', {
|
||||||
|
'id': guild_id,
|
||||||
|
'unavailable': False,
|
||||||
|
})
|
||||||
|
|
||||||
|
# remove from the dispatcher so nobody
|
||||||
|
# becomes the little memer that tries to fuck up with
|
||||||
|
# everybody's gateway
|
||||||
|
app.dispatcher.remove_guild(guild_id)
|
||||||
|
|
||||||
return '', 204
|
return '', 204
|
||||||
|
|
||||||
|
|
@ -166,8 +184,7 @@ async def create_channel(guild_id):
|
||||||
|
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
# TODO: fire Channel Create event
|
await app.dispatcher.dispatch_guild(guild_id, 'CHANNEL_CREATE', channel)
|
||||||
|
|
||||||
return jsonify(channel)
|
return jsonify(channel)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,55 @@
|
||||||
|
import collections
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from logbook import Logger
|
||||||
|
|
||||||
|
log = Logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class EventDispatcher:
|
class EventDispatcher:
|
||||||
"""Pub/Sub routines for litecord."""
|
"""Pub/Sub routines for litecord."""
|
||||||
pass
|
def __init__(self, sm):
|
||||||
|
self.state_manager = sm
|
||||||
|
self.guild_buckets = collections.defaultdict(set)
|
||||||
|
|
||||||
|
def sub_guild(self, guild_id: int, user_id: int):
|
||||||
|
"""Subscribe to a guild's events, given the user ID."""
|
||||||
|
self.guild_buckets[guild_id].add(user_id)
|
||||||
|
|
||||||
|
def unsub_guild(self, guild_id: int, user_id: int):
|
||||||
|
"""Unsubscribe from a guild, given user ID"""
|
||||||
|
self.guild_buckets[guild_id].discard(user_id)
|
||||||
|
|
||||||
|
def remove_guild(self, guild_id):
|
||||||
|
"""Reset the guild bucket."""
|
||||||
|
self.guild_buckets[guild_id] = set()
|
||||||
|
|
||||||
|
async def dispatch_guild(self, guild_id: int,
|
||||||
|
event_name: str, event_payload: Any):
|
||||||
|
"""Dispatch an event to a guild"""
|
||||||
|
users = self.guild_buckets[guild_id]
|
||||||
|
dispatched = 0
|
||||||
|
|
||||||
|
log.info('Dispatching {} {!r} to {} users',
|
||||||
|
guild_id, event_name, len(users))
|
||||||
|
|
||||||
|
for user_id in set(users):
|
||||||
|
# fetch all connections that are tied to the guild,
|
||||||
|
# this includes all connections that are just a single shard
|
||||||
|
# and all shards that are nicely working
|
||||||
|
states = self.state_manager.fetch_states(user_id, guild_id)
|
||||||
|
|
||||||
|
# if there are no more states tied to the guild,
|
||||||
|
# why keep the user as a subscriber?
|
||||||
|
if not states:
|
||||||
|
self.unsub_guild(guild_id, user_id)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# for each reasonable state/shard, dispatch event
|
||||||
|
for state in states:
|
||||||
|
# NOTE: maybe a separate task for that async?
|
||||||
|
await state.ws.dispatch(event_name, event_payload)
|
||||||
|
dispatched += 1
|
||||||
|
|
||||||
|
log.info('Dispatched {} {!r} to {} states',
|
||||||
|
guild_id, event_name, dispatched)
|
||||||
|
|
|
||||||
|
|
@ -37,11 +37,14 @@ class StateManager:
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def fetch_states(self, user_id, guild_id) -> List[GatewayState]:
|
def fetch_states(self, user_id: int, guild_id: int) -> List[GatewayState]:
|
||||||
"""Fetch all states that are tied to a guild."""
|
"""Fetch all states that are tied to a guild."""
|
||||||
states = []
|
states = []
|
||||||
|
|
||||||
for state in self.states[user_id]:
|
for state in self.states[user_id].values():
|
||||||
|
# find out if we are the shard for the guild id
|
||||||
|
# this works if shard_count == 1 (the default for
|
||||||
|
# single gw connections) since N % 1 is always 0
|
||||||
shard_id = (guild_id >> 22) % state.shard_count
|
shard_id = (guild_id >> 22) % state.shard_count
|
||||||
|
|
||||||
if shard_id == state.current_shard:
|
if shard_id == state.current_shard:
|
||||||
|
|
|
||||||
|
|
@ -204,6 +204,8 @@ class GatewayWebsocket:
|
||||||
compress=compress,
|
compress=compress,
|
||||||
large=large,
|
large=large,
|
||||||
shard=shard,
|
shard=shard,
|
||||||
|
current_shard=shard[0],
|
||||||
|
shard_count=shard[1],
|
||||||
presence=presence,
|
presence=presence,
|
||||||
ws=self
|
ws=self
|
||||||
)
|
)
|
||||||
|
|
|
||||||
2
run.py
2
run.py
|
|
@ -13,6 +13,7 @@ from litecord.gateway import websocket_handler
|
||||||
from litecord.errors import LitecordError
|
from litecord.errors import LitecordError
|
||||||
from litecord.gateway.state_manager import StateManager
|
from litecord.gateway.state_manager import StateManager
|
||||||
from litecord.storage import Storage
|
from litecord.storage import Storage
|
||||||
|
from litecord.dispatcher import EventDispatcher
|
||||||
|
|
||||||
# setup logbook
|
# setup logbook
|
||||||
handler = StreamHandler(sys.stdout, level=logbook.INFO)
|
handler = StreamHandler(sys.stdout, level=logbook.INFO)
|
||||||
|
|
@ -49,6 +50,7 @@ async def app_before_serving():
|
||||||
g.loop = asyncio.get_event_loop()
|
g.loop = asyncio.get_event_loop()
|
||||||
|
|
||||||
app.state_manager = StateManager()
|
app.state_manager = StateManager()
|
||||||
|
app.dispatcher = EventDispatcher(app.state_manager)
|
||||||
app.storage = Storage(app.db)
|
app.storage = Storage(app.db)
|
||||||
|
|
||||||
# start the websocket, etc
|
# start the websocket, etc
|
||||||
|
|
|
||||||
|
|
@ -143,7 +143,7 @@ CREATE TABLE IF NOT EXISTS guilds (
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS guild_channels (
|
CREATE TABLE IF NOT EXISTS guild_channels (
|
||||||
id bigint REFERENCES channels (id) PRIMARY KEY,
|
id bigint REFERENCES channels (id) PRIMARY KEY,
|
||||||
guild_id bigint REFERENCES guilds (id),
|
guild_id bigint REFERENCES guilds (id) ON DELETE CASCADE,
|
||||||
|
|
||||||
-- an id to guild_channels
|
-- an id to guild_channels
|
||||||
parent_id bigint DEFAULT NULL,
|
parent_id bigint DEFAULT NULL,
|
||||||
|
|
@ -155,12 +155,12 @@ CREATE TABLE IF NOT EXISTS guild_channels (
|
||||||
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS guild_text_channels (
|
CREATE TABLE IF NOT EXISTS guild_text_channels (
|
||||||
id bigint REFERENCES guild_channels (id) PRIMARY KEY,
|
id bigint REFERENCES guild_channels (id) PRIMARY KEY ON DELETE CASCADE,
|
||||||
topic text DEFAULT ''
|
topic text DEFAULT ''
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS guild_voice_channels (
|
CREATE TABLE IF NOT EXISTS guild_voice_channels (
|
||||||
id bigint REFERENCES guild_channels (id) PRIMARY KEY,
|
id bigint REFERENCES guild_channels (id) PRIMARY KEY ON DELETE CASCADE,
|
||||||
|
|
||||||
-- default bitrate for discord is 64kbps
|
-- default bitrate for discord is 64kbps
|
||||||
bitrate int DEFAULT 64,
|
bitrate int DEFAULT 64,
|
||||||
|
|
@ -176,7 +176,7 @@ CREATE TABLE IF NOT EXISTS dm_channels (
|
||||||
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS group_dm_channels (
|
CREATE TABLE IF NOT EXISTS group_dm_channels (
|
||||||
id bigint REFERENCES channels (id) PRIMARY KEY,
|
id bigint REFERENCES channels (id) PRIMARY KEY ON DELETE CASCADE,
|
||||||
owner_id bigint REFERENCES users (id),
|
owner_id bigint REFERENCES users (id),
|
||||||
icon bigint REFERENCES files (id)
|
icon bigint REFERENCES files (id)
|
||||||
);
|
);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue