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 }