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 }