Make the TimedEvents work with asyncio

Improvements: events now occur precisely at the specified date. You don’t
need to stop touching your keyboard to execute them.
This commit is contained in:
Florent Le Coz 2014-08-01 01:22:59 +02:00
parent 55daf9d49d
commit 87cf38995a
3 changed files with 36 additions and 98 deletions

View file

@ -262,8 +262,6 @@ class Core(object):
self.initial_joins = []
self.timed_events = set()
self.connected_events = {}
self.pending_invites = {}
@ -749,22 +747,13 @@ class Core(object):
def remove_timed_event(self, event):
"""Remove an existing timed event"""
if event and event in self.timed_events:
self.timed_events.remove(event)
event.handler.cancel()
def add_timed_event(self, event):
"""Add a new timed event"""
self.timed_events.add(event)
def check_timed_events(self):
"""Check for the execution of timed events"""
now = datetime.now()
for event in self.timed_events:
if event.has_timed_out(now):
res = event()
if not res:
self.timed_events.remove(event)
break
event.handler = asyncio.get_event_loop().call_later(event.delay,
event.callback,
*event.args)
####################### XMPP-related actions ##################################
@ -1628,9 +1617,6 @@ class Core(object):
a non-None value), but we check for timed events instead.
"""
res = self.keyboard.get_user_input(self.stdscr)
while res is None:
self.check_timed_events()
res = self.keyboard.get_user_input(self.stdscr)
return res
def escape_next_key(self):

View file

@ -624,17 +624,12 @@ class ChatTab(Tab):
"""
if not config.get_by_tabname('send_chat_states', True, self.general_jid, True):
return
if self.timed_event_paused:
# check the weakref
event = self.timed_event_paused()
if event:
# the event already exists: we just update
# its date
event.change_date(datetime.now() + timedelta(seconds=4))
return
# First, cancel the delay if it already exists, before rescheduling
# it at a new date
self.cancel_paused_delay()
new_event = timed_events.DelayedEvent(4, self.send_chat_state, 'paused')
self.core.add_timed_event(new_event)
self.timed_event_paused = weakref.ref(new_event)
self.timed_event_paused = new_event
def cancel_paused_delay(self):
"""
@ -642,12 +637,9 @@ class ChatTab(Tab):
Called for example when the input is emptied, or when the message
is sent
"""
if self.timed_event_paused:
event = self.timed_event_paused()
if event:
self.core.remove_timed_event(event)
del event
self.timed_event_paused = None
if self.timed_event_paused is not None:
self.core.remove_timed_event(self.timed_event_paused)
self.timed_event_paused = None
def command_correct(self, line):
"""

View file

@ -13,75 +13,14 @@ Once created, they must be added to the list of checked events with
:py:func:`.PluginAPI.add_timed_event` (within a plugin).
"""
import asyncio
import logging
log = logging.getLogger(__name__)
import datetime
class TimedEvent(object):
"""
An event with a callback that is called when the specified time is passed.
Note that these events can NOT be used for very small delay or a very
precise date, since the check for events is done once per second, as
a maximum.
The callback and its arguments should be passed as the lasts arguments.
"""
def __init__(self, date, callback, *args):
"""
Create a new timed event.
:param datetime.datetime date: Time at which the callback must be run.
:param function callback: The handler that will be executed.
:param \*args: Optional arguments passed to the handler.
"""
self._callback = callback
self.args = args
self.repetive = False
self.next_call_date = date
def __call__(self):
"""
the call should return False if this event should be remove from
the events list.
If its true, the date should be updated beforehand to a later date,
or else it will be called every second
"""
self._callback(*self.args)
return self.repetive
def has_timed_out(self, current_date):
"""
Check if the event has timed out.
:param datetime.datetime current_date: The current date.
:returns: True if the callback should be called
:rtype: bool
"""
if self.next_call_date < current_date:
return True
else:
return False
def change_date(self, date):
"""
Simply change the date of the event.
:param datetime.datetime date: Next date.
"""
self.next_call_date = date
def add_delay(self, delay):
"""
Add a delay (in seconds) to the date.
:param int delay: The delay to add.
"""
self.next_call_date += datetime.timedelta(seconds=delay)
class DelayedEvent(TimedEvent):
class DelayedEvent(object):
"""
A TimedEvent, but with the date calculated from now + a delay in seconds.
Use it if you want an event to happen in, e.g. 6 seconds.
@ -94,6 +33,27 @@ class DelayedEvent(TimedEvent):
:param function callback: The handler that will be executed.
:param \*args: Optional arguments passed to the handler.
"""
date = datetime.datetime.now() + datetime.timedelta(seconds=delay)
TimedEvent.__init__(self, date, callback, *args)
self.callback = callback
self.args = args
self.repetive = False
self.delay = delay
# An asyncio handler, as returned by call_later() or call_at()
self.handler = None
class TimedEvent(DelayedEvent):
"""
An event with a callback that is called when the specified time is passed.
The callback and its arguments should be passed as the lasts arguments.
"""
def __init__(self, date, callback, *args):
"""
Create a new timed event.
:param datetime.datetime date: Time at which the callback must be run.
:param function callback: The handler that will be executed.
:param \*args: Optional arguments passed to the handler.
"""
delta = date - datetime.datetime.now()
delay = delta.total_seconds()
DelayedEvent.__init__(self, delay, callback, *args)