def _getSkillUpdateVersion(self, skillName: str) -> Optional[tuple]:
        versionMapping = self._skillStoreData.get(skillName, dict()).get(
            'versionMapping', dict())

        userUpdatePref = self.ConfigManager.getAliceConfigByName(
            'skillsUpdateChannel')
        skillUpdateVersion = (Version(), 'master')

        aliceVersion = Version.fromString(constants.VERSION)
        for aliceMinVersion, repoVersion in versionMapping.items():
            aliceMinVersion = Version.fromString(aliceMinVersion)
            repoVersion = Version.fromString(repoVersion)

            if not repoVersion.isVersionNumber or not aliceMinVersion.isVersionNumber or aliceMinVersion > aliceVersion:
                continue

            releaseType = repoVersion.releaseType
            if userUpdatePref == 'master' and releaseType in {'rc', 'b', 'a'} \
              or userUpdatePref == 'rc' and releaseType in {'b', 'a'} \
              or userUpdatePref == 'beta' and releaseType == 'a':
                continue

            if repoVersion > skillUpdateVersion[0]:
                skillUpdateVersion = (
                    repoVersion, f'{str(repoVersion)}_{str(aliceMinVersion)}')

        if not skillUpdateVersion[0].isVersionNumber:
            raise GithubNotFound

        return skillUpdateVersion
	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'])
Exemple #3
0
    def loadStoreData(self):
        self.SkillStoreManager.refreshStoreData()
        skillStoreData = self.SkillStoreManager.skillStoreData
        activeLanguage = self.LanguageManager.activeLanguage.lower()
        aliceVersion = Version.fromString(constants.VERSION)
        supportedSkillStoreData = dict()

        for skillName, skillInfo in skillStoreData.items():
            if self.SkillManager.getSkillInstance(skillName=skillName, silent=True) \
              or ('lang' in skillInfo['conditions'] and activeLanguage not in skillInfo['conditions']['lang']):
                continue

            version = Version()
            for aliceMinVersion, skillVersion in skillInfo[
                    'versionMapping'].items():
                if Version.fromString(aliceMinVersion) > aliceVersion:
                    continue

                skillRepoVersion = Version.fromString(skillVersion)

                if skillRepoVersion > version:
                    version = skillRepoVersion

            skillInfo['version'] = str(version)
            supportedSkillStoreData[skillName] = skillInfo

        return supportedSkillStoreData
Exemple #4
0
    def checkDependencies(self) -> bool:
        self.logInfo('Checking dependencies')

        for dep in {
                **self.DEPENDENCIES.get('internal', dict()),
                **self.DEPENDENCIES.get('external', dict())
        }:
            result = self.Commons.runRootSystemCommand(
                ['dpkg-query', '-l', dep])
            if result.returncode:
                self.logWarning(f'Found missing dependency: {dep}')
                return False

        for dep in self.DEPENDENCIES['pip']:
            match = re.match(r'^([a-zA-Z0-9-_]*)(?:([=><]{0,2})([\d.]*)$)',
                             dep)
            if not match:
                continue

            packageName, operator, version = match.groups()
            if not packageName:
                self.logWarning('Wrongly declared PIP requirement')
                continue

            try:
                installedVersion = packageVersion(packageName)
            except PackageNotFoundError:
                self.logWarning(f'Found missing dependencies: {packageName}')
                return False

            if not installedVersion or not operator or not version:
                continue

            version = Version.fromString(version)
            installedVersion = Version.fromString(installedVersion)

            if (operator == '==' and version != installedVersion) or \
              (operator == '>=' and installedVersion < version) or \
              (operator == '>' and (installedVersion < version or installedVersion == version)) or \
              (operator == '<' and (installedVersion > version or installedVersion == version)):

                self.logWarning(
                    f'Dependency "{packageName}" is not conform with version requirements'
                )
                return False

        for dep in self.DEPENDENCIES['system']:
            result = self.Commons.runRootSystemCommand(
                ['dpkg-query', '-l', dep])
            if result.returncode:
                self.logWarning(f'Found missing dependency: {dep}')
                return False

        return True
Exemple #5
0
	def getSkillScenarioVersion(self, skillName: str) -> Version:
		if skillName not in self._skillList:
			return Version.fromString('0.0.0')
		else:
			# noinspection SqlResolve
			query = 'SELECT * FROM :__table__ WHERE skillName = :skillName'
			data = self.DatabaseManager.fetch(tableName='skills', query=query, values={'skillName': skillName}, callerName=self.name)
			if not data:
				return Version.fromString('0.0.0')

			return Version.fromString(data[0]['scenarioVersion'])
