Fix/refactor initial_setup function. Resolve #2438.
This commit is contained in:
parent
d3cc7cf630
commit
b9771e701f
4 changed files with 135 additions and 126 deletions
|
|
@ -1,7 +1,7 @@
|
||||||
"""
|
"""
|
||||||
This module handles initial database propagation, which is only run the first
|
This module handles initial database propagation, which is only run the first time the game starts.
|
||||||
time the game starts. It will create some default channels, objects, and
|
It will create some default objects (notably give #1 its evennia-specific properties, and create the
|
||||||
other things.
|
Limbo room). It will also hooks, and then perform an initial restart.
|
||||||
|
|
||||||
Everything starts at handle_setup()
|
Everything starts at handle_setup()
|
||||||
"""
|
"""
|
||||||
|
|
@ -41,16 +41,22 @@ WARNING_POSTGRESQL_FIX = """
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
def get_god_account():
|
def _get_superuser_account():
|
||||||
"""
|
"""
|
||||||
Creates the god user and don't take no for an answer.
|
Get the superuser (created at the command line) and don't take no for an answer.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Account: The first superuser (User #1).
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
AccountDB.DoesNotExist: If the superuser couldn't be found.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
god_account = AccountDB.objects.get(id=1)
|
superuser = AccountDB.objects.get(id=1)
|
||||||
except AccountDB.DoesNotExist:
|
except AccountDB.DoesNotExist:
|
||||||
raise AccountDB.DoesNotExist(ERROR_NO_SUPERUSER)
|
raise AccountDB.DoesNotExist(ERROR_NO_SUPERUSER)
|
||||||
return god_account
|
return superuser
|
||||||
|
|
||||||
|
|
||||||
def create_objects():
|
def create_objects():
|
||||||
|
|
@ -63,84 +69,68 @@ def create_objects():
|
||||||
|
|
||||||
# Set the initial User's account object's username on the #1 object.
|
# Set the initial User's account object's username on the #1 object.
|
||||||
# This object is pure django and only holds name, email and password.
|
# This object is pure django and only holds name, email and password.
|
||||||
god_account = get_god_account()
|
superuser = _get_superuser_account()
|
||||||
|
from evennia.objects.models import ObjectDB
|
||||||
|
|
||||||
# Create an Account 'user profile' object to hold eventual
|
# Create an Account 'user profile' object to hold eventual
|
||||||
# mud-specific settings for the AccountDB object.
|
# mud-specific settings for the AccountDB object.
|
||||||
account_typeclass = settings.BASE_ACCOUNT_TYPECLASS
|
account_typeclass = settings.BASE_ACCOUNT_TYPECLASS
|
||||||
|
|
||||||
# run all creation hooks on god_account (we must do so manually
|
# run all creation hooks on superuser (we must do so manually
|
||||||
# since the manage.py command does not)
|
# since the manage.py command does not)
|
||||||
god_account.swap_typeclass(account_typeclass, clean_attributes=True)
|
superuser.swap_typeclass(account_typeclass, clean_attributes=True)
|
||||||
god_account.basetype_setup()
|
superuser.basetype_setup()
|
||||||
god_account.at_account_creation()
|
superuser.at_account_creation()
|
||||||
god_account.locks.add(
|
superuser.locks.add(
|
||||||
"examine:perm(Developer);edit:false();delete:false();boot:false();msg:all()"
|
"examine:perm(Developer);edit:false();delete:false();boot:false();msg:all()"
|
||||||
)
|
)
|
||||||
# this is necessary for quelling to work correctly.
|
# this is necessary for quelling to work correctly.
|
||||||
god_account.permissions.add("Developer")
|
superuser.permissions.add("Developer")
|
||||||
|
|
||||||
# Limbo is the default "nowhere" starting room
|
# Limbo is the default "nowhere" starting room
|
||||||
|
|
||||||
# Create the in-game god-character for account #1 and set
|
# Create the in-game god-character for account #1 and set
|
||||||
# it to exist in Limbo.
|
# it to exist in Limbo.
|
||||||
character_typeclass = settings.BASE_CHARACTER_TYPECLASS
|
character_typeclass = settings.BASE_CHARACTER_TYPECLASS
|
||||||
god_character = create.create_object(character_typeclass, key=god_account.username, nohome=True)
|
try:
|
||||||
|
superuser_character = ObjectDB.objects.get(id=1)
|
||||||
|
except ObjectDB.DoesNotExist:
|
||||||
|
superuser_character = create.create_object(
|
||||||
|
character_typeclass, key=superuser.username, nohome=True)
|
||||||
|
|
||||||
god_character.id = 1
|
superuser_character.db_typeclass_path = character_typeclass
|
||||||
god_character.save()
|
superuser_character.db.desc = _("This is User #1.")
|
||||||
god_character.db.desc = _("This is User #1.")
|
superuser_character.locks.add(
|
||||||
god_character.locks.add(
|
|
||||||
"examine:perm(Developer);edit:false();delete:false();boot:false();msg:all();puppet:false()"
|
"examine:perm(Developer);edit:false();delete:false();boot:false();msg:all();puppet:false()"
|
||||||
)
|
)
|
||||||
# we set this low so that quelling is more useful
|
# we set this low so that quelling is more useful
|
||||||
god_character.permissions.add("Player")
|
superuser_character.permissions.add("Player")
|
||||||
|
superuser_character.save()
|
||||||
|
|
||||||
god_account.attributes.add("_first_login", True)
|
superuser.attributes.add("_first_login", True)
|
||||||
god_account.attributes.add("_last_puppet", god_character)
|
superuser.attributes.add("_last_puppet", superuser_character)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
god_account.db._playable_characters.append(god_character)
|
superuser.db._playable_characters.append(superuser_character)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
god_account.db_playable_characters = [god_character]
|
superuser.db_playable_characters = [superuser_character]
|
||||||
|
|
||||||
room_typeclass = settings.BASE_ROOM_TYPECLASS
|
room_typeclass = settings.BASE_ROOM_TYPECLASS
|
||||||
limbo_obj = create.create_object(room_typeclass, _("Limbo"), nohome=True)
|
try:
|
||||||
limbo_obj.id = 2
|
limbo_obj = ObjectDB.objects.get(id=2)
|
||||||
limbo_obj.save()
|
except ObjectDB.DoesNotExist:
|
||||||
|
limbo_obj = create.create_object(room_typeclass, _("Limbo"), nohome=True)
|
||||||
|
|
||||||
|
limbo_obj.db_typeclass_path = room_typeclass
|
||||||
limbo_obj.db.desc = LIMBO_DESC.strip()
|
limbo_obj.db.desc = LIMBO_DESC.strip()
|
||||||
limbo_obj.save()
|
limbo_obj.save()
|
||||||
|
|
||||||
# Now that Limbo exists, try to set the user up in Limbo (unless
|
# Now that Limbo exists, try to set the user up in Limbo (unless
|
||||||
# the creation hooks already fixed this).
|
# the creation hooks already fixed this).
|
||||||
if not god_character.location:
|
if not superuser_character.location:
|
||||||
god_character.location = limbo_obj
|
superuser_character.location = limbo_obj
|
||||||
if not god_character.home:
|
if not superuser_character.home:
|
||||||
god_character.home = limbo_obj
|
superuser_character.home = limbo_obj
|
||||||
|
|
||||||
|
|
||||||
def create_channels():
|
|
||||||
"""
|
|
||||||
Creates some sensible default channels.
|
|
||||||
|
|
||||||
"""
|
|
||||||
logger.log_info("Initial setup: Creating default channels ...")
|
|
||||||
|
|
||||||
goduser = get_god_account()
|
|
||||||
|
|
||||||
channel_mudinfo = settings.CHANNEL_MUDINFO
|
|
||||||
if channel_mudinfo:
|
|
||||||
channel = create.create_channel(**channel_mudinfo)
|
|
||||||
channel.connect(goduser)
|
|
||||||
|
|
||||||
channel_connectinfo = settings.CHANNEL_CONNECTINFO
|
|
||||||
if channel_connectinfo:
|
|
||||||
channel = create.create_channel(**channel_connectinfo)
|
|
||||||
|
|
||||||
for channeldict in settings.DEFAULT_CHANNELS:
|
|
||||||
channel = create.create_channel(**channeldict)
|
|
||||||
channel.connect(goduser)
|
|
||||||
|
|
||||||
|
|
||||||
def at_initial_setup():
|
def at_initial_setup():
|
||||||
"""
|
"""
|
||||||
|
|
@ -188,49 +178,46 @@ def reset_server():
|
||||||
SESSIONS.portal_reset_server()
|
SESSIONS.portal_reset_server()
|
||||||
|
|
||||||
|
|
||||||
def handle_setup(last_step):
|
def handle_setup(last_step=None):
|
||||||
"""
|
"""
|
||||||
Main logic for the module. It allows for restarting the
|
Main logic for the module. It allows for restarting the
|
||||||
initialization at any point if one of the modules should crash.
|
initialization at any point if one of the modules should crash.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
last_step (int): The last stored successful step, for starting
|
last_step (str, None): The last stored successful step, for starting
|
||||||
over on errors. If `< 0`, initialization has finished and no
|
over on errors. None if starting from scratch. If this is 'done',
|
||||||
steps need to be redone.
|
the function will exit immediately.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
if last_step in('done', -1):
|
||||||
if last_step < 0:
|
|
||||||
# this means we don't need to handle setup since
|
# this means we don't need to handle setup since
|
||||||
# it already ran sucessfully once.
|
# it already ran sucessfully once. -1 is the legacy
|
||||||
|
# value for existing databases.
|
||||||
return
|
return
|
||||||
# if None, set it to 0
|
|
||||||
last_step = last_step or 0
|
|
||||||
|
|
||||||
# setting up the list of functions to run
|
# setup sequence
|
||||||
setup_queue = [create_objects, create_channels, at_initial_setup, collectstatic, reset_server]
|
setup_sequence = {
|
||||||
|
'create_objects': create_objects,
|
||||||
|
'at_initial_setup': at_initial_setup,
|
||||||
|
'collectstatic': collectstatic,
|
||||||
|
'done': reset_server,
|
||||||
|
}
|
||||||
|
|
||||||
# step through queue, from last completed function
|
# determine the sequence so we can skip ahead
|
||||||
for num, setup_func in enumerate(setup_queue[last_step:]):
|
steps = list(setup_sequence)
|
||||||
# run the setup function. Note that if there is a
|
steps = steps[steps.index(last_step) + 1 if last_step is not None else 0:]
|
||||||
# traceback we let it stop the system so the config
|
|
||||||
# step is not saved.
|
|
||||||
|
|
||||||
|
# step through queue from last completed function. Once completed,
|
||||||
|
# the 'done' key should be set.
|
||||||
|
for stepname in steps:
|
||||||
try:
|
try:
|
||||||
setup_func()
|
setup_sequence[stepname]()
|
||||||
except Exception:
|
except Exception:
|
||||||
if last_step + num == 1:
|
# we re-raise to make sure to stop startup
|
||||||
from evennia.objects.models import ObjectDB
|
|
||||||
|
|
||||||
for obj in ObjectDB.objects.all():
|
|
||||||
obj.delete()
|
|
||||||
elif last_step + num == 2:
|
|
||||||
from evennia.comms.models import ChannelDB
|
|
||||||
|
|
||||||
ChannelDB.objects.all().delete()
|
|
||||||
raise
|
raise
|
||||||
# save this step
|
else:
|
||||||
ServerConfig.objects.conf("last_initial_setup_step", last_step + num + 1)
|
# save the step
|
||||||
# We got through the entire list. Set last_step to -1 so we don't
|
ServerConfig.objects.conf("last_initial_setup_step", stepname)
|
||||||
# have to run this again.
|
if stepname == 'done':
|
||||||
ServerConfig.objects.conf("last_initial_setup_step", -1)
|
# always exit on 'done'
|
||||||
|
break
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ evennia/server/server_runner.py).
|
||||||
import time
|
import time
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
|
import traceback
|
||||||
|
|
||||||
from twisted.web import static
|
from twisted.web import static
|
||||||
from twisted.application import internet, service
|
from twisted.application import internet, service
|
||||||
|
|
@ -331,25 +332,60 @@ class Evennia:
|
||||||
to the portal has been established.
|
to the portal has been established.
|
||||||
This attempts to run the initial_setup script of the server.
|
This attempts to run the initial_setup script of the server.
|
||||||
It returns if this is not the first time the server starts.
|
It returns if this is not the first time the server starts.
|
||||||
Once finished the last_initial_setup_step is set to -1.
|
Once finished the last_initial_setup_step is set to 'done'
|
||||||
|
|
||||||
"""
|
"""
|
||||||
global INFO_DICT
|
global INFO_DICT
|
||||||
initial_setup = importlib.import_module(settings.INITIAL_SETUP_MODULE)
|
initial_setup = importlib.import_module(settings.INITIAL_SETUP_MODULE)
|
||||||
last_initial_setup_step = ServerConfig.objects.conf("last_initial_setup_step")
|
last_initial_setup_step = ServerConfig.objects.conf("last_initial_setup_step")
|
||||||
if not last_initial_setup_step:
|
try:
|
||||||
# None is only returned if the config does not exist,
|
if not last_initial_setup_step:
|
||||||
# i.e. this is an empty DB that needs populating.
|
# None is only returned if the config does not exist,
|
||||||
INFO_DICT["info"] = " Server started for the first time. Setting defaults."
|
# i.e. this is an empty DB that needs populating.
|
||||||
initial_setup.handle_setup(0)
|
INFO_DICT["info"] = " Server started for the first time. Setting defaults."
|
||||||
elif int(last_initial_setup_step) >= 0:
|
initial_setup.handle_setup()
|
||||||
# a positive value means the setup crashed on one of its
|
elif last_initial_setup_step not in ('done', -1):
|
||||||
# modules and setup will resume from this step, retrying
|
# last step crashed, so we weill resume from this step.
|
||||||
# the last failed module. When all are finished, the step
|
# modules and setup will resume from this step, retrying
|
||||||
# is set to -1 to show it does not need to be run again.
|
# the last failed module. When all are finished, the step
|
||||||
INFO_DICT["info"] = " Resuming initial setup from step {last}.".format(
|
# is set to 'done' to show it does not need to be run again.
|
||||||
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))
|
)
|
||||||
|
initial_setup.handle_setup(last_initial_setup_step)
|
||||||
|
except Exception:
|
||||||
|
# stop server if this happens.
|
||||||
|
print(traceback.format_exc())
|
||||||
|
print("Error in initial setup. Stopping Server + Portal.")
|
||||||
|
self.sessions.portal_shutdown()
|
||||||
|
|
||||||
|
def create_default_channels(self):
|
||||||
|
"""
|
||||||
|
check so default channels exist on every restart, create if not.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from evennia.comms.models import ChannelDB
|
||||||
|
from evennia.accounts.models import AccountDB
|
||||||
|
from evennia.utils.create import create_channel
|
||||||
|
|
||||||
|
superuser = AccountDB.objects.get(id=1)
|
||||||
|
# mudinfo
|
||||||
|
mudinfo_chan = settings.CHANNEL_MUDINFO
|
||||||
|
if mudinfo_chan:
|
||||||
|
if not ChannelDB.objects.filter(db_key=mudinfo_chan["key"]):
|
||||||
|
channel = create_channel(**mudinfo_chan)
|
||||||
|
channel.connect(superuser)
|
||||||
|
# connectinfo
|
||||||
|
connectinfo_chan = settings.CHANNEL_MUDINFO
|
||||||
|
if connectinfo_chan:
|
||||||
|
if not ChannelDB.objects.filter(db_key=mudinfo_chan["key"]):
|
||||||
|
channel = create_channel(**connectinfo_chan)
|
||||||
|
# default channels
|
||||||
|
for chan_info in settings.DEFAULT_CHANNELS:
|
||||||
|
if not ChannelDB.objects.filter(db_key=chan_info["key"]):
|
||||||
|
channel = create_channel(**chan_info)
|
||||||
|
channel.connect(superuser)
|
||||||
|
|
||||||
def run_init_hooks(self, mode):
|
def run_init_hooks(self, mode):
|
||||||
"""
|
"""
|
||||||
|
|
@ -534,28 +570,8 @@ class Evennia:
|
||||||
TASK_HANDLER.load()
|
TASK_HANDLER.load()
|
||||||
TASK_HANDLER.create_delays()
|
TASK_HANDLER.create_delays()
|
||||||
|
|
||||||
# check so default channels exist
|
# create/update channels
|
||||||
from evennia.comms.models import ChannelDB
|
self.create_default_channels()
|
||||||
from evennia.accounts.models import AccountDB
|
|
||||||
from evennia.utils.create import create_channel
|
|
||||||
|
|
||||||
god_account = AccountDB.objects.get(id=1)
|
|
||||||
# mudinfo
|
|
||||||
mudinfo_chan = settings.CHANNEL_MUDINFO
|
|
||||||
if mudinfo_chan:
|
|
||||||
if not ChannelDB.objects.filter(db_key=mudinfo_chan["key"]):
|
|
||||||
channel = create_channel(**mudinfo_chan)
|
|
||||||
channel.connect(god_account)
|
|
||||||
# connectinfo
|
|
||||||
connectinfo_chan = settings.CHANNEL_MUDINFO
|
|
||||||
if connectinfo_chan:
|
|
||||||
if not ChannelDB.objects.filter(db_key=mudinfo_chan["key"]):
|
|
||||||
channel = create_channel(**connectinfo_chan)
|
|
||||||
# default channels
|
|
||||||
for chan_info in settings.DEFAULT_CHANNELS:
|
|
||||||
if not ChannelDB.objects.filter(db_key=chan_info["key"]):
|
|
||||||
channel = create_channel(**chan_info)
|
|
||||||
channel.connect(god_account)
|
|
||||||
|
|
||||||
# delete the temporary setting
|
# delete the temporary setting
|
||||||
ServerConfig.objects.conf("server_restart_mode", delete=True)
|
ServerConfig.objects.conf("server_restart_mode", delete=True)
|
||||||
|
|
|
||||||
|
|
@ -14,5 +14,5 @@ class TestInitialSetup(TestCase):
|
||||||
@patch("evennia.server.initial_setup.AccountDB")
|
@patch("evennia.server.initial_setup.AccountDB")
|
||||||
def test_get_god_account(self, mocked_accountdb):
|
def test_get_god_account(self, mocked_accountdb):
|
||||||
mocked_accountdb.objects.get = MagicMock(return_value=1)
|
mocked_accountdb.objects.get = MagicMock(return_value=1)
|
||||||
self.assertEqual(initial_setup.get_god_account(), 1)
|
self.assertEqual(initial_setup._get_superuser_account(), 1)
|
||||||
mocked_accountdb.objects.get.assert_called_with(id=1)
|
mocked_accountdb.objects.get.assert_called_with(id=1)
|
||||||
|
|
|
||||||
|
|
@ -358,8 +358,14 @@ CONNECTION_SCREEN_MODULE = "server.conf.connection_screens"
|
||||||
# started when the autoconnects starts sending menu commands.
|
# started when the autoconnects starts sending menu commands.
|
||||||
DELAY_CMD_LOGINSTART = 0.3
|
DELAY_CMD_LOGINSTART = 0.3
|
||||||
# A module that must exist - this holds the instructions Evennia will use to
|
# A module that must exist - this holds the instructions Evennia will use to
|
||||||
# first prepare the database for use. Generally should not be changed. If this
|
# first prepare the database for use (create user #1 and Limbo etc). Only override if
|
||||||
# cannot be imported, bad things will happen.
|
# you really know what # you are doing. If replacing, it must contain a function
|
||||||
|
# handle_setup(stepname=None). The function will start being called with no argument
|
||||||
|
# and is expected to maintain a named sequence of steps. Once each step is completed, it
|
||||||
|
# should be saved with ServerConfig.objects.conf('last_initial_setup_step', stepname)
|
||||||
|
# on a crash, the system will continue by calling handle_setup with the last completed
|
||||||
|
# step. The last step in the sequence must be named 'done'. Once this key is saved,
|
||||||
|
# initialization will not run again.
|
||||||
INITIAL_SETUP_MODULE = "evennia.server.initial_setup"
|
INITIAL_SETUP_MODULE = "evennia.server.initial_setup"
|
||||||
# An optional module that, if existing, must hold a function
|
# An optional module that, if existing, must hold a function
|
||||||
# named at_initial_setup(). This hook method can be used to customize
|
# named at_initial_setup(). This hook method can be used to customize
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue