refactored skill settings and fixed test

pull/933/head
Michael Nguyen 2017-07-27 16:28:32 -05:00
parent 6eb1c194cb
commit 37ada28dbd
3 changed files with 73 additions and 128 deletions

View File

@ -232,7 +232,7 @@ class MycroftSkill(object):
try:
return self._settings
except:
self._settings = SkillSettings(self._dir, self.name)
self._settings = SkillSettings(self._dir)
return self._settings
def bind(self, emitter):

View File

@ -44,33 +44,32 @@ class SkillSettings(dict):
"""
SkillSettings creates a dictionary that can easily be stored
to file, serialized as json. It also syncs to the backend for
skill configuration
skill settings
Args:
settings_file (str): Path to storage file
"""
def __init__(self, directory, name):
def __init__(self, directory):
super(SkillSettings, self).__init__()
self.api = DeviceApi()
self.name = name
self._device_identity = self.api.identity.uuid
# set file paths
self._settings_path = join(directory, 'settings.json')
self._meta_path = join(directory, 'settingsmeta.json')
self._identity_path = join(
expanduser('~/.mycroft/skills/') + self.name, 'identity.json')
self._hashed_meta_path = join(
expanduser('~/.mycroft/skills/' + self.name), 'hashed_meta.json')
self._api_path = "/" + self._device_identity + "/skill"
self.loaded_hash = hash(str(self))
# if settingsmeta.json exists
if isfile(self._meta_path):
self.settings_meta = self._load_settings_meta()
self.settings = self._get_settings()
self._send_settings_meta()
self._patch_settings_meta()
self._poll_skill_settings()
self._load_skill_settings()
# start polling timer
Timer(60, self._poll_skill_settings).start()
@property
def _is_stored(self):
return hash(str(self)) == self.loaded_hash
self.load_skill_settings()
def __getitem__(self, key):
return super(SkillSettings, self).__getitem__(key)
@ -81,102 +80,73 @@ class SkillSettings(dict):
"""
return super(SkillSettings, self).__setitem__(key, value)
def _load_settings_meta(self):
with open(self._meta_path) as f:
data = json.load(f)
return data
def _skill_exist_in_backend(self):
"""
see if skill settings already exist in the backend
"""
skill_identity = self._get_skill_identity()
for skill_setting in self.settings:
if skill_identity == skill_setting["identifier"]:
return True
return False
def _send_settings_meta(self):
"""
If settings meta data exists and skill does not have a uuid,
send settingsmeta.json to the backend and store uuid for skill
send settingsmeta.json to the backend if skill doesn't
already exist
"""
if isfile(self._meta_path):
with open(self._meta_path) as f:
self.settings_meta = json.load(f)
# If skill is loaded for the first time post metadata
# to backend and store uuid for skill
try:
if not isfile(self._identity_path):
if self._skill_exist_in_backend is False:
response = self._put_metadata(self.settings_meta)
self._store_uuid(response)
except Exception as e:
logger.error(e)
def _patch_settings_meta(self):
"""
If settingsmeta.json is edited, do a PUT request to
implement the changes
TODO: allow structure changes i.e. ability to add more sections
"""
try:
if isfile(self._meta_path):
with open(self._meta_path, 'r') as f:
self.settings_meta = json.load(f)
hashed_meta = hash(str(self.settings_meta))
if isfile(self._hashed_meta_path):
with open(self._hashed_meta_path, 'r') as f:
hashed_data = json.load(f)
if hashed_data["hashed_meta"] != hashed_meta:
skill_object = self._get_settings()
skill_identity = self._get_skill_identity()
settings = {}
for skill_setting in skill_object:
if skill_setting['uuid'] == skill_identity:
settings = skill_setting
settings["skillMetadata"]["sections"] = \
self.settings_meta["skillMetadata"]["sections"]
self._put_metadata(settings)
else:
if isfile(self._meta_path):
with open(self._hashed_meta_path, 'w') as f:
hashed_data = {
"hashed_meta": hash(str(self.settings_meta))
}
json.dump(hashed_data, f)
except Exception as e:
logger.error(e)
def _poll_skill_settings(self):
"""
If uuid exists for this skill poll to backend to
If identifier exists for this skill poll to backend to
request settings and store it if it changes
TODO: implement as websocket
"""
if isfile(self._identity_path):
skill_identity = self._get_skill_identity()
if self._skill_exist_in_backend:
try:
response = self._get_settings()
for skill_setting in response:
if skill_setting['uuid'] == skill_identity:
# update settings
self.settings = self._get_settings()
skill_identity = self._get_skill_identity()
for skill_setting in self.settings:
if skill_setting['identifier'] == skill_identity:
sections = skill_setting['skillMetadata']['sections']
for section in sections:
for field in section["fields"]:
self.__setitem__(field["name"], field["value"])
# store value if settings has changed from backend
new_hash = hash(str(self))
if new_hash != self.loaded_hash:
self.store()
self.loaded_hash = hash(str(self))
except Exception as e:
logger.error(e)
self.store()
# poll backend every 60 seconds for new settings
Timer(60, self._poll_skill_settings).start()
def _get_skill_identity(self):
"""
returns the skill uuid
returns the skill identifier
"""
try:
with open(self._identity_path, 'r') as f:
data = json.load(f)
return data["uuid"]
return self.settings_meta["identifier"]
except Exception as e:
logger.error(e)
return None
def _load_skill_settings(self):
def load_skill_settings(self):
"""
If settings.json exist, open and read stored values into self
"""
@ -191,13 +161,6 @@ class SkillSettings(dict):
# metadata to be able to edit later.
logger.error(e)
def _store_uuid(self, uuid):
"""
Store uuid as identity.json in ~/.mycroft/skills/{skillname}
"""
with open(self._identity_path, 'w') as f:
json.dump(uuid, f)
def _get_settings(self):
"""
Get skill settings for this device from backend
@ -220,9 +183,7 @@ class SkillSettings(dict):
def store(self):
"""
Store dictionary to file if it has changed
Store dictionary to file
"""
if not self._is_stored:
with open(self._settings_path, 'w') as f:
json.dump(self, f)
self.loaded_hash = hash(str(self))

