From 1383fde3457168ed759d6ed3b913dfae8a6085ef Mon Sep 17 00:00:00 2001
From: Martin Braun <martin.braun@ettus.com>
Date: Thu, 5 Mar 2020 16:23:07 -0800
Subject: uhd: paths: Harmonize around XDG Base Directory specification

Up until now, we completely ignore the XDG specification.

This commit does the following to change that:

- It uses XDG_DATA_HOME and XDG_CONFIG_HOME for cal and config data,
  respectively.
- If config data is in ~/.uhd/uhd.conf, that is still accepted, but if
  it conflicts with $XDG_CONFIG_HOME/uhd.conf, it is ignored and a
  warning is displayed
- The default location for cal data is thus ${HOME}/.local/share/uhd/cal
  on Unix, and %LOCALAPPDATA%\uhd\cal on Windows. This is a change in
  location!
- The UHD_CONFIG_DIR environment variable was confusingly named and is
  now removed. It provided an alternative location than the home
  directory. The same purpose is now much better served by XDG_DATA_HOME
  and XDG_CONFIG_HOME.

The specification can be found here:
specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
---
 host/lib/utils/paths.cpp        | 117 ++++++++++++++++++++++++++++++----------
 host/lib/utils/paths_python.hpp |   4 +-
 host/lib/utils/prefs.cpp        |  28 ++++++++--
 3 files changed, 115 insertions(+), 34 deletions(-)

(limited to 'host/lib/utils')

diff --git a/host/lib/utils/paths.cpp b/host/lib/utils/paths.cpp
index 3ecd5fe38..54b8e0323 100644
--- a/host/lib/utils/paths.cpp
+++ b/host/lib/utils/paths.cpp
@@ -35,6 +35,8 @@
 
 namespace fs = boost::filesystem;
 
+static constexpr char UHD_CAL_DATA_PATH_VAR[] = "UHD_CAL_DATA_PATH";
+
 /*! Get the value of an environment variable.
  *
  * The returned std::string is the full environment variable string, and thus
@@ -128,6 +130,89 @@ static std::string expand_home_directory(std::string path)
 }
 #endif
 
+fs::path uhd::get_xdg_data_home()
+{
+    std::string xdg_data_home_str = get_env_var("XDG_DATA_HOME", "");
+    fs::path xdg_data_home(xdg_data_home_str);
+    if (!xdg_data_home_str.empty()) {
+        return fs::path(xdg_data_home_str);
+    }
+#ifdef UHD_PLATFORM_WIN32
+    const std::string localappdata = get_env_var("LOCALAPPDATA", "");
+    if (!localappdata.empty()) {
+        return fs::path(localappdata);
+    }
+    const std::string appdata = get_env_var("APPDATA", "");
+    if (!appdata.empty()) {
+        return fs::path(appdata);
+    }
+#endif
+    const std::string home = get_env_var("HOME", "");
+    if (home.empty()) {
+#ifdef UHD_PLATFORM_WIN32
+        const std::string err_msg =
+            "get_xdg_data_home(): Unable to find \%HOME\%, \%XDG_DATA_HOME\%, "
+            "\%LOCALAPPDATA\% or \%APPDATA\%.";
+#else
+        const std::string err_msg =
+            "get_xdg_data_home(): Unable to find $HOME or $XDG_DATA_HOME.";
+#endif
+        throw uhd::runtime_error(err_msg);
+    }
+    return fs::path(home) / ".local" / "share";
+}
+
+fs::path uhd::get_xdg_config_home()
+{
+    std::string xdg_config_home_str = get_env_var("XDG_CONFIG_HOME", "");
+    fs::path xdg_config_home(xdg_config_home_str);
+    if (!xdg_config_home_str.empty()) {
+        return fs::path(xdg_config_home_str);
+    }
+#ifdef UHD_PLATFORM_WIN32
+    const std::string localappdata = get_env_var("LOCALAPPDATA", "");
+    if (!localappdata.empty()) {
+        return fs::path(localappdata);
+    }
+    const std::string appdata = get_env_var("APPDATA", "");
+    if (!appdata.empty()) {
+        return fs::path(appdata);
+    }
+#endif
+    const std::string home = get_env_var("HOME", "");
+    if (home.empty()) {
+#ifdef UHD_PLATFORM_WIN32
+        const std::string err_msg =
+            "get_xdg_config_home(): Unable to find \%HOME\%, \%XDG_CONFIG_HOME\%, "
+            "\%LOCALAPPDATA\% or \%APPDATA\%.";
+#else
+        const std::string err_msg =
+            "get_xdg_config_home(): Unable to find $HOME or $XDG_CONFIG_HOME.";
+#endif
+        throw uhd::runtime_error(err_msg);
+    }
+    return fs::path(home) / ".config";
+}
+
+fs::path uhd::get_legacy_config_home()
+{
+#ifdef UHD_PLATFORM_WIN32
+    const std::string localappdata = get_env_var("LOCALAPPDATA", "");
+    if (!localappdata.empty()) {
+        return fs::path(localappdata) / ".uhd";
+    }
+    const std::string appdata = get_env_var("APPDATA", "");
+    if (!appdata.empty()) {
+        return fs::path(appdata) / ".uhd";
+    }
+#endif
+    const std::string home = get_env_var("HOME", "");
+    if (home.empty()) {
+        throw uhd::runtime_error("Unable to find $HOME.");
+    }
+    return fs::path(home) / ".uhd";
+}
+
 /***********************************************************************
  * Implement the functions in paths.hpp
  **********************************************************************/
@@ -173,23 +258,6 @@ std::string uhd::get_tmp_path(void)
 #endif
 }
 
