Back end data storage separation.
authorJeremy Stanley <fungi@yuggoth.org>
Mon, 13 Sep 2010 19:05:19 +0000 (19:05 +0000)
committerJeremy Stanley <fungi@yuggoth.org>
Mon, 13 Sep 2010 19:05:19 +0000 (19:05 +0000)
* lib/mudpy/__init__.py (modules): Added the new data module to the
list.

* lib/mudpy/data.py: Implemented a new module to contain back end
data storage and retrieval functions, preparing to merge work on the
new data handling routines.

* lib/mudpy/misc.py (DataFile, find_file, makedict, makelist): Moved
to lib/mudpy/data.py.
(Element.__init__, Element.getdict, Element.getlist, Universe.load)
(replace_macros): Updated references to relocated classes and
functions.

lib/mudpy/__init__.py
lib/mudpy/data.py [new file with mode: 0644]
lib/mudpy/misc.py

index 4efd9b2..128aba5 100644 (file)
@@ -21,5 +21,5 @@ def load():
       except NameError: exec(u"import %s" % module)
 
 # load the modules contained in this package
-modules = [ u"misc", u"password", u"telnet" ]
+modules = [ u"data", u"misc", u"password", u"telnet" ]
 load()
diff --git a/lib/mudpy/data.py b/lib/mudpy/data.py
new file mode 100644 (file)
index 0000000..7960176
--- /dev/null
@@ -0,0 +1,298 @@
+# -*- coding: utf-8 -*-
+u"""Data interface functions for the mudpy engine."""
+
+# Copyright (c) 2004-2010 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.
+
+class DataFile:
+   u"""A file containing universe elements."""
+   def __init__(self, filename, universe):
+      self.filename = filename
+      self.universe = universe
+      self.load()
+   def load(self):
+      u"""Read a file and create elements accordingly."""
+      import ConfigParser, misc, os, os.path
+      self.data = ConfigParser.RawConfigParser()
+      self.modified = False
+      if os.access(self.filename, os.R_OK): self.data.read(self.filename)
+      if not hasattr(self.universe, u"files"): self.universe.files = {}
+      self.universe.files[self.filename] = self
+      includes = []
+      if self.data.has_option(u"__control__", u"include_files"):
+         for included in makelist(
+            self.data.get(u"__control__", u"include_files")
+         ):
+            included = find_file(
+               included,
+               relative=self.filename,
+               universe=self.universe
+            )
+            if included not in includes: includes.append(included)
+      if self.data.has_option(u"__control__", u"include_dirs"):
+         for included in [
+            os.path.join(x, u"__init__.mpy") for x in makelist(
+               self.data.get(u"__control__", u"include_dirs")
+            )
+         ]:
+            included = find_file(
+               included,
+               relative=self.filename,
+               universe=self.universe
+            )
+            if included not in includes: includes.append(included)
+      if self.data.has_option(u"__control__", u"default_files"):
+         origins = makedict(
+            self.data.get(u"__control__", u"default_files")
+         )
+         for key in origins.keys():
+            origins[key] = find_file(
+               origins[key],
+               relative=self.filename,
+               universe=self.universe
+            )
+            if origins[key] not in includes: includes.append(origins[key])
+            self.universe.default_origins[key] = origins[key]
+            if key not in self.universe.categories:
+               self.universe.categories[key] = {}
+      if self.data.has_option(u"__control__", u"private_files"):
+         for item in makelist(
+            self.data.get(u"__control__", u"private_files")
+         ):
+            item = find_file(
+               item,
+               relative=self.filename,
+               universe=self.universe
+            )
+            if item not in includes: includes.append(item)
+            if item not in self.universe.private_files:
+               self.universe.private_files.append(item)
+      for section in self.data.sections():
+         if section != u"__control__":
+            misc.Element(section, self.universe, self.filename)
+      for include_file in includes:
+         if not os.path.isabs(include_file):
+            include_file = find_file(
+               include_file,
+               relative=self.filename,
+               universe=self.universe
+            )
+         if include_file not in self.universe.files or not self.universe.files[
+            include_file
+         ].is_writeable():
+            DataFile(include_file, self.universe)
+   def save(self):
+      u"""Write the data, if necessary."""
+      import codecs, os, os.path, re, stat
+
+      # when modified, writeable and has content or the file exists
+      if self.modified and self.is_writeable() and (
+         self.data.sections() or os.path.exists(self.filename)
+      ):
+
+         # make parent directories if necessary
+         if not os.path.exists(os.path.dirname(self.filename)):
+            os.makedirs(os.path.dirname(self.filename))
+
+         # backup the file
+         if self.data.has_option(u"__control__", u"backup_count"):
+            max_count = self.data.has_option(u"__control__", u"backup_count")
+         else:
+            max_count = self.universe.categories[
+               u"internal"
+            ][
+               u"limits"
+            ].getint(u"default_backup_count")
+         if os.path.exists(self.filename) and max_count:
+            backups = []
+            for candidate in os.listdir(os.path.dirname(self.filename)):
+               if re.match(
+                  os.path.basename(self.filename) + u"""\.\d+$""", candidate
+               ):
+                  backups.append(int(candidate.split(u".")[-1]))
+            backups.sort()
+            backups.reverse()
+            for old_backup in backups:
+               if old_backup >= max_count-1:
+                  os.remove(self.filename+u"."+unicode(old_backup))
+               elif not os.path.exists(
+                  self.filename+u"."+unicode(old_backup+1)
+               ):
+                  os.rename(
+                     self.filename + u"."+unicode(old_backup),
+                     self.filename + u"."+unicode( old_backup + 1 )
+                  )
+            if not os.path.exists(self.filename+u".0"):
+               os.rename( self.filename, self.filename + u".0" )
+
+         # our data file
+         file_descriptor = codecs.open(self.filename, u"w", u"utf-8")
+
+         # if it's marked private, chmod it appropriately
+         if self.filename in self.universe.private_files and oct(
+            stat.S_IMODE( os.stat(self.filename)[stat.ST_MODE] )
+         ) != 0600:
+            os.chmod(self.filename, 0600)
+
+         # write it back sorted, instead of using ConfigParser
+         sections = self.data.sections()
+         sections.sort()
+         for section in sections:
+            file_descriptor.write(u"[" + section + u"]\n")
+            options = self.data.options(section)
+            options.sort()
+            for option in options:
+               file_descriptor.write(
+                  option + u" = " + self.data.get(section, option) + u"\n"
+               )
+            file_descriptor.write(u"\n")
+
+         # flush and close the file
+         file_descriptor.flush()
+         file_descriptor.close()
+
+         # unset the modified flag
+         self.modified = False
+   def is_writeable(self):
+      u"""Returns True if the __control__ read_only is False."""
+      return not self.data.has_option(
+         u"__control__", u"read_only"
+      ) or not self.data.getboolean(
+         u"__control__", u"read_only"
+      )
+
+def find_file(
+   file_name=None,
+   root_path=None,
+   search_path=None,
+   default_dir=None,
+   relative=None,
+   universe=None
+):
+   u"""Return an absolute file path based on configuration."""
+   import os, os.path, sys
+
+   # make sure to get rid of any surrounding quotes first thing
+   if file_name: file_name = file_name.strip(u"\"'")
+
+   # this is all unnecessary if it's already absolute
+   if file_name and os.path.isabs(file_name):
+      return os.path.realpath(file_name)
+
+   # when no file name is specified, look for <argv[0]>.conf
+   elif not file_name: file_name = os.path.basename( sys.argv[0] ) + u".conf"
+
+   # if a universe was provided, try to get some defaults from there
+   if universe:
+
+      if hasattr(
+         universe,
+         u"contents"
+      ) and u"internal:storage" in universe.contents:
+         storage = universe.categories[u"internal"][u"storage"]
+         if not root_path: root_path = storage.get(u"root_path").strip("\"'")
+         if not search_path: search_path = storage.getlist(u"search_path")
+         if not default_dir:
+            default_dir = storage.get(u"default_dir").strip("\"'")
+
+      # if there's only one file loaded, try to work around a chicken<egg
+      elif hasattr(universe, u"files") and len(
+         universe.files
+      ) == 1 and not universe.files[universe.files.keys()[0]].is_writeable():
+         data_file = universe.files[universe.files.keys()[0]].data
+
+         # try for a fallback default directory
+         if not default_dir and data_file.has_option(
+            u"internal:storage",
+            u"default_dir"
+         ):
+            default_dir = data_file.get(
+               u"internal:storage",
+               u"default_dir"
+            ).strip(u"\"'")
+
+         # try for a fallback root path
+         if not root_path and data_file.has_option(
+            u"internal:storage",
+            u"root_path"
+         ):
+            root_path = data_file.get(
+               u"internal:storage",
+               u"root_path"
+            ).strip(u"\"'")
+
+         # try for a fallback search path
+         if not search_path and data_file.has_option(
+            u"internal:storage",
+            u"search_path"
+         ):
+            search_path = makelist(
+               data_file.get(u"internal:storage", u"search_path").strip(u"\"'")
+            )
+
+      # another fallback root path, this time from the universe startdir
+      if not root_path and hasattr(universe, "startdir"):
+         root_path = universe.startdir
+
+   # when no root path is specified, assume the current working directory
+   if not root_path: root_path = os.getcwd()
+
+   # otherwise, make sure it's absolute
+   elif not os.path.isabs(root_path): root_path = os.path.realpath(root_path)
+
+   # if there's no search path, just use the root path and etc
+   if not search_path: search_path = [root_path, u"etc"]
+
+   # work on a copy of the search path, to avoid modifying the caller's
+   else: search_path = search_path[:]
+
+   # if there's no default path, use the last element of the search path
+   if not default_dir: default_dir = search_path[-1]
+
+   # if an existing file or directory reference was supplied, prepend it
+   if relative:
+      relative = relative.strip(u"\"'")
+      if os.path.isdir(relative): search_path = [relative] + search_path
+      else: search_path = [ os.path.dirname(relative) ] + search_path
+
+   # make the search path entries absolute and throw away any dupes
+   clean_search_path = []
+   for each_path in search_path:
+      each_path = each_path.strip(u"\"'")
+      if not os.path.isabs(each_path):
+         each_path = os.path.realpath( os.path.join(root_path, each_path) )
+      if each_path not in clean_search_path:
+         clean_search_path.append(each_path)
+
+   # start hunting for the file now
+   for each_path in clean_search_path:
+
+      # if the file exists and is readable, we're done
+      if os.path.isfile( os.path.join(each_path, file_name) ):
+         file_name = os.path.realpath( os.path.join(each_path, file_name) )
+         break
+
+   # it didn't exist after all, so use the default path instead
+   if not os.path.isabs(file_name):
+      file_name = os.path.join(default_dir, file_name)
+   if not os.path.isabs(file_name):
+      file_name = os.path.join(root_path, file_name)
+
+   # and normalize it last thing before returning
+   file_name = os.path.realpath(file_name)
+
+   # normalize the resulting file path and hand it back
+   return file_name
+
+def makelist(value):
+   u"""Turn string into list type."""
+   if value[0] + value[-1] == u"[]": return eval(value)
+   elif value[0] + value[-1] == u"\"\"": return [ value[1:-1] ]
+   else: return [ value ]
+
+def makedict(value):
+   u"""Turn string into dict type."""
+   if value[0] + value[-1] == u"{}": return eval(value)
+   elif value.find(u":") > 0: return eval(u"{" + value + u"}")
+   else: return { value: None }
index 609e155..6a16436 100644 (file)
@@ -9,7 +9,7 @@ class Element:
    u"""An element of the universe."""
    def __init__(self, key, universe, filename=None):
       u"""Set up a new element."""
-      import os.path
+      import data, os.path
 
       # keep track of our key name
       self.key = key
@@ -51,7 +51,7 @@ class Element:
 
          # add the file if it doesn't exist yet
          if not filename in self.universe.files:
-            DataFile(filename, self.universe)
+            data.DataFile(filename, self.universe)
 
       # record or reset a pointer to the origin file
       self.origin = self.universe.files[filename]
@@ -144,15 +144,17 @@ class Element:
       else: return default
    def getlist(self, facet, default=None):
       u"""Return values as list type."""
+      import data
       if default is None: default = []
       value = self.get(facet)
-      if value: return makelist(value)
+      if value: return data.makelist(value)
       else: return default
    def getdict(self, facet, default=None):
       u"""Return values as dict type."""
+      import data
       if default is None: default = {}
       value = self.get(facet)
-      if value: return makedict(value)
+      if value: return data.makedict(value)
       else: return default
    def set(self, facet, value):
       u"""Set values."""
@@ -356,155 +358,6 @@ class Element:
       ].contents.values():
          if element is not self: element.send(message)
 
-class DataFile:
-   u"""A file containing universe elements."""
-   def __init__(self, filename, universe):
-      self.filename = filename
-      self.universe = universe
-      self.load()
-   def load(self):
-      u"""Read a file and create elements accordingly."""
-      import ConfigParser, os, os.path
-      self.data = ConfigParser.RawConfigParser()
-      self.modified = False
-      if os.access(self.filename, os.R_OK): self.data.read(self.filename)
-      if not hasattr(self.universe, u"files"): self.universe.files = {}
-      self.universe.files[self.filename] = self
-      includes = []
-      if self.data.has_option(u"__control__", u"include_files"):
-         for included in makelist(
-            self.data.get(u"__control__", u"include_files")
-         ):
-            included = find_file(
-               included,
-               relative=self.filename,
-               universe=self.universe
-            )
-            if included not in includes: includes.append(included)
-      if self.data.has_option(u"__control__", u"include_dirs"):
-         for included in [ os.path.join(x, u"__init__.mpy") for x in makelist(
-            self.data.get(u"__control__", u"include_dirs")
-         ) ]:
-            included = find_file(
-               included,
-               relative=self.filename,
-               universe=self.universe
-            )
-            if included not in includes: includes.append(included)
-      if self.data.has_option(u"__control__", u"default_files"):
-         origins = makedict(self.data.get(u"__control__", u"default_files"))
-         for key in origins.keys():
-            origins[key] = find_file(
-               origins[key],
-               relative=self.filename,
-               universe=self.universe
-            )
-            if origins[key] not in includes: includes.append(origins[key])
-            self.universe.default_origins[key] = origins[key]
-            if key not in self.universe.categories:
-               self.universe.categories[key] = {}
-      if self.data.has_option(u"__control__", u"private_files"):
-         for item in makelist(self.data.get(u"__control__", u"private_files")):
-            item = find_file(
-               item,
-               relative=self.filename,
-               universe=self.universe
-            )
-            if item not in includes: includes.append(item)
-            if item not in self.universe.private_files:
-               self.universe.private_files.append(item)
-      for section in self.data.sections():
-         if section != u"__control__":
-            Element(section, self.universe, self.filename)
-      for include_file in includes:
-         if not os.path.isabs(include_file):
-            include_file = find_file(
-               include_file,
-               relative=self.filename,
-               universe=self.universe
-            )
-         if include_file not in self.universe.files or not self.universe.files[
-            include_file
-         ].is_writeable():
-            DataFile(include_file, self.universe)
-   def save(self):
-      u"""Write the data, if necessary."""
-      import codecs, os, os.path, re, stat
-
-      # when modified, writeable and has content or the file exists
-      if self.modified and self.is_writeable() and (
-         self.data.sections() or os.path.exists(self.filename)
-      ):
-
-         # make parent directories if necessary
-         if not os.path.exists(os.path.dirname(self.filename)):
-            os.makedirs(os.path.dirname(self.filename))
-
-         # backup the file
-         if self.data.has_option(u"__control__", u"backup_count"):
-            max_count = self.data.has_option(u"__control__", u"backup_count")
-         else:
-            max_count = universe.categories[u"internal"][u"limits"].getint(
-               u"default_backup_count"
-            )
-         if os.path.exists(self.filename) and max_count:
-            backups = []
-            for candidate in os.listdir(os.path.dirname(self.filename)):
-               if re.match(
-                  os.path.basename(self.filename) + u"""\.\d+$""", candidate
-               ):
-                  backups.append(int(candidate.split(u".")[-1]))
-            backups.sort()
-            backups.reverse()
-            for old_backup in backups:
-               if old_backup >= max_count-1:
-                  os.remove(self.filename+u"."+unicode(old_backup))
-               elif not os.path.exists(
-                  self.filename+u"."+unicode(old_backup+1)
-               ):
-                  os.rename(
-                     self.filename + u"."+unicode(old_backup),
-                     self.filename + u"."+unicode( old_backup + 1 )
-                  )
-            if not os.path.exists(self.filename+u".0"):
-               os.rename( self.filename, self.filename + u".0" )
-
-         # our data file
-         file_descriptor = codecs.open(self.filename, u"w", u"utf-8")
-
-         # if it's marked private, chmod it appropriately
-         if self.filename in self.universe.private_files and oct(
-            stat.S_IMODE( os.stat(self.filename)[stat.ST_MODE] )
-         ) != 0600:
-            os.chmod(self.filename, 0600)
-
-         # write it back sorted, instead of using ConfigParser
-         sections = self.data.sections()
-         sections.sort()
-         for section in sections:
-            file_descriptor.write(u"[" + section + u"]\n")
-            options = self.data.options(section)
-            options.sort()
-            for option in options:
-               file_descriptor.write(
-                  option + u" = " + self.data.get(section, option) + u"\n"
-               )
-            file_descriptor.write(u"\n")
-
-         # flush and close the file
-         file_descriptor.flush()
-         file_descriptor.close()
-
-         # unset the modified flag
-         self.modified = False
-   def is_writeable(self):
-      u"""Returns True if the __control__ read_only is False."""
-      return not self.data.has_option(
-         u"__control__", u"read_only"
-      ) or not self.data.getboolean(
-         u"__control__", u"read_only"
-      )
-
 class Universe:
    u"""The universe."""
 
@@ -543,6 +396,7 @@ class Universe:
 
    def load(self):
       u"""Load universe data from persistent storage."""
+      import data
 
       # the files dict must exist and filename needs to be read-only
       if not hasattr(
@@ -560,7 +414,7 @@ class Universe:
                   del self.files[data_filename]
 
          # start loading from the initial file
-         DataFile(self.filename, self)
+         data.DataFile(self.filename, self)
 
       # make a list of inactive avatars
       inactive_avatars = []
@@ -1093,18 +947,6 @@ class User:
          ) for avatar in self.account.getlist(u"avatars")
       ]
 
-def makelist(value):
-   u"""Turn string into list type."""
-   if value[0] + value[-1] == u"[]": return eval(value)
-   elif value[0] + value[-1] == u"\"\"": return [ value[1:-1] ]
-   else: return [ value ]
-
-def makedict(value):
-   u"""Turn string into dict type."""
-   if value[0] + value[-1] == u"{}": return eval(value)
-   elif value.find(u":") > 0: return eval(u"{" + value + u"}")
-   else: return { value: None }
-
 def broadcast(message, add_prompt=True):
    u"""Send a message to all connected users."""
    for each_user in universe.userlist:
@@ -1372,7 +1214,7 @@ def random_name():
 
 def replace_macros(user, text, is_input=False):
    u"""Replaces macros in text output."""