Exemple #6
0
	def checkSkillConditions(self, installer: dict = None) -> bool:
		conditions = {
			'aliceMinVersion': installer['aliceMinVersion'],
			**installer.get('conditions', dict())
		}

		notCompliant = 'Skill is not compliant'

		if 'aliceMinVersion' in conditions and \
				Version.fromString(conditions['aliceMinVersion']) > Version.fromString(constants.VERSION):
			raise SkillNotConditionCompliant(message=notCompliant, skillName=installer['name'], condition='Alice minimum version', conditionValue=conditions['aliceMinVersion'])

		for conditionName, conditionValue in conditions.items():
			if conditionName == 'lang' and self.LanguageManager.activeLanguage not in conditionValue:
				raise SkillNotConditionCompliant(message=notCompliant, skillName=installer['name'], condition=conditionName, conditionValue=conditionValue)

			elif conditionName == 'online':
				if conditionValue and self.ConfigManager.getAliceConfigByName('stayCompletelyOffline') \
						or not conditionValue and not self.ConfigManager.getAliceConfigByName('stayCompletelyOffline'):
					raise SkillNotConditionCompliant(message=notCompliant, skillName=installer['name'], condition=conditionName, conditionValue=conditionValue)

			elif conditionName == 'skill':
				for requiredSkill in conditionValue:
					if requiredSkill in self._skillList and not self._skillList[requiredSkill]['active']:
						raise SkillNotConditionCompliant(message=notCompliant, skillName=installer['name'], condition=conditionName, conditionValue=conditionValue)
					elif requiredSkill not in self._skillList:
						self.logInfo(f'Skill {installer["name"]} has another skill as dependency, adding download')
						if not self.downloadInstallTicket(requiredSkill):
							raise SkillNotConditionCompliant(message=notCompliant, skillName=installer['name'], condition=conditionName, conditionValue=conditionValue)

			elif conditionName == 'notSkill':
				for excludedSkill in conditionValue:
					author, name = excludedSkill.split('/')
					if name in self._skillList and self._skillList[name]['active']:
						raise SkillNotConditionCompliant(message=notCompliant, skillName=installer['name'], condition=conditionName, conditionValue=conditionValue)

			elif conditionName == 'asrArbitraryCapture':
				if conditionValue and not self.ASRManager.asr.capableOfArbitraryCapture:
					raise SkillNotConditionCompliant(message=notCompliant, skillName=installer['name'], condition=conditionName, conditionValue=conditionValue)

			elif conditionName == 'activeManager':
				for manager in conditionValue:
					if not manager:
						continue

					man = SuperManager.getInstance().getManager(manager)
					if not man or not man.isActive:
						raise SkillNotConditionCompliant(message=notCompliant, skillName=installer['name'], condition=conditionName, conditionValue=conditionValue)

		return True
Exemple #7
0
	def getUpdateSource(self, definedSource: str) -> str:
		updateSource = 'master'
		if definedSource in {'master', 'release'}:
			return updateSource

		try:
			import requests
		except:
			self.setServiceFileTo('system')
			subprocess.run(['sudo', 'systemctl', 'restart', 'ProjectAlice'])
			self.informUser()
			exit(0)

		# noinspection PyUnboundLocalVariable
		req = requests.get('https://api.github.com/repos/project-alice-assistant/ProjectAlice/branches')
		result = req.json()

		versions = list()
		from core.base.model.Version import Version
		for branch in result:
			repoVersion = Version.fromString(branch['name'])

			releaseType = repoVersion.releaseType
			if not repoVersion.isVersionNumber \
					or definedSource == 'rc' and releaseType in {'b', 'a'} \
					or definedSource == 'beta' and releaseType == 'a':
				continue

			versions.append(repoVersion)

		if versions:
			versions.sort(reverse=True)
			updateSource = versions[0]

		return str(updateSource)
Exemple #8
0
    def __init__(self,
                 supportedIntents: Iterable = None,
                 databaseSchema: dict = None,
                 **kwargs):
        super().__init__(**kwargs)
        try:
            self._skillPath = Path(inspect.getfile(self.__class__)).parent
            self._installFile = Path(inspect.getfile(
                self.__class__)).with_suffix('.install')
            self._installer = json.loads(self._installFile.read_text())
        except FileNotFoundError:
            raise SkillStartingFailed(
                skillName=constants.UNKNOWN,
                error=f'[{type(self).__name__}] Cannot find install file')
        except Exception as e:
            raise SkillStartingFailed(
                skillName=constants.UNKNOWN,
                error=f'[{type(self).__name__}] Failed loading skill: {e}')

        instructionsFile = self.getResource(
            f'instructions/{self.LanguageManager.activeLanguage}.md')
        if not instructionsFile.exists():
            instructionsFile = self.getResource(f'instructions/en.md')

        self._instructions = instructionsFile.read_text(
        ) if instructionsFile.exists() else ''

        self._name = self._installer['name']
        self._author = self._installer.get('author', constants.UNKNOWN)
        self._version = self._installer.get('version', '0.0.1')
        self._icon = self._installer.get('icon', 'fas fa-biohazard')
        self._aliceMinVersion = Version.fromString(
            self._installer.get('aliceMinVersion', '1.0.0-b4'))
        self._maintainers = self._installer.get('maintainers', list())
        self._description = self._installer.get('desc', '')
        self._category = self._installer.get('category', constants.UNKNOWN)
        self._conditions = self._installer.get('conditions', dict())
        self._updateAvailable = False
        self._modified = False
        self._active = False
        self._delayed = False
        self._required = False
        self._failedStarting = False
        self._databaseSchema = databaseSchema
        self._widgets = list()
        self._widgetTemplates = dict()
        self._deviceTypes = list()
        self._intentsDefinitions = dict()
        self._scenarioPackageName = ''
        self._scenarioPackageVersion = Version(mainVersion=0,
                                               updateVersion=0,
                                               hotfix=0)

        self._supportedIntents: Dict[str, Intent] = self.buildIntentList(
            supportedIntents)
        self.loadIntentsDefinition()

        self._utteranceSlotCleaner = re.compile('{(.+?):=>.+?}')
        self._myDevicesTemplates = dict()
        self._myDevices: Dict[str, Device] = dict()
