Merge with develop and fix merge conflicts

This commit is contained in:
Griatch 2018-10-01 20:58:16 +02:00
commit 72f4fedcbe
148 changed files with 20005 additions and 2718 deletions

View file

@ -7,7 +7,6 @@ sets up all the networking features. (this is done automatically
by evennia/server/server_runner.py).
"""
from builtins import object
import time
import sys
@ -17,6 +16,7 @@ from twisted.web import static
from twisted.application import internet, service
from twisted.internet import reactor, defer
from twisted.internet.task import LoopingCall
from twisted.python.log import ILogObserver
import django
django.setup()
@ -33,6 +33,7 @@ from evennia.server.models import ServerConfig
from evennia.server import initial_setup
from evennia.utils.utils import get_evennia_version, mod_import, make_iter
from evennia.utils import logger
from evennia.comms import channelhandler
from evennia.server.sessionhandler import SESSIONS
@ -40,11 +41,6 @@ from django.utils.translation import ugettext as _
_SA = object.__setattr__
SERVER_PIDFILE = ""
if os.name == 'nt':
# For Windows we need to handle pid files manually.
SERVER_PIDFILE = os.path.join(settings.GAME_DIR, "server", 'server.pid')
# a file with a flag telling the server to restart after shutdown or not.
SERVER_RESTART = os.path.join(settings.GAME_DIR, "server", 'server.restart')
@ -53,16 +49,11 @@ SERVER_STARTSTOP_MODULE = mod_import(settings.AT_SERVER_STARTSTOP_MODULE)
# modules containing plugin services
SERVER_SERVICES_PLUGIN_MODULES = [mod_import(module) for module in make_iter(settings.SERVER_SERVICES_PLUGIN_MODULES)]
try:
WEB_PLUGINS_MODULE = mod_import(settings.WEB_PLUGINS_MODULE)
except ImportError:
WEB_PLUGINS_MODULE = None
print ("WARNING: settings.WEB_PLUGINS_MODULE not found - "
"copy 'evennia/game_template/server/conf/web_plugins.py to mygame/server/conf.")
#------------------------------------------------------------
# ------------------------------------------------------------
# Evennia Server settings
#------------------------------------------------------------
# ------------------------------------------------------------
SERVERNAME = settings.SERVERNAME
VERSION = get_evennia_version()
@ -83,6 +74,17 @@ IRC_ENABLED = settings.IRC_ENABLED
RSS_ENABLED = settings.RSS_ENABLED
WEBCLIENT_ENABLED = settings.WEBCLIENT_ENABLED
INFO_DICT = {"servername": SERVERNAME, "version": VERSION,
"amp": "", "errors": "", "info": "", "webserver": "", "irc_rss": ""}
try:
WEB_PLUGINS_MODULE = mod_import(settings.WEB_PLUGINS_MODULE)
except ImportError:
WEB_PLUGINS_MODULE = None
INFO_DICT["errors"] = (
"WARNING: settings.WEB_PLUGINS_MODULE not found - "
"copy 'evennia/game_template/server/conf/web_plugins.py to mygame/server/conf.")
# Maintenance function - this is called repeatedly by the server
@ -127,6 +129,10 @@ def _server_maintenance():
if _MAINTENANCE_COUNT % 3700 == 0:
# validate channels off-sync with scripts
evennia.CHANNEL_HANDLER.update()
if _MAINTENANCE_COUNT % (3600 * 7) == 0:
# drop database connection every 7 hrs to avoid default timeouts on MySQL
# (see https://github.com/evennia/evennia/issues/1376)
connection.close()
# handle idle timeouts
if _IDLE_TIMEOUT > 0:
@ -137,11 +143,6 @@ def _server_maintenance():
session.account.access(session.account, "noidletimeout", default=False):
SESSIONS.disconnect(session, reason=reason)
# Commenting this out, it is probably not needed
# with CONN_MAX_AGE set. Keeping it as a reminder
# if database-gone-away errors appears again /Griatch
# if _MAINTENANCE_COUNT % 18000 == 0:
# connection.close()
maintenance_task = LoopingCall(_server_maintenance)
maintenance_task.start(60, now=True) # call every minute
@ -173,15 +174,13 @@ class Evennia(object):
self.amp_protocol = None # set by amp factory
self.sessions = SESSIONS
self.sessions.server = self
self.process_id = os.getpid()
# Database-specific startup optimizations.
self.sqlite3_prep()
self.start_time = time.time()
# Run the initial setup if needed
self.run_initial_setup()
# initialize channelhandler
channelhandler.CHANNELHANDLER.update()
@ -193,18 +192,13 @@ class Evennia(object):
from twisted.internet.defer import Deferred
if hasattr(self, "web_root"):
d = self.web_root.empty_threadpool()
d.addCallback(lambda _: self.shutdown(_reactor_stopping=True))
d.addCallback(lambda _: self.shutdown("reload", _reactor_stopping=True))
else:
d = Deferred(lambda _: self.shutdown(_reactor_stopping=True))
d = Deferred(lambda _: self.shutdown("reload", _reactor_stopping=True))
d.addCallback(lambda _: reactor.stop())
reactor.callLater(1, d.callback, None)
reactor.sigInt = _wrap_sigint_handler
self.game_running = True
# track the server time
self.run_init_hooks()
# Server startup methods
def sqlite3_prep(self):
@ -212,7 +206,8 @@ class Evennia(object):
Optimize some SQLite stuff at startup since we
can't save it to the database.
"""
if ((".".join(str(i) for i in django.VERSION) < "1.2" and settings.DATABASES.get('default', {}).get('ENGINE') == "sqlite3") or
if ((".".join(str(i) for i in django.VERSION) < "1.2" and
settings.DATABASES.get('default', {}).get('ENGINE') == "sqlite3") or
(hasattr(settings, 'DATABASES') and
settings.DATABASES.get("default", {}).get('ENGINE', None) ==
'django.db.backends.sqlite3')):
@ -230,6 +225,8 @@ class Evennia(object):
typeclasses in the settings file and have them auto-update all
already existing objects.
"""
global INFO_DICT
# setting names
settings_names = ("CMDSET_CHARACTER", "CMDSET_ACCOUNT",
"BASE_ACCOUNT_TYPECLASS", "BASE_OBJECT_TYPECLASS",
@ -248,7 +245,7 @@ class Evennia(object):
#from evennia.accounts.models import AccountDB
for i, prev, curr in ((i, tup[0], tup[1]) for i, tup in enumerate(settings_compare) if i in mismatches):
# update the database
print(" %s:\n '%s' changed to '%s'. Updating unchanged entries in database ..." % (settings_names[i], prev, curr))
INFO_DICT['info'] = " %s:\n '%s' changed to '%s'. Updating unchanged entries in database ..." % (settings_names[i], prev, curr)
if i == 0:
ObjectDB.objects.filter(db_cmdset_storage__exact=prev).update(db_cmdset_storage=curr)
if i == 1:
@ -274,33 +271,37 @@ class Evennia(object):
def run_initial_setup(self):
"""
This is triggered by the amp protocol when the connection
to the portal has been established.
This attempts to run the initial_setup script of the server.
It returns if this is not the first time the server starts.
Once finished the last_initial_setup_step is set to -1.
"""
global INFO_DICT
last_initial_setup_step = ServerConfig.objects.conf('last_initial_setup_step')
if not last_initial_setup_step:
# None is only returned if the config does not exist,
# i.e. this is an empty DB that needs populating.
print(' Server started for the first time. Setting defaults.')
INFO_DICT['info'] = ' Server started for the first time. Setting defaults.'
initial_setup.handle_setup(0)
print('-' * 50)
elif int(last_initial_setup_step) >= 0:
# a positive value means the setup crashed on one of its
# modules and setup will resume from this step, retrying
# the last failed module. When all are finished, the step
# is set to -1 to show it does not need to be run again.
print(' Resuming initial setup from step %(last)s.' %
{'last': last_initial_setup_step})
INFO_DICT['info'] = ' Resuming initial setup from step {last}.'.format(
last=last_initial_setup_step)
initial_setup.handle_setup(int(last_initial_setup_step))
print('-' * 50)
def run_init_hooks(self):
def run_init_hooks(self, mode):
"""
Called every server start
Called by the amp client once receiving sync back from Portal
Args:
mode (str): One of shutdown, reload or reset
"""
from evennia.objects.models import ObjectDB
#from evennia.accounts.models import AccountDB
# update eventual changed defaults
self.update_defaults()
@ -308,47 +309,24 @@ class Evennia(object):
[o.at_init() for o in ObjectDB.get_all_cached_instances()]
[p.at_init() for p in AccountDB.get_all_cached_instances()]
mode = self.getset_restart_mode()
# call correct server hook based on start file value
if mode == 'reload':
# True was the old reload flag, kept for compatibilty
logger.log_msg("Server successfully reloaded.")
self.at_server_reload_start()
elif mode == 'reset':
# only run hook, don't purge sessions
self.at_server_cold_start()
elif mode in ('reset', 'shutdown'):
logger.log_msg("Evennia Server successfully restarted in 'reset' mode.")
elif mode == 'shutdown':
self.at_server_cold_start()
# clear eventual lingering session storages
ObjectDB.objects.clear_all_sessids()
logger.log_msg("Evennia Server successfully started.")
# always call this regardless of start type
self.at_server_start()
def getset_restart_mode(self, mode=None):
"""
This manages the flag file that tells the runner if the server is
reloading, resetting or shutting down.
Args:
mode (string or None, optional): Valid values are
'reload', 'reset', 'shutdown' and `None`. If mode is `None`,
no change will be done to the flag file.
Returns:
mode (str): The currently active restart mode, either just
set or previously set.
"""
if mode is None:
with open(SERVER_RESTART, 'r') as f:
# mode is either shutdown, reset or reload
mode = f.read()
else:
with open(SERVER_RESTART, 'w') as f:
f.write(str(mode))
return mode
@defer.inlineCallbacks
def shutdown(self, mode=None, _reactor_stopping=False):
def shutdown(self, mode='reload', _reactor_stopping=False):
"""
Shuts down the server from inside it.
@ -359,7 +337,6 @@ class Evennia(object):
at_shutdown hooks called but sessions will not
be disconnected.
'shutdown' - like reset, but server will not auto-restart.
None - keep currently set flag from flag file.
_reactor_stopping - this is set if server is stopped by a kill
command OR this method was already called
once - in both cases the reactor is
@ -370,10 +347,7 @@ class Evennia(object):
# once; we don't need to run the shutdown procedure again.
defer.returnValue(None)
mode = self.getset_restart_mode(mode)
from evennia.objects.models import ObjectDB
#from evennia.accounts.models import AccountDB
from evennia.server.models import ServerConfig
from evennia.utils import gametime as _GAMETIME_MODULE
@ -382,7 +356,8 @@ class Evennia(object):
ServerConfig.objects.conf("server_restart_mode", "reload")
yield [o.at_server_reload() for o in ObjectDB.get_all_cached_instances()]
yield [p.at_server_reload() for p in AccountDB.get_all_cached_instances()]
yield [(s.pause(manual_pause=False), s.at_server_reload()) for s in ScriptDB.get_all_cached_instances() if s.is_active]
yield [(s.pause(manual_pause=False), s.at_server_reload())
for s in ScriptDB.get_all_cached_instances() if s.is_active]
yield self.sessions.all_sessions_portal_sync()
self.at_server_reload_stop()
# only save monitor state on reload, not on shutdown/reset
@ -412,10 +387,6 @@ class Evennia(object):
# always called, also for a reload
self.at_server_stop()
if os.name == 'nt' and os.path.exists(SERVER_PIDFILE):
# for Windows we need to remove pid files manually
os.remove(SERVER_PIDFILE)
if hasattr(self, "web_root"): # not set very first start
yield self.web_root.empty_threadpool()
@ -427,6 +398,10 @@ class Evennia(object):
# we make sure the proper gametime is saved as late as possible
ServerConfig.objects.conf("runtime", _GAMETIME_MODULE.runtime())
def get_info_dict(self):
"Return the server info, for display."
return INFO_DICT
# server start/stop hooks
def at_server_start(self):
@ -452,13 +427,15 @@ class Evennia(object):
if SERVER_STARTSTOP_MODULE:
SERVER_STARTSTOP_MODULE.at_server_reload_start()
def at_post_portal_sync(self):
def at_post_portal_sync(self, mode):
"""
This is called just after the portal has finished syncing back data to the server
after reconnecting.
Args:
mode (str): One of reload, reset or shutdown.
"""
# one of reload, reset or shutdown
mode = self.getset_restart_mode()
from evennia.scripts.monitorhandler import MONITOR_HANDLER
MONITOR_HANDLER.restore(mode == 'reload')
@ -530,13 +507,16 @@ ServerConfig.objects.conf("server_starting_mode", True)
# what to execute from.
application = service.Application('Evennia')
if "--nodaemon" not in sys.argv:
# custom logging, but only if we are not running in interactive mode
logfile = logger.WeeklyLogFile(os.path.basename(settings.SERVER_LOG_FILE),
os.path.dirname(settings.SERVER_LOG_FILE))
application.setComponent(ILogObserver, logger.ServerLogObserver(logfile).emit)
# The main evennia server program. This sets up the database
# and is where we store all the other services.
EVENNIA = Evennia(application)
print('-' * 50)
print(' %(servername)s Server (%(version)s) started.' % {'servername': SERVERNAME, 'version': VERSION})
if AMP_ENABLED:
# The AMP protocol handles the communication between
@ -546,20 +526,20 @@ if AMP_ENABLED:
ifacestr = ""
if AMP_INTERFACE != '127.0.0.1':
ifacestr = "-%s" % AMP_INTERFACE
print(' amp (to Portal)%s: %s (internal)' % (ifacestr, AMP_PORT))
from evennia.server import amp
INFO_DICT["amp"] = 'amp %s: %s' % (ifacestr, AMP_PORT)
factory = amp.AmpServerFactory(EVENNIA)
amp_service = internet.TCPServer(AMP_PORT, factory, interface=AMP_INTERFACE)
amp_service.setName("EvenniaPortal")
from evennia.server import amp_client
factory = amp_client.AMPClientFactory(EVENNIA)
amp_service = internet.TCPClient(AMP_HOST, AMP_PORT, factory)
amp_service.setName('ServerAMPClient')
EVENNIA.services.addService(amp_service)
if WEBSERVER_ENABLED:
# Start a django-compatible webserver.
#from twisted.python import threadpool
from evennia.server.webserver import DjangoWebRoot, WSGIWebServer, Website, LockableThreadPool
# start a thread pool and define the root url (/) as a wsgi resource
@ -579,14 +559,16 @@ if WEBSERVER_ENABLED:
web_root = WEB_PLUGINS_MODULE.at_webserver_root_creation(web_root)
web_site = Website(web_root, logPath=settings.HTTP_LOG_FILE)
web_site.is_portal = False
INFO_DICT["webserver"] = ""
for proxyport, serverport in WEBSERVER_PORTS:
# create the webserver (we only need the port for this)
webserver = WSGIWebServer(threads, serverport, web_site, interface='127.0.0.1')
webserver.setName('EvenniaWebServer%s' % serverport)
EVENNIA.services.addService(webserver)
print(" webserver: %s (internal)" % serverport)
INFO_DICT["webserver"] += "webserver: %s" % serverport
ENABLED = []
if IRC_ENABLED:
@ -598,18 +580,11 @@ if RSS_ENABLED:
ENABLED.append('rss')
if ENABLED:
print(" " + ", ".join(ENABLED) + " enabled.")
INFO_DICT["irc_rss"] = ", ".join(ENABLED) + " enabled."
for plugin_module in SERVER_SERVICES_PLUGIN_MODULES:
# external plugin protocols
plugin_module.start_plugin_services(EVENNIA)
print('-' * 50) # end of terminal output
# clear server startup mode
ServerConfig.objects.conf("server_starting_mode", delete=True)
if os.name == 'nt':
# Windows only: Set PID file manually
with open(SERVER_PIDFILE, 'w') as f:
f.write(str(os.getpid()))