comparison 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
comparison
equal deleted inserted replaced
14:0047b364b6d9 15:4608be19ebe2
1 /** Resource paths module.
2 *
3 * Internally to mde code other than code dealing directly with files and this module, paths are
4 * relative to the mde directory. This module transforms those paths to absolute paths.
5 *
6 * Additionally, the intention is to look for all files in two directories: the installation (i.e.
7 * main data) directory and a user directory (for user-specific configuration). Besides exposing
8 * both paths and checking in which valid files exist, this module provides some extra mergetag
9 * functionality to simplify correct reading and writing.
10 *
11 * Currently the paths are found as follows: (see codeDoc/paths.txt)
12 */
13 /* Implementation note:
14 * All paths are stored internally as strings, rather than as an instance of FilePath/PathView once
15 * the FilePath has served its immediate purpose, since it's more convenient and creating new
16 * FilePaths for adjusted paths should be no slower than mutating existing ones. */
17 module mde.resource.paths;
18
19 import mde.exception;
20 import mde.mergetag.Reader;
21 import mde.mergetag.Writer;
22 import mde.mergetag.DataSet;
23 import mde.mergetag.exception;
24
25 import tango.io.FilePath;
26 import tango.util.log.Log : Log, Logger;
27 import tango.stdc.stdlib;
28 import tango.stdc.stringz;
29
30 /** Order to read files in.
31 *
32 * Values: HIGH_LOW, LOW_HIGH, HIGH_ONLY. */
33 enum PRIORITY : byte { HIGH_LOW, LOW_HIGH, HIGH_ONLY }
34
35 /** This struct has one instance for each "directory".
36 *
37 * It is the only item within this module that you should need to interact with. */
38 struct mdeDirectory
39 {
40 /** Creates an MT reader for each file.
41 *
42 * Params:
43 * file = The file path and name relative to the mdeDirectory, without a suffix
44 * (e.g. "options")
45 * readOrder = Read the highest priority or lowest priority files first? For correct merging,
46 * this should be LOW_HIGH when newly-read items override old ones (as is the case
47 * with DefaultData) and HIGH_LOW when the first-read items survive. Thus override
48 * order needs to be the same for each section, except the header which is always
49 * read with LOW_HIGH order.
50 * Alternately, for files which shouldn't be
51 * merged where only the highest priority file should be read, pass HIGH_ONLY.
52 * ds = The dataset, as for mergetag. Note: all actual readers share one dataset.
53 * rdHeader = Read the headers for each file and merge if rdHeader == true.
54 */
55 IReader makeMTReader (char[] file, PRIORITY readOrder, DataSet ds = null, bool rdHeader = false)
56 {
57 if (readOrder == PRIORITY.HIGH_ONLY) return makeReader (paths[pathsLen-1] ~ file, ds, rdHeader);
58 else return new mdeReader (file, readOrder, ds, rdHeader, paths);
59 }
60
61 /** Creates an MT writer for file deciding on the best path to use.
62 *
63 * Params:
64 * file = The file path and name relative to the mdeDirectory, without a suffix
65 * (e.g. "options")
66 * ds = The dataset, as for mergetag.
67 */
68 IWriter makeMTWriter (char[] file, DataSet ds = null)
69 {
70 // FIXME: use highest priority writable path
71 return makeWriter (paths[pathsLen-1] ~ file, ds, WriterMethod.Text);
72 }
73
74 /** Check whether the given file exists under any path with either .mtt or .mtb suffix. */
75 bool exists (char[] file) {
76 for (uint i = 0; i < pathsLen; ++i) {
77 if (FilePath (paths[i]~file~".mtt").exists) return true;
78 if (FilePath (paths[i]~file~".mtb").exists) return true;
79 }
80 return false;
81 }
82
83 private:
84
85 // Unconditionally add a path
86 void addPath (char[] path) {
87 paths[pathsLen++] = path~'/';
88 }
89
90 // Test a path and add if is a folder
91 void tryPath (char[] path) {
92 PathView pv = FilePath (path);
93 if (pv.exists && pv.isFolder) {
94 paths[pathsLen++] = path~'/';
95 logger.info ("Path "~path~" is writable: " ~ (pv.isWritable ? "yes" : "no"));
96 }
97 }
98
99 // Use a static array to store all possible paths with separate length counters.
100 // Lowest priority paths are first.
101 char[][MAX_PATHS] paths;
102 ubyte pathsLen = 0;
103 }
104
105 /** These are the actual instances, one for each of the data and conf "directories". */
106 mdeDirectory dataDir, confDir;
107
108 //BEGIN Path resolution
109 static this() {
110 logger = Log.getLogger ("mde.resource.paths");
111
112 // NOTE: May fail. Currently I think I'll just let the exception halt execution.
113 resolvePaths();
114 if (!dataDir.pathsLen) throw new mdeException ("Fatal: no data path found!");
115 if (!confDir.pathsLen) throw new mdeException ("Fatal: no conf path found!");
116 }
117
118 private:
119 // The maximum number of paths for any one "directory".
120 // There are NO CHECKS that this is not exceeded.
121 const MAX_PATHS = 3;
122
123 Logger logger;
124
125 /* Try each path in succession, returning the first two exist and be a folder. Throws if none are. */
126 char[] findPath (char[][] paths ...) {
127 foreach (path; paths) {
128 PathView pv = new FilePath (path);
129 if (pv.exists && pv.isFolder) return pv.toString; // got a valid path
130 }
131 // no valid path...
132 logger.fatal ("Unable to resolve a required path! The following were tried:");
133 foreach (path; paths) logger.fatal ('\t' ~ path);
134 throw new mdeException ("Unable to resolve a required path (see log for details).");
135 }
136
137 // These are used several times:
138 const DATA = "/data";
139 const CONF = "/conf";
140
141 version (linux) {
142 void resolvePaths () {
143 // Home directory:
144 char[] HOME = fromStringz (getenv (toStringz ("HOME")));
145
146 // Base paths:
147 char[] staticPath = findPath ("/usr/share/games/mde", "/usr/local/share/games/mde", "data");
148 char[] userPath = findPath (HOME~"/.config/mde", HOME~"/.mde");
149
150 // Static data paths:
151 dataDir.addPath (staticPath); // we know this is valid anyway
152 dataDir.tryPath (userPath ~ DATA);
153
154 // Configuration paths:
155 confDir.tryPath (staticPath ~ CONF);
156 confDir.tryPath ("/etc/mde");
157 confDir.tryPath (userPath ~ CONF);
158 }
159 } else version (Windows) {
160 void resolvePaths () {
161 static assert (false, "No registry code");
162
163 // Base paths:
164 char[] userPath = `...`;
165 char[] installPath = `registryInstallPath or "."`;
166 char[] staticPath = findPath (installPath ~ DATA);
167
168 // Static data paths:
169 dataDir.addPath[dataLength++] = staticPath; // we know this is valid anyway
170 dataDir.tryPath (userPath ~ DATA);
171
172 // Configuration paths:
173 confDir.tryPath (staticPath.toString ~ CONF);
174 confDir.tryPath (installPath ~ CONF);
175 confDir.tryPath (userPath.toString ~ CONF);
176 }
177 } else {
178 static assert (false, "Platform is not linux or Windows: no support for paths on this platform yet!");
179 }
180 //END Path resolution
181
182 /** A special adapter for reading from multiple mergetag files with the same relative path to an
183 * mdeDirectory simultaneously.
184 */
185 class mdeReader : IReader
186 {
187 private this (char[] file, PRIORITY readOrder, DataSet ds, bool rdHeader, char[][MAX_PATHS] paths)
188 in { assert (readOrder == PRIORITY.LOW_HIGH || readOrder == PRIORITY.HIGH_LOW); }
189 body {
190 rdOrder = readOrder;
191 if (ds is null) ds = new DataSet;
192
193 foreach (path; paths) {
194 try {
195 IReader r = makeReader (path~file, ds, rdHeader);
196
197 readers[readersLen++] = r;
198 }
199 catch (MTFileIOException) {} // Ignore errors regarding no file for now.
200 }
201
202 if (readersLen == 0) { // totally failed to find any valid files
203 throw new MTFileIOException ("Unable to find the file: "~file[1..$]~"[.mtt|mtb]");
204 }
205
206 // This is simply the easiest way of adjusting the reading order:
207 if (readOrder == PRIORITY.HIGH_LOW) readers[0..readersLen].reverse;
208 }
209
210 DataSet dataset () { /// Get the DataSet
211 return readers[0].dataset; // all readers share the same dataset
212 }
213 void dataset (DataSet ds) { /// Set the DataSet
214 for (uint i = 0; i < readersLen; ++i) readers[i].dataset (ds);
215 }
216
217 void dataSecCreator (IDataSection delegate (ID) dsC) { /// Set the dataSecCreator
218 for (uint i = 0; i < readersLen; ++i) readers[i].dataSecCreator = dsC;
219 }
220
221 /** Get identifiers for all sections.
222 *
223 * Note: the identifiers from all sections in all files are just strung together, starting with
224 * the highest-priority file. */
225 ID[] getSectionNames () {
226 ID[] names;
227 for (int i = readersLen-1; i >= 0; --i) names ~= readers[i].getSectionNames;
228 return names;
229 }
230 void read () { /// Commence reading
231 for (uint i = 0; i < readersLen; ++i) readers[i].read();
232 }
233 void read (ID[] secSet) { /// ditto
234 for (uint i = 0; i < readersLen; ++i) readers[i].read(secSet);
235 }
236 void read (View!(ID) secSet) { /// ditto
237 for (uint i = 0; i < readersLen; ++i) readers[i].read(secSet);
238 }
239
240 private:
241 IReader[MAX_PATHS] readers;
242 ubyte readersLen = 0;
243
244 PRIORITY rdOrder;
245 }