diff --git a/.github/workflows/notify_matrix.yml b/.github/workflows/notify_matrix.yml new file mode 100644 index 00000000..8e1036f4 --- /dev/null +++ b/.github/workflows/notify_matrix.yml @@ -0,0 +1,23 @@ +name: Notify Matrix Chat + +# only trigger on pull request closed events +on: + pull_request: + types: [ closed ] + +jobs: + merge_job: + # this job will only run if the PR has been merged + if: github.event.pull_request.merged == true + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Send message to Matrix bots channel + id: matrix-chat-message + uses: fadenb/matrix-chat-message@v0.0.6 + with: + homeserver: 'matrix.org' + token: ${{ secrets.MATRIX_TOKEN }} + channel: '!WjxEKjjINpyBRPFgxl:krbel.duckdns.org' + message: | + new ovos-utils PR merged! https://github.com/OpenVoiceOS/ovos_utils/pull/${{ github.event.number }} diff --git a/ovos_utils/intents/intent_service_interface.py b/ovos_utils/intents/intent_service_interface.py index a0148806..eb239a1e 100644 --- a/ovos_utils/intents/intent_service_interface.py +++ b/ovos_utils/intents/intent_service_interface.py @@ -1,6 +1,6 @@ from os.path import exists, isfile -from adapt.intent import Intent + from mycroft_bus_client import MessageBusClient from mycroft_bus_client.message import Message, dig_for_message from ovos_utils import create_daemon @@ -502,6 +502,9 @@ def get_keywords_manifest(self): def open_intent_envelope(message): """Convert dictionary received over messagebus to Intent.""" + # TODO can this method be fully removed from ovos_utils ? + from adapt.intent import Intent + intent_dict = message.data return Intent(intent_dict.get('name'), intent_dict.get('requires'), diff --git a/ovos_utils/process_utils.py b/ovos_utils/process_utils.py new file mode 100644 index 00000000..cfdb7198 --- /dev/null +++ b/ovos_utils/process_utils.py @@ -0,0 +1,143 @@ +import sys +from collections import namedtuple +from enum import IntEnum + + +class ProcessState(IntEnum): + """Oredered enum to make state checks easy. + + For example Alive can be determined using >= ProcessState.ALIVE, + which will return True if the state is READY as well as ALIVE. + """ + NOT_STARTED = 0 + STARTED = 1 + ERROR = 2 + STOPPING = 3 + ALIVE = 4 + READY = 5 + + +# Process state change callback mappings. +_STATUS_CALLBACKS = [ + 'on_started', + 'on_alive', + 'on_ready', + 'on_error', + 'on_stopping', +] + +# namedtuple defaults only available on 3.7 and later python versions +if sys.version_info < (3, 7): + StatusCallbackMap = namedtuple('CallbackMap', _STATUS_CALLBACKS) + StatusCallbackMap.__new__.__defaults__ = (None,) * 5 +else: + StatusCallbackMap = namedtuple( + 'CallbackMap', + _STATUS_CALLBACKS, + defaults=(None,) * len(_STATUS_CALLBACKS), + ) + + +class ProcessStatus: + """Process status tracker. + + The class tracks process status and execute callback methods on + state changes as well as replies to messagebus queries of the + process status. + + Args: + name (str): process name, will be used to create the messagebus + messagetype "mycroft.{name}...". + bus (MessageBusClient): Connection to the Mycroft messagebus. + callback_map (StatusCallbackMap): optionally, status callbacks for the + various status changes. + """ + + def __init__(self, name, bus=None, callback_map=None, namespace="mycroft"): + self.name = name + self._namespace = namespace + self.callbacks = callback_map or StatusCallbackMap() + self.state = ProcessState.NOT_STARTED + + # Messagebus connection + self.bus = None + if bus: + self.bind(bus) + + def bind(self, bus): + self.bus = bus + self._register_handlers() + + def _register_handlers(self): + """Register messagebus handlers for status queries.""" + self.bus.on(f'{self._namespace}.{self.name}.is_alive', self.check_alive) + self.bus.on(f'{self._namespace}.{self.name}.is_ready', self.check_ready) + + # The next one is for backwards compatibility + self.bus.on(f'mycroft.{self.name}.all_loaded', self.check_ready) + + def check_alive(self, message=None): + """Respond to is_alive status request. + + Args: + message: Optional message to respond to, if omitted no message + is sent. + + Returns: + bool, True if process is alive. + """ + is_alive = self.state >= ProcessState.ALIVE + + if message: + status = {'status': is_alive} + self.bus.emit(message.response(data=status)) + + return is_alive + + def check_ready(self, message=None): + """Respond to all_loaded status request. + + Args: + message: Optional message to respond to, if omitted no message + is sent. + + Returns: + bool, True if process is ready. + """ + is_ready = self.state >= ProcessState.READY + if message: + status = {'status': is_ready} + self.bus.emit(message.response(data=status)) + + return is_ready + + def set_started(self): + """Process is started.""" + self.state = ProcessState.STARTED + if self.callbacks.on_started: + self.callbacks.on_started() + + def set_alive(self): + """Basic loading is done.""" + self.state = ProcessState.ALIVE + if self.callbacks.on_alive: + self.callbacks.on_alive() + + def set_ready(self): + """All loading is done.""" + self.state = ProcessState.READY + if self.callbacks.on_ready: + self.callbacks.on_ready() + + def set_stopping(self): + """Process shutdown has started.""" + self.state = ProcessState.STOPPING + if self.callbacks.on_stopping: + self.callbacks.on_stopping() + + def set_error(self, err=''): + """An error has occured and the process is non-functional.""" + # Intentionally leave is_started True + self.state = ProcessState.ERROR + if self.callbacks.on_error: + self.callbacks.on_error(err) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index cb83588d..0fd9f813 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -2,5 +2,4 @@ mycroft-messagebus-client~=0.9.1,!=0.9.2,!=0.9.3 pexpect~=4.8 requests~=2.26 json_database~=0.7 -kthread~=0.2 -PyYAML~=5.4 +kthread~=0.2 \ No newline at end of file