Exemple #9
0
	def getUpdateSource(definedSource: str) -> str:
		updateSource = 'master'
		if definedSource == 'master':
			return updateSource

		req = requests.get('https://api.github.com/repos/project-alice-assistant/ProjectAlice/branches')
		result = req.json()

		versions = list()
		for branch in result:
			repoVersion = Version.fromString(branch['name'])

			releaseType = repoVersion.releaseType
			if not repoVersion.isVersionNumber \
					or definedSource == 'rc' and releaseType in {'b', 'a'} \
					or definedSource == 'beta' and releaseType == 'a':
				continue

			versions.append(repoVersion)

		if versions:
			versions.sort(reverse=True)
			updateSource = versions[0]

		return str(updateSource)
    def injectSkillNodes(self):
        package = Path('../.node-red/package.json')
        if not package.exists():
            #self.logWarning('Package json file for Node Red is missing. Is Node Red even installed?')
            return

        for skillName, tup in self.SkillManager.allScenarioNodes().items():
            scenarioNodeName, scenarioNodeVersion, scenarioNodePath = tup
            path = Path('../.node-red/node_modules', scenarioNodeName,
                        'package.json')
            if not path.exists():
                self.logInfo('New scenario node found')
                install = self.Commons.runSystemCommand(
                    f'cd ~/.node-red && npm install {scenarioNodePath}',
                    shell=True)
                if install.returncode == 1:
                    self.logWarning(
                        f'Something went wrong installing new node: {install.stderr}'
                    )

                continue

            with path.open('r') as fp:
                data = json.load(fp)
                version = Version.fromString(data['version'])

                if version < scenarioNodeVersion:
                    self.logInfo('New scenario node update found')
                    install = self.Commons.runSystemCommand(
                        f'cd ~/.node-red && npm install {scenarioNodePath}',
                        shell=True)
                    if install.returncode == 1:
                        self.logWarning(
                            f'Something went wrong updating node: {install.stderr}'
                        )
Exemple #11
0
	def checkForSkillUpdates(self, skillToCheck: str = None) -> bool:
		"""
		Check all installed skills for availability of updates.
		Includes failed skills but not inactive.
		:param skillToCheck:
		:return:
		"""
		self.logInfo('Checking for skill updates')
		updateCount = 0

		for skillName, data in self._skillList.items():
			if not data['active']:
				continue

			try:
				if skillToCheck and skillName != skillToCheck:
					continue

				remoteVersion = self.SkillStoreManager.getSkillUpdateVersion(skillName)
				localVersion = Version.fromString(self._skillList[skillName]['installer']['version'])
				if localVersion < remoteVersion:
					updateCount += 1

					self.WebUINotificationManager.newNotification(
						typ=UINotificationType.INFO,
						notification='skillUpdateAvailable',
						key='skillUpdate_{}'.format(skillName),
						replaceBody=[skillName, str(remoteVersion)]
					)

					if data.get('modified', False):
						self.allSkills[skillName].updateAvailable = True
						self.logInfo(f'![blue]({skillName}) - Version {self._skillList[skillName]["installer"]["version"]} < {str(remoteVersion)} in {self.ConfigManager.getAliceConfigByName("skillsUpdateChannel")} - ![blue](LOCKED) for local changes!')
						continue

					self.logInfo(f'![yellow]({skillName}) - Version {self._skillList[skillName]["installer"]["version"]} < {str(remoteVersion)} in {self.ConfigManager.getAliceConfigByName("skillsUpdateChannel")}')

					if not self.ConfigManager.getAliceConfigByName('skillAutoUpdate'):
						if skillName in self.allSkills:
							self.allSkills[skillName].updateAvailable = True
					else:
						if not self.downloadInstallTicket(skillName, isUpdate=True):
							raise Exception
				else:
					if data.get('modified', False):
						self.logInfo(f'![blue]({skillName}) - Version {self._skillList[skillName]["installer"]["version"]} in {self.ConfigManager.getAliceConfigByName("skillsUpdateChannel")} - ![blue](LOCKED) for local changes!')
					else:
						self.logInfo(f'![green]({skillName}) - Version {self._skillList[skillName]["installer"]["version"]} in {self.ConfigManager.getAliceConfigByName("skillsUpdateChannel")}')

			except GithubNotFound:
				self.logInfo(f'![red](Skill **{skillName}**) is not available on Github. Deprecated or is it a dev skill?')

			except Exception as e:
				self.logError(f'Error checking updates for skill **{skillName}**: {e}')

		self.logInfo(f'Found {updateCount} skill update', plural='update')
		return updateCount > 0
