gnucash master: Multiple changes pushed

Geert Janssens gjanssens at code.gnucash.org
Sat Aug 26 16:44:35 EDT 2017


Updated	 via  https://github.com/Gnucash/gnucash/commit/bbea5061 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/2662f4bc (commit)
	 via  https://github.com/Gnucash/gnucash/commit/045ee429 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/a153412e (commit)
	 via  https://github.com/Gnucash/gnucash/commit/ae75bc96 (commit)
	from  https://github.com/Gnucash/gnucash/commit/20061559 (commit)



commit bbea5061a3d69f69fe4960ca8e6846e6f66ffe36
Author: Geert Janssens <geert at kobaltwit.be>
Date:   Sat Aug 26 20:42:53 2017 +0200

    Extend environment file parsing to also parse environment.local
    
    This latter file can be created by the administrator next to the environment
    file in order to override parameters set in environment file.
    The reason for this is the environment file is always overwritten
    during installation, while environment.local is never touched by
    the installers. So making the changes in there ensures they persist
    acros gnucash updates.

diff --git a/gnucash/environment.in b/gnucash/environment.in
index fe5f453..43e17ce 100644
--- a/gnucash/environment.in
+++ b/gnucash/environment.in
@@ -5,10 +5,20 @@
 @-NOTE - gnucash-on-windows.git:gnucash.iss don't conflict. -@
 # environment
 #
-# This configuration file can be used to change/add environment variables
+# This configuration file defines a number of environment variables to set/alter
 # during GnuCash startup.
 #
-# It uses a very simple syntax for this:
+# The contents of this file is overwritten with each gnucash update.
+# If you want to add your own variables to the environment or wish
+# to override what gets set in this file please create a new file
+# named "environment.local" next to this one with the same structure:
+#
+# [Variables]
+# VAR_X=value_x
+# ....
+#
+#
+# Both files use a very simple syntax:
 # <variable_name>=<variable_value>
 #
 # You can refer to existing environment variables in the new value by
@@ -61,8 +71,8 @@ LD_LIBRARY_PATH={GNC_LIBRARY_PATH};{LD_LIBRARY_PATH}
 # The same, but for GnuCash on OS X
 DYLD_LIBRARY_PATH={GNC_LIBRARY_PATH};{DYLD_LIBRARY_PATH}
 
-# If you wish GnuCash to use a different language, uncomment the two parameters
-# below and set LANG to your preferred locale
+# If you wish GnuCash to use a different language, copy the two parameters
+# below to environment.local, remove '# ' in front and set LANG to your preferred locale
 # LANG=nl_BE
 # LANGUAGE={LANG}
 
@@ -71,7 +81,7 @@ DYLD_LIBRARY_PATH={GNC_LIBRARY_PATH};{DYLD_LIBRARY_PATH}
 # Linux - follows the freedesktop xdg data home specification
 # Windows - uses the user's AppData(Roaming)\GnuCash
 # OS X/Quarz - uses the value of NSApplicationSupportDirectory/GnuCash
-# If these are not what you want, you can override it here
+# If these are not what you want, you can override it in enviromnent.local
 # GNC_DATA_HOME=
 
 # Similarly you can override the default location for AqBanking's settings.
diff --git a/libgnucash/core-utils/gnc-environment.c b/libgnucash/core-utils/gnc-environment.c
index 06b2055..69be489 100644
--- a/libgnucash/core-utils/gnc-environment.c
+++ b/libgnucash/core-utils/gnc-environment.c
@@ -96,60 +96,17 @@ static gchar  *environment_expand(gchar *param)
     return expanded;
 }
 
-void
-gnc_environment_setup (void)
+static void
+gnc_environment_parse_one (const gchar *env_path)
 {
-    gchar *config_path;
-    gchar *env_file;
     GKeyFile    *keyfile = g_key_file_new();
     GError      *error = NULL;
     gchar **env_vars;
     gsize param_count;
     gint i;
     gboolean got_keyfile;
-    gchar *env_parm;
 
-    /* Export default parameters to the environment */
-    env_parm = gnc_path_get_prefix();
-    if (!g_setenv("GNC_HOME", env_parm, FALSE))
-        g_warning ("Couldn't set/override environment variable GNC_HOME.");
-    g_free (env_parm);
-    env_parm = gnc_path_get_bindir();
-    if (!g_setenv("GNC_BIN", env_parm, FALSE))
-        g_warning ("Couldn't set/override environment variable GNC_BIN.");
-    g_free (env_parm);
-    env_parm = gnc_path_get_pkglibdir();
-    if (!g_setenv("GNC_LIB", env_parm, FALSE))
-        g_warning ("Couldn't set/override environment variable GNC_LIB.");
-    g_free (env_parm);
-    env_parm = gnc_path_get_pkgdatadir();
-    if (!g_setenv("GNC_DATA", env_parm, FALSE))
-        g_warning ("Couldn't set/override environment variable GNC_DATA.");
-    g_free (env_parm);
-    env_parm = gnc_path_get_pkgsysconfdir();
-    if (!g_setenv("GNC_CONF", env_parm, FALSE))
-        g_warning ("Couldn't set/override environment variable GNC_CONF.");
-    g_free (env_parm);
-    env_parm = gnc_path_get_libdir();
-    if (!g_setenv("SYS_LIB", env_parm, FALSE))
-        g_warning ("Couldn't set/override environment variable SYS_LIB.");
-    g_free (env_parm);
-
-    config_path = gnc_path_get_pkgsysconfdir();
-#ifdef G_OS_WIN32
-    {
-        /* unhide files without extension */
-        gchar *pathext = g_build_path(";", ".", g_getenv("PATHEXT"),
-                                      (gchar*) NULL);
-        g_setenv("PATHEXT", pathext, TRUE);
-        g_free(pathext);
-    }
-#endif
-
-    env_file = g_build_filename (config_path, "environment", NULL);
-    got_keyfile = g_key_file_load_from_file (keyfile, env_file, G_KEY_FILE_NONE, &error);
-    g_free (config_path);
-    g_free (env_file);
+    got_keyfile = g_key_file_load_from_file (keyfile, env_path, G_KEY_FILE_NONE, &error);
     if ( !got_keyfile )
     {
         g_key_file_free(keyfile);
@@ -195,7 +152,7 @@ gnc_environment_setup (void)
 
             if (!g_setenv (env_vars[i], new_val, TRUE))
                 g_warning ("Couldn't properly override environment variable \"%s\". "
-                           "This may lead to unexpected results", env_vars[i]);
+                "This may lead to unexpected results", env_vars[i]);
             g_free(new_val);
         }
     }
@@ -203,3 +160,59 @@ gnc_environment_setup (void)
     g_strfreev(env_vars);
     g_key_file_free(keyfile);
 }
+
+void
+gnc_environment_setup (void)
+{
+    gchar *config_path;
+    gchar *env_path;
+    gchar *env_parm;
+
+    /* Export default parameters to the environment */
+    env_parm = gnc_path_get_prefix();
+    if (!g_setenv("GNC_HOME", env_parm, FALSE))
+        g_warning ("Couldn't set/override environment variable GNC_HOME.");
+    g_free (env_parm);
+    env_parm = gnc_path_get_bindir();
+    if (!g_setenv("GNC_BIN", env_parm, FALSE))
+        g_warning ("Couldn't set/override environment variable GNC_BIN.");
+    g_free (env_parm);
+    env_parm = gnc_path_get_pkglibdir();
+    if (!g_setenv("GNC_LIB", env_parm, FALSE))
+        g_warning ("Couldn't set/override environment variable GNC_LIB.");
+    g_free (env_parm);
+    env_parm = gnc_path_get_pkgdatadir();
+    if (!g_setenv("GNC_DATA", env_parm, FALSE))
+        g_warning ("Couldn't set/override environment variable GNC_DATA.");
+    g_free (env_parm);
+    env_parm = gnc_path_get_pkgsysconfdir();
+    if (!g_setenv("GNC_CONF", env_parm, FALSE))
+        g_warning ("Couldn't set/override environment variable GNC_CONF.");
+    g_free (env_parm);
+    env_parm = gnc_path_get_libdir();
+    if (!g_setenv("SYS_LIB", env_parm, FALSE))
+        g_warning ("Couldn't set/override environment variable SYS_LIB.");
+    g_free (env_parm);
+
+    config_path = gnc_path_get_pkgsysconfdir();
+#ifdef G_OS_WIN32
+    {
+        /* unhide files without extension */
+        gchar *pathext = g_build_path(";", ".", g_getenv("PATHEXT"),
+                                      (gchar*) NULL);
+        g_setenv("PATHEXT", pathext, TRUE);
+        g_free(pathext);
+    }
+#endif
+
+    /* Parse the environment file that got installed with gnucash */
+    env_path = g_build_filename (config_path, "environment", NULL);
+    gnc_environment_parse_one(env_path);
+    g_free (env_path);
+
+    /* Parse local overrides for this file */
+    env_path = g_build_filename (config_path, "environment.local", NULL);
+    gnc_environment_parse_one(env_path);
+    g_free (env_path);
+    g_free (config_path);
+}

commit 2662f4bca3ce05edc77e49e210245cd6aaa376b3
Author: Geert Janssens <geert at kobaltwit.be>
Date:   Sat Aug 26 19:49:36 2017 +0200

    Mention GNC_DATA_HOME and AQBANKING_HOME in the environment file.

diff --git a/gnucash/environment.in b/gnucash/environment.in
index 69a805d..fe5f453 100644
--- a/gnucash/environment.in
+++ b/gnucash/environment.in
@@ -65,3 +65,16 @@ DYLD_LIBRARY_PATH={GNC_LIBRARY_PATH};{DYLD_LIBRARY_PATH}
 # below and set LANG to your preferred locale
 # LANG=nl_BE
 # LANGUAGE={LANG}
+
+# Set the location of the gnucash user specific application data
+# The default depends on the operating system
+# Linux - follows the freedesktop xdg data home specification
+# Windows - uses the user's AppData(Roaming)\GnuCash
+# OS X/Quarz - uses the value of NSApplicationSupportDirectory/GnuCash
+# If these are not what you want, you can override it here
+# GNC_DATA_HOME=
+
+# Similarly you can override the default location for AqBanking's settings.
+# This will only be used if gnucash is built with AqBanking support enabled.
+# Default: $HOME/.aqbanking
+# AQBANKING_HOME=

commit 045ee429bff37ea03dc18a8c8de5350972aa8f0d
Author: Geert Janssens <geert at kobaltwit.be>
Date:   Sat Aug 26 19:43:05 2017 +0200

    Check GNC_DATA_HOME environment to override default userdata location.
    
    This replaces GNC_DOT_DIR as it was called in older gnucash releases.

