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