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: try:
return self._settings return self._settings
except: except:
self._settings = SkillSettings(self._dir, self.name) self._settings = SkillSettings(self._dir)
return self._settings return self._settings
def bind(self, emitter): def bind(self, emitter):

View File

@ -44,33 +44,32 @@ class SkillSettings(dict):
""" """
SkillSettings creates a dictionary that can easily be stored SkillSettings creates a dictionary that can easily be stored
to file, serialized as json. It also syncs to the backend for to file, serialized as json. It also syncs to the backend for
skill configuration skill settings
Args: Args:
settings_file (str): Path to storage file settings_file (str): Path to storage file
""" """
def __init__(self, directory, name): def __init__(self, directory):
super(SkillSettings, self).__init__() super(SkillSettings, self).__init__()
self.api = DeviceApi() self.api = DeviceApi()
self.name = name
self._device_identity = self.api.identity.uuid self._device_identity = self.api.identity.uuid
# set file paths
self._settings_path = join(directory, 'settings.json') self._settings_path = join(directory, 'settings.json')
self._meta_path = join(directory, 'settingsmeta.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._api_path = "/" + self._device_identity + "/skill"
self.loaded_hash = hash(str(self)) 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._send_settings_meta()
self._patch_settings_meta() # start polling timer
self._poll_skill_settings() Timer(60, self._poll_skill_settings).start()
self._load_skill_settings()
@property self.load_skill_settings()
def _is_stored(self):
return hash(str(self)) == self.loaded_hash
def __getitem__(self, key): def __getitem__(self, key):
return super(SkillSettings, self).__getitem__(key) return super(SkillSettings, self).__getitem__(key)
@ -81,102 +80,73 @@ class SkillSettings(dict):
""" """
return super(SkillSettings, self).__setitem__(key, value) 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): def _send_settings_meta(self):
""" """
If settings meta data exists and skill does not have a uuid, send settingsmeta.json to the backend if skill doesn't
send settingsmeta.json to the backend and store uuid for skill 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: try:
if not isfile(self._identity_path): if self._skill_exist_in_backend is False:
response = self._put_metadata(self.settings_meta) 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: except Exception as e:
logger.error(e) logger.error(e)
def _poll_skill_settings(self): 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 request settings and store it if it changes
TODO: implement as websocket TODO: implement as websocket
""" """
if isfile(self._identity_path): if self._skill_exist_in_backend:
skill_identity = self._get_skill_identity()
try: try:
response = self._get_settings() # update settings
self.settings = self._get_settings()
for skill_setting in response: skill_identity = self._get_skill_identity()
if skill_setting['uuid'] == skill_identity: for skill_setting in self.settings:
if skill_setting['identifier'] == skill_identity:
sections = skill_setting['skillMetadata']['sections'] sections = skill_setting['skillMetadata']['sections']
for section in sections: for section in sections:
for field in section["fields"]: for field in section["fields"]:
self.__setitem__(field["name"], field["value"]) 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: except Exception as e:
logger.error(e) logger.error(e)
self.store()
# poll backend every 60 seconds for new settings # poll backend every 60 seconds for new settings
Timer(60, self._poll_skill_settings).start() Timer(60, self._poll_skill_settings).start()
def _get_skill_identity(self): def _get_skill_identity(self):
""" """
returns the skill uuid returns the skill identifier
""" """
try: try:
with open(self._identity_path, 'r') as f: return self.settings_meta["identifier"]
data = json.load(f)
return data["uuid"]
except Exception as e: except Exception as e:
logger.error(e) logger.error(e)
return None return None
def _load_skill_settings(self): def load_skill_settings(self):
""" """
If settings.json exist, open and read stored values into 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. # metadata to be able to edit later.
logger.error(e) 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): def _get_settings(self):
""" """
Get skill settings for this device from backend Get skill settings for this device from backend
@ -220,9 +183,7 @@ class SkillSettings(dict):
def store(self): 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: with open(self._settings_path, 'w') as f:
json.dump(self, f) json.dump(self, f)
self.loaded_hash = hash(str(self))

View File

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