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