# HG changeset patch # User Diggory Hardy # Date 1233241185 0 # Node ID 7ababdf97748880287c9b76afca191a965a2fc48 # Parent 9fd705793568ee7440eb9a667976f3375e1e48f8 Moved mde.setup.paths to mde.file.paths and paths.mdeReader to mde.file.mergetag.Reader.MTMultiReader. diff -r 9fd705793568 -r 7ababdf97748 mde/file/mergetag/Reader.d --- a/mde/file/mergetag/Reader.d Fri Jan 23 16:05:05 2009 +0000 +++ b/mde/file/mergetag/Reader.d Thu Jan 29 14:59:45 2009 +0000 @@ -518,3 +518,67 @@ void read (ID[] secSet) {} /// ditto void read (IContainer!(ID) secSet) {}/// ditto } + + +/** A special adapter for reading from multiple mergetag files. + * + * The number of files $(B must not) exceed MAX_PATHS. */ +class MTMultiReader : IReader +{ + 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; + + Exception exc; + foreach (file; files) { + try { // try reading header of each file + IReader r = makeReader (file, ds, rdHeader); + readers[readersLen++] = r; + } catch (Exception e) { + exc = e; + } + } + if (readersLen == 0) // no files have valid headers + throw exc; // fail: re-throw last exception + } + + 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 (IContainer !(ID) secSet) { /// ditto + for (uint i = 0; i < readersLen; ++i) readers[i].read(secSet); + } + + const MAX_READERS = 4; +private: + // Use a simpler static array: + IReader[MAX_READERS] readers; + ubyte readersLen = 0; +} diff -r 9fd705793568 -r 7ababdf97748 mde/file/paths.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mde/file/paths.d Thu Jan 29 14:59:45 2009 +0000 @@ -0,0 +1,360 @@ +/* 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 . */ + +/** Resource paths module. + * + * Internally, most mde code using files doesn't use absolute paths but paths + * relative to mde directory. Further, mergetag files are usually not read + * just from one file, but are the result of merging multiple files from the + * same relative paths in different directories, including the root data + * directory, possibly a directory in /etc, a directory for files specific to + * the user accont, and possibly other directories. + * + * This module transforms relative paths to absolute paths, allowing for + * multiple files to be read and merged. + * + * The base paths from which relative files are read are slit into two groups: + * data paths, and conf paths (usually the conf paths refer to a "conf" + * directory within the data paths). */ +/* 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.file.paths; + +import mde.exception; +import mde.file.mergetag.Reader; +import mde.file.mergetag.Writer; +import mde.file.mergetag.DataSet; +import mde.file.mergetag.exception; + +import tango.io.Console; +import tango.io.FilePath; +version (linux) { + import tango.io.FileScan; + import tango.util.container.SortedMap; + import tango.sys.Environment; +} else version (Windows) + import tango.sys.win32.SpecialPath; + +debug { + import tango.util.log.Log : Log, Logger; + private Logger logger; + static this() { + logger = Log.getLogger ("mde.file.paths"); + } +} + +/** 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 MTMultiReader (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; + } + + /// 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); + version (Windows) { + Cout ("\nFont directory:\n\t")(fontDir).newline; + } else version (linux) { + Cout ("\nFont files found:"); + foreach (f,p; fontFiles) + Cout ("\n\t")(f)("\t")(p[0..$-1]); + Cout.newline; + } + } + +private: + FilePath[] getFiles (char[] filename, PRIORITY readOrder) + { + FilePath[] 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) { + assert (pathsLen < MAX_PATHS); + paths[pathsLen++] = path~'/'; + } + + // Test a path and add if is a folder. + bool tryPath (char[] path, bool create = false) { + assert (pathsLen < MAX_PATHS); + 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; /// Directory for log files + +//BEGIN Path resolution +/** Find at least one path for each required directory. */ +debug (mdeUnitTest) { + /** In unittest mode, add paths unittest/data and unittest/conf before init runs. */ + static this () { + dataDir.tryPath ("unittest/data"); + confDir.tryPath ("unittest/conf"); + if (!(dataDir.pathsLen && confDir.pathsLen)) + throw new mdeException ("Fatal: unittest/data and unittest/conf don't both exist"); + // Don't bother with real paths or logDir or font dir(s) for unittest. + } +} +version (linux) { + SortedMap!(char[],char[]) fontFiles; // key is file name, value is CString path + /** Get the actual path of a font file, or throw NoFileException if not found. + * + * Returns a C string (null terminated). */ + char[] getFontPath (char[] file) { + char[] ret; + if (fontFiles.get (file, ret)) + return ret; + throw new NoFileException ("Unable to find font file: "~file); + } + + // base-path not used on posix + void resolvePaths (char[] base = "data") { + // 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", base); + // 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; + + // Font paths: + auto fs = new FileScan; + // Scan for directories containing truetype and type1 fonts: + fs.sweep ("/usr/share/fonts", delegate bool(FilePath fp, bool isDir) + { return isDir || fp.suffix == ".ttf" || fp.suffix == ".pfb"; }, + true); + fontFiles = new SortedMap!(char[],char[]); + foreach (fp; fs.files) + fontFiles.add (fp.file, fp.cString); // both strings should be slices of same memory + } +} else version (Windows) { + char[] fontDir; + /** Get the actual path of a font file, or throw NoFileException if not found. + * + * Returns a C string (null terminated). */ + char[] getFontPath (char[] file) { + FilePath path = new FilePath (fontDir~file); + if (path.exists && !path.isFolder) + return path.cString; + throw new NoFileException ("Unable to find font file: "~file); + } + + void resolvePaths (char[] base = "./") { + //FIXME: Get base path from registry + + // Base paths: + FilePath installPath = findPath (false, base); + FilePath staticPath = findPath (false, installPath.toString); + FilePath userPath = findPath (true, getSpecialPath(CSIDL_LOCAL_APPDATA) ~ "/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 (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; + + // Font path: + fontDir = getSpecialPath (CSIDL_FONTS) ~ "/"; // append separator + } +} 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". + const MAX_PATHS = 4; + static assert (MTMultiReader.MAX_READERS == MAX_PATHS, "MAX_PATHS not all equal"); + + /* 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 + +/// Thrown when makeMTReader couldn't find a file. +class NoFileException : MTFileIOException { + this (char[] msg) { + super(msg); + } +} \ No newline at end of file diff -r 9fd705793568 -r 7ababdf97748 mde/font/font.d --- a/mde/font/font.d Fri Jan 23 16:05:05 2009 +0000 +++ b/mde/font/font.d Thu Jan 29 14:59:45 2009 +0000 @@ -22,7 +22,7 @@ import mde.font.exception; import mde.setup.exception; // InitStage stuff -import mde.setup.paths; +import mde.file.paths; import derelict.freetype.ft; diff -r 9fd705793568 -r 7ababdf97748 mde/gui/WidgetManager.d --- a/mde/gui/WidgetManager.d Fri Jan 23 16:05:05 2009 +0000 +++ b/mde/gui/WidgetManager.d Thu Jan 29 14:59:45 2009 +0000 @@ -37,7 +37,7 @@ import mt = mde.file.mergetag.DataSet; import mde.file.mergetag.Reader; import mde.file.mergetag.Writer; -import mde.setup.paths; +import mde.file.paths; // Widgets to create: import mde.gui.widget.layout; diff -r 9fd705793568 -r 7ababdf97748 mde/input/Config.d --- a/mde/input/Config.d Fri Jan 23 16:05:05 2009 +0000 +++ b/mde/input/Config.d Thu Jan 29 14:59:45 2009 +0000 @@ -19,7 +19,7 @@ import mde.input.exception; import MT = mde.file.mergetag.Reader; -import mde.setup.paths; +import mde.file.paths; import mde.file.deserialize; debug import mde.file.serialize; diff -r 9fd705793568 -r 7ababdf97748 mde/lookup/Options.d --- a/mde/lookup/Options.d Fri Jan 23 16:05:05 2009 +0000 +++ b/mde/lookup/Options.d Thu Jan 29 14:59:45 2009 +0000 @@ -17,7 +17,7 @@ * of a simple type (see Options.TYPES). */ module mde.lookup.Options; -import mde.setup.paths; +import mde.file.paths; import mde.exception; public import mde.content.AStringContent; diff -r 9fd705793568 -r 7ababdf97748 mde/lookup/Translation.d --- a/mde/lookup/Translation.d Fri Jan 23 16:05:05 2009 +0000 +++ b/mde/lookup/Translation.d Thu Jan 29 14:59:45 2009 +0000 @@ -38,7 +38,7 @@ module mde.lookup.Translation; import mde.lookup.Options; -import mde.setup.paths; +import mde.file.paths; import mde.exception; import mde.file.mergetag.DataSet; diff -r 9fd705793568 -r 7ababdf97748 mde/setup/Init.d --- a/mde/setup/Init.d Fri Jan 23 16:05:05 2009 +0000 +++ b/mde/setup/Init.d Thu Jan 29 14:59:45 2009 +0000 @@ -44,7 +44,7 @@ import mde.setup.exception; import mde.lookup.Options; -import paths = mde.setup.paths; +import paths = mde.file.paths; import mde.exception; // optionsLoadException import imde = mde.imde; diff -r 9fd705793568 -r 7ababdf97748 mde/setup/paths.d --- a/mde/setup/paths.d Fri Jan 23 16:05:05 2009 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,418 +0,0 @@ -/* 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 . */ - -/** 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.file.mergetag.Reader; -import mde.file.mergetag.Writer; -import mde.file.mergetag.DataSet; -import mde.file.mergetag.exception; - -import tango.io.Console; -import tango.io.FilePath; -version (linux) { - import tango.io.FileScan; - import tango.util.container.SortedMap; - import tango.sys.Environment; -} else version (Windows) - import tango.sys.win32.SpecialPath; - -debug { - import tango.util.log.Log : Log, Logger; - private Logger logger; - static this() { - logger = Log.getLogger ("mde.setup.paths"); - } -} - -/** 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; - } - - /// 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); - version (Windows) { - Cout ("\nFont directory:\n\t")(fontDir).newline; - } else version (linux) { - Cout ("\nFont files found:"); - foreach (f,p; fontFiles) - Cout ("\n\t")(f)("\t")(p[0..$-1]); - Cout.newline; - } - } - -private: - FilePath[] getFiles (char[] filename, PRIORITY readOrder) - { - FilePath[] 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) { - assert (pathsLen < MAX_PATHS); - paths[pathsLen++] = path~'/'; - } - - // Test a path and add if is a folder. - bool tryPath (char[] path, bool create = false) { - assert (pathsLen < MAX_PATHS); - 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; /// Directory for log files - -//BEGIN Path resolution -/** Find at least one path for each required directory. */ -debug (mdeUnitTest) { - /** In unittest mode, add paths unittest/data and unittest/conf before init runs. */ - static this () { - dataDir.tryPath ("unittest/data"); - confDir.tryPath ("unittest/conf"); - if (!(dataDir.pathsLen && confDir.pathsLen)) - throw new mdeException ("Fatal: unittest/data and unittest/conf don't both exist"); - // Don't bother with real paths or logDir or font dir(s) for unittest. - } -} -version (linux) { - SortedMap!(char[],char[]) fontFiles; // key is file name, value is CString path - /** Get the actual path of a font file, or throw NoFileException if not found. - * - * Returns a C string (null terminated). */ - char[] getFontPath (char[] file) { - char[] ret; - if (fontFiles.get (file, ret)) - return ret; - throw new NoFileException ("Unable to find font file: "~file); - } - - // base-path not used on posix - void resolvePaths (char[] base = "data") { - // 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", base); - // 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; - - // Font paths: - auto fs = new FileScan; - // Scan for directories containing truetype and type1 fonts: - fs.sweep ("/usr/share/fonts", (FilePath fp, bool isDir) - { return isDir || fp.suffix == ".ttf" || fp.suffix == ".pfb"; }, - true); - fontFiles = new SortedMap!(char[],char[]); - foreach (fp; fs.files) - fontFiles.add (fp.file, fp.cString); // both strings should be slices of same memory - } -} else version (Windows) { - char[] fontDir; - /** Get the actual path of a font file, or throw NoFileException if not found. - * - * Returns a C string (null terminated). */ - char[] getFontPath (char[] file) { - FilePath path = new FilePath (fontDir~file); - if (path.exists && !path.isFolder) - return path.cString; - throw new NoFileException ("Unable to find font file: "~file); - } - - void resolvePaths (char[] base = "./") { - //FIXME: Get base path from registry - - // Base paths: - FilePath installPath = findPath (false, base); - FilePath staticPath = findPath (false, installPath.toString); - FilePath userPath = findPath (true, getSpecialPath(CSIDL_LOCAL_APPDATA) ~ "/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 (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; - - // Font path: - fontDir = getSpecialPath (CSIDL_FONTS) ~ "/"; // append separator - } -} 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". - 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; - - Exception exc; - foreach (file; files) { - try { // try reading header of each file - IReader r = makeReader (file, ds, rdHeader); - readers[readersLen++] = r; - } catch (Exception e) { - exc = e; - } - } - if (readersLen == 0) // no files have valid headers - throw exc; // fail: re-throw last exception - } - - 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 (IContainer !(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); - } -} \ No newline at end of file