diff --git a/libgnucash/core-utils/gnc-filepath-utils.cpp b/libgnucash/core-utils/gnc-filepath-utils.cpp
index 4b15ea8..efc2440 100644
--- a/libgnucash/core-utils/gnc-filepath-utils.cpp
+++ b/libgnucash/core-utils/gnc-filepath-utils.cpp
@@ -489,34 +489,58 @@ get_userdata_home(bool create)
 gboolean
 gnc_filepath_init(gboolean create)
 {
-    auto userdata_home = get_userdata_home(create);
-
-    if (userdata_is_home)
+    auto gnc_userdata_home_exists = false;
+    auto gnc_userdata_home_from_env = false;
+    auto gnc_userdata_home_env = g_getenv("GNC_DATA_HOME");
+    if (gnc_userdata_home_env)
     {
-        /* If we get here that means the platform
-         * dependent gnc_userdata_home is not available for
-         * some reason. If legacy .gnucash directory still exists,
-         * use it as first fallback, but never create it (we want
-         * it to go away eventually).
-         * If missing, fall back to tmp_dir instead */
-        gnc_userdata_home = userdata_home / ".gnucash";
-        if (!bfs::exists(gnc_userdata_home))
+        gnc_userdata_home = bfs::path(gnc_userdata_home_env);
+        try
         {
-            userdata_home = g_get_tmp_dir();
-            userdata_is_home = false;
+            gnc_userdata_home_exists = bfs::exists(gnc_userdata_home);
+            if (!gnc_validate_directory(gnc_userdata_home, create))
+                throw (bfs::filesystem_error(
+                    std::string(_("Directory doesn't exist: "))
+                    + userdata_home.c_str(), userdata_home,
+                    bst::error_code(bst::errc::permission_denied, bst::generic_category())));
+            gnc_userdata_home_from_env = true;
+        }
+        catch (const bfs::filesystem_error& ex)
+        {
+            g_warning("%s (from environment variable 'GNC_DATA_HOME') is not a suitable directory for the user data."
+            "Trying the default instead.\nThe failure is\n%s",
+            gnc_userdata_home.c_str(), ex.what());
         }
     }
 
-    if (!userdata_is_home)
-        gnc_userdata_home = userdata_home / PACKAGE_NAME;
+    if (!gnc_userdata_home_from_env)
+    {
+        auto userdata_home = get_userdata_home(create);
+        if (userdata_is_home)
+        {
+            /* If we get here that means the platform
+            * dependent gnc_userdata_home is not available for
+            * some reason. If legacy .gnucash directory still exists,
+            * use it as first fallback, but never create it (we want
+            * it to go away eventually).
+            * If missing, fall back to tmp_dir instead */
+            gnc_userdata_home = userdata_home / ".gnucash";
+            if (!bfs::exists(gnc_userdata_home))
+            {
+                userdata_home = g_get_tmp_dir();
+                userdata_is_home = false;
+            }
+        }
+
+        if (!userdata_is_home)
+            gnc_userdata_home = userdata_home / PACKAGE_NAME;
+        gnc_userdata_home_exists = bfs::exists(gnc_userdata_home);
+        /* This may throw and end the program! */
+        gnc_validate_directory(gnc_userdata_home, create);
+    }
 
-    auto try_migrate = false;
-    if (!userdata_is_home && !bfs::exists(gnc_userdata_home) && create)
-        try_migrate = true;
-    /* This may throw and end the program! */
-    gnc_validate_directory(gnc_userdata_home, create);
     auto migrated = FALSE;
-    if (try_migrate)
+    if (!userdata_is_home && !gnc_userdata_home_exists && create)
         migrated = copy_recursive(bfs::path (g_get_home_dir()) / ".gnucash",
                                   gnc_userdata_home);
 
diff --git a/libgnucash/core-utils/test/test-userdata-dir.c b/libgnucash/core-utils/test/test-userdata-dir.c
index 68bd5e0..3197a0b 100644
--- a/libgnucash/core-utils/test/test-userdata-dir.c
+++ b/libgnucash/core-utils/test/test-userdata-dir.c
@@ -41,20 +41,16 @@ typedef struct usr_confpath_strings_struct usr_confpath_strings;
 usr_confpath_strings strs2[] =
 {
     {
-        0, "gnc_build_userdata_path",
-        PACKAGE_NAME
+        0, "gnc_build_userdata_path", ""
     },
     {
-        1, "gnc_build_book_path",
-        PACKAGE_NAME G_DIR_SEPARATOR_S "books"
+        1, "gnc_build_book_path", "books"
     },
     {
-        2, "gnc_build_translog_path",
-        PACKAGE_NAME G_DIR_SEPARATOR_S "translog"
+        2, "gnc_build_translog_path", "translog"
     },
     {
-        3, "gnc_build_data_path",
-        PACKAGE_NAME G_DIR_SEPARATOR_S "data"
+        3, "gnc_build_data_path", "data"
     },
     { 0, NULL, NULL },
 };
@@ -66,6 +62,7 @@ main(int argc, char **argv)
     char *home_dir = NULL;
     const char *userdata_dir = NULL;
     const char *tmp_dir = g_get_tmp_dir();
+    char *gnc_data_home_dir = NULL;
 
     if (argc > 1)
         /* One can pass a homedir on the command line. This
@@ -89,25 +86,25 @@ main(int argc, char **argv)
 
         if (strs2[i].func_num == 0)
         {
-            wantout = g_build_filename(tmp_dir, strs2[i].output, "foo",
+            wantout = g_build_filename(tmp_dir, PACKAGE_NAME, "foo",
                                        (gchar *)NULL);
             daout = gnc_build_userdata_path("foo");
         }
         else if (strs2[i].func_num == 1)
         {
-            wantout = g_build_filename(tmp_dir, strs2[i].output, "foo",
+            wantout = g_build_filename(tmp_dir, PACKAGE_NAME, strs2[i].output, "foo",
                                        (gchar *)NULL);
             daout = gnc_build_book_path("foo");
         }
         else if (strs2[i].func_num == 2)
         {
-            wantout = g_build_filename(tmp_dir, strs2[i].output, "foo",
+            wantout = g_build_filename(tmp_dir, PACKAGE_NAME, strs2[i].output, "foo",
                                        (gchar *)NULL);
             daout = gnc_build_translog_path("foo");
         }
         else // if (strs2[i].prefix_home == 3)
         {
-            wantout = g_build_filename(tmp_dir, strs2[i].output, "foo",
+            wantout = g_build_filename(tmp_dir, PACKAGE_NAME, strs2[i].output, "foo",
                                        (gchar *)NULL);
             daout = gnc_build_data_path("foo");
         }
@@ -130,25 +127,67 @@ main(int argc, char **argv)
 
         if (strs2[i].func_num == 0)
         {
-            wantout = g_build_filename(userdata_dir, strs2[i].output, "foo",
+            wantout = g_build_filename(userdata_dir, PACKAGE_NAME, "foo",
                                        (gchar *)NULL);
             daout = gnc_build_userdata_path("foo");
         }
         else if (strs2[i].func_num == 1)
         {
-            wantout = g_build_filename(userdata_dir, strs2[i].output, "foo",
+            wantout = g_build_filename(userdata_dir, PACKAGE_NAME, strs2[i].output, "foo",
                                        (gchar *)NULL);
             daout = gnc_build_book_path("foo");
         }
         else if (strs2[i].func_num == 2)
         {
-            wantout = g_build_filename(userdata_dir, strs2[i].output, "foo",
+            wantout = g_build_filename(userdata_dir, PACKAGE_NAME, strs2[i].output, "foo",
                                        (gchar *)NULL);
             daout = gnc_build_translog_path("foo");
         }
         else // if (strs2[i].prefix_home == 3)
         {
-            wantout = g_build_filename(userdata_dir, strs2[i].output, "foo",
+            wantout = g_build_filename(userdata_dir, PACKAGE_NAME, strs2[i].output, "foo",
+                                       (gchar *)NULL);
+            daout = gnc_build_data_path("foo");
+        }
+
+        do_test_args(g_strcmp0(daout, wantout) == 0,
+                     "gnc_build_x_path",
+                     __FILE__, __LINE__,
+                     "%s (%s) vs %s", daout, strs2[i].funcname, wantout);
+        g_free(wantout);
+        g_free(daout);
+    }
+
+    /* Third run, after setting GNC_DATA_HOME gnc_filepath_init */
+    gnc_data_home_dir = g_build_filename(home_dir, "Test", NULL);
+    g_setenv("GNC_DATA_HOME", gnc_data_home_dir, TRUE);
+    gnc_filepath_init(TRUE);
+    for (i = 0; strs2[i].funcname != NULL; i++)
+    {
+        char *daout;
+        char *wantout;
+
+        if (strs2[i].func_num == 0)
+        {
+            wantout = g_build_filename(gnc_data_home_dir, "foo",
+                                       (gchar *)NULL);
+            daout = gnc_build_userdata_path("foo");
+        }
+        else if (strs2[i].func_num == 1)
+        {
+            wantout = g_build_filename(gnc_data_home_dir, strs2[i].output, "foo",
+                                       (gchar *)NULL);
+            daout = gnc_build_book_path("foo");
+        }
+        else if (strs2[i].func_num == 2)
+        {
+            wantout = g_build_filename(gnc_data_home_dir, strs2[i].output, "foo",
+                                       (gchar *)NULL);
+            daout = gnc_build_translog_path("foo");
+        }
+        else // if (strs2[i].prefix_home == 3)
+        {
+            wantout = g_build_filename(gnc_data_home_dir, strs2[i].output, "foo",
                                        (gchar *)NULL);
             daout = gnc_build_data_path("foo");
         }

commit a153412e5a8fca7519fadb62935bab797860fbcd
Author: Geert Janssens <geert at kobaltwit.be>
Date:   Wed Aug 23 17:42:54 2017 +0200

    Use platform dependent locations for gnucash user's data (gnc_userdata_home)
    
    - Linux: use whatever the xdg spec dictates
    - Windows: use AppData(Roaming)
    - OS X/Quarz: use NSApplicationSupportDirectory special directory, which
                  typically resolves to $HOME/Library/Application Support
    
    If the preferred directory can't be used the code will fallback to
    $HOME/.gnucash (the old location) if it exists. It won't create it
    however. Instead if missing it will fall back to the platform's
    temporary directory.
    
    Code is added also to automatically migrate all data from the old
    location to the new (only the first time).

diff --git a/gnucash/gnucash-bin.c b/gnucash/gnucash-bin.c
index 5bc7063..0b51a18 100644
--- a/gnucash/gnucash-bin.c
+++ b/gnucash/gnucash-bin.c
@@ -148,6 +148,9 @@ static GOptionEntry options[] =
     { NULL }
 };
 
+static gboolean userdata_migrated = FALSE;
+static gchar *userdata_migration_msg = NULL;
+
 static void
 gnc_print_unstable_message(void)
 {
@@ -658,6 +661,18 @@ inner_main (void *closure, int argc, char **argv)
         gnc_destroy_splash_screen();
         gnc_ui_new_user_dialog();
     }
+
+    if (userdata_migrated)
+    {
+        GtkWidget *dialog = gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL,
+                                                   GTK_MESSAGE_INFO,
+                                                   GTK_BUTTONS_OK,
+                                                   "%s",
+                                                   userdata_migration_msg);
+        gnc_destroy_splash_screen();
+        gtk_dialog_run(GTK_DIALOG(dialog));
+        gtk_widget_destroy (dialog);
+    }
     /* Ensure temporary preferences are temporary */
     gnc_prefs_reset_group (GNC_PREFS_GROUP_WARNINGS_TEMP);
 
@@ -777,10 +792,22 @@ main(int argc, char ** argv)
         g_free(localedir);
     }
 #endif
-    
+
     gnc_parse_command_line(&argc, &argv);
     gnc_print_unstable_message();
 
+    /* Make sure gnucash' user data directory is properly set up */
+    userdata_migrated = gnc_filepath_init(TRUE);
+    /* Translators: the message below will be completed with two directory names. */
+    userdata_migration_msg = g_strdup_printf (
+        _("Notice\n\nYour gnucash metadata has been migrated.\n\n"
+          "Old location: %s%s\n"
+          "New location: %s\n\n"
+          "If you no longer intend to run " PACKAGE_NAME " 2.6.x or older on this system you can safely remove the old directory."),
+        g_get_home_dir(), G_DIR_SEPARATOR_S ".gnucash", gnc_userdata_dir());
+    if (userdata_migrated)
+        g_print("\n\n%s\n", userdata_migration_msg);
+
     gnc_log_init();
 
 #ifndef MAC_INTEGRATION
diff --git a/libgnucash/core-utils/gnc-filepath-utils.cpp b/libgnucash/core-utils/gnc-filepath-utils.cpp
index 045674c..4b15ea8 100644
--- a/libgnucash/core-utils/gnc-filepath-utils.cpp
+++ b/libgnucash/core-utils/gnc-filepath-utils.cpp
@@ -52,7 +52,7 @@ extern "C" {
 #include "gnc-path.h"
 #include "gnc-filepath-utils.h"
 
-#ifdef _MSC_VER
+#if defined (_MSC_VER) || defined (G_OS_WIN32)
 #include <glib/gwin32.h>
 #define PATH_MAX MAXPATHLEN
 #endif
@@ -336,93 +336,259 @@ gnc_validate_directory (const bfs::path &dirname, bool create)
     return true;
 }
 
-static auto usr_conf_dir = bfs::path();
+static bool userdata_is_home = false;
+static bool userdata_is_tmp = false;
+static auto userdata_home = bfs::path();
+static auto gnc_userdata_home = bfs::path();
 
-static void
-gnc_filepath_init()
+/* Will attempt to copy all files and directories from src to dest
+ * Returns true if successful or false if not */
+static bool
+copy_recursive(const bfs::path& src, const bfs::path& dest)
+{
+    if (!bfs::exists(src))
+        return false;
+
+    auto old_str = src.string();
+    auto old_len = old_str.size();
+    try
+    {
+        /* Note: the for(auto elem : iterator) construct fails
+         * on travis (g++ 4.8.x, boost 1.54) so I'm using
+         * a traditional for loop here */
+        for(auto direntry = bfs::recursive_directory_iterator(src);
+            direntry != bfs::recursive_directory_iterator(); ++direntry)
+        {
+            auto cur_str = direntry->path().string();
+            auto cur_len = cur_str.size();
+            auto rel_str = std::string(cur_str, old_len, cur_len - old_len);
+            auto relpath = bfs::path(rel_str).relative_path();
+            auto newpath = bfs::absolute (relpath, dest);
+            bfs::copy(direntry->path(), newpath);
+        }
+    }
+    catch(const bfs::filesystem_error& ex)
+    {
+        g_warning("An error occured while trying to migrate the user configation from\n%s to\n%s"
+                  "The reported failure is\n%s",
+                  src.c_str(), gnc_userdata_home.c_str(), ex.what());
+        return false;
+    }
+
+    return true;
+}
+
+#ifdef G_OS_WIN32
+/* g_get_user_data_dir defaults to CSIDL_LOCAL_APPDATA, but
+ * the roaming profile makes more sense.
+ * So this function is a copy of glib's internal get_special_folder
+ * and minimally adjusted to fetch CSIDL_APPDATA
+ */
+static gchar *
+win32_get_userdata_home (void)
+{
+    wchar_t path[MAX_PATH+1];
+    HRESULT hr;
+    LPITEMIDLIST pidl = NULL;
+    BOOL b;
+    gchar *retval = NULL;
+
+    hr = SHGetSpecialFolderLocation (NULL, CSIDL_APPDATA, &pidl);
+    if (hr == S_OK)
+    {
+        b = SHGetPathFromIDListW (pidl, path);
+        if (b)
+            retval = g_utf16_to_utf8 (path, -1, NULL, NULL, NULL);
+        CoTaskMemFree (pidl);
+    }
+    return retval;
+}
+#elif defined MAC_INTEGRATION
+static gchar*
+quarz_get_userdata_home(void)
+{
+    gchar *retval = NULL;
+    NSFileManager*fm = [NSFileManager defaultManager];
+    NSArray* appSupportDir = [fm URLsForDirectory:NSApplicationSupportDirectory
+    inDomains:NSUserDomainMask];
+    if ([appSupportDir count] > 0)
+    {
+        NSURL* dirUrl = [appSupportDir objectAtIndex:0];
+        NSString* dirPath = [dirUrl path];
+        retval = g_strdup([dirPath UTF8String]);
+    }
+    return retval;
+}
+#endif
+
+static bfs::path
+get_userdata_home(bool create)
 {
     auto try_home_dir = true;
-    auto env_var = g_getenv("GNC_DOT_DIR");
-    if (env_var)
-        usr_conf_dir = env_var;
+    gchar *data_dir = NULL;
+
+#ifdef G_OS_WIN32
+    data_dir = win32_get_userdata_home ();
+#elif defined MAC_INTEGRATION
+    data_dir = quarz_get_userdata_home ();
+#endif
 
-    if (!usr_conf_dir.empty())
+    if (data_dir)
+    {
+        userdata_home = data_dir;
+        g_free(data_dir);
+    }
+    else
+        userdata_home = g_get_user_data_dir();
+
+    if (!userdata_home.empty())
     {
         try
         {
-            gnc_validate_directory(usr_conf_dir, true);
+            if (!gnc_validate_directory(userdata_home, create))
+                throw (bfs::filesystem_error(
+                    std::string(_("Directory doesn't exist: "))
+                    + userdata_home.c_str(), userdata_home,
+                    bst::error_code(bst::errc::permission_denied, bst::generic_category())));
             try_home_dir = false;
         }
         catch (const bfs::filesystem_error& ex)
         {
-            g_warning("%s is not a suitable base directory for the user configuration."
-                      "Trying home directory instead.\nThe failure is\n%s",
-                      usr_conf_dir.c_str(), ex.what());
+            g_warning("%s is not a suitable base directory for the user data."
+            "Trying home directory instead.\nThe failure is\n%s",
+            userdata_home.c_str(), ex.what());
         }
     }
 
     if (try_home_dir)
     {
-        usr_conf_dir = g_get_home_dir();
+        userdata_home = g_get_home_dir();
         try
         {
-            if (!gnc_validate_directory(usr_conf_dir, false))
-                usr_conf_dir = g_get_tmp_dir();
+            /* Never attempt to create a home directory, hence the false below */
+            if (!gnc_validate_directory(userdata_home, false))
+                throw (bfs::filesystem_error(
+                    std::string(_("Directory doesn't exist: "))
+                    + userdata_home.c_str(), userdata_home,
+                    bst::error_code(bst::errc::permission_denied, bst::generic_category())));
+            userdata_is_home = true;
         }
         catch (const bfs::filesystem_error& ex)
         {
             g_warning("Cannot find suitable home directory. Using tmp directory instead.\n"
-                    "The failure is\n%s", ex.what());
-            usr_conf_dir = g_get_tmp_dir();
+            "The failure is\n%s", ex.what());
+            userdata_home = g_get_tmp_dir();
+            userdata_is_tmp = true;
         }
     }
-    g_assert(!usr_conf_dir.empty());
+    g_assert(!userdata_home.empty());
 
-    usr_conf_dir /= ".gnucash";
+    return userdata_home;
+}
 
-    if (!gnc_validate_directory(usr_conf_dir, true))
-    {
-        g_warning("Cannot find suitable .gnucash directory in home directory. Using tmp directory instead.");
+gboolean
+gnc_filepath_init(gboolean create)
+{
+    auto userdata_home = get_userdata_home(create);
 
-        usr_conf_dir = g_get_tmp_dir();
-        g_assert(!usr_conf_dir.empty());
-        usr_conf_dir /= ".gnucash";
-        /* This may throw and end the program! */
-        gnc_validate_directory(usr_conf_dir, true);
+    if (userdata_is_home)
+    {
+        /* If we get here that means the platform
+         * dependent gnc_userdata_home is not available for
+         * some reason. If legacy .gnucash directory still exists,
+         * use it as first fallback, but never create it (we want
+         * it to go away eventually).
+         * If missing, fall back to tmp_dir instead */
+        gnc_userdata_home = userdata_home / ".gnucash";
+        if (!bfs::exists(gnc_userdata_home))
+        {
+            userdata_home = g_get_tmp_dir();
+            userdata_is_home = false;
+        }
     }
 
+    if (!userdata_is_home)
+        gnc_userdata_home = userdata_home / PACKAGE_NAME;
+
+    auto try_migrate = false;
+    if (!userdata_is_home && !bfs::exists(gnc_userdata_home) && create)
+        try_migrate = true;
+    /* This may throw and end the program! */
+    gnc_validate_directory(gnc_userdata_home, create);
+    auto migrated = FALSE;
+    if (try_migrate)
+        migrated = copy_recursive(bfs::path (g_get_home_dir()) / ".gnucash",
+                                  gnc_userdata_home);
+
     /* Since we're in code that is only executed once....
      * Note these calls may throw and end the program! */
-    gnc_validate_directory(usr_conf_dir / "books", true);
-    gnc_validate_directory(usr_conf_dir / "checks", true);
-    gnc_validate_directory(usr_conf_dir / "translog", true);
+    gnc_validate_directory(gnc_userdata_home / "books", (create || userdata_is_tmp));
+    gnc_validate_directory(gnc_userdata_home / "checks", (create || userdata_is_tmp));
+    gnc_validate_directory(gnc_userdata_home / "translog", (create || userdata_is_tmp));
+
+    return migrated;
 }
 
 /** @fn const gchar * gnc_userdata_dir ()
  *  @brief Ensure that the user's configuration directory exists and is minimally populated.
  *
- *  Note that the default path is $HOME/.gnucash; This can be changed
- *  by the environment variable $GNC_DOT_DIR.
+ *  Note that the default path depends on the platform.
+ *  - Windows: CSIDL_APPDATA/Gnucash
+ *  - OS X: $HOME/Application Support/Gnucash
+ *  - Linux: $XDG_DATA_HOME/Gnucash (or the default $HOME/.local/share/Gnucash)
+ *
+ *  If any of these paths doesn't exist and can't be created the function will
+ *  fall back to
+ *  - $HOME/.gnucash
+ *  - <TMP_DIR>/Gnucash
+ *  (in that order)
  *
  *  @return An absolute path to the configuration directory. This string is
- *  by the gnc_filepath_utils code and should not be freed by the user.
+ *  owned by the gnc_filepath_utils code and should not be freed by the user.
+ */
+
+
+/* Note Don't create missing directories automatically
+ * here and in the next function except if the
+ * target directory is the temporary directory. This
+ * should be done properly at a higher level (in the gui
+ * code most likely) very early in application startup.
+ * This call is just a fallback to prevent the code from
+ * crashing because no directories were configured. This
+ * weird construct is set up because compiling our guile
+ * scripts also triggers this code and that's not the
+ * right moment to start creating the necessary directories.
+ * FIXME A better approach would be to have the gnc_userdata_home
+ * verification/creation be part of the application code instead
+ * of libgnucash. If libgnucash needs access to this directory
+ * libgnucash will need some kind of initialization routine
+ * that the application can call to set (among others) the proper
+ * gnc_uderdata_home for libgnucash. The only other aspect to
+ * consider here is how to handle this in the bindings (if they
+ * need it).
  */
 const gchar *
 gnc_userdata_dir (void)
 {
-    if (usr_conf_dir.empty())
-        gnc_filepath_init();
+    if (gnc_userdata_home.empty())
+        gnc_filepath_init(false);
 
-    return usr_conf_dir.c_str();
+    return gnc_userdata_home.c_str();
 }
 
 static const bfs::path&
 gnc_userdata_dir_as_path (void)
 {
-    if (usr_conf_dir.empty())
-        gnc_filepath_init();
-
-    return usr_conf_dir;
+    if (gnc_userdata_home.empty())
+        /* Don't create missing directories automatically except
+         * if the target directory is the temporary directory. This
+         * should be done properly at a higher level (in the gui
+         * code most likely) very early in application startup.
+         * This call is just a fallback to prevent the code from
+         * crashing because no directories were configured. */
+        gnc_filepath_init(false);
+
+    return gnc_userdata_home;
 }
 
 /** @fn gchar * gnc_build_userdata_path (const gchar *filename)
diff --git a/libgnucash/core-utils/gnc-filepath-utils.h b/libgnucash/core-utils/gnc-filepath-utils.h
index 0e8adfa..9c2a93a 100644
--- a/libgnucash/core-utils/gnc-filepath-utils.h
+++ b/libgnucash/core-utils/gnc-filepath-utils.h
@@ -75,6 +75,27 @@ gchar *gnc_resolve_file_path (const gchar *filefrag);
  */
 gchar *gnc_path_find_localized_html_file (const gchar *file_name);
 
+/** @brief Initializes the gnucash user data directory.
+ * This function should be called very early at startup (before any
+ * other of the user data directory related function gets called).
+ *
+ * @param create If true the function will attempt to create the
+ * gnucash user data directory its subdirectories and parent directories
+ * if they don't exist yet. Note it won't attempt to create a home directory
+ * if that is missing. In that case the system's default temporary
+ * directory will be used instead. If false it will not attempt to create
+ * any directories at all unless they are in the temporary directory. This
+ * is a fallback measure to allow the calling application to more or less
+ * function even if gnc_filepath_init was never called.
+ *
+ * @note If the necessary directories did get created this
+ * function will also try to copy files from $HOME/.gnucash
+ * to there if that old location exists.
+ *
+ * @return whether files got copied from the old location.
+ */
+gboolean gnc_filepath_init(gboolean create);
+
 const gchar *gnc_userdata_dir (void);
 gchar *gnc_build_userdata_path (const gchar *filename);
 gchar *gnc_build_book_path (const gchar *filename);
diff --git a/libgnucash/core-utils/test/CMakeLists.txt b/libgnucash/core-utils/test/CMakeLists.txt
index 1c7d805..a21fea0 100644
--- a/libgnucash/core-utils/test/CMakeLists.txt
+++ b/libgnucash/core-utils/test/CMakeLists.txt
@@ -14,8 +14,8 @@ ENDMACRO()
 
 ADD_CORE_UTILS_TEST(test-gnc-glib-utils test-gnc-glib-utils.c)
 ADD_CORE_UTILS_TEST(test-resolve-file-path test-resolve-file-path.c)
-ADD_CORE_UTILS_TEST(test-usr-conf-dir test-usr-conf-dir.c)
-ADD_CORE_UTILS_TEST(test-usr-conf-dir-invalid-home test-usr-conf-dir-invalid-home.c)
+ADD_CORE_UTILS_TEST(test-userdata-dir test-userdata-dir.c)
+ADD_CORE_UTILS_TEST(test-userdata-dir-invalid-home test-userdata-dir-invalid-home.c)
 
 SET_DIST_LIST(test_core_utils_DIST CMakeLists.txt Makefile.am test-gnc-glib-utils.c
-    test-resolve-file-path.c test-usr-conf-dir.c test-usr-conf-dir-invalid-home.c)
+    test-resolve-file-path.c test-userdata-dir.c test-userdata-dir-invalid-home.c)
diff --git a/libgnucash/core-utils/test/Makefile.am b/libgnucash/core-utils/test/Makefile.am
index 2bcbdb3..cb1b269 100644
--- a/libgnucash/core-utils/test/Makefile.am
+++ b/libgnucash/core-utils/test/Makefile.am
@@ -26,8 +26,8 @@ LDADD = \
 TESTS = \
   test-gnc-glib-utils \
   test-resolve-file-path \
