class WakewordUploadThread(Thread): def __init__(self, host: str, port: int, zipPath: str): super().__init__() self._logger = Logger(prepend='[HotwordUploadThread]') self.setDaemon(True) self._host = host self._port = port self._zipPath = Path(zipPath) def run(self): try: wakewordName = self._zipPath.stem with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: sock.bind((self._host, self._port)) self._logger.logInfo('Waiting for a device to connect') sock.listen() conn, addr = sock.accept() self._logger.logInfo( f'New device connected with address **{addr}**') with self._zipPath.open(mode='rb') as f: data = f.read(1024) while data: conn.send(data) data = f.read(1024) self._logger.logInfo( f'Waiting on a feedback from **{addr[0]}**') conn.settimeout(20) try: while True: answer = conn.recv(1024).decode() if not answer: raise Exception( 'The device closed the connection before confirming...' ) if answer == '0': self._logger.logInfo( f'Wakeword **{wakewordName}** upload to **{addr[0]}** success' ) break elif answer == '-1': raise Exception( 'The device failed downloading the hotword') elif answer == '-2': raise Exception( 'The device failed installing the hotword') except timeout: self._logger.logWarning( 'The device did not confirm the operation as successfull in time. The hotword installation might have failed' ) except Exception as e: self._logger.logError(f'Error uploading wakeword: {e}')
class ProjectAlice(Singleton): NAME = 'ProjectAlice' def __init__(self, restartHandler: callable): Singleton.__init__(self, self.NAME) self._logger = Logger(prepend='[Project Alice]') self._logger.logInfo('Starting Alice main unit') self._booted = False self._isUpdating = False self._shuttingDown = False self._restart = False self._restartHandler = restartHandler if not self.checkDependencies(): self._restart = True self._restartHandler() else: with Stopwatch() as stopWatch: self._superManager = SuperManager(self) self._superManager.initManagers() self._superManager.onStart() if self._superManager.configManager.getAliceConfigByName( 'useHLC'): self._superManager.commons.runRootSystemCommand( ['systemctl', 'start', 'hermesledcontrol']) self._superManager.onBooted() self._logger.logInfo(f'Started in {stopWatch} seconds') self._booted = True def checkDependencies(self) -> bool: """ Compares .hash files against requirements.txt and sysrequirement.txt. Updates dependencies if necessary :return: boolean False if the check failed, new deps were installed (reboot maybe? :) ) """ HASH_SUFFIX = '.hash' TXT_SUFFIX = '.txt' path = Path('requirements') savedHash = path.with_suffix(HASH_SUFFIX) reqHash = hashlib.blake2b( path.with_suffix(TXT_SUFFIX).read_bytes()).hexdigest() if not savedHash.exists() or savedHash.read_text() != reqHash: self._logger.logInfo( 'Pip dependencies added or removed, updating virtual environment' ) subprocess.run([ './venv/bin/pip', 'install', '-r', str(path.with_suffix(TXT_SUFFIX)) ]) savedHash.write_text(reqHash) return False path = Path('sysrequirements') savedHash = path.with_suffix(HASH_SUFFIX) reqHash = hashlib.blake2b( path.with_suffix(TXT_SUFFIX).read_bytes()).hexdigest() if not savedHash.exists() or savedHash.read_text() != reqHash: self._logger.logInfo( 'System dependencies added or removed, updating system') reqs = [ line.rstrip('\n') for line in open(path.with_suffix(TXT_SUFFIX)) ] subprocess.run([ 'sudo', 'apt-get', 'install', '-y', '--allow-unauthenticated' ] + reqs) savedHash.write_text(reqHash) return False path = Path('pipuninstalls') savedHash = path.with_suffix(HASH_SUFFIX) reqHash = hashlib.blake2b( path.with_suffix(TXT_SUFFIX).read_bytes()).hexdigest() if not savedHash.exists() or savedHash.read_text() != reqHash: self._logger.logInfo( 'Pip conflicting dependencies added, updating virtual environment' ) subprocess.run([ './venv/bin/pip', 'uninstall', '-y', '-r', str(path.with_suffix(TXT_SUFFIX)) ]) savedHash.write_text(reqHash) return False return True @property def name(self) -> str: # NOSONAR return self.NAME @property def isBooted(self) -> bool: return self._booted @property def restart(self) -> bool: return self._restart @restart.setter def restart(self, value: bool): self._restart = value def doRestart(self): self._restart = True self.onStop() 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']) self._booted = False self.INSTANCE = None if withReboot: subprocess.run(['sudo', 'shutdown', '-r', 'now']) else: self._restartHandler() def wipeAll(self): # Set as restarting so skills don't install / update self._restart = True self._superManager.skillManager.wipeSkills() self._superManager.databaseManager.clearDB() self._superManager.assistantManager.clearAssistant() self._superManager.dialogTemplateManager.clearCache(False) self._superManager.nluManager.clearCache() def updateProjectAlice(self): self._logger.logInfo('Checking for core updates') STATE = 'projectalice.core.updating' state = self._superManager.stateManager.getState(STATE) if not state: self._superManager.stateManager.register( STATE, initialState=StateType.RUNNING) elif state.currentState == StateType.RUNNING: self._logger.logInfo('Update cancelled, already running') return self._superManager.stateManager.setState(STATE, newState=StateType.RUNNING) self._isUpdating = True req = requests.get( url=f'{constants.GITHUB_API_URL}/ProjectAlice/branches', auth=SuperManager.getInstance().configManager.githubAuth) if req.status_code != 200: self._logger.logWarning('Failed checking for updates') self._superManager.stateManager.setState(STATE, newState=StateType.ERROR) return userUpdatePref = SuperManager.getInstance( ).configManager.getAliceConfigByName('aliceUpdateChannel') if userUpdatePref == 'master': candidate = 'master' else: candidate = Version.fromString(constants.VERSION) for branch in req.json(): if 'dependabot' in branch['name']: continue repoVersion = Version.fromString(branch['name']) if not repoVersion.isVersionNumber: continue releaseType = repoVersion.releaseType if userUpdatePref == 'rc' and releaseType in { 'b', 'a' } or userUpdatePref == 'beta' and releaseType == 'a': continue if repoVersion > candidate: candidate = repoVersion self._logger.logInfo(f'Checking on "{str(candidate)}" update channel') commons = SuperManager.getInstance().commons currentHash = subprocess.check_output( ['git', 'rev-parse', '--short HEAD']) commons.runSystemCommand(['git', '-C', commons.rootDir(), 'stash']) commons.runSystemCommand( ['git', '-C', commons.rootDir(), 'clean', '-df']) commons.runSystemCommand( ['git', '-C', commons.rootDir(), 'checkout', str(candidate)]) commons.runSystemCommand(['git', '-C', commons.rootDir(), 'pull']) commons.runSystemCommand( ['git', '-C', commons.rootDir(), 'submodule', 'init']) commons.runSystemCommand( ['git', '-C', commons.rootDir(), 'submodule', 'update']) commons.runSystemCommand([ 'git', '-C', commons.rootDir(), 'submodule', 'foreach', 'git', 'checkout', f'builds_{str(candidate)}' ]) commons.runSystemCommand([ 'git', '-C', commons.rootDir(), 'submodule', 'foreach', 'git', 'pull' ]) newHash = subprocess.check_output(['git', 'rev-parse', '--short HEAD']) # Remove install tickets [ file.unlink() for file in Path( commons.rootDir(), 'system/skillInstallTickets').glob('*') if file.is_file() ] self._superManager.stateManager.setState(STATE, newState=StateType.FINISHED) if currentHash != newHash: self._logger.logWarning( 'New Alice version installed, need to restart...') self._superManager.webUINotificationManager.newNotification( typ=UINotificationType.INFO, notification='aliceUpdated') self.doRestart() self._logger.logInfo('Update checks completed.') self._isUpdating = False @property def updating(self) -> bool: return self._isUpdating @property def shuttingDown(self) -> bool: return self._shuttingDown
class ProjectAlice(Singleton): NAME = 'ProjectAlice' def __init__(self, restartHandler: callable): Singleton.__init__(self, self.NAME) self._logger = Logger() self._logger.logInfo('Starting up Project Alice') self._booted = False with Stopwatch() as stopWatch: self._restart = False self._restartHandler = restartHandler self._superManager = SuperManager(self) self._superManager.initManagers() self._superManager.onStart() if self._superManager.configManager.getAliceConfigByName('useHLC'): self._superManager.commons.runRootSystemCommand( ['systemctl', 'start', 'hermesledcontrol']) self._superManager.onBooted() self._logger.logInfo(f'- Started Project Alice in {stopWatch} seconds') self._booted = True @property def name(self) -> str: return self.NAME @property def isBooted(self) -> bool: return self._booted @property def restart(self) -> bool: return self._restart @restart.setter def restart(self, value: bool): self._restart = value def doRestart(self): self._restart = True self.onStop() def onStop(self): self._logger.logInfo('Shutting down Project Alice') self._superManager.onStop() if self._superManager.configManager.getAliceConfigByName('useHLC'): self._superManager.commons.runRootSystemCommand( ['systemctl', 'stop', 'hermesledcontrol']) self.INSTANCE = None self._restartHandler() def wipeAll(self): # Set as restarting so skills don't install / update self._restart = True self._superManager.skillManager.wipeSkills() self._superManager.databaseManager.clearDB() self._superManager.dialogTemplateManager.clearCache(False) self._superManager.nluManager.clearCache() self._superManager.snipsAssistantManager.clearData() def updateProjectAlice(self): self._logger.logInfo('Checking Project Alice updates') req = requests.get( url=f'{constants.GITHUB_API_URL}/ProjectAlice/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') if userUpdatePref == 'master': candidate = 'master' else: candidate = Version.fromString(constants.VERSION) for branch in req.json(): repoVersion = Version.fromString(branch['name']) if not repoVersion.isVersionNumber: continue releaseType = repoVersion.releaseType if userUpdatePref == 'rc' and releaseType in { 'b', 'a' } or userUpdatePref == 'beta' and releaseType == 'a': continue if repoVersion > candidate: candidate = repoVersion self._logger.logInfo(f'Checking on "{str(candidate)}" update channel') commons = SuperManager.getInstance().commons commons.runSystemCommand(['git', '-C', commons.rootDir(), 'stash']) commons.runSystemCommand( ['git', '-C', commons.rootDir(), 'clean', '-df']) commons.runSystemCommand( ['git', '-C', commons.rootDir(), 'checkout', str(candidate)]) commons.runSystemCommand(['git', '-C', commons.rootDir(), 'pull']) # Remove install tickets [ file.unlink() for file in Path( commons.rootDir(), 'system/skillInstallTickets').glob('*') if file.is_file() ]
class HotwordDownloadThread(Thread): def __init__(self, host: str, port: int, hotwordName: str): super().__init__() self._logger = Logger(prepend='[HotwordDownloadThread]') self._host = host self._port = int(port) self._hotwordName = hotwordName self.setDaemon(True) def run(self): sock = None try: self._logger.logInfo('Cleaning up') rootPath = Path(SuperManager.getInstance().commons.rootDir(), 'hotwords') hotwordPath = rootPath / f'{self._hotwordName}' zipPath = hotwordPath.with_suffix('.zip') if zipPath.exists(): zipPath.unlink() if hotwordPath.exists(): shutil.rmtree(hotwordPath, ignore_errors=True) self._logger.logInfo( f'Connecting to **{self._host}_{self._port}**') sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect((self._host, self._port)) self._logger.logInfo( f'Receiving hotword package: **{self._hotwordName}**') sock.settimeout(2) try: with zipPath.open('wb') as f: while True: data = sock.recv(1024) if not data: break f.write(data) except socket.timeout: sock.settimeout(None) except Exception as e: self._logger.logError(f'Error downloading hotword: {e}') if sock: sock.send(b'-1') sock.close() return try: self._logger.logInfo('New hotword received, unpacking...') shutil.unpack_archive(filename=zipPath, extract_dir=hotwordPath) conf = SuperManager.getInstance( ).configManager.getSnipsConfiguration(parent='snips-hotword', key='model', createIfNotExist=True) if not isinstance(conf, list): conf = list() addSnips = True regex = re.compile(f'.*/{hotwordPath}=[0-9.]+$') copy = conf.copy() for i, hotword in enumerate(copy): if hotword.find('/snips_hotword='): addSnips = False elif regex.match(hotword): conf.pop(i) if addSnips: conf.append(str(rootPath / 'snips_hotword=0.53')) conf.append(f'{hotwordPath}=0.52') SuperManager.getInstance().configManager.updateSnipsConfiguration( parent='snips-hotword', key='model', value=conf, createIfNotExist=True) subprocess.run(['sudo', 'systemctl', 'restart', 'snips-satellite']) sock.send(b'0') self._logger.logInfo( f'Sucessfully installed new hotword **{self._hotwordName}**') except Exception as e: self._logger.logError( f'Error while unpacking and installing hotword: {e}') sock.send(b'-2') finally: sock.close() os.remove(zipPath)
class ProjectAlice(Singleton): NAME = 'ProjectAlice' def __init__(self, restartHandler: callable): Singleton.__init__(self, self.NAME) self._logger = Logger(prepend='[Project Alice]') self._logger.logInfo('Starting Alice satellite unit') self._booted = False with Stopwatch() as stopWatch: self._restart = False self._restartHandler = restartHandler self._superManager = SuperManager(self) self._superManager.initManagers() self._superManager.onStart() if self._superManager.configManager.getAliceConfigByName('useHLC'): self._superManager.commons.runRootSystemCommand(['systemctl', 'start', 'hermesledcontrol']) self._superManager.onBooted() self._logger.logInfo(f'Started in {stopWatch} seconds') self._booted = True @property def name(self) -> str: return self.NAME @property def isBooted(self) -> bool: return self._booted @property def restart(self) -> bool: return self._restart @restart.setter def restart(self, value: bool): self._restart = value def doRestart(self): self._restart = True self.onStop() def onStop(self): self._logger.logInfo('Shutting down') self._superManager.onStop() if self._superManager.configManager.getAliceConfigByName('useHLC'): self._superManager.commons.runRootSystemCommand(['systemctl', 'stop', 'hermesledcontrol']) self.INSTANCE = None self._restartHandler() def updateProjectAlice(self): self._logger.logInfo('Checking for satellite updates') 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') if userUpdatePref == 'master': candidate = 'master' else: candidate = Version.fromString(constants.VERSION) for branch in req.json(): repoVersion = Version.fromString(branch['name']) if not repoVersion.isVersionNumber: continue releaseType = repoVersion.releaseType if userUpdatePref == 'rc' and releaseType in {'b', 'a'} or userUpdatePref == 'beta' and releaseType == 'a': continue if repoVersion > candidate: candidate = repoVersion self._logger.logInfo(f'Checking on "{str(candidate)}" update channel') commons = SuperManager.getInstance().commons commons.runSystemCommand(['git', '-C', commons.rootDir(), 'stash']) commons.runSystemCommand(['git', '-C', commons.rootDir(), 'clean', '-df']) commons.runSystemCommand(['git', '-C', commons.rootDir(), 'checkout', str(candidate)]) commons.runSystemCommand(['git', '-C', commons.rootDir(), 'pull'])