Exemple #12
0
	def checkForSkillUpdates(self, skillToCheck: str = None) -> List[str]:
		"""
		Checks all installed skills for availability of updates.
		Includes failed skills but not inactive.
		:param skillToCheck:
		:return:
		"""
		self.logInfo('Checking for skill updates')
		skillsToUpdate = list()

		for skillName in self._skillList:
			try:
				if skillToCheck and skillName != skillToCheck:
					continue

				installer = json.loads(self.getSkillInstallFilePath(skillName=skillName).read_text())

				remoteVersion = self.SkillStoreManager.getSkillUpdateVersion(skillName)
				localVersion = Version.fromString(installer['version'])
				if localVersion < remoteVersion:

					self.WebUINotificationManager.newNotification(
						typ=UINotificationType.INFO,
						notification='skillUpdateAvailable',
						key='skillUpdate_{}'.format(skillName),
						replaceBody=[skillName, str(remoteVersion)]
					)

					if self.isSkillUserModified(skillName=skillName) and self.ConfigManager.getAliceConfigByName('devMode'):
						if skillName in self.allSkills:
							self.allSkills[skillName].updateAvailable = True

						self.logInfo(f'![blue]({skillName}) - Version {installer["version"]} < {str(remoteVersion)} in {self.ConfigManager.getAliceConfigByName("skillsUpdateChannel")} - Locked for local changes!')
						continue

					self.logInfo(f'![yellow]({skillName}) - Version {installer["version"]} < {str(remoteVersion)} in {self.ConfigManager.getAliceConfigByName("skillsUpdateChannel")}')

					if not self.ConfigManager.getAliceConfigByName('skillAutoUpdate'):
						if skillName in self.allSkills:
							self.allSkills[skillName].updateAvailable = True
					else:
						skillsToUpdate.append(skillName)
				else:
					if self.isSkillUserModified(skillName=skillName) and self.ConfigManager.getAliceConfigByName('devMode'):
						self.logInfo(f'![blue]({skillName}) - Version {installer["version"]} in {self.ConfigManager.getAliceConfigByName("skillsUpdateChannel")} - Locked for local changes!')
					else:
						self.logInfo(f'![green]({skillName}) - Version {installer["version"]} in {self.ConfigManager.getAliceConfigByName("skillsUpdateChannel")}')

			except GithubNotFound:
				self.logInfo(f'![red](Skill **{skillName}**) is not available on Github. Deprecated or is it a dev skill?')

			except Exception as e:
				self.logError(f'Error checking updates for skill **{skillName}**: {e}')

		self.logInfo(f'Found {len(skillsToUpdate)} skill update', plural='update')
		return skillsToUpdate
    def _getSkillUpdateVersion(self, skillName: str) -> Optional[tuple]:
        """
		Get the highest skill version number a user can install.
		This is based on the user preferences, dependending on the current Alice version
		and the user's selected update channel for skills
		In case nothing is found, DO NOT FALLBACK TO MASTER

		:param skillName: The skill to look for
		:return: tuple
		"""
        versionMapping = self._skillStoreData.get(skillName, dict()).get(
            'versionMapping', dict())

        if not versionMapping:
            raise GithubNotFound

        userUpdatePref = self.ConfigManager.getAliceConfigByName(
            'skillsUpdateChannel')
        skillUpdateVersion = (Version(), '')

        aliceVersion = Version.fromString(constants.VERSION)
        for aliceMinVersion, repoVersion in versionMapping.items():
            aliceMinVersion = Version.fromString(aliceMinVersion)
            repoVersion = Version.fromString(repoVersion)

            if not repoVersion.isVersionNumber or not aliceMinVersion.isVersionNumber or aliceMinVersion > aliceVersion:
                continue

            releaseType = repoVersion.releaseType
            if userUpdatePref == 'master' and releaseType in {'rc', 'b', 'a'} \
              or userUpdatePref == 'rc' and releaseType in {'b', 'a'} \
              or userUpdatePref == 'beta' and releaseType == 'a':
                continue

            if repoVersion > skillUpdateVersion[0]:
                skillUpdateVersion = (
                    repoVersion, f'{str(repoVersion)}_{str(aliceMinVersion)}')

        if not skillUpdateVersion[0].isVersionNumber:
            raise GithubNotFound

        return skillUpdateVersion
 def __init__(self, installer: dict):
     self._installer = installer
     self._updateAvailable = False
     self._name = installer['name']
     self._icon = self._installer.get('icon', 'fas fa-biohazard')
     self._aliceMinVersion = Version.fromString(
         self._installer.get('aliceMinVersion', '1.0.0-b4'))
     self._maintainers = self._installer.get('maintainers', list())
     self._description = self._installer.get('desc', '')
     self._category = self._installer.get('category', constants.UNKNOWN)
     self._conditions = self._installer.get('conditions', dict())
     super().__init__()
