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)
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() ]
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
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
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 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}')
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__()
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) }
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
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('{(.+?):=>.+?}')
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)
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
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}' )
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) )
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')
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
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 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
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()
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
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