Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
resource.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
/*
5 * Authors:
6 * MenTaLguY <mental@rydia.net>
7 * Martin Owens <doctormo@gmail.com>
8 *
9 * Copyright (C) 2018 Authors
10 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
11 */
12
13#include "resource.h"
14
15#ifdef HAVE_CONFIG_H
16# include "config.h" // only include where actually required!
17#endif
18
19#ifdef _WIN32
20#include <shlobj.h> // for SHGetSpecialFolderLocation
21#undef IGNORE
22#undef near
23#endif
24
25#include <sys/stat.h>
26
27#include <glibmm/convert.h>
28#include <glibmm/i18n.h>
29#include <glibmm/miscutils.h>
30#include <glibmm/stringutils.h>
31#include <glibmm/fileutils.h>
32
34#include "path-prefix.h"
35#include "preferences.h"
36#include "sys.h"
37
39
41
42#define INKSCAPE_PROFILE_DIR "inkscape"
43
47gchar *_get_path(Domain domain, Type type, char const *filename, char const *extra=nullptr)
48{
49 if (domain == USER || domain == SHARED) {
50 switch (type) {
51 case ATTRIBUTES:
52 case EXAMPLES:
53 case DOCS:
54 case SCREENS:
55 case TUTORIALS:
56 // Happens for example with `get_filename_string(SCREENS, ...)`
57 // but we don't want a user configurable about screen.
58 return nullptr;
59 }
60 }
61
62 char const *name = nullptr;
63 char const *sysdir = nullptr;
64 char const *envor = nullptr;
65
66 switch (domain) {
67 case CREATE: {
68 sysdir = "create";
69 switch (type) {
70 case PAINT: name = "paint"; break;
71 case PALETTES: name = "swatches"; break;
72 default: return nullptr;
73 }
74 } break;
75 case CACHE: {
76 g_assert(type == NONE);
77 return g_build_filename(g_get_user_cache_dir(), "inkscape", filename, extra, nullptr);
78 } break;
79
80 case SYSTEM:
81 sysdir = "inkscape";
82 case SHARED:
83 case USER: {
84 switch (type) {
85 case ATTRIBUTES: name = "attributes"; break;
86 case DOCS: name = "doc"; break;
87 case EXAMPLES: name = "examples"; break;
88 case EXTENSIONS: name = "extensions"; envor = "INKSCAPE_EXTENSIONS_DIR"; break;
89 case FILTERS: name = "filters"; break;
90 case FONTS: name = "fonts"; break;
91 case FONTCOLLECTIONS: name = "fontcollections"; break;
92 case ICONS: name = "icons"; break;
93 case KEYS: name = "keys"; break;
94 case MARKERS: name = "markers"; break;
95 case PAINT: name = "paint"; break;
96 case PALETTES: name = "palettes"; break;
97 case SCREENS: name = "screens"; break;
98 case SYMBOLS: name = "symbols"; break;
99 case TEMPLATES: name = "templates"; break;
100 case THEMES: name = "themes"; break;
101 case TUTORIALS: name = "tutorials"; break;
102 case UIS: name = "ui"; break;
103 default: g_assert_not_reached();
104 return nullptr;
105 }
106 } break;
107 }
108 // Look for an over-ride in the local environment
109 if (envor && domain == USER) {
110 std::string env_dir = Glib::getenv(envor);
111 if (!env_dir.empty()) {
112 return g_build_filename(env_dir.c_str(), filename, extra, nullptr);
113 }
114 }
115
116 if (!name) {
117 return nullptr;
118 }
119
120 if (sysdir) {
121 return g_build_filename(get_inkscape_datadir(), sysdir, name, filename, extra, nullptr);
122 } else if (domain == SHARED) {
123 if (shared_path().empty()) {
124 return nullptr;
125 }
126 return g_build_filename(shared_path().c_str(), name, filename, extra, nullptr);
127 } else {
128 if (profile_path().empty()) {
129 return nullptr;
130 }
131 return g_build_filename(profile_path().c_str(), name, filename, extra, nullptr);
132 }
133}
134
135
136
137Util::ptr_shared get_path(Domain domain, Type type, char const *filename, char const *extra)
138{
139 char *path = _get_path(domain, type, filename, extra);
140 if (!path) {
141 return Util::ptr_shared();
142 }
144 g_free(path);
145 return result;
146}
147
148std::string get_path_string(Domain domain, Type type, char const *filename, char const *extra)
149{
150 std::string result;
151 char *path = _get_path(domain, type, filename, extra);
152 if (path) {
153 result = path;
154 g_free(path);
155 }
156 return result;
157}
158
159/*
160 * Same as get_path, but checks for file's existence and falls back
161 * from USER to SYSTEM modes.
162 *
163 * type - The type of file to get, such as extension, template, ui etc
164 * filename - The filename to get, i.e. preferences.xml
165 * localized - Prefer a localized version of the file, i.e. default.de.svg instead of default.svg.
166 * (will use gettext to determine the preferred language of the user)
167 * silent - do not warn if file doesn't exist
168 *
169 */
170std::string get_filename(Type type, char const *filename, bool localized, bool silent)
171{
172 std::string result;
173
174 char *user_filename = nullptr;
175 char *shared_filename = nullptr;
176 char *sys_filename = nullptr;
177 char *user_filename_localized = nullptr;
178 char *sys_filename_localized = nullptr;
179
180 // TRANSLATORS: 'en' is an ISO 639-1 language code.
181 // Replace with language code for your language, i.e. the name of your .po file
182 localized = localized && strcmp(_("en"), "en");
183
184 if (localized) {
185 std::string localized_filename = filename;
186 localized_filename.insert(localized_filename.rfind('.'), ".");
187 localized_filename.insert(localized_filename.rfind('.'), _("en"));
188
189 user_filename_localized = _get_path(USER, type, localized_filename.c_str());
190 sys_filename_localized = _get_path(SYSTEM, type, localized_filename.c_str());
191 }
192 user_filename = _get_path(USER, type, filename);
193 shared_filename = _get_path(SHARED, type, filename);
194 sys_filename = _get_path(SYSTEM, type, filename);
195
196 // impose the following load order:
197 // USER (localized) > USER > SYSTEM (localized) > SYSTEM
198 if (localized && file_test(user_filename_localized, G_FILE_TEST_EXISTS)) {
199 result = user_filename_localized;
200 g_info("Found localized version of resource file '%s' in profile directory:\n\t%s", filename, result.c_str());
201 } else if (file_test(user_filename, G_FILE_TEST_EXISTS)) {
202 result = user_filename;
203 g_info("Found resource file '%s' in profile directory:\n\t%s", filename, result.c_str());
204 } else if (file_test(shared_filename, G_FILE_TEST_EXISTS)) {
205 result = shared_filename;
206 g_info("Found resource file '%s' in profile directory:\n\t%s", filename, result.c_str());
207 } else if (localized && file_test(sys_filename_localized, G_FILE_TEST_EXISTS)) {
208 result = sys_filename_localized;
209 g_info("Found localized version of resource file '%s' in system directory:\n\t%s", filename, result.c_str());
210 } else if (file_test(sys_filename, G_FILE_TEST_EXISTS)) {
211 result = sys_filename;
212 g_info("Found resource file '%s' in system directory:\n\t%s", filename, result.c_str());
213 } else if (!silent) {
214 if (localized) {
215 g_warning("Failed to find resource file '%s'. Looked in:\n\t%s\n\t%s\n\t%s\n\t%s\n\t%s",
216 filename, user_filename_localized, user_filename, shared_filename, sys_filename_localized, sys_filename);
217 } else {
218 g_warning("Failed to find resource file '%s'. Looked in:\n\t%s\n\t%s\n\t%s",
219 filename, user_filename, shared_filename, sys_filename);
220 }
221 }
222
223 g_free(user_filename);
224 g_free(shared_filename);
225 g_free(sys_filename);
226 g_free(user_filename_localized);
227 g_free(sys_filename_localized);
228
229 return result;
230}
231
232/*
233 * Similar to get_filename, but takes a path (or filename) for relative resolution
234 *
235 * path - A directory or filename that is considered local to the path resolution.
236 * filename - The filename that we are looking for.
237 */
238std::string get_filename(std::string const& path, std::string const& filename)
239{
240 // Test if it's a filename and get the parent directory instead
241 if (Glib::file_test(path, Glib::FileTest::IS_REGULAR)) {
242 auto dirname = Glib::path_get_dirname(path);
243 g_assert(!Glib::file_test(dirname, Glib::FileTest::IS_REGULAR)); // recursion sanity check
244 return get_filename(dirname, filename);
245 }
246 if (g_path_is_absolute(filename.c_str())) {
247 if (Glib::file_test(filename, Glib::FileTest::EXISTS)) {
248 return filename;
249 }
250 } else {
251 auto ret = Glib::build_filename(path, filename);
252 if (Glib::file_test(ret, Glib::FileTest::EXISTS)) {
253 return ret;
254 }
255 }
256 return {};
257}
258
259/*
260 * Gets all the files in a given type, for all domain types.
261 *
262 * domain - Optional domain (overload), will check return domains if not.
263 * type - The type of files, e.g. TEMPLATES
264 * extensions - A list of extensions to return, e.g. xml, svg
265 * exclusions - A list of names to exclude e.g. default.xml
266 */
267std::vector<std::string> get_filenames(Type type, std::vector<const char *> const &extensions, std::vector<const char *> const &exclusions)
268{
269 std::vector<std::string> ret;
270 get_filenames_from_path(ret, get_path_string(USER, type), extensions, exclusions);
271 get_filenames_from_path(ret, get_path_string(SHARED, type), extensions, exclusions);
272 get_filenames_from_path(ret, get_path_string(SYSTEM, type), extensions, exclusions);
273 get_filenames_from_path(ret, get_path_string(CREATE, type), extensions, exclusions);
274 return ret;
275}
276
277std::vector<std::string> get_filenames(Domain domain, Type type, std::vector<const char *> const &extensions, std::vector<const char *> const &exclusions)
278{
279 std::vector<std::string> ret;
280 get_filenames_from_path(ret, get_path_string(domain, type), extensions, exclusions);
281 return ret;
282}
283std::vector<std::string> get_filenames(std::string path, std::vector<const char *> const &extensions, std::vector<const char *> const &exclusions)
284{
285 std::vector<std::string> ret;
286 get_filenames_from_path(ret, Glib::filename_from_utf8(path), extensions, exclusions);
287 return ret;
288}
289
290/*
291 * Gets all folders inside each type, for all domain types.
292 *
293 * domain - Optional domain (overload), will check return domains if not.
294 * type - The type of files, e.g. TEMPLATES
295 * extensions - A list of extensions to return, e.g. xml, svg
296 * exclusions - A list of names to exclude e.g. default.xml
297 */
298std::vector<std::string> get_foldernames(Type type, std::vector<const char *> const &exclusions)
299{
300 std::vector<std::string> ret;
301 get_foldernames_from_path(ret, get_path_string(USER, type), exclusions);
302 get_foldernames_from_path(ret, get_path_string(SHARED, type), exclusions);
303 get_foldernames_from_path(ret, get_path_string(SYSTEM, type), exclusions);
304 get_foldernames_from_path(ret, get_path_string(CREATE, type), exclusions);
305 return ret;
306}
307
308std::vector<std::string> get_foldernames(Domain domain, Type type, std::vector<const char *> const &exclusions)
309{
310 std::vector<std::string> ret;
311 get_foldernames_from_path(ret, get_path_string(domain, type), exclusions);
312 return ret;
313}
314
315std::vector<std::string> get_foldernames(std::string const &path, std::vector<const char *> const &exclusions)
316{
317 std::vector<std::string> ret;
318 get_foldernames_from_path(ret, path, exclusions);
319 return ret;
320}
321
322
323/*
324 * Get all the files from a specific path and any sub-dirs, populating &files vector
325 *
326 * &files - Output list to populate, will be populated with full paths
327 * path - The directory to parse, will add nothing if directory doesn't exist
328 * extensions - Only add files with these extensions, they must be duplicated
329 * exclusions - Exclude files that exactly match these names.
330 */
331void get_filenames_from_path(std::vector<std::string> &files, std::string const &path,
332 std::vector<const char *> const &extensions, std::vector<const char *> const &exclusions)
333{
334 if(!Glib::file_test(path, Glib::FileTest::IS_DIR)) {
335 return;
336 }
337
338 Glib::Dir dir(path);
339 std::string file = dir.read_name();
340 while (!file.empty()){
341 // If not extensions are specified, don't reject ANY files.
342 bool reject = !extensions.empty();
343
344 // Unreject any file which has one of the extensions.
345 for (auto &ext: extensions) {
346 reject ^= Glib::str_has_suffix(file, ext);
347 }
348
349 // Reject any file which matches the exclusions.
350 for (auto &exc: exclusions) {
351 reject |= Glib::str_has_prefix(file, exc);
352 }
353
354 // Reject any filename which isn't a regular file
355 auto filename = Glib::build_filename(path, file);
356
357 if(Glib::file_test(filename, Glib::FileTest::IS_DIR)) {
358 get_filenames_from_path(files, filename, extensions, exclusions);
359 } else if(Glib::file_test(filename, Glib::FileTest::IS_REGULAR) && !reject) {
360 files.push_back(Glib::filename_to_utf8(filename));
361 }
362 file = dir.read_name();
363 }
364}
365
366/*
367 * Get all the files from a specific path and any sub-dirs, populating &files vector
368 *
369 * &folders - Output list to populate, will be poulated with full paths
370 * path - The directory to parse, will add nothing if directory doesn't exist
371 * exclusions - Exclude files that exactly match these names.
372 */
373void get_foldernames_from_path(std::vector<std::string> &folders, std::string const &path,
374 std::vector<const char *> const &exclusions)
375{
376 if (!Glib::file_test(path, Glib::FileTest::IS_DIR)) {
377 return;
378 }
379
380 Glib::Dir dir(path);
381 std::string file = dir.read_name();
382 while (!file.empty()) {
383 // If not extensions are specified, don't reject ANY files.
384 bool reject = false;
385
386 // Reject any file which matches the exclusions.
387 for (auto &exc : exclusions) {
388 reject |= Glib::str_has_prefix(file, exc);
389 }
390
391 // Reject any filename which isn't a regular file
392 auto filename = Glib::build_filename(path, file);
393
394 if (Glib::file_test(filename, Glib::FileTest::IS_DIR) && !reject) {
395 folders.push_back(Glib::filename_to_utf8(filename));
396 }
397 file = dir.read_name();
398 }
399}
400
401
407std::string profile_path(const char *filename)
408{
409 if (profile_path().empty()) {
410 return std::string("");
411 }
412 return Glib::build_filename(profile_path(), filename);
413}
414
415std::string profile_path()
416{
417
418 static std::string prefdir = "";
419
420 if (prefdir.empty()) {
421 // Check if profile directory is overridden using environment variable
422 prefdir = Glib::getenv("INKSCAPE_PROFILE_DIR");
423#ifdef _WIN32
424 // prefer c:\Documents and Settings\UserName\Application Data\ to c:\Documents and Settings\userName\;
425 // TODO: CSIDL_APPDATA is C:\Users\UserName\AppData\Roaming these days
426 // should we migrate to AppData\Local? Then we can simply use the portable g_get_user_config_dir()
427 if (prefdir.empty()) {
428 ITEMIDLIST *pidl = 0;
429 if ( SHGetFolderLocation( NULL, CSIDL_APPDATA, NULL, 0, &pidl ) == S_OK ) {
430 gchar * utf8Path = NULL;
431
432 {
433 wchar_t pathBuf[MAX_PATH+1];
434 g_assert(sizeof(wchar_t) == sizeof(gunichar2));
435
436 if ( SHGetPathFromIDListW( pidl, pathBuf ) ) {
437 utf8Path = g_utf16_to_utf8( (gunichar2*)(&pathBuf[0]), -1, NULL, NULL, NULL );
438 }
439 }
440
441 if ( utf8Path ) {
442 if (!g_utf8_validate(utf8Path, -1, NULL)) {
443 g_warning( "SHGetPathFromIDListW() resulted in invalid UTF-8");
444 g_free( utf8Path );
445 utf8Path = 0;
446 } else {
447 prefdir = utf8Path;
448 }
449 }
450 }
451
452 if (!prefdir.empty()) {
453 prefdir = Glib::build_filename(prefdir, INKSCAPE_PROFILE_DIR);
454 }
455 }
456#endif
457 if (prefdir.empty()) {
458 prefdir = Glib::build_filename(get_user_config_dir(), INKSCAPE_PROFILE_DIR);
459 // In case the XDG user config dir of the moment does not yet exist...
460 int mode = S_IRWXU;
461#ifdef S_IRGRP
462 mode |= S_IRGRP;
463#endif
464#ifdef S_IXGRP
465 mode |= S_IXGRP;
466#endif
467#ifdef S_IXOTH
468 mode |= S_IXOTH;
469#endif
470 if (g_mkdir_with_parents(prefdir.c_str(), mode) == -1 ) {
471 int problem = errno;
472 g_warning("Unable to create profile directory (%s) (%d)", g_strerror(problem), problem);
473 } else {
474 gchar const *userDirs[] = { "keys", "templates", "icons", "extensions", "ui",
475 "symbols", "paint", "themes", "palettes", "fontcollections", nullptr };
476 for (gchar const** name = userDirs; *name; ++name) {
477 gchar *dir = g_build_filename(prefdir.c_str(), *name, nullptr);
478 g_mkdir_with_parents(dir, mode);
479 g_free(dir);
480 }
481 }
482 }
483 }
484 return prefdir;
485}
486
487std::string shared_path(const char *filename)
488{
489 return shared_path().empty() ? shared_path() : Glib::build_filename(shared_path(), filename);
490}
491
492std::string shared_path()
493{
496 std::string shared_dir = prefs->getString("/options/resources/sharedpath");
497 if (!shared_dir.empty() && Glib::file_test(shared_dir, Glib::FileTest::IS_DIR)) {
498 return shared_dir;
499 }
500 }
501 return std::string("");
502}
503
504/*
505 * We return the profile_path because that is where most documentation
506 * days log files will be generated in inkscape 0.92
507 */
508std::string log_path(const char *filename)
509{
510 return profile_path(filename);
511}
512
513std::string homedir_path()
514{
515 return g_get_home_dir();
516}
517
518} // namespace Inkscape::IO::Resource
519
520/*
521 Local Variables:
522 mode:c++
523 c-file-style:"stroustrup"
524 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
525 indent-tabs-mode:nil
526 fill-column:99
527 End:
528*/
529// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
static InkscapeApplication * instance()
Singleton instance.
Preference storage class.
Definition preferences.h:66
Glib::ustring getString(Glib::ustring const &pref_path, Glib::ustring const &def="")
Retrieve an UTF-8 string.
static Preferences * get()
Access the singleton Preferences object.
Css & result
Util::ptr_shared get_path(Domain domain, Type type, char const *filename, char const *extra)
Definition resource.cpp:137
void get_foldernames_from_path(std::vector< std::string > &folders, std::string const &path, std::vector< const char * > const &exclusions)
Definition resource.cpp:373
std::string get_path_string(Domain domain, Type type, char const *filename, char const *extra)
Definition resource.cpp:148
std::string homedir_path()
Definition resource.cpp:513
gchar * _get_path(Domain domain, Type type, char const *filename, char const *extra=nullptr)
Definition resource.cpp:47
std::string shared_path()
Definition resource.cpp:492
std::string get_filename(Type type, char const *filename, bool localized, bool silent)
Definition resource.cpp:170
std::vector< std::string > get_foldernames(Type type, std::vector< const char * > const &exclusions)
Definition resource.cpp:298
void get_filenames_from_path(std::vector< std::string > &files, std::string const &path, std::vector< const char * > const &extensions, std::vector< const char * > const &exclusions)
Definition resource.cpp:331
std::string profile_path()
Definition resource.cpp:415
std::vector< std::string > get_filenames(Type type, std::vector< const char * > const &extensions, std::vector< const char * > const &exclusions)
Definition resource.cpp:267
std::string log_path(const char *filename)
Definition resource.cpp:508
bool file_test(char const *utf8name, GFileTest test)
Definition sys.cpp:116
ptr_shared share_string(char const *string)
Definition share.cpp:20
int mode
char const * get_inkscape_datadir()
Determine the location of the Inkscape data directory (typically the share/ folder from where Inkscap...
const char * get_user_config_dir()
Get the user configuration directory.
TODO: insert short description here.
Singleton class to access the preferences file in a convenient way.
Inkscape::IO::Resource - simple resource API.
Glib::ustring name
Definition toolbars.cpp:55