Exemple #15
0
	def loadScenarioNodes(self):
		path = self.getResource('scenarioNodes/package.json')
		if not path.exists():
			return

		try:
			with path.open('r') as fp:
				data = json.load(fp)
				self._scenarioNodeName = data['name']
				self._scenarioNodeVersion = Version.fromString(data['version'])
		except Exception as e:
			self.logWarning(f'Failed to load scenario nodes: {e}')
Exemple #16
0
	def test_fromString(self):
		self.assertEqual(Version.fromString('1.2.3-a4'), Version(1, 2, 3, 'a', 4))
		self.assertEqual(Version.fromString('1.2.3'), Version(1, 2, 3, 'release', 1))
		self.assertEqual(Version.fromString('1.2'), Version(1, 2, 0, 'release', 1))
		self.assertEqual(Version.fromString('test'), Version(0, 0, 0, '', 0))
		self.assertFalse(Version.fromString('test').isVersionNumber)
		self.assertTrue(Version.fromString('1.2').isVersionNumber)
 def __init__(self, installer: dict):
     self._installer = installer
     self._updateAvailable = False
     self._name = installer['name']
     self._icon = self._installer.get('icon', 'fas fa-biohazard')
     self._aliceMinVersion = Version.fromString(
         self._installer.get('aliceMinVersion', '1.0.0-b4'))
     self._maintainers = self._installer.get('maintainers', list())
     self._description = self._installer.get('desc', '')
     self._category = self._installer.get('category', constants.UNKNOWN)
     self._conditions = self._installer.get('conditions', dict())
     self._skillPath = Path('skills') / self._name
     self._repository = Repository(directory=self._skillPath,
                                   init=True,
                                   raiseIfExisting=False)
     super().__init__()
    def loadScenarioNodes(self) -> None:
        """
		Load the scenario nodes (folder scenarioNodes) for Node-Red and store them in _scenarioPackageName
		:return:
		"""
        path = self.getResource('scenarioNodes/package.json')
        if not path.exists():
            return

        try:
            with path.open('r') as fp:
                data = json.load(fp)
                self._scenarioPackageName = data['name']
                self._scenarioPackageVersion = Version.fromString(
                    data['version'])
        except Exception as e:
            self.logWarning(f'Failed to load scenario nodes: {e}')
    def checkForSkillUpdates(self, skillToCheck: str = None) -> bool:
        self.logInfo('Checking for skill updates')
        updateCount = 0

        for skillName, data in self._skillList.items():
            if not data['active']:
                continue

            try:
                if skillToCheck and skillName != skillToCheck:
                    continue

                remoteVersion = self.SkillStoreManager.getSkillUpdateVersion(
                    skillName)
                localVersion = Version.fromString(
                    self._skillList[skillName]['installer']['version'])
                if localVersion < remoteVersion:
                    updateCount += 1
                    self.logInfo(
                        f'![yellow]({skillName}) - Version {self._skillList[skillName]["installer"]["version"]} < {str(remoteVersion)} in {self.ConfigManager.getAliceConfigByName("skillsUpdateChannel")}'
                    )

                    if not self.ConfigManager.getAliceConfigByName(
                            'skillAutoUpdate'):
                        if skillName in self._activeSkills:
                            self._activeSkills[
                                skillName].updateAvailable = True
                    else:
                        if not self.downloadInstallTicket(skillName):
                            raise Exception
                else:
                    self.logInfo(
                        f'![green]({skillName}) - Version {self._skillList[skillName]["installer"]["version"]} in {self.ConfigManager.getAliceConfigByName("skillsUpdateChannel")}'
                    )

            except GithubNotFound:
                self.logInfo(
                    f'![red](Skill **{skillName}**) is not available on Github. Deprecated or is it a dev skill?'
                )

            except Exception as e:
                self.logError(
                    f'Error checking updates for skill **{skillName}**: {e}')

        self.logInfo(f'Found {updateCount} skill update', plural='update')
        return updateCount > 0
