aboutsummaryrefslogtreecommitdiffstats
path: root/fg21sim/webui/handlers/configs.py
diff options
context:
space:
mode:
Diffstat (limited to 'fg21sim/webui/handlers/configs.py')
-rw-r--r--fg21sim/webui/handlers/configs.py265
1 files changed, 265 insertions, 0 deletions
diff --git a/fg21sim/webui/handlers/configs.py b/fg21sim/webui/handlers/configs.py
new file mode 100644
index 0000000..2e6c3a9
--- /dev/null
+++ b/fg21sim/webui/handlers/configs.py
@@ -0,0 +1,265 @@
+# Copyright (c) 2016 Weitian LI <liweitianux@live.com>
+# MIT license
+
+"""
+Handle the configurations operations with the client.
+"""
+
+import os
+import logging
+
+from ...errors import ConfigError
+
+
+logger = logging.getLogger(__name__)
+
+
+class ConfigsHandler:
+ """
+ Handle the "configs" type of messages from the client.
+ """
+ def __init__(self, configs):
+ self.configs = configs
+
+ def handle_message(self, msg):
+ """
+ Handle the message of type "configs", which request to get or
+ set some configurations by the client.
+
+ TODO:
+ * improve the description ...
+ * split these handling functions into a separate class in a module
+
+ Parameters
+ ----------
+ msg : dict
+ A dictionary parsed from the incoming JSON message, which
+ generally has the following syntax:
+ ``{"type": "configs", "action": <action>, "data": <data>}``
+ where the ``<action>`` is ``set`` or ``get``, and the ``<data>``
+ is a list of config keys or a dict of config key-value pairs.
+
+ Returns
+ -------
+ response : dict
+ A dictionary parsed from the incoming JSON message, which
+ generally has the following syntax:
+ ``{"type": "configs", "action": <action>,
+ "data": <data>, "errors": <errors>}``
+ where the ``<action>`` is the same as input, the ``<data>`` is
+ a list of config keys or a dict of config key-value pairs, and
+ ``<errors>`` contains the error message for the invalid config
+ values.
+ """
+ try:
+ msg_type = msg["type"]
+ msg_action = msg["action"]
+ response = {"type": msg_type, "action": msg_action}
+ logger.info("WebSocket: handle message: " +
+ "type: {0}, action: {1}".format(msg_type, msg_action))
+ if msg_action == "get":
+ # Get the values of the specified options
+ try:
+ data, errors = self._get_configs(keys=msg["keys"])
+ response["success"] = True
+ response["data"] = data
+ response["errors"] = errors
+ except KeyError:
+ response["success"] = False
+ response["error"] = "'keys' is missing"
+ elif msg_action == "set":
+ # Set the values of the specified options
+ try:
+ errors = self._set_configs(data=msg["data"])
+ response["success"] = True
+ response["data"] = {} # be more consistent
+ response["errors"] = errors
+ except KeyError:
+ response["success"] = False
+ response["error"] = "'data' is missing"
+ elif msg_action == "reset":
+ # Reset the configurations to the defaults
+ self._reset_configs()
+ response["success"] = True
+ elif msg_action == "load":
+ # Load the supplied user configuration file
+ try:
+ success, error = self._load_configs(msg["userconfig"])
+ response["success"] = success
+ if not success:
+ response["error"] = error
+ except KeyError:
+ response["success"] = False
+ response["error"] = "'userconfig' is missing"
+ elif msg_action == "save":
+ # Save current configurations to file
+ try:
+ success, error = self._save_configs(msg["outfile"],
+ msg["clobber"])
+ response["success"] = success
+ if not success:
+ response["error"] = error
+ except KeyError:
+ response["success"] = False
+ response["error"] = "'outfile' or 'clobber' is missing"
+ else:
+ logger.warning("WebSocket: " +
+ "unknown action: {0}".format(msg_action))
+ response["success"] = False
+ response["error"] = "unknown action: {0}".format(msg_action)
+ except KeyError:
+ # Received message has wrong syntax/format
+ response = {"success": False,
+ "type": msg_type,
+ "error": "no action specified"}
+ #
+ logger.debug("WebSocket: response: {0}".format(response))
+ return response
+
+ def _get_configs(self, keys=None):
+ """Get the values of the config options specified by the given keys.
+
+ Parameters
+ ----------
+ keys : list[str], optional
+ A list of keys specifying the config options whose values will
+ be obtained.
+ If ``keys=None``, then all the configurations values are dumped.
+
+ Returns
+ -------
+ data : dict
+ A dictionary with keys the same as the input keys, and values
+ the corresponding config option values.
+ errors : dict
+ When error occurs (e.g., invalid key), then the specific errors
+ with details are stored in this dictionary.
+
+ NOTE
+ ----
+ Do not forget the ``userconfig`` option.
+ """
+ if keys is None:
+ # Dump all the configurations
+ data = self.configs.dump(flatten=True)
+ data["userconfig"] = self.configs.userconfig
+ errors = {}
+ else:
+ data = {}
+ errors = {}
+ for key in keys:
+ if key == "userconfig":
+ data["userconfig"] = self.configs.userconfig
+ else:
+ try:
+ data[key] = self.configs.getn(key)
+ except KeyError as e:
+ errors[key] = str(e)
+ #
+ return (data, errors)
+
+ def _set_configs(self, data):
+ """Set the values of the config options specified by the given keys
+ to the corresponding supplied data.
+
+ NOTE
+ ----
+ The ``userconfig`` needs special handle.
+ The ``workdir`` and ``configfile`` options should be ignored.
+
+ Parameters
+ ----------
+ data : dict
+ A dictionary of key-value pairs, with keys specifying the config
+ options whose value will be changed, and values the new values
+ to which config options will be set.
+ NOTE:
+ If want to set the ``userconfig`` option, an *absolute path*
+ must be provided.
+
+ Returns
+ -------
+ errors : dict
+ When error occurs (e.g., invalid key, invalid values), then the
+ specific errors with details are stored in this dictionary.
+ """
+ errors = {}
+ for key, value in data.items():
+ if key in ["workdir", "configfile"]:
+ # Ignore "workdir" and "configfile"
+ continue
+ elif key == "userconfig":
+ if os.path.isabs(os.path.expanduser(value)):
+ self.configs.userconfig = value
+ else:
+ errors[key] = "Not an absolute path"
+ else:
+ try:
+ self.configs.setn(key, value)
+ except KeyError as e:
+ errors[key] = str(e)
+ # NOTE:
+ # Check the whole configurations after all provided options are
+ # updated, and merge the validation errors.
+ __, cherr = self.configs.check_all(raise_exception=False)
+ errors.update(cherr)
+ return errors
+
+ def _reset_configs(self):
+ """Reset the configurations to the defaults."""
+ self.configs.reset()
+
+ def _load_configs(self, userconfig):
+ """Load configurations from the provided user configuration file.
+
+ Parameters
+ ----------
+ userconfig: str
+ The filepath to the user configuration file, which must be
+ an *absolute path*.
+
+ Returns
+ -------
+ success : bool
+ ``True`` if the operation succeeded, otherwise, ``False``.
+ error : str
+ If failed, this ``error`` saves the details, otherwise, ``None``.
+ """
+ success = False
+ error = None
+ if os.path.isabs(os.path.expanduser(userconfig)):
+ try:
+ self.configs.read_userconfig(userconfig)
+ success = True
+ except ConfigError as e:
+ error = str(e)
+ else:
+ error = "Not an absolute path"
+ return (success, error)
+
+ def _save_configs(self, outfile, clobber=False):
+ """Save current configurations to file.
+
+ Parameters
+ ----------
+ outfile: str
+ The filepath to the output configuration file, which must be
+ an *absolute path*.
+ clobber : bool, optional
+ Whether overwrite the output file if already exists?
+
+ Returns
+ -------
+ success : bool
+ ``True`` if the operation succeeded, otherwise, ``False``.
+ error : str
+ If failed, this ``error`` saves the details, otherwise, ``None``.
+ """
+ success = False
+ error = None
+ try:
+ self.configs.save(outfile, clobber=clobber)
+ success = True
+ except (ValueError, OSError) as e:
+ error = str(e)
+ return (success, error)