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