Exemple #20
0
	def checkForSkillUpdates(self, skillToCheck: str = None) -> bool:
		self.logInfo('Checking for skill updates')

		availableSkills = self.ConfigManager.skillsConfigurations
		updateCount = 0

		for skillName in availableSkills:
			try:
				if skillToCheck and skillName != skillToCheck:
					continue

				remoteVersion = self.SkillStoreManager.getSkillUpdateVersion(skillName)
				localVersion = Version.fromString(availableSkills[skillName]['version'])
				if localVersion < remoteVersion:
					updateCount += 1
					self.logInfo(f'❌ {skillName} - Version {availableSkills[skillName]["version"]} < {str(remoteVersion)} in {self.ConfigManager.getAliceConfigByName("skillsUpdateChannel")}')

					if not self.ConfigManager.getAliceConfigByName('skillAutoUpdate'):
						if skillName in self._activeSkills:
							self._activeSkills[skillName].updateAvailable = True
						elif skillName in self._deactivatedSkills:
							self._deactivatedSkills[skillName].updateAvailable = True
					else:
						if not self.downloadInstallTicket(skillName):
							raise Exception

						if skillName in self._failedSkills:
							del self._failedSkills[skillName]
				else:
					self.logInfo(f'✔ {skillName} - Version {availableSkills[skillName]["version"]} in {self.ConfigManager.getAliceConfigByName("skillsUpdateChannel")}')

			except GithubNotFound:
				self.logInfo(f'❓ Skill "{skillName}" is not available on Github. Deprecated or is it a dev skill?')

			except Exception as e:
				self.logError(f'❗ Error checking updates for skill "{skillName}": {e}')

		self.logInfo(f'Found {updateCount} skill update(s)')
		return updateCount > 0
Exemple #21
0
	def _installSkills(self, skills: list) -> dict:
		root = Path(self.Commons.rootDir(), constants.SKILL_INSTALL_TICKET_PATH)
		availableSkills = self.ConfigManager.skillsConfigurations
		skillsToBoot = dict()
		self.MqttManager.mqttBroadcast(topic='hermes/leds/systemUpdate', payload={'sticky': True})
		for file in skills:
			skillName = Path(file).with_suffix('')

			self.logInfo(f'Now taking care of skill {skillName.stem}')
			res = root / file

			try:
				updating = False

				installFile = json.loads(res.read_text())

				skillName = installFile['name']
				path = Path(installFile['author'], skillName)

				if not skillName:
					self.logError('Skill name to install not found, aborting to avoid casualties!')
					continue

				directory = Path(self.Commons.rootDir()) / 'skills' / skillName

				conditions = {
					'aliceMinVersion': installFile['aliceMinVersion'],
					**installFile.get('conditions', dict())
				}

				self.checkSkillConditions(skillName, conditions, availableSkills)

				if skillName in availableSkills:
					installedVersion = Version.fromString(availableSkills[skillName]['version'])
					remoteVersion = Version.fromString(installFile['version'])
					localVersionIsLatest: bool = \
						directory.is_dir() and \
						'version' in availableSkills[skillName] and \
						installedVersion >= remoteVersion

					if localVersionIsLatest:
						self.logWarning(f'Skill "{skillName}" is already installed, skipping')
						self.Commons.runRootSystemCommand(['rm', res])
						continue
					else:
						self.logWarning(f'Skill "{skillName}" needs updating')
						updating = True

				if skillName in self._activeSkills:
					try:
						self._activeSkills[skillName].onStop()
					except Exception as e:
						self.logError(f'Error stopping "{skillName}" for update: {e}')
						raise

				gitCloner = GithubCloner(baseUrl=f'{constants.GITHUB_URL}/skill_{skillName}.git', path=path, dest=directory)

				try:
					gitCloner.clone(skillName=skillName)
					self.logInfo('Skill successfully downloaded')
					self._installSkill(res)
					skillsToBoot[skillName] = {
						'update': updating
					}
				except (GithubTokenFailed, GithubRateLimit):
					self.logError('Failed cloning skill')
					raise
				except GithubNotFound:
					if self.ConfigManager.getAliceConfigByName('devMode'):
						if not Path(f'{self.Commons.rootDir}/skills/{skillName}').exists() or not \
								Path(f'{self.Commons.rootDir}/skills/{skillName}/{skillName.py}').exists() or not \
								Path(f'{self.Commons.rootDir}/skills/{skillName}/dialogTemplate').exists() or not \
								Path(f'{self.Commons.rootDir}/skills/{skillName}/talks').exists():
							self.logWarning(f'Skill "{skillName}" cannot be installed in dev mode due to missing base files')
						else:
							self._installSkill(res)
							skillsToBoot[skillName] = {
								'update': updating
							}
						continue
					else:
						self.logWarning(f'Skill "{skillName}" is not available on Github, cannot install')
						raise

			except SkillNotConditionCompliant as e:
				self.logInfo(f'Skill "{skillName}" does not comply to "{e.condition}" condition, required "{e.conditionValue}"')
				if res.exists():
					res.unlink()

				self.broadcast(
					method=constants.EVENT_SKILL_INSTALL_FAILED,
					exceptions=self._name,
					skill=skillName
				)

			except Exception as e:
				self.logError(f'Failed installing skill "{skillName}": {e}')
				if res.exists():
					res.unlink()

				self.broadcast(
					method=constants.EVENT_SKILL_INSTALL_FAILED,
					exceptions=self.name,
					skill=skillName
				)
				raise

		return skillsToBoot
