#!/usr/bin/env python3
# The MIT License (MIT)
#
# Copyright (c) 2014-2018 <see AUTHORS.txt>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
# Modules
# ------------------------------------------------
# std
import os
import logging
import configparser
# local
from .worlds import WorldWrapper
# Backward compatibility
# ------------------------------------------------
try:
FileNotFoundError
except NameError:
FileNotFoundError = OSError
# Data
# ------------------------------------------------
__all__ = [
"ConfigParser",
"MainConfiguration",
"ServerConfiguration",
"WorldConfiguration",
"Configuration"
]
log = logging.getLogger(__file__)
# Classes
# ------------------------------------------------
[docs]class ConfigParser(configparser.ConfigParser):
"""
Extends the standard Python :class:`configparser.ConfigParser` by some
useful methods.
:param str path:
The path to the configuration file. This file is used, when you call
:meth:`read` or :meth:`write`.
"""
def __init__(self, path):
"""
"""
super().__init__(
allow_no_value = False,
strict = True,
empty_lines_in_values = False,
interpolation=configparser.ExtendedInterpolation()
)
self._path = path
return None
[docs] def epilog(self):
"""
Returns a comment, which is written at the begin of a configuration
file.
"""
return ""
[docs] def path(self):
"""
Returns the path of the configuration file.
"""
return self._path
[docs] def read(self):
"""
Reads the configuration from :meth:`path`.
"""
try:
with open(self._path, "r") as file:
super().read_file(file)
except (FileNotFoundError, IOError):
pass
return None
[docs] def write(self):
"""
Writes the configuration into :meth:`path`.
"""
# Get the comment prefix.
comment_prefix = self._comment_prefixes[0]
# Convert the EPILOG to comment lines.
epilog = self.epilog().split("\n")
epilog = [comment_prefix + " " + line for line in epilog]
epilog = "\n".join(epilog) + "\n\n"
# Write the configuration into the file.
with open(self._path, "w") as file:
file.write(epilog)
super().write(file)
return None
[docs] def remove(self):
"""
Removes the configuration file from the file system.
"""
try:
os.remove(self.path())
except FileNotFoundError:
log.error(
"could not remove the '%s' configuration file.", self.path()
)
return None
[docs]class MainConfiguration(ConfigParser):
"""
Handles the :file:`main.conf` configuration file.
This file includes the configuration for the EMSM Application and the
plugins.
The EMSM owns the ``[emsm]`` section and each plugin has its own section
with the plugin name.
.. code-block:: ini
[emsm]
user = minecraft
timeout = 0
screenrc =
[backups]
include_server = ...
# ...
"""
def __init__(self, path):
"""
"""
super().__init__(path)
# Add the default configuration for the EMSM.
self.add_section("emsm")
self["emsm"]["user"] = "minecraft"
self["emsm"]["timeout"] = "0"
self["emsm"]["screenrc"] = ""
return None
[docs] def epilog(self):
epilog = "\n".join([
"This file contains the settings for the EMSM core application and",
"the plugins.",
"",
"The section of the EMSM looks like this per default:",
"",
"[emsm]",
"user = minecraft",
"timeout = -1",
"screenrc = ",
"",
"The configuration section of each plugin is titled with the plugins",
"name.",
])
return epilog
[docs]class ServerConfiguration(ConfigParser):
"""
Handles the *server.conf* configuration file, which allows the user
to overwrite the default EMSM settings for a server wrapper like
the *url* or the *start command*.
.. seealso::
* :meth:`emsm.core.server.BaseServerWrapper.conf`
* :meth:`emsm.core.conf.WorldConfiguration`
"""
[docs] def epilog(self):
epilog = "\n".join([
"[server name]",
"url = string",
"start_command = string",
"",
"The EMSM comes with tested default settings for each server.",
"so you should only overwrite these values, if you have to.",
])
return epilog
[docs]class WorldConfiguration(ConfigParser):
"""
Handles a configuration file for *one* world and allows the user
to set custom configuration values for each plugin, server and
the EMSM.
:arg str path:
"""
def __init__(self, path):
"""
"""
super().__init__(path)
# Add the default options for the world.
self.add_section("world")
self["world"]["stop_timeout"] = "10"
self["world"]["stop_delay"] = "5"
self["world"]["stop_message"] = "The server is going down.\n"\
"Hope to see you soon."
self["world"]["server"] = "vanilla 1.13"
return None
[docs] def epilog(self):
filename = os.path.basename(self.path())
world_name = filename[:-len(".world.conf")]
epilog = "\n".join([
"This configuration file contains the configuration for the world",
"",
" **{world_name}**",
"",
"This file can be used to override global configuration values in ",
"the *server.conf* and *main.conf* configuration files.",
"",
"[world]",
"stop_timeout = int",
"stop_message = string",
"stop_delay = int",
"server = a server in server.conf",
"",
"Custom options for the backups plugin:",
"",
"[plugin:backups]",
"archive_format = bztar",
"max_storage_size = 30",
"",
"Custom options for the vanilla 1.8 server:",
"",
"[server:vanilla 1.8]",
"start_command = java -Xms512m -Xmx1G -jar {{server_exe}} nogui",
""
]).format(world_name=world_name)
return epilog
[docs]class Configuration(object):
"""
Manages all configuration files of an EMSM application
object.
.. seealso::
* :meth:`emsm.core.application.Application.conf`
* :meth:`emsm.core.paths.Pathsystem.conf_dir`
"""
def __init__(self, app):
"""
"""
self._app = app
self._dir = app.paths().conf()
self._main = MainConfiguration(os.path.join(self._dir, "main.conf"))
self._server = ServerConfiguration(os.path.join(self._dir, "server.conf"))
# Load all *.world.conf configuration files
# We ignore files, that start with an underscore.
self._worlds = dict()
if os.path.exists(self._dir):
for name in os.listdir(self._dir):
path = os.path.join(self._dir, name)
if not os.path.isfile(path):
continue
if not name.endswith(".world.conf"):
continue
if name.startswith("_"):
continue
world_name = name[:-len(".world.conf")]
self._worlds[world_name] = WorldConfiguration(path)
WorldWrapper.world_uninstalled.connect(self.__remove_world)
return None
def __remove_world(self, world):
"""
Removes the :class:`WorldConfiguration` of *world* from the internal
map.
"""
if world.name() in self._worlds:
del self._worlds[world.name()]
return None
[docs] def main(self):
"""
Returns the :class:`MainConfiguration`.
"""
return self._main
[docs] def server(self):
"""
Returns the :class:`ServerConfiguration`.
"""
return self._server
[docs] def worlds(self):
"""
Returns a list with all :class:`WorldConfiguration` objects.
"""
return list(self._worlds.values())
[docs] def world(self, name):
"""
Returns the :class:`WorldConfiguration` for the world with the name
*name* and ``None``, if there is not such a world.
"""
return self._worlds.get(name)
[docs] def list_worlds(self):
"""
Returns a list with the names of all worlds, for which a configuration
file has been found.
"""
return list(self._worlds.keys())
[docs] def read(self):
"""
Reads all configration files.
"""
log.info("reading configuration ...")
# Don't change the order!
self._main.read()
self._server.read()
for conf in self._worlds.values():
conf.read()
return None
[docs] def write(self):
"""
Saves all configuration values.
"""
log.info("writing configuration ...")
self._main.write()
self._server.write()
for conf in self._worlds.values():
conf.write()
return None