From be572d08540be9f5b1b18e5bb4d99055513b0ee7 Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Mon, 31 Jan 2022 12:08:42 +0100 Subject: [PATCH] :art: Bunch of cleanups, updates etc --- core/ProjectAlice.py | 16 +-- core/ProjectAliceExceptions.py | 6 +- core/base/SuperManager.py | 169 ++++++++++++++--------- core/base/model/HotwordDownloadThread.py | 4 +- core/base/model/Manager.py | 2 +- core/base/model/ProjectAliceObject.py | 53 ++++--- core/commons/CommonsManager.py | 7 +- core/util/Decorators.py | 4 +- core/util/SubprocessManager.py | 4 +- core/util/ThreadManager.py | 62 +++++++-- core/util/model/Logger.py | 104 +++++++++----- core/voice/WakewordManager.py | 1 + requirements.txt | 3 +- 13 files changed, 275 insertions(+), 160 deletions(-) diff --git a/core/ProjectAlice.py b/core/ProjectAlice.py index 32338a4..ada3318 100644 --- a/core/ProjectAlice.py +++ b/core/ProjectAlice.py @@ -29,8 +29,8 @@ def __init__(self, restartHandler: callable): self._superManager.initManagers() - if self._superManager.configManager.getAliceConfigByName('useHLC'): - self._superManager.commons.runRootSystemCommand(['systemctl', 'start', 'hermesledcontrol']) + if self._superManager.ConfigManager.getAliceConfigByName('useHLC'): + self._superManager.Commons.runRootSystemCommand(['systemctl', 'start', 'hermesledcontrol']) self._superManager.onStart() @@ -69,8 +69,8 @@ def onStop(self, withReboot: bool = False): self._logger.logInfo('Shutting down') self._shuttingDown = True self._superManager.onStop() - if self._superManager.configManager.getAliceConfigByName('useHLC'): - self._superManager.commons.runRootSystemCommand(['systemctl', 'stop', 'hermesledcontrol']) + if self._superManager.ConfigManager.getAliceConfigByName('useHLC'): + self._superManager.Commons.runRootSystemCommand(['systemctl', 'stop', 'hermesledcontrol']) self._booted = False self.INSTANCE = None @@ -82,7 +82,7 @@ def onStop(self, withReboot: bool = False): def onFullHour(self): - if not self._superManager.configManager.getAliceConfigByName('aliceAutoUpdate'): + if not self._superManager.ConfigManager.getAliceConfigByName('aliceAutoUpdate'): return self.updateProjectAlice() @@ -90,12 +90,12 @@ def onFullHour(self): def updateProjectAlice(self): self._logger.logInfo('Checking for satellite updates') self._isUpdating = True - req = requests.get(url=f'{constants.GITHUB_API_URL}/ProjectAliceSatellite/branches', auth=SuperManager.getInstance().configManager.getGithubAuth()) + req = requests.get(url=f'{constants.GITHUB_API_URL}/ProjectAliceSatellite/branches', auth=SuperManager.getInstance().ConfigManager.getGithubAuth()) if req.status_code != 200: self._logger.logWarning('Failed checking for updates') return - userUpdatePref = SuperManager.getInstance().configManager.getAliceConfigByName('aliceUpdateChannel') + userUpdatePref = SuperManager.getInstance().ConfigManager.getAliceConfigByName('aliceUpdateChannel') if userUpdatePref == 'master': candidate = 'master' @@ -114,7 +114,7 @@ def updateProjectAlice(self): candidate = repoVersion self._logger.logInfo(f'Checking on "{str(candidate)}" update channel') - commons = SuperManager.getInstance().commons + commons = SuperManager.getInstance().Commons currentHash = subprocess.check_output(['git', 'rev-parse', '--short HEAD']) diff --git a/core/ProjectAliceExceptions.py b/core/ProjectAliceExceptions.py index f253c14..7d690b5 100644 --- a/core/ProjectAliceExceptions.py +++ b/core/ProjectAliceExceptions.py @@ -40,14 +40,14 @@ def __init__(self, skillName: str = '', error: str = ''): self._logger.logWarning(f'[{skillName}] ❗ Error starting skill: {error}') if skillName: - SuperManager.getInstance().skillManager.deactivateSkill(skillName) + SuperManager.getInstance().SkillManager.deactivateSkill(skillName) class SkillStartDelayed(ProjectAliceException): def __init__(self, skillName): super().__init__(skillName) self._logger.logWarning(f'[{skillName}] ⌛ Delaying skill start') - SuperManager.getInstance().skillManager.getSkillInstance(skillName).delayed = True + SuperManager.getInstance().SkillManager.getSkillInstance(skillName).delayed = True class IntentError(ProjectAliceException): @@ -123,5 +123,5 @@ class PlayBytesStopped(ProjectAliceException): class VitalConfigMissing(ProjectAliceException): def __init__(self, message: str = None): super().__init__(message) - self._logger.logWarning(f'A vital configuration --{message}-- is missing. Make sure the following configurations are set: {" / ".join(SuperManager.getInstance().configManager.vitalConfigs)}') + self._logger.logWarning(f'A vital configuration --{message}-- is missing. Make sure the following configurations are set: {" / ".join(SuperManager.getInstance().ConfigManager.vitalConfigs)}') SuperManager.getInstance().projectAlice.onStop() diff --git a/core/base/SuperManager.py b/core/base/SuperManager.py index 75454d4..6b7b3ea 100644 --- a/core/base/SuperManager.py +++ b/core/base/SuperManager.py @@ -1,6 +1,5 @@ from __future__ import annotations -from core.commons import constants from core.util.model.Logger import Logger @@ -21,61 +20,70 @@ def __init__(self, mainClass): self._managers = dict() self.projectAlice = mainClass - self.commons = None - self.commonsManager = None - self.configManager = None - self.databaseManager = None - self.threadManager = None - self.mqttManager = None - self.timeManager = None - self.networkManager = None - self.hotwordManager = None - self.skillManager = None - self.internetManager = None - self.audioManager = None - self.wakewordManager = None - self.subprocessManager = None + self.Commons = None + self.CommonsManager = None + self.ConfigManager = None + self.DatabaseManager = None + self.ThreadManager = None + self.MqttManager = None + self.TimeManager = None + self.NetworkManager = None + self.HotwordManager = None + self.SkillManager = None + self.InternetManager = None + self.AudioManager = None + self.WakewordManager = None + self.SubprocessManager = None def onStart(self): - commons = self._managers.pop('CommonsManager') - commons.onStart() + try: + commons = self._managers.pop('CommonsManager') + commons.onStart() - configManager = self._managers.pop('ConfigManager') - configManager.onStart() + configManager = self._managers.pop('ConfigManager') + configManager.onStart() - subprocessManager = self._managers.pop('SubprocessManager') - subprocessManager.onStart() + subprocessManager = self._managers.pop('SubprocessManager') + subprocessManager.onStart() - internetManager = self._managers.pop('InternetManager') - internetManager.onStart() + internetManager = self._managers.pop('InternetManager') + internetManager.onStart() - databaseManager = self._managers.pop('DatabaseManager') - databaseManager.onStart() + databaseManager = self._managers.pop('DatabaseManager') + databaseManager.onStart() - networkManager = self._managers.pop('NetworkManager') - networkManager.onStart() + networkManager = self._managers.pop('NetworkManager') + networkManager.onStart() - mqttManager = self._managers.pop('MqttManager') - mqttManager.onStart() + mqttManager = self._managers.pop('MqttManager') + mqttManager.onStart() - for manager in self._managers.values(): - if manager: - manager.onStart() + for manager in self._managers.values(): + if manager: + manager.onStart() - self._managers[commons.name] = commons - self._managers[configManager.name] = configManager - self._managers[subprocessManager.name] = subprocessManager - self._managers[databaseManager.name] = databaseManager - self._managers[mqttManager.name] = mqttManager - self._managers[networkManager.name] = networkManager - self._managers[internetManager.name] = internetManager + self._managers[commons.name] = commons + self._managers[configManager.name] = configManager + self._managers[subprocessManager.name] = subprocessManager + self._managers[databaseManager.name] = databaseManager + self._managers[mqttManager.name] = mqttManager + self._managers[networkManager.name] = networkManager + self._managers[internetManager.name] = internetManager + except Exception as e: + import traceback + traceback.print_exc() + Logger().logFatal(f'Error while starting managers: {e}') def onBooted(self): - for manager in self._managers.values(): - if manager: - manager.onBooted() + manager = None + try: + for manager in self._managers.values(): + if manager: + manager.onBooted() + except Exception as e: + Logger().logError(f'Error while sending onBooted to manager **{manager.name}**: {e}') @staticmethod @@ -98,43 +106,70 @@ def initManagers(self): from core.server.AudioServer import AudioManager from core.voice.WakewordManager import WakewordManager - self.commonsManager = CommonsManager() - self.commons = self.commonsManager - self.configManager = ConfigManager() - self.databaseManager = DatabaseManager() - self.threadManager = ThreadManager() - self.mqttManager = MqttManager() - self.timeManager = TimeManager() - self.networkManager = NetworkManager() - self.hotwordManager = HotwordManager() - self.skillManager = SkillManager() - self.internetManager = InternetManager() - self.audioManager = AudioManager() - self.wakewordManager = WakewordManager() - self.subprocessManager = SubprocessManager() + self.CommonsManager = CommonsManager() + self.Commons = self.CommonsManager + self.ConfigManager = ConfigManager() + self.DatabaseManager = DatabaseManager() + self.ThreadManager = ThreadManager() + self.MqttManager = MqttManager() + self.TimeManager = TimeManager() + self.NetworkManager = NetworkManager() + self.HotwordManager = HotwordManager() + self.SkillManager = SkillManager() + self.InternetManager = InternetManager() + self.AudioManager = AudioManager() + self.WakewordManager = WakewordManager() + self.SubprocessManager = SubprocessManager() - self._managers = {name[0].upper() + name[1:]: manager for name, manager in self.__dict__.items() if name.endswith('Manager')} + self._managers = {name: manager for name, manager in self.__dict__.items() if name.endswith('Manager')} def onStop(self): - managerName = constants.UNKNOWN_MANAGER - try: - mqttManager = self._managers.pop('MqttManager', None) - - for managerName, manager in self._managers.items(): - manager.onStop() - - if mqttManager: + mqttManager = self._managers.pop('MqttManager', None) # Mqtt goes down last with bug reporter + bugReportManager = self._managers.pop('BugReportManager', None) # bug reporter goes down as last + + skillManager = self._managers.pop('SkillManager', None) # Skill manager goes down first, to tell the skills + if skillManager: + try: + skillManager.onStop() + except Exception as e: + Logger().logError(f'Error stopping SkillManager: {e}') + + for managerName, manager in self._managers.items(): + try: + if manager.isActive: + manager.onStop() + except Exception as e: + Logger().logError(f'Error while shutting down manager **{managerName}**: {e}') + + if mqttManager: + try: mqttManager.onStop() + except Exception as e: + Logger().logError(f'Error stopping MqttManager: {e}') - except Exception as e: - Logger().logError(f'Error while shutting down manager "{managerName}": {e}') + if bugReportManager: + try: + bugReportManager.onStop() + except Exception as e: + Logger().logError(f'Error stopping BugReportManager: {e}') def getManager(self, managerName: str): return self._managers.get(managerName, None) + def restartManager(self, manager: str): + managerInstance = self._managers.get(manager, None) + if not managerInstance: + Logger().logWarning(f'Was asking to restart manager **{manager}** but it doesn\'t exist') + return + + managerInstance.onStop() + managerInstance.onStart() + managerInstance.onBooted() + + @property def managers(self) -> dict: return self._managers diff --git a/core/base/model/HotwordDownloadThread.py b/core/base/model/HotwordDownloadThread.py index 794663b..b84b053 100644 --- a/core/base/model/HotwordDownloadThread.py +++ b/core/base/model/HotwordDownloadThread.py @@ -24,7 +24,7 @@ def run(self): try: self._logger.logInfo('Cleaning up') - rootPath = Path(SuperManager.getInstance().commons.rootDir(), 'trained/hotwords/snips_hotword') + rootPath = Path(SuperManager.getInstance().Commons.rootDir(), 'trained/hotwords/snips_hotword') hotwordPath = rootPath / f'{self._hotwordName}' zipPath = hotwordPath.with_suffix('.zip') @@ -71,4 +71,4 @@ def run(self): finally: sock.close() os.remove(zipPath) - SuperManager.getInstance().wakewordManager.restartEngine() + SuperManager.getInstance().WakewordManager.restartEngine() diff --git a/core/base/model/Manager.py b/core/base/model/Manager.py index 2a531b5..63058fb 100644 --- a/core/base/model/Manager.py +++ b/core/base/model/Manager.py @@ -50,7 +50,7 @@ def onStop(self): def _initDB(self): if self._databaseSchema: - return SuperManager.getInstance().databaseManager.initDB(schema=self._databaseSchema, callerName=self.name) + return SuperManager.getInstance().DatabaseManager.initDB(schema=self._databaseSchema, callerName=self.name) return True diff --git a/core/base/model/ProjectAliceObject.py b/core/base/model/ProjectAliceObject.py index 8f24575..0b1fbae 100644 --- a/core/base/model/ProjectAliceObject.py +++ b/core/base/model/ProjectAliceObject.py @@ -3,7 +3,7 @@ import json import re from copy import copy -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Union from importlib_metadata import PackageNotFoundError, version as packageVersion @@ -141,33 +141,32 @@ def installDependencies(self) -> bool: return False - def logInfo(self, msg: str): - self._logger.doLog(function='info', msg=self.decorateLogs(msg), printStack=False) + def logInfo(self, msg: str, plural: Union[list, str] = None): + self._logger.doLog(function='info', msg=self.decorateLogs(msg), printStack=False, plural=plural) - def logError(self, msg: str): - self._logger.doLog(function='error', msg=self.decorateLogs(msg)) + def logError(self, msg: str, plural: Union[list, str] = None, printStack: bool = True): + self._logger.doLog(function='error', msg=self.decorateLogs(msg), plural=plural, printStack=printStack) - def logDebug(self, msg: str): - self._logger.doLog(function='debug', msg=self.decorateLogs(msg), printStack=False) + def logDebug(self, msg: str, plural: Union[list, str] = None): + self._logger.doLog(function='debug', msg=self.decorateLogs(msg), printStack=False, plural=plural) - def logFatal(self, msg: str): - self._logger.doLog(function='fatal', msg=self.decorateLogs(msg)) + def logFatal(self, msg: str, plural: Union[list, str] = None): + self._logger.doLog(function='fatal', msg=self.decorateLogs(msg), plural=plural) try: self.ProjectAlice.onStop() - exit() except: exit() - def logWarning(self, msg: str, printStack: bool = False): - self._logger.doLog(function='warning', msg=self.decorateLogs(msg), printStack=printStack) + def logWarning(self, msg: str, printStack: bool = False, plural: Union[list, str] = None): + self._logger.doLog(function='warning', msg=self.decorateLogs(msg), printStack=printStack, plural=plural) - def logCritical(self, msg: str): - self._logger.doLog(function='critical', msg=self.decorateLogs(msg)) + def logCritical(self, msg: str, plural: Union[list, str] = None): + self._logger.doLog(function='critical', msg=self.decorateLogs(msg), plural=plural) def decorateLogs(self, text: str) -> str: @@ -261,62 +260,62 @@ def ProjectAlice(self) -> ProjectAlice: #NOSONAR @property def ConfigManager(self) -> ConfigManager: #NOSONAR - return SM.SuperManager.getInstance().configManager + return SM.SuperManager.getInstance().ConfigManager @property def MqttManager(self) -> MqttManager: #NOSONAR - return SM.SuperManager.getInstance().mqttManager + return SM.SuperManager.getInstance().MqttManager @property def DatabaseManager(self) -> DatabaseManager: #NOSONAR - return SM.SuperManager.getInstance().databaseManager + return SM.SuperManager.getInstance().DatabaseManager @property def ThreadManager(self) -> ThreadManager: #NOSONAR - return SM.SuperManager.getInstance().threadManager + return SM.SuperManager.getInstance().ThreadManager @property def TimeManager(self) -> TimeManager: #NOSONAR - return SM.SuperManager.getInstance().timeManager + return SM.SuperManager.getInstance().TimeManager @property def HotwordManager(self) -> HotwordManager: #NOSONAR - return SM.SuperManager.getInstance().hotwordManager + return SM.SuperManager.getInstance().HotwordManager @property def Commons(self) -> CommonsManager: #NOSONAR - return SM.SuperManager.getInstance().commonsManager + return SM.SuperManager.getInstance().CommonsManager @property def NetworkManager(self) -> NetworkManager: #NOSONAR - return SM.SuperManager.getInstance().networkManager + return SM.SuperManager.getInstance().NetworkManager @property def SkillManager(self) -> SkillManager: #NOSONAR - return SM.SuperManager.getInstance().skillManager + return SM.SuperManager.getInstance().SkillManager @property def InternetManager(self) -> InternetManager: #NOSONAR - return SM.SuperManager.getInstance().internetManager + return SM.SuperManager.getInstance().InternetManager @property def WakewordManager(self) -> WakewordManager: #NOSONAR - return SM.SuperManager.getInstance().wakewordManager + return SM.SuperManager.getInstance().WakewordManager @property def AudioServer(self) -> AudioManager: #NOSONAR - return SM.SuperManager.getInstance().audioManager + return SM.SuperManager.getInstance().AudioManager @property def SubprocessManager(self) -> SubprocessManager: # NOSONAR - return SM.SuperManager.getInstance().subprocessManager + return SM.SuperManager.getInstance().SubprocessManager diff --git a/core/commons/CommonsManager.py b/core/commons/CommonsManager.py index 6953f5f..2c33e48 100644 --- a/core/commons/CommonsManager.py +++ b/core/commons/CommonsManager.py @@ -2,16 +2,17 @@ import inspect import json import random -import requests import socket import string import subprocess from contextlib import contextmanager from ctypes import * -from paho.mqtt.client import MQTTMessage from pathlib import Path from typing import Any +import requests +from paho.mqtt.client import MQTTMessage + from core.base.model.Manager import Manager @@ -70,7 +71,7 @@ def parseSiteId(cls, message: MQTTMessage) -> str: else: from core.base.SuperManager import SuperManager - return data.get('IPAddress', SuperManager.getInstance().configManager.getAliceConfigByName('uuid')) + return data.get('IPAddress', SuperManager.getInstance().ConfigManager.getAliceConfigByName('uuid')) @staticmethod diff --git a/core/util/Decorators.py b/core/util/Decorators.py index ebae780..6c65d37 100644 --- a/core/util/Decorators.py +++ b/core/util/Decorators.py @@ -64,7 +64,7 @@ def exampleIntent(self, session: DialogSession, **_kwargs): def argumentWrapper(func): @functools.wraps(func) def offlineDecorator(*args, **kwargs): - internetManager = SuperManager.getInstance().internetManager + internetManager = SuperManager.getInstance().InternetManager if internetManager.online: try: return func(*args, **kwargs) @@ -104,7 +104,7 @@ def settingDecorator(*args, **kwargs): Logger(prepend='[Decorator]').logWarning(msg='Cannot use IfSetting decorator without settingName') return None - configManager = SuperManager.getInstance().configManager + configManager = SuperManager.getInstance().ConfigManager value = configManager.getSkillConfigByName(skillName, settingName) if skillName else configManager.getAliceConfigByName(settingName) if value is None: diff --git a/core/util/SubprocessManager.py b/core/util/SubprocessManager.py index 3d1f889..8895dc0 100644 --- a/core/util/SubprocessManager.py +++ b/core/util/SubprocessManager.py @@ -40,7 +40,7 @@ def onStart(self): if self._thread and self._thread.is_alive(): self._thread.join(timeout=5) - self._thread: threading.Thread = self.ThreadManager.newThread(name='subprocessManager', target=self.run, autostart=False) + self._thread: threading.Thread = self.ThreadManager.newThread(name='SubprocessManager', target=self.run, autostart=False) self._thread.start() @@ -53,7 +53,7 @@ def onStop(self): self._flag.clear() if self._thread and self._thread.is_alive(): - self.ThreadManager.terminateThread(name='subprocessManager') + self.ThreadManager.terminateThread(name='SubprocessManager') def isSubprocessAlive(self, name: str) -> bool: diff --git a/core/util/ThreadManager.py b/core/util/ThreadManager.py index 4136022..863be8e 100644 --- a/core/util/ThreadManager.py +++ b/core/util/ThreadManager.py @@ -1,5 +1,24 @@ +# Copyright (c) 2021 +# +# This file, ThreadManager.py, is part of Project Alice. +# +# Project Alice is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see +# +# Last modified: 2021.04.13 at 12:56:48 CEST + import threading -from typing import Callable +from typing import Callable, Union from core.base.model.Manager import Manager from core.util.model.AliceEvent import AliceEvent @@ -27,17 +46,30 @@ def onStop(self): thread.join(timeout=1) for event in self._events.values(): - if event.isSet(): + if event.is_set(): event.clear() def onQuarterHour(self): deadTimers = 0 - for threadTimer in self._timers: + deadThreads = 0 + timers = self._timers.copy() + for threadTimer in timers: if not threadTimer.timer.isAlive(): self._timers.remove(threadTimer) deadTimers += 1 - self.logInfo(f'Cleaned {deadTimers} dead timers') + + threads = self._threads.copy() + for threadName, thread in threads.items(): + if not thread.is_alive(): + self._threads.pop(threadName, None) + deadThreads += 1 + + if deadTimers > 0: + self.logInfo(f'Cleaned {deadTimers} dead timer', 'timer') + + if deadThreads > 0: + self.logInfo(f'Cleaned {deadThreads} dead thread', 'thread') def newTimer(self, interval: float, func: Callable, autoStart: bool = True, args: list = None, kwargs: dict = None) -> threading.Timer: @@ -45,15 +77,15 @@ def newTimer(self, interval: float, func: Callable, autoStart: bool = True, args kwargs = kwargs or dict() threadTimer = ThreadTimer(callback=func, args=args, kwargs=kwargs) - thread = threading.Timer(interval=interval, function=self.onTimerEnd, args=[threadTimer]) - thread.daemon = True - threadTimer.timer = thread + timer = threading.Timer(interval=interval, function=self.onTimerEnd, args=[threadTimer]) + timer.daemon = True + threadTimer.timer = timer self._timers.append(threadTimer) if autoStart: - thread.start() + timer.start() - return thread + return timer def doLater(self, interval: float, func: Callable, args: list = None, kwargs: dict = None): @@ -84,7 +116,10 @@ def newThread(self, name: str, target: Callable, autostart: bool = True, args: l kwargs = kwargs or dict() if name in self._threads: - self._threads[name].join(timeout=2) + try: + self._threads[name].join(timeout=2) + except: + pass # Might be a non started thread only thread = threading.Thread(name=name, target=target, args=args, kwargs=kwargs) thread.setDaemon(True) @@ -93,6 +128,7 @@ def newThread(self, name: str, target: Callable, autostart: bool = True, args: l thread.start() self._threads[name] = thread + self.logDebug(f'Started new thread **{name}**, thread count: {threading.active_count()}') return thread @@ -107,7 +143,9 @@ def terminateThread(self, name: str): if thread and thread.is_alive(): thread.join(timeout=1) except Exception as e: - self.logDebug(f'Error terminating thread "{name}": {e}') + self.logError(f'Error terminating thread "{name}": {e}') + + self.logDebug(f'Terminated thread **{name}**, thread count: {threading.active_count()}') def isThreadAlive(self, name: str) -> bool: @@ -117,7 +155,7 @@ def isThreadAlive(self, name: str) -> bool: return self._threads[name].isAlive() - def newEvent(self, name: str, onSetCallback: str = None, onClearCallback: str = None) -> AliceEvent: + def newEvent(self, name: str, onSetCallback: Union[str, Callable] = None, onClearCallback: Union[str, Callable] = None) -> AliceEvent: if name in self._events: self._events[name].clear() diff --git a/core/util/model/Logger.py b/core/util/model/Logger.py index 966bd02..295e794 100644 --- a/core/util/model/Logger.py +++ b/core/util/model/Logger.py @@ -1,32 +1,49 @@ -import logging -import traceback +# Copyright (c) 2021 +# +# This file, Logger.py, is part of Project Alice. +# +# Project Alice is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see +# +# Last modified: 2021.04.13 at 12:56:48 CEST +import logging import re +import traceback +from typing import Match, Union -class Logger: +class Logger(object): - def __init__(self, prepend: str = None): + def __init__(self, prepend: str = None, **_kwargs): self._prepend = prepend self._logger = logging.getLogger('ProjectAlice') - def logInfo(self, msg: str): - self.doLog(function='info', msg=msg, printStack=False) + def logInfo(self, msg: str, plural: Union[list, str] = None): + self.doLog(function='info', msg=msg, printStack=False, plural=plural) - def logError(self, msg: str): - self.doLog(function='error', msg=msg) - self.printTraceback() + def logError(self, msg: str, plural: Union[list, str] = None): + self.doLog(function='error', msg=msg, plural=plural) - def logDebug(self, msg: str): - self.doLog(function='debug', msg=msg, printStack=False) + def logDebug(self, msg: str, plural: Union[list, str] = None): + self.doLog(function='debug', msg=msg, printStack=False, plural=plural) - def logFatal(self, msg: str): - self.doLog(function='fatal', msg=msg) - self.printTraceback() + def logFatal(self, msg: str, plural: Union[list, str] = None): + self.doLog(function='fatal', msg=msg, plural=plural) try: from core.base.SuperManager import SuperManager @@ -35,37 +52,60 @@ def logFatal(self, msg: str): exit() - def logWarning(self, msg: str, printStack: bool = False): - self.doLog(function='warning', msg=msg, printStack=printStack) - self.printTraceback() + def logWarning(self, msg: str, printStack: bool = False, plural: Union[list, str] = None): + from core.base.SuperManager import SuperManager + + if SuperManager.getInstance().ConfigManager.getAliceConfigByName('debug'): + self.doLog(function='warning', msg=msg, printStack=True, plural=plural) + else: + self.doLog(function='warning', msg=msg, printStack=printStack, plural=plural) + + def logCritical(self, msg: str, plural: Union[list, str] = None): + self.doLog(function='critical', msg=msg, plural=plural) - def logCritical(self, msg: str): - self.doLog(function='critical', msg=msg) - self.printTraceback() + def doLog(self, function: str, msg: str, printStack=True, plural: Union[list, str] = None): + if not msg: + return + + if plural: + msg = self.doPlural(string=msg, word=plural) - def doLog(self, function: callable, msg: str, printStack = True): if self._prepend: msg = f'{self._prepend} {msg}' + elif not msg.startswith('['): + msg = f'[Project Alice Logger] {msg}' - match = re.match(r'^(\[[\w ]+\])(.*)$', msg) + match = re.match(r'^(\[[\w ]+])(.*)$', msg) if match: tag, log = match.groups() - space = ''.join([' ' for _ in range(25 - len(tag))]) + space = ''.join([' ' for _ in range(35 - len(tag))]) msg = f'{tag}{space}{log}' func = getattr(self._logger, function) - func(msg, exc_info=printStack) + func(msg) + if printStack: + for line in traceback.format_exc().split('\n'): + if not line.strip(): + continue + self.doLog(function=function, msg=f'[Traceback] {line}', printStack=False) @staticmethod - def printTraceback(): - from core.base.SuperManager import SuperManager + def doPlural(string: str, word: Union[list, str]) -> str: + def plural(match: Match) -> str: + matched = match.group() + if int(match.group(1)) > 1: + return matched + 's' + return matched - try: - if SuperManager.getInstance().configManager.getAliceConfigByName('debug'): - traceback.print_exc() - except: - # Would mean that warning was triggered before configManager was even loaded - pass + + words = word + if isinstance(word, str): + words = [word] + + for word in words: + string = re.sub(r'([\d]+)[* ]+?({})'.format(word), plural, string) + + return string diff --git a/core/voice/WakewordManager.py b/core/voice/WakewordManager.py index 1040437..0ef02e3 100644 --- a/core/voice/WakewordManager.py +++ b/core/voice/WakewordManager.py @@ -19,6 +19,7 @@ def onStart(self): def onStop(self): + super().onStop() if self._engine: self._engine.onStop() diff --git a/requirements.txt b/requirements.txt index 730d236..05ac356 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,4 +5,5 @@ paho-mqtt==1.6.1 PyAudio==0.2.11 webrtcvad==2.0.10 importlib_metadata==4.8.2 -sounddevice==0.4.3 \ No newline at end of file +sounddevice==0.4.3 +alicegit~=0.0.37 \ No newline at end of file