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 		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 else // POSIX
79 {
80 	import std.algorithm.iteration;
81 	import std.array;
82 	import std.ascii;
83 	import std.conv : octal;
84 	import std.file;
85 	import std.process;
86 	import std.string;
87 
88 	alias toLower = std.ascii.toLower;
89 
90 	private string getPosixAppName(string appName)
91 	{
92 		string s = appName ? appName : getExecutableName();
93 		string s2;
94 		foreach (c; s)
95 			if (isAlphaNum(c))
96 				s2 ~= toLower(c);
97 			else
98 				if (!s2.endsWith('-'))
99 					s2 ~= '-';
100 		return s2;
101 	}
102 
103 	struct XdgDir
104 	{
105 		string homeVarName;
106 		string homeDefaultValue;
107 		string dirsVarName;
108 		string dirsDefaultValue;
109 
110 		string getHome() const
111 		{
112 			return environment.get(homeVarName, homeDefaultValue.expandTilde());
113 		}
114 
115 		string getAppHome(string appName) const
116 		{
117 			return getHome().buildPath(getPosixAppName(appName));
118 		}
119 
120 		string[] getDirs() const
121 		{
122 			string paths = environment.get(dirsVarName, dirsDefaultValue);
123 			return [getHome()] ~ paths.split(pathSeparator);
124 		}
125 
126 		string[] getAppDirs(string appName) const
127 		{
128 			return getDirs()
129 				.map!(dir => dir.buildPath(getPosixAppName(appName)))
130 				.array();
131 		}
132 
133 	}
134 
135 	immutable XdgDir xdgData   = XdgDir("XDG_DATA_HOME"  , "~/.local/share", "XDG_DATA_DIRS"  , "/usr/local/share/:/usr/share/");
136 	immutable XdgDir xdgConfig = XdgDir("XDG_CONFIG_HOME", "~/.config"     , "XDG_CONFIG_DIRS", "/etc/xdg");
137 	immutable XdgDir xdgCache  = XdgDir("XDG_CACHE_HOME" , "~/.cache"      );
138 
139 	/*private*/ string getXdgAppDir(alias xdgDir)(string appName = null)
140 	{
141 		return xdgDir.getAppHome(appName);
142 	}
143 
144 	/*private*/ string[] getXdgAppDirs(alias xdgDir)(string appName = null)
145 	{
146 		return xdgDir.getAppDirs(appName);
147 	}
148 
149 	alias getDataDir    = getXdgAppDir!xdgData;
150 	alias getConfigDir  = getXdgAppDir!xdgConfig;
151 	alias getCacheDir   = getXdgAppDir!xdgCache;
152 
153 	alias getDataDirs   = getXdgAppDirs!xdgData;
154 	alias getConfigDirs = getXdgAppDirs!xdgConfig;
155 }
156 
157 /// Get the base name of the current executable.
158 string getExecutableName()
159 {
160 	import std.file;
161 	return thisExePath().baseName();
162 }
163 
164 /*private*/ template bindArgs(alias fun, CTArgs...)
165 {
166 	auto bindArgs(RTArgs...)(auto ref RTArgs rtArgs)
167 	{
168 		return fun(CTArgs, rtArgs);
169 	}
170 }