1 /** 2 * OS-specific paths. 3 * 4 * getConfigDir - roaming, for configuration 5 * getDataDir - roaming, for user data 6 * getCacheDir - local 7 * 8 * License: 9 * This Source Code Form is subject to the terms of 10 * the Mozilla Public License, v. 2.0. If a copy of 11 * the MPL was not distributed with this file, You 12 * can obtain one at http://mozilla.org/MPL/2.0/. 13 * 14 * Authors: 15 * Vladimir Panteleev <vladimir@thecybershadow.net> 16 * 17 * References: 18 * https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html 19 */ 20 21 module ae.sys.paths; 22 23 import std.path; 24 25 version (Windows) 26 { 27 import core.stdc.wctype; 28 29 import std.exception; 30 import std.file; 31 import std.utf; 32 33 import ae.sys.windows.imports; 34 mixin(importWin32!q{shlobj}); 35 mixin(importWin32!q{objidl}); 36 mixin(importWin32!q{windef}); 37 mixin(importWin32!q{winbase}); 38 39 private string getShellPath(int csidl) 40 { 41 LPITEMIDLIST pidl; 42 SHGetSpecialFolderLocation(null, csidl, &pidl); 43 scope(exit) 44 { 45 IMalloc aMalloc; 46 SHGetMalloc(&aMalloc); 47 aMalloc.Free(pidl); 48 } 49 50 auto path = new wchar[MAX_PATH]; 51 if (!SHGetPathFromIDListW(pidl, path.ptr)) 52 return null; 53 path.length = wcslen(path.ptr); 54 55 return toUTF8(path); 56 } 57 58 /*private*/ string getAppDir(int csidl, string appName = null) 59 { 60 string dir = getShellPath(csidl) ~ `\` ~ (appName ? appName : getExecutableName()); 61 if (!exists(dir)) 62 mkdir(dir); 63 return dir; 64 } 65 66 /*private*/ string[] getAppDirs(int csidl, string appName = null) 67 { 68 return [thisExePath.dirName(), getAppDir(csidl, appName)]; 69 } 70 71 alias getLocalAppProfile = bindArgs!(getAppDir, CSIDL_LOCAL_APPDATA); 72 alias getRoamingAppProfile = bindArgs!(getAppDir, CSIDL_APPDATA); 73 74 alias getConfigDir = getRoamingAppProfile; 75 alias getDataDir = getRoamingAppProfile; 76 alias getCacheDir = getLocalAppProfile; 77 78 alias getConfigDirs = bindArgs!(getAppDir, CSIDL_LOCAL_APPDATA);; 79 alias getDataDirs = bindArgs!(getAppDir, CSIDL_LOCAL_APPDATA);; 80 } 81 else // POSIX 82 { 83 import std.algorithm.iteration; 84 import std.array; 85 import std.ascii; 86 import std.conv : octal; 87 import std.file; 88 import std.process; 89 import std.string; 90 91 alias toLower = std.ascii.toLower; 92 93 private string getPosixAppName(string appName) 94 { 95 string s = appName ? appName : getExecutableName(); 96 string s2; 97 foreach (c; s) 98 if (isAlphaNum(c)) 99 s2 ~= toLower(c); 100 else 101 if (!s2.endsWith('-')) 102 s2 ~= '-'; 103 return s2; 104 } 105 106 struct XdgDir 107 { 108 string homeVarName; 109 string homeDefaultValue; 110 string dirsVarName; 111 string dirsDefaultValue; 112 113 string getHome() const 114 { 115 string path = environment.get(homeVarName, homeDefaultValue.expandTilde()); 116 if (!exists(path)) 117 { 118 mkdir(path); 119 setAttributes(path, octal!700); 120 } 121 return path; 122 } 123 124 string getAppHome(string appName) const 125 { 126 string path = getHome(); 127 path = path.buildPath(getPosixAppName(appName)); 128 if (!exists(path)) 129 mkdir(path); 130 return path; 131 } 132 133 string[] getDirs() const 134 { 135 string paths = environment.get(dirsVarName, dirsDefaultValue); 136 return [getHome()] ~ paths.split(pathSeparator); 137 } 138 139 string[] getAppDirs(string appName) const 140 { 141 return getDirs() 142 .map!(dir => dir.buildPath(getPosixAppName(appName))) 143 .array(); 144 } 145 146 } 147 148 immutable XdgDir xdgData = XdgDir("XDG_DATA_HOME" , "~/.local/share", "XDG_DATA_DIRS" , "/usr/local/share/:/usr/share/"); 149 immutable XdgDir xdgConfig = XdgDir("XDG_CONFIG_HOME", "~/.config" , "XDG_CONFIG_DIRS", "/etc/xdg"); 150 immutable XdgDir xdgCache = XdgDir("XDG_CACHE_HOME" , "~/.cache" ); 151 152 /*private*/ string getXdgAppDir(alias xdgDir)(string appName = null) 153 { 154 return xdgDir.getAppHome(appName); 155 } 156 157 /*private*/ string[] getXdgAppDirs(alias xdgDir)(string appName = null) 158 { 159 return xdgDir.getAppDirs(appName); 160 } 161 162 alias getDataDir = getXdgAppDir!xdgData; 163 alias getConfigDir = getXdgAppDir!xdgConfig; 164 alias getCacheDir = getXdgAppDir!xdgCache; 165 166 alias getDataDirs = getXdgAppDirs!xdgData; 167 alias getConfigDirs = getXdgAppDirs!xdgConfig; 168 } 169 170 /// Get the base name of the current executable. 171 string getExecutableName() 172 { 173 import std.file; 174 return thisExePath().baseName(); 175 } 176 177 /*private*/ template bindArgs(alias fun, CTArgs...) 178 { 179 auto bindArgs(RTArgs...)(auto ref RTArgs rtArgs) 180 { 181 return fun(CTArgs, rtArgs); 182 } 183 }