Overhaul data reloading
authorJeremy Stanley <fungi@yuggoth.org>
Fri, 13 Jul 2018 16:56:17 +0000 (16:56 +0000)
committerJeremy Stanley <fungi@yuggoth.org>
Fri, 13 Jul 2018 17:47:48 +0000 (17:47 +0000)
The data model change left the reload feature in a miserable state
causing facets of mutable elements to be lost, changes introduced in
read-only origins to be ignored, and so on. Simplify the
implementation to just save, wipe and re-read all persistent data as
if the engine were starting initially. Also add a basic check to the
reload test to make sure a mutable element still has facets after
reloading, so that we reduce the risk of future regression. Include
a bit more verbose logging around when and what files are read at
load/reload time.

mudpy/data.py
mudpy/misc.py
mudpy/tests/selftest.py

index d9eb8eb..6371045 100644 (file)
@@ -1,6 +1,6 @@
 """Data interface functions for the mudpy engine."""
 
-# Copyright (c) 2004-2017 Jeremy Stanley <fungi@yuggoth.org>. Permission
+# Copyright (c) 2004-2018 Jeremy Stanley <fungi@yuggoth.org>. Permission
 # to use, copy, modify, and distribute this software is granted under
 # terms provided in the LICENSE file distributed with this software.
 
@@ -58,15 +58,16 @@ class Data:
                 self.source, relative=self.relative, universe=self.universe)
         try:
             self.data = yaml.safe_load(open(self.source))
+            log_entry = ("Loaded file %s into memory." % self.source, 5)
         except FileNotFoundError:
             # it's normal if the file is one which doesn't exist yet
             self.data = {}
             log_entry = ("File %s is unavailable." % self.source, 6)
-            try:
-                mudpy.misc.log(*log_entry)
-            except NameError:
-                # happens when we're not far enough along in the init process
-                self.universe.setup_loglines.append(log_entry)
+        try:
+            mudpy.misc.log(*log_entry)
+        except NameError:
+            # happens when we're not far enough along in the init process
+            self.universe.setup_loglines.append(log_entry)
         if not hasattr(self.universe, "files"):
             self.universe.files = {}
         self.universe.files[self.source] = self
index 3cd8e64..bae94b6 100644 (file)
@@ -64,8 +64,9 @@ class Element:
 
     def reload(self):
         """Create a new element and replace this one."""
-        Element(self.key, self.universe, self.origin)
-        del(self)
+        args = (self.key, self.universe, self.origin)
+        self.destroy()
+        Element(*args)
 
     def destroy(self):
         """Remove an element from the universe and destroy it."""
@@ -363,23 +364,9 @@ class Universe:
         # it's possible for this to enter before logging configuration is read
         pending_loglines = []
 
-        # the files dict must exist and filename needs to be read-only
-        if not hasattr(
-           self, "files"
-           ) or not (
-            self.filename in self.files and self.files[
-                self.filename
-            ].is_writeable()
-        ):
-
-            # clear out all read-only files
-            if hasattr(self, "files"):
-                for data_filename in list(self.files.keys()):
-                    if not self.files[data_filename].is_writeable():
-                        del self.files[data_filename]
-
-            # start loading from the initial file
-            mudpy.data.Data(self.filename, self)
+        # start populating the (re)files dict from the base config
+        self.files = {}
+        mudpy.data.Data(self.filename, self)
 
         # load default storage locations for groups
         if hasattr(self, "contents") and "mudpy.filing" in self.contents:
@@ -404,17 +391,6 @@ class Universe:
             if user.avatar in inactive_avatars:
                 inactive_avatars.remove(user.avatar)
 
-        # go through all elements to clear out inactive avatar locations
-        for element in self.contents.values():
-            area = element.get("location")
-            if element in inactive_avatars and area:
-                if area in self.contents and element.key in self.contents[
-                   area
-                   ].contents:
-                    del self.contents[area].contents[element.key]
-                element.set("default_location", area)
-                element.remove_facet("location")
-
         # another pass to straighten out all the element contents
         for element in self.contents.values():
             element.update_location()
@@ -584,26 +560,30 @@ class User:
     def reload(self):
         """Save, load a new user and relocate the connection."""
 
+        # copy old attributes
+        attributes = self.__dict__
+
         # get out of the list
         self.remove()
 
+        # get rid of the old user object
+        del(self)
+
         # create a new user object
         new_user = User()
 
         # set everything equivalent
-        for attribute in vars(self).keys():
-            exec("new_user." + attribute + " = self." + attribute)
+        new_user.__dict__ = attributes
 
         # the avatar needs a new owner
         if new_user.avatar:
+            new_user.account = universe.contents[new_user.account.key]
+            new_user.avatar = universe.contents[new_user.avatar.key]
             new_user.avatar.owner = new_user
 
         # add it to the list
         universe.userlist.append(new_user)
 
-        # get rid of the old user object
-        del(self)
-
     def replace_old_connections(self):
         """Disconnect active users with the same name."""
 
@@ -1414,12 +1394,13 @@ def on_pulse():
 
 def reload_data():
     """Reload all relevant objects."""
-    for user in universe.userlist[:]:
-        user.reload()
-    for element in universe.contents.values():
-        if element.origin.is_writeable():
-            element.reload()
+    universe.save()
+    old_userlist = universe.userlist[:]
+    for element in list(universe.contents.values()):
+        element.destroy()
     universe.load()
+    for user in old_userlist:
+        user.reload()
 
 
 def check_for_connection(listening_socket):
index 0b29386..f262063 100644 (file)
@@ -183,7 +183,10 @@ test_admin_help = (
 test_reload = (
     (2, "> ", "reload"),
     (2, r"Reloading all code modules, configs and data\."
-        r".* User admin reloaded the world\.", ""),
+        r".* User admin reloaded the world\.",
+     "show element account.admin"),
+    (2, 'These are the properties of the "account.admin" element.*'
+        "  \x1b\[32mpasshash:\r\n\x1b\[31m\$.*> ", ""),
 )
 
 test_set_facet = (