Mercurial > projects > mde
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; +}