Start a huge refactor of events
This commit is contained in:
parent
2d9a533dc4
commit
69dcd53937
5 changed files with 85 additions and 177 deletions
|
|
@ -26,10 +26,8 @@ def load(context):
|
||||||
for evt in context.data.index.keys():
|
for evt in context.data.index.keys():
|
||||||
if context.data.index[evt].hasAttribute("end"):
|
if context.data.index[evt].hasAttribute("end"):
|
||||||
event = ModuleEvent(call=fini, call_data=dict(strend=context.data.index[evt]))
|
event = ModuleEvent(call=fini, call_data=dict(strend=context.data.index[evt]))
|
||||||
event._end = context.data.index[evt].getDate("end")
|
event.schedule(context.data.index[evt].getDate("end"))
|
||||||
idt = context.add_event(event)
|
context.add_event(event)
|
||||||
if idt is not None:
|
|
||||||
context.data.index[evt]["_id"] = idt
|
|
||||||
|
|
||||||
|
|
||||||
def fini(d, strend):
|
def fini(d, strend):
|
||||||
|
|
@ -100,8 +98,8 @@ def start_countdown(msg):
|
||||||
strnd["end"] = datetime(now.year, now.month, now.day, hou, minu, sec, timezone.utc)
|
strnd["end"] = datetime(now.year, now.month, now.day, hou, minu, sec, timezone.utc)
|
||||||
else:
|
else:
|
||||||
strnd["end"] = datetime(now.year, now.month, now.day + 1, hou, minu, sec, timezone.utc)
|
strnd["end"] = datetime(now.year, now.month, now.day + 1, hou, minu, sec, timezone.utc)
|
||||||
evt._end = strnd.getDate("end")
|
evt.schedule(strnd.getDate("end"))
|
||||||
strnd["_id"] = context.add_event(evt)
|
context.add_event(evt)
|
||||||
except:
|
except:
|
||||||
context.data.delChild(strnd)
|
context.data.delChild(strnd)
|
||||||
raise IMException("Mauvais format de date pour l'événement %s. Il n'a pas été créé." % msg.args[0])
|
raise IMException("Mauvais format de date pour l'événement %s. Il n'a pas été créé." % msg.args[0])
|
||||||
|
|
@ -121,10 +119,8 @@ def start_countdown(msg):
|
||||||
strnd["end"] += timedelta(days=int(t)*365)
|
strnd["end"] += timedelta(days=int(t)*365)
|
||||||
else:
|
else:
|
||||||
strnd["end"] += timedelta(seconds=int(t))
|
strnd["end"] += timedelta(seconds=int(t))
|
||||||
evt._end = strnd.getDate("end")
|
evt.schedule(strnd.getDate("end"))
|
||||||
eid = context.add_event(evt)
|
context.add_event(evt)
|
||||||
if eid is not None:
|
|
||||||
strnd["_id"] = eid
|
|
||||||
|
|
||||||
context.save()
|
context.save()
|
||||||
if "end" in strnd:
|
if "end" in strnd:
|
||||||
|
|
@ -147,7 +143,7 @@ def end_countdown(msg):
|
||||||
if msg.args[0] in context.data.index:
|
if msg.args[0] in context.data.index:
|
||||||
if context.data.index[msg.args[0]]["proprio"] == msg.frm or (msg.cmd == "forceend" and msg.frm_owner):
|
if context.data.index[msg.args[0]]["proprio"] == msg.frm or (msg.cmd == "forceend" and msg.frm_owner):
|
||||||
duration = countdown(msg.date - context.data.index[msg.args[0]].getDate("start"))
|
duration = countdown(msg.date - context.data.index[msg.args[0]].getDate("start"))
|
||||||
context.del_event(context.data.index[msg.args[0]]["_id"])
|
context.del_event(context.data.index[msg.args[0]])
|
||||||
context.data.delChild(context.data.index[msg.args[0]])
|
context.data.delChild(context.data.index[msg.args[0]])
|
||||||
context.save()
|
context.save()
|
||||||
return Response("%s a duré %s." % (msg.args[0], duration),
|
return Response("%s a duré %s." % (msg.args[0], duration),
|
||||||
|
|
|
||||||
147
nemubot/bot.py
147
nemubot/bot.py
|
|
@ -19,6 +19,7 @@ from datetime import datetime, timezone
|
||||||
import logging
|
import logging
|
||||||
from multiprocessing import JoinableQueue
|
from multiprocessing import JoinableQueue
|
||||||
import threading
|
import threading
|
||||||
|
import traceback
|
||||||
import select
|
import select
|
||||||
import sys
|
import sys
|
||||||
import weakref
|
import weakref
|
||||||
|
|
@ -78,10 +79,6 @@ class Bot(threading.Thread):
|
||||||
self.modules = dict()
|
self.modules = dict()
|
||||||
self.modules_configuration = dict()
|
self.modules_configuration = dict()
|
||||||
|
|
||||||
# Events
|
|
||||||
self.events = list()
|
|
||||||
self.event_timer = None
|
|
||||||
|
|
||||||
# Own hooks
|
# Own hooks
|
||||||
from nemubot.treatment import MessageTreater
|
from nemubot.treatment import MessageTreater
|
||||||
self.treater = MessageTreater()
|
self.treater = MessageTreater()
|
||||||
|
|
@ -245,7 +242,7 @@ class Bot(threading.Thread):
|
||||||
|
|
||||||
# Events methods
|
# Events methods
|
||||||
|
|
||||||
def add_event(self, evt, eid=None, module_src=None):
|
def add_event(self, evt):
|
||||||
"""Register an event and return its identifiant for futur update
|
"""Register an event and return its identifiant for futur update
|
||||||
|
|
||||||
Return:
|
Return:
|
||||||
|
|
@ -254,129 +251,31 @@ class Bot(threading.Thread):
|
||||||
|
|
||||||
Argument:
|
Argument:
|
||||||
evt -- The event object to add
|
evt -- The event object to add
|
||||||
|
|
||||||
Keyword arguments:
|
|
||||||
eid -- The desired event ID (object or string UUID)
|
|
||||||
module_src -- The module to which the event is attached to
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if hasattr(self, "stop") and self.stop:
|
if hasattr(evt, "handle") and evt.handle is not None:
|
||||||
logger.warn("The bot is stopped, can't register new events")
|
raise Exception("Try to launch an already launched event.")
|
||||||
return
|
|
||||||
|
|
||||||
import uuid
|
def _end_event_timer(event):
|
||||||
|
"""Function called at the end of the event timer"""
|
||||||
|
|
||||||
# Generate the event id if no given
|
logger.debug("Trigering event")
|
||||||
if eid is None:
|
event.handle = None
|
||||||
eid = uuid.uuid1()
|
self.cnsr_queue.put_nowait(EventConsumer(event))
|
||||||
|
|
||||||
# Fill the id field of the event
|
|
||||||
if type(eid) is uuid.UUID:
|
|
||||||
evt.id = str(eid)
|
|
||||||
else:
|
|
||||||
# Ok, this is quiet useless...
|
|
||||||
try:
|
|
||||||
evt.id = str(uuid.UUID(eid))
|
|
||||||
except ValueError:
|
|
||||||
evt.id = eid
|
|
||||||
|
|
||||||
# TODO: mutex here plz
|
|
||||||
|
|
||||||
# Add the event in its place
|
|
||||||
t = evt.current
|
|
||||||
i = 0 # sentinel
|
|
||||||
for i in range(0, len(self.events)):
|
|
||||||
if self.events[i].current > t:
|
|
||||||
break
|
|
||||||
self.events.insert(i, evt)
|
|
||||||
|
|
||||||
if i == 0:
|
|
||||||
# First event changed, reset timer
|
|
||||||
self._update_event_timer()
|
|
||||||
if len(self.events) <= 0 or self.events[i] != evt:
|
|
||||||
# Our event has been executed and removed from queue
|
|
||||||
return None
|
|
||||||
|
|
||||||
# Register the event in the source module
|
|
||||||
if module_src is not None:
|
|
||||||
module_src.__nemubot_context__.events.append(evt.id)
|
|
||||||
evt.module_src = module_src
|
|
||||||
|
|
||||||
logger.info("New event registered in %d position: %s", i, t)
|
|
||||||
return evt.id
|
|
||||||
|
|
||||||
|
|
||||||
def del_event(self, evt, module_src=None):
|
|
||||||
"""Find and remove an event from list
|
|
||||||
|
|
||||||
Return:
|
|
||||||
True if the event has been found and removed, False else
|
|
||||||
|
|
||||||
Argument:
|
|
||||||
evt -- The ModuleEvent object to remove or just the event identifier
|
|
||||||
|
|
||||||
Keyword arguments:
|
|
||||||
module_src -- The module to which the event is attached to (ignored if evt is a ModuleEvent)
|
|
||||||
"""
|
|
||||||
|
|
||||||
logger.info("Removing event: %s from %s", evt, module_src)
|
|
||||||
|
|
||||||
from nemubot.event import ModuleEvent
|
|
||||||
if type(evt) is ModuleEvent:
|
|
||||||
id = evt.id
|
|
||||||
module_src = evt.module_src
|
|
||||||
else:
|
|
||||||
id = evt
|
|
||||||
|
|
||||||
if len(self.events) > 0 and id == self.events[0].id:
|
|
||||||
self.events.remove(self.events[0])
|
|
||||||
self._update_event_timer()
|
|
||||||
if module_src is not None:
|
|
||||||
module_src.__nemubot_context__.events.remove(id)
|
|
||||||
return True
|
|
||||||
|
|
||||||
for evt in self.events:
|
|
||||||
if evt.id == id:
|
|
||||||
self.events.remove(evt)
|
|
||||||
|
|
||||||
if module_src is not None:
|
|
||||||
module_src.__nemubot_context__.events.remove(evt.id)
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def _update_event_timer(self):
|
|
||||||
"""(Re)launch the timer to end with the closest event"""
|
|
||||||
|
|
||||||
# Reset the timer if this is the first item
|
|
||||||
if self.event_timer is not None:
|
|
||||||
self.event_timer.cancel()
|
|
||||||
|
|
||||||
if len(self.events):
|
|
||||||
try:
|
|
||||||
remaining = self.events[0].time_left.total_seconds()
|
|
||||||
except:
|
|
||||||
logger.exception("An error occurs during event time calculation:")
|
|
||||||
self.events.pop(0)
|
|
||||||
return self._update_event_timer()
|
|
||||||
|
|
||||||
logger.debug("Update timer: next event in %d seconds", remaining)
|
|
||||||
self.event_timer = threading.Timer(remaining if remaining > 0 else 0, self._end_event_timer)
|
|
||||||
self.event_timer.start()
|
|
||||||
|
|
||||||
else:
|
|
||||||
logger.debug("Update timer: no timer left")
|
|
||||||
|
|
||||||
|
|
||||||
def _end_event_timer(self):
|
|
||||||
"""Function called at the end of the event timer"""
|
|
||||||
|
|
||||||
while len(self.events) > 0 and datetime.now(timezone.utc) >= self.events[0].current:
|
|
||||||
evt = self.events.pop(0)
|
|
||||||
self.cnsr_queue.put_nowait(EventConsumer(evt))
|
|
||||||
sync_act("launch_consumer")
|
sync_act("launch_consumer")
|
||||||
|
|
||||||
self._update_event_timer()
|
evt.start(self.loop)
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def _add_event():
|
||||||
|
return self.loop.call_at(evt._next, _end_event_timer, evt)
|
||||||
|
future = asyncio.run_coroutine_threadsafe(_add_event(), loop=self.loop)
|
||||||
|
evt.handle = future.result()
|
||||||
|
|
||||||
|
logger.debug("New event registered in %ss", evt._next - self.loop.time())
|
||||||
|
|
||||||
|
return evt.handle
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Consumers methods
|
# Consumers methods
|
||||||
|
|
@ -509,10 +408,6 @@ class Bot(threading.Thread):
|
||||||
def quit(self):
|
def quit(self):
|
||||||
"""Save and unload modules and disconnect servers"""
|
"""Save and unload modules and disconnect servers"""
|
||||||
|
|
||||||
if self.event_timer is not None:
|
|
||||||
logger.info("Stop the event timer...")
|
|
||||||
self.event_timer.cancel()
|
|
||||||
|
|
||||||
logger.info("Save and unload all modules...")
|
logger.info("Save and unload all modules...")
|
||||||
for mod in [m for m in self.modules.keys()]:
|
for mod in [m for m in self.modules.keys()]:
|
||||||
self.unload_module(mod)
|
self.unload_module(mod)
|
||||||
|
|
|
||||||
|
|
@ -88,13 +88,8 @@ class EventConsumer:
|
||||||
logger.exception("Error during event end")
|
logger.exception("Error during event end")
|
||||||
|
|
||||||
# Reappend the event in the queue if it has next iteration
|
# Reappend the event in the queue if it has next iteration
|
||||||
if self.evt.next is not None:
|
if self.evt.next():
|
||||||
context.add_event(self.evt, eid=self.evt.id)
|
context.add_event(self.evt)
|
||||||
|
|
||||||
# Or remove reference of this event
|
|
||||||
elif (hasattr(self.evt, "module_src") and
|
|
||||||
self.evt.module_src is not None):
|
|
||||||
self.evt.module_src.__nemubot_context__.events.remove(self.evt.id)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -65,37 +65,28 @@ class ModuleEvent:
|
||||||
# Store times
|
# Store times
|
||||||
self.offset = timedelta(seconds=offset) # Time to wait before the first check
|
self.offset = timedelta(seconds=offset) # Time to wait before the first check
|
||||||
self.interval = timedelta(seconds=interval)
|
self.interval = timedelta(seconds=interval)
|
||||||
self._end = None # Cache
|
self._next = None # Cache
|
||||||
|
|
||||||
# How many times do this event?
|
# How many times do this event?
|
||||||
self.times = times
|
self.times = times
|
||||||
|
|
||||||
@property
|
|
||||||
def current(self):
|
|
||||||
"""Return the date of the near check"""
|
|
||||||
if self.times != 0:
|
|
||||||
if self._end is None:
|
|
||||||
self._end = datetime.now(timezone.utc) + self.offset + self.interval
|
|
||||||
return self._end
|
|
||||||
return None
|
|
||||||
|
|
||||||
@property
|
def start(self, loop):
|
||||||
|
if self._next is None:
|
||||||
|
self._next = loop.time() + self.offset.total_seconds() + self.interval.total_seconds()
|
||||||
|
|
||||||
|
|
||||||
|
def schedule(self, end):
|
||||||
|
self.interval = timedelta(seconds=0)
|
||||||
|
self.offset = end - datetime.now(timezone.utc)
|
||||||
|
|
||||||
|
|
||||||
def next(self):
|
def next(self):
|
||||||
"""Return the date of the next check"""
|
|
||||||
if self.times != 0:
|
if self.times != 0:
|
||||||
if self._end is None:
|
self._next += self.interval.total_seconds()
|
||||||
return self.current
|
return True
|
||||||
elif self._end < datetime.now(timezone.utc):
|
return False
|
||||||
self._end += self.interval
|
|
||||||
return self._end
|
|
||||||
return None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def time_left(self):
|
|
||||||
"""Return the time left before/after the near check"""
|
|
||||||
if self.current is not None:
|
|
||||||
return self.current - datetime.now(timezone.utc)
|
|
||||||
return timedelta.max
|
|
||||||
|
|
||||||
def check(self):
|
def check(self):
|
||||||
"""Run a check and realized the event if this is time"""
|
"""Run a check and realized the event if this is time"""
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,19 @@
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
class _FakeHandle:
|
||||||
|
|
||||||
|
def __init__(self, true_handle, callback):
|
||||||
|
self.handle = true_handle
|
||||||
|
self.callback = callback
|
||||||
|
|
||||||
|
def cancel(self):
|
||||||
|
self.handle.cancel()
|
||||||
|
if self.callback:
|
||||||
|
return self.callback()
|
||||||
|
|
||||||
class _ModuleContext:
|
class _ModuleContext:
|
||||||
|
|
||||||
def __init__(self, module=None):
|
def __init__(self, module=None):
|
||||||
|
|
@ -24,8 +37,8 @@ class _ModuleContext:
|
||||||
else:
|
else:
|
||||||
self.module_name = ""
|
self.module_name = ""
|
||||||
|
|
||||||
self.hooks = list()
|
|
||||||
self.events = list()
|
self.events = list()
|
||||||
|
self.hooks = list()
|
||||||
self.debug = False
|
self.debug = False
|
||||||
|
|
||||||
from nemubot.config.module import Module
|
from nemubot.config.module import Module
|
||||||
|
|
@ -36,6 +49,7 @@ class _ModuleContext:
|
||||||
from nemubot.tools.xmlparser import module_state
|
from nemubot.tools.xmlparser import module_state
|
||||||
return module_state.ModuleState("nemubotstate")
|
return module_state.ModuleState("nemubotstate")
|
||||||
|
|
||||||
|
|
||||||
def add_hook(self, hook, *triggers):
|
def add_hook(self, hook, *triggers):
|
||||||
from nemubot.hooks import Abstract as AbstractHook
|
from nemubot.hooks import Abstract as AbstractHook
|
||||||
assert isinstance(hook, AbstractHook), hook
|
assert isinstance(hook, AbstractHook), hook
|
||||||
|
|
@ -46,19 +60,17 @@ class _ModuleContext:
|
||||||
assert isinstance(hook, AbstractHook), hook
|
assert isinstance(hook, AbstractHook), hook
|
||||||
self.hooks.remove((triggers, hook))
|
self.hooks.remove((triggers, hook))
|
||||||
|
|
||||||
|
|
||||||
def subtreat(self, msg):
|
def subtreat(self, msg):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def add_event(self, evt, eid=None):
|
|
||||||
return self.events.append((evt, eid))
|
def add_event(self, evt):
|
||||||
|
return self.events.append(evt)
|
||||||
|
|
||||||
def del_event(self, evt):
|
def del_event(self, evt):
|
||||||
for i in self.events:
|
return self.events.remove(evt)
|
||||||
e, eid = i
|
|
||||||
if e == evt:
|
|
||||||
self.events.remove(i)
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def send_response(self, server, res):
|
def send_response(self, server, res):
|
||||||
self.module.logger.info("Send response: %s", res)
|
self.module.logger.info("Send response: %s", res)
|
||||||
|
|
@ -114,6 +126,10 @@ class ModuleContext(_ModuleContext):
|
||||||
def load_data(self):
|
def load_data(self):
|
||||||
return self.context.datastore.load(self.module_name)
|
return self.context.datastore.load(self.module_name)
|
||||||
|
|
||||||
|
def save(self):
|
||||||
|
self.context.datastore.save(self.module_name, self.data)
|
||||||
|
|
||||||
|
|
||||||
def add_hook(self, hook, *triggers):
|
def add_hook(self, hook, *triggers):
|
||||||
from nemubot.hooks import Abstract as AbstractHook
|
from nemubot.hooks import Abstract as AbstractHook
|
||||||
assert isinstance(hook, AbstractHook), hook
|
assert isinstance(hook, AbstractHook), hook
|
||||||
|
|
@ -126,14 +142,29 @@ class ModuleContext(_ModuleContext):
|
||||||
self.hooks.remove((triggers, hook))
|
self.hooks.remove((triggers, hook))
|
||||||
return self.context.treater.hm.del_hooks(*triggers, hook=hook)
|
return self.context.treater.hm.del_hooks(*triggers, hook=hook)
|
||||||
|
|
||||||
|
|
||||||
def subtreat(self, msg):
|
def subtreat(self, msg):
|
||||||
yield from self.context.treater.treat_msg(msg)
|
yield from self.context.treater.treat_msg(msg)
|
||||||
|
|
||||||
def add_event(self, evt, eid=None):
|
|
||||||
return self.context.add_event(evt, eid, module_src=self.module)
|
def add_event(self, evt):
|
||||||
|
if evt in self.events:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _cancel_event():
|
||||||
|
logger.debug("Cancel event")
|
||||||
|
evt.handle = None
|
||||||
|
return super().del_event(evt)
|
||||||
|
|
||||||
|
hd = self.context.add_event(evt)
|
||||||
|
evt.handle = _FakeHandle(hd, _cancel_event)
|
||||||
|
|
||||||
|
return super().add_event(evt)
|
||||||
|
|
||||||
def del_event(self, evt):
|
def del_event(self, evt):
|
||||||
return self.context.del_event(evt, module_src=self.module)
|
# Call to super().del_event is done in the _FakeHandle.cancel
|
||||||
|
return evt.handle.cancel()
|
||||||
|
|
||||||
|
|
||||||
def send_response(self, server, res):
|
def send_response(self, server, res):
|
||||||
if server in self.context.servers:
|
if server in self.context.servers:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue