diff mde/resource/paths.d @ 15:4608be19ebe2

Use OS paths (linux only for now), merging multiple paths. Init changes regarding options. Reorganised policies.txt a little. Implemented mde.resource.paths to read config from appropriate paths (currently linux only). Changed Init to load options before all other delegates are run and set logging level from options. committer: Diggory Hardy <diggory.hardy@gmail.com>
author Diggory Hardy <diggory.hardy@gmail.com>
date Fri, 14 Mar 2008 11:39:45 +0000
parents
children 5f90774ea1ef
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mde/resource/paths.d	Fri Mar 14 11:39:45 2008 +0000
@@ -0,0 +1,245 @@
+/** Resource paths module.
+*
+* Internally to mde code other than code dealing directly with files and this module, paths are
+* relative to the mde directory. This module transforms those paths to absolute paths.
+*
+* Additionally, the intention is to look for all files in two directories: the installation (i.e.
+* main data) directory and a user directory (for user-specific configuration). Besides exposing
+* both paths and checking in which valid files exist, this module provides some extra mergetag
+* functionality to simplify correct reading and writing.
+*
+* Currently the paths are found as follows: (see codeDoc/paths.txt)
+*/
+/* Implementation note:
+* All paths are stored internally as strings, rather than as an instance of FilePath/PathView once
+* the FilePath has served its immediate purpose, since it's more convenient and creating new
+* FilePaths for adjusted paths should be no slower than mutating existing ones. */
+module mde.resource.paths;
+
+import mde.exception;
+import mde.mergetag.Reader;
+import mde.mergetag.Writer;
+import mde.mergetag.DataSet;
+import mde.mergetag.exception;
+
+import tango.io.FilePath;
+import tango.util.log.Log : Log, Logger;
+import tango.stdc.stdlib;
+import tango.stdc.stringz;
+
+/** Order to read files in.
+*
+* Values: HIGH_LOW, LOW_HIGH, HIGH_ONLY. */
+enum PRIORITY : byte { HIGH_LOW, LOW_HIGH, HIGH_ONLY }
+
+/** This struct has one instance for each "directory".
+*
+* It is the only item within this module that you should need to interact with. */
+struct mdeDirectory
+{
+    /** Creates an MT reader for each file.
+    *
+    * Params:
+    *   file      = The file path and name relative to the mdeDirectory, without a suffix
+    *               (e.g. "options")
+    *   readOrder = Read the highest priority or lowest priority files first? For correct merging,
+    *               this should be LOW_HIGH when newly-read items override old ones (as is the case
+    *               with DefaultData) and HIGH_LOW when the first-read items survive. Thus override
+    *               order needs to be the same for each section, except the header which is always
+    *               read with LOW_HIGH order.
+    *               Alternately, for files which shouldn't be
+    *               merged where only the highest priority file should be read, pass HIGH_ONLY.
+    *   ds        = The dataset, as for mergetag. Note: all actual readers share one dataset.
+    *   rdHeader  = Read the headers for each file and merge if rdHeader == true.
+    */
+    IReader makeMTReader (char[] file, PRIORITY readOrder, DataSet ds = null, bool rdHeader = false)
+    {
+        if (readOrder == PRIORITY.HIGH_ONLY) return makeReader (paths[pathsLen-1] ~ file, ds, rdHeader);
+        else return new mdeReader (file, readOrder, ds, rdHeader, paths);
+    }
+    
+    /** Creates an MT writer for file deciding on the best path to use.
+    *
+    * Params:
+    *   file      = The file path and name relative to the mdeDirectory, without a suffix
+    *               (e.g. "options")
+    *   ds        = The dataset, as for mergetag.
+    */
+    IWriter makeMTWriter (char[] file, DataSet ds = null)
+    {
+        // FIXME: use highest priority writable path
+        return makeWriter (paths[pathsLen-1] ~ file, ds, WriterMethod.Text);
+    }
+    
+    /** Check whether the given file exists under any path with either .mtt or .mtb suffix. */
+    bool exists (char[] file) {
+        for (uint i = 0; i < pathsLen; ++i) {
+            if (FilePath (paths[i]~file~".mtt").exists) return true;
+            if (FilePath (paths[i]~file~".mtb").exists) return true;
+        }
+        return false;
+    }
+    
+private:
+    
+    // Unconditionally add a path
+    void addPath (char[] path) {
+        paths[pathsLen++] = path~'/';
+    }
+    
+    // Test a path and add if is a folder
+    void tryPath (char[] path) {
+        PathView pv = FilePath (path);
+        if (pv.exists && pv.isFolder) {
+            paths[pathsLen++] = path~'/';
+            logger.info ("Path "~path~" is writable: " ~ (pv.isWritable ? "yes" : "no"));
+        }
+    }
+    
+    // Use a static array to store all possible paths with separate length counters.
+    // Lowest priority paths are first.
+    char[][MAX_PATHS] paths;
+    ubyte pathsLen = 0;
+}
+
+/** These are the actual instances, one for each of the data and conf "directories". */
+mdeDirectory dataDir, confDir;
+
+//BEGIN Path resolution
+static this() {
+    logger = Log.getLogger ("mde.resource.paths");
+    
+    // NOTE: May fail. Currently I think I'll just let the exception halt execution.
+    resolvePaths();
+    if (!dataDir.pathsLen) throw new mdeException ("Fatal: no data path found!");
+    if (!confDir.pathsLen) throw new mdeException ("Fatal: no conf path found!");
+}
+
+private:
+// The maximum number of paths for any one "directory".
+// There are NO CHECKS that this is not exceeded.
+const MAX_PATHS = 3;
+
+Logger logger;
+
+/* Try each path in succession, returning the first two exist and be a folder. Throws if none are. */
+char[] findPath (char[][] paths ...) {
+    foreach (path; paths) {
+        PathView pv = new FilePath (path);
+        if (pv.exists && pv.isFolder) return pv.toString;    // got a valid path
+    }
+    // no valid path...
+    logger.fatal ("Unable to resolve a required path! The following were tried:");
+    foreach (path; paths) logger.fatal ('\t' ~ path);
+    throw new mdeException ("Unable to resolve a required path (see log for details).");
+}
+
+// These are used several times:
+const DATA = "/data";
+const CONF = "/conf";
+
+version (linux) {
+    void resolvePaths () {
+        // Home directory:
+        char[] HOME = fromStringz (getenv (toStringz ("HOME")));
+        
+        // Base paths:
+        char[] staticPath = findPath ("/usr/share/games/mde", "/usr/local/share/games/mde", "data");
+        char[] userPath = findPath (HOME~"/.config/mde", HOME~"/.mde");
+        
+        // Static data paths:
+        dataDir.addPath (staticPath);      // we know this is valid anyway
+        dataDir.tryPath (userPath ~ DATA);
+        
+        // Configuration paths:
+        confDir.tryPath (staticPath ~ CONF);
+        confDir.tryPath ("/etc/mde");
+        confDir.tryPath (userPath ~ CONF);
+    }
+} else version (Windows) {
+    void resolvePaths () {
+        static assert (false, "No registry code");
+        
+        // Base paths:
+        char[] userPath = `...`;
+        char[] installPath = `registryInstallPath or "."`;
+        char[] staticPath = findPath (installPath ~ DATA);
+        
+        // Static data paths:
+        dataDir.addPath[dataLength++] = staticPath;   // we know this is valid anyway
+        dataDir.tryPath (userPath ~ DATA);
+        
+        // Configuration paths:
+        confDir.tryPath (staticPath.toString ~ CONF);
+        confDir.tryPath (installPath ~ CONF);
+        confDir.tryPath (userPath.toString ~ CONF);
+    }
+} else {
+    static assert (false, "Platform is not linux or Windows: no support for paths on this platform yet!");
+}
+//END Path resolution
+
+/** A special adapter for reading from multiple mergetag files with the same relative path to an
+* mdeDirectory simultaneously.
+*/
+class mdeReader : IReader
+{
+    private this (char[] file, PRIORITY readOrder, DataSet ds, bool rdHeader, char[][MAX_PATHS] paths)
+    in { assert (readOrder == PRIORITY.LOW_HIGH || readOrder == PRIORITY.HIGH_LOW); }
+    body {
+        rdOrder = readOrder;
+        if (ds is null) ds = new DataSet;
+        
+        foreach (path; paths) {
+            try {
+                IReader r = makeReader (path~file, ds, rdHeader);
+                
+                readers[readersLen++] = r;
+            }
+            catch (MTFileIOException) {}    // Ignore errors regarding no file for now.
+        }
+        
+        if (readersLen == 0) {          // totally failed to find any valid files
+            throw new MTFileIOException ("Unable to find the file: "~file[1..$]~"[.mtt|mtb]");
+        }
+        
+        // This is simply the easiest way of adjusting the reading order:
+        if (readOrder == PRIORITY.HIGH_LOW) readers[0..readersLen].reverse;
+    }
+    
+    DataSet dataset () {                /// Get the DataSet
+        return readers[0].dataset;      // all readers share the same dataset
+    }
+    void dataset (DataSet ds) {         /// Set the DataSet
+        for (uint i = 0; i < readersLen; ++i) readers[i].dataset (ds);
+    }
+    
+    void dataSecCreator (IDataSection delegate (ID) dsC) {  /// Set the dataSecCreator
+        for (uint i = 0; i < readersLen; ++i) readers[i].dataSecCreator = dsC;
+    }
+    
+    /** Get identifiers for all sections.
+    *
+    * Note: the identifiers from all sections in all files are just strung together, starting with
+    * the highest-priority file. */
+    ID[] getSectionNames () {
+        ID[] names;
+        for (int i = readersLen-1; i >= 0; --i) names ~= readers[i].getSectionNames;
+        return names;
+    }
+    void read () {                      /// Commence reading
+        for (uint i = 0; i < readersLen; ++i) readers[i].read();
+    }
+    void read (ID[] secSet) {           /// ditto
+        for (uint i = 0; i < readersLen; ++i) readers[i].read(secSet);
+    }
+    void read (View!(ID) secSet) {      /// ditto
+        for (uint i = 0; i < readersLen; ++i) readers[i].read(secSet);
+    }
+        
+    private:
+    IReader[MAX_PATHS] readers;
+    ubyte readersLen = 0;
+    
+    PRIORITY rdOrder;
+}