1 /*
2  * TilEm II
3  *
4  * Copyright (c) 2011-2012 Benjamin Moody
5  *
6  * This program is free software: you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License as
8  * published by the Free Software Foundation, either version 3 of the
9  * License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful, but
12  * WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #ifdef HAVE_CONFIG_H
21 # include <config.h>
22 #endif
23 
24 #include <stdio.h>
25 #include <string.h>
26 #include <stdarg.h>
27 #include <glib.h>
28 #include <glib/gstdio.h>
29 
30 #ifdef G_OS_WIN32
31 # include <shlobj.h>
32 #endif
33 
34 #include "files.h"
35 
36 static char *program_dir;
37 
38 /* Set the name used to invoke this program */
set_program_path(const char * path)39 void set_program_path(const char *path)
40 {
41 	if (path && strchr(path, G_DIR_SEPARATOR))
42 		program_dir = g_path_get_dirname(path);
43 }
44 
45 /* Build a filename out of varargs */
build_filenamev(const char * start,va_list rest)46 static char *build_filenamev(const char *start, va_list rest)
47 {
48 	char *args[10];
49 	int i;
50 
51 	args[0] = (char*) start;
52 	for (i = 1; i < 10; i++) {
53 		args[i] = (char*) va_arg(rest, const char *);
54 		if (!args[i])
55 			break;
56 	}
57 	g_assert(i < 10);
58 
59 	return g_build_filenamev(args);
60 }
61 
62 #ifdef G_OS_WIN32
get_special_folder(int csidl)63 static char * get_special_folder(int csidl)
64 {
65 	char lpath[MAX_PATH+1];
66 	wchar_t wpath[MAX_PATH+1];
67 	LPITEMIDLIST pidl = NULL;
68 	gchar *s = NULL;
69 
70 	if (SHGetSpecialFolderLocation(NULL, csidl, &pidl))
71 		return NULL;
72 
73 	if (G_WIN32_HAVE_WIDECHAR_API()) {
74 		if (SHGetPathFromIDListW(pidl, wpath))
75 			s = g_utf16_to_utf8(wpath, -1, NULL, NULL, NULL);
76 	}
77 	else {
78 		if (SHGetPathFromIDListA(pidl, lpath))
79 			s = g_locale_to_utf8(lpath, -1, NULL, NULL, NULL);
80 	}
81 
82 	CoTaskMemFree(pidl);
83 	return s;
84 }
85 #endif
86 
87 /* Get the default configuration directory.
88 
89    On Unix, this is $XDG_CONFIG_HOME/tilem2 (where $XDG_CONFIG_HOME
90    defaults to $HOME/.config/ if not set.)
91 
92    On Windows, this is $CSIDL_LOCAL_APPDATA\tilem2 (where
93    $CSIDL_LOCAL_APPDATA is typically "Local Settings\Application Data"
94    in the user's profile.)
95 
96    Result is cached and should not be freed. */
get_default_config_dir()97 static char * get_default_config_dir()
98 {
99 	static char *result;
100 
101 	if (!result) {
102 #ifdef G_OS_WIN32
103 		/* Do not use g_get_user_config_dir() on Windows,
104 		   because the behavior of that function is not
105 		   consistent across versions of GLib. */
106 		char *s = get_special_folder(CSIDL_LOCAL_APPDATA);
107 		if (s)
108 			result = g_build_filename(s, "tilem2", NULL);
109 		g_free(s);
110 #else
111 		result = g_build_filename(g_get_user_config_dir(),
112 		                          "tilem2", NULL);
113 #endif
114 	}
115 
116 	return result;
117 }
118 
119 /* Search for an existing file.
120 
121    The default package configuration directory (defined above) is
122    searched first; if the file is not found there, try to find the
123    file that was installed along with the package, or (in case the
124    package hasn't yet been installed) the copy included in the source
125    package. */
find_filev(GFileTest test,const char * name,va_list rest)126 static char * find_filev(GFileTest test, const char *name, va_list rest)
127 {
128 	char *fullname, *dname, *path;
129 	const char *userdir;
130 	const char * const *sysdirs;
131 
132 	fullname = build_filenamev(name, rest);
133 
134 	dname = get_default_config_dir();
135 	path = g_build_filename(dname, fullname, NULL);
136 	if (g_file_test(path, test)) {
137 		g_free(fullname);
138 		return path;
139 	}
140 	g_free(path);
141 
142 #ifdef G_OS_WIN32
143 	if ((dname = g_win32_get_package_installation_directory(NULL, NULL))) {
144 		path = g_build_filename(dname, "share", "tilem2", fullname, NULL);
145 		g_free(dname);
146 		if (g_file_test(path, test)) {
147 			g_free(fullname);
148 			return path;
149 		}
150 		g_free(path);
151 	}
152 #endif
153 
154 #ifdef UNINSTALLED_SHARE_DIR
155 	if (program_dir) {
156 		path = g_build_filename(program_dir, UNINSTALLED_SHARE_DIR,
157 		                        fullname, NULL);
158 		if (g_file_test(path, test)) {
159 			g_free(fullname);
160 			return path;
161 		}
162 		g_free(path);
163 	}
164 #endif
165 
166 #ifdef SHARE_DIR
167 	path = g_build_filename(SHARE_DIR, fullname, NULL);
168 	if (g_file_test(path, test)) {
169 		g_free(fullname);
170 		return path;
171 	}
172 	g_free(path);
173 #endif
174 
175 	userdir = g_get_user_data_dir();
176 	if (userdir) {
177 		path = g_build_filename(userdir, "tilem2", fullname, NULL);
178 		if (g_file_test(path, test)) {
179 			g_free(fullname);
180 			return path;
181 		}
182 	}
183 
184 	sysdirs = g_get_system_data_dirs();
185 	while (sysdirs && sysdirs[0]) {
186 		path = g_build_filename(sysdirs[0], "tilem2", fullname, NULL);
187 		if (g_file_test(path, test)) {
188 			g_free(fullname);
189 			return path;
190 		}
191 		sysdirs++;
192 	}
193 
194 	g_free(fullname);
195 	return NULL;
196 }
197 
198 /* Locate an existing configuration or data file */
get_shared_file_path(const char * name,...)199 char * get_shared_file_path(const char *name, ...)
200 {
201 	va_list ap;
202 	char *path;
203 	va_start(ap, name);
204 	path = find_filev(G_FILE_TEST_IS_REGULAR, name, ap);
205 	va_end(ap);
206 	return path;
207 }
208 
209 /* Locate an existing configuration or data directory */
get_shared_dir_path(const char * name,...)210 char * get_shared_dir_path(const char *name, ...)
211 {
212 	va_list ap;
213 	char *path;
214 	va_start(ap, name);
215 	path = find_filev(G_FILE_TEST_IS_DIR, name, ap);
216 	va_end(ap);
217 	return path;
218 }
219 
220 /* Return the path to the user's configuration directory, where any
221    new or modified config files should be written.  Result is cached
222    and should not be freed. */
get_config_dir()223 static char * get_config_dir()
224 {
225 	static char *result;
226 	char *fname;
227 	FILE *f;
228 
229 	if (result)
230 		return result;
231 
232 	/* If config.ini already exists, in any of the standard
233 	   locations, and is writable, use the directory containing
234 	   it.  This will allow building the package as a relocatable
235 	   bundle. */
236 	fname = get_shared_file_path("config.ini", NULL);
237 	if (fname) {
238 		f = g_fopen(fname, "r+");
239 		if (f) {
240 			result = g_path_get_dirname(fname);
241 			fclose(f);
242 		}
243 		g_free(fname);
244 	}
245 
246 	/* Otherwise use default config directory */
247 	if (!result)
248 		result = g_strdup(get_default_config_dir());
249 
250 	return result;
251 }
252 
253 /* Get path for writing a new or modified configuration file */
get_config_file_path(const char * name,...)254 char * get_config_file_path(const char *name, ...)
255 {
256 	va_list ap;
257 	const char *cfgdir;
258 	char *fullname, *path;
259 
260 	cfgdir = get_config_dir();
261 	g_mkdir_with_parents(cfgdir, 0775);
262 
263 	va_start(ap, name);
264 	fullname = build_filenamev(name, ap);
265 	va_end(ap);
266 	path = g_build_filename(cfgdir, fullname, NULL);
267 	g_free(fullname);
268 	return path;
269 }
270 
271