-std::string uhd::get_app_path(void)
-{
-    const std::string uhdcalib_path = get_env_var("UHD_CONFIG_DIR");
-    if (not uhdcalib_path.empty())
-        return uhdcalib_path;
-
-    const std::string appdata_path = get_env_var("APPDATA");
-    if (not appdata_path.empty())
-        return appdata_path;
-
-    const std::string home_path = get_env_var("HOME");
-    if (not home_path.empty())
-        return home_path;
-
-    return uhd::get_tmp_path();
-}
-
 // Only used for deprecated CSV file loader. Delete this once CSV support is
 // removed.
 std::string uhd::get_appdata_path(void)
@@ -242,21 +310,14 @@ std::string uhd::get_lib_path(void)
 
 std::string uhd::get_cal_data_path(void)
 {
-    const std::string uhdcalib_path = get_env_var("UHD_CAL_DATA_PATH");
+    // The easy case: User has set the environment variable
+    const std::string uhdcalib_path = get_env_var(UHD_CAL_DATA_PATH_VAR);
     if (not uhdcalib_path.empty()) {
         return uhdcalib_path;
     }
 
-    std::string xdg_data_home_str = get_env_var("XDG_DATA_HOME", "");
-    fs::path xdg_data_home(xdg_data_home_str);
-    if (xdg_data_home_str.empty()) {
-        const std::string home = get_env_var("HOME", "");
-        xdg_data_home          = fs::path(home) / ".local" / "share";
-    }
-
-    // FIXME: This needs to check if paths make sense, work on Windows, etc.
-
-    fs::path cal_data_path = fs::path(xdg_data_home) / "uhd" / "cal_data";
+    // If not, we use the default location
+    const fs::path cal_data_path = get_xdg_data_home() / "uhd" / "cal";
     return cal_data_path.string();
 }
 
diff --git a/host/lib/utils/paths_python.hpp b/host/lib/utils/paths_python.hpp
index fb91ec373..f738904ca 100644
--- a/host/lib/utils/paths_python.hpp
+++ b/host/lib/utils/paths_python.hpp
@@ -12,9 +12,9 @@
 void export_paths(py::module& m)
 {
     m.def("get_tmp_path", &uhd::get_tmp_path);
-    m.def("get_app_path", &uhd::get_app_path);
-    m.def("get_pkg_path", &uhd::get_pkg_path);
     m.def("get_lib_path", &uhd::get_lib_path);
+    m.def("get_pkg_path", &uhd::get_pkg_path);
+    m.def("get_cal_data_path", &uhd::get_cal_data_path);
     m.def("get_images_dir", &uhd::get_images_dir);
     m.def("find_image_path", &uhd::find_image_path);
     m.def("find_utility", &uhd::find_utility);
diff --git a/host/lib/utils/prefs.cpp b/host/lib/utils/prefs.cpp
index 72a9c9eea..58c47f730 100644
--- a/host/lib/utils/prefs.cpp
+++ b/host/lib/utils/prefs.cpp
@@ -17,7 +17,7 @@ using namespace uhd;
 namespace {
 constexpr char UHD_CONF_FILE_VAR[] = "UHD_CONFIG_FILE";
 
-inline void _update_conf_file(
+inline bool _update_conf_file(
     const std::string& path, const std::string& config_type, config_parser& conf_file)
 {
     if (not path.empty()) {
@@ -27,15 +27,19 @@ inline void _update_conf_file(
                 conf_file.read_file(path);
                 UHD_LOG_DEBUG(
                     "PREFS", "Loaded " << config_type << " config file " << path);
+                return true;
             } catch (...) {
                 UHD_LOG_DEBUG(
                     "PREFS", "Failed to load " << config_type << " config file " << path);
+                return false;
             }
         } else {
             UHD_LOG_TRACE(
                 "PREFS", "No " << config_type << " config file found at " << path);
+            return false;
         }
     }
+    return false;
 }
 
 void update_from_key(
@@ -76,10 +80,26 @@ config_parser& uhd::prefs::get_uhd_config()
         UHD_LOG_TRACE("CONF", "Initializing config file object...");
         const std::string sys_conf_file = path_expandvars(UHD_SYS_CONF_FILE);
         _update_conf_file(sys_conf_file, "system", _conf_files);
+        // prefer .config/uhd.conf
+        // otherwise ~/.uhd/uhd.conf
         const std::string user_conf_file =
-            (boost::filesystem::path(get_app_path()) / std::string(UHD_USER_CONF_FILE))
-                .string();
-        _update_conf_file(user_conf_file, "user", _conf_files);
+            (get_xdg_config_home() / std::string(UHD_USER_CONF_FILE)).string();
+        const bool user_conf_loaded =
+            _update_conf_file(user_conf_file, "user", _conf_files);
+        // Config files can be in ~/.config/ or in ~/.uhd. The latter is
+        // considered deprecated. We load from there (if we have not already
+        // loaded from ~/.config), but we show a warning.
+        if (!user_conf_loaded
+            && _update_conf_file(
+                   (get_legacy_config_home() / std::string(UHD_USER_CONF_FILE)).string(),
+                   "user",
+                   _conf_files)) {
+            UHD_LOG_WARNING("PREFS",
+                "Loaded config from " << get_legacy_config_home().string()
+                                      << ". This location is considered deprecated, "
+                                         "consider moving your config file to "
+                                      << get_xdg_config_home().string() << " instead.");
+        }
         std::string env_conf_file;
         try { // getenv into std::string can fail
             if (std::getenv(UHD_CONF_FILE_VAR) != NULL) {
-- 
cgit v1.2.3