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