Mercurial > projects > mde
comparison mde/file/paths.d @ 134:7ababdf97748
Moved mde.setup.paths to mde.file.paths and paths.mdeReader to mde.file.mergetag.Reader.MTMultiReader.
author | Diggory Hardy <diggory.hardy@gmail.com> |
---|---|
date | Thu, 29 Jan 2009 14:59:45 +0000 |
parents | mde/setup/paths.d@1b1e2297e2fc |
children | bc697a218716 |
comparison
equal
deleted
inserted
replaced
133:9fd705793568 | 134:7ababdf97748 |
---|---|
1 /* LICENSE BLOCK | |
2 Part of mde: a Modular D game-oriented Engine | |
3 Copyright © 2007-2008 Diggory Hardy | |
4 | |
5 This program is free software: you can redistribute it and/or modify it under the terms | |
6 of the GNU General Public License as published by the Free Software Foundation, either | |
7 version 2 of the License, or (at your option) any later version. | |
8 | |
9 This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; | |
10 without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. | |
11 See the GNU General Public License for more details. | |
12 | |
13 You should have received a copy of the GNU General Public License | |
14 along with this program. If not, see <http://www.gnu.org/licenses/>. */ | |
15 | |
16 /** Resource paths module. | |
17 * | |
18 * Internally, most mde code using files doesn't use absolute paths but paths | |
19 * relative to mde directory. Further, mergetag files are usually not read | |
20 * just from one file, but are the result of merging multiple files from the | |
21 * same relative paths in different directories, including the root data | |
22 * directory, possibly a directory in /etc, a directory for files specific to | |
23 * the user accont, and possibly other directories. | |
24 * | |
25 * This module transforms relative paths to absolute paths, allowing for | |
26 * multiple files to be read and merged. | |
27 * | |
28 * The base paths from which relative files are read are slit into two groups: | |
29 * data paths, and conf paths (usually the conf paths refer to a "conf" | |
30 * directory within the data paths). */ | |
31 /* Implementation note: | |
32 * All paths are stored internally as strings, rather than as an instance of FilePath/FilePath once | |
33 * the FilePath has served its immediate purpose, since it's more convenient and creating new | |
34 * FilePaths for adjusted paths should be no slower than mutating existing ones. */ | |
35 module mde.file.paths; | |
36 | |
37 import mde.exception; | |
38 import mde.file.mergetag.Reader; | |
39 import mde.file.mergetag.Writer; | |
40 import mde.file.mergetag.DataSet; | |
41 import mde.file.mergetag.exception; | |
42 | |
43 import tango.io.Console; | |
44 import tango.io.FilePath; | |
45 version (linux) { | |
46 import tango.io.FileScan; | |
47 import tango.util.container.SortedMap; | |
48 import tango.sys.Environment; | |
49 } else version (Windows) | |
50 import tango.sys.win32.SpecialPath; | |
51 | |
52 debug { | |
53 import tango.util.log.Log : Log, Logger; | |
54 private Logger logger; | |
55 static this() { | |
56 logger = Log.getLogger ("mde.file.paths"); | |
57 } | |
58 } | |
59 | |
60 /** Order to read files in. | |
61 * | |
62 * Values: HIGH_LOW, LOW_HIGH, HIGH_ONLY. */ | |
63 enum PRIORITY : byte { HIGH_LOW, LOW_HIGH, HIGH_ONLY } | |
64 | |
65 /** This struct has one instance for each "directory". | |
66 * | |
67 * It is the only item within this module that you should need to interact with. | |
68 * | |
69 * In the case of confDir, the user path is guaranteed to exist (as highest priority path). */ | |
70 struct mdeDirectory | |
71 { | |
72 /** Creates an MT reader for each file. | |
73 * | |
74 * Params: | |
75 * file = The file path and name relative to the mdeDirectory, without a suffix | |
76 * (e.g. "options") | |
77 * readOrder = Read the highest priority or lowest priority files first? For correct merging, | |
78 * this should be LOW_HIGH when newly-read items override old ones (as is the case | |
79 * with DefaultData) and HIGH_LOW when the first-read items survive. Thus override | |
80 * order needs to be the same for each section, except the header which is always | |
81 * read with LOW_HIGH order. | |
82 * Alternately, for files which shouldn't be | |
83 * merged where only the highest priority file should be read, pass HIGH_ONLY. | |
84 * ds = The dataset, as for mergetag. Note: all actual readers share one dataset. | |
85 * rdHeader = Read the headers for each file and merge if rdHeader == true. | |
86 */ | |
87 IReader makeMTReader (char[] file, PRIORITY readOrder, DataSet ds = null, bool rdHeader = false) | |
88 { | |
89 FilePath[] files = getFiles (file, readOrder); | |
90 if (files is null) | |
91 throw new NoFileException ("Unable to find the file: "~file~"[.mtt|mtb]"); | |
92 | |
93 return new MTMultiReader (files, ds, rdHeader); | |
94 } | |
95 | |
96 /** Creates an MT writer for file deciding on the best path to use. | |
97 * | |
98 * Params: | |
99 * file = The file path and name relative to the mdeDirectory, without a suffix | |
100 * (e.g. "options") | |
101 * ds = The dataset, as for mergetag. | |
102 */ | |
103 IWriter makeMTWriter (char[] file, DataSet ds = null) | |
104 { | |
105 // FIXME: use highest priority writable path | |
106 return makeWriter (paths[pathsLen-1] ~ file, ds, WriterMethod.Text); | |
107 } | |
108 | |
109 /** Returns a string listing the file name or names (if readOrder is not HIGH_ONLY and multiple | |
110 * matches are found), or "no file found". Intended for user output only. */ | |
111 char[] getFileName (char[] file, PRIORITY readOrder) | |
112 { | |
113 FilePath[] files = getFiles (file, readOrder); | |
114 if (files is null) | |
115 return "no file found"; | |
116 | |
117 char[] ret = files[0].toString; | |
118 foreach (f; files[1..$]) | |
119 ret ~= ", " ~ f.toString; | |
120 return ret; | |
121 } | |
122 | |
123 /// Print all paths found. | |
124 static void printPaths () { | |
125 Cout ("Data paths found:"); | |
126 dataDir.coutPaths; | |
127 Cout ("\nConf paths found:"); | |
128 confDir.coutPaths; | |
129 Cout ("\nLog file directory:\n\t")(logDir); | |
130 version (Windows) { | |
131 Cout ("\nFont directory:\n\t")(fontDir).newline; | |
132 } else version (linux) { | |
133 Cout ("\nFont files found:"); | |
134 foreach (f,p; fontFiles) | |
135 Cout ("\n\t")(f)("\t")(p[0..$-1]); | |
136 Cout.newline; | |
137 } | |
138 } | |
139 | |
140 private: | |
141 FilePath[] getFiles (char[] filename, PRIORITY readOrder) | |
142 { | |
143 FilePath[] ret; | |
144 | |
145 if (readOrder == PRIORITY.LOW_HIGH) { | |
146 for (size_t i = 0; i < pathsLen; ++i) { | |
147 FilePath file = findFile (paths[i]~filename); | |
148 if (file !is null) | |
149 ret ~= file; | |
150 } | |
151 } else { | |
152 assert (readOrder == PRIORITY.HIGH_LOW || | |
153 readOrder == PRIORITY.HIGH_ONLY ); | |
154 | |
155 for (int i = pathsLen - 1; i >= 0; --i) { | |
156 FilePath file = findFile (paths[i]~filename); | |
157 if (file !is null) | |
158 ret ~= file; | |
159 if (readOrder == PRIORITY.HIGH_ONLY) break; | |
160 } | |
161 } | |
162 return ret; | |
163 } | |
164 | |
165 // Unconditionally add a path | |
166 void addPath (char[] path) { | |
167 assert (pathsLen < MAX_PATHS); | |
168 paths[pathsLen++] = path~'/'; | |
169 } | |
170 | |
171 // Test a path and add if is a folder. | |
172 bool tryPath (char[] path, bool create = false) { | |
173 assert (pathsLen < MAX_PATHS); | |
174 FilePath fp = FilePath (path); | |
175 if (fp.exists && fp.isFolder) { | |
176 paths[pathsLen++] = path~'/'; | |
177 return true; | |
178 } else if (create) { | |
179 try { | |
180 fp.create; | |
181 paths[pathsLen++] = fp.toString~'/'; | |
182 return true; | |
183 } catch (Exception e) { | |
184 // No logging avaiable yet: Use Stdout/Cout | |
185 Cout ("Creating path "~path~" failed:" ~ e.msg).newline; | |
186 } | |
187 } | |
188 return false; | |
189 } | |
190 | |
191 void coutPaths () { | |
192 if (pathsLen) { | |
193 for (size_t i = 0; i < pathsLen; ++i) | |
194 Cout ("\n\t" ~ paths[i]); | |
195 } else | |
196 Cout ("[none]"); | |
197 } | |
198 | |
199 // Use a static array to store all possible paths with separate length counters. | |
200 // Lowest priority paths are first. | |
201 char[][MAX_PATHS] paths; | |
202 ubyte pathsLen = 0; | |
203 } | |
204 | |
205 /** These are the actual instances, one for each of the data and conf "directories". */ | |
206 mdeDirectory dataDir, confDir; | |
207 char[] logDir; /// Directory for log files | |
208 | |
209 //BEGIN Path resolution | |
210 /** Find at least one path for each required directory. */ | |
211 debug (mdeUnitTest) { | |
212 /** In unittest mode, add paths unittest/data and unittest/conf before init runs. */ | |
213 static this () { | |
214 dataDir.tryPath ("unittest/data"); | |
215 confDir.tryPath ("unittest/conf"); | |
216 if (!(dataDir.pathsLen && confDir.pathsLen)) | |
217 throw new mdeException ("Fatal: unittest/data and unittest/conf don't both exist"); | |
218 // Don't bother with real paths or logDir or font dir(s) for unittest. | |
219 } | |
220 } | |
221 version (linux) { | |
222 SortedMap!(char[],char[]) fontFiles; // key is file name, value is CString path | |
223 /** Get the actual path of a font file, or throw NoFileException if not found. | |
224 * | |
225 * Returns a C string (null terminated). */ | |
226 char[] getFontPath (char[] file) { | |
227 char[] ret; | |
228 if (fontFiles.get (file, ret)) | |
229 return ret; | |
230 throw new NoFileException ("Unable to find font file: "~file); | |
231 } | |
232 | |
233 // base-path not used on posix | |
234 void resolvePaths (char[] base = "data") { | |
235 // Home directory: | |
236 char[] HOME = Environment.get("HOME", "."); | |
237 | |
238 // Base paths: | |
239 // Static data (must exist): | |
240 FilePath staticPath = | |
241 findPath (false, "/usr/share/games/mde", "/usr/local/share/games/mde", base); | |
242 // Config (can just use defaults if necessary, so long as we can save afterwards): | |
243 FilePath userPath = findPath (true, HOME~"/.config/mde", HOME~"/.mde"); | |
244 | |
245 // Static data paths: | |
246 dataDir.addPath (staticPath.toString); // we know this is valid anyway | |
247 dataDir.tryPath (userPath.toString ~ "/data"); | |
248 if (extraDataPath) dataDir.tryPath (extraDataPath); | |
249 if (!dataDir.pathsLen) throw new mdeException ("Fatal: no data path found!"); | |
250 | |
251 // Configuration paths: | |
252 confDir.tryPath (staticPath.toString ~ "/conf"); | |
253 confDir.tryPath ("/etc/mde"); | |
254 confDir.tryPath (userPath.toString ~ "/conf", true); | |
255 if (extraConfPath) confDir.tryPath (extraConfPath); | |
256 if (!confDir.pathsLen) throw new mdeException ("Fatal: no conf path found!"); | |
257 | |
258 // Logging path: | |
259 logDir = userPath.toString; | |
260 | |
261 // Font paths: | |
262 auto fs = new FileScan; | |
263 // Scan for directories containing truetype and type1 fonts: | |
264 fs.sweep ("/usr/share/fonts", delegate bool(FilePath fp, bool isDir) | |
265 { return isDir || fp.suffix == ".ttf" || fp.suffix == ".pfb"; }, | |
266 true); | |
267 fontFiles = new SortedMap!(char[],char[]); | |
268 foreach (fp; fs.files) | |
269 fontFiles.add (fp.file, fp.cString); // both strings should be slices of same memory | |
270 } | |
271 } else version (Windows) { | |
272 char[] fontDir; | |
273 /** Get the actual path of a font file, or throw NoFileException if not found. | |
274 * | |
275 * Returns a C string (null terminated). */ | |
276 char[] getFontPath (char[] file) { | |
277 FilePath path = new FilePath (fontDir~file); | |
278 if (path.exists && !path.isFolder) | |
279 return path.cString; | |
280 throw new NoFileException ("Unable to find font file: "~file); | |
281 } | |
282 | |
283 void resolvePaths (char[] base = "./") { | |
284 //FIXME: Get base path from registry | |
285 | |
286 // Base paths: | |
287 FilePath installPath = findPath (false, base); | |
288 FilePath staticPath = findPath (false, installPath.toString); | |
289 FilePath userPath = findPath (true, getSpecialPath(CSIDL_LOCAL_APPDATA) ~ "/mde"); | |
290 | |
291 // Static data paths: | |
292 dataDir.addPath (staticPath.toString); // we know this is valid anyway | |
293 dataDir.tryPath (userPath.toString ~ "/data"); | |
294 if (extraDataPath) dataDir.tryPath (extraDataPath); | |
295 if (!dataDir.pathsLen) throw new mdeException ("Fatal: no data path found!"); | |
296 | |
297 // Configuration paths: | |
298 confDir.tryPath (staticPath.toString ~ "/conf"); | |
299 confDir.tryPath (installPath.append("user").toString); | |
300 confDir.tryPath (userPath.toString ~ "/conf", true); | |
301 if (extraConfPath) confDir.tryPath (extraConfPath); | |
302 if (!confDir.pathsLen) throw new mdeException ("Fatal: no conf path found!"); | |
303 | |
304 // Logging path: | |
305 logDir = userPath.toString; | |
306 | |
307 // Font path: | |
308 fontDir = getSpecialPath (CSIDL_FONTS) ~ "/"; // append separator | |
309 } | |
310 } else { | |
311 static assert (false, "Platform is not linux or Windows: no support for paths on this platform yet!"); | |
312 } | |
313 | |
314 /// For command line args: these paths are added if non-null, with highest priority. | |
315 char[] extraDataPath, extraConfPath; | |
316 | |
317 private { | |
318 class PathException : mdeException { | |
319 this(char[] msg) { | |
320 super (msg); | |
321 } | |
322 } | |
323 | |
324 // The maximum number of paths for any one "directory". | |
325 const MAX_PATHS = 4; | |
326 static assert (MTMultiReader.MAX_READERS == MAX_PATHS, "MAX_PATHS not all equal"); | |
327 | |
328 /* Try each path in succession, returning the first to exist and be a folder. | |
329 * If none are valid and create is true, will try creating each in turn. | |
330 * If still none are valid, throws. */ | |
331 FilePath findPath (bool create, char[][] paths ...) { | |
332 FilePath[] fps; | |
333 fps.length = paths.length; | |
334 foreach (i,path; paths) { | |
335 FilePath pv = new FilePath (path); | |
336 if (pv.exists && pv.isFolder) return pv; // got a valid path | |
337 fps[i] = pv; | |
338 } | |
339 if (create) { // try to create a folder, using each path in turn until succesful | |
340 foreach (fp; fps) { | |
341 try { | |
342 return fp.create; | |
343 } | |
344 catch (Exception e) {} | |
345 } | |
346 } | |
347 // no valid path... | |
348 char[] msg = "Unable to find"~(create ? " or create" : "")~" a required path! The following were tried:"; | |
349 foreach (path; paths) msg ~= " \"" ~ path ~ '\"'; | |
350 throw new PathException (msg); | |
351 } | |
352 } | |
353 //END Path resolution | |
354 | |
355 /// Thrown when makeMTReader couldn't find a file. | |
356 class NoFileException : MTFileIOException { | |
357 this (char[] msg) { | |
358 super(msg); | |
359 } | |
360 } |