213 lines
8.8 KiB
Python
213 lines
8.8 KiB
Python
import inspect
|
|
import time
|
|
from threading import Thread
|
|
|
|
from core.decorators import instance
|
|
from core.functions import get_attrs
|
|
from core.logger import Logger
|
|
from core.registry import Registry
|
|
|
|
|
|
@instance()
|
|
class EventService:
|
|
def __init__(self):
|
|
self.handlers = {}
|
|
self.logger = Logger(__name__)
|
|
self.event_types = []
|
|
self.db_cache = {}
|
|
|
|
def inject(self, registry):
|
|
self.db = registry.get_instance("db")
|
|
self.bot = registry.get_instance("bot")
|
|
self.util = registry.get_instance("util")
|
|
|
|
def pre_start(self):
|
|
self.register_event_type("timer")
|
|
|
|
def start(self):
|
|
# process decorators
|
|
for _, inst in Registry.get_all_instances().items():
|
|
for name, method in get_attrs(inst).items():
|
|
if hasattr(method, "event"):
|
|
key = Registry.get_module_name(inst).split(".")
|
|
# We dont want to load events, if their modules not enabled in our config...
|
|
if key[0] not in self.bot.modules:
|
|
continue
|
|
attrs = getattr(method, "event")
|
|
handler = getattr(inst, name)
|
|
self.register(handler, attrs.event_type, attrs.description, inst.module_name, attrs.is_hidden,
|
|
attrs.is_enabled)
|
|
|
|
def register_event_type(self, event_type):
|
|
"""
|
|
Call during pre_start
|
|
|
|
Args:
|
|
event_type (str)
|
|
"""
|
|
|
|
event_type = event_type.lower()
|
|
|
|
if event_type in self.event_types:
|
|
self.logger.error("Could not register event type '%s': event type already registered" % event_type)
|
|
return
|
|
|
|
self.logger.debug("Registering event type '%s'" % event_type)
|
|
self.event_types.append(event_type)
|
|
|
|
def is_event_type(self, event_base_type):
|
|
return event_base_type in self.event_types
|
|
|
|
def register(self, handler, event_type, description, module, is_hidden, is_enabled):
|
|
"""
|
|
Call during pre_start
|
|
|
|
Args:
|
|
handler: (event_type, event_data) -> void
|
|
event_type: str
|
|
description: str
|
|
module: str
|
|
is_hidden: bool
|
|
is_enabled: bool
|
|
"""
|
|
if len(inspect.signature(handler).parameters) != 2:
|
|
raise Exception(
|
|
"Incorrect number of arguments for handler '%s.%s()'" % (handler.__module__, handler.__name__))
|
|
|
|
event_base_type, event_sub_type = self.get_event_type_parts(event_type)
|
|
module = module.lower()
|
|
handler_name = self.util.get_handler_name(handler)
|
|
is_hidden = 1 if is_hidden else 0
|
|
is_enabled = 1 if is_enabled else 0
|
|
if event_base_type not in self.event_types:
|
|
self.logger.error("Could not register handler '%s' for event type '%s': event type does not exist" % (
|
|
handler_name, event_type))
|
|
return
|
|
|
|
if not description:
|
|
self.logger.warning("No description for event_type '%s' and handler '%s'" % (event_type, handler_name))
|
|
|
|
row = self.db.query_single("SELECT 1 FROM event_config WHERE event_type = ? AND handler = ?",
|
|
[event_base_type, handler_name])
|
|
if row is None:
|
|
# add new event commands
|
|
self.db.exec(
|
|
"INSERT INTO event_config (event_type, event_sub_type, handler, "
|
|
"description, module, enabled, verified, is_hidden) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
|
|
[event_base_type, event_sub_type, handler_name, description, module, is_enabled, 1, is_hidden])
|
|
if event_base_type == "timer":
|
|
self.db.exec(
|
|
"INSERT INTO timer_event (event_type, event_sub_type, handler, next_run) VALUES (?, ?, ?, ?)",
|
|
[event_base_type, event_sub_type, handler_name, int(time.time())])
|
|
else:
|
|
# mark command as verified
|
|
self.db.exec(
|
|
"UPDATE event_config SET verified = ?, module = ?, description = ?, event_sub_type = ?, is_hidden = ? "
|
|
"WHERE event_type = ? AND handler = ?",
|
|
[1, module, description, event_sub_type, is_hidden, event_base_type, handler_name])
|
|
|
|
if event_base_type == "timer":
|
|
self.db.exec("UPDATE timer_event SET event_sub_type = ? WHERE event_type = ? AND handler = ?",
|
|
[event_sub_type, event_base_type, handler_name])
|
|
|
|
# load command handler
|
|
self.handlers[handler_name] = handler
|
|
|
|
def fire_event(self, event_type, event_data=None):
|
|
event_base_type, event_sub_type = self.get_event_type_parts(event_type)
|
|
|
|
if event_base_type not in self.event_types:
|
|
self.logger.error("Could not fire event type '%s': event type does not exist" % event_type)
|
|
return
|
|
|
|
data = self.get_handlers(event_base_type, event_sub_type)
|
|
for row in data:
|
|
if event_type != "connect":
|
|
t = Thread(target=self.call_handler, args=(row.handler, event_type, event_data), daemon=True)
|
|
t.run()
|
|
else:
|
|
self.call_handler(row.handler, event_type, event_data)
|
|
|
|
def call_handler(self, handler_method, event_type, event_data):
|
|
handler = self.handlers.get(handler_method, None)
|
|
if not handler:
|
|
self.logger.error(
|
|
"Could not find handler callback for event type '%s' and handler '%s'" % (event_type, handler_method))
|
|
return
|
|
|
|
try:
|
|
handler(event_type, event_data)
|
|
except Exception as e:
|
|
self.logger.error("error processing event '%s'" % event_type, e)
|
|
|
|
def get_event_type_parts(self, event_type):
|
|
parts = event_type.lower().split(":", 1)
|
|
if len(parts) == 2:
|
|
return parts[0], parts[1]
|
|
else:
|
|
return parts[0], ""
|
|
|
|
def get_event_type_key(self, event_base_type, event_sub_type):
|
|
return event_base_type + ":" + event_sub_type
|
|
|
|
def check_for_timer_events(self, current_timestamp):
|
|
data = self.db.query("SELECT e.event_type, e.event_sub_type, e.handler, t.next_run FROM timer_event t "
|
|
"JOIN event_config e ON t.event_type = e.event_type AND t.handler = e.handler "
|
|
"WHERE t.next_run <= ? AND e.enabled = 1", [current_timestamp])
|
|
for row in data:
|
|
self.execute_timed_event(row, current_timestamp)
|
|
|
|
def execute_timed_event(self, row, current_timestamp):
|
|
event_type_key = self.get_event_type_key(row.event_type, row.event_sub_type)
|
|
|
|
# timer event run times should be consistent, so we base the next run time off the last run time,
|
|
# instead of the current timestamp
|
|
next_run = row.next_run + int(row.event_sub_type)
|
|
|
|
# prevents timer events from getting too far behind, or having a large "catch-up" after
|
|
# the bot has been offline for a time
|
|
if next_run < current_timestamp:
|
|
next_run = current_timestamp + int(row.event_sub_type)
|
|
|
|
self.db.exec("UPDATE timer_event SET next_run = ? WHERE event_type = ? AND handler = ?",
|
|
[next_run, row.event_type, row.handler])
|
|
|
|
self.call_handler(row.handler, event_type_key, None)
|
|
|
|
def update_event_status(self, event_base_type, event_sub_type, event_handler, enabled_status):
|
|
# clear cache
|
|
self.db_cache[event_base_type + ":" + event_sub_type] = None
|
|
|
|
return self.db.exec(
|
|
"UPDATE event_config SET enabled = ? WHERE event_type = ? AND event_sub_type = ? AND handler LIKE ?",
|
|
[enabled_status, event_base_type, event_sub_type, event_handler])
|
|
|
|
def get_event_types(self):
|
|
return self.event_types
|
|
|
|
def get_handlers(self, event_base_type, event_sub_type):
|
|
# check first in cache
|
|
result = self.db_cache.get(event_base_type + ":" + event_sub_type, None)
|
|
if result is not None:
|
|
return result
|
|
else:
|
|
result = self.db.query(
|
|
"SELECT handler FROM event_config WHERE event_type = ? AND event_sub_type = ? AND enabled = 1",
|
|
[event_base_type, event_sub_type])
|
|
|
|
# store result in cache
|
|
self.db_cache[event_base_type + ":" + event_sub_type] = result
|
|
|
|
return result
|
|
|
|
def run_timer_events_at_startup(self):
|
|
t = int(time.time())
|
|
data = self.db.query("SELECT e.event_type, e.event_sub_type, e.handler, t.next_run FROM timer_event t "
|
|
"JOIN event_config e ON t.event_type = e.event_type AND t.handler = e.handler "
|
|
"WHERE e.event_type = ? AND e.enabled = 1", ["timer"])
|
|
for row in data:
|
|
handler = self.handlers[row.handler]
|
|
attrs = getattr(handler, "event")
|
|
if attrs.get("run_at_startup", False):
|
|
self.execute_timed_event(row, t)
|