-  test-usr-conf-dir \
-  test-usr-conf-dir-invalid-home
+  test-userdata-dir \
+  test-userdata-dir-invalid-home
 
 GNC_TEST_DEPS = \
   --library-dir    ${top_builddir}/libgnucash/core-utils
diff --git a/libgnucash/core-utils/test/test-usr-conf-dir-invalid-home.c b/libgnucash/core-utils/test/test-userdata-dir-invalid-home.c
similarity index 95%
rename from libgnucash/core-utils/test/test-usr-conf-dir-invalid-home.c
rename to libgnucash/core-utils/test/test-userdata-dir-invalid-home.c
index 790f818..0195c43 100644
--- a/libgnucash/core-utils/test/test-usr-conf-dir-invalid-home.c
+++ b/libgnucash/core-utils/test/test-userdata-dir-invalid-home.c
@@ -42,19 +42,19 @@ usr_confpath_strings strs2[] =
 {
     {
         0, "gnc_build_userdata_path",
-        ".gnucash"
+        PACKAGE_NAME
     },
     {
         1, "gnc_build_book_path",
-        ".gnucash" G_DIR_SEPARATOR_S "books"
+        PACKAGE_NAME G_DIR_SEPARATOR_S "books"
     },
     {
         2, "gnc_build_translog_path",
-        ".gnucash" G_DIR_SEPARATOR_S "translog"
+        PACKAGE_NAME G_DIR_SEPARATOR_S "translog"
     },
     {
         3, "gnc_build_data_path",
-        ".gnucash" G_DIR_SEPARATOR_S "data"
+        PACKAGE_NAME G_DIR_SEPARATOR_S "data"
     },
     { 0, NULL, NULL },
 };
diff --git a/libgnucash/core-utils/test/test-usr-conf-dir.c b/libgnucash/core-utils/test/test-userdata-dir.c
similarity index 61%
rename from libgnucash/core-utils/test/test-usr-conf-dir.c
rename to libgnucash/core-utils/test/test-userdata-dir.c
index 91a6c67..68bd5e0 100644
--- a/libgnucash/core-utils/test/test-usr-conf-dir.c
+++ b/libgnucash/core-utils/test/test-userdata-dir.c
@@ -42,19 +42,19 @@ usr_confpath_strings strs2[] =
 {
     {
         0, "gnc_build_userdata_path",
-        ".gnucash"
+        PACKAGE_NAME
     },
     {
         1, "gnc_build_book_path",
-        ".gnucash" G_DIR_SEPARATOR_S "books"
+        PACKAGE_NAME G_DIR_SEPARATOR_S "books"
     },
     {
         2, "gnc_build_translog_path",
-        ".gnucash" G_DIR_SEPARATOR_S "translog"
+        PACKAGE_NAME G_DIR_SEPARATOR_S "translog"
     },
     {
         3, "gnc_build_data_path",
-        ".gnucash" G_DIR_SEPARATOR_S "data"
+        PACKAGE_NAME G_DIR_SEPARATOR_S "data"
     },
     { 0, NULL, NULL },
 };