Exemple #22
0
	def checkSkillConditions(self, installer: dict = None, checkOnly=False) -> Union[bool, List[Dict[str, str]]]:
		"""
		Checks if the given skill is compliant to its conditions
		:param installer:
		:param checkOnly: Do not perform any other action (download other skill, etc.) but checking conditions
		:return:
		"""
		conditions = {
			'aliceMinVersion': installer['aliceMinVersion'],
			**installer.get('conditions', dict())
		}

		notCompliant = 'Skill is not compliant'
		notCompliantRules = list()

		if 'aliceMinVersion' in conditions and Version.fromString(conditions['aliceMinVersion']) > Version.fromString(constants.VERSION):
			if not checkOnly:
				raise SkillNotConditionCompliant(message=notCompliant, skillName=installer['name'], condition='Alice minimum version', conditionValue=conditions['aliceMinVersion'])
			else:
				notCompliantRules.append({'Alice version': conditions['aliceMinVersion']})

		for conditionName, conditionValue in conditions.items():
			if conditionName == 'lang' and self.LanguageManager.activeLanguage not in conditionValue:
				if not checkOnly:
					raise SkillNotConditionCompliant(message=notCompliant, skillName=installer['name'], condition=conditionName, conditionValue=conditionValue)
				else:
					notCompliantRules.append({conditionName: conditionValue})

			elif conditionName == 'online':
				if conditionValue and self.ConfigManager.getAliceConfigByName('stayCompletelyOffline') or not conditionValue and not self.ConfigManager.getAliceConfigByName('stayCompletelyOffline'):
					if not checkOnly:
						raise SkillNotConditionCompliant(message=notCompliant, skillName=installer['name'], condition=conditionName, conditionValue=conditionValue)
					else:
						notCompliantRules.append({conditionName: conditionValue})

			elif conditionName == 'skill':
				for requiredSkill in conditionValue:
					if requiredSkill in self._skillList and not self.isSkillActive(skillName=installer['name']):
						if not checkOnly:
							raise SkillNotConditionCompliant(message=notCompliant, skillName=installer['name'], condition=conditionName, conditionValue=conditionValue)
						else:
							notCompliantRules.append({conditionName: conditionValue})
					elif requiredSkill not in self._skillList:
						if not checkOnly:
							self.logInfo(f'Skill {installer["name"]} has another skill as dependency, adding download')
							try:
								self.downloadSkills(skills=requiredSkill)
							except:
								raise SkillNotConditionCompliant(message=notCompliant, skillName=installer['name'], condition=conditionName, conditionValue=conditionValue)
						else:
							notCompliantRules.append({conditionName: conditionValue})

			elif conditionName == 'notSkill':
				for excludedSkill in conditionValue:
					author, name = excludedSkill.split('/')
					if name in self._skillList and self.isSkillActive(skillName=installer['name']):
						if not checkOnly:
							raise SkillNotConditionCompliant(message=notCompliant, skillName=installer['name'], condition=conditionName, conditionValue=conditionValue)
						else:
							notCompliantRules.append({conditionName: conditionValue})

			elif conditionName == 'asrArbitraryCapture':
				if conditionValue and self.ASRManager.asr and not self.ASRManager.asr.capableOfArbitraryCapture:
					if not checkOnly:
						raise SkillNotConditionCompliant(message=notCompliant, skillName=installer['name'], condition=conditionName, conditionValue=conditionValue)
					else:
						notCompliantRules.append({conditionName: conditionValue})

			elif conditionName == 'activeManager':
				for manager in conditionValue:
					if not manager:
						continue

					man = SuperManager.getInstance().getManager(manager)
					if not man or not man.isActive:
						if not checkOnly:
							raise SkillNotConditionCompliant(message=notCompliant, skillName=installer['name'], condition=conditionName, conditionValue=conditionValue)
						else:
							notCompliantRules.append({conditionName: conditionValue})

		return True if not checkOnly else notCompliantRules
    def updateProjectAlice(self):
        self._logger.logInfo('Checking for core updates')
        self._isUpdating = True
        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

        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'])

        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()
        ]

        if currentHash != newHash:
            self._logger.logWarning(
                'New Alice version installed, need to restart...')
            self.doRestart()

        self._isUpdating = False
