コード例 #1
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)
コード例 #2
0
    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()
        ]
コード例 #3
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 in availableSkills and not availableSkills[requiredSkill]['active']:
						raise SkillNotConditionCompliant(message=notCompliant, skillName=skillName, condition=conditionName, conditionValue=conditionValue)
					elif requiredSkill not in availableSkills:
						self.logInfo(f'Skill {skillName} has another skill as dependency, adding download')
						if not self.downloadInstallTicket(requiredSkill):
							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
コード例 #4
0
ファイル: SkillManager.py プロジェクト: LazzaAU/ProjectAlice
	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
コード例 #5
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
コード例 #6
0
    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
コード例 #7
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}')
コード例 #8
0
 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__()
コード例 #9
0
ファイル: SkillsView.py プロジェクト: scottwedge/ProjectAlice
	def loadStoreData(self):
		installers = dict()
		updateSource = self.ConfigManager.getSkillsUpdateSource()
		req = requests.get(url=f'https://alice.maxbachmann.de/assets/{updateSource}/store/store.json')
		results = req.json()

		if not results:
			return dict()

		for skill in results:
			if 'lang' not in skill['conditions']:
				skill['conditions']['lang'] = constants.ALL
			installers[skill['name']] = skill

		aliceVersion = Version(constants.VERSION)
		activeLanguage = self.LanguageManager.activeLanguage.lower()
		return {
			skillName: skillInfo for skillName, skillInfo in installers.items()
			if self.SkillManager.getSkillInstance(skillName=skillName, silent=True) is None
				and aliceVersion >= Version(skillInfo['aliceMinVersion'])
				and (activeLanguage in skillInfo['conditions']['lang'] or skillInfo['conditions']['lang'] == constants.ALL)
		}
コード例 #10
0
 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__()
コード例 #11
0
    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}')
コード例 #12
0
    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
コード例 #13
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}')

        self._name = self._installer['name']
        self._author = self._installer['author']
        self._version = self._installer['version']
        self._icon = self._installer['icon']
        self._description = self._installer['desc']
        self._category = self._installer[
            'category'] if 'category' in self._installer else 'undefined'
        self._conditions = self._installer['conditions']
        self._updateAvailable = False
        self._active = False
        self._delayed = False
        self._required = False
        self._databaseSchema = databaseSchema
        self._widgets = dict()
        self._deviceTypes = dict()
        self._intentsDefinitions = dict()
        self._scenarioNodeName = ''
        self._scenarioNodeVersion = Version(mainVersion=0,
                                            updateVersion=0,
                                            hotfix=0)

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

        self._utteranceSlotCleaner = re.compile('{(.+?):=>.+?}')
コード例 #14
0
ファイル: test_AliceSkill.py プロジェクト: tsvsj/ProjectAlice
 def __init__(self):
     self._name = 'ExampleSkill'
     self._instructions = ''
     self._author = 'unittest'
     self._version = '0.0.1'
     self._icon = ''
     self._description = ''
     self._category = 'undefined'
     self._conditions = dict()
     self._updateAvailable = False
     self._active = False
     self._delayed = False
     self._required = False
     self._databaseSchema = dict()
     self._widgets = dict()
     self._deviceTypes = dict()
     self._intentsDefinitions = dict()
     self._scenarioNodeName = ''
     self._scenarioNodeVersion = Version(mainVersion=0,
                                         updateVersion=0,
                                         hotfix=0)
コード例 #15
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
コード例 #16
0
    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}'
                        )
コード例 #17
0
	def test_comparison(self):
		self.assertTrue(
			Version(1, 0, 0, 'release', 1) > Version(1, 0, 0, 'rc', 1) > Version(1, 0, 0, 'b', 1) > Version(1, 0, 0, 'a', 1) > Version(0, 9, 0, 'release', 1)
		)
コード例 #18
0
	def test_stringConvertsion(self):
		self.assertEqual(str(Version(1, 2, 0, 'release', 1)), '1.2.0')
		self.assertEqual(str(Version(1, 2, 0, 'a', 3)), '1.2.0-a3')
コード例 #19
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
コード例 #20
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
コード例 #21
0
    def checkForSkillUpdates(self, skillToCheck: str = None) -> bool:
        if self.ConfigManager.getAliceConfigByName('stayCompletlyOffline'):
            return False

        self.logInfo('Checking for skill updates')
        if not self.InternetManager.online:
            self.logInfo('Not connected...')
            return False

        availableSkills = self.ConfigManager.skillsConfigurations
        updateSource = self.ConfigManager.getSkillsUpdateSource()

        i = 0
        for skillName in self._allSkills:
            try:
                if skillName not in availableSkills or (
                        skillToCheck is not None
                        and skillName != skillToCheck):
                    continue

                req = requests.get(
                    f'https://raw.githubusercontent.com/project-alice-assistant/ProjectAliceSkills/{updateSource}/PublishedSkills/{availableSkills[skillName]["author"]}/{skillName}/{skillName}.install'
                )

                if req.status_code == 404:
                    raise GithubNotFound

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

                if Version(availableSkills[skillName]['version']) < Version(
                        remoteFile['version']):
                    i += 1
                    self.logInfo(
                        f'❌ {skillName} - Version {availableSkills[skillName]["version"]} < {remoteFile["version"]} in {self.ConfigManager.getAliceConfigByName("updateChannel")}'
                    )

                    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:
                        skillFile = Path(self.Commons.rootDir(),
                                         'system/skillInstallTickets',
                                         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("updateChannel")}'
                    )

            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 {i} skill update(s)')
        return i > 0
コード例 #22
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 SkillInstanceFailed(
                skillName=constants.UNKNOWN,
                error=f'[{type(self).__name__}] Cannot find install file')
        except Exception as e:
            raise SkillInstanceFailed(
                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._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._repository = Repository(directory=self._skillPath,
                                      init=True,
                                      raiseIfExisting=False)
        self.loadIntentsDefinition()

        self._utteranceSlotCleaner = re.compile('{(.+?):=>.+?}')
        self._myDevicesTemplates = dict()
        self._myDevices: Dict[str, Device] = dict()
コード例 #23
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
コード例 #24
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