mycroft-core/mycroft/skills/settings.py

315 lines
11 KiB
Python
Raw Normal View History

Change to Apache 2.0 license from GPLv3.0 This commit officially switches the mycroft-core repository from GPLv3.0 licensing to Apache 2.0. All dependencies on GPL'ed code have been removed and we have contacted all previous contributors with still-existing code in the repository to agree to this change. Going forward, all contributors will sign a Contributor License Agreement (CLA) by visiting https://mycroft.ai/cla, then they will be included in the Mycroft Project's overall Contributor list, found at: https://github.com/MycroftAI/contributors. This cleanly protects the project, the contributor and all who use the technology to build upon. Futher discussion can be found at this blog post: https://mycroft.ai/blog/right-license/ This commit also removes all __author__="" from the code. These lines are painful to maintain and the etiquette surrounding their maintainence is unclear. Do you remove a name from the list if the last line of code the wrote gets replaced? Etc. Now all contributors are publicly acknowledged in the aforementioned repo, and actual authorship is maintained by Github in a much more effective and elegant way! Finally, a few references to "Mycroft AI" were changed to the correct legal entity name "Mycroft AI Inc." ==== Fixed Issues ==== #403 Update License.md and file headers to Apache 2.0 #400 Update LICENSE.md ==== Documentation Notes ==== Deprecated the ScheduledSkill and ScheduledCRUDSkill classes. These capabilities have been superceded by the more flexible MycroftSkill class methods schedule_event(), schedule_repeating_event(), update_event(), and cancel_event().
2017-10-04 06:28:44 +00:00
# Copyright 2017 Mycroft AI Inc.
2017-04-13 05:26:45 +00:00
#
Change to Apache 2.0 license from GPLv3.0 This commit officially switches the mycroft-core repository from GPLv3.0 licensing to Apache 2.0. All dependencies on GPL'ed code have been removed and we have contacted all previous contributors with still-existing code in the repository to agree to this change. Going forward, all contributors will sign a Contributor License Agreement (CLA) by visiting https://mycroft.ai/cla, then they will be included in the Mycroft Project's overall Contributor list, found at: https://github.com/MycroftAI/contributors. This cleanly protects the project, the contributor and all who use the technology to build upon. Futher discussion can be found at this blog post: https://mycroft.ai/blog/right-license/ This commit also removes all __author__="" from the code. These lines are painful to maintain and the etiquette surrounding their maintainence is unclear. Do you remove a name from the list if the last line of code the wrote gets replaced? Etc. Now all contributors are publicly acknowledged in the aforementioned repo, and actual authorship is maintained by Github in a much more effective and elegant way! Finally, a few references to "Mycroft AI" were changed to the correct legal entity name "Mycroft AI Inc." ==== Fixed Issues ==== #403 Update License.md and file headers to Apache 2.0 #400 Update LICENSE.md ==== Documentation Notes ==== Deprecated the ScheduledSkill and ScheduledCRUDSkill classes. These capabilities have been superceded by the more flexible MycroftSkill class methods schedule_event(), schedule_repeating_event(), update_event(), and cancel_event().
2017-10-04 06:28:44 +00:00
# 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
2017-04-13 05:26:45 +00:00
#
Change to Apache 2.0 license from GPLv3.0 This commit officially switches the mycroft-core repository from GPLv3.0 licensing to Apache 2.0. All dependencies on GPL'ed code have been removed and we have contacted all previous contributors with still-existing code in the repository to agree to this change. Going forward, all contributors will sign a Contributor License Agreement (CLA) by visiting https://mycroft.ai/cla, then they will be included in the Mycroft Project's overall Contributor list, found at: https://github.com/MycroftAI/contributors. This cleanly protects the project, the contributor and all who use the technology to build upon. Futher discussion can be found at this blog post: https://mycroft.ai/blog/right-license/ This commit also removes all __author__="" from the code. These lines are painful to maintain and the etiquette surrounding their maintainence is unclear. Do you remove a name from the list if the last line of code the wrote gets replaced? Etc. Now all contributors are publicly acknowledged in the aforementioned repo, and actual authorship is maintained by Github in a much more effective and elegant way! Finally, a few references to "Mycroft AI" were changed to the correct legal entity name "Mycroft AI Inc." ==== Fixed Issues ==== #403 Update License.md and file headers to Apache 2.0 #400 Update LICENSE.md ==== Documentation Notes ==== Deprecated the ScheduledSkill and ScheduledCRUDSkill classes. These capabilities have been superceded by the more flexible MycroftSkill class methods schedule_event(), schedule_repeating_event(), update_event(), and cancel_event().
2017-10-04 06:28:44 +00:00
# http://www.apache.org/licenses/LICENSE-2.0
2017-04-13 05:26:45 +00:00
#
Change to Apache 2.0 license from GPLv3.0 This commit officially switches the mycroft-core repository from GPLv3.0 licensing to Apache 2.0. All dependencies on GPL'ed code have been removed and we have contacted all previous contributors with still-existing code in the repository to agree to this change. Going forward, all contributors will sign a Contributor License Agreement (CLA) by visiting https://mycroft.ai/cla, then they will be included in the Mycroft Project's overall Contributor list, found at: https://github.com/MycroftAI/contributors. This cleanly protects the project, the contributor and all who use the technology to build upon. Futher discussion can be found at this blog post: https://mycroft.ai/blog/right-license/ This commit also removes all __author__="" from the code. These lines are painful to maintain and the etiquette surrounding their maintainence is unclear. Do you remove a name from the list if the last line of code the wrote gets replaced? Etc. Now all contributors are publicly acknowledged in the aforementioned repo, and actual authorship is maintained by Github in a much more effective and elegant way! Finally, a few references to "Mycroft AI" were changed to the correct legal entity name "Mycroft AI Inc." ==== Fixed Issues ==== #403 Update License.md and file headers to Apache 2.0 #400 Update LICENSE.md ==== Documentation Notes ==== Deprecated the ScheduledSkill and ScheduledCRUDSkill classes. These capabilities have been superceded by the more flexible MycroftSkill class methods schedule_event(), schedule_repeating_event(), update_event(), and cancel_event().
2017-10-04 06:28:44 +00:00
# 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.
2017-04-13 05:26:45 +00:00
#
2017-04-24 10:09:23 +00:00
"""
2017-11-09 12:24:42 +00:00
SkillSettings is a simple extension of the python dict which enables
local storage of settings. Additionally it can interact with a backend
system to provide a GUI interface, described by meta-data described in
an optional 'settingsmeta.json' file.
2017-04-24 10:09:23 +00:00
Usage Example:
2017-04-24 10:09:23 +00:00
from mycroft.skill.settings import SkillSettings
s = SkillSettings('./settings.json')
s['meaning of life'] = 42
s['flower pot sayings'] = 'Not again...'
s.store()
2017-11-09 12:24:42 +00:00
Metadata format:
2017-11-09 12:24:42 +00:00
TODO: see https://goo.gl/MY3i1S
2017-04-24 10:09:23 +00:00
"""
2017-04-13 05:26:45 +00:00
import json
from threading import Timer
from os.path import isfile, join, expanduser
from mycroft.api import DeviceApi
from mycroft.util.log import LOG
from mycroft.configuration import ConfigurationManager
Change to Apache 2.0 license from GPLv3.0 This commit officially switches the mycroft-core repository from GPLv3.0 licensing to Apache 2.0. All dependencies on GPL'ed code have been removed and we have contacted all previous contributors with still-existing code in the repository to agree to this change. Going forward, all contributors will sign a Contributor License Agreement (CLA) by visiting https://mycroft.ai/cla, then they will be included in the Mycroft Project's overall Contributor list, found at: https://github.com/MycroftAI/contributors. This cleanly protects the project, the contributor and all who use the technology to build upon. Futher discussion can be found at this blog post: https://mycroft.ai/blog/right-license/ This commit also removes all __author__="" from the code. These lines are painful to maintain and the etiquette surrounding their maintainence is unclear. Do you remove a name from the list if the last line of code the wrote gets replaced? Etc. Now all contributors are publicly acknowledged in the aforementioned repo, and actual authorship is maintained by Github in a much more effective and elegant way! Finally, a few references to "Mycroft AI" were changed to the correct legal entity name "Mycroft AI Inc." ==== Fixed Issues ==== #403 Update License.md and file headers to Apache 2.0 #400 Update LICENSE.md ==== Documentation Notes ==== Deprecated the ScheduledSkill and ScheduledCRUDSkill classes. These capabilities have been superceded by the more flexible MycroftSkill class methods schedule_event(), schedule_repeating_event(), update_event(), and cancel_event().
2017-10-04 06:28:44 +00:00
2017-04-13 05:26:45 +00:00
class SkillSettings(dict):
""" A dictionary that can easily be save to a file, serialized as json. It
also syncs to the backend for skill settings
2017-04-24 10:09:23 +00:00
Args:
settings_file (str): Path to storage file
"""
def __init__(self, directory, name):
2017-04-13 05:26:45 +00:00
super(SkillSettings, self).__init__()
self.api = DeviceApi()
self._device_identity = self.api.identity.uuid
self.config = ConfigurationManager.get()
self.name = name
# set file paths
self._settings_path = join(directory, 'settings.json')
self._meta_path = join(directory, 'settingsmeta.json')
self._api_path = "/" + self._device_identity + "/skill"
2017-10-13 21:06:07 +00:00
self.is_alive = True
self.loaded_hash = hash(str(self))
2017-06-28 22:31:35 +00:00
# if settingsmeta.json exists
# this block of code is a control flow for
# different scenarios that may arises with settingsmeta
if isfile(self._meta_path):
LOG.info("settingsmeta.json exist for {}".format(self.name))
settings_meta = self._load_settings_meta()
hashed_meta = hash(str(settings_meta)+str(self._device_identity))
# check if hash is different from the saved hashed
if self._is_new_hash(hashed_meta):
LOG.info("looks like settingsmeta.json " +
"has changed for {}".format(self.name))
# TODO: once the delete api for device is created uncomment
2017-10-13 21:06:07 +00:00
if self._uuid_exist():
try:
LOG.info("a uuid exist for {}".format(self.name) +
" deleting old one")
old_uuid = self._load_uuid()
self._delete_metatdata(old_uuid)
except Exception as e:
LOG.info(e)
LOG.info("sending settingsmeta.json for {}".format(self.name) +
" to home.mycroft.ai")
new_uuid = self._send_settings_meta(settings_meta, hashed_meta)
self._save_uuid(new_uuid)
self._save_hash(hashed_meta)
else: # if hash is old
2017-10-12 19:36:23 +00:00
found_in_backend = False
settings = self._get_remote_settings()
# checks backend if the settings have been deleted via webUI
for skill in settings:
if skill["identifier"] == str(hashed_meta):
2017-10-12 19:36:23 +00:00
found_in_backend = True
# if it's been deleted from webUI resend
2017-10-12 19:36:23 +00:00
if found_in_backend is False:
LOG.info("seems like it got deleted from home... " +
"sending settingsmeta.json for " +
"{}".format(self.name))
new_uuid = self._send_settings_meta(
settings_meta, hashed_meta)
self._save_uuid(new_uuid)
self._save_hash(hashed_meta)
2017-10-13 18:43:55 +00:00
t = Timer(60, self._poll_skill_settings, [hashed_meta])
t.daemon = True
t.start()
2017-07-27 21:38:24 +00:00
self.load_skill_settings()
@property
def _is_stored(self):
return hash(str(self)) == self.loaded_hash
2017-04-13 05:26:45 +00:00
def __getitem__(self, key):
2017-10-12 19:36:23 +00:00
""" Get key """
2017-04-13 05:26:45 +00:00
return super(SkillSettings, self).__getitem__(key)
def __setitem__(self, key, value):
2017-10-12 19:36:23 +00:00
""" Add/Update key. """
2017-04-13 05:26:45 +00:00
return super(SkillSettings, self).__setitem__(key, value)
def _load_settings_meta(self):
2017-10-12 19:36:23 +00:00
""" loads settings metadata from skills path """
with open(self._meta_path) as f:
data = json.load(f)
return data
def _send_settings_meta(self, settings_meta, hashed_meta):
""" send settingsmeta.json to the backend
2017-07-06 17:40:21 +00:00
Args:
param1 (dict): dictionary of the current settings meta data
param1 (int): hashed settings meta data
Returns:
uuid (str): a unique id for the setting meta data
2017-07-27 21:38:24 +00:00
"""
2017-07-11 23:19:21 +00:00
try:
settings_meta["identifier"] = str(hashed_meta)
self._put_metadata(settings_meta)
2017-10-12 19:36:23 +00:00
settings = self._get_remote_settings()
skill_identity = str(hashed_meta)
uuid = None
# TODO: note uuid should be returned from the put request
for skill_setting in settings:
if skill_setting['identifier'] == skill_identity:
uuid = skill_setting["uuid"]
return uuid
2017-07-11 23:19:21 +00:00
except Exception as e:
2017-09-18 18:55:58 +00:00
LOG.error(e)
2017-06-28 22:31:35 +00:00
def _load_uuid(self):
""" loads uuid
Returns:
2017-10-12 18:32:50 +00:00
uuid (str): uuid of the previous settingsmeta
2017-06-28 22:31:35 +00:00
"""
directory = self.config.get("skills")["directory"]
directory = join(directory, self.name)
directory = expanduser(directory)
uuid_file = join(directory, 'uuid')
if isfile(uuid_file):
with open(uuid_file, 'r') as f:
uuid = f.read()
return uuid
def _save_uuid(self, uuid):
""" saves uuid to path
Args:
2017-10-12 18:32:50 +00:00
param1 (str): uuid of new seetingsmeta
2017-06-28 22:31:35 +00:00
"""
LOG.info("saving uuid {}".format(str(uuid)))
directory = self.config.get("skills")["directory"]
directory = join(directory, self.name)
directory = expanduser(directory)
uuid_file = join(directory, 'uuid')
with open(uuid_file, 'w') as f:
f.write(str(uuid))
def _save_hash(self, hashed_meta):
""" saves hashed_meta to path
Args:
param1 (int): hashed of new seetingsmeta
"""
LOG.info("saving hash {}".format(str(hashed_meta)))
directory = self.config.get("skills")["directory"]
directory = join(directory, self.name)
directory = expanduser(directory)
hash_file = join(directory, 'hash')
with open(hash_file, 'w') as f:
f.write(str(hashed_meta))
def _uuid_exist(self):
""" checks if there is a 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)
def _is_new_hash(self, hashed_meta):
""" checks if the 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:
2017-10-12 18:32:50 +00:00
param1 (int): hash of metadata and uuid of device
Returns:
bool: True if hash is new False otherwise
"""
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 _poll_skill_settings(self, hashed_meta):
""" If identifier exists for this skill poll to backend to
request settings and store it if it changes
TODO: implement as websocket
Args:
param1 (int): the hashed identifier
"""
LOG.info("getting settings from home.mycroft.ai")
try:
# update settings
2017-10-12 19:36:23 +00:00
settings = self._get_remote_settings()
skill_identity = str(hashed_meta)
for skill_setting in 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
self.store()
except Exception as e:
2017-09-18 18:55:58 +00:00
LOG.error(e)
2017-10-13 21:06:07 +00:00
if self.is_alive:
# continues to poll settings every 60 seconds
t = Timer(60, self._poll_skill_settings, [hashed_meta])
t.daemon = True
t.start()
2017-06-28 22:31:35 +00:00
def load_skill_settings(self):
2017-10-12 19:36:23 +00:00
""" If settings.json exist, open and read stored values into self """
2017-06-28 22:31:35 +00:00
if isfile(self._settings_path):
with open(self._settings_path) as f:
try:
json_data = json.load(f)
for key in json_data:
self.__setitem__(key, json_data[key])
except Exception as e:
# TODO: Show error on webUI. Dev will have to fix
# metadata to be able to edit later.
2017-09-18 18:55:58 +00:00
LOG.error(e)
2017-10-12 19:36:23 +00:00
def _get_remote_settings(self):
""" Get skill settings for this device from backend """
settings = self.api.request({
"method": "GET",
"path": self._api_path
})
settings = [skills for skills in settings if skills is not None]
return settings
def _put_metadata(self, settings_meta):
""" PUT settingsmeta to backend to be configured in home.mycroft.ai.
used in plcae of POST and PATCH
2017-06-28 22:31:35 +00:00
"""
return self.api.request({
"method": "PUT",
"path": self._api_path,
"json": settings_meta
})
def _delete_metatdata(self, uuid):
""" Deletes the current skill metadata
Args:
param1 (str): unique id of the skill
2017-06-28 22:31:35 +00:00
"""
return self.api.request({
"method": "DELETE",
2017-10-13 22:39:14 +00:00
"path": self._api_path + "/{}".format(uuid)
})
def store(self, force=False):
""" Store dictionary to file if a change has occured.
Args:
force: Force write despite no change
2017-06-28 22:31:35 +00:00
"""
if force or not self._is_stored:
with open(self._settings_path, 'w') as f:
json.dump(self, f)
self.loaded_hash = hash(str(self))