Exemple #24
0
    def checkForSkillUpdates(self, skillToCheck: str = None) -> bool:
        self.logInfo('Checking for skill updates')

        availableSkills = self.ConfigManager.skillsConfigurations
        updateCount = 0

        for skillName in availableSkills:
            try:
                if skillToCheck and skillName != skillToCheck:
                    continue

                remoteVersion = self.SkillStoreManager.getSkillUpdateVersion(
                    skillName)
                localVersion = Version.fromString(
                    availableSkills[skillName]['version'])
                if localVersion < remoteVersion:
                    updateCount += 1
                    self.logInfo(
                        f'❌ {skillName} - Version {availableSkills[skillName]["version"]} < {str(remoteVersion)} in {self.ConfigManager.getAliceConfigByName("skillsUpdateChannel")}'
                    )

                    if not self.ConfigManager.getAliceConfigByName(
                            'skillAutoUpdate'):
                        if skillName in self._activeSkills:
                            self._activeSkills[
                                skillName].updateAvailable = True
                        elif skillName in self._deactivatedSkills:
                            self._deactivatedSkills[
                                skillName].updateAvailable = True
                    else:
                        req = requests.get(
                            f'{constants.GITHUB_RAW_URL}/skill_{skillName}/{self.SkillStoreManager.getSkillUpdateTag(skillName)}/{skillName}.install'
                        )
                        if req.status_code == 404:
                            raise GithubNotFound

                        remoteFile = req.json()
                        if not remoteFile:
                            raise Exception

                        skillFile = Path(self.Commons.rootDir(),
                                         constants.SKILL_INSTALL_TICKET_PATH,
                                         skillName + '.install')
                        skillFile.write_text(json.dumps(remoteFile))
                        if skillName in self._failedSkills:
                            del self._failedSkills[skillName]
                else:
                    self.logInfo(
                        f'✔ {skillName} - Version {availableSkills[skillName]["version"]} in {self.ConfigManager.getAliceConfigByName("skillsUpdateChannel")}'
                    )

            except GithubNotFound:
                self.logInfo(
                    f'❓ Skill "{skillName}" is not available on Github. Deprecated or is it a dev skill?'
                )

            except Exception as e:
                self.logError(
                    f'❗ Error checking updates for skill "{skillName}": {e}')

        self.logInfo(f'Found {updateCount} skill update(s)')
        return updateCount > 0
Exemple #25
0
    def checkSkillConditions(self, skillName: str, conditions: dict,
                             availableSkills: dict) -> bool:

        notCompliant = 'Skill is not compliant'

        if 'aliceMinVersion' in conditions and \
          Version.fromString(conditions['aliceMinVersion']) > Version.fromString(constants.VERSION):
            raise SkillNotConditionCompliant(
                message=notCompliant,
                skillName=skillName,
                condition='Alice minimum version',
                conditionValue=conditions['aliceMinVersion'])

        for conditionName, conditionValue in conditions.items():
            if conditionName == 'lang' and self.LanguageManager.activeLanguage not in conditionValue:
                raise SkillNotConditionCompliant(message=notCompliant,
                                                 skillName=skillName,
                                                 condition=conditionName,
                                                 conditionValue=conditionValue)

            elif conditionName == 'online':
                if conditionValue and self.ConfigManager.getAliceConfigByName('stayCompletlyOffline') \
                  or not conditionValue and not self.ConfigManager.getAliceConfigByName('stayCompletlyOffline'):
                    raise SkillNotConditionCompliant(
                        message=notCompliant,
                        skillName=skillName,
                        condition=conditionName,
                        conditionValue=conditionValue)

            elif conditionName == 'skill':
                for requiredSkill in conditionValue:
                    if requiredSkill[
                            'name'] in availableSkills and not availableSkills[
                                requiredSkill['name']]['active']:
                        raise SkillNotConditionCompliant(
                            message=notCompliant,
                            skillName=skillName,
                            condition=conditionName,
                            conditionValue=conditionValue)
                    elif requiredSkill['name'] not in availableSkills:
                        self.logInfo(
                            f'Skill {skillName} has another skill as dependency, adding download'
                        )
                        if not self.Commons.downloadFile(
                                requiredSkill['url'],
                                Path(
                                    self.Commons.rootDir(),
                                    f"system/skillInstallTickets/{requiredSkill['name']}.install"
                                )):
                            raise SkillNotConditionCompliant(
                                message=notCompliant,
                                skillName=skillName,
                                condition=conditionName,
                                conditionValue=conditionValue)

            elif conditionName == 'notSkill':
                for excludedSkill in conditionValue:
                    author, name = excludedSkill.split('/')
                    if name in availableSkills and availableSkills[name][
                            'author'] == author and availableSkills[name][
                                'active']:
                        raise SkillNotConditionCompliant(
                            message=notCompliant,
                            skillName=skillName,
                            condition=conditionName,
                            conditionValue=conditionValue)

            elif conditionName == 'asrArbitraryCapture':
                if conditionValue and not self.ASRManager.asr.capableOfArbitraryCapture:
                    raise SkillNotConditionCompliant(
                        message=notCompliant,
                        skillName=skillName,
                        condition=conditionName,
                        conditionValue=conditionValue)

            elif conditionName == 'activeManager':
                for manager in conditionValue:
                    if not manager: continue

                    man = SuperManager.getInstance().getManager(manager)
                    if not man or not man.isActive:
                        raise SkillNotConditionCompliant(
                            message=notCompliant,
                            skillName=skillName,
                            condition=conditionName,
                            conditionValue=conditionValue)

        return True
Exemple #26
0
    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