commit
1300773f3d
|
@ -6,6 +6,7 @@ before_install:
|
|||
- sudo apt-get install -qq mpg123 portaudio19-dev libglib2.0-dev swig bison libtool autoconf libglib2.0-dev libicu-dev libfann-dev realpath
|
||||
- sudo apt-get install -y gcc-4.8 g++-4.8
|
||||
- export CC="gcc-4.8"
|
||||
- export TMPDIR="/tmp/${TRAVIS_PYTHON_VERSION}"
|
||||
python:
|
||||
- "3.4"
|
||||
- "3.5"
|
||||
|
@ -15,6 +16,9 @@ python:
|
|||
cache: pocketsphinx-python
|
||||
# command to install dependencies
|
||||
install:
|
||||
- rm -rf ${TMPDIR}
|
||||
- mkdir ${TMPDIR}
|
||||
- echo ${TMPDIR}
|
||||
- VIRTUALENV_ROOT=${VIRTUAL_ENV} ./dev_setup.sh
|
||||
- pip install -r requirements.txt
|
||||
- pip install -r test-requirements.txt
|
||||
|
|
|
@ -371,6 +371,11 @@ class DeviceApi(Api):
|
|||
skills = {s['name']: s for s in data['skills']}
|
||||
to_send['skills'] = [skills[key] for key in skills]
|
||||
|
||||
# Finalize skill_gid with uuid if needed
|
||||
for s in to_send['skills']:
|
||||
s['skill_gid'] = s.get('skill_gid', '').replace(
|
||||
'@|', '@{}|'.format(self.identity.uuid))
|
||||
|
||||
self.request({
|
||||
"method": "PUT",
|
||||
"path": "/" + self.identity.uuid + "/skillJson",
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
# Copyright 2019 Mycroft AI Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
import os
|
||||
from os.path import join, expanduser, exists
|
||||
|
||||
from msm import MycroftSkillsManager, SkillRepo
|
||||
from mycroft.util.combo_lock import ComboLock
|
||||
|
||||
mycroft_msm_lock = ComboLock('/tmp/mycroft-msm.lck')
|
||||
|
||||
|
||||
def create_msm(config):
|
||||
""" Create msm object from config. """
|
||||
msm_config = config['skills']['msm']
|
||||
repo_config = msm_config['repo']
|
||||
data_dir = expanduser(config['data_dir'])
|
||||
skills_dir = join(data_dir, msm_config['directory'])
|
||||
repo_cache = join(data_dir, repo_config['cache'])
|
||||
platform = config['enclosure'].get('platform', 'default')
|
||||
|
||||
with mycroft_msm_lock:
|
||||
# Try to create the skills directory if it doesn't exist
|
||||
if not exists(skills_dir):
|
||||
os.makedirs(skills_dir)
|
||||
|
||||
return MycroftSkillsManager(
|
||||
platform=platform, skills_dir=skills_dir,
|
||||
repo=SkillRepo(repo_cache, repo_config['url'],
|
||||
repo_config['branch']),
|
||||
versioned=msm_config['versioned'])
|
|
@ -63,14 +63,52 @@ import json
|
|||
import hashlib
|
||||
import os
|
||||
import yaml
|
||||
import time
|
||||
import copy
|
||||
import re
|
||||
from threading import Timer
|
||||
from os.path import isfile, join, expanduser
|
||||
from requests.exceptions import RequestException, HTTPError
|
||||
from msm import SkillEntry
|
||||
|
||||
from mycroft.api import DeviceApi, is_paired
|
||||
from mycroft.util.log import LOG
|
||||
from mycroft.util import camel_case_split
|
||||
from mycroft.configuration import ConfigurationManager
|
||||
|
||||
from .msm_wrapper import create_msm
|
||||
|
||||
|
||||
msm = None
|
||||
msm_creation_time = 0
|
||||
|
||||
|
||||
def build_global_id(directory, config):
|
||||
""" Create global id for the skill.
|
||||
|
||||
TODO: Handle dirty skill
|
||||
|
||||
Arguments:
|
||||
directory: skill directory
|
||||
config: config for the device to fetch msm setup
|
||||
"""
|
||||
# Update the msm object if it's more than an hour old
|
||||
global msm
|
||||
global msm_creation_time
|
||||
if msm is None or time.time() - msm_creation_time > 60 * 60:
|
||||
msm_creation_time = time.time()
|
||||
msm = create_msm(config)
|
||||
|
||||
s = SkillEntry.from_folder(directory, msm)
|
||||
# If modified prepend the device uuid
|
||||
return s.skill_gid, s.meta_info.get('display_name')
|
||||
|
||||
|
||||
def display_name(name):
|
||||
""" Splits camelcase and removes leading/trailing Skill. """
|
||||
name = re.sub(r'(^[Ss]kill|[Ss]kill$)', '', name)
|
||||
return camel_case_split(name)
|
||||
|
||||
|
||||
class DelayRequest(Exception):
|
||||
""" Indicate that the next request should be delayed. """
|
||||
|
@ -82,8 +120,10 @@ class SkillSettings(dict):
|
|||
also syncs to the backend for skill settings
|
||||
|
||||
Args:
|
||||
directory (str): Path to storage directory
|
||||
name (str): user readable name associated with the settings
|
||||
directory (str): Path to storage directory
|
||||
name (str): user readable name associated with the settings
|
||||
no_upload (bool): True if the upload to mycroft servers should be
|
||||
disabled.
|
||||
"""
|
||||
|
||||
def __init__(self, directory, name):
|
||||
|
@ -100,6 +140,8 @@ class SkillSettings(dict):
|
|||
# set file paths
|
||||
self._settings_path = join(directory, 'settings.json')
|
||||
self._meta_path = _get_meta_path(directory)
|
||||
self._directory = directory
|
||||
|
||||
self.is_alive = True
|
||||
self.loaded_hash = hash(json.dumps(self, sort_keys=True))
|
||||
self._complete_intialization = False
|
||||
|
@ -108,11 +150,33 @@ class SkillSettings(dict):
|
|||
self._user_identity = None
|
||||
self.changed_callback = None
|
||||
self._poll_timer = None
|
||||
self._blank_poll_timer = None
|
||||
self._is_alive = True
|
||||
self._meta_upload = True # Flag allowing upload of settings meta
|
||||
|
||||
# Add Information extracted from the skills-meta.json entry for the
|
||||
# skill.
|
||||
skill_gid, disp_name = build_global_id(self._directory, self.config)
|
||||
self.__skill_gid = skill_gid
|
||||
self.display_name = disp_name
|
||||
|
||||
# if settingsmeta exist
|
||||
if self._meta_path:
|
||||
self._poll_skill_settings()
|
||||
# if not disallowed by user upload an entry for all skills installed
|
||||
elif self.config['skills']['upload_skill_manifest']:
|
||||
self._blank_poll_timer = Timer(1, self._init_blank_meta)
|
||||
self._blank_poll_timer.daemon = True
|
||||
self._blank_poll_timer.start()
|
||||
|
||||
@property
|
||||
def skill_gid(self):
|
||||
""" Finalizes the skill gid to include device uuid if needed. """
|
||||
if is_paired():
|
||||
return self.__skill_gid.replace('@|', '@{}|'.format(
|
||||
DeviceApi().identity.uuid))
|
||||
else:
|
||||
return self.__skill_gid
|
||||
|
||||
def __hash__(self):
|
||||
""" Simple object unique hash. """
|
||||
|
@ -128,6 +192,8 @@ class SkillSettings(dict):
|
|||
self._is_alive = False
|
||||
if self._poll_timer:
|
||||
self._poll_timer.cancel()
|
||||
if self._blank_poll_timer:
|
||||
self._blank_poll_timer.cancel()
|
||||
|
||||
def set_changed_callback(self, callback):
|
||||
""" Set callback to perform when server settings have changed.
|
||||
|
@ -158,34 +224,17 @@ class SkillSettings(dict):
|
|||
except RequestException:
|
||||
return
|
||||
|
||||
hashed_meta = self._get_meta_hash(settings_meta)
|
||||
skill_settings = self._request_other_settings(hashed_meta)
|
||||
# if hash is new then there is a diff version of settingsmeta
|
||||
if self._is_new_hash(hashed_meta):
|
||||
# first look at all other devices on user account to see
|
||||
# if the settings exist. if it does then sync with device
|
||||
if skill_settings:
|
||||
# not_owner flags that this settings is loaded from
|
||||
# another device. If a skill settings doesn't have
|
||||
# not_owner, then the skill is created from that device
|
||||
self['not_owner'] = True
|
||||
self.save_skill_settings(skill_settings)
|
||||
else: # upload skill settings if
|
||||
uuid = self._load_uuid()
|
||||
if uuid is not None:
|
||||
self._delete_metadata(uuid)
|
||||
self._upload_meta(settings_meta, hashed_meta)
|
||||
else: # hash is not new
|
||||
if skill_settings is not None:
|
||||
self['not_owner'] = True
|
||||
self.save_skill_settings(skill_settings)
|
||||
else:
|
||||
settings = self._request_my_settings(hashed_meta)
|
||||
if settings is None:
|
||||
# metadata got deleted from Home, send up
|
||||
self._upload_meta(settings_meta, hashed_meta)
|
||||
else:
|
||||
self.save_skill_settings(settings)
|
||||
settings = (self._request_my_settings(self.skill_gid) or
|
||||
self._request_other_settings(self.skill_gid))
|
||||
if settings:
|
||||
self.save_skill_settings(settings)
|
||||
|
||||
# TODO if this skill_gid is not a modified version check if a modified
|
||||
# version exists on the server and delete it
|
||||
|
||||
# Always try to upload settingsmeta on startup
|
||||
self._upload_meta(settings_meta, self.skill_gid)
|
||||
|
||||
self._complete_intialization = True
|
||||
|
||||
@property
|
||||
|
@ -202,24 +251,41 @@ class SkillSettings(dict):
|
|||
return super(SkillSettings, self).__setitem__(key, value)
|
||||
|
||||
def _load_settings_meta(self):
|
||||
""" Loads settings metadata from skills path. """
|
||||
if not self._meta_path:
|
||||
return None
|
||||
""" Load settings metadata from the skill folder.
|
||||
|
||||
_, ext = os.path.splitext(self._meta_path)
|
||||
json_file = True if ext.lower() == ".json" else False
|
||||
If no settingsmeta exists a basic settingsmeta will be created
|
||||
containing a basic identifier.
|
||||
|
||||
try:
|
||||
with open(self._meta_path, encoding='utf-8') as f:
|
||||
if json_file:
|
||||
data = json.load(f)
|
||||
else:
|
||||
data = yaml.load(f)
|
||||
return data
|
||||
except Exception as e:
|
||||
LOG.error("Failed to load setting file: " + self._meta_path)
|
||||
LOG.error(repr(e))
|
||||
return None
|
||||
Returns:
|
||||
(dict) settings meta
|
||||
"""
|
||||
if self._meta_path and os.path.isfile(self._meta_path):
|
||||
_, ext = os.path.splitext(self._meta_path)
|
||||
json_file = True if ext.lower() == ".json" else False
|
||||
|
||||
try:
|
||||
with open(self._meta_path, encoding='utf-8') as f:
|
||||
if json_file:
|
||||
data = json.load(f)
|
||||
else:
|
||||
data = yaml.load(f)
|
||||
except Exception as e:
|
||||
LOG.error("Failed to load setting file: " + self._meta_path)
|
||||
LOG.error(repr(e))
|
||||
data = copy.copy(BLANK_META)
|
||||
else:
|
||||
data = {}
|
||||
|
||||
# Insert skill_gid and display_name
|
||||
data['skill_gid'] = self.skill_gid
|
||||
data['display_name'] = (self.display_name or data.get('name') or
|
||||
display_name(self.name))
|
||||
|
||||
# Backwards compatibility:
|
||||
if 'name' not in data:
|
||||
data['name'] = data['display_name']
|
||||
|
||||
return data
|
||||
|
||||
def _send_settings_meta(self, settings_meta):
|
||||
""" Send settingsmeta to the server.
|
||||
|
@ -229,18 +295,21 @@ class SkillSettings(dict):
|
|||
Returns:
|
||||
dict: uuid, a unique id for the setting meta data
|
||||
"""
|
||||
try:
|
||||
uuid = self._put_metadata(settings_meta)
|
||||
return uuid
|
||||
except HTTPError as e:
|
||||
if e.response.status_code in [422, 500, 501]:
|
||||
raise DelayRequest
|
||||
else:
|
||||
if self._meta_upload:
|
||||
try:
|
||||
uuid = self._put_metadata(settings_meta)
|
||||
return uuid
|
||||
except HTTPError as e:
|
||||
if e.response.status_code in [422, 500, 501]:
|
||||
self._meta_upload = False
|
||||
raise DelayRequest
|
||||
else:
|
||||
LOG.error(e)
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
LOG.error(e)
|
||||
return None
|
||||
except Exception as e:
|
||||
LOG.error(e)
|
||||
return None
|
||||
|
||||
def save_skill_settings(self, skill_settings):
|
||||
""" Takes skill object and save onto self
|
||||
|
@ -248,61 +317,22 @@ class SkillSettings(dict):
|
|||
Args:
|
||||
skill_settings (dict): skill
|
||||
"""
|
||||
if self._is_new_hash(skill_settings['identifier']):
|
||||
self._save_uuid(skill_settings['uuid'])
|
||||
self._save_hash(skill_settings['identifier'])
|
||||
sections = skill_settings['skillMetadata']['sections']
|
||||
for section in sections:
|
||||
for field in section["fields"]:
|
||||
if "name" in field and "value" in field:
|
||||
self[field['name']] = field['value']
|
||||
self.store()
|
||||
|
||||
def _load_uuid(self):
|
||||
""" Loads uuid
|
||||
|
||||
Returns:
|
||||
str: uuid of the previous settingsmeta
|
||||
"""
|
||||
directory = self.config.get("skills")["directory"]
|
||||
directory = join(directory, self.name)
|
||||
directory = expanduser(directory)
|
||||
uuid_file = join(directory, 'uuid')
|
||||
uuid = None
|
||||
if isfile(uuid_file):
|
||||
with open(uuid_file, 'r') as f:
|
||||
uuid = f.read()
|
||||
return uuid
|
||||
|
||||
def _save_uuid(self, uuid):
|
||||
""" Saves uuid.
|
||||
|
||||
Args:
|
||||
uuid (str): uuid, unique id of new settingsmeta
|
||||
"""
|
||||
directory = self.config.get("skills")["directory"]
|
||||
directory = join(directory, self.name)
|
||||
directory = expanduser(directory)
|
||||
uuid_file = join(directory, 'uuid')
|
||||
os.makedirs(directory, exist_ok=True)
|
||||
with open(uuid_file, 'w') as f:
|
||||
f.write(str(uuid))
|
||||
|
||||
def _uuid_exist(self):
|
||||
""" Checks if there is an uuid file.
|
||||
|
||||
Returns:
|
||||
bool: True if uuid file exist False otherwise
|
||||
"""
|
||||
directory = self.config.get("skills")["directory"]
|
||||
directory = join(directory, self.name)
|
||||
directory = expanduser(directory)
|
||||
uuid_file = join(directory, 'uuid')
|
||||
return isfile(uuid_file)
|
||||
if 'skillMetadata' in skill_settings:
|
||||
sections = skill_settings['skillMetadata']['sections']
|
||||
for section in sections:
|
||||
for field in section["fields"]:
|
||||
if "name" in field and "value" in field:
|
||||
# Bypass the change lock to allow server to update
|
||||
# during skill init
|
||||
super(SkillSettings, self).__setitem__(field['name'],
|
||||
field['value'])
|
||||
self.store()
|
||||
|
||||
def _migrate_settings(self, settings_meta):
|
||||
""" sync settings.json and settingsmeta in memory """
|
||||
meta = settings_meta.copy()
|
||||
if 'skillMetadata' not in meta:
|
||||
return meta
|
||||
self.load_skill_settings_from_file()
|
||||
sections = meta['skillMetadata']['sections']
|
||||
for i, section in enumerate(sections):
|
||||
|
@ -314,90 +344,50 @@ class SkillSettings(dict):
|
|||
meta['skillMetadata']['sections'] = sections
|
||||
return meta
|
||||
|
||||
def _upload_meta(self, settings_meta, hashed_meta):
|
||||
def _upload_meta(self, settings_meta, identifier):
|
||||
""" uploads the new meta data to settings with settings migration
|
||||
|
||||
Args:
|
||||
settings_meta (dict): from settingsmeta.json or settingsmeta.yaml
|
||||
hashed_meta (str): {skill-folder}-settinsmeta.json
|
||||
settings_meta (dict): settingsmeta.json or settingsmeta.yaml
|
||||
identifier (str): identifier for skills meta data
|
||||
"""
|
||||
LOG.debug('Uploading settings meta for {}'.format(identifier))
|
||||
meta = self._migrate_settings(settings_meta)
|
||||
meta['identifier'] = str(hashed_meta)
|
||||
meta['identifier'] = identifier
|
||||
response = self._send_settings_meta(meta)
|
||||
if response and 'uuid' in response:
|
||||
self._save_uuid(response['uuid'])
|
||||
if 'not_owner' in self:
|
||||
del self['not_owner']
|
||||
self._save_hash(hashed_meta)
|
||||
|
||||
def hash(self, string):
|
||||
""" md5 hasher for consistency across cpu architectures """
|
||||
return hashlib.md5(bytes(string, 'utf-8')).hexdigest()
|
||||
|
||||
def _get_meta_hash(self, settings_meta):
|
||||
""" Gets the hash of skill
|
||||
|
||||
Args:
|
||||
settings_meta (dict): settingsmeta object
|
||||
Returns:
|
||||
_hash (str): hashed to identify skills
|
||||
"""
|
||||
_hash = self.hash(json.dumps(settings_meta, sort_keys=True) +
|
||||
self._user_identity)
|
||||
return "{}--{}".format(self.name, _hash)
|
||||
|
||||
def _save_hash(self, hashed_meta):
|
||||
""" Saves hashed_meta to settings directory.
|
||||
|
||||
Args:
|
||||
hashed_meta (str): hash of new settingsmeta
|
||||
"""
|
||||
directory = self.config.get("skills")["directory"]
|
||||
directory = join(directory, self.name)
|
||||
directory = expanduser(directory)
|
||||
hash_file = join(directory, 'hash')
|
||||
os.makedirs(directory, exist_ok=True)
|
||||
with open(hash_file, 'w') as f:
|
||||
f.write(hashed_meta)
|
||||
|
||||
def _is_new_hash(self, hashed_meta):
|
||||
""" Check if stored hash is the same as current.
|
||||
|
||||
If the hashed file does not exist, usually in the
|
||||
case of first load, then the create it and return True
|
||||
|
||||
Args:
|
||||
hashed_meta (str): hash of metadata and uuid of device
|
||||
Returns:
|
||||
bool: True if hash is new, otherwise False
|
||||
"""
|
||||
directory = self.config.get("skills")["directory"]
|
||||
directory = join(directory, self.name)
|
||||
directory = expanduser(directory)
|
||||
hash_file = join(directory, 'hash')
|
||||
if isfile(hash_file):
|
||||
with open(hash_file, 'r') as f:
|
||||
current_hash = f.read()
|
||||
return False if current_hash == str(hashed_meta) else True
|
||||
return True
|
||||
|
||||
def update_remote(self):
|
||||
""" update settings state from server """
|
||||
skills_settings = None
|
||||
settings_meta = self._load_settings_meta()
|
||||
if settings_meta is None:
|
||||
return
|
||||
hashed_meta = self._get_meta_hash(settings_meta)
|
||||
if self.get('not_owner'):
|
||||
skills_settings = self._request_other_settings(hashed_meta)
|
||||
if not skills_settings:
|
||||
skills_settings = self._request_my_settings(hashed_meta)
|
||||
# Get settings
|
||||
skills_settings = (self._request_my_settings(self.skill_gid) or
|
||||
self._request_other_settings(self.skill_gid))
|
||||
|
||||
if skills_settings is not None:
|
||||
self.save_skill_settings(skills_settings)
|
||||
self.store()
|
||||
else:
|
||||
LOG.debug("No Settings on server for {}".format(self.skill_gid))
|
||||
# Settings meta doesn't exist on server push them
|
||||
settings_meta = self._load_settings_meta()
|
||||
self._upload_meta(settings_meta, hashed_meta)
|
||||
self._upload_meta(settings_meta, self.skill_gid)
|
||||
|
||||
def _init_blank_meta(self):
|
||||
""" Send blank settingsmeta to remote. """
|
||||
try:
|
||||
if not is_paired() and self.is_alive:
|
||||
self._blank_poll_timer = Timer(60, self._init_blank_meta)
|
||||
self._blank_poll_timer.daemon = True
|
||||
self._blank_poll_timer.start()
|
||||
else:
|
||||
self.initialize_remote_settings()
|
||||
except Exception as e:
|
||||
LOG.exception('Failed to send blank meta: {}'.format(repr(e)))
|
||||
|
||||
def _poll_skill_settings(self):
|
||||
""" If identifier exists for this skill poll to backend to
|
||||
|
@ -466,6 +456,9 @@ class SkillSettings(dict):
|
|||
dict: skills object
|
||||
"""
|
||||
meta = settings_meta.copy()
|
||||
if 'skillMetadata' not in settings_meta:
|
||||
return meta
|
||||
|
||||
sections = meta['skillMetadata']['sections']
|
||||
|
||||
for i, section in enumerate(sections):
|
||||
|
@ -504,7 +497,6 @@ class SkillSettings(dict):
|
|||
def _request_my_settings(self, identifier):
|
||||
""" Get skill settings for this device associated
|
||||
with the identifier
|
||||
|
||||
Args:
|
||||
identifier (str): a hashed_meta
|
||||
Returns:
|
||||
|
@ -515,12 +507,33 @@ class SkillSettings(dict):
|
|||
# this loads the settings into memory for use in self.store
|
||||
for skill_settings in settings:
|
||||
if skill_settings['identifier'] == identifier:
|
||||
LOG.debug("Fetched settings for {}".format(identifier))
|
||||
skill_settings = \
|
||||
self._type_cast(skill_settings, to_platform='core')
|
||||
self._remote_settings = skill_settings
|
||||
return skill_settings
|
||||
return None
|
||||
|
||||
def _request_other_settings(self, identifier):
|
||||
""" Retrieve skill settings from other devices by identifier
|
||||
Args:
|
||||
identifier (str): identifier for this skill
|
||||
Returns:
|
||||
settings (dict or None): the retrieved settings or None
|
||||
"""
|
||||
path = \
|
||||
"/" + self._device_identity + "/userSkill?identifier=" + identifier
|
||||
try:
|
||||
user_skill = self.api.request({"method": "GET", "path": path})
|
||||
except RequestException:
|
||||
# Some kind of Timeout, connection HTTPError, etc.
|
||||
user_skill = None
|
||||
if not user_skill or not user_skill[0]:
|
||||
return None
|
||||
else:
|
||||
settings = self._type_cast(user_skill[0], to_platform='core')
|
||||
return settings
|
||||
|
||||
def _request_settings(self):
|
||||
""" Get all skill settings for this device from server.
|
||||
|
||||
|
@ -538,27 +551,6 @@ class SkillSettings(dict):
|
|||
settings = [skills for skills in settings if skills is not None]
|
||||
return settings
|
||||
|
||||
def _request_other_settings(self, identifier):
|
||||
""" Retrieve skill settings from other devices by identifier
|
||||
|
||||
Args:
|
||||
identifier (str): identifier for this skill
|
||||
Returns:
|
||||
settings (dict or None): the retrieved settings or None
|
||||
"""
|
||||
path = \
|
||||
"/" + self._device_identity + "/userSkill?identifier=" + identifier
|
||||
try:
|
||||
user_skill = self.api.request({"method": "GET", "path": path})
|
||||
except RequestException:
|
||||
# Some kind of Timeout, connection HTTPError, etc.
|
||||
user_skill = None
|
||||
if not user_skill:
|
||||
return None
|
||||
else:
|
||||
settings = self._type_cast(user_skill[0], to_platform='core')
|
||||
return settings
|
||||
|
||||
def _put_metadata(self, settings_meta):
|
||||
""" PUT settingsmeta to backend to be configured in server.
|
||||
used in place of POST and PATCH.
|
||||
|
@ -576,6 +568,7 @@ class SkillSettings(dict):
|
|||
def _delete_metadata(self, uuid):
|
||||
""" Delete the current skill metadata
|
||||
|
||||
TODO: UPDATE FOR NEW BACKEND
|
||||
Args:
|
||||
uuid (str): unique id of the skill
|
||||
"""
|
||||
|
@ -594,7 +587,8 @@ class SkillSettings(dict):
|
|||
@property
|
||||
def _should_upload_from_change(self):
|
||||
changed = False
|
||||
if hasattr(self, '_remote_settings'):
|
||||
if (hasattr(self, '_remote_settings') and
|
||||
'skillMetadata' in self._remote_settings):
|
||||
sections = self._remote_settings['skillMetadata']['sections']
|
||||
for i, section in enumerate(sections):
|
||||
for j, field in enumerate(section['fields']):
|
||||
|
@ -624,11 +618,7 @@ class SkillSettings(dict):
|
|||
|
||||
if self._should_upload_from_change:
|
||||
settings_meta = self._load_settings_meta()
|
||||
hashed_meta = self._get_meta_hash(settings_meta)
|
||||
uuid = self._load_uuid()
|
||||
if uuid is not None:
|
||||
self._delete_metadata(uuid)
|
||||
self._upload_meta(settings_meta, hashed_meta)
|
||||
self._upload_meta(settings_meta, self.skill_gid)
|
||||
|
||||
|
||||
def _get_meta_path(base_directory):
|
||||
|
|
|
@ -33,7 +33,7 @@ from mycroft.util.log import LOG
|
|||
from mycroft.api import DeviceApi, is_paired
|
||||
|
||||
from .core import load_skill, create_skill_descriptor, MainModule
|
||||
|
||||
from .msm_wrapper import create_msm as msm_creator
|
||||
|
||||
DEBUG = Configuration.get().get("debug", False)
|
||||
skills_config = Configuration.get().get("skills")
|
||||
|
@ -132,23 +132,7 @@ class SkillManager(Thread):
|
|||
|
||||
@staticmethod
|
||||
def create_msm():
|
||||
config = Configuration.get()
|
||||
msm_config = config['skills']['msm']
|
||||
repo_config = msm_config['repo']
|
||||
data_dir = expanduser(config['data_dir'])
|
||||
skills_dir = join(data_dir, msm_config['directory'])
|
||||
# Try to create the skills directory if it doesn't exist
|
||||
if not exists(skills_dir):
|
||||
os.makedirs(skills_dir)
|
||||
|
||||
repo_cache = join(data_dir, repo_config['cache'])
|
||||
platform = config['enclosure'].get('platform', 'default')
|
||||
return MycroftSkillsManager(
|
||||
platform=platform, skills_dir=skills_dir,
|
||||
repo=SkillRepo(
|
||||
repo_cache, repo_config['url'], repo_config['branch']
|
||||
), versioned=msm_config['versioned']
|
||||
)
|
||||
return msm_creator(Configuration.get())
|
||||
|
||||
def schedule_now(self, message=None):
|
||||
self.next_download = time.time() - 1
|
||||
|
|
|
@ -23,7 +23,7 @@ google-api-python-client==1.6.4
|
|||
fasteners==0.14.1
|
||||
PyYAML==3.13
|
||||
|
||||
msm==0.7.3
|
||||
msm==0.7.5
|
||||
msk==0.3.12
|
||||
adapt-parser==0.3.2
|
||||
padatious==0.4.6
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
#
|
||||
import json
|
||||
import unittest
|
||||
from mock import MagicMock
|
||||
|
||||
from os import remove
|
||||
from os.path import join, dirname
|
||||
|
@ -21,6 +22,10 @@ from os.path import join, dirname
|
|||
from mycroft.skills.settings import SkillSettings
|
||||
|
||||
|
||||
SkillSettings._poll_skill_settings = MagicMock()
|
||||
SkillSettings._init_blank_meta = MagicMock()
|
||||
|
||||
|
||||
class SkillSettingsTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
try:
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import unittest
|
||||
import mock
|
||||
import copy
|
||||
import tempfile
|
||||
from os.path import exists, join
|
||||
from shutil import rmtree
|
||||
|
@ -9,7 +10,6 @@ from mycroft.configuration import Configuration
|
|||
from mycroft.skills.skill_manager import SkillManager
|
||||
|
||||
BASE_CONF = base_config()
|
||||
BASE_CONF['data_dir'] = tempfile.mkdtemp()
|
||||
BASE_CONF['skills'] = {
|
||||
'msm': {
|
||||
'directory': 'skills',
|
||||
|
@ -17,7 +17,7 @@ BASE_CONF['skills'] = {
|
|||
'repo': {
|
||||
'cache': '.skills-repo',
|
||||
'url': 'https://github.com/MycroftAI/mycroft-skills',
|
||||
'branch': '18.08'
|
||||
'branch': '19.02'
|
||||
}
|
||||
},
|
||||
'update_interval': 3600,
|
||||
|
@ -56,14 +56,16 @@ class MycroftSkillTest(unittest.TestCase):
|
|||
self.emitter.reset()
|
||||
self.temp_dir = tempfile.mkdtemp()
|
||||
|
||||
@mock.patch.dict(Configuration._Configuration__config, BASE_CONF)
|
||||
def test_create_manager(self):
|
||||
""" Verify that the skill manager and msm loads as expected and
|
||||
that the skills dir is created as needed.
|
||||
"""
|
||||
SkillManager(self.emitter)
|
||||
self.assertTrue(exists(join(BASE_CONF['data_dir'], 'skills')))
|
||||
conf = copy.deepcopy(BASE_CONF)
|
||||
conf['data_dir'] = self.temp_dir
|
||||
with mock.patch.dict(Configuration._Configuration__config,
|
||||
BASE_CONF):
|
||||
SkillManager(self.emitter)
|
||||
self.assertTrue(exists(join(BASE_CONF['data_dir'], 'skills')))
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
rmtree(BASE_CONF['data_dir'])
|
||||
def tearDown(self):
|
||||
rmtree(self.temp_dir)
|
||||
|
|
|
@ -15,23 +15,25 @@
|
|||
import unittest
|
||||
from shutil import rmtree
|
||||
|
||||
from os.path import exists, isfile
|
||||
from os.path import exists, isfile, join
|
||||
from tempfile import gettempdir
|
||||
|
||||
from mycroft.util import create_signal, check_for_signal
|
||||
|
||||
|
||||
class TestSignals(unittest.TestCase):
|
||||
def setUp(self):
|
||||
if exists('/tmp/mycroft'):
|
||||
rmtree('/tmp/mycroft')
|
||||
if exists(join(gettempdir(), 'mycroft')):
|
||||
rmtree(join(gettempdir(), 'mycroft'))
|
||||
|
||||
def test_create_signal(self):
|
||||
create_signal('test_signal')
|
||||
self.assertTrue(isfile('/tmp/mycroft/ipc/signal/test_signal'))
|
||||
self.assertTrue(isfile(join(gettempdir(),
|
||||
'mycroft/ipc/signal/test_signal')))
|
||||
|
||||
def test_check_signal(self):
|
||||
if exists('/tmp/mycroft'):
|
||||
rmtree('/tmp/mycroft')
|
||||
if exists(join(gettempdir(), 'mycroft')):
|
||||
rmtree(join(gettempdir(), 'mycroft'))
|
||||
# check that signal is not found if file does not exist
|
||||
self.assertFalse(check_for_signal('test_signal'))
|
||||
|
||||
|
@ -39,7 +41,8 @@ class TestSignals(unittest.TestCase):
|
|||
create_signal('test_signal')
|
||||
self.assertTrue(check_for_signal('test_signal'))
|
||||
# Check that the signal is removed after use
|
||||
self.assertFalse(isfile('/tmp/mycroft/ipc/signal/test_signal'))
|
||||
self.assertFalse(isfile(join(gettempdir(),
|
||||
'mycroft/ipc/signal/test_signal')))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
Loading…
Reference in New Issue