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}')
Example #2
0
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
Example #3
0
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'])