forked from platypush/platypush
[#340] Ironed out some bugs in the alarm
integration.
- The alarm ID should be randomly generated - auto-increment IDs are subject to race conditions when alarms are created in separate processes. - Clean up alarms that are not static and have been removed from the db. - Better alarm shut down detection logic.
This commit is contained in:
parent
ca57d3d7b3
commit
42574d054a
2 changed files with 24 additions and 14 deletions
|
@ -167,6 +167,13 @@ class AlarmPlugin(RunnablePlugin, EntityManager):
|
||||||
media_plugin=alarm.media_plugin or self.media_plugin,
|
media_plugin=alarm.media_plugin or self.media_plugin,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Stop and remove alarms that are not statically configured no longer
|
||||||
|
# present in the db
|
||||||
|
for name, alarm in self.alarms.copy().items():
|
||||||
|
if not alarm.static and name not in alarms:
|
||||||
|
del self.alarms[name]
|
||||||
|
alarm.stop()
|
||||||
|
|
||||||
def _sync_alarms(self):
|
def _sync_alarms(self):
|
||||||
with self._get_session() as session:
|
with self._get_session() as session:
|
||||||
db_alarms = {
|
db_alarms = {
|
||||||
|
@ -184,15 +191,16 @@ class AlarmPlugin(RunnablePlugin, EntityManager):
|
||||||
self._synced = True
|
self._synced = True
|
||||||
|
|
||||||
def _clear_alarm(self, alarm: DbAlarm, session: Session):
|
def _clear_alarm(self, alarm: DbAlarm, session: Session):
|
||||||
self.alarms.pop(str(alarm.name), None)
|
alarm_obj = self.alarms.pop(str(alarm.name), None)
|
||||||
|
if alarm_obj:
|
||||||
|
alarm_obj.stop()
|
||||||
|
|
||||||
session.delete(alarm)
|
session.delete(alarm)
|
||||||
self._bus.post(EntityDeleteEvent(entity=alarm))
|
self._bus.post(EntityDeleteEvent(entity=alarm))
|
||||||
|
|
||||||
def _clear_expired_alarms(self, session: Session):
|
def _clear_expired_alarms(self, session: Session):
|
||||||
expired_alarms = [
|
expired_alarms = [
|
||||||
alarm
|
alarm for alarm in self.alarms.values() if alarm.should_stop()
|
||||||
for alarm in self.alarms.values()
|
|
||||||
if alarm.is_expired() and alarm.is_shut_down()
|
|
||||||
]
|
]
|
||||||
|
|
||||||
if not expired_alarms:
|
if not expired_alarms:
|
||||||
|
@ -220,7 +228,7 @@ class AlarmPlugin(RunnablePlugin, EntityManager):
|
||||||
iter(
|
iter(
|
||||||
alarm
|
alarm
|
||||||
for alarm in self.alarms.values()
|
for alarm in self.alarms.values()
|
||||||
if alarm.state == AlarmState.RUNNING
|
if alarm.state in {AlarmState.RUNNING, AlarmState.SNOOZED}
|
||||||
),
|
),
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
|
@ -233,6 +241,9 @@ class AlarmPlugin(RunnablePlugin, EntityManager):
|
||||||
|
|
||||||
def _on_alarm_update(self, alarm: Alarm):
|
def _on_alarm_update(self, alarm: Alarm):
|
||||||
with self._db_lock:
|
with self._db_lock:
|
||||||
|
if alarm.should_stop():
|
||||||
|
return
|
||||||
|
|
||||||
self.publish_entities([alarm])
|
self.publish_entities([alarm])
|
||||||
|
|
||||||
def _add(
|
def _add(
|
||||||
|
|
|
@ -3,6 +3,7 @@ import enum
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
import threading
|
import threading
|
||||||
|
from random import randint
|
||||||
from typing import Callable, Optional, Union
|
from typing import Callable, Optional, Union
|
||||||
|
|
||||||
import croniter
|
import croniter
|
||||||
|
@ -40,9 +41,6 @@ class Alarm:
|
||||||
Alarm model and controller.
|
Alarm model and controller.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_alarms_count = 0
|
|
||||||
_id_lock = threading.RLock()
|
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
when: Union[str, int, float],
|
when: Union[str, int, float],
|
||||||
|
@ -59,10 +57,7 @@ class Alarm:
|
||||||
on_change: Optional[Callable[['Alarm'], None]] = None,
|
on_change: Optional[Callable[['Alarm'], None]] = None,
|
||||||
**_,
|
**_,
|
||||||
):
|
):
|
||||||
with self._id_lock:
|
self.id = randint(0, 65535)
|
||||||
self._alarms_count += 1
|
|
||||||
self.id = self._alarms_count
|
|
||||||
|
|
||||||
self.when = when
|
self.when = when
|
||||||
self.name = name or f'Alarm_{self.id}'
|
self.name = name or f'Alarm_{self.id}'
|
||||||
self.media = self._get_media_resource(media)
|
self.media = self._get_media_resource(media)
|
||||||
|
@ -240,10 +235,10 @@ class Alarm:
|
||||||
sleep_time = self._runtime_snooze_interval
|
sleep_time = self._runtime_snooze_interval
|
||||||
else:
|
else:
|
||||||
self.state = AlarmState.WAITING
|
self.state = AlarmState.WAITING
|
||||||
self._on_change()
|
|
||||||
|
|
||||||
break
|
break
|
||||||
|
|
||||||
|
self._on_change()
|
||||||
self.wait_stop(self.poll_interval)
|
self.wait_stop(self.poll_interval)
|
||||||
|
|
||||||
if self.state == AlarmState.SNOOZED:
|
if self.state == AlarmState.SNOOZED:
|
||||||
|
@ -266,7 +261,11 @@ class Alarm:
|
||||||
self.stop_event.wait(timeout)
|
self.stop_event.wait(timeout)
|
||||||
|
|
||||||
def should_stop(self):
|
def should_stop(self):
|
||||||
return self.stop_event.is_set() or (self.is_expired() and self.is_shut_down())
|
return (
|
||||||
|
self.stop_event.is_set()
|
||||||
|
or (self.is_expired() and self.state == AlarmState.DISMISSED)
|
||||||
|
or self.state == AlarmState.SHUTDOWN
|
||||||
|
)
|
||||||
|
|
||||||
def to_dict(self) -> dict:
|
def to_dict(self) -> dict:
|
||||||
return {
|
return {
|
||||||
|
|
Loading…
Reference in a new issue