]> de.git.xonotic.org Git - xonotic/netradiant.git/blob - libs/transformpath/transformpath.cpp
67e482e14ab58aa1c41c28f7ebdfe1e0ac05309b
[xonotic/netradiant.git] / libs / transformpath / transformpath.cpp
1 /* SPDX-License-Identifier: MIT License
2
3 Copyright © 2021 Thomas “illwieckz” Debesse
4
5 Permission is hereby granted, free of charge, to any person obtaining a
6 copy of this software and associated documentation files (the “Software”),
7 to deal in the Software without restriction, including without limitation
8 the rights to use, copy, modify, merge, publish, distribute, sublicense,
9 and/or sell copies of the Software, and to permit persons to whom the
10 Software is furnished to do so, subject to the following conditions:
11
12 The above copyright notice and this permission notice shall be included
13 in all copies or substantial portions of the Software.
14
15 THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18 THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21 DEALINGS IN THE SOFTWARE. */
22
23 /* transformpath: transform file path based on keywords.
24
25 This is not an environment variable parser, this only supports
26 a set of keywords using common environment name syntax when it
27 exists to make the strings easier to read.
28
29 Supported substitution keywords,
30 Windows:
31
32 - %HOMEPATH%
33 - %USERPROFILE%
34 - %ProgramFiles%
35 - %ProgramFiles(x86)%
36 - %ProgramW6432%
37 - %APPDATA%
38 - [CSIDL_MYDOCUMENTS]
39
40 Supported substitution keywords,
41 Linux, FreeBSD, macOS:
42
43 - ~
44 - ${HOME}
45
46 Supported substitution keywords,
47 Linux, FreeBSD, other XDG systems:
48
49 - ${XDG_CONFIG_HOME}
50 - ${XDG_DATA_HOME}
51
52 Examples,
53 game engine directories:
54
55 - Windows: %ProgramFiles%\Unvanquished
56 - Linux: ${XDG_DATA_HOME}/unvanquished/base
57 - macOS: ${HOME}/Games/Unvanquished
58
59 Examples,
60 game home directories:
61
62 - Windows: [CSIDL_MYDOCUMENTS]\My Games\Unvanquished
63 - Linux: ${XDG_DATA_HOME}/unvanquished
64 - macOS: ${HOME}/Application Data/Unvanquished
65
66 */
67
68 #include "globaldefs.h"
69 #include "stringio.h"
70
71 #include <cstring>
72 #include <string>
73
74 #if GDEF_OS_WINDOWS
75 #include <windows.h>
76 #include <iostream>
77 #include <shlobj.h>
78 #pragma comment(lib, "shell32.lib")
79 #endif // !GDEF_OS_WINDOWS
80
81 #if GDEF_OS_LINUX || GDEF_OS_BSD || GDEF_OS_MACOS
82 #include <unistd.h>
83 #include <sys/types.h>
84 #include <pwd.h>
85 #endif // !GDEF_OS_LINUX && !GDEF_OS_BSD && !GDEF_OS_MACOS
86
87 #if GDEF_OS_WINDOWS
88 static std::string getUserProfilePath();
89 #endif // !GDEF_OS_WINDOWS
90
91 static std::string getUserName()
92 {
93 #if GDEF_OS_WINDOWS
94         std::string path( getenv( "USERNAME" ) );
95
96         if ( ! path.empty() )
97         {
98                 return path;
99         }
100
101         globalErrorStream() << "\%USERNAME\% not found.\n";
102
103         return "";
104 #endif // !GDEF_OS_WINDOWS
105
106 #if GDEF_OS_LINUX || GDEF_OS_BSD || GDEF_OS_MACOS
107         std::string path( getenv( "USERNAME" ) );
108
109         if ( ! path.empty() )
110         {
111                 return path;
112         }
113
114         globalErrorStream() << "${USERNAME} not found, guessing…\n";
115
116         path = std::string( getenv( "LOGNAME" ) );
117
118         if ( ! path.empty() )
119         {
120                 return path;
121         }
122
123         globalErrorStream() << "${LOGNAME} not found, guessing…\n";
124
125         path = std::string( getenv( "USER" ) );
126
127         if ( ! path.empty() )
128         {
129                 return path;
130         }
131
132         globalErrorStream() << "${USER} not found.\n";
133
134         return "";
135 #endif // !GDEF_OS_LINUX && !GDEF_OS_BSD && !GDEF_OS_MACOS
136 }
137
138 static std::string getHomePath()
139 {
140 #if GDEF_OS_WINDOWS
141         std::string path( getenv( "HOMEPATH" ) );
142
143         if ( ! path.empty() )
144         {
145                 return path;
146         }
147
148         globalErrorStream() << "\%HOMEPATH\% not found, guessing…\n";
149
150         std::string path1 = getUserProfilePath();
151
152         if ( ! path1.empty() )
153         {
154                 return path1;
155         }
156
157         globalErrorStream() << "\%HOMEPATH\% not found.\n";
158
159         return "";
160 #endif // !GDEF_OS_WINDOWS
161
162 #if GDEF_OS_LINUX || GDEF_OS_BSD || GDEF_OS_MACOS
163         // Get the path environment variable.
164         std::string path( getenv( "HOME" ) );
165
166         // Look up path directory in password database.
167         if( ! path.empty() )
168         {
169                 return path;
170         }
171
172         globalErrorStream() << "${HOME} not found, guessing…\n";
173
174         static char     buf[ 4096 ];
175         struct passwd pw, *pwp;
176
177         if ( getpwuid_r( getuid(), &pw, buf, sizeof( buf ), &pwp ) == 0 )
178         {
179                 return std::string( pw.pw_dir );
180         }
181
182         globalErrorStream() << "${HOME} not found, guessing…\n";
183
184         std::string path1( "/home/" );
185
186         std::string path2 = getUserName();
187
188         if ( ! path2.empty() )
189         {
190                 return path1 + path2;
191         }
192
193         globalErrorStream() << "${HOME} not found…\n";
194
195         return "";
196 #endif // !GDEF_OS_LINUX && !GDEF_OS_BSD && !GDEF_OS_MACOS
197 }
198
199 #if GDEF_OS_WINDOWS
200 static std::string getSystemDrive()
201 {
202         std::string path( getenv( "SYSTEMDRIVE" ) );
203
204         if ( ! path.empty() )
205         {
206                 return path;
207         }
208
209         globalErrorStream() << "\%SYSTEMDRIVE\% not found, guessing…\n";
210
211         return "C:";
212 }
213
214 static std::string getUserProfilePath()
215 {
216         std::string path( getenv( "USERPROFILE" ) );
217
218         if ( ! path.empty() )
219         {
220                 return path;
221         }
222
223         globalErrorStream() << "\%USERPROFILE\% not found, guessing…\n";
224
225         std::string path1 = getSystemDrive();
226         std::string path2 = getUserName();
227
228         if ( ! path2.empty() )
229         {
230                 return path1 + "\\Users\\" + path2;
231         }
232
233         globalErrorStream() << "\%USERPROFILE\% not found.\n";
234
235         return "";
236 }
237
238 static std::string getProgramFilesPath()
239 {
240         std::string path( getenv( "ProgramFiles" ) );
241
242         if ( ! path.empty() )
243         {
244                 return path;
245         }
246
247         globalErrorStream() << "\%ProgramFiles\% not found, guessing…\n";
248
249         std::string path1 = getSystemDrive();
250         return path1 + "\\Program Files";
251 }
252
253 static std::string getProgramFilesX86Path()
254 {
255         std::string path( getenv( "ProgramFiles(x86)" ) );
256
257         if ( ! path.empty() )
258         {
259                 return path;
260         }
261
262         globalErrorStream() << "\%ProgramFiles(x86)\% not found, guessing…\n";
263
264         return getProgramFilesPath();
265 }
266
267 static std::string getProgramW6432Path()
268 {
269         std::string path( getenv( "ProgramW6432" ) );
270
271         if ( ! path.empty() )
272         {
273                 return path;
274         }
275
276         globalErrorStream() << "\%ProgramW6432\% not found, guessing…\n";
277
278         return getProgramFilesPath();
279 }
280
281 static std::string getAppDataPath()
282 {
283         std::string path( getenv( "APPDATA" ) );
284
285         if ( ! path.empty() )
286         {
287                 return path;
288         }
289
290         globalErrorStream() << "\%APPDATA\% not found, guessing…\n";
291
292         std::string path1 = getUserProfilePath();
293
294         if ( ! path1.empty() )
295         {
296                 return path1 + "\\AppData\\Roaming";
297         }
298
299         globalErrorStream() << "\%APPDATA\% not found.\n";
300
301         return std::string( "" );
302 }
303
304 /* TODO: see also qFOLDERID_SavedGames in mainframe.cpp,
305 HomePaths_Realise and other things like that,
306 they look to be game paths, not NetRadiant paths. */
307
308 static std::string getMyDocumentsPath()
309 {
310         CHAR path[ MAX_PATH ];
311         HRESULT result = SHGetFolderPath(NULL, CSIDL_MYDOCUMENTS, NULL, SHGFP_TYPE_CURRENT, path);
312
313         if ( result == S_OK )
314         {
315                 return std::string( path );
316         }
317
318         globalErrorStream() << "[CSIDL_MYDOCUMENTS] not found, guessing…\n";
319
320         std::string path1 = getHomePath();
321
322         if ( ! path1.empty() )
323         {
324                 path1 += "\\Documents";
325                 
326                 return path1;
327         }
328
329         globalErrorStream() << "[CSIDL_MYDOCUMENTS] not found.\n";
330
331         return "";
332 }
333 #endif // !GDEF_OS_WINDOWS
334
335 #if GDEF_OS_XDG
336 static std::string getXdgConfigHomePath()
337 {
338         /* FIXME: we may want to rely on g_get_user_config_dir()
339         provided by GLib. */
340         std::string path ( getenv( "XDG_CONFIG_HOME" ) );
341
342         if ( ! path.empty() )
343         {
344                 return path;
345         }
346
347         // This is not an error.
348         // globalErrorStream() << "${XDG_CONFIG_HOME} not found, guessing…\n";
349
350         std::string path1 = getHomePath();
351
352         if ( ! path1.empty() )
353         {
354                 return path1 + "/.config";
355         }
356
357         globalErrorStream() << "${XDG_CONFIG_HOME} not found.\n";
358
359         return "";
360 }
361
362 static std::string getXdgDataHomePath()
363 {
364         std::string path ( getenv( "XDG_DATA_HOME" ) );
365
366         if ( ! path.empty() )
367         {
368                 return path;
369         }
370
371         // This is not an error.
372         // globalErrorStream() << "${XDG_DATA_HOME} not found, guessing…\n";
373
374         std::string path1 = getHomePath();
375
376         if ( ! path1.empty() )
377         {
378                 return path1 + "/.local/share";
379         }
380
381         globalErrorStream() << "${XDG_DATA_HOME} not found.\n";
382
383         return "";
384 }
385 #endif // GDEF_OS_XDG
386
387 struct pathTransformer_t
388 {
389         std::string pattern;
390         std::string ( *function )();
391 };
392
393 static const pathTransformer_t pathTransformers[] =
394 {
395 #if GDEF_OS_WINDOWS
396         { "\%HOMEPATH\%", getHomePath },
397         { "\%USERPROFILE\%", getUserProfilePath },
398         { "\%ProgramFiles\%", getProgramFilesPath },
399         { "\%ProgramFiles(x86)\%", getProgramFilesX86Path },
400         { "\%ProgramW6432\%", getProgramW6432Path },
401         { "\%APPDATA\%", getAppDataPath },
402         { "[CSIDL_MYDOCUMENTS]", getMyDocumentsPath },
403 #endif // GDEF_OS_WINDOWS
404
405 #if GDEF_OS_LINUX || GDEF_OS_BSD || GDEF_OS_MACOS
406         { "~", getHomePath },
407         { "${HOME}", getHomePath },
408 #endif // GDEF_OS_LINUX || GDEF_OS_BSD || GDEF_OS_MACOS
409
410 #if GDEF_OS_XDG
411         { "${XDG_CONFIG_HOME}", getXdgConfigHomePath },
412         { "${XDG_DATA_HOME}", getXdgDataHomePath },
413 #endif // GDEF_OS_XDG
414 };
415
416 /* If no transformation succeeds, the path will be returned untransformed. */
417 std::string transformPath( std::string transformedPath )
418 {
419         for ( const pathTransformer_t &pathTransformer : pathTransformers )
420         {
421                 if ( transformedPath.find( pathTransformer.pattern, 0 ) == 0 )
422                 {
423                         globalOutputStream() << "Path Transforming: '" << transformedPath.c_str() << "'\n";
424
425                         std::string path = pathTransformer.function();
426
427                         if ( ! path.empty() )
428                         {
429                                 transformedPath.replace( 0, pathTransformer.pattern.length(), path );
430
431                                 globalOutputStream() << "Path Transformed: '" << transformedPath.c_str() << "'\n";
432
433                                 return transformedPath;
434                         }
435
436                         break;
437                 }
438         }
439
440         globalErrorStream() << "Path not transformed: '" << transformedPath.c_str() << "'\n";
441
442         return transformedPath;
443 }