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