-   import codecs, os.path
+   import codecs, data, os.path
 
    # third person pronouns
    pronouns = {
@@ -1420,7 +1262,7 @@ def replace_macros(user, text, is_input=False):
 
       # this is how we handle local file inclusion (dangerous!)
       elif macro.startswith(u"inc:"):
-         incfile = find_file(macro[4:], universe=universe)
+         incfile = data.find_file(macro[4:], universe=universe)
          if os.path.exists(incfile):
             incfd = codecs.open(incfile, u"r", u"utf-8")
             replacement = u""
@@ -2339,128 +2181,6 @@ def command_error(actor, input_data):
    # send the error message
    actor.send(message)
 
-def find_file(
-   file_name=None,
-   root_path=None,
-   search_path=None,
-   default_dir=None,
-   relative=None,
-   universe=None
-):
-   u"""Return an absolute file path based on configuration."""
-   import os, os.path, sys
-
-   # make sure to get rid of any surrounding quotes first thing
-   if file_name: file_name = file_name.strip(u"\"'")
-
-   # this is all unnecessary if it's already absolute
-   if file_name and os.path.isabs(file_name):
-      return os.path.realpath(file_name)
-
-   # when no file name is specified, look for <argv[0]>.conf
-   elif not file_name: file_name = os.path.basename( sys.argv[0] ) + u".conf"
-
-   # if a universe was provided, try to get some defaults from there
-   if universe:
-
-      if hasattr(
-         universe,
-         u"contents"
-      ) and u"internal:storage" in universe.contents:
-         storage = universe.categories[u"internal"][u"storage"]
-         if not root_path: root_path = storage.get(u"root_path").strip("\"'")
-         if not search_path: search_path = storage.getlist(u"search_path")
-         if not default_dir: default_dir = storage.get(u"default_dir").strip("\"'")
-
-      # if there's only one file loaded, try to work around a chicken<egg
-      elif hasattr(universe, u"files") and len(
-         universe.files
-      ) == 1 and not universe.files[universe.files.keys()[0]].is_writeable():
-         data_file = universe.files[universe.files.keys()[0]].data
-
-         # try for a fallback default directory
-         if not default_dir and data_file.has_option(
-            u"internal:storage",
-            u"default_dir"
-         ):
-            default_dir = data_file.get(
-               u"internal:storage",
-               u"default_dir"
-            ).strip(u"\"'")
-
-         # try for a fallback root path
-         if not root_path and data_file.has_option(
-            u"internal:storage",
-            u"root_path"
-         ):
-            root_path = data_file.get(
-               u"internal:storage",
-               u"root_path"
-            ).strip(u"\"'")
-
-         # try for a fallback search path
-         if not search_path and data_file.has_option(
-            u"internal:storage",
-            u"search_path"
-         ):
-            search_path = makelist(
-               data_file.get(u"internal:storage", u"search_path").strip(u"\"'")
-            )
-
-      # another fallback root path, this time from the universe startdir
-      if not root_path and hasattr(universe, "startdir"):
-         root_path = universe.startdir
-
-   # when no root path is specified, assume the current working directory
-   if not root_path: root_path = os.getcwd()
-
-   # otherwise, make sure it's absolute
-   elif not os.path.isabs(root_path): root_path = os.path.realpath(root_path)
-
-   # if there's no search path, just use the root path and etc
-   if not search_path: search_path = [root_path, u"etc"]
-
-   # work on a copy of the search path, to avoid modifying the caller's
-   else: search_path = search_path[:]
-
-   # if there's no default path, use the last element of the search path
-   if not default_dir: default_dir = search_path[-1]
-
-   # if an existing file or directory reference was supplied, prepend it
-   if relative:
-      relative = relative.strip(u"\"'")
-      if os.path.isdir(relative): search_path = [relative] + search_path
-      else: search_path = [ os.path.dirname(relative) ] + search_path
-
-   # make the search path entries absolute and throw away any dupes
-   clean_search_path = []
-   for each_path in search_path:
-      each_path = each_path.strip(u"\"'")
-      if not os.path.isabs(each_path):
-         each_path = os.path.realpath( os.path.join(root_path, each_path) )
-      if each_path not in clean_search_path:
-         clean_search_path.append(each_path)
-
-   # start hunting for the file now
-   for each_path in clean_search_path:
-
-      # if the file exists and is readable, we're done
-      if os.path.isfile( os.path.join(each_path, file_name) ):
-         file_name = os.path.realpath( os.path.join(each_path, file_name) )
-         break
-
-   # it didn't exist after all, so use the default path instead
-   if not os.path.isabs(file_name):
-      file_name = os.path.join(default_dir, file_name)
-   if not os.path.isabs(file_name):
-      file_name = os.path.join(root_path, file_name)
-
-   # and normalize it last thing before returning
-   file_name = os.path.realpath(file_name)
-
-   # normalize the resulting file path and hand it back
-   return file_name
-
 def daemonize(universe):
    u"""Fork and disassociate from everything."""
    import codecs, ctypes, ctypes.util, os, os.path, sys