summaryrefslogtreecommitdiffstats
path: root/src/io/resource.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/io/resource.cpp')
-rw-r--r--src/io/resource.cpp559
1 files changed, 559 insertions, 0 deletions
diff --git a/src/io/resource.cpp b/src/io/resource.cpp
new file mode 100644
index 0000000..aa8af77
--- /dev/null
+++ b/src/io/resource.cpp
@@ -0,0 +1,559 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * Inkscape::IO::Resource - simple resource API
+ *//*
+ * Authors:
+ * MenTaLguY <mental@rydia.net>
+ * Martin Owens <doctormo@gmail.com>
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h" // only include where actually required!
+#endif
+
+#ifdef _WIN32
+#include <shlobj.h> // for SHGetSpecialFolderLocation
+#endif
+
+#include <glibmm/convert.h>
+#include <glibmm/i18n.h>
+#include <glibmm/miscutils.h>
+#include <glibmm/stringutils.h>
+#include <glibmm/fileutils.h>
+
+#include "path-prefix.h"
+#include "io/sys.h"
+#include "io/resource.h"
+#include "inkscape-application.h"
+#include "preferences.h"
+
+using Inkscape::IO::file_test;
+
+namespace Inkscape {
+
+namespace IO {
+
+namespace Resource {
+
+
+static void get_foldernames_from_path(std::vector<Glib::ustring> &folders, std::string const &path,
+ std::vector<const char *> const &exclusions = {});
+
+#define INKSCAPE_PROFILE_DIR "inkscape"
+
+gchar *_get_path(Domain domain, Type type, char const *filename)
+{
+ if (domain == USER || domain == SHARED) {
+ switch (type) {
+ case ATTRIBUTES:
+ case EXAMPLES:
+ case DOCS:
+ case SCREENS:
+ case TUTORIALS:
+ // Happens for example with `get_filename_string(SCREENS, ...)`
+ // but we don't want a user configurable about screen.
+ return nullptr;
+ }
+ }
+
+ char const *name = nullptr;
+ char const *sysdir = nullptr;
+ char const *envor = nullptr;
+
+ switch (domain) {
+ case CREATE: {
+ sysdir = "create";
+ switch (type) {
+ case PAINT: name = "paint"; break;
+ case PALETTES: name = "swatches"; break;
+ default: return nullptr;
+ }
+ } break;
+ case CACHE: {
+ g_assert(type == NONE);
+ return g_build_filename(g_get_user_cache_dir(), "inkscape", filename, nullptr);
+ } break;
+
+ case SYSTEM:
+ sysdir = "inkscape";
+ case SHARED:
+ case USER: {
+ switch (type) {
+ case ATTRIBUTES: name = "attributes"; break;
+ case DOCS: name = "doc"; break;
+ case EXAMPLES: name = "examples"; break;
+ case EXTENSIONS: name = "extensions"; envor = "INKSCAPE_EXTENSIONS_DIR"; break;
+ case FILTERS: name = "filters"; break;
+ case FONTS: name = "fonts"; break;
+ case FONTCOLLECTIONS: name = "fontcollections"; break;
+ case ICONS: name = "icons"; break;
+ case KEYS: name = "keys"; break;
+ case MARKERS: name = "markers"; break;
+ case PAINT: name = "paint"; break;
+ case PALETTES: name = "palettes"; break;
+ case SCREENS: name = "screens"; break;
+ case SYMBOLS: name = "symbols"; break;
+ case TEMPLATES: name = "templates"; break;
+ case THEMES: name = "themes"; break;
+ case TUTORIALS: name = "tutorials"; break;
+ case UIS: name = "ui"; break;
+ case PIXMAPS: name = "pixmaps"; break;
+ default: g_assert_not_reached();
+ return nullptr;
+ }
+ } break;
+ }
+ // Look for an over-ride in the local environment
+ if (envor && domain == USER) {
+ std::string env_dir = Glib::getenv(envor);
+ if (!env_dir.empty()) {
+ return g_build_filename(env_dir.c_str(), filename, nullptr);
+ }
+ }
+
+ if (!name) {
+ return nullptr;
+ }
+
+ if (sysdir) {
+ return g_build_filename(get_inkscape_datadir(), sysdir, name, filename, nullptr);
+ } else if (domain == SHARED) {
+ if (shared_path().empty()) {
+ return nullptr;
+ }
+ return g_build_filename(shared_path().c_str(), name, filename, nullptr);
+ } else {
+ if (profile_path().empty()) {
+ return nullptr;
+ }
+ return g_build_filename(profile_path().c_str(), name, filename, nullptr);
+ }
+}
+
+
+
+Util::ptr_shared get_path(Domain domain, Type type, char const *filename)
+{
+ char *path = _get_path(domain, type, filename);
+ if (!path) {
+ return Util::ptr_shared();
+ }
+ Util::ptr_shared result=Util::share_string(path);
+ g_free(path);
+ return result;
+}
+Glib::ustring get_path_ustring(Domain domain, Type type, char const *filename)
+{
+ Glib::ustring result;
+ char *path = _get_path(domain, type, filename);
+ if(path) {
+ result = Glib::ustring(path);
+ g_free(path);
+ }
+ return result;
+}
+std::string get_path_string(Domain domain, Type type, char const *filename)
+{
+ std::string result;
+ char *path = _get_path(domain, type, filename);
+ if (path) {
+ result = path;
+ g_free(path);
+ }
+ return result;
+}
+
+/*
+ * Same as get_path, but checks for file's existence and falls back
+ * from USER to SYSTEM modes.
+ *
+ * type - The type of file to get, such as extension, template, ui etc
+ * filename - The filename to get, i.e. preferences.xml
+ * localized - Prefer a localized version of the file, i.e. default.de.svg instead of default.svg.
+ * (will use gettext to determine the preferred language of the user)
+ * silent - do not warn if file doesn't exist
+ *
+ */
+Glib::ustring get_filename(Type type, char const *filename, bool localized, bool silent)
+{
+ return get_filename_string(type, filename, localized, silent);
+}
+
+std::string get_filename_string(Type type, char const *filename, bool localized, bool silent)
+{
+ std::string result;
+
+ char *user_filename = nullptr;
+ char *shared_filename = nullptr;
+ char *sys_filename = nullptr;
+ char *user_filename_localized = nullptr;
+ char *sys_filename_localized = nullptr;
+
+ // TRANSLATORS: 'en' is an ISO 639-1 language code.
+ // Replace with language code for your language, i.e. the name of your .po file
+ localized = localized && strcmp(_("en"), "en");
+
+ if (localized) {
+ std::string localized_filename = filename;
+ localized_filename.insert(localized_filename.rfind('.'), ".");
+ localized_filename.insert(localized_filename.rfind('.'), _("en"));
+
+ user_filename_localized = _get_path(USER, type, localized_filename.c_str());
+ sys_filename_localized = _get_path(SYSTEM, type, localized_filename.c_str());
+ }
+ user_filename = _get_path(USER, type, filename);
+ shared_filename = _get_path(SHARED, type, filename);
+ sys_filename = _get_path(SYSTEM, type, filename);
+
+ // impose the following load order:
+ // USER (localized) > USER > SYSTEM (localized) > SYSTEM
+ if (localized && file_test(user_filename_localized, G_FILE_TEST_EXISTS)) {
+ result = user_filename_localized;
+ g_info("Found localized version of resource file '%s' in profile directory:\n\t%s", filename, result.c_str());
+ } else if (file_test(user_filename, G_FILE_TEST_EXISTS)) {
+ result = user_filename;
+ g_info("Found resource file '%s' in profile directory:\n\t%s", filename, result.c_str());
+ } else if (file_test(shared_filename, G_FILE_TEST_EXISTS)) {
+ result = shared_filename;
+ g_info("Found resource file '%s' in profile directory:\n\t%s", filename, result.c_str());
+ } else if (localized && file_test(sys_filename_localized, G_FILE_TEST_EXISTS)) {
+ result = sys_filename_localized;
+ g_info("Found localized version of resource file '%s' in system directory:\n\t%s", filename, result.c_str());
+ } else if (file_test(sys_filename, G_FILE_TEST_EXISTS)) {
+ result = sys_filename;
+ g_info("Found resource file '%s' in system directory:\n\t%s", filename, result.c_str());
+ } else if (!silent) {
+ if (localized) {
+ 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",
+ filename, user_filename_localized, user_filename, shared_filename, sys_filename_localized, sys_filename);
+ } else {
+ g_warning("Failed to find resource file '%s'. Looked in:\n\t%s\n\t%s\n\t%s",
+ filename, user_filename, shared_filename, sys_filename);
+ }
+ }
+
+ g_free(user_filename);
+ g_free(shared_filename);
+ g_free(sys_filename);
+ g_free(user_filename_localized);
+ g_free(sys_filename_localized);
+
+ return result;
+}
+
+/*
+ * Similar to get_filename, but takes a path (or filename) for relative resolution
+ *
+ * path - A directory or filename that is considered local to the path resolution.
+ * filename - The filename that we are looking for.
+ */
+Glib::ustring get_filename(Glib::ustring path, Glib::ustring filename)
+{
+ return get_filename(Glib::filename_from_utf8(path), //
+ Glib::filename_from_utf8(filename));
+}
+
+std::string get_filename(std::string const& path, std::string const& filename)
+{
+ // Test if it's a filename and get the parent directory instead
+ if (Glib::file_test(path, Glib::FILE_TEST_IS_REGULAR)) {
+ auto dirname = Glib::path_get_dirname(path);
+ g_assert(!Glib::file_test(dirname, Glib::FILE_TEST_IS_REGULAR)); // recursion sanity check
+ return get_filename(dirname, filename);
+ }
+ if (g_path_is_absolute(filename.c_str())) {
+ if (Glib::file_test(filename, Glib::FILE_TEST_EXISTS)) {
+ return filename;
+ }
+ } else {
+ auto ret = Glib::build_filename(path, filename);
+ if (Glib::file_test(ret, Glib::FILE_TEST_EXISTS)) {
+ return ret;
+ }
+ }
+ return {};
+}
+
+/*
+ * Gets all the files in a given type, for all domain types.
+ *
+ * domain - Optional domain (overload), will check return domains if not.
+ * type - The type of files, e.g. TEMPLATES
+ * extensions - A list of extensions to return, e.g. xml, svg
+ * exclusions - A list of names to exclude e.g. default.xml
+ */
+std::vector<Glib::ustring> get_filenames(Type type, std::vector<const char *> const &extensions, std::vector<const char *> const &exclusions)
+{
+ std::vector<Glib::ustring> ret;
+ get_filenames_from_path(ret, get_path_string(USER, type), extensions, exclusions);
+ get_filenames_from_path(ret, get_path_string(SHARED, type), extensions, exclusions);
+ get_filenames_from_path(ret, get_path_string(SYSTEM, type), extensions, exclusions);
+ get_filenames_from_path(ret, get_path_string(CREATE, type), extensions, exclusions);
+ return ret;
+}
+
+std::vector<Glib::ustring> get_filenames(Domain domain, Type type, std::vector<const char *> const &extensions, std::vector<const char *> const &exclusions)
+{
+ std::vector<Glib::ustring> ret;
+ get_filenames_from_path(ret, get_path_string(domain, type), extensions, exclusions);
+ return ret;
+}
+std::vector<Glib::ustring> get_filenames(Glib::ustring path, std::vector<const char *> const &extensions, std::vector<const char *> const &exclusions)
+{
+ std::vector<Glib::ustring> ret;
+ get_filenames_from_path(ret, Glib::filename_from_utf8(path), extensions, exclusions);
+ return ret;
+}
+
+/*
+ * Gets all folders inside each type, for all domain types.
+ *
+ * domain - Optional domain (overload), will check return domains if not.
+ * type - The type of files, e.g. TEMPLATES
+ * extensions - A list of extensions to return, e.g. xml, svg
+ * exclusions - A list of names to exclude e.g. default.xml
+ */
+std::vector<Glib::ustring> get_foldernames(Type type, std::vector<const char *> const &exclusions)
+{
+ std::vector<Glib::ustring> ret;
+ get_foldernames_from_path(ret, get_path_ustring(USER, type), exclusions);
+ get_foldernames_from_path(ret, get_path_ustring(SHARED, type), exclusions);
+ get_foldernames_from_path(ret, get_path_ustring(SYSTEM, type), exclusions);
+ get_foldernames_from_path(ret, get_path_ustring(CREATE, type), exclusions);
+ return ret;
+}
+
+std::vector<Glib::ustring> get_foldernames(Domain domain, Type type, std::vector<const char *> const &exclusions)
+{
+ std::vector<Glib::ustring> ret;
+ get_foldernames_from_path(ret, get_path_ustring(domain, type), exclusions);
+ return ret;
+}
+std::vector<Glib::ustring> get_foldernames(Glib::ustring path, std::vector<const char *> const &exclusions)
+{
+ std::vector<Glib::ustring> ret;
+ get_foldernames_from_path(ret, path, exclusions);
+ return ret;
+}
+
+
+/*
+ * Get all the files from a specific path and any sub-dirs, populating &files vector
+ *
+ * &files - Output list to populate, will be populated with full paths
+ * path - The directory to parse, will add nothing if directory doesn't exist
+ * extensions - Only add files with these extensions, they must be duplicated
+ * exclusions - Exclude files that exactly match these names.
+ */
+void get_filenames_from_path(std::vector<Glib::ustring> &files, std::string const &path,
+ std::vector<const char *> const &extensions, std::vector<const char *> const &exclusions)
+{
+ if(!Glib::file_test(path, Glib::FILE_TEST_IS_DIR)) {
+ return;
+ }
+
+ Glib::Dir dir(path);
+ std::string file = dir.read_name();
+ while (!file.empty()){
+ // If not extensions are specified, don't reject ANY files.
+ bool reject = !extensions.empty();
+
+ // Unreject any file which has one of the extensions.
+ for (auto &ext: extensions) {
+ reject ^= Glib::str_has_suffix(file, ext);
+ }
+
+ // Reject any file which matches the exclusions.
+ for (auto &exc: exclusions) {
+ reject |= Glib::str_has_prefix(file, exc);
+ }
+
+ // Reject any filename which isn't a regular file
+ auto filename = Glib::build_filename(path, file);
+
+ if(Glib::file_test(filename, Glib::FILE_TEST_IS_DIR)) {
+ get_filenames_from_path(files, filename, extensions, exclusions);
+ } else if(Glib::file_test(filename, Glib::FILE_TEST_IS_REGULAR) && !reject) {
+ files.push_back(Glib::filename_to_utf8(filename));
+ }
+ file = dir.read_name();
+ }
+}
+
+/*
+ * Get all the files from a specific path and any sub-dirs, populating &files vector
+ *
+ * &folders - Output list to populate, will be poulated with full paths
+ * path - The directory to parse, will add nothing if directory doesn't exist
+ * exclusions - Exclude files that exactly match these names.
+ */
+void get_foldernames_from_path(std::vector<Glib::ustring> &folders, Glib::ustring path,
+ std::vector<const char *> exclusions)
+{
+ get_foldernames_from_path(folders, Glib::filename_from_utf8(path), exclusions);
+}
+
+void get_foldernames_from_path(std::vector<Glib::ustring> &folders, std::string const &path,
+ std::vector<const char *> const &exclusions)
+{
+ if (!Glib::file_test(path, Glib::FILE_TEST_IS_DIR)) {
+ return;
+ }
+
+ Glib::Dir dir(path);
+ std::string file = dir.read_name();
+ while (!file.empty()) {
+ // If not extensions are specified, don't reject ANY files.
+ bool reject = false;
+
+ // Reject any file which matches the exclusions.
+ for (auto &exc : exclusions) {
+ reject |= Glib::str_has_prefix(file, exc);
+ }
+
+ // Reject any filename which isn't a regular file
+ auto filename = Glib::build_filename(path, file);
+
+ if (Glib::file_test(filename, Glib::FILE_TEST_IS_DIR) && !reject) {
+ folders.push_back(Glib::filename_to_utf8(filename));
+ }
+ file = dir.read_name();
+ }
+}
+
+
+/**
+ * Get, or guess, or decide the location where the preferences.xml
+ * file should be located. This also indicates where all other inkscape
+ * shared files may optionally exist.
+ */
+std::string profile_path(const char *filename)
+{
+ if (profile_path().empty()) {
+ return std::string("");
+ }
+ return Glib::build_filename(profile_path(), filename);
+}
+
+std::string profile_path()
+{
+
+ static std::string prefdir = "";
+
+ if (prefdir.empty()) {
+ // Check if profile directory is overridden using environment variable
+ prefdir = Glib::getenv("INKSCAPE_PROFILE_DIR");
+#ifdef _WIN32
+ // prefer c:\Documents and Settings\UserName\Application Data\ to c:\Documents and Settings\userName\;
+ // TODO: CSIDL_APPDATA is C:\Users\UserName\AppData\Roaming these days
+ // should we migrate to AppData\Local? Then we can simply use the portable g_get_user_config_dir()
+ if (prefdir.empty()) {
+ ITEMIDLIST *pidl = 0;
+ if ( SHGetFolderLocation( NULL, CSIDL_APPDATA, NULL, 0, &pidl ) == S_OK ) {
+ gchar * utf8Path = NULL;
+
+ {
+ wchar_t pathBuf[MAX_PATH+1];
+ g_assert(sizeof(wchar_t) == sizeof(gunichar2));
+
+ if ( SHGetPathFromIDListW( pidl, pathBuf ) ) {
+ utf8Path = g_utf16_to_utf8( (gunichar2*)(&pathBuf[0]), -1, NULL, NULL, NULL );
+ }
+ }
+
+ if ( utf8Path ) {
+ if (!g_utf8_validate(utf8Path, -1, NULL)) {
+ g_warning( "SHGetPathFromIDListW() resulted in invalid UTF-8");
+ g_free( utf8Path );
+ utf8Path = 0;
+ } else {
+ prefdir = utf8Path;
+ }
+ }
+ }
+
+ if (!prefdir.empty()) {
+ prefdir = Glib::build_filename(prefdir, INKSCAPE_PROFILE_DIR);
+ }
+ }
+#endif
+ if (prefdir.empty()) {
+ prefdir = Glib::build_filename(get_user_config_dir(), INKSCAPE_PROFILE_DIR);
+ // In case the XDG user config dir of the moment does not yet exist...
+ int mode = S_IRWXU;
+#ifdef S_IRGRP
+ mode |= S_IRGRP;
+#endif
+#ifdef S_IXGRP
+ mode |= S_IXGRP;
+#endif
+#ifdef S_IXOTH
+ mode |= S_IXOTH;
+#endif
+ if (g_mkdir_with_parents(prefdir.c_str(), mode) == -1 ) {
+ int problem = errno;
+ g_warning("Unable to create profile directory (%s) (%d)", g_strerror(problem), problem);
+ } else {
+ gchar const *userDirs[] = { "keys", "templates", "icons", "extensions", "ui",
+ "symbols", "paint", "themes", "palettes", "fontcollections", nullptr };
+ for (gchar const** name = userDirs; *name; ++name) {
+ gchar *dir = g_build_filename(prefdir.c_str(), *name, nullptr);
+ g_mkdir_with_parents(dir, mode);
+ g_free(dir);
+ }
+ }
+ }
+ }
+ return prefdir;
+}
+
+std::string shared_path(const char *filename)
+{
+ return shared_path().empty() ? shared_path() : Glib::build_filename(shared_path(), filename);
+}
+
+std::string shared_path()
+{
+ if (InkscapeApplication::instance()) {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ std::string shared_dir = prefs->getString("/options/resources/sharedpath");
+ if (!shared_dir.empty() && Glib::file_test(shared_dir, Glib::FILE_TEST_IS_DIR)) {
+ return shared_dir;
+ }
+ }
+ return std::string("");
+}
+
+/*
+ * We return the profile_path because that is where most documentation
+ * days log files will be generated in inkscape 0.92
+ */
+std::string log_path(const char *filename)
+{
+ return profile_path(filename);
+}
+
+std::string homedir_path()
+{
+ return g_get_home_dir();
+}
+
+}
+
+}
+
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :