1 /**
2  * Functions for retrieving standard paths in cross-platform manner.
3  * Authors: 
4  *  $(LINK2 https://github.com/FreeSlave, Roman Chistokhodov)
5  * License: 
6  *  $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
7  * Copyright:
8  *  Roman Chistokhodov 2015-2016
9  */
10 
11 module standardpaths;
12 
13 private {
14     import std.process : environment;
15     import std.path;
16     import std.file;
17     import std.exception;
18     import std.range;
19     
20     import isfreedesktop;
21     
22     debug {
23         import std.stdio : stderr;
24     }
25     
26     static if( __VERSION__ < 2066 ) enum nogc = 1;
27     
28     string verifyIfNeeded(string path, bool shouldVerify) nothrow @trusted
29     {
30         if (path.length && shouldVerify) {
31             bool dirExists;
32             collectException(path.isDir, dirExists);
33             return dirExists ? path : null;
34         } else {
35             return path;
36         }
37     }
38 
39     string createIfNeeded(string path, bool shouldCreate) nothrow @trusted
40     {
41         if (path.length && shouldCreate) {
42             bool pathExist;
43             collectException(path.isDir, pathExist);
44             if (pathExist || collectException(mkdirRecurse(path)) is null) {
45                 return path;
46             } else {
47                 return null;
48             }
49         } else {
50             return path;
51         }
52     }
53 }
54 
55 version(Windows) {
56     private {
57         static if (__VERSION__ < 2070) {
58             import std.c.windows.windows;
59         } else {
60             import core.sys.windows.windows;
61         }
62         
63         import std.utf;
64     }
65 } else version(Posix) {
66     private {
67         import std.string : toStringz;
68         
69         static if (is(typeof({import std.string : fromStringz;}))) {
70             import std.string : fromStringz;
71         } else { //own fromStringz implementation for compatibility reasons
72             import std.c.string : strlen;
73             @system pure inout(char)[] fromStringz(inout(char)* cString) {
74                 return cString ? cString[0..strlen(cString)] : null;
75             }
76         }
77 
78         //Concat two strings, but if the first one is empty, then null string is returned.
79         string maybeConcat(string start, string path) nothrow @safe
80         {
81             return start.empty ? null : start ~ path;
82         }
83         
84         string maybeBuild(string start, string path) nothrow @safe
85         {
86             return start.empty ? null : buildPath(start, path);
87         }
88     }
89 } else {
90     static assert(false, "Unsupported platform");
91 }
92 
93 /** 
94  * Location types that can be passed to $(D writablePath) and $(D standardPaths) functions.
95  * 
96  * Not all these paths are suggested for showing in file managers or file dialogs. 
97  * Some of them are meant for internal application usage or should be treated in special way.
98  * On usual circumstances user wants to see Desktop, Documents, Downloads, Pictures, Music and Videos directories.
99  * 
100  * See_Also:
101  *  $(D writablePath), $(D standardPaths)
102  */
103 enum StandardPath {
104     /**
105      * General location of persisted application data. Every application should have its own subdirectory here.
106      * Note: on Windows it's the same as $(D config) path.
107      */
108     data,
109     /**
110      * General location of configuration files. Every application should have its own subdirectory here.
111      * Note: on Windows it's the same as $(D data) path.
112      */
113     config,
114     /**
115      * Location of cached data.
116      * Note: Not available on Windows.
117      */
118     cache,
119     ///User's desktop directory.
120     desktop,
121     ///User's documents.
122     documents,
123     ///User's pictures.
124     pictures,
125     
126     ///User's music.
127     music,
128     
129     ///User's videos (movies).
130     videos,
131     
132     ///Directory for user's downloaded files.
133     downloads,
134     
135     /**
136      * Location of file templates (e.g. office suite document templates).
137      * Note: Not available on OS X.
138      */
139     templates,
140     
141     /** 
142      * Public share folder.
143      * Note: Not available on Windows.
144      */
145     publicShare,
146     /**
147      * Location of fonts files.
148      * Note: don't rely on this on freedesktop, since it uses hardcoded paths there. Better consider using $(LINK2 http://www.freedesktop.org/wiki/Software/fontconfig/, fontconfig library)
149      */
150     fonts,
151     /**
152      * User's applications. This has different meaning across platforms.
153      * On Windows it's directory where links (.lnk) to programs for Start menu are stored.
154      * On OS X it's folder where applications are typically put.
155      * On Freedesktop it's directory where .desktop files are put.
156      */
157     applications,
158     
159     /**
160      * Automatically started applications.
161      * On Windows it's directory where links (.lnk) to autostarted programs are stored.
162      * On OSX it's not available.
163      * On Freedesktop it's directory where autostarted .desktop files are stored.
164      */
165     startup
166 }
167 
168 /**
169  * Control behavior of functions.
170  * See_Also: $(D writablePath)
171  */
172 enum FolderFlag
173 {
174     none = 0,   /// Don't verify that folder exist.
175     /** 
176      * Create if folder does not exist. 
177      * On Windows and OS X directory will be created using platform specific API, so it will have appropriate icon and other settings special for this kind of folder.
178      */
179     create = 1,
180     /**
181      * Verify that folder exists.
182      * On Windows directory is verified using platform specific API.
183      */
184     verify = 2
185 }
186 
187 /**
188  * Current user home directory.
189  * Returns: Path to user home directory, or an empty string if could not determine home directory.
190  * Relies on environment variables.
191  * Note: This function does not cache its result.
192  */
193 string homeDir() nothrow @safe
194 {
195     try {
196         version(Windows) {
197             //Use GetUserProfileDirectoryW from Userenv.dll?
198             string home = environment.get("USERPROFILE");
199             if (home.empty) {
200                 string homeDrive = environment.get("HOMEDRIVE");
201                 string homePath = environment.get("HOMEPATH");
202                 if (homeDrive.length && homePath.length) {
203                     home = homeDrive ~ homePath;
204                 }
205             }
206             return home;
207         } else {
208             string home = environment.get("HOME");
209             return home;
210         }
211     }
212     catch (Exception e) {
213         debug {
214             @trusted void writeException(Exception e) nothrow {
215                 collectException(stderr.writefln("Error when getting home directory %s", e.msg));
216             }
217             writeException(e);
218         }
219         return null;
220     }
221 }
222 
223 /**
224  * Get writable path for specific location.
225  * Returns: Path where files of $(U type) should be written to by current user, or an empty string if could not determine path.
226  * Params:
227  *  type = Location to lookup.
228  *  params = Union of $(D FolderFlag)s.
229  * Note: This function does not cache its results.
230  * Example:
231 --------------------
232 string downloadsDir = writablePath(StandardPath.downloads, FolderFlag.verify);
233 if (downloadsDir.length) {
234     //Open file dialog with this directory.
235 } else {
236     //Could not detect default downloads directory.
237     //Ask user to choose default downloads directory for this application.
238 }
239 --------------------
240  * See_Also: $(D StandardPath), $(D FolderFlag), $(D standardPaths)
241  */
242 string writablePath(StandardPath type, FolderFlag params = FolderFlag.none) nothrow @safe;
243 
244 /**
245  * Get paths for various locations.
246  * Returns: Array of paths where files of $(U type) belong including one returned by $(D writablePath), or an empty array if no paths are defined for $(U type).
247  * This function does not ensure if all returned paths exist and appear to be accessible directories. Returned strings are not required to be unique.
248  * Note: This function does cache its results. 
249  * It may cause performance impact to call this function often since retrieving some paths can be relatively expensive operation.
250  * Example:
251 --------------------
252 string[] templateDirs = standardPaths(StandardPath.templates);
253 //List all available file templates including system defined and user created ones.
254 --------------------
255  * See_Also: $(D StandardPath), $(D writablePath)
256  */
257 string[] standardPaths(StandardPath type) nothrow @safe;
258 
259 /**
260  * Evaluate writable path for specific location and append subfolder.
261  * This can be used with $(D StandardPath.config) and $(D StandardPath.data) to retrieve folder specific for this application instead of generic path.
262  * Returns: Path where files of $(U type) should be written to by current user concatenated with subfolder, 
263  *  or an empty string if could not determine path.
264  * Params:
265  *  type = Location to lookup.
266  *  subfolder = Subfolder that will be appended to base writable path. 
267  *  params = Union of $(D FolderFlag)s. This affects both base path and sub path.
268  * Note: This function does not cache its results.
269  * Example:
270 --------------------
271 enum organizationName = "MyLittleCompany";
272 enum applicationName = "MyLittleApplication";
273 
274 string configDir = writablePath(StandardPath.config, buildPath(organizationName, applicationName), FolderFlag.create);
275 if (configDir.length) {
276     string configFile = buildPath(configDir, "config.conf");
277     //read or write configuration file.
278 } else {
279     throw new Exception("Could not create application config directory");
280 }
281 --------------------
282  */
283 string writablePath(StandardPath type, string subfolder, FolderFlag params = FolderFlag.none) nothrow @safe
284 {
285     string baseWritablePath = writablePath(type, params);
286     if (baseWritablePath.length) {
287         string toReturn = buildPath(baseWritablePath, subfolder);
288         const bool shouldCreate = (params & FolderFlag.create) != 0;
289         const bool shouldVerify = (params & FolderFlag.verify) != 0;
290         return toReturn.createIfNeeded(shouldCreate).verifyIfNeeded(shouldVerify);
291     } else {
292         return null;
293     }
294 }
295 
296 /**
297  * Evaluate paths for various locations and append subfolder.
298  * Example:
299 --------------------
300 enum organizationName = "MyLittleCompany";
301 enum applicationName = "MyLittleApplication";
302 
303 string[] appDataDirs = standardPaths(StandardPath.data, buildPath(organizationName, applicationName));
304 //Gather data files for this application from each found directory.
305 --------------------
306  */
307 string[] standardPaths(StandardPath type, string subfolder) nothrow @safe
308 {
309     auto toReturn = standardPaths(type);
310     foreach(ref s; toReturn) {
311         s = buildPath(s, subfolder);
312     }
313     return toReturn;
314 }
315 
316 version(D_Ddoc)
317 {   
318     /**
319      * Path to $(B Roaming) data directory. Windows only.
320      * Returns: User's Roaming directory. On fail returns an empty string.
321      * See_Also: $(D writablePath), $(D FolderFlag)
322      */
323     string roamingPath(FolderFlag params = FolderFlag.none) nothrow @safe;
324     
325     /**
326      * Location where games may store their saves. Windows only.
327      * Note: This is common path for games. One should use subfolder for their game saves.
328      * Returns: User's Saved Games directory. On fail returns an empty string.
329      * See_Also: $(D writablePath), $(D FolderFlag)
330      */
331     string savedGames(FolderFlag params = FolderFlag.none) nothrow @safe;
332 }
333 
334 version(Windows) {
335     
336     private {
337         enum {
338             CSIDL_DESKTOP            =  0,
339             CSIDL_INTERNET,
340             CSIDL_PROGRAMS,
341             CSIDL_CONTROLS,
342             CSIDL_PRINTERS,
343             CSIDL_PERSONAL,
344             CSIDL_FAVORITES,
345             CSIDL_STARTUP,
346             CSIDL_RECENT,
347             CSIDL_SENDTO,
348             CSIDL_BITBUCKET,
349             CSIDL_STARTMENU,      // = 11
350             CSIDL_MYMUSIC            = 13,
351             CSIDL_MYVIDEO,        // = 14
352             CSIDL_DESKTOPDIRECTORY   = 16,
353             CSIDL_DRIVES,
354             CSIDL_NETWORK,
355             CSIDL_NETHOOD,
356             CSIDL_FONTS,
357             CSIDL_TEMPLATES,
358             CSIDL_COMMON_STARTMENU,
359             CSIDL_COMMON_PROGRAMS,
360             CSIDL_COMMON_STARTUP,
361             CSIDL_COMMON_DESKTOPDIRECTORY,
362             CSIDL_APPDATA,
363             CSIDL_PRINTHOOD,
364             CSIDL_LOCAL_APPDATA,
365             CSIDL_ALTSTARTUP,
366             CSIDL_COMMON_ALTSTARTUP,
367             CSIDL_COMMON_FAVORITES,
368             CSIDL_INTERNET_CACHE,
369             CSIDL_COOKIES,
370             CSIDL_HISTORY,
371             CSIDL_COMMON_APPDATA,
372             CSIDL_WINDOWS,
373             CSIDL_SYSTEM,
374             CSIDL_PROGRAM_FILES,
375             CSIDL_MYPICTURES,
376             CSIDL_PROFILE,
377             CSIDL_SYSTEMX86,
378             CSIDL_PROGRAM_FILESX86,
379             CSIDL_PROGRAM_FILES_COMMON,
380             CSIDL_PROGRAM_FILES_COMMONX86,
381             CSIDL_COMMON_TEMPLATES,
382             CSIDL_COMMON_DOCUMENTS,
383             CSIDL_COMMON_ADMINTOOLS,
384             CSIDL_ADMINTOOLS,
385             CSIDL_CONNECTIONS,  // = 49
386             CSIDL_COMMON_MUSIC     = 53,
387             CSIDL_COMMON_PICTURES,
388             CSIDL_COMMON_VIDEO,
389             CSIDL_RESOURCES,
390             CSIDL_RESOURCES_LOCALIZED,
391             CSIDL_COMMON_OEM_LINKS,
392             CSIDL_CDBURN_AREA,  // = 59
393             CSIDL_COMPUTERSNEARME  = 61,
394             CSIDL_FLAG_DONT_VERIFY = 0x4000,
395             CSIDL_FLAG_CREATE      = 0x8000,
396             CSIDL_FLAG_MASK        = 0xFF00
397         }
398         
399         enum {
400             KF_FLAG_SIMPLE_IDLIST                = 0x00000100,
401             KF_FLAG_NOT_PARENT_RELATIVE          = 0x00000200,
402             KF_FLAG_DEFAULT_PATH                 = 0x00000400,
403             KF_FLAG_INIT                         = 0x00000800,
404             KF_FLAG_NO_ALIAS                     = 0x00001000,
405             KF_FLAG_DONT_UNEXPAND                = 0x00002000,
406             KF_FLAG_DONT_VERIFY                  = 0x00004000,
407             KF_FLAG_CREATE                       = 0x00008000,
408             KF_FLAG_NO_APPCONTAINER_REDIRECTION  = 0x00010000,
409             KF_FLAG_ALIAS_ONLY                   = 0x80000000
410         };
411         
412         alias GUID KNOWNFOLDERID;
413         
414         enum KNOWNFOLDERID FOLDERID_LocalAppData = {0xf1b32785, 0x6fba, 0x4fcf, [0x9d,0x55,0x7b,0x8e,0x7f,0x15,0x70,0x91]};
415         enum KNOWNFOLDERID FOLDERID_RoamingAppData = {0x3eb685db, 0x65f9, 0x4cf6, [0xa0,0x3a,0xe3,0xef,0x65,0x72,0x9f,0x3d]};
416 
417         enum KNOWNFOLDERID FOLDERID_Desktop = {0xb4bfcc3a, 0xdb2c, 0x424c, [0xb0,0x29,0x7f,0xe9,0x9a,0x87,0xc6,0x41]};
418         enum KNOWNFOLDERID FOLDERID_Documents = {0xfdd39ad0, 0x238f, 0x46af, [0xad,0xb4,0x6c,0x85,0x48,0x3,0x69,0xc7]};
419         enum KNOWNFOLDERID FOLDERID_Downloads = {0x374de290, 0x123f, 0x4565, [0x91,0x64,0x39,0xc4,0x92,0x5e,0x46,0x7b]};
420         enum KNOWNFOLDERID FOLDERID_Favorites = {0x1777f761, 0x68ad, 0x4d8a, [0x87,0xbd,0x30,0xb7,0x59,0xfa,0x33,0xdd]};
421         enum KNOWNFOLDERID FOLDERID_Links = {0xbfb9d5e0, 0xc6a9, 0x404c, [0xb2,0xb2,0xae,0x6d,0xb6,0xaf,0x49,0x68]};
422         enum KNOWNFOLDERID FOLDERID_Music = {0x4bd8d571, 0x6d19, 0x48d3, [0xbe,0x97,0x42,0x22,0x20,0x8,0xe,0x43]};
423         enum KNOWNFOLDERID FOLDERID_Pictures = {0x33e28130, 0x4e1e, 0x4676, [0x83,0x5a,0x98,0x39,0x5c,0x3b,0xc3,0xbb]};
424         enum KNOWNFOLDERID FOLDERID_Programs = {0xa77f5d77, 0x2e2b, 0x44c3, [0xa6,0xa2,0xab,0xa6,0x1,0x5,0x4a,0x51]};
425         enum KNOWNFOLDERID FOLDERID_SavedGames = {0x4c5c32ff, 0xbb9d, 0x43b0, [0xb5,0xb4,0x2d,0x72,0xe5,0x4e,0xaa,0xa4]};
426         enum KNOWNFOLDERID FOLDERID_Startup = {0xb97d20bb, 0xf46a, 0x4c97, [0xba,0x10,0x5e,0x36,0x8,0x43,0x8,0x54]};
427         enum KNOWNFOLDERID FOLDERID_Templates = {0xa63293e8, 0x664e, 0x48db, [0xa0,0x79,0xdf,0x75,0x9e,0x5,0x9,0xf7]};
428         enum KNOWNFOLDERID FOLDERID_Videos = {0x18989b1d, 0x99b5, 0x455b, [0x84,0x1c,0xab,0x7c,0x74,0xe4,0xdd,0xfc]};
429 
430         enum KNOWNFOLDERID FOLDERID_Fonts = {0xfd228cb7, 0xae11, 0x4ae3, [0x86,0x4c,0x16,0xf3,0x91,0xa,0xb8,0xfe]};
431         enum KNOWNFOLDERID FOLDERID_ProgramData = {0x62ab5d82, 0xfdc1, 0x4dc3, [0xa9,0xdd,0x7,0xd,0x1d,0x49,0x5d,0x97]};
432         enum KNOWNFOLDERID FOLDERID_CommonPrograms = {0x139d44e, 0x6afe, 0x49f2, [0x86,0x90,0x3d,0xaf,0xca,0xe6,0xff,0xb8]};
433         enum KNOWNFOLDERID FOLDERID_CommonStartup = {0x82a5ea35, 0xd9cd, 0x47c5, [0x96,0x29,0xe1,0x5d,0x2f,0x71,0x4e,0x6e]};
434         enum KNOWNFOLDERID FOLDERID_CommonTemplates = {0xb94237e7, 0x57ac, 0x4347, [0x91,0x51,0xb0,0x8c,0x6c,0x32,0xd1,0xf7]};
435 
436         enum KNOWNFOLDERID FOLDERID_PublicDesktop = {0xc4aa340d, 0xf20f, 0x4863, [0xaf,0xef,0xf8,0x7e,0xf2,0xe6,0xba,0x25]};
437         enum KNOWNFOLDERID FOLDERID_PublicDocuments = {0xed4824af, 0xdce4, 0x45a8, [0x81,0xe2,0xfc,0x79,0x65,0x8,0x36,0x34]};
438         enum KNOWNFOLDERID FOLDERID_PublicDownloads = {0x3d644c9b, 0x1fb8, 0x4f30, [0x9b,0x45,0xf6,0x70,0x23,0x5f,0x79,0xc0]};
439         enum KNOWNFOLDERID FOLDERID_PublicMusic = {0x3214fab5, 0x9757, 0x4298, [0xbb,0x61,0x92,0xa9,0xde,0xaa,0x44,0xff]};
440         enum KNOWNFOLDERID FOLDERID_PublicPictures = {0xb6ebfb86, 0x6907, 0x413c, [0x9a,0xf7,0x4f,0xc2,0xab,0xf0,0x7c,0xc5]};
441         enum KNOWNFOLDERID FOLDERID_PublicVideos = {0x2400183a, 0x6185, 0x49fb, [0xa2,0xd8,0x4a,0x39,0x2a,0x60,0x2b,0xa3]};
442     }
443     
444     private  {
445         extern(Windows) @nogc @system BOOL _dummy_SHGetSpecialFolderPath(HWND, wchar*, int, BOOL) nothrow { return 0; }
446         extern(Windows) @nogc @system HRESULT _dummy_SHGetKnownFolderPath(const(KNOWNFOLDERID)* rfid, DWORD dwFlags, HANDLE hToken, wchar** ppszPath) nothrow { return 0; }
447         extern(Windows) @nogc @system void _dummy_CoTaskMemFree(void* pv) nothrow {return;}
448         
449         __gshared typeof(&_dummy_SHGetSpecialFolderPath) ptrSHGetSpecialFolderPath = null;
450         __gshared typeof(&_dummy_SHGetKnownFolderPath) ptrSHGetKnownFolderPath = null;
451         __gshared typeof(&_dummy_CoTaskMemFree) ptrCoTaskMemFree = null;
452         
453         @nogc @trusted bool hasSHGetSpecialFolderPath() nothrow {
454             return ptrSHGetSpecialFolderPath !is null;
455         }
456         
457         @nogc @trusted bool hasSHGetKnownFolderPath() nothrow {
458             return ptrSHGetKnownFolderPath !is null && ptrCoTaskMemFree !is null;
459         }
460     }
461     
462     shared static this() 
463     {
464         HMODULE shellLib = LoadLibraryA("Shell32");
465         if (shellLib !is null) {
466             ptrSHGetKnownFolderPath = cast(typeof(ptrSHGetKnownFolderPath))enforce(GetProcAddress(shellLib, "SHGetKnownFolderPath"));
467             if (ptrSHGetKnownFolderPath) {
468                 HMODULE ole = LoadLibraryA("Ole32");
469                 if (ole !is null) {
470                     ptrCoTaskMemFree = cast(typeof(ptrCoTaskMemFree))enforce(GetProcAddress(ole, "CoTaskMemFree"));
471                     if (!ptrCoTaskMemFree) {
472                         FreeLibrary(ole);
473                     }
474                 }
475             }
476             
477             if (!hasSHGetKnownFolderPath()) {
478                 ptrSHGetSpecialFolderPath = cast(typeof(ptrSHGetSpecialFolderPath))GetProcAddress(shellLib, "SHGetSpecialFolderPathW");
479             }
480         }
481     }
482     
483     private string getCSIDLFolder(int csidl, FolderFlag params = FolderFlag.none) nothrow @trusted {
484         import core.stdc.wchar_ : wcslen;
485         
486         if (params & FolderFlag.create) {
487             csidl |= CSIDL_FLAG_CREATE;
488         }
489         if (!(params & FolderFlag.verify)) {
490             csidl |= CSIDL_FLAG_DONT_VERIFY;
491         }
492         wchar[MAX_PATH] path = void;
493         if (hasSHGetSpecialFolderPath() && ptrSHGetSpecialFolderPath(null, path.ptr, csidl, FALSE)) {
494             size_t len = wcslen(path.ptr);
495             try {
496                 return toUTF8(path[0..len]);
497             } catch(Exception e) {
498                 
499             }
500         }
501         return null;
502     }
503     
504     private string getKnownFolder(const(KNOWNFOLDERID) folder, FolderFlag params = FolderFlag.none) nothrow @trusted {
505         import core.stdc.wchar_ : wcslen;
506         
507         wchar* str;
508         
509         DWORD flags = 0;
510         if (params & FolderFlag.create) {
511             flags |= KF_FLAG_CREATE;
512         }
513         if (!(params & FolderFlag.verify)) {
514             flags |= KF_FLAG_DONT_VERIFY;
515         }
516         
517         if (hasSHGetKnownFolderPath() && ptrSHGetKnownFolderPath(&folder, flags, null, &str) == S_OK) {
518             scope(exit) ptrCoTaskMemFree(str);
519             try {
520                 return str[0..wcslen(str)].toUTF8;
521             } catch(Exception e) {
522                 
523             }
524         }
525         return null;
526     }
527     
528     string roamingPath(FolderFlag params = FolderFlag.none) nothrow @safe
529     {
530         if (hasSHGetKnownFolderPath()) {
531             return getKnownFolder(FOLDERID_RoamingAppData, params);
532         } else if (hasSHGetSpecialFolderPath()) {
533             return getCSIDLFolder(CSIDL_APPDATA, params);
534         } else {
535             return null;
536         }
537     }
538     
539     string savedGames(FolderFlag params = FolderFlag.none) nothrow @safe
540     {
541         if (hasSHGetKnownFolderPath()) {
542             return getKnownFolder(FOLDERID_SavedGames, params);
543         } else {
544             return null;
545         }
546     }
547     
548     string writablePath(StandardPath type, FolderFlag params = FolderFlag.none) nothrow @safe
549     {
550         if (hasSHGetKnownFolderPath()) {
551             final switch(type) {
552                 case StandardPath.config:
553                 case StandardPath.data:
554                     return getKnownFolder(FOLDERID_LocalAppData, params);
555                 case StandardPath.cache:
556                     return null;
557                 case StandardPath.desktop:
558                     return getKnownFolder(FOLDERID_Desktop, params);
559                 case StandardPath.documents:
560                     return getKnownFolder(FOLDERID_Documents, params);
561                 case StandardPath.pictures:
562                     return getKnownFolder(FOLDERID_Pictures, params);
563                 case StandardPath.music:
564                     return getKnownFolder(FOLDERID_Music, params);
565                 case StandardPath.videos:
566                     return getKnownFolder(FOLDERID_Videos, params);
567                 case StandardPath.downloads:
568                     return getKnownFolder(FOLDERID_Downloads, params);
569                 case StandardPath.templates:
570                     return getKnownFolder(FOLDERID_Templates, params);
571                 case StandardPath.publicShare:
572                     return null;
573                 case StandardPath.fonts:
574                     return null;
575                 case StandardPath.applications:
576                     return getKnownFolder(FOLDERID_Programs, params);
577                 case StandardPath.startup:
578                     return getKnownFolder(FOLDERID_Startup, params);
579             }
580         } else if (hasSHGetSpecialFolderPath()) {
581             final switch(type) {
582                 case StandardPath.config:
583                 case StandardPath.data:
584                     return getCSIDLFolder(CSIDL_LOCAL_APPDATA, params);
585                 case StandardPath.cache:
586                     return null;
587                 case StandardPath.desktop:
588                     return getCSIDLFolder(CSIDL_DESKTOPDIRECTORY, params);
589                 case StandardPath.documents:
590                     return getCSIDLFolder(CSIDL_PERSONAL, params);
591                 case StandardPath.pictures:
592                     return getCSIDLFolder(CSIDL_MYPICTURES, params);
593                 case StandardPath.music:
594                     return getCSIDLFolder(CSIDL_MYMUSIC, params);
595                 case StandardPath.videos:
596                     return getCSIDLFolder(CSIDL_MYVIDEO, params);
597                 case StandardPath.downloads:
598                     return null;
599                 case StandardPath.templates:
600                     return getCSIDLFolder(CSIDL_TEMPLATES, params);
601                 case StandardPath.publicShare:
602                     return null;
603                 case StandardPath.fonts:
604                     return null;
605                 case StandardPath.applications:
606                     return getCSIDLFolder(CSIDL_PROGRAMS, params);
607                 case StandardPath.startup:
608                     return getCSIDLFolder(CSIDL_STARTUP, params);
609             }
610         } else {
611             return null;
612         }
613     }
614     
615     string[] standardPaths(StandardPath type) nothrow @safe
616     {   
617         string commonPath;
618         
619         if (hasSHGetKnownFolderPath()) {
620             switch(type) {
621                 case StandardPath.config:
622                 case StandardPath.data:
623                     commonPath = getKnownFolder(FOLDERID_ProgramData);
624                     break;
625                 case StandardPath.desktop:
626                     commonPath = getKnownFolder(FOLDERID_PublicDesktop);
627                     break;
628                 case StandardPath.documents:
629                     commonPath = getKnownFolder(FOLDERID_PublicDocuments);
630                     break;
631                 case StandardPath.pictures:
632                     commonPath = getKnownFolder(FOLDERID_PublicPictures);
633                     break;
634                 case StandardPath.music:
635                     commonPath = getKnownFolder(FOLDERID_PublicMusic);
636                     break;
637                 case StandardPath.videos:
638                     commonPath = getKnownFolder(FOLDERID_PublicVideos);
639                     break;
640                 case StandardPath.downloads:
641                     commonPath = getKnownFolder(FOLDERID_PublicDownloads);
642                     break;
643                 case StandardPath.templates:
644                     commonPath = getKnownFolder(FOLDERID_CommonTemplates);
645                     break;
646                 case StandardPath.fonts:
647                     commonPath = getKnownFolder(FOLDERID_Fonts);
648                     break;
649                 case StandardPath.applications:
650                     commonPath = getKnownFolder(FOLDERID_CommonPrograms);
651                     break;
652                 case StandardPath.startup:
653                     commonPath = getKnownFolder(FOLDERID_CommonStartup);
654                     break;
655                 default:
656                     break;
657             }
658         } else if (hasSHGetSpecialFolderPath()) {
659             switch(type) {
660                 case StandardPath.config:
661                 case StandardPath.data:
662                     commonPath = getCSIDLFolder(CSIDL_COMMON_APPDATA);
663                     break;
664                 case StandardPath.desktop:
665                     commonPath = getCSIDLFolder(CSIDL_COMMON_DESKTOPDIRECTORY);
666                     break;
667                 case StandardPath.documents:
668                     commonPath = getCSIDLFolder(CSIDL_COMMON_DOCUMENTS);
669                     break;
670                 case StandardPath.pictures:
671                     commonPath = getCSIDLFolder(CSIDL_COMMON_PICTURES);
672                     break;
673                 case StandardPath.music:
674                     commonPath = getCSIDLFolder(CSIDL_COMMON_MUSIC);
675                     break;
676                 case StandardPath.videos:
677                     commonPath = getCSIDLFolder(CSIDL_COMMON_VIDEO);
678                     break;
679                 case StandardPath.templates:
680                     commonPath = getCSIDLFolder(CSIDL_COMMON_TEMPLATES);
681                     break;
682                 case StandardPath.fonts:
683                     commonPath = getCSIDLFolder(CSIDL_FONTS);
684                     break;
685                 case StandardPath.applications:
686                     commonPath = getCSIDLFolder(CSIDL_COMMON_PROGRAMS);
687                     break;
688                 case StandardPath.startup:
689                     commonPath = getCSIDLFolder(CSIDL_COMMON_STARTUP);
690                     break;
691                 default:
692                     break;
693             }
694         }
695         
696         string[] paths;
697         string userPath = writablePath(type);
698         if (userPath.length) 
699             paths ~= userPath;
700         if (commonPath.length)
701             paths ~= commonPath;
702         return paths;
703     }
704 } else version(OSX) {
705     private {
706         version(StandardPathsCocoa) {
707             alias size_t NSUInteger;
708             
709             
710             enum objectiveC_declarations = q{
711                 extern (Objective-C)
712                 interface NSString
713                 {
714                     NSString initWithUTF8String(in char* str) @selector("initWithUTF8String:");
715                     const(char)* UTF8String() @selector("UTF8String");
716                     void release() @selector("release");
717                 }
718 
719                 extern(Objective-C)
720                 interface NSArray
721                 {
722                     NSString objectAtIndex(size_t) @selector("objectAtIndex:");
723                     NSString firstObject() @selector("firstObject");
724                     NSUInteger count() @selector("count");
725                     void release() @selector("release");
726                 }
727 
728                 extern(Objective-C)
729                 interface NSURL
730                 {
731                     NSString absoluteString() @selector("absoluteString");
732                     void release() @selector("release");
733                 }
734 
735                 extern(Objective-C)
736                 interface NSError
737                 {
738 
739                 }
740 
741                 extern (C) NSFileManager objc_lookUpClass(in char* name);
742 
743                 extern(Objective-C)
744                 interface NSFileManager
745                 {
746                     NSFileManager defaultManager() @selector("defaultManager");
747                     NSURL URLForDirectory(NSSearchPathDirectory, NSSearchPathDomainMask domain, NSURL url, int shouldCreate, NSError* error) @selector("URLForDirectory:inDomain:appropriateForURL:create:error:");
748                 }
749             };
750             
751             mixin(objectiveC_declarations);
752 
753             enum : NSUInteger {
754                NSApplicationDirectory = 1,
755                NSDemoApplicationDirectory,
756                NSDeveloperApplicationDirectory,
757                NSAdminApplicationDirectory,
758                NSLibraryDirectory,
759                NSDeveloperDirectory,
760                NSUserDirectory,
761                NSDocumentationDirectory,
762                NSDocumentDirectory,
763                NSCoreServiceDirectory,
764                NSAutosavedInformationDirectory = 11,
765                NSDesktopDirectory = 12,
766                NSCachesDirectory = 13,
767                NSApplicationSupportDirectory = 14,
768                NSDownloadsDirectory = 15,
769                NSInputMethodsDirectory = 16,
770                NSMoviesDirectory = 17,
771                NSMusicDirectory = 18,
772                NSPicturesDirectory = 19,
773                NSPrinterDescriptionDirectory = 20,
774                NSSharedPublicDirectory = 21,
775                NSPreferencePanesDirectory = 22,
776                NSItemReplacementDirectory = 99,
777                NSAllApplicationsDirectory = 100,
778                NSAllLibrariesDirectory = 101,
779             };
780 
781             alias NSUInteger NSSearchPathDirectory;
782 
783             enum : NSUInteger {
784                NSUserDomainMask = 1,
785                NSLocalDomainMask = 2,
786                NSNetworkDomainMask = 4,
787                NSSystemDomainMask = 8,
788                NSAllDomainsMask = 0x0ffff,
789             };
790 
791             alias NSUInteger NSSearchPathDomainMask;
792 
793             string domainDir(NSSearchPathDirectory dir, NSSearchPathDomainMask domain, bool shouldCreate = false) nothrow @trusted
794             {
795                 import std.uri;
796                 import std.algorithm : startsWith;
797 
798                 try {
799                     auto managerInterface = objc_lookUpClass("NSFileManager");
800                     if (!managerInterface) {
801                         return null;
802                     }
803 
804                     auto manager = managerInterface.defaultManager();
805                     if (!manager) {
806                         return null;
807                     }
808 
809                     NSURL url = manager.URLForDirectory(dir, domain, null, shouldCreate, null);
810                     if (!url) {
811                         return null;
812                     }
813                     scope(exit) url.release();  
814                     NSString nsstr = url.absoluteString();
815                     scope(exit) nsstr.release();
816 
817                     string str = fromStringz(nsstr.UTF8String()).idup;
818 
819                     enum fileProtocol = "file://";
820                     if (str.startsWith(fileProtocol)) {
821 						str = str.decode()[fileProtocol.length..$];
822 						if (str.length > 1 && str[$-1] == '/') {
823 							return str[0..$-1];
824 						} else {
825 							return str;
826 						}
827                     }
828                 } catch(Exception e) {
829 
830                 }
831                 return null;
832             }
833         } else {
834             private enum : short {
835                 kOnSystemDisk                 = -32768L, /* previously was 0x8000 but that is an unsigned value whereas vRefNum is signed*/
836                 kOnAppropriateDisk            = -32767, /* Generally, the same as kOnSystemDisk, but it's clearer that this isn't always the 'boot' disk.*/
837                                                         /* Folder Domains - Carbon only.  The constants above can continue to be used, but the folder/volume returned will*/
838                                                         /* be from one of the domains below.*/
839                 kSystemDomain                 = -32766, /* Read-only system hierarchy.*/
840                 kLocalDomain                  = -32765, /* All users of a single machine have access to these resources.*/
841                 kNetworkDomain                = -32764, /* All users configured to use a common network server has access to these resources.*/
842                 kUserDomain                   = -32763, /* Read/write. Resources that are private to the user.*/
843                 kClassicDomain                = -32762, /* Domain referring to the currently configured Classic System Folder.  Not supported in Mac OS X Leopard and later.*/
844                 kFolderManagerLastDomain      = -32760
845             }
846 
847             private @nogc int k(string s) nothrow {
848                 return s[0] << 24 | s[1] << 16 | s[2] << 8 | s[3];
849             }
850 
851             private enum {
852                 kDesktopFolderType            = k("desk"), /* the desktop folder; objects in this folder show on the desktop. */
853                 kTrashFolderType              = k("trsh"), /* the trash folder; objects in this folder show up in the trash */
854                 kWhereToEmptyTrashFolderType  = k("empt"), /* the "empty trash" folder; Finder starts empty from here down */
855                 kFontsFolderType              = k("font"), /* Fonts go here */
856                 kPreferencesFolderType        = k("pref"), /* preferences for applications go here */
857                 kSystemPreferencesFolderType  = k("sprf"), /* the PreferencePanes folder, where Mac OS X Preference Panes go */
858                 kTemporaryFolderType          = k("temp"), /*    On Mac OS X, each user has their own temporary items folder, and the Folder Manager attempts to set permissions of these*/
859                                                         /*    folders such that other users can not access the data inside.  On Mac OS X 10.4 and later the data inside the temporary*/
860                                                         /*    items folder is deleted at logout and at boot, but not otherwise.  Earlier version of Mac OS X would delete items inside*/
861                                                         /*    the temporary items folder after a period of inaccess.  You can ask for a temporary item in a specific domain or on a */
862                                                         /*    particular volume by FSVolumeRefNum.  If you want a location for temporary items for a short time, then use either*/
863                                                         /*    ( kUserDomain, kkTemporaryFolderType ) or ( kSystemDomain, kTemporaryFolderType ).  The kUserDomain varient will always be*/
864                                                         /*    on the same volume as the user's home folder, while the kSystemDomain version will be on the same volume as /var/tmp/ ( and*/
865                                                         /*    will probably be on the local hard drive in case the user's home is a network volume ).  If you want a location for a temporary*/
866                                                         /*    file or folder to use for saving a document, especially if you want to use FSpExchangeFile() to implement a safe-save, then*/
867                                                         /*    ask for the temporary items folder on the same volume as the file you are safe saving.*/
868                                                         /*    However, be prepared for a failure to find a temporary folder in any domain or on any volume.  Some volumes may not have*/
869                                                         /*    a location for a temporary folder, or the permissions of the volume may be such that the Folder Manager can not return*/
870                                                         /*    a temporary folder for the volume.*/
871                                                         /*    If your application creates an item in a temporary items older you should delete that item as soon as it is not needed,*/
872                                                         /*    and certainly before your application exits, since otherwise the item is consuming disk space until the user logs out or*/
873                                                         /*    restarts.  Any items left inside a temporary items folder should be moved into a folder inside the Trash folder on the disk*/
874                                                         /*    when the user logs in, inside a folder named "Recovered items", in case there is anything useful to the end user.*/
875                 kChewableItemsFolderType      = k("flnt"), /* similar to kTemporaryItemsFolderType, except items in this folder are deleted at boot or when the disk is unmounted */
876                 kTemporaryItemsInCacheDataFolderType = k("vtmp"), /* A folder inside the kCachedDataFolderType for the given domain which can be used for transient data*/
877                 kApplicationsFolderType       = k("apps"), /*    Applications on Mac OS X are typically put in this folder ( or a subfolder ).*/
878                 kVolumeRootFolderType         = k("root"), /* root folder of a volume or domain */
879                 kDomainTopLevelFolderType     = k("dtop"), /* The top-level of a Folder domain, e.g. "/System"*/
880                 kDomainLibraryFolderType      = k("dlib"), /* the Library subfolder of a particular domain*/
881                 kUsersFolderType              = k("usrs"), /* "Users" folder, usually contains one folder for each user. */
882                 kCurrentUserFolderType        = k("cusr"), /* The folder for the currently logged on user; domain passed in is ignored. */
883                 kSharedUserDataFolderType     = k("sdat"), /* A Shared folder, readable & writeable by all users */
884                 kCachedDataFolderType         = k("cach"), /* Contains various cache files for different clients*/
885                 kDownloadsFolderType          = k("down"), /* Refers to the ~/Downloads folder*/
886                 kApplicationSupportFolderType = k("asup"), /* third-party items and folders */
887 
888 
889                 kDocumentsFolderType          = k("docs"), /*    User documents are typically put in this folder ( or a subfolder ).*/
890                 kPictureDocumentsFolderType   = k("pdoc"), /* Refers to the "Pictures" folder in a users home directory*/
891                 kMovieDocumentsFolderType     = k("mdoc"), /* Refers to the "Movies" folder in a users home directory*/
892                 kMusicDocumentsFolderType     = 0xB5646F63/*'µdoc'*/, /* Refers to the "Music" folder in a users home directory*/
893                 kInternetSitesFolderType      = k("site"), /* Refers to the "Sites" folder in a users home directory*/
894                 kPublicFolderType             = k("pubb"), /* Refers to the "Public" folder in a users home directory*/
895 
896                 kDropBoxFolderType            = k("drop") /* Refers to the "Drop Box" folder inside the user's home directory*/
897             };
898             
899             struct FSRef {
900               char[80] hidden;    /* private to File Manager*/
901             };
902 
903             alias ubyte Boolean;
904             alias int OSType;
905             alias short OSErr;
906             alias int OSStatus;
907             
908             extern(C) @nogc @system OSErr _dummy_FSFindFolder(short, OSType, Boolean, FSRef*) nothrow { return 0; }
909             extern(C) @nogc @system OSStatus _dummy_FSRefMakePath(const(FSRef)*, char*, uint) nothrow { return 0; }
910 
911             __gshared typeof(&_dummy_FSFindFolder) ptrFSFindFolder = null;
912             __gshared typeof(&_dummy_FSRefMakePath) ptrFSRefMakePath = null;
913         }
914     }
915 
916     version(StandardPathsCocoa) {
917 
918     } else {
919         shared static this()
920         {
921             enum carbonPath = "CoreServices.framework/Versions/A/CoreServices";
922 
923             import core.sys.posix.dlfcn;
924 
925             void* handle = dlopen(toStringz(carbonPath), RTLD_NOW | RTLD_LOCAL);
926             if (handle) {
927                 ptrFSFindFolder = cast(typeof(ptrFSFindFolder))dlsym(handle, "FSFindFolder");
928                 ptrFSRefMakePath = cast(typeof(ptrFSRefMakePath))dlsym(handle, "FSRefMakePath");
929             }
930             if (ptrFSFindFolder == null || ptrFSRefMakePath == null) {
931                 debug collectException(stderr.writeln("Could not load carbon functions"));
932                 if (handle) dlclose(handle);
933             }
934         }
935 
936         private @nogc @trusted bool isCarbonLoaded() nothrow
937         {
938             return ptrFSFindFolder != null && ptrFSRefMakePath != null;
939         }
940 
941         private enum OSErr noErr = 0;
942 
943         private string fsPath(short domain, OSType type, bool shouldCreate = false) nothrow @trusted
944         {
945             import std.stdio;   
946             FSRef fsref;
947             if (isCarbonLoaded() && ptrFSFindFolder(domain, type, shouldCreate, &fsref) == noErr) {
948 
949                 char[2048] buf;
950                 char* path = buf.ptr;
951                 if (ptrFSRefMakePath(&fsref, path, buf.sizeof) == noErr) {
952                     try {
953                         return fromStringz(path).idup;
954                     }
955                     catch(Exception e) {
956 
957                     }
958                 }
959             }
960             return null;
961         }
962     }
963     
964     private string writablePathImpl(StandardPath type, bool shouldCreate = false) nothrow @safe
965     {
966         version(StandardPathsCocoa) {
967             final switch(type) {
968                 case StandardPath.config:
969                     return domainDir(NSLibraryDirectory, NSUserDomainMask, shouldCreate).maybeBuild("Preferences").createIfNeeded(shouldCreate);
970                 case StandardPath.cache:
971                     return domainDir(NSCachesDirectory, NSUserDomainMask, shouldCreate);
972                 case StandardPath.data:
973                     return domainDir(NSApplicationSupportDirectory, NSUserDomainMask, shouldCreate);
974                 case StandardPath.desktop:
975                     return domainDir(NSDesktopDirectory, NSUserDomainMask, shouldCreate);
976                 case StandardPath.documents:
977                     return domainDir(NSDocumentDirectory, NSUserDomainMask, shouldCreate);
978                 case StandardPath.pictures:
979                     return domainDir(NSPicturesDirectory, NSUserDomainMask, shouldCreate);
980                 case StandardPath.music:
981                     return domainDir(NSMusicDirectory, NSUserDomainMask, shouldCreate);
982                 case StandardPath.videos:
983                     return domainDir(NSMoviesDirectory, NSUserDomainMask, shouldCreate);
984                 case StandardPath.downloads:
985                     return domainDir(NSDownloadsDirectory, NSUserDomainMask, shouldCreate);
986                 case StandardPath.templates:
987                     return null;
988                 case StandardPath.publicShare:
989                     return domainDir(NSSharedPublicDirectory, NSUserDomainMask, shouldCreate);
990                 case StandardPath.fonts:
991                     return domainDir(NSLibraryDirectory, NSUserDomainMask, shouldCreate).maybeBuild("Fonts").createIfNeeded(shouldCreate);
992                 case StandardPath.applications:
993                     return domainDir(NSApplicationDirectory, NSUserDomainMask, shouldCreate);
994                 case StandardPath.startup:
995                     return null;
996             }
997         } else {
998             final switch(type) {
999                 case StandardPath.config:
1000                     return fsPath(kUserDomain, kPreferencesFolderType, shouldCreate);
1001                 case StandardPath.cache:
1002                     return fsPath(kUserDomain, kCachedDataFolderType, shouldCreate);
1003                 case StandardPath.data:
1004                     return fsPath(kUserDomain, kApplicationSupportFolderType, shouldCreate);
1005                 case StandardPath.desktop:
1006                     return fsPath(kUserDomain, kDesktopFolderType, shouldCreate);
1007                 case StandardPath.documents:
1008                     return fsPath(kUserDomain, kDocumentsFolderType, shouldCreate);
1009                 case StandardPath.pictures:
1010                     return fsPath(kUserDomain, kPictureDocumentsFolderType, shouldCreate);
1011                 case StandardPath.music:
1012                     return fsPath(kUserDomain, kMusicDocumentsFolderType, shouldCreate);
1013                 case StandardPath.videos:
1014                     return fsPath(kUserDomain, kMovieDocumentsFolderType, shouldCreate);
1015                 case StandardPath.downloads:
1016                     return fsPath(kUserDomain, kDownloadsFolderType, shouldCreate);
1017                 case StandardPath.templates:
1018                     return null;
1019                 case StandardPath.publicShare:
1020                     return fsPath(kUserDomain, kPublicFolderType, shouldCreate);
1021                 case StandardPath.fonts:
1022                     return fsPath(kUserDomain, kFontsFolderType, shouldCreate);
1023                 case StandardPath.applications:
1024                     return fsPath(kUserDomain, kApplicationsFolderType, shouldCreate);
1025                 case StandardPath.startup:
1026                     return null;
1027             }
1028         }
1029     }
1030     
1031     string writablePath(StandardPath type, FolderFlag params = FolderFlag.none) nothrow @safe
1032     {
1033         const bool shouldCreate = (params & FolderFlag.create) != 0;
1034         const bool shouldVerify = (params & FolderFlag.verify) != 0;
1035         return writablePathImpl(type, shouldCreate).verifyIfNeeded(shouldVerify);
1036     }
1037     
1038     string[] standardPaths(StandardPath type) nothrow @safe
1039     {
1040         string commonPath;
1041         
1042         version(StandardPathsCocoa) {
1043             switch(type) {
1044                 case StandardPath.fonts:
1045                     commonPath = domainDir(NSLibraryDirectory, NSSystemDomainMask).maybeBuild("Fonts");
1046                     break;
1047                 case StandardPath.applications:
1048                     commonPath = domainDir(NSApplicationDirectory, NSSystemDomainMask);
1049                     break;
1050                 case StandardPath.data:
1051                     commonPath = domainDir(NSApplicationSupportDirectory, NSSystemDomainMask);
1052                     break;
1053                 case StandardPath.cache:
1054                     commonPath = domainDir(NSCachesDirectory, NSSystemDomainMask);
1055                     break;
1056                 default:
1057                     break;
1058             }
1059         } else {
1060             switch(type) {
1061                 case StandardPath.fonts:
1062                     commonPath = fsPath(kOnAppropriateDisk, kFontsFolderType);
1063                     break;
1064                 case StandardPath.applications:
1065                     commonPath = fsPath(kOnAppropriateDisk, kApplicationsFolderType);
1066                     break;
1067                 case StandardPath.data:
1068                     commonPath = fsPath(kOnAppropriateDisk, kApplicationSupportFolderType);
1069                     break;
1070                 case StandardPath.cache:
1071                     commonPath = fsPath(kOnAppropriateDisk, kCachedDataFolderType);
1072                     break;
1073                 default:
1074                     break;
1075             }
1076         }
1077         
1078         string[] paths;
1079         string userPath = writablePath(type);
1080         if (userPath.length)
1081             paths ~= userPath;
1082         if (commonPath.length)
1083             paths ~= commonPath;
1084         return paths;
1085     }
1086     
1087 } else {
1088     
1089     static if (!isFreedesktop) {
1090         static assert(false, "Unsupported platform");
1091     } else {
1092         public import xdgpaths;
1093         
1094         private {
1095             import std.stdio : File;
1096             import std.algorithm : startsWith;
1097             import std.string;
1098             import std.traits;
1099         }
1100         
1101         unittest
1102         {
1103             assert(maybeConcat(null, "path") == string.init);
1104             assert(maybeConcat("path", "/file") == "path/file");
1105         }
1106         
1107         private @trusted string getFromUserDirs(Range)(string xdgdir, string home, Range range) if (isInputRange!Range && isSomeString!(ElementType!Range))
1108         {
1109             foreach(line; range) {
1110                 line = strip(line);
1111                 auto index = xdgdir.length;
1112                 if (line.startsWith(xdgdir) && line.length > index && line[index] == '=') {
1113                     line = line[index+1..$];
1114                     if (line.length > 2 && line[0] == '"' && line[$-1] == '"') 
1115                     {
1116                         line = line[1..$-1];
1117                     
1118                         if (line.startsWith("$HOME")) {
1119                             return maybeConcat(home, assumeUnique(line[5..$]));
1120                         }
1121                         if (line.length == 0 || line[0] != '/') {
1122                             continue;
1123                         }
1124                         return assumeUnique(line);
1125                     }
1126                 }
1127             }
1128             return null;
1129         }
1130         
1131         
1132         unittest
1133         {
1134             string content = 
1135 `# Comment
1136 
1137 XDG_DOCUMENTS_DIR="$HOME/My Documents" 
1138 XDG_MUSIC_DIR="/data/Music"
1139 XDG_VIDEOS_DIR="data/Video"
1140 `;
1141             string home = "/home/user";
1142             
1143             assert(getFromUserDirs("XDG_DOCUMENTS_DIR", home, content.splitLines) == "/home/user/My Documents");
1144             assert(getFromUserDirs("XDG_MUSIC_DIR", home, content.splitLines) == "/data/Music");
1145             assert(getFromUserDirs("XDG_DOWNLOAD_DIR", home, content.splitLines).empty);
1146             assert(getFromUserDirs("XDG_VIDEOS_DIR", home, content.splitLines).empty);
1147         }
1148         
1149         private @trusted string getFromDefaultDirs(Range)(string key, string home, Range range) if (isInputRange!Range && isSomeString!(ElementType!Range))
1150         {
1151             foreach(line; range) {
1152                 line = strip(line);
1153                 auto index = key.length;
1154                 if (line.startsWith(key) && line.length > index && line[index] == '=') 
1155                 {
1156                     line = line[index+1..$];
1157                     return home ~ "/" ~ assumeUnique(line);
1158                 }
1159             }
1160             return null;
1161         }
1162         
1163         unittest
1164         {
1165             string content = 
1166 `# Comment
1167 
1168 DOCUMENTS=MyDocuments
1169 PICTURES=Images
1170 `;
1171             string home = "/home/user";
1172             assert(getFromDefaultDirs("DOCUMENTS", home, content.splitLines) == "/home/user/MyDocuments");
1173             assert(getFromDefaultDirs("PICTURES", home, content.splitLines) == "/home/user/Images");
1174             assert(getFromDefaultDirs("VIDEOS", home, content.splitLines).empty);
1175         }
1176         
1177         private string xdgUserDir(string key, string fallback = null) nothrow @trusted {
1178             string fileName = maybeConcat(writablePath(StandardPath.config), "/user-dirs.dirs");
1179             string home = homeDir();
1180             try {
1181                 auto f = File(fileName, "r");
1182                 auto xdgdir = "XDG_" ~ key ~ "_DIR";
1183                 auto path = getFromUserDirs(xdgdir, home, f.byLine());
1184                 if (path.length) {
1185                     return path;
1186                 }
1187             } catch(Exception e) {
1188                 
1189             }
1190             
1191             if (home.length) {
1192                 try {
1193                     auto f = File("/etc/xdg/user-dirs.defaults", "r");
1194                     auto path = getFromDefaultDirs(key, home, f.byLine());
1195                     if (path.length) {
1196                         return path;
1197                     }
1198                 } catch (Exception e) {
1199                     
1200                 }
1201                 if (fallback.length) {
1202                     return home ~ fallback;
1203                 }
1204             }
1205             return null;
1206         }
1207         
1208         private string homeFontsPath() nothrow @trusted {
1209             return maybeConcat(homeDir(), "/.fonts");
1210         }
1211         
1212         private string[] fontPaths() nothrow @trusted
1213         {
1214             enum localShare = "/usr/local/share/fonts";
1215             enum share = "/usr/share/fonts";
1216             
1217             string homeFonts = homeFontsPath();
1218             if (homeFonts.length) {
1219                 return [homeFonts, localShare, share];
1220             } else {
1221                 return [localShare, share];
1222             }
1223         }
1224         
1225         private string writablePathImpl(StandardPath type, bool shouldCreate) nothrow @safe
1226         {
1227             final switch(type) {
1228                 case StandardPath.config:
1229                     return xdgConfigHome(null, shouldCreate);
1230                 case StandardPath.cache:
1231                     return xdgCacheHome(null, shouldCreate);
1232                 case StandardPath.data:
1233                     return xdgDataHome(null, shouldCreate);
1234                 case StandardPath.desktop:
1235                     return xdgUserDir("DESKTOP", "/Desktop").createIfNeeded(shouldCreate);
1236                 case StandardPath.documents:
1237                     return xdgUserDir("DOCUMENTS").createIfNeeded(shouldCreate);
1238                 case StandardPath.pictures:
1239                     return xdgUserDir("PICTURES").createIfNeeded(shouldCreate);
1240                 case StandardPath.music:
1241                     return xdgUserDir("MUSIC").createIfNeeded(shouldCreate);
1242                 case StandardPath.videos:
1243                     return xdgUserDir("VIDEOS").createIfNeeded(shouldCreate);
1244                 case StandardPath.downloads:
1245                     return xdgUserDir("DOWNLOAD").createIfNeeded(shouldCreate);
1246                 case StandardPath.templates:
1247                     return xdgUserDir("TEMPLATES", "/Templates").createIfNeeded(shouldCreate);
1248                 case StandardPath.publicShare:
1249                     return xdgUserDir("PUBLICSHARE", "/Public").createIfNeeded(shouldCreate);
1250                 case StandardPath.fonts:
1251                     return homeFontsPath().createIfNeeded(shouldCreate);
1252                 case StandardPath.applications:
1253                     return xdgDataHome("applications", shouldCreate);
1254                 case StandardPath.startup:
1255                     return xdgConfigHome("autostart", shouldCreate);
1256             }
1257         }
1258         
1259         string writablePath(StandardPath type, FolderFlag params = FolderFlag.none) nothrow @safe
1260         {
1261             const bool shouldCreate = (params & FolderFlag.create) != 0;
1262             const bool shouldVerify = (params & FolderFlag.verify) != 0;
1263             return writablePathImpl(type, shouldCreate).verifyIfNeeded(shouldVerify);
1264         }
1265         
1266         string[] standardPaths(StandardPath type) nothrow @safe
1267         {
1268             string[] paths;
1269             
1270             switch(type) {
1271                 case StandardPath.data:
1272                     return xdgAllDataDirs();
1273                 case StandardPath.config:
1274                     return xdgAllConfigDirs();
1275                 case StandardPath.applications:
1276                     return xdgAllDataDirs("applications");
1277                 case StandardPath.startup:
1278                     return xdgAllConfigDirs("autostart");
1279                 case StandardPath.fonts:
1280                     return fontPaths();
1281                 default:
1282                     break;
1283             }
1284             
1285             string userPath = writablePath(type);
1286             if (userPath.length) {
1287                 return [userPath];
1288             }
1289             return null;
1290         }
1291     }
1292 }