Mercurial > projects > mde
view mde/setup/paths.d @ 75:25cb7420dc91
A massive overhaul/rewrite for the gui's data management and setup code. Currently much that was working is broken.
imde's classes are created in a static this instead of mde's main.
gl setup code moved from gl/basic.d to gl/draw.d
mergetag.DefaultData: now HIGH_LOW priority instead of LOW_HIGH. Reduced type list to only used types; small fix for indent function.
setup.paths: new NoFileException thrown instead of MTFileIOException
author | Diggory Hardy <diggory.hardy@gmail.com> |
---|---|
date | Mon, 28 Jul 2008 18:17:48 +0100 |
parents | 3a737e06dc50 |
children | 79a1809421aa |
line wrap: on
line source
/* LICENSE BLOCK Part of mde: a Modular D game-oriented Engine Copyright © 2007-2008 Diggory Hardy This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ /** 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/FilePath 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.setup.paths; import mde.exception; import mde.mergetag.Reader; import mde.mergetag.Writer; import mde.mergetag.DataSet; import mde.mergetag.exception; import tango.io.Console; import tango.io.FilePath; import tango.sys.Environment; //import tango.scrapple.sys.win32.Registry; // Trouble getting this to work /** 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. * * In the case of confDir, the user path is guaranteed to exist (as highest priority path). */ 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) { FilePath[] files = getFiles (file, readOrder); if (files is null) throw new NoFileException ("Unable to find the file: "~file~"[.mtt|mtb]"); return new mdeReader (files, ds, rdHeader); } /** 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); } /** Returns a string listing the file name or names (if readOrder is not HIGH_ONLY and multiple * matches are found), or "no file found". Intended for user output only. */ char[] getFileName (char[] file, PRIORITY readOrder) { FilePath[] files = getFiles (file, readOrder); if (files is null) return "no file found"; char[] ret = files[0].toString; foreach (f; files[1..$]) ret ~= ", " ~ f.toString; return ret; } /** 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; } /// Print all paths found. static void printPaths () { Cout ("Data paths found:"); dataDir.coutPaths; Cout ("\nConf paths found:"); confDir.coutPaths; Cout ("\nLog file directory:\n\t")(logDir).newline; } private: FilePath[] getFiles (char[] filename, PRIORITY readOrder) { FilePath[] ret; debug (mdeUnitTest) { /* Alternate approach - just try from current directory. * Really just for unittests, but with the if condition it shouldn't break anything else. */ if (pathsLen == 0) { FilePath file = findFile (filename); if (file !is null) ret ~= file; return ret; } } if (readOrder == PRIORITY.LOW_HIGH) { for (size_t i = 0; i < pathsLen; ++i) { FilePath file = findFile (paths[i]~filename); if (file !is null) ret ~= file; } } else { assert (readOrder == PRIORITY.HIGH_LOW || readOrder == PRIORITY.HIGH_ONLY ); for (int i = pathsLen - 1; i >= 0; --i) { FilePath file = findFile (paths[i]~filename); if (file !is null) { ret ~= file; if (readOrder == PRIORITY.HIGH_ONLY) break; } } } return ret; } // Unconditionally add a path void addPath (char[] path) { paths[pathsLen++] = path~'/'; } // Test a path and add if is a folder. bool tryPath (char[] path, bool create = false) { FilePath fp = FilePath (path); if (fp.exists && fp.isFolder) { paths[pathsLen++] = path~'/'; return true; } else if (create) { try { fp.create; paths[pathsLen++] = fp.toString~'/'; return true; } catch (Exception e) { // No logging avaiable yet: Use Stdout/Cout Cout ("Creating path "~path~" failed:" ~ e.msg).newline; } } return false; } void coutPaths () { if (pathsLen) { for (size_t i = 0; i < pathsLen; ++i) Cout ("\n\t" ~ paths[i]); } else Cout ("[none]"); } // 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; char[] logDir; //BEGIN Path resolution // These are used several times: const DATA = "/data"; const CONF = "/conf"; /** Find at least one path for each required directory. * * Note: the logger cannot be used yet, so only output is exception messages. */ // FIXME: use tango/sys/Environment.d version (linux) { // base-path not used on posix void resolvePaths (char[] = null) { // Home directory: char[] HOME = Environment.get("HOME", "."); // Base paths: // Static data (must exist): FilePath staticPath = findPath (false, "/usr/share/games/mde", "/usr/local/share/games/mde", "data"); // Config (can just use defaults if necessary, so long as we can save afterwards): FilePath userPath = findPath (true, HOME~"/.config/mde", HOME~"/.mde"); // Static data paths: dataDir.addPath (staticPath.toString); // we know this is valid anyway dataDir.tryPath (userPath.toString ~ DATA); if (extraDataPath) dataDir.tryPath (extraDataPath); if (!dataDir.pathsLen) throw new mdeException ("Fatal: no data path found!"); // Configuration paths: confDir.tryPath (staticPath.toString ~ CONF); confDir.tryPath ("/etc/mde"); confDir.tryPath (userPath.toString ~ CONF, true); if (extraConfPath) confDir.tryPath (extraConfPath); if (!confDir.pathsLen) throw new mdeException ("Fatal: no conf path found!"); // Logging path: logDir = userPath.toString; } } else version (Windows) { void resolvePaths (char[] base = "./") { //FIXME: Get path from registry //FIXME: Get user path (Docs&Settings/USER/Local Settings/Application data/mde) //http://www.dsource.org/projects/tango/forums/topic/187 // Base paths: FilePath installPath = findPath (false, base); FilePath staticPath = findPath (false, installPath.append("data").toString); FilePath userPath = findPath (true, installPath.append("user").toString); // FIXME: see above // Static data paths: dataDir.addPath (staticPath.toString); // we know this is valid anyway dataDir.tryPath (userPath.toString ~ DATA); if (extraDataPath) dataDir.tryPath (extraDataPath); if (!dataDir.pathsLen) throw new mdeException ("Fatal: no data path found!"); // Configuration paths: confDir.tryPath (staticPath.toString ~ CONF); confDir.tryPath (installPath.append("user").toString); confDir.tryPath (userPath.toString ~ CONF, true); if (extraConfPath) confDir.tryPath (extraConfPath); if (!confDir.pathsLen) throw new mdeException ("Fatal: no conf path found!"); // Logging path: logDir = userPath.toString; } } else { static assert (false, "Platform is not linux or Windows: no support for paths on this platform yet!"); } /// For command line args: these paths are added if non-null, with highest priority. char[] extraDataPath, extraConfPath; private { class PathException : mdeException { this(char[] msg) { super (msg); } } // The maximum number of paths for any one "directory". // There are NO CHECKS that this is not exceeded. const MAX_PATHS = 4; /* Try each path in succession, returning the first to exist and be a folder. * If none are valid and create is true, will try creating each in turn. * If still none are valid, throws. */ FilePath findPath (bool create, char[][] paths ...) { FilePath[] fps; fps.length = paths.length; foreach (i,path; paths) { FilePath pv = new FilePath (path); if (pv.exists && pv.isFolder) return pv; // got a valid path fps[i] = pv; } if (create) { // try to create a folder, using each path in turn until succesful foreach (fp; fps) { try { return fp.create; } catch (Exception e) {} } } // no valid path... char[] msg = "Unable to find"~(create ? " or create" : "")~" a required path! The following were tried:"; foreach (path; paths) msg ~= " \"" ~ path ~ '\"'; throw new PathException (msg); } //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 (FilePath[] files, DataSet ds, bool rdHeader) in { assert (files !is null, "mdeReader.this: files is null"); } body { // Don't let sub-readers create their own, separate, datasets: if (ds is null) ds = new DataSet; foreach (file; files) { IReader r = makeReader (file, ds, rdHeader); readers[readersLen++] = r; } } 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; } } /// Thrown when makeMTReader couldn't find a file. class NoFileException : MTFileIOException { this (char[] msg) { super(msg); } }