# Python X2Go (Client) API Documentation (v0.6.1.3)

## x2go.inifiles

# Source code for x2go.inifiles

    # -*- coding: utf-8 -*-

    # Copyright (C) 2010-2020 by Mike Gabriel <mike.gabriel@das-netzwerkteam.de>
    #
    # Python X2Go is free software; you can redistribute it and/or modify
    # it under the terms of the GNU Affero General Public License as published by
    # the Free Software Foundation; either version 3 of the License, or
    # (at your option) any later version.
    #
    # Python X2Go 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 Affero General Public License for more details.
    #
    # You should have received a copy of the GNU Affero General Public License
    # along with this program; if not, write to the
    # Free Software Foundation, Inc.,
    # 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
    #
    # This code was initially written by:
    #       2010 Dick Kniep <dick.kniep@lindix.nl>
    #
    # Other contributors:
    #       none so far

    """\
    X2GoProcessIniFile - helper class for parsing .ini files

    """
    __NAME__ = 'x2goinifiles-pylib'

    __package__ = 'x2go'
    __name__    = 'x2go.inifiles'

    # modules
    import os
    import sys
    try:
        import configparser
    except:
        import ConfigParser as configparser
    import types
    import io
    import copy

    # Python X2Go modules
    from .defaults import LOCAL_HOME as _current_home
    from . import log
    from . import utils

    class X2GoIniFile(object):
        """\
        Base class for processing the different ini files used by X2Go
        clients. Primarily used to standardize the content of the different
        X2Go client ini file (settings, printing, sessions, xconfig).

        If entries are omitted in an ini file, they are filled with
        default values (as hard coded in Python X2Go), so the resulting objects
        always contain the same fields.

        :param config_files: a list of configuration file names (e.g. a global filename and a user's home
            directory filename)
        :type config_files: ``list``
        :param defaults: a cascaded Python dicitionary structure with ini file defaults (to override
            Python X2Go's hard coded defaults in :mod:`x2go.defaults`
        :type defaults: ``dict``
        :param logger: you can pass an :class:`x2go.log.X2GoLogger` object to the
            :class:`x2go.inifiles.X2GoIniFile` constructor
        :type logger: :class:`x2go.log.X2GoLogger` instance
        :param loglevel: if no :class:`x2go.log.X2GoLogger` object has been supplied a new one will be
            constructed with the given loglevel
        :type loglevel: ``int``

        """

        def __init__(self, config_files, defaults=None, logger=None, loglevel=log.loglevel_DEFAULT):

            self.user_config_file = None
            self._write_user_config = True

            # make sure a None type gets turned into list type
            if not config_files:
                config_files = []

            if logger is None:
                self.logger = log.X2GoLogger(loglevel=loglevel)
            else:
                self.logger = copy.deepcopy(logger)
            self.logger.tag = __NAME__

            self.config_files = config_files

            if utils._checkIniFileDefaults(defaults):
                self.defaultValues = defaults
            else:
                self.defaultValues = {}

            # we purposefully do not inherit the SafeConfigParser class
            # here as we do not want to run into name conflicts between
            # X2Go ini file options and method / property names in
            # SafeConfigParser... This is a pre-cautious approach...
            self.iniConfig = configparser.SafeConfigParser()
            self.iniConfig.optionxform = str

            _create_file = False
            for file_name in self.config_files:
                file_name = os.path.normpath(file_name)
                if file_name.startswith(_current_home):
                    if not os.path.exists(file_name):
                        utils.touch_file(file_name)
                        _create_file = True
                    self.user_config_file = file_name
                    break
            self.load()

            if _create_file:
                self._write_user_config = True
                self._X2GoIniFile__write()

        def load(self):
            """\
            R(e-r)ead configuration file(s).

            """
            self.logger('proposed config files are %s' % self.config_files, loglevel=log.loglevel_INFO, )
            _found_config_files = self.iniConfig.read(self.config_files)
            self.logger('config files found: %s' % _found_config_files or 'none', loglevel=log.loglevel_INFO, )

            for file_name in _found_config_files:
                if file_name.startswith(os.path.normpath(_current_home)):
                    # we will use the first file found in the user's home dir for writing modifications
                    self.user_config_file = file_name
                    break

            self.config_files = _found_config_files
            self._fill_defaults()

        def __repr__(self):
            result = 'X2GoIniFile('
            for p in dir(self):
                if '__' in p or not p in self.__dict__: continue
                result += p + '=' + str(self.__dict__[p]) + ','
            result = result.strip(',')
            return result + ')'

        def _storeValue(self, section, key, value):
            """\
            Stores a value for a given section and key.

            This methods affects a SafeConfigParser object held in
            RAM. No configuration file is affected by this
            method. To write the configuration to disk use
            the :func:`write()` method.

            :param section: the ini file section
            :type section: ``str``
            :param key: the ini file key in the given section
            :type key: ``str``
            :param value: the value for the given section and key
            :type value: ``str``, ``list``, ``booAl``, ...

            """
            if type(value) is bool:
                self.iniConfig.set(section, key, str(int(value)))
            elif type(value) in (list, tuple):
                self.iniConfig.set(section, key, ", ".join(value))
            else:
                self.iniConfig.set(section, key, str(value))

        def _fill_defaults(self):
            """\
            Fills a ``SafeConfigParser`` object with the default ini file
            values as pre-defined in Python X2Go or. This SafeConfigParser
            object is held in RAM. No configuration file is affected by this
            method.

            """
            for section, sectionvalue in list(self.defaultValues.items()):
                for key, value in list(sectionvalue.items()):
                    if self.iniConfig.has_option(section, key): continue
                    if not self.iniConfig.has_section(section):
                        self.iniConfig.add_section(section)
                    self._storeValue(section, key, value)

        def update_value(self, section, key, value):
            """\
            Change a value for a given section and key. This method
            does not have any effect on configuration files.

            :param section: the ini file section
            :type section: ``str``
            :param key: the ini file key in the given section
            :type key: ``str``
            :param value: the value for the given section and key
            :type value: ``str``, ``list``, ``bool``, ...

            """
            if not self.iniConfig.has_section(section):
                self.iniConfig.add_section(section)
            self._storeValue(section, key, value)
            self._write_user_config = True
        __update_value = update_value

        def write(self):
            """\
            Write the ini file modifications (SafeConfigParser object) from RAM to disk.

            For writing the first of the ``config_files`` specified on instance construction
            that is writable will be used.

            :returns: ``True`` if the user config file has been successfully written, ``False`` otherwise.

            :rtype: ``bool``

            """
            if self.user_config_file and self._write_user_config:
                try:
                    if sys.version_info[0] >= 3:
                        fd = open(self.user_config_file.encode(), 'w')
                    else:
                        fd = open(self.user_config_file.encode(), 'wb')
                    self.iniConfig.write(fd)
                    fd.close()
                    self._write_user_config = False
                    return True
                except Exception as e:
                    self.logger('failure during write operation of %s; reported error: %s' % (self.user_config_file, e), loglevel=log.loglevel_ERROR, )
            return False
        __write = write

        def get_type(self, section, key):
            """\
            Retrieve a value type for a given section and key. The returned
            value type is based on the default values dictionary.

            :param section: the ini file section
            :type section: ``str``
            :param key: the ini file key in the given section
            :type key: ``str``
            :returns: a Python variable type
            :rtype: class

            """
            return type(self.defaultValues[section][key])

        def get_value(self, section, key, key_type=None):
            """\
            Retrieve a value for a given section and key.

            :param section: the ini file section
            :type section: ``str``
            :param key: the ini file key in the given section
            :type key: ``str``
            :param key_type: Python data type of the given key (Default value = None)
            :returns: the value for the given section and key
            :rtype: class

            """
            if key_type is None:
                key_type = self.get_type(section, key)
            if self.iniConfig.has_option(section, key):
                if key_type is bool:
                    return self.iniConfig.getboolean(section, key)
                elif key_type is int:
                    return self.iniConfig.getint(section, key)
                elif key_type is list:
                    _val = self.iniConfig.get(section, key)
                    _val = _val.strip()
                    if _val.startswith('[') and _val.endswith(']'):
                        return eval(_val)
                    elif ',' in _val:
                        _val = [ v.strip() for v in _val.split(',') ]
                    else:
                        _val = [ _val ]
                    return _val
                else:
                    _val = self.iniConfig.get(section, key)
                    return _val
        get = get_value
        __call__ = get_value

        @property
        def printable_config_file(self):
            """\
            Returns a printable configuration file as a multi-line string.

            """
            if sys.version_info[0] >= 3:
                stdout = io.BytesIO()
            else:
                stdout = io.StringIO()
            self.iniConfig.write(stdout)
            _ret_val = stdout.getvalue()
            stdout.close()
            return _ret_val