Mercurial > projects > mde
view 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 source
/** 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; }