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