20 from itertools
import chain
23 from os.path
import exists, join, basename, dirname, expanduser, isfile
24 from threading
import Thread, Event, Lock
26 from msm
import MycroftSkillsManager, SkillRepo, MsmException
27 from mycroft
import dialog
35 from .core
import load_skill, create_skill_descriptor, MainModule
36 from .msm_wrapper
import create_msm
as msm_creator
38 DEBUG = Configuration.get().
get(
"debug",
False)
39 skills_config = Configuration.get().
get(
"skills")
40 BLACKLISTED_SKILLS = skills_config.get(
"blacklisted_skills", [])
41 PRIORITY_SKILLS = skills_config.get(
"priority_skills", [])
43 installer_config = Configuration.get().
get(
"SkillInstallerSkill")
49 """ Checks if the file is valid file to require a reload. """ 50 return (f.endswith(
'.pyc')
or 51 f ==
'settings.json' or 58 Get last modified date excluding compiled python files, hidden 59 directories and the settings.json file. 62 path: skill directory to check 65 int: time of last change 68 for root_dir, dirs, files
in os.walk(path):
69 dirs[:] = [d
for d
in dirs
if not d.startswith(
'.')]
72 all_files.append(join(root_dir, f))
74 return max(os.path.getmtime(f)
for f
in all_files)
81 """ Load, update and manage instances of Skill on this system. 84 bus (eventemitter): Mycroft messagebus connection 103 self.
dot_msm = join(self.msm.skills_dir,
'.msm')
116 bus.on(
'mycroft.internet.connected',
117 lambda x: self._connected_event.set())
135 return msm_creator(Configuration.get())
143 return Configuration.get()[
'skills'].
get(
'upload_skill_manifest')
147 venv = dirname(dirname(sys.executable))
148 if os.access(venv, os.W_OK | os.R_OK | os.X_OK):
149 return join(venv,
'.mycroft-skills')
150 return expanduser(
'~/.mycroft/.mycroft-skills')
154 if not isfile(skills_file):
156 with open(skills_file)
as f:
158 i.strip()
for i
in f.read().split(
'\n')
if i.strip()
163 f.write(
'\n'.join(skill_names))
166 """ Invoke MSM to install default skills and/or update installed skills 169 speak (bool, optional): Speak the result? Defaults to False 172 LOG.error(
'msm failed, network connection not available')
174 self.bus.emit(
Message(
"speak", {
175 'utterance': dialog.get(
176 "not connected to the internet")}))
181 msm = SkillManager.create_msm()
183 default_groups = dict(msm.repo.get_default_skill_names())
184 if msm.platform
in default_groups:
185 platform_groups = default_groups[msm.platform]
187 LOG.info(
'Platform defaults not found, using DEFAULT ' 190 default_names = set(chain(default_groups[
'default'],
192 default_skill_errored =
False 194 def get_skill_data(skill_name):
195 """ Get skill data structure from name. """ 196 for e
in msm.skills_data.get(
'skills', []):
197 if e.get(
'name') == skill_name:
202 def install_or_update(skill):
203 """Install missing defaults and update existing skills""" 204 if get_skill_data(skill.name).
get(
'beta'):
208 if skill.name
not in installed_skills:
210 elif skill.name
in default_names:
212 msm.install(skill, origin=
'default')
214 if skill.name
in default_names:
215 LOG.warning(
'Failed to install default skill: ' +
217 nonlocal default_skill_errored
218 default_skill_errored =
True 220 installed_skills.add(skill.name)
222 msm.apply(install_or_update, msm.list())
223 if SkillManager.manifest_upload_allowed
and is_paired():
225 DeviceApi().upload_skills_data(msm.skills_data)
227 LOG.exception(
'Could not upload skill manifest')
229 except MsmException
as e:
230 LOG.error(
'Failed to update skills: {}'.format(repr(e)))
235 data = {
'utterance': dialog.get(
"skills updated")}
236 self.bus.emit(
Message(
"speak", data))
254 """ Shutdown removed skills. 257 paths: list of current directories in the skills folder 259 paths = [p.rstrip(
'/')
for p
in paths]
262 removed_skills = [str(s)
for s
in skills.keys()
if str(s)
not in paths]
263 for s
in removed_skills:
265 if skills[s].
get(
"is_ros_node",
False):
267 LOG.info(
'removing {}'.format(s))
269 LOG.debug(
'Removing: {}'.format(skills[s]))
270 skills[s][
'instance'].default_shutdown()
271 except Exception
as e:
273 self.loaded_skills.pop(s)
277 Check if unloaded skill or changed skill needs reloading 278 and perform loading if necessary. 280 Returns True if the skill was loaded/reloaded 282 skill_path = skill_path.rstrip(
'/')
283 skill = self.loaded_skills.setdefault(skill_path, {})
284 skill.update({
"id": basename(skill_path),
"path": skill_path})
287 if not MainModule +
".py" in os.listdir(skill_path):
292 last_mod = skill.get(
"last_modified", 0)
295 if skill.get(
"loaded")
and modified <= last_mod:
299 elif skill.get(
"instance")
and modified > last_mod:
301 if (
not skill[
"instance"].reload_skill
or 302 not skill.get(
'active',
True)):
305 LOG.debug(
"Reloading Skill: " + basename(skill_path))
308 skill[
"instance"].default_shutdown()
310 LOG.exception(
"An error occured while shutting down {}" 311 .format(skill[
"instance"].name))
316 refs = sys.getrefcount(skill[
"instance"]) - 2
318 msg = (
"After shutdown of {} there are still " 319 "{} references remaining. The skill " 320 "won't be cleaned from memory.")
321 LOG.warning(msg.format(skill[
'instance'].name, refs))
322 del skill[
"instance"]
323 self.bus.emit(
Message(
"mycroft.skills.shutdown",
327 skill[
"loaded"] =
True 330 self.
bus, skill[
"id"],
333 skill[
"last_modified"] = modified
334 if skill[
'instance']
is not None:
335 self.bus.emit(
Message(
'mycroft.skills.loaded',
338 'name': skill[
'instance'].name,
339 'modified': modified}))
342 self.bus.emit(
Message(
'mycroft.skills.loading_failure',
348 skills = {skill.name: skill
for skill
in self.msm.list()}
349 for skill_name
in PRIORITY_SKILLS:
350 skill = skills.get(skill_name)
352 if not skill.is_local:
356 LOG.exception(
'Downloading priority skill: ' 357 '{} failed'.format(skill.name))
358 if not skill.is_local:
362 LOG.error(
'Priority skill {} can\'t be found')
365 """If git gets killed from an abrupt shutdown it leaves lock files""" 366 for i
in glob(join(self.msm.skills_dir,
'*/.git/index.lock')):
367 LOG.warning(
'Found and removed git lock file: ' + i)
371 """ Load skills and update periodically from disk and internet """ 374 self._connected_event.wait()
378 update = Configuration.get()[
"skills"][
"auto_update"]
382 while not self._stop_event.is_set():
389 skill_paths = glob(join(self.msm.skills_dir,
'*/'))
390 still_loading =
False 391 for skill_path
in skill_paths:
397 except Exception
as e:
398 LOG.error(
'(Re)loading of {} failed ({})'.format(
399 skill_path, repr(e)))
400 if not has_loaded
and not still_loading
and len(skill_paths) > 0:
402 LOG.info(
"Skills all loaded!")
403 self.bus.emit(
Message(
'mycroft.skills.initialized'))
411 Send list of loaded skills. 418 info[basename(s)] = {
422 self.bus.emit(
Message(
'mycroft.skills.list', data=info))
423 except Exception
as e:
427 """ Deactivate a skill. """ 435 except Exception
as e:
436 LOG.error(
'Couldn\'t deactivate skill, {}'.format(repr(e)))
439 """ Deactivate a skill. """ 441 skill = message.data[
'skill']
444 except Exception
as e:
445 LOG.error(
'Couldn\'t deactivate skill, {}'.format(repr(e)))
448 """ Deactivate all skills except the provided. """ 450 skill_to_keep = message.data[
'skill']
451 LOG.info(
'DEACTIVATING ALL SKILLS EXCEPT {}'.format(skill_to_keep))
452 if skill_to_keep
in [basename(i)
for i
in self.
loaded_skills]:
454 if basename(skill) != skill_to_keep:
457 LOG.info(
'Couldn\'t find skill')
458 except Exception
as e:
459 LOG.error(
'Error during skill removal, {}'.format(repr(e)))
467 """ Activate a deactivated skill. """ 469 skill = message.data[
'skill']
479 except Exception
as e:
480 LOG.error(
'Couldn\'t activate skill, {}'.format(repr(e)))
483 """ Tell the manager to shutdown """ 484 self._stop_event.set()
487 for name, skill_info
in self.loaded_skills.items():
488 instance = skill_info.get(
'instance')
491 instance.default_shutdown()
493 LOG.exception(
'Shutting down skill: ' + name)
496 """ Check if the targeted skill id can handle conversation 498 If supported, the conversation is invoked. 501 skill_id = message.data[
"skill_id"]
502 utterances = message.data[
"utterances"]
503 lang = message.data[
"lang"]
510 self.bus.emit(message.reply(
"skill.converse.error",
511 {
"skill_id": skill_id,
512 "error":
"converse requested" 517 result = instance.converse(utterances, lang)
518 self.bus.emit(message.reply(
"skill.converse.response", {
519 "skill_id": skill_id,
"result": result}))
521 except BaseException:
522 self.bus.emit(message.reply(
"skill.converse.error",
523 {
"skill_id": skill_id,
524 "error":
"exception in " 528 self.bus.emit(message.reply(
"skill.converse.error",
529 {
"skill_id": skill_id,
530 "error":
"skill id does not exist"}))
def _get_last_modified_date(path)
def _unload_removed(self, paths)
def manifest_upload_allowed(self)
def send_skill_list(self, message=None)
def is_paired(ignore_errors=True)
def load_skill(skill_descriptor, bus, skill_id, BLACKLISTED_SKILLS=None)
def _load_or_reload_skill(self, skill_path)
def __deactivate_skill(self, skill)
def download_skills(self, speak=False)
def schedule_now(self, message=None)
def handle_converse_request(self, message)
def deactivate_skill(self, message)
def remove_git_locks(self)
def activate_skill(self, message)
def load_installed_skills(self)
def create_skill_descriptor(skill_path)
def deactivate_except(self, message)
def installed_skills_file(self)
def get(phrase, lang=None, context=None)
def save_installed_skills(self, skill_names)
def __activate_skill(self, skill)