View File

@ -8,28 +8,21 @@ import unittest
class SkillSettingsTest(unittest.TestCase):
def setUp(self):
try:
remove(join(dirname(__file__), 'settings'))
remove(join(dirname(__file__), 'settings', 'settings.json'))
except OSError:
pass
def test_new(self):
s = SkillSettings(join(dirname(__file__), 'settings.json'),
"skill_name")
s = SkillSettings(join(dirname(__file__), 'settings'))
self.assertEqual(len(s), 0)
def test_add_value(self):
s = SkillSettings(join(dirname(__file__), 'settings.json'),
"skill_name")
s = SkillSettings(join(dirname(__file__), 'settings'))
s['test_val'] = 1
def test_load_existing(self):
s = SkillSettings(join(dirname(__file__), 'settings'),
"skill_name")
self.assertEqual(len(s), 7)
self.assertEqual(s['test_val'], 1)
def test_store(self):
s = SkillSettings(join(dirname(__file__), 'settings'),
"skill_name")
s = SkillSettings(join(dirname(__file__), 'settings'))
s['bool'] = True
s['int'] = 42
s['float'] = 4.2
@ -37,51 +30,42 @@ class SkillSettingsTest(unittest.TestCase):
s['list'] = ['batman', 2, True, 'superman']
s.store()
s2 = SkillSettings(join(dirname(__file__), 'settings'),
"skill_name")
s2 = SkillSettings(join(dirname(__file__), 'settings'))
for key in s:
self.assertEqual(s[key], s2[key])
def test_update_list(self):
s = SkillSettings(join(dirname(__file__), 'settings'),
"skill_name")
s = SkillSettings(join(dirname(__file__), 'settings'))
s['l'] = ['a', 'b', 'c']
s.store()
s2 = SkillSettings(join(dirname(__file__), 'settings'),
"skill_name")
s2 = SkillSettings(join(dirname(__file__), 'settings'))
self.assertEqual(s['l'], s2['l'])
# Update list
s2['l'].append('d')
s2.store()
s3 = SkillSettings(join(dirname(__file__), 'settings'),
"skill_name")
s3 = SkillSettings(join(dirname(__file__), 'settings'))
self.assertEqual(s2['l'], s3['l'])
def test_update_dict(self):
s = SkillSettings(join(dirname(__file__), 'settings'),
"skill_name")
s = SkillSettings(join(dirname(__file__), 'settings'))
s['d'] = {'a': 1, 'b': 2}
s.store()
s2 = SkillSettings(join(dirname(__file__), 'settings'),
"skill_name")
s2 = SkillSettings(join(dirname(__file__), 'settings'))
self.assertEqual(s['d'], s2['d'])
# Update dict
s2['d']['c'] = 3
s2.store()
s3 = SkillSettings(join(dirname(__file__), 'settings'),
"skill_name")
s3 = SkillSettings(join(dirname(__file__), 'settings'))
self.assertEqual(s2['d'], s3['d'])
def test_no_change(self):
s = SkillSettings(join(dirname(__file__), 'settings'),
"skill_name")
s = SkillSettings(join(dirname(__file__), 'settings'))
s['d'] = {'a': 1, 'b': 2}
s.store()
s2 = SkillSettings(join(dirname(__file__), 'settings'),
"skill_name")
s2 = SkillSettings(join(dirname(__file__), 'settings'))
self.assertTrue(len(s) == len(s2))