217 lines
6.8 KiB
Python
217 lines
6.8 KiB
Python
# Copyright 2016 Mycroft AI, Inc.
|
|
#
|
|
# This file is part of Mycroft Core.
|
|
#
|
|
# Mycroft Core is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# Mycroft Core is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with Mycroft Core. If not, see <http://www.gnu.org/licenses/>.
|
|
import json
|
|
import re
|
|
from genericpath import exists, isfile
|
|
from os.path import join, dirname, expanduser
|
|
|
|
import inflection
|
|
|
|
from mycroft.util.log import getLogger
|
|
|
|
__author__ = 'seanfitz, jdorleans'
|
|
|
|
LOG = getLogger(__name__)
|
|
|
|
DEFAULT_CONFIG = join(dirname(__file__), 'mycroft.conf')
|
|
SYSTEM_CONFIG = '/etc/mycroft/mycroft.conf'
|
|
USER_CONFIG = join(expanduser('~'), '.mycroft/mycroft.conf')
|
|
|
|
|
|
class ConfigurationLoader(object):
|
|
"""
|
|
A utility for loading Mycroft configuration files.
|
|
"""
|
|
|
|
@staticmethod
|
|
def init_config(config=None):
|
|
if not config:
|
|
return {}
|
|
return config
|
|
|
|
@staticmethod
|
|
def init_locations(locations=None, keep_user_config=True):
|
|
if not locations:
|
|
locations = [DEFAULT_CONFIG, SYSTEM_CONFIG, USER_CONFIG]
|
|
elif keep_user_config:
|
|
locations += [USER_CONFIG]
|
|
return locations
|
|
|
|
@staticmethod
|
|
def validate(config=None, locations=None):
|
|
if not (isinstance(config, dict) and isinstance(locations, list)):
|
|
LOG.error("Invalid configuration data type.")
|
|
LOG.error("Locations: %s" % locations)
|
|
LOG.error("Configuration: %s" % config)
|
|
raise TypeError
|
|
|
|
@staticmethod
|
|
def load(config=None, locations=None, keep_user_config=True):
|
|
"""
|
|
Loads default or specified configuration files
|
|
"""
|
|
config = ConfigurationLoader.init_config(config)
|
|
locations = ConfigurationLoader.init_locations(locations,
|
|
keep_user_config)
|
|
ConfigurationLoader.validate(config, locations)
|
|
|
|
for location in locations:
|
|
config = ConfigurationLoader.__load(config, location)
|
|
|
|
return config
|
|
|
|
@staticmethod
|
|
def __load(config, location):
|
|
if exists(location) and isfile(location):
|
|
try:
|
|
with open(location) as f:
|
|
config.update(json.load(f))
|
|
LOG.debug("Configuration '%s' loaded" % location)
|
|
except Exception, e:
|
|
LOG.error("Error loading configuration '%s'" % location)
|
|
LOG.error(repr(e))
|
|
else:
|
|
LOG.debug("Configuration '%s' not found" % location)
|
|
return config
|
|
|
|
|
|
class RemoteConfiguration(object):
|
|
"""
|
|
map remote configuration properties to
|
|
config in the [core] config section
|
|
"""
|
|
IGNORED_SETTINGS = ["uuid", "@type", "active", "user", "device"]
|
|
|
|
@staticmethod
|
|
def validate(config):
|
|
if not (config and isinstance(config, dict)):
|
|
LOG.error("Invalid configuration: %s" % config)
|
|
raise TypeError
|
|
|
|
@staticmethod
|
|
def load(config=None):
|
|
RemoteConfiguration.validate(config)
|
|
update = config.get("server", {}).get("update")
|
|
|
|
if update:
|
|
try:
|
|
from mycroft.api import DeviceApi
|
|
setting = DeviceApi().find_setting()
|
|
RemoteConfiguration.__load(config, setting)
|
|
except Exception as e:
|
|
LOG.warn("Failed to fetch remote configuration: %s" % repr(e))
|
|
else:
|
|
LOG.debug("Remote configuration not activated.")
|
|
return config
|
|
|
|
@staticmethod
|
|
def __load(config, setting):
|
|
for k, v in setting.iteritems():
|
|
if k not in RemoteConfiguration.IGNORED_SETTINGS:
|
|
key = inflection.underscore(re.sub(r"Setting(s)?", "", k))
|
|
if isinstance(v, dict):
|
|
config[key] = config.get(key, {})
|
|
RemoteConfiguration.__load(config[key], v)
|
|
elif isinstance(v, list):
|
|
RemoteConfiguration.__load_list(config[key], v)
|
|
else:
|
|
config[key] = v
|
|
|
|
@staticmethod
|
|
def __load_list(config, values):
|
|
for v in values:
|
|
module = v["@type"]
|
|
if v.get("active"):
|
|
config["module"] = module
|
|
config[module] = config.get(module, {})
|
|
RemoteConfiguration.__load(config[module], v)
|
|
|
|
|
|
class ConfigurationManager(object):
|
|
"""
|
|
Static management utility for calling up cached configuration.
|
|
"""
|
|
__config = None
|
|
__listener = None
|
|
|
|
@staticmethod
|
|
def init(ws):
|
|
ConfigurationManager.__listener = ConfigurationListener(ws)
|
|
|
|
@staticmethod
|
|
def load_defaults():
|
|
ConfigurationManager.__config = ConfigurationLoader.load()
|
|
return RemoteConfiguration.load(ConfigurationManager.__config)
|
|
|
|
@staticmethod
|
|
def load_local(locations=None, keep_user_config=True):
|
|
return ConfigurationLoader.load(ConfigurationManager.get(), locations,
|
|
keep_user_config)
|
|
|
|
@staticmethod
|
|
def load_remote():
|
|
if not ConfigurationManager.__config:
|
|
ConfigurationManager.__config = ConfigurationLoader.load()
|
|
return RemoteConfiguration.load(ConfigurationManager.__config)
|
|
|
|
@staticmethod
|
|
def get(locations=None):
|
|
"""
|
|
Get cached configuration.
|
|
|
|
:return: A dictionary representing Mycroft configuration.
|
|
"""
|
|
if not ConfigurationManager.__config:
|
|
ConfigurationManager.load_defaults()
|
|
|
|
if locations:
|
|
ConfigurationManager.load_local(locations)
|
|
|
|
return ConfigurationManager.__config
|
|
|
|
@staticmethod
|
|
def update(config):
|
|
"""
|
|
Update cached configuration with the new ``config``.
|
|
"""
|
|
if not ConfigurationManager.__config:
|
|
ConfigurationManager.load_defaults()
|
|
|
|
if config:
|
|
ConfigurationManager.__config.update(config)
|
|
|
|
@staticmethod
|
|
def save(config, is_system=False):
|
|
"""
|
|
Save configuration ``config``.
|
|
"""
|
|
ConfigurationManager.update(config)
|
|
location = SYSTEM_CONFIG if is_system else USER_CONFIG
|
|
with open(location, 'rw') as f:
|
|
config = json.load(f).update(config)
|
|
json.dump(config, f)
|
|
|
|
|
|
class ConfigurationListener(object):
|
|
def __init__(self, ws):
|
|
super(ConfigurationListener, self).__init__()
|
|
ws.on("configuration.updated", self.updated)
|
|
|
|
@staticmethod
|
|
def updated(message):
|
|
ConfigurationManager.update(message.data)
|