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 <ae@cy.md>
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 		return getShellPath(csidl) ~ `\` ~ (appName ? appName : getExecutableName());
61 	}
62 
63 	/*private*/ string[] _getAppDirs(int csidl, string appName = null)
64 	{
65 		return [thisExePath.dirName(), _getAppDir(csidl, appName)];
66 	}
67 
68 	alias getLocalAppProfile   = _bindArgs!(_getAppDir, CSIDL_LOCAL_APPDATA); ///
69 	alias getRoamingAppProfile = _bindArgs!(_getAppDir, CSIDL_APPDATA);       ///
70 
71 	alias getConfigDir  = getRoamingAppProfile; ///
72 	alias getDataDir    = getRoamingAppProfile; ///
73 	alias getCacheDir   = getLocalAppProfile;   ///
74 
75 	alias getConfigDirs = _bindArgs!(_getAppDir, CSIDL_LOCAL_APPDATA); ///
76 	alias getDataDirs   = _bindArgs!(_getAppDir, CSIDL_LOCAL_APPDATA); ///
77 
78 	string getHomeDir() { return getShellPath(CSIDL_PROFILE); }
79 }
80 else // POSIX
81 {
82 	import std.algorithm.iteration;
83 	import std.array;
84 	import std.ascii;
85 	import std.conv : octal;
86 	import std.file;
87 	import std.process;
88 	import std.string;
89 
90 	private alias toLower = std.ascii.toLower;
91 
92 	private string getPosixAppName(string appName)
93 	{
94 		string s = appName ? appName : getExecutableName();
95 		string s2;
96 		foreach (c; s)
97 			if (isAlphaNum(c))
98 				s2 ~= toLower(c);
99 			else
100 				if (!s2.endsWith('-'))
101 					s2 ~= '-';
102 		return s2;
103 	}
104 
105 	private struct XdgDir
106 	{
107 		string homeVarName;
108 		string homeDefaultValue;
109 		string dirsVarName;
110 		string dirsDefaultValue;
111 
112 		string getHome() const
113 		{
114 			return environment.get(homeVarName, homeDefaultValue.expandTilde());
115 		}
116 
117 		string getAppHome(string appName) const
118 		{
119 			return getHome().buildPath(getPosixAppName(appName));
120 		}
121 
122 		string[] getDirs() const
123 		{
124 			string paths = environment.get(dirsVarName, dirsDefaultValue);
125 			return [getHome()] ~ paths.split(pathSeparator);
126 		}
127 
128 		string[] getAppDirs(string appName) const
129 		{
130 			return getDirs()
131 				.map!(dir => dir.buildPath(getPosixAppName(appName)))
132 				.array();
133 		}
134 
135 	}
136 
137 	immutable XdgDir _xdgData   = XdgDir("XDG_DATA_HOME"  , "~/.local/share", "XDG_DATA_DIRS"  , "/usr/local/share/:/usr/share/");
138 	immutable XdgDir _xdgConfig = XdgDir("XDG_CONFIG_HOME", "~/.config"     , "XDG_CONFIG_DIRS", "/etc/xdg");
139 	immutable XdgDir _xdgCache  = XdgDir("XDG_CACHE_HOME" , "~/.cache"      );
140 
141 	/*private*/ string _getXdgAppDir(alias xdgDir)(string appName = null)
142 	{
143 		return xdgDir.getAppHome(appName);
144 	}
145 
146 	/*private*/ string[] _getXdgAppDirs(alias xdgDir)(string appName = null)
147 	{
148 		return xdgDir.getAppDirs(appName);
149 	}
150 
151 	alias getDataDir    = _getXdgAppDir!_xdgData;    ///
152 	alias getConfigDir  = _getXdgAppDir!_xdgConfig;  ///
153 	alias getCacheDir   = _getXdgAppDir!_xdgCache;   ///
154 
155 	alias getDataDirs   = _getXdgAppDirs!_xdgData;   ///
156 	alias getConfigDirs = _getXdgAppDirs!_xdgConfig; ///
157 
158 	string getHomeDir() { return environment.get("HOME"); }
159 }
160 
161 /// Get the base name of the current executable.
162 string getExecutableName()
163 {
164 	import std.file;
165 	return thisExePath().baseName();
166 }
167 
168 /*private*/ template _bindArgs(alias fun, CTArgs...)
169 {
170 	auto _bindArgs(RTArgs...)(auto ref RTArgs rtArgs)
171 	{
172 		return fun(CTArgs, rtArgs);
173 	}
174 }