@@ -64,6 +64,8 @@ main(int argc, char **argv)
 {
     int i;
     char *home_dir = NULL;
+    const char *userdata_dir = NULL;
+    const char *tmp_dir = g_get_tmp_dir();
 
     if (argc > 1)
         /* One can pass a homedir on the command line. This
@@ -78,6 +80,49 @@ main(int argc, char **argv)
 
     /* Run usr conf dir tests with a valid and writable homedir */
     g_setenv("HOME", home_dir, TRUE);
+
+    /* First run, before calling gnc_filepath_init */
+    for (i = 0; strs2[i].funcname != NULL; i++)
+    {
+        char *daout;
+        char *wantout;
+
+        if (strs2[i].func_num == 0)
+        {
+            wantout = g_build_filename(tmp_dir, strs2[i].output, "foo",
+                                       (gchar *)NULL);
+            daout = gnc_build_userdata_path("foo");
+        }
+        else if (strs2[i].func_num == 1)
+        {
+            wantout = g_build_filename(tmp_dir, strs2[i].output, "foo",
+                                       (gchar *)NULL);
+            daout = gnc_build_book_path("foo");
+        }
+        else if (strs2[i].func_num == 2)
+        {
+            wantout = g_build_filename(tmp_dir, strs2[i].output, "foo",
+                                       (gchar *)NULL);
+            daout = gnc_build_translog_path("foo");
+        }
+        else // if (strs2[i].prefix_home == 3)
+        {
+            wantout = g_build_filename(tmp_dir, strs2[i].output, "foo",
+                                       (gchar *)NULL);
+            daout = gnc_build_data_path("foo");
+        }
+
+        do_test_args(g_strcmp0(daout, wantout) == 0,
+                     "gnc_build_x_path",
+                     __FILE__, __LINE__,
+                     "%s (%s) vs %s", daout, strs2[i].funcname, wantout);
+        g_free(wantout);
+        g_free(daout);
+    }
+
+    /* Second run, after properly having called gnc_filepath_init */
+    gnc_filepath_init(TRUE);
+    userdata_dir = g_get_user_data_dir();
     for (i = 0; strs2[i].funcname != NULL; i++)
     {
         char *daout;
@@ -85,25 +130,25 @@ main(int argc, char **argv)
 
         if (strs2[i].func_num == 0)
         {
-            wantout = g_build_filename(home_dir, strs2[i].output, "foo",
+            wantout = g_build_filename(userdata_dir, strs2[i].output, "foo",
                                        (gchar *)NULL);
             daout = gnc_build_userdata_path("foo");
         }
         else if (strs2[i].func_num == 1)
         {
-            wantout = g_build_filename(home_dir, strs2[i].output, "foo",
+            wantout = g_build_filename(userdata_dir, strs2[i].output, "foo",
                                        (gchar *)NULL);
             daout = gnc_build_book_path("foo");
         }
         else if (strs2[i].func_num == 2)
         {
-            wantout = g_build_filename(home_dir, strs2[i].output, "foo",
+            wantout = g_build_filename(userdata_dir, strs2[i].output, "foo",
                                        (gchar *)NULL);
             daout = gnc_build_translog_path("foo");
         }
         else // if (strs2[i].prefix_home == 3)
         {
-            wantout = g_build_filename(home_dir, strs2[i].output, "foo",
+            wantout = g_build_filename(userdata_dir, strs2[i].output, "foo",
                                        (gchar *)NULL);
             daout = gnc_build_data_path("foo");
         }

commit ae75bc963f18fd0b9a3a8333a9f2a93f503449df
Author: Geert Janssens <geert at kobaltwit.be>
Date:   Tue Aug 22 16:33:34 2017 +0200

    Rewrite several file path routines to use boost::filesystem
    
    This is a basis for moving .gnucash to a more modern location for
    application specific user data (following the xdg spec).

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 4ec44d6..f017a85 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -438,7 +438,7 @@ SET (Boost_FIND_QUIETLY ON)
 IF (NOT DEFINED ${BOOST_ROOT})
   SET(BOOST_ROOT $ENV{BOOST_ROOT})
 ENDIF()
-FIND_PACKAGE (Boost 1.54.0 REQUIRED COMPONENTS date_time regex locale)
+FIND_PACKAGE (Boost 1.54.0 REQUIRED COMPONENTS date_time regex locale filesystem)
 
 IF (Boost_FOUND)
   include_directories(${Boost_INCLUDE_DIRS})
diff --git a/gnucash/gnome-utils/gnc-file.c b/gnucash/gnome-utils/gnc-file.c
index 6682145..680ebfc 100644
--- a/gnucash/gnome-utils/gnc-file.c
+++ b/gnucash/gnome-utils/gnc-file.c
@@ -430,7 +430,7 @@ show_session_error (QofBackendError io_error,
         fmt = _("You attempted to save in\n%s\nor a subdirectory thereof. "
                 "This is not allowed as %s reserves that directory for internal use.\n\n"
                 "Please try again in a different directory.");
-        gnc_error_dialog (parent, fmt, gnc_dotgnucash_dir(), PACKAGE_NAME);
+        gnc_error_dialog (parent, fmt, gnc_userdata_dir(), PACKAGE_NAME);
         break;
 
     case ERR_SQL_DB_TOO_OLD:
@@ -1139,7 +1139,7 @@ check_file_path (const char *path)
 {
     /* Remember the directory as the default. */
      gchar *dir = g_path_get_dirname(path);
-     const gchar *dotgnucash = gnc_dotgnucash_dir();
+     const gchar *dotgnucash = gnc_userdata_dir();
      char *dirpath = dir;
 
      /* Prevent user from storing file in GnuCash' private configuration
diff --git a/gnucash/gnome-utils/gnc-gnome-utils.c b/gnucash/gnome-utils/gnc-gnome-utils.c
index 845f63e..7687ead 100644
--- a/gnucash/gnome-utils/gnc-gnome-utils.c
+++ b/gnucash/gnome-utils/gnc-gnome-utils.c
@@ -719,16 +719,16 @@ gnc_gui_init(void)
     gnc_window_set_progressbar_window (GNC_WINDOW(main_window));
 
 #ifdef MAC_INTEGRATION
-    map = gnc_build_dotgnucash_path(ACCEL_MAP_NAME);
+    map = gnc_build_userdata_path(ACCEL_MAP_NAME);
     if (!g_file_test (map, G_FILE_TEST_EXISTS))
     {
-	g_free (map);
-	data_dir = gnc_path_get_pkgdatadir();
-	map = g_build_filename(data_dir, "ui", "osx_accel_map", NULL);
-	g_free(data_dir);
+        g_free (map);
+        data_dir = gnc_path_get_pkgdatadir();
+        map = g_build_filename(data_dir, "ui", "osx_accel_map", NULL);
+        g_free(data_dir);
     }
 #else
-    map = gnc_build_dotgnucash_path(ACCEL_MAP_NAME);
+    map = gnc_build_userdata_path(ACCEL_MAP_NAME);
 #endif /* MAC_INTEGRATION */
     gtk_accel_map_load(map);
     g_free(map);
@@ -765,7 +765,7 @@ gnc_gui_shutdown (void)
     if (gnome_is_running && !gnome_is_terminating)
     {
         gnome_is_terminating = TRUE;
-        map = gnc_build_dotgnucash_path(ACCEL_MAP_NAME);
+        map = gnc_build_userdata_path(ACCEL_MAP_NAME);
         gtk_accel_map_save(map);
         g_free(map);
         gtk_main_quit();
diff --git a/gnucash/gnome/dialog-print-check.c b/gnucash/gnome/dialog-print-check.c
index 0c7f0f2..8cd0018 100644
--- a/gnucash/gnome/dialog-print-check.c
+++ b/gnucash/gnome/dialog-print-check.c
@@ -799,7 +799,7 @@ pcd_save_custom_data(PrintCheckDialog *pcd, const gchar *title)
                               pcd->splits_account_x, pcd->splits_account_y);
 
     filename = g_strconcat(title, CHECK_NAME_EXTENSION, NULL);
-    pathname = g_build_filename(gnc_dotgnucash_dir(), CHECK_FMT_DIR,
+    pathname = g_build_filename(gnc_userdata_dir(), CHECK_FMT_DIR,
                                 filename, NULL);
 
     if (gnc_key_file_save_to_file(pathname, key_file, &error))
@@ -1558,7 +1558,7 @@ read_formats(PrintCheckDialog *pcd, GtkListStore *store)
     g_free(dirname);
     g_free(pkgdatadir);
 
-    dirname = gnc_build_dotgnucash_path(CHECK_FMT_DIR);
+    dirname = gnc_build_userdata_path(CHECK_FMT_DIR);
     /* Translators: This is a directory name. It may be presented to
      * the user to indicate that some data file was defined by a
      * user herself. */
@@ -1901,7 +1901,7 @@ read_image (const gchar *filename)
     if (!g_file_test(tmp_name, G_FILE_TEST_EXISTS))
     {
         g_free(tmp_name);
-        dirname = gnc_build_dotgnucash_path(CHECK_FMT_DIR);
+        dirname = gnc_build_userdata_path(CHECK_FMT_DIR);
         tmp_name = g_build_filename(dirname, filename, (char *)NULL);
         g_free(dirname);
     }
diff --git a/gnucash/gnucash-bin.c b/gnucash/gnucash-bin.c
index dbacffd..5bc7063 100644
--- a/gnucash/gnucash-bin.c
+++ b/gnucash/gnucash-bin.c
@@ -344,7 +344,7 @@ try_load_config_array(const gchar *fns[])
 
     for (i = 0; fns[i]; i++)
     {
-        filename = gnc_build_dotgnucash_path(fns[i]);
+        filename = gnc_build_userdata_path(fns[i]);
         if (gfec_try_load(filename))
         {
             g_free(filename);
@@ -703,7 +703,7 @@ gnc_log_init()
 
     {
         gchar *log_config_filename;
-        log_config_filename = gnc_build_dotgnucash_path("log.conf");
+        log_config_filename = gnc_build_userdata_path("log.conf");
         if (g_file_test(log_config_filename, G_FILE_TEST_EXISTS))
             qof_log_parse_log_config(log_config_filename);
         g_free(log_config_filename);
diff --git a/gnucash/import-export/qif-imp/qif-guess-map.scm b/gnucash/import-export/qif-imp/qif-guess-map.scm
index 554921c..1280208 100644
--- a/gnucash/import-export/qif-imp/qif-guess-map.scm
+++ b/gnucash/import-export/qif-imp/qif-guess-map.scm
@@ -91,7 +91,7 @@
     (false-if-exception
      (read)))
 
-  (let* ((pref-filename (gnc-build-dotgnucash-path "qif-accounts-map"))
+  (let* ((pref-filename (gnc-build-userdata-path "qif-accounts-map"))
          (results '()))
 
     ;; Get the user's saved mappings.
@@ -293,7 +293,7 @@
 
   ;; This procedure does all the work. We'll define it, then call it safely.
   (define (private-save)
-    (with-output-to-file (gnc-build-dotgnucash-path "qif-accounts-map")
+    (with-output-to-file (gnc-build-userdata-path "qif-accounts-map")
       (lambda ()
         (display ";;; qif-accounts-map")
         (newline)
diff --git a/gnucash/report/report-gnome/gnc-plugin-page-report.c b/gnucash/report/report-gnome/gnc-plugin-page-report.c
index 9712193..3493600 100644
--- a/gnucash/report/report-gnome/gnc-plugin-page-report.c
+++ b/gnucash/report/report-gnome/gnc-plugin-page-report.c
@@ -1116,7 +1116,7 @@ gnc_plugin_page_report_constr_init(GncPluginPageReport *plugin_page, gint report
     GncPluginPage *parent;
     gboolean use_new;
     gchar *name;
-    gchar *saved_reports_path = gnc_build_dotgnucash_path (SAVED_REPORTS_FILE);
+    gchar *saved_reports_path = gnc_build_userdata_path (SAVED_REPORTS_FILE);
     gchar *report_save_str = g_strdup_printf (
         _("Update the current report's saved configuration. "
         "The report will be saved in the file %s. "), saved_reports_path);
diff --git a/gnucash/report/report-system/eguile-utilities.scm b/gnucash/report/report-system/eguile-utilities.scm
index a582cae..5eb12e1 100644
--- a/gnucash/report/report-system/eguile-utilities.scm
+++ b/gnucash/report/report-system/eguile-utilities.scm
@@ -82,7 +82,7 @@
   ;; Then look in Gnucash's standard report directory.
   ;; If no file is found, returns just 'fname' for use in error messages.
   ;; Note: this has been tested on Linux and Windows Vista so far...
-  (let* ((userpath (gnc-build-dotgnucash-path fname))
+  (let* ((userpath (gnc-build-userdata-path fname))
          (syspath  (gnc-build-report-path fname)))
     ; make sure there's a trailing delimiter
       (if (access? userpath R_OK)
diff --git a/gnucash/report/report-system/gnc-report.c b/gnucash/report/report-system/gnc-report.c
index 763afad..04b7164 100644
--- a/gnucash/report/report-system/gnc-report.c
+++ b/gnucash/report/report-system/gnc-report.c
@@ -279,8 +279,8 @@ gnc_saved_reports_write_internal (const gchar *file, const gchar *contents, gboo
 gboolean gnc_saved_reports_backup (void)
 {
     gboolean success = FALSE;
-    gchar *saved_rpts_path     = gnc_build_dotgnucash_path (SAVED_REPORTS_FILE);
-    gchar *saved_rpts_bkp_path = g_strconcat (saved_rpts_path, "-backup", NULL);
+    gchar *saved_rpts_path     = gnc_build_userdata_path (SAVED_REPORTS_FILE);
+    gchar *saved_rpts_bkp_path = gnc_build_userdata_path (SAVED_REPORTS_FILE "-backup");
     gchar *contents = NULL;
     GError *save_error = NULL;
 
@@ -310,7 +310,7 @@ gboolean
 gnc_saved_reports_write_to_file (const gchar* report_def, gboolean overwrite)
 {
     gboolean success = FALSE;
-    gchar *saved_rpts_path     = gnc_build_dotgnucash_path (SAVED_REPORTS_FILE);
+    gchar *saved_rpts_path = gnc_build_userdata_path (SAVED_REPORTS_FILE);
 
     if (report_def)
     {
diff --git a/gnucash/report/report-system/html-style-sheet.scm b/gnucash/report/report-system/html-style-sheet.scm
index fc7f60b..fd7c3cc 100644
--- a/gnucash/report/report-system/html-style-sheet.scm
+++ b/gnucash/report/report-system/html-style-sheet.scm
@@ -127,7 +127,7 @@
   (record-accessor <html-style-sheet> 'style))
 
 (define gnc:current-saved-stylesheets
-  (gnc-build-dotgnucash-path "stylesheets-2.0"))
+  (gnc-build-userdata-path "stylesheets-2.0"))
 
 (define (gnc:save-style-sheet-options) 
   (let ((port (false-if-exception
diff --git a/gnucash/report/report-system/report-collectors.scm b/gnucash/report/report-system/report-collectors.scm
index ad3ae53..78c9992 100644
--- a/gnucash/report/report-system/report-collectors.scm
+++ b/gnucash/report/report-system/report-collectors.scm
@@ -30,7 +30,6 @@
 (use-modules (gnucash report report-system))
 (use-modules (gnucash app-utils))
 (use-modules (gnucash engine))
-(use-modules (sw_engine))
 (use-modules (gnucash report report-system collectors))
 (use-modules (gnucash report report-system list-extras))
 
diff --git a/libgnucash/app-utils/gfec.c b/libgnucash/app-utils/gfec.c
index b27c911..631fcdd 100644
--- a/libgnucash/app-utils/gfec.c
+++ b/libgnucash/app-utils/gfec.c
@@ -228,7 +228,7 @@ error_handler(const char *msg)
 }
 
 gboolean
-gfec_try_load(gchar *fn)
+gfec_try_load(const gchar *fn)
 {
     g_debug("looking for %s", fn);
     if (g_file_test(fn, G_FILE_TEST_EXISTS))
diff --git a/libgnucash/app-utils/gfec.h b/libgnucash/app-utils/gfec.h
index 16852ca..b1ecc30 100644
--- a/libgnucash/app-utils/gfec.h
+++ b/libgnucash/app-utils/gfec.h
@@ -18,6 +18,6 @@ typedef void (*gfec_error_handler)(const char *error_message);
 SCM gfec_eval_file(const char *file, gfec_error_handler error_handler);
 SCM gfec_eval_string(const char *str, gfec_error_handler error_handler);
 SCM gfec_apply(SCM proc, SCM arglist, gfec_error_handler error_handler);
-gboolean gfec_try_load(gchar *fn);
+gboolean gfec_try_load(const gchar *fn);
 
 #endif
diff --git a/libgnucash/app-utils/gnc-exp-parser.c b/libgnucash/app-utils/gnc-exp-parser.c
index c4cae40..b3aa68b 100644
--- a/libgnucash/app-utils/gnc-exp-parser.c
+++ b/libgnucash/app-utils/gnc-exp-parser.c
@@ -61,7 +61,7 @@ static gboolean      parser_inited     = FALSE;
 static gchar *
 gnc_exp_parser_filname (void)
 {
-    return gnc_build_dotgnucash_path("expressions-2.0");
+    return gnc_build_userdata_path("expressions-2.0");
 }
 
 void
diff --git a/libgnucash/core-utils/CMakeLists.txt b/libgnucash/core-utils/CMakeLists.txt
index 7847446..e4b5148 100644
--- a/libgnucash/core-utils/CMakeLists.txt
+++ b/libgnucash/core-utils/CMakeLists.txt
@@ -16,7 +16,7 @@ SET (core_utils_SOURCES
   binreloc.c
   gnc-prefs.c
   gnc-environment.c
-  gnc-filepath-utils.c
+  gnc-filepath-utils.cpp
   gnc-gkeyfile-utils.c
   gnc-glib-utils.c
   gnc-guile-utils.c
@@ -117,7 +117,7 @@ SET(core_utils_noinst_HEADERS
 )
 
 SET(core_utils_ALL_SOURCES ${core_utils_SOURCES} ${core_utils_noinst_HEADERS})
-SET(core_utils_ALL_LIBRARIES ${GUILE_LDFLAGS} ${GLIB2_LDFLAGS} ${GOBJECT_LDFLAGS} ${GTK_MAC_LDFLAGS})
+SET(core_utils_ALL_LIBRARIES ${Boost_LIBRARIES} -lboost_filesystem ${GUILE_LDFLAGS} ${GLIB2_LDFLAGS} ${GOBJECT_LDFLAGS} ${GTK_MAC_LDFLAGS})
 SET(core_utils_ALL_INCLUDES
   ${CMAKE_SOURCE_DIR}/common
   ${CMAKE_BINARY_DIR}/common
diff --git a/libgnucash/core-utils/Makefile.am b/libgnucash/core-utils/Makefile.am
index 9733c60..c9830f6 100644
--- a/libgnucash/core-utils/Makefile.am
+++ b/libgnucash/core-utils/Makefile.am
@@ -8,7 +8,7 @@ libgnc_core_utils_la_SOURCES = \
   binreloc.c \
   gnc-prefs.c \
   gnc-environment.c \
-  gnc-filepath-utils.c \
+  gnc-filepath-utils.cpp \
   gnc-gkeyfile-utils.c \
   gnc-glib-utils.c \
   gnc-guile-utils.c \
@@ -21,7 +21,8 @@ libgnc_core_utils_la_LIBADD = \
   ${GUILE_LIBS} \
   ${GLIB_LIBS} \
   ${BINRELOC_LIBS} \
-  ${GTK_MAC_LIBS}
+  ${GTK_MAC_LIBS} \
+  ${BOOST_LDFLAGS} -lboost_filesystem
 
 
 noinst_HEADERS = \
@@ -58,7 +59,8 @@ AM_CPPFLAGS = \
   ${GLIB_CFLAGS} \
   ${GTK_MAC_CFLAGS} \
   -I${top_builddir}/common \
-  -I${top_srcdir}/common
+  -I${top_srcdir}/common \
+  $(BOOST_CPPFLAGS)
 
 gncscmmoddir = ${GNC_SCM_INSTALL_DIR}/gnucash
 gncscmmod_DATA = core-utils.scm
diff --git a/libgnucash/core-utils/core-utils.i b/libgnucash/core-utils/core-utils.i
index e92fae4..d8fd67d 100644
--- a/libgnucash/core-utils/core-utils.i
+++ b/libgnucash/core-utils/core-utils.i
@@ -65,8 +65,8 @@ gchar * gnc_path_get_stdreportsdir(void);
 %newobject gnc_path_find_localized_html_file;
 gchar * gnc_path_find_localized_html_file(const gchar *);
 
-%newobject gnc_build_dotgnucash_path;
-gchar * gnc_build_dotgnucash_path(const gchar *);
+%newobject gnc_build_userdata_path;
+gchar * gnc_build_userdata_path(const gchar *);
 
 gchar * gnc_build_report_path(const gchar *);
 gchar * gnc_build_stdreports_path(const gchar *);
diff --git a/libgnucash/core-utils/core-utils.scm b/libgnucash/core-utils/core-utils.scm
index 8fd2869..645228b 100644
--- a/libgnucash/core-utils/core-utils.scm
+++ b/libgnucash/core-utils/core-utils.scm
@@ -41,7 +41,7 @@
 (re-export gnc-path-get-bindir)
 (re-export gnc-path-get-stdreportsdir)
 (re-export gnc-path-find-localized-html-file)
-(re-export gnc-build-dotgnucash-path)
+(re-export gnc-build-userdata-path)
 (re-export gnc-build-report-path)
 (re-export gnc-build-stdreports-path)
 (re-export gnc-utf8?)
diff --git a/libgnucash/core-utils/gnc-filepath-utils.c b/libgnucash/core-utils/gnc-filepath-utils.cpp
similarity index 69%
rename from libgnucash/core-utils/gnc-filepath-utils.c
rename to libgnucash/core-utils/gnc-filepath-utils.cpp
index 477cfb0..045674c 100644
--- a/libgnucash/core-utils/gnc-filepath-utils.c
+++ b/libgnucash/core-utils/gnc-filepath-utils.cpp
@@ -26,6 +26,7 @@
  * @author Copyright (c) 2000 Dave Peticolas
  */
 
+extern "C" {
 #include "config.h"
 
 #include <platform.h>
@@ -55,6 +56,12 @@
 #include <glib/gwin32.h>
 #define PATH_MAX MAXPATHLEN
 #endif
+}
+
+#include <boost/filesystem.hpp>
+
+namespace bfs = boost::filesystem;
+namespace bst = boost::system;
 
 /**
  * Scrubs a filename by changing "strange" chars (e.g. those that are not
@@ -104,7 +111,7 @@ check_path_return_if_valid(gchar *path)
  *
  *  Otherwise, assume that filefrag is a well-formed relative path and
  *  try to find a file with its path relative to
- *  \li  the current working directory,
+ *  \li the current working directory,
  *  \li the installed system-wide data directory (e.g., /usr/local/share/gnucash),
  *  \li the installed system configuration directory (e.g., /usr/local/etc/gnucash),
  *  \li or in the user's configuration directory (e.g., $HOME/.gnucash/data)
@@ -164,7 +171,7 @@ gnc_resolve_file_path (const gchar * filefrag)
         return fullpath;
 
     /* Look in the users config dir (e.g. $HOME/.gnucash/data) */
-    fullpath = gnc_build_data_path(filefrag);
+    fullpath = g_strdup(gnc_build_data_path(filefrag));
     if (g_file_test(fullpath, G_FILE_TEST_IS_REGULAR))
         return fullpath;
 
@@ -196,7 +203,7 @@ gnc_path_find_localized_html_file_internal (const gchar * file_name)
     const gchar *env_doc_path = g_getenv("GNC_DOC_PATH");
     const gchar *default_dirs[] =
         {
-            gnc_build_dotgnucash_path ("html"),
+            gnc_build_userdata_path ("html"),
             gnc_path_get_pkgdocdir (),
             gnc_path_get_pkgdatadir (),
             NULL
@@ -292,190 +299,156 @@ gnc_path_find_localized_html_file (const gchar *file_name)
 }
 
 /* ====================================================================== */
-
 /** @brief Check that the supplied directory path exists, is a directory, and
  * that the user has adequate permissions to use it.
  *
  * @param dirname The path to check
  */
-static gboolean
-gnc_validate_directory (const gchar *dirname, gboolean create, gchar **msg)
+static bool
+gnc_validate_directory (const bfs::path &dirname, bool create)
+{
+    if (dirname.empty())
+        return false;
+
+    if (!bfs::exists(dirname) && (!create))
+        return false;
+
+    /* Optionally create directories if they don't exist yet
+     * Note this will do nothing if the directory and its
+     * parents already exist, but will fail if the path
+     * points to a file or a softlink. So it serves as a test
+     * for that as well.
+     */
+    bfs::create_directories(dirname);
+
+    auto d = bfs::directory_entry (dirname);
+    auto perms = d.status().permissions();
+
+    /* On Windows only write permission will be checked.
+     * So strictly speaking we'd need two error messages here depending
+     * on the platform. For simplicity this detail is glossed over though. */
+    if ((perms & bfs::owner_all) != bfs::owner_all)
+        throw (bfs::filesystem_error(
+            std::string(_("Insufficient permissions, at least write and access permissions required: "))
+            + dirname.c_str(), dirname,
+            bst::error_code(bst::errc::permission_denied, bst::generic_category())));
+
+    return true;
+}
+
+static auto usr_conf_dir = bfs::path();
+
+static void
+gnc_filepath_init()
 {
-    GStatBuf statbuf;
-    gint rc;
+    auto try_home_dir = true;
+    auto env_var = g_getenv("GNC_DOT_DIR");
+    if (env_var)
+        usr_conf_dir = env_var;
 
-    *msg = NULL;
-    rc = g_stat (dirname, &statbuf);
-    if (rc)
+    if (!usr_conf_dir.empty())
     {
-        switch (errno)
+        try
         {
-        case ENOENT:
-            if (create)
-            {
-                rc = g_mkdir (dirname,
-#ifdef G_OS_WIN32
-                              0          /* The mode argument is ignored on windows */
-#else
-                              S_IRWXU    /* perms = S_IRWXU = 0700 */
-#endif
-                              );
-                if (rc)
-                {
-                    *msg = g_strdup_printf(
-                            _("An error occurred while creating the directory:\n"
-                              "  %s\n"
-                              "Please correct the problem and restart GnuCash.\n"
-                              "The reported error was '%s' (errno %d).\n"),
-                              dirname, g_strerror(errno) ? g_strerror(errno) : "", errno);
-                    return FALSE;
-                }
-            }
-            else
-            {
-                *msg = g_strdup_printf(
-                        _("Note: the directory\n"
-                          "  %s\n"
-                          "doesn't exist. This is however not fatal.\n"),
-                          dirname);
-                return FALSE;
-            }
-            g_stat (dirname, &statbuf);
-            break;
-
-        case EACCES:
-            *msg = g_strdup_printf(
-                      _("The directory\n"
-                        "  %s\n"
-                        "exists but cannot be accessed. This program \n"
-                        "must have full access (read/write/execute) to \n"
-                        "the directory in order to function properly.\n"),
-                      dirname);
-            return FALSE;
-
-        case ENOTDIR:
-            *msg = g_strdup_printf(
-                      _("The path\n"
-                        "  %s\n"
-                        "exists but it is not a directory. Please delete\n"
-                        "the file and start GnuCash again.\n"),
-                      dirname);
-            return FALSE;
-
-        default:
-            *msg = g_strdup_printf(
-                      _("An unknown error occurred when validating that the\n"
-                        "  %s\n"
-                        "directory exists and is usable. Please correct the\n"
-                        "problem and restart GnuCash. The reported error \n"
-                        "was '%s' (errno %d)."),
-                      dirname, g_strerror(errno) ? g_strerror(errno) : "", errno);
-            return FALSE;
+            gnc_validate_directory(usr_conf_dir, true);
+            try_home_dir = false;
+        }
+        catch (const bfs::filesystem_error& ex)
+        {
+            g_warning("%s is not a suitable base directory for the user configuration."
+                      "Trying home directory instead.\nThe failure is\n%s",
+                      usr_conf_dir.c_str(), ex.what());
         }
     }
 
-    if ((statbuf.st_mode & S_IFDIR) != S_IFDIR)
+    if (try_home_dir)
     {
-        *msg = g_strdup_printf(
-                  _("The path\n"
-                    "  %s\n"
-                    "exists but it is not a directory. Please delete\n"
-                    "the file and start GnuCash again.\n"),
-                  dirname);
-        return FALSE;
+        usr_conf_dir = g_get_home_dir();
+        try
+        {
+            if (!gnc_validate_directory(usr_conf_dir, false))
+                usr_conf_dir = g_get_tmp_dir();
+        }
+        catch (const bfs::filesystem_error& ex)
+        {
+            g_warning("Cannot find suitable home directory. Using tmp directory instead.\n"
+                    "The failure is\n%s", ex.what());
+            usr_conf_dir = g_get_tmp_dir();
+        }
     }
-#ifndef G_OS_WIN32
-    /* The mode argument is ignored on windows anyway */
-    if ((statbuf.st_mode & S_IRWXU) != S_IRWXU)
+    g_assert(!usr_conf_dir.empty());
+
+    usr_conf_dir /= ".gnucash";
+
+    if (!gnc_validate_directory(usr_conf_dir, true))
     {
-        *msg = g_strdup_printf(
-                  _("The permissions are wrong on the directory\n"
-                    "  %s\n"
-                    "They must be at least 'rwx' for the user.\n"),
-                  dirname);
-        return FALSE;
+        g_warning("Cannot find suitable .gnucash directory in home directory. Using tmp directory instead.");
+
+        usr_conf_dir = g_get_tmp_dir();
+        g_assert(!usr_conf_dir.empty());
+        usr_conf_dir /= ".gnucash";
+        /* This may throw and end the program! */
+        gnc_validate_directory(usr_conf_dir, true);
     }
-#endif
 
-    return TRUE;
+    /* Since we're in code that is only executed once....
+     * Note these calls may throw and end the program! */
+    gnc_validate_directory(usr_conf_dir / "books", true);
+    gnc_validate_directory(usr_conf_dir / "checks", true);
+    gnc_validate_directory(usr_conf_dir / "translog", true);
 }
 
-/** @fn const gchar * gnc_dotgnucash_dir ()
+/** @fn const gchar * gnc_userdata_dir ()
  *  @brief Ensure that the user's configuration directory exists and is minimally populated.
  *
  *  Note that the default path is $HOME/.gnucash; This can be changed
  *  by the environment variable $GNC_DOT_DIR.
  *
- *  @return An absolute path to the configuration directory
+ *  @return An absolute path to the configuration directory. This string is
+ *  by the gnc_filepath_utils code and should not be freed by the user.
  */
 const gchar *
-gnc_dotgnucash_dir (void)
+gnc_userdata_dir (void)
 {
-    static gchar *dotgnucash = NULL;
-    gchar *tmp_dir;
-    gchar *errmsg = NULL;
-
-    if (dotgnucash)
-        return dotgnucash;
-
-    dotgnucash = g_strdup(g_getenv("GNC_DOT_DIR"));
-
-    if (!dotgnucash)
-    {
-        const gchar *home = g_get_home_dir();
-        if (!home || !gnc_validate_directory(home, FALSE, &errmsg))
-        {
-            g_free(errmsg);
-            g_warning("Cannot find suitable home directory. Using tmp directory instead.");
-            home = g_get_tmp_dir();
-        }
-        g_assert(home);
+    if (usr_conf_dir.empty())
+        gnc_filepath_init();
 
-        dotgnucash = g_build_filename(home, ".gnucash", (gchar *)NULL);
-    }
-    if (!gnc_validate_directory(dotgnucash, TRUE, &errmsg))
-    {
-        const gchar *tmp = g_get_tmp_dir();
-        g_free(errmsg);
-        g_free(dotgnucash);
-        g_warning("Cannot find suitable .gnucash directory in home directory. Using tmp directory instead.");
-        g_assert(tmp);
+    return usr_conf_dir.c_str();
+}
 
-        dotgnucash = g_build_filename(tmp, ".gnucash", (gchar *)NULL);
-        
-        if (!gnc_validate_directory(dotgnucash, TRUE, &errmsg))
-            exit(1);
-    }
+static const bfs::path&
+gnc_userdata_dir_as_path (void)
+{
+    if (usr_conf_dir.empty())
+        gnc_filepath_init();
 
-    /* Since we're in code that is only executed once.... */
-    tmp_dir = g_build_filename(dotgnucash, "books", (gchar *)NULL);
-    if (!gnc_validate_directory(tmp_dir, TRUE, &errmsg))
-        exit(1);
-    g_free(tmp_dir);
-    tmp_dir = g_build_filename(dotgnucash, "checks", (gchar *)NULL);
-    if (!gnc_validate_directory(tmp_dir, TRUE, &errmsg))
-        exit(1);
-    g_free(tmp_dir);
-    tmp_dir = g_build_filename(dotgnucash, "translog", (gchar *)NULL);
-    if (!gnc_validate_directory(tmp_dir, TRUE, &errmsg))
-        exit(1);
-    g_free(tmp_dir);
-
-    return dotgnucash;
+    return usr_conf_dir;
 }
 
-/** @fn gchar * gnc_build_dotgnucash_path (const gchar *filename)
+/** @fn gchar * gnc_build_userdata_path (const gchar *filename)
  *  @brief Make a path to filename in the user's configuration directory.
  *
  * @param filename The name of the file
  *
- *  @return An absolute path.
+ *  @return An absolute path. The returned string should be freed by the user
+ *  using g_free().
  */
 
 gchar *
-gnc_build_dotgnucash_path (const gchar *filename)
+gnc_build_userdata_path (const gchar *filename)
 {
-    return g_build_filename(gnc_dotgnucash_dir(), filename, (gchar *)NULL);
+    return g_strdup((gnc_userdata_dir_as_path() / filename).c_str());
+}
+
+static bfs::path
+gnc_build_userdata_subdir_path (const gchar *subdir, const gchar *filename)
+{
+    gchar* filename_dup = g_strdup(filename);
+
+    scrub_filename(filename_dup);
+    auto result = (gnc_userdata_dir_as_path() / subdir) / filename_dup;
+    g_free(filename_dup);
+    return result;
 }
 
 /** @fn gchar * gnc_build_book_path (const gchar *filename)
@@ -483,20 +456,14 @@ gnc_build_dotgnucash_path (const gchar *filename)
  *
  * @param filename The name of the file
  *
- *  @return An absolute path.
+ *  @return An absolute path. The returned string should be freed by the user
+ *  using g_free().
  */
 
 gchar *
 gnc_build_book_path (const gchar *filename)
 {
-    gchar* filename_dup = g_strdup(filename);
-    gchar* result = NULL;
-
-    scrub_filename(filename_dup);
-    result = g_build_filename(gnc_dotgnucash_dir(), "books",
-                              filename_dup, (gchar *)NULL);
-    g_free(filename_dup);
-    return result;
+    return g_strdup(gnc_build_userdata_subdir_path("books", filename).c_str());
 }
 
 /** @fn gchar * gnc_build_translog_path (const gchar *filename)
@@ -504,20 +471,14 @@ gnc_build_book_path (const gchar *filename)
  *
  * @param filename The name of the file
  *
- *  @return An absolute path.
+ *  @return An absolute path. The returned string should be freed by the user
+ *  using g_free().
  */
 
 gchar *
 gnc_build_translog_path (const gchar *filename)
 {
-    gchar* filename_dup = g_strdup(filename);
-    gchar* result = NULL;
-
-    scrub_filename(filename_dup);
-    result = g_build_filename(gnc_dotgnucash_dir(), "translog",
-                              filename_dup, (gchar *)NULL);
-    g_free(filename_dup);
-    return result;
+    return g_strdup(gnc_build_userdata_subdir_path("translog", filename).c_str());
 }
 
 /** @fn gchar * gnc_build_data_path (const gchar *filename)
@@ -525,19 +486,14 @@ gnc_build_translog_path (const gchar *filename)
  *
  * @param filename The name of the file
  *
- *  @return An absolute path.
+ *  @return An absolute path. The returned string should be freed by the user
+ *  using g_free().
  */
 
 gchar *
 gnc_build_data_path (const gchar *filename)
 {
-    gchar* filename_dup = g_strdup(filename);
-    gchar* result;
-
-    scrub_filename(filename_dup);
-    result = g_build_filename(gnc_dotgnucash_dir(), "data", filename_dup, (gchar *)NULL);
-    g_free(filename_dup);
-    return result;
+    return g_strdup(gnc_build_userdata_subdir_path("data", filename).c_str());
 }
 
 /** @fn gchar * gnc_build_report_path (const gchar *filename)
@@ -545,7 +501,8 @@ gnc_build_data_path (const gchar *filename)
  *
  * @param filename The name of the file
  *
- *  @return An absolute path.
+ *  @return An absolute path. The returned string should be freed by the user
+ *  using g_free().
  */
 
 gchar *
@@ -560,7 +517,8 @@ gnc_build_report_path (const gchar *filename)
  *
  * @param filename The name of the file
  *
- *  @return An absolute path.
+ *  @return An absolute path. The returned string should be freed by the user
+ *  using g_free().
  */
 
 gchar *
diff --git a/libgnucash/core-utils/gnc-filepath-utils.h b/libgnucash/core-utils/gnc-filepath-utils.h
index 8f1c2dc..0e8adfa 100644
--- a/libgnucash/core-utils/gnc-filepath-utils.h
+++ b/libgnucash/core-utils/gnc-filepath-utils.h
@@ -75,8 +75,8 @@ gchar *gnc_resolve_file_path (const gchar *filefrag);
  */
 gchar *gnc_path_find_localized_html_file (const gchar *file_name);
 
-const gchar *gnc_dotgnucash_dir (void);
-gchar *gnc_build_dotgnucash_path (const gchar *filename);
+const gchar *gnc_userdata_dir (void);
+gchar *gnc_build_userdata_path (const gchar *filename);
 gchar *gnc_build_book_path (const gchar *filename);
 gchar *gnc_build_translog_path (const gchar *filename);
 gchar *gnc_build_data_path (const gchar *filename);
diff --git a/libgnucash/core-utils/test/CMakeLists.txt b/libgnucash/core-utils/test/CMakeLists.txt
index 15bf1a4..1c7d805 100644
--- a/libgnucash/core-utils/test/CMakeLists.txt
+++ b/libgnucash/core-utils/test/CMakeLists.txt
@@ -14,5 +14,8 @@ ENDMACRO()
 
 ADD_CORE_UTILS_TEST(test-gnc-glib-utils test-gnc-glib-utils.c)
 ADD_CORE_UTILS_TEST(test-resolve-file-path test-resolve-file-path.c)
+ADD_CORE_UTILS_TEST(test-usr-conf-dir test-usr-conf-dir.c)
+ADD_CORE_UTILS_TEST(test-usr-conf-dir-invalid-home test-usr-conf-dir-invalid-home.c)
 
-SET_DIST_LIST(test_core_utils_DIST CMakeLists.txt Makefile.am test-gnc-glib-utils.c test-resolve-file-path.c)
+SET_DIST_LIST(test_core_utils_DIST CMakeLists.txt Makefile.am test-gnc-glib-utils.c
+    test-resolve-file-path.c test-usr-conf-dir.c test-usr-conf-dir-invalid-home.c)
diff --git a/libgnucash/core-utils/test/Makefile.am b/libgnucash/core-utils/test/Makefile.am
index c88ace0..2bcbdb3 100644
--- a/libgnucash/core-utils/test/Makefile.am
+++ b/libgnucash/core-utils/test/Makefile.am
@@ -10,20 +10,24 @@ AM_CPPFLAGS = \
   -I${top_srcdir}/libgnucash/core-utils \
   -I${top_srcdir}/libgnucash/engine \
   ${GUILE_CFLAGS} \
-  ${GLIB_CFLAGS}
+  ${GLIB_CFLAGS} \
+  $(BOOST_CPPFLAGS)
 
 LDADD = \
   ../libgnc-core-utils.la \
   ${top_builddir}/common/test-core/libtest-core.la \
-  ${GLIB_LIBS}
+  ${GLIB_LIBS} \
+  ${BOOST_LDFLAGS}
 
 # these tests are ordered kind more or less in the order
 # that they should be executed, with more basic tests coming first.
 #
 
 TESTS = \
+  test-gnc-glib-utils \
   test-resolve-file-path \
-  test-gnc-glib-utils
+  test-usr-conf-dir \
+  test-usr-conf-dir-invalid-home
 
 GNC_TEST_DEPS = \
   --library-dir    ${top_builddir}/libgnucash/core-utils
diff --git a/libgnucash/core-utils/test/test-resolve-file-path.c b/libgnucash/core-utils/test/test-resolve-file-path.c
index b79d136..ada10a7 100644
--- a/libgnucash/core-utils/test/test-resolve-file-path.c
+++ b/libgnucash/core-utils/test/test-resolve-file-path.c
@@ -29,16 +29,16 @@
 #include "test-stuff.h"
 #include "gnc-filepath-utils.h"
 
-struct test_strings_struct
+struct relpath_strings_struct
 {
     char *input;
     char *output;
     int prefix_home;
 };
 
-typedef struct test_strings_struct test_strings;
+typedef struct relpath_strings_struct relpath_strings;
 
-test_strings strs[] =
+relpath_strings strs[] =
 {
     {
         G_DIR_SEPARATOR_S ".gnucash" G_DIR_SEPARATOR_S "test-account-name",
@@ -57,6 +57,18 @@ int
 main(int argc, char **argv)
 {
     int i;
+    char *home_dir = NULL;
+
+    if (argc > 1)
+        /* One can pass a homedir on the command line. This
+         * will most likely cause the test to fail, but it can be
+         * used to pass invalid home directories manually. The
+         * test error messages should then show the system's temporary
+         * directory to be used instead */
+        home_dir = argv[1];
+    else
+        /* Set up a fake home directory to play with */
+        home_dir = g_dir_make_tmp("gnucashXXXXXX", NULL);
 
     for (i = 0; strs[i].input != NULL; i++)
     {
@@ -66,15 +78,15 @@ main(int argc, char **argv)
 
         if (strs[i].prefix_home == 1)
         {
-            dain = g_build_filename(g_get_home_dir(), strs[i].input,
+            dain = g_build_filename(home_dir, strs[i].input,
                                     (gchar *)NULL);
-            wantout = g_build_filename(g_get_home_dir(), strs[i].output,
+            wantout = g_build_filename(home_dir, strs[i].output,
                                        (gchar *)NULL);
         }
         else if (strs[i].prefix_home == 2)
         {
             dain = g_strdup(strs[i].input);
-            wantout = g_build_filename(g_get_home_dir(), strs[i].output,
+            wantout = g_build_filename(home_dir, strs[i].output,
                                        (gchar *)NULL);
         }
         else
@@ -92,6 +104,7 @@ main(int argc, char **argv)
         g_free(wantout);
         g_free(daout);
     }
+
     print_test_results();
     return get_rv();
 }
diff --git a/libgnucash/core-utils/test/test-usr-conf-dir-invalid-home.c b/libgnucash/core-utils/test/test-usr-conf-dir-invalid-home.c
new file mode 100644
index 0000000..790f818
--- /dev/null
+++ b/libgnucash/core-utils/test/test-usr-conf-dir-invalid-home.c
@@ -0,0 +1,110 @@
+/***************************************************************************
+ *            test-resolve-file-path.c
+ *
+ *  Thu Sep 29 22:48:57 2005
+ *  Copyright  2005  GnuCash team
+ ****************************************************************************/
+/*
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ *  02110-1301, USA.
+ */
+
+#include "config.h"
+#include <stdlib.h>
+#include <string.h>
+
+#include <glib.h>
+#include "test-stuff.h"
+#include "gnc-filepath-utils.h"
+
+struct usr_confpath_strings_struct
+{
+    int func_num;
+    char *funcname;
+    char *output;
+};
+
+typedef struct usr_confpath_strings_struct usr_confpath_strings;
+
+usr_confpath_strings strs2[] =
+{
+    {
+        0, "gnc_build_userdata_path",
+        ".gnucash"
+    },
+    {
+        1, "gnc_build_book_path",
+        ".gnucash" G_DIR_SEPARATOR_S "books"
+    },
+    {
+        2, "gnc_build_translog_path",
+        ".gnucash" G_DIR_SEPARATOR_S "translog"
+    },
+    {
+        3, "gnc_build_data_path",
+        ".gnucash" G_DIR_SEPARATOR_S "data"
+    },
+    { 0, NULL, NULL },
+};
+
+int
+main(G_GNUC_UNUSED int argc, G_GNUC_UNUSED char **argv)
+{
+    int i;
+    const char *tmp_dir = g_get_tmp_dir();
+
+    /* Run usr conf dir tests with a valid and writable homedir */
+    g_setenv("HOME", "/notexist", TRUE);
+    for (i = 0; strs2[i].funcname != NULL; i++)
+    {
+        char *daout;
+        char *wantout;
+
+        if (strs2[i].func_num == 0)
+        {
+            wantout = g_build_filename(tmp_dir, strs2[i].output, "foo",
+                                       (gchar *)NULL);
+            daout = gnc_build_userdata_path("foo");
+        }
+        else if (strs2[i].func_num == 1)
+        {
+            wantout = g_build_filename(tmp_dir, strs2[i].output, "foo",
+                                       (gchar *)NULL);
+            daout = gnc_build_book_path("foo");
+        }
+        else if (strs2[i].func_num == 2)
+        {
+            wantout = g_build_filename(tmp_dir, strs2[i].output, "foo",
+                                       (gchar *)NULL);
+            daout = gnc_build_translog_path("foo");
+        }
+        else // if (strs2[i].prefix_home == 3)
+        {
+            wantout = g_build_filename(tmp_dir, strs2[i].output, "foo",
+                                       (gchar *)NULL);
+            daout = gnc_build_data_path("foo");
+        }
+
+        do_test_args(g_strcmp0(daout, wantout) == 0,
+                     "gnc_build_x_path",
+                     __FILE__, __LINE__,
+                     "%s (%s) vs %s", daout, strs2[i].funcname, wantout);
+        g_free(wantout);
+        g_free(daout);
+    }
+
+    print_test_results();
+    return get_rv();
+}
diff --git a/libgnucash/core-utils/test/test-usr-conf-dir.c b/libgnucash/core-utils/test/test-usr-conf-dir.c
new file mode 100644
index 0000000..91a6c67
--- /dev/null
+++ b/libgnucash/core-utils/test/test-usr-conf-dir.c
@@ -0,0 +1,121 @@
+/***************************************************************************
+ *            test-resolve-file-path.c
+ *
+ *  Thu Sep 29 22:48:57 2005
+ *  Copyright  2005  GnuCash team
+ ****************************************************************************/
+/*
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ *  02110-1301, USA.
+ */
+
+#include "config.h"
+#include <stdlib.h>
+#include <string.h>
+
+#include <glib.h>
+#include "test-stuff.h"
+#include "gnc-filepath-utils.h"
+
+struct usr_confpath_strings_struct
+{
+    int func_num;
+    char *funcname;
+    char *output;
+};
+
+typedef struct usr_confpath_strings_struct usr_confpath_strings;
+
+usr_confpath_strings strs2[] =
+{
+    {
+        0, "gnc_build_userdata_path",
+        ".gnucash"
+    },
+    {
+        1, "gnc_build_book_path",
+        ".gnucash" G_DIR_SEPARATOR_S "books"
+    },
+    {
+        2, "gnc_build_translog_path",
+        ".gnucash" G_DIR_SEPARATOR_S "translog"
+    },
+    {
+        3, "gnc_build_data_path",
+        ".gnucash" G_DIR_SEPARATOR_S "data"
+    },
+    { 0, NULL, NULL },
+};
+
+int
+main(int argc, char **argv)
+{
+    int i;
+    char *home_dir = NULL;
+
+    if (argc > 1)
+        /* One can pass a homedir on the command line. This
+         * will most likely cause the test to fail, but it can be
+         * used to pass invalid home directories manually. The
+         * test error messages should then show the system's temporary
+         * directory to be used instead */
+        home_dir = argv[1];
+    else
+        /* Set up a fake home directory to play with */
+        home_dir = g_dir_make_tmp("gnucashXXXXXX", NULL);
+
+    /* Run usr conf dir tests with a valid and writable homedir */
+    g_setenv("HOME", home_dir, TRUE);
+    for (i = 0; strs2[i].funcname != NULL; i++)
+    {
+        char *daout;
+        char *wantout;
+
+        if (strs2[i].func_num == 0)
+        {
+            wantout = g_build_filename(home_dir, strs2[i].output, "foo",
+                                       (gchar *)NULL);
+            daout = gnc_build_userdata_path("foo");
+        }
+        else if (strs2[i].func_num == 1)
+        {
+            wantout = g_build_filename(home_dir, strs2[i].output, "foo",
+                                       (gchar *)NULL);
+            daout = gnc_build_book_path("foo");
+        }
+        else if (strs2[i].func_num == 2)
+        {
+            wantout = g_build_filename(home_dir, strs2[i].output, "foo",
+                                       (gchar *)NULL);
+            daout = gnc_build_translog_path("foo");
+        }
+        else // if (strs2[i].prefix_home == 3)
+        {
+            wantout = g_build_filename(home_dir, strs2[i].output, "foo",
+                                       (gchar *)NULL);
+            daout = gnc_build_data_path("foo");
+        }
+
+        do_test_args(g_strcmp0(daout, wantout) == 0,
+                     "gnc_build_x_path",
+                     __FILE__, __LINE__,
+                     "%s (%s) vs %s", daout, strs2[i].funcname, wantout);
+        g_free(wantout);
+        g_free(daout);
+    }
+
+    print_test_results();
+    return get_rv();
+}
diff --git a/po/POTFILES.in b/po/POTFILES.in
index cb4a777..3cf6ed4 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -598,7 +598,7 @@ libgnucash/backend/xml/sixtp-utils.cpp
 libgnucash/core-utils/binreloc.c
 libgnucash/core-utils/core-utils.scm
 libgnucash/core-utils/gnc-environment.c
-libgnucash/core-utils/gnc-filepath-utils.c
+libgnucash/core-utils/gnc-filepath-utils.cpp
 libgnucash/core-utils/gnc-gkeyfile-utils.c
 libgnucash/core-utils/gnc-glib-utils.c
 libgnucash/core-utils/gnc-guile-utils.c



Summary of changes:
 CMakeLists.txt                                     |   2 +-
 gnucash/environment.in                             |  31 +-
 gnucash/gnome-utils/gnc-file.c                     |   4 +-
 gnucash/gnome-utils/gnc-gnome-utils.c              |  14 +-
 gnucash/gnome/dialog-print-check.c                 |   6 +-
 gnucash/gnucash-bin.c                              |  33 +-
 gnucash/import-export/qif-imp/qif-guess-map.scm    |   4 +-
 .../report/report-gnome/gnc-plugin-page-report.c   |   2 +-
 gnucash/report/report-system/eguile-utilities.scm  |   2 +-
 gnucash/report/report-system/gnc-report.c          |   6 +-
 gnucash/report/report-system/html-style-sheet.scm  |   2 +-
 gnucash/report/report-system/report-collectors.scm |   1 -
 libgnucash/app-utils/gfec.c                        |   2 +-
 libgnucash/app-utils/gfec.h                        |   2 +-
 libgnucash/app-utils/gnc-exp-parser.c              |   2 +-
 libgnucash/core-utils/CMakeLists.txt               |   4 +-
 libgnucash/core-utils/Makefile.am                  |   8 +-
 libgnucash/core-utils/core-utils.i                 |   4 +-
 libgnucash/core-utils/core-utils.scm               |   2 +-
 libgnucash/core-utils/gnc-environment.c            | 107 +++--
 ...gnc-filepath-utils.c => gnc-filepath-utils.cpp} | 500 +++++++++++++--------
 libgnucash/core-utils/gnc-filepath-utils.h         |  25 +-
 libgnucash/core-utils/test/CMakeLists.txt          |   5 +-
 libgnucash/core-utils/test/Makefile.am             |  10 +-
 .../core-utils/test/test-resolve-file-path.c       |  25 +-
 .../test/test-userdata-dir-invalid-home.c          | 110 +++++
 libgnucash/core-utils/test/test-userdata-dir.c     | 205 +++++++++
 po/POTFILES.in                                     |   2 +-
 28 files changed, 844 insertions(+), 276 deletions(-)
 rename libgnucash/core-utils/{gnc-filepath-utils.c => gnc-filepath-utils.cpp} (52%)
 create mode 100644 libgnucash/core-utils/test/test-userdata-dir-invalid-home.c
 create mode 100644 libgnucash/core-utils/test/test-userdata-dir.c



More information about the gnucash-changes mailing list