App: Migrate directory handling to helper class

Use std::filesystem wherever possible, replacing most uses of
std::string when the object is actually a path. This is the first stage
of refactoring, and does not make any changes to Application that affect
client code. Access to the new directory-handling class is implemented,
but is unused by any external code.
This commit is contained in:
Chris Hennes
2025-08-22 21:49:08 -05:00
committed by Chris Hennes
parent 5fe310a3c2
commit 3426c9ff21
6 changed files with 796 additions and 41 deletions

View File

@@ -102,6 +102,7 @@
#include "Annotation.h"
#include "Application.h"
#include "ApplicationDirectories.h"
#include "CleanupProcess.h"
#include "ComplexGeoData.h"
#include "Services.h"
@@ -185,6 +186,7 @@ Base::ConsoleObserverStd *Application::_pConsoleObserverStd = nullptr;
Base::ConsoleObserverFile *Application::_pConsoleObserverFile = nullptr;
AppExport std::map<std::string, std::string> Application::mConfig;
std::shared_ptr<ApplicationDirectories> Application::_appDirs;
//**************************************************************************
@@ -1113,7 +1115,7 @@ int64_t Application::applicationPid()
std::string Application::getHomePath()
{
return mConfig["AppHomePath"];
return Base::FileInfo::pathToString(Application::directories()->getHomePath()) + PATHSEP;
}
std::string Application::getExecutableName()
@@ -1140,78 +1142,53 @@ bool Application::isDevelopmentVersion()
return suffix == "dev";
}
std::shared_ptr<ApplicationDirectories> Application::directories() {
return _appDirs;
}
std::string Application::getTempPath()
{
return mConfig["AppTempPath"];
return Base::FileInfo::pathToString(_appDirs->getTempPath()) + PATHSEP;
}
std::string Application::getTempFileName(const char* FileName)
{
return Base::FileInfo::getTempFileName(FileName, getTempPath().c_str());
return Base::FileInfo::pathToString(_appDirs->getTempFileName(FileName)) + PATHSEP;
}
std::string Application::getUserCachePath()
{
return mConfig["UserCachePath"];
return Base::FileInfo::pathToString(_appDirs->getUserCachePath()) + PATHSEP;
}
std::string Application::getUserConfigPath()
{
return mConfig["UserConfigPath"];
return Base::FileInfo::pathToString(_appDirs->getUserConfigPath()) + PATHSEP;
}
std::string Application::getUserAppDataDir()
{
return mConfig["UserAppData"];
return Base::FileInfo::pathToString(_appDirs->getUserAppDataDir()) + PATHSEP;
}
std::string Application::getUserMacroDir()
{
return mConfig["UserMacroPath"];
return Base::FileInfo::pathToString(_appDirs->getUserMacroDir()) + PATHSEP;
}
std::string Application::getResourceDir()
{
#ifdef RESOURCEDIR
// #6892: Conda may inject null characters => remove them using c_str()
std::string path = std::string(RESOURCEDIR).c_str();
path += PATHSEP;
const QDir dir(QString::fromStdString(path));
if (dir.isAbsolute())
return path;
return mConfig["AppHomePath"] + path;
#else
return mConfig["AppHomePath"];
#endif
return Base::FileInfo::pathToString(_appDirs->getResourceDir()) + PATHSEP;
}
std::string Application::getLibraryDir()
{
#ifdef LIBRARYDIR
// #6892: Conda may inject null characters => remove them using c_str()
std::string path = std::string(LIBRARYDIR).c_str();
const QDir dir(QString::fromStdString(path));
if (dir.isAbsolute())
return path;
return mConfig["AppHomePath"] + path;
#else
return mConfig["AppHomePath"] + "lib";
#endif
return Base::FileInfo::pathToString(_appDirs->getLibraryDir()) + PATHSEP;
}
std::string Application::getHelpDir()
{
#ifdef DOCDIR
// #6892: Conda may inject null characters => remove them using c_str()
std::string path = std::string(DOCDIR).c_str();
path += PATHSEP;
const QDir dir(QString::fromStdString(path));
if (dir.isAbsolute())
return path;
return mConfig["AppHomePath"] + path;
#else
return mConfig["DocPath"];
#endif
return Base::FileInfo::pathToString(_appDirs->getHelpDir()) + PATHSEP;
}
int Application::checkLinkDepth(int depth, MessageOption option)
@@ -2535,7 +2512,7 @@ void processProgramOptions(const boost::program_options::variables_map& vm, std:
void Application::initConfig(int argc, char ** argv)
{
// find the home path....
mConfig["AppHomePath"] = FindHomePath(argv[0]);
mConfig["AppHomePath"] = ApplicationDirectories::findHomePath(argv[0]).string();
// Version of the application extracted from SubWCRef into src/Build/Version.h
// We only set these keys if not yet defined. Therefore it suffices to search
@@ -2593,7 +2570,7 @@ void Application::initConfig(int argc, char ** argv)
}
// extract home paths
ExtractUserPath();
_appDirs = std::make_shared<ApplicationDirectories>(mConfig);
if (vm.contains("safe-mode")) {
SafeMode::StartSafeMode();

View File

@@ -53,6 +53,7 @@ namespace App
class Document;
class DocumentObject;
class ApplicationDirectories;
class ApplicationObserver;
class Property;
class AutoTransaction;
@@ -423,6 +424,10 @@ public:
static std::string getExecutableName();
static std::string getNameWithVersion();
static bool isDevelopmentVersion();
/// Access to the various directories for the program a replacement for the get*Path methods below
static std::shared_ptr<ApplicationDirectories> directories();
/*!
Returns the temporary directory. By default, this is set to the
system's temporary directory but can be customized by the user.
@@ -626,6 +631,8 @@ private:
static void SaveEnv(const char *);
/// startup configuration container
static std::map<std::string,std::string> mConfig;
/// Management of and access to applications directories
static std::shared_ptr<ApplicationDirectories> _appDirs;
static int _argc;
static char ** _argv;
//@}

View File

@@ -0,0 +1,581 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
/***************************************************************************************************
* *
* Copyright (c) 2002 Jürgen Riegel <juergen.riegel@web.de> *
* Copyright (c) 2025 The FreeCAD project association AISBL *
* *
* This file is part of FreeCAD. *
* *
* FreeCAD is free software: you can redistribute it and/or modify it under the terms of the *
* GNU Lesser General Public License as published by the Free Software Foundation, either *
* version 2.1 of the License, or (at your option) any later version. *
* *
* FreeCAD 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 Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public License along with FreeCAD. *
* If not, see <https://www.gnu.org/licenses/>. *
* *
**************************************************************************************************/
#include "PreCompiled.h"
#ifndef _PreComp_
#include <format>
#include <utility>
#include <QDir>
#include <QProcessEnvironment>
#include <QStandardPaths>
#include <QCoreApplication>
#endif
#include "ApplicationDirectories.h"
#if defined(FC_OS_LINUX) || defined(FC_OS_MACOSX) || defined(FC_OS_BSD)
#include <pwd.h>
#endif
#include <Base/FileInfo.h>
#include <Base/Exception.h>
#include <Python.h>
#include <QString>
using namespace App;
namespace fs = std::filesystem;
fs::path qstringToPath(const QString& path)
{
#if defined(FC_OS_WIN32)
return {path.toStdWString()};
#else
return {path.toStdString()};
#endif
}
ApplicationDirectories::ApplicationDirectories(std::map<std::string,std::string> &config)
{
try {
int major = std::stoi(config.at("BuildVersionMajor"));
int minor = std::stoi(config.at("BuildVersionMinor"));
_currentVersion = std::make_tuple(major, minor);
} catch (const std::exception& e) {
throw Base::RuntimeError("Failed to parse version from config: " + std::string(e.what()));
}
configurePaths(config);
configureResourceDirectory(config);
configureLibraryDirectory(config);
configureHelpDirectory(config);
}
const fs::path& ApplicationDirectories::getHomePath() const
{
return this->_home;
}
const fs::path& ApplicationDirectories::getTempPath() const {
return this->_temp;
}
fs::path ApplicationDirectories::getTempFileName(const std::string & filename) const {
return Base::FileInfo::getTempFileName(filename.c_str(), getTempPath().string().c_str());
}
const fs::path& ApplicationDirectories::getUserCachePath() const
{
return this->_userCache;
}
const fs::path& ApplicationDirectories::getUserAppDataDir() const
{
return this->_userAppData;
}
const fs::path& ApplicationDirectories::getUserMacroDir() const
{
return this->_userMacro;
}
const fs::path& ApplicationDirectories::getResourceDir() const
{
return this->_resource;
}
const fs::path& ApplicationDirectories::getHelpDir() const
{
return this->_help;
}
const fs::path& ApplicationDirectories::getUserConfigPath() const {
return this->_userConfig;
}
const fs::path& ApplicationDirectories::getLibraryDir() const {
return this->_library;
}
/*!
* \brief findPath
* Returns the path where to store application files to.
* If \a customHome is not empty, it will be used, otherwise a path starting from \a stdHome will be used.
*/
fs::path ApplicationDirectories::findPath(const fs::path& stdHome, const fs::path& customHome,
const std::vector<std::string>& subdirs, bool create) {
fs::path appData = customHome;
if (appData.empty()) {
appData = stdHome;
}
// If a custom user home path is given, then don't modify it
if (customHome.empty()) {
for (const auto& it : subdirs) {
appData = appData / it;
}
}
// To write to our data path, we must create some directories, first.
if (create && !fs::exists(appData) && !Py_IsInitialized()) {
try {
fs::create_directories(appData);
} catch (const fs::filesystem_error& e) {
throw Base::FileSystemError("Could not create directories. Failed with: " + e.code().message());
}
}
return appData;
}
void ApplicationDirectories::configurePaths(std::map<std::string,std::string>& mConfig)
{
bool keepDeprecatedPaths = mConfig.contains("KeepDeprecatedPaths");
// std paths
_home = fs::path(mConfig.at("AppHomePath"));
mConfig["BinPath"] = mConfig.at("AppHomePath") + "bin" + PATHSEP;
mConfig["DocPath"] = mConfig.at("AppHomePath") + "doc" + PATHSEP;
// this is to support a portable version of FreeCAD
auto [customHome, customData, customTemp] = getCustomPaths();
// get the system standard paths
auto [configHome, dataHome, cacheHome, tempPath] = getStandardPaths();
// User home path
//
fs::path homePath = findUserHomePath(customHome);
mConfig["UserHomePath"] = Base::FileInfo::pathToString(homePath);
// the old path name to save config and data files
std::vector<std::string> subdirs;
if (keepDeprecatedPaths) {
configHome = homePath;
dataHome = homePath;
cacheHome = homePath;
getOldDataLocation(mConfig, subdirs);
}
else {
getSubDirectories(mConfig, subdirs);
}
// User data path
//
fs::path data = findPath(dataHome, customData, subdirs, true);
_userAppData = data;
mConfig["UserAppData"] = Base::FileInfo::pathToString(_userAppData) + PATHSEP;
// User config path
//
fs::path config = findPath(configHome, customHome, subdirs, true);
_userConfig = config;
mConfig["UserConfigPath"] = Base::FileInfo::pathToString(_userConfig) + PATHSEP;
// User cache path
//
std::vector<std::string> cachedirs = subdirs;
cachedirs.emplace_back("Cache");
fs::path cache = findPath(cacheHome, customTemp, cachedirs, true);
_userCache = cache;
mConfig["UserCachePath"] = Base::FileInfo::pathToString(cache) + PATHSEP;
// Set application temporary directory
//
std::vector<std::string> empty;
fs::path tmp = findPath(tempPath, customTemp, empty, true);
_temp = tmp;
mConfig["AppTempPath"] = Base::FileInfo::pathToString(tmp) + PATHSEP;
// Set the default macro directory
//
std::vector<std::string> macrodirs = std::move(subdirs); // Last use in this method, just move
macrodirs.emplace_back("Macro");
fs::path macro = findPath(dataHome, customData, macrodirs, true);
_userMacro = macro;
mConfig["UserMacroPath"] = Base::FileInfo::pathToString(macro) + PATHSEP;
}
void ApplicationDirectories::configureResourceDirectory(const std::map<std::string,std::string>& mConfig) {
#ifdef RESOURCEDIR
// #6892: Conda may inject null characters => remove them using c_str()
fs::path path {std::string(RESOURCEDIR).c_str()};
if (path.is_absolute()) {
_resource = path;
} else {
_resource = Base::FileInfo::stringToPath(mConfig.at("AppHomePath")) / path;
}
#else
_resource = fs::path(mConfig.at("AppHomePath"));
#endif
}
void ApplicationDirectories::configureLibraryDirectory(const std::map<std::string,std::string>& mConfig) {
#ifdef LIBRARYDIR
// #6892: Conda may inject null characters => remove them using c_str()
fs::path path {std::string(LIBRARYDIR).c_str()};
if (path.is_absolute()) {
_library = path;
} else {
_library = Base::FileInfo::stringToPath(mConfig.at("AppHomePath")) / path;
}
#else
_library = Base::FileInfo::stringToPath(mConfig.at("AppHomePath")) / "lib";
#endif
}
void ApplicationDirectories::configureHelpDirectory(const std::map<std::string,std::string>& mConfig)
{
#ifdef DOCDIR
// #6892: Conda may inject null characters => remove them using c_str()
fs::path path {std::string(DOCDIR).c_str()};
if (path.is_absolute()) {
_help = path;
} else {
_help = Base::FileInfo::stringToPath(mConfig.at("AppHomePath")) / path;
}
#else
_help = Base::FileInfo::stringToPath(mConfig.at("DocPath"));
#endif
}
fs::path ApplicationDirectories::getUserHome()
{
fs::path path;
#if defined(FC_OS_LINUX) || defined(FC_OS_CYGWIN) || defined(FC_OS_BSD) || defined(FC_OS_MACOSX)
// Default paths for the user-specific stuff
struct passwd pwd {};
struct passwd *result {};
constexpr std::size_t bufferLength = 16384;
std::vector<char> buffer(bufferLength);
const int error = getpwuid_r(getuid(), &pwd, buffer.data(), buffer.size(), &result);
if (!result || error != 0) {
throw Base::RuntimeError("Getting HOME path from system failed!");
}
path = Base::FileInfo::stringToPath(result->pw_dir);
#else
path = Base::FileInfo::stringToPath(QStandardPaths::writableLocation(QStandardPaths::HomeLocation).toStdString());
#endif
return path;
}
#if defined(FC_OS_WIN32) // This is ONLY used on Windows now, so don't even compile it elsewhere
#include <codecvt>
#include "ShlObj.h"
QString ApplicationDirectories::getOldGenericDataLocation()
{
WCHAR szPath[MAX_PATH];
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
if (SUCCEEDED(SHGetFolderPathW(NULL, CSIDL_APPDATA, NULL, 0, szPath))) {
return QString::fromStdString(converter.to_bytes(szPath));
}
return {};
}
#endif
void ApplicationDirectories::getSubDirectories(const std::map<std::string,std::string>& mConfig,
std::vector<std::string>& appData)
{
// If 'AppDataSkipVendor' is defined, the value of 'ExeVendor' must not be part of
// the path.
if (!mConfig.contains("AppDataSkipVendor") && mConfig.contains("ExeVendor")) {
appData.push_back(mConfig.at("ExeVendor"));
}
appData.push_back(mConfig.at("ExeName"));
}
void ApplicationDirectories::getOldDataLocation(const std::map<std::string,std::string>& mConfig,
std::vector<std::string>& appData)
{
// The name of the directory where the parameters are stored should be the name of
// the application (for branding reasons).
#if defined(FC_OS_LINUX) || defined(FC_OS_CYGWIN) || defined(FC_OS_BSD)
// If 'AppDataSkipVendor' is defined, the value of 'ExeVendor' must not be part of
// the path.
if (!mConfig.contains("AppDataSkipVendor")) {
appData.push_back(std::string(".") + mConfig.at("ExeVendor"));
appData.push_back(mConfig.at("ExeName"));
} else {
appData.push_back(std::string(".") + mConfig.at("ExeName"));
}
#elif defined(FC_OS_MACOSX) || defined(FC_OS_WIN32)
getSubDirectories(mConfig, appData);
#endif
}
fs::path ApplicationDirectories::findUserHomePath(const fs::path& userHome)
{
return userHome.empty() ? getUserHome() : userHome;
}
std::tuple<fs::path, fs::path, fs::path> ApplicationDirectories::getCustomPaths()
{
const QProcessEnvironment env(QProcessEnvironment::systemEnvironment());
QString userHome = env.value(QStringLiteral("FREECAD_USER_HOME"));
QString userData = env.value(QStringLiteral("FREECAD_USER_DATA"));
QString userTemp = env.value(QStringLiteral("FREECAD_USER_TEMP"));
auto toNativePath = [](QString& path) {
if (!path.isEmpty()) {
if (const QDir dir(path); dir.exists()) {
path = QDir::toNativeSeparators(dir.canonicalPath());
}
else {
path.clear();
}
}
};
// verify env. variables
toNativePath(userHome);
toNativePath(userData);
toNativePath(userTemp);
// if FREECAD_USER_HOME is set but not FREECAD_USER_DATA
if (!userHome.isEmpty() && userData.isEmpty()) {
userData = userHome;
}
// if FREECAD_USER_HOME is set but not FREECAD_USER_TEMP
if (!userHome.isEmpty() && userTemp.isEmpty()) {
const QDir dir(userHome);
dir.mkdir(QStringLiteral("temp"));
const QFileInfo fi(dir, QStringLiteral("temp"));
userTemp = fi.absoluteFilePath();
}
return {qstringToPath(userHome),
qstringToPath(userData),
qstringToPath(userTemp)};
}
std::tuple<fs::path, fs::path, fs::path, fs::path> ApplicationDirectories::getStandardPaths()
{
QString configHome = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation);
QString dataHome = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation);
QString cacheHome = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation);
QString tempPath = QStandardPaths::writableLocation(QStandardPaths::TempLocation);
// Keep the old behaviour
#if defined(FC_OS_WIN32)
configHome = getOldGenericDataLocation();
dataHome = configHome;
// On systems with non-7-bit-ASCII application data directories,
// GetTempPathW will return a path in DOS format. This path will be
// accepted by boost's file_lock class.
// Since boost 1.76, there is now a version that accepts a wide string.
#if (BOOST_VERSION < 107600)
tempPath = QString::fromStdString(Base::FileInfo::getTempPath());
cacheHome = tempPath;
#endif
#endif
return {qstringToPath(configHome),
qstringToPath(dataHome),
qstringToPath(cacheHome),
qstringToPath(tempPath)};
}
// TODO: Consider using this for all UNIX-like OSes
#if defined(__OpenBSD__)
#include <cstdio>
#include <cstdlib>
#include <sys/param.h>
#include <QCoreApplication>
fs::path ApplicationDirectories::findHomePath(const char* sCall)
{
// We have three ways to start this application either use one of the two executables or
// import the FreeCAD.so module from a running Python session. In the latter case the
// Python interpreter is already initialized.
std::string absPath;
std::string homePath;
if (Py_IsInitialized()) {
// Note: `realpath` is known to cause a buffer overflow because it
// expands the given path to an absolute path of unknown length.
// Even setting PATH_MAX does not necessarily solve the problem
// for sure, but the risk of overflow is rather small.
char resolved[PATH_MAX];
char* path = realpath(sCall, resolved);
if (path)
absPath = path;
}
else {
int argc = 1;
QCoreApplication app(argc, (char**)(&sCall));
absPath = QCoreApplication::applicationFilePath().toStdString();
}
// should be an absolute path now
std::string::size_type pos = absPath.find_last_of("/");
homePath.assign(absPath,0,pos);
pos = homePath.find_last_of("/");
homePath.assign(homePath,0,pos+1);
return Base::FileInfo::stringToPath(homePath);
}
#elif defined (FC_OS_LINUX) || defined(FC_OS_CYGWIN) || defined(FC_OS_BSD)
#include <cstdio>
#include <cstdlib>
#include <sys/param.h>
fs::path ApplicationDirectories::findHomePath(const char* sCall)
{
// We have three ways to start this application either use one of the two executables or
// import the FreeCAD.so module from a running Python session. In the latter case the
// Python interpreter is already initialized.
std::string absPath;
std::string homePath;
if (Py_IsInitialized()) {
// Note: `realpath` is known to cause a buffer overflow because it
// expands the given path to an absolute path of unknown length.
// Even setting PATH_MAX does not necessarily solve the problem
// for sure, but the risk of overflow is rather small.
char resolved[PATH_MAX];
char* path = realpath(sCall, resolved);
if (path)
absPath = path;
}
else {
// Find the path of the executable. Theoretically, there could occur a
// race condition when using readlink, but we only use this method to
// get the absolute path of the executable to compute the actual home
// path. In the worst case we simply get q wrong path, and FreeCAD is not
// able to load its modules.
char resolved[PATH_MAX];
#if defined(FC_OS_BSD)
int mib[4];
mib[0] = CTL_KERN;
mib[1] = KERN_PROC;
mib[2] = KERN_PROC_PATHNAME;
mib[3] = -1;
size_t cb = sizeof(resolved);
sysctl(mib, 4, resolved, &cb, NULL, 0);
int nchars = strlen(resolved);
#else
int nchars = readlink("/proc/self/exe", resolved, PATH_MAX);
#endif
if (nchars < 0 || nchars >= PATH_MAX)
throw Base::FileSystemError("Cannot determine the absolute path of the executable");
resolved[nchars] = '\0'; // enforce null termination
absPath = resolved;
}
// should be an absolute path now
std::string::size_type pos = absPath.find_last_of("/");
homePath.assign(absPath,0,pos);
pos = homePath.find_last_of("/");
homePath.assign(homePath,0,pos+1);
return Base::FileInfo::stringToPath(homePath);
}
#elif defined(FC_OS_MACOSX)
#include <mach-o/dyld.h>
#include <string>
#include <cstdlib>
#include <sys/param.h>
fs::path ApplicationDirectories::findHomePath(const char* sCall)
{
// If Python is initialized at this point, then we're being run from
// MainPy.cpp, which hopefully rewrote argv[0] to point at the
// FreeCAD shared library.
if (!Py_IsInitialized()) {
uint32_t sz = 0;
// function only returns "sz" if the first arg is too small to hold value
_NSGetExecutablePath(nullptr, &sz);
if (const auto buf = new char[++sz]; _NSGetExecutablePath(buf, &sz) == 0) {
std::array<char, PATH_MAX> resolved{};
const char* path = realpath(buf, resolved.data());
delete [] buf;
if (path) {
const std::string Call(resolved.data());
std::string TempHomePath;
std::string::size_type pos = Call.find_last_of(fs::path::preferred_separator);
TempHomePath.assign(Call,0,pos);
pos = TempHomePath.find_last_of(fs::path::preferred_separator);
TempHomePath.assign(TempHomePath,0,pos+1);
return Base::FileInfo::stringToPath(TempHomePath);
}
} else {
delete [] buf;
}
}
return Base::FileInfo::stringToPath(sCall);
}
#elif defined (FC_OS_WIN32)
fs::path ApplicationDirectories::findHomePath(const char* sCall)
{
// We have several ways to start this application:
// * use one of the two executables
// * import the FreeCAD.pyd module from a running Python session. In this case the
// Python interpreter is already initialized.
// * use a custom dll that links FreeCAD core dlls and that is loaded by its host application
// In this case the calling name should be set to FreeCADBase.dll or FreeCADApp.dll in order
// to locate the correct home directory
wchar_t szFileName [MAX_PATH];
QString dll(QString::fromUtf8(sCall));
if (Py_IsInitialized() || dll.endsWith(QLatin1String(".dll"))) {
GetModuleFileNameW(GetModuleHandleA(sCall),szFileName, MAX_PATH-1);
}
else {
GetModuleFileNameW(0, szFileName, MAX_PATH-1);
}
std::wstring Call(szFileName), homePath;
std::wstring::size_type pos = Call.find_last_of(fs::path::preferred_separator);
homePath.assign(Call,0,pos);
pos = homePath.find_last_of(fs::path::preferred_separator);
homePath.assign(homePath,0,pos+1);
// fixes #0001638 to avoid loading DLLs from Windows' system directories before FreeCAD's bin folder
std::wstring binPath = homePath;
binPath += L"bin";
SetDllDirectoryW(binPath.c_str());
// https://stackoverflow.com/questions/5625884/conversion-of-stdwstring-to-qstring-throws-linker-error
#ifdef _MSC_VER
QString str = QString::fromUtf16(reinterpret_cast<const ushort *>(homePath.c_str()));
#else
QString str = QString::fromStdWString(homePath);
#endif
return qstringToPath(str);
}
#else
# error "std::string ApplicationDirectories::FindHomePath(const char*) not implemented"
#endif

View File

@@ -0,0 +1,179 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
/***************************************************************************************************
* *
* Copyright (c) 2002 Jürgen Riegel <juergen.riegel@web.de> *
* Copyright (c) 2025 The FreeCAD project association AISBL *
* *
* This file is part of FreeCAD. *
* *
* FreeCAD is free software: you can redistribute it and/or modify it under the terms of the *
* GNU Lesser General Public License as published by the Free Software Foundation, either *
* version 2.1 of the License, or (at your option) any later version. *
* *
* FreeCAD 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 Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public License along with FreeCAD. *
* If not, see <https://www.gnu.org/licenses/>. *
* *
**************************************************************************************************/
#ifndef SRC_APP_APPLICATIONDIRECTORIES_H_
#define SRC_APP_APPLICATIONDIRECTORIES_H_
#include "FCGlobal.h"
#include <filesystem>
#include <map>
#include <string>
#include <vector>
namespace App {
/// A helper class to handle application-wide directory management on behalf of the main
/// App::Application class
class AppExport ApplicationDirectories {
public:
/// Constructor
/// \param currentVersion A tuple {major, minor} used to create/manage the versioned
/// directories, if any
explicit ApplicationDirectories(std::map<std::string,std::string> &config);
/// Given argv[0], figure out the home path
static std::filesystem::path findHomePath(const char* sCall);
/// "Home" here is the parent directory of the actual executable file being run. It is
/// exposed here primarily for historical compatibility reasons, and new code should almost
/// certainly NOT use this path for anything. See alternatives in the
/// `App::ApplicationDirectories` class.
const std::filesystem::path& getHomePath() const;
/// Temp path is the location of all temporary files: it is not guaranteed to preserve
/// information between runs of the program, but *is* guaranteed to exist for the duration
/// of a single program execution (that is, files are not deleted from it *during* the run).
const std::filesystem::path& getTempPath() const;
/// Get a file in the temp directory. WARNING: NOT THREAD-SAFE! Currently just forwards to
/// the FileInfo class. TODO: Rewrite to be thread safe
std::filesystem::path getTempFileName(const std::string & filename = "") const;
/// The user cache path can be used to store files that the program *prefers* not be deleted
/// between runs, but that will be recreated or otherwise handled if they do not exist due
/// to the cache being cleared. There is no guarantee that the files will exist from
/// run-to-run, but an effort is made to preserve them (unlike the temp directory, which
/// should never be used to save data between runs).
const std::filesystem::path& getUserCachePath() const;
/// The primary directory used to store per-user application data. This is the parent
/// directory of all installed addons, per-user configuration files, etc. Developers looking
/// for a place to put per-user data should begin here. Common subdirectories include "Mod",
/// "Macros", "Materials" and many others that don't begin with the letter "M". This is
/// typically a versioned directory, though users may choose to use a single path for
/// multiple versions of the software.
const std::filesystem::path& getUserAppDataDir() const;
/// Historically, a single directory was used to store user-created (or user-installed)
/// macro files. This is the path to that directory. Note that going forward it should *not*
/// be assumed that all installed macros are located in this directory. This is typically a
/// versioned directory, though users may choose to use a single path for multiple versions
/// of the software.
const std::filesystem::path& getUserMacroDir() const;
/// The "resource" directory should be used to store non-ephemeral resources such as icons,
/// templates, hardware setup, etc. -- items that should be preserved from run-to-run of the
/// program, and between versions. This is *not* a versioned directory, and multiple
/// versions of the software may access the same data.
const std::filesystem::path& getResourceDir() const;
/// Nominally, this is the directory where "help" files are stored, though for historical
/// reasons several other informational files are stored here as well. It should only be
/// used for user-facing informational files.
const std::filesystem::path& getHelpDir() const;
/// The root path of user config files `user.cfg` and `system.cfg`.
const std::filesystem::path& getUserConfigPath() const;
/// The directory of all extension modules. Added to `sys.path` during Python
/// initialization.
const std::filesystem::path& getLibraryDir() const;
/// Get the user's home directory
static std::filesystem::path getUserHome();
#ifdef FC_OS_WIN32
/// On Windows, gets the location of the user's "AppData" directory. Invalid on other OSes.
QString getOldGenericDataLocation();
#endif
/// Adds subdirectory information to the appData vector for use in constructing full paths to config files, etc.
static void getSubDirectories(const std::map<std::string,std::string>& mConfig,
std::vector<std::string>& appData);
/// To a given path it adds the subdirectories where to store application-specific files.
/// On Linux or BSD a hidden directory (i.e. starting with a dot) is added.
static void getOldDataLocation(const std::map<std::string,std::string>& mConfig,
std::vector<std::string>& appData);
/// If the passed path name is not empty, it will be returned, otherwise the user home path of the system will
/// be returned.
static std::filesystem::path findUserHomePath(const std::filesystem::path& userHome);
bool isVersionedPath(const std::filesystem::path &startingPath) const;
std::string mostRecentAvailableConfigVersion(const std::filesystem::path &startingPath) const;
std::filesystem::path mostRecentConfigFromBase(const std::filesystem::path &startingPath) const;
static void migrateConfig(const std::filesystem::path &oldPath, const std::filesystem::path &newPath);
protected:
static std::filesystem::path findPath(
const std::filesystem::path& stdHome,
const std::filesystem::path& customHome,
const std::vector<std::string>& paths,
bool create);
void configurePaths(std::map<std::string,std::string> &config);
void configureResourceDirectory(const std::map<std::string,std::string>& mConfig);
void configureLibraryDirectory(const std::map<std::string,std::string>& mConfig);
void configureHelpDirectory(const std::map<std::string,std::string>& mConfig);
/*!
* \brief getCustomPaths
* Returns a tuple of path names where to store config, data, and temp. files.
* The method therefore reads the environment variables:
* \list
* \li FREECAD_USER_HOME
* \li FREECAD_USER_DATA
* \li FREECAD_USER_TEMP
* \endlist
*/
static std::tuple<std::filesystem::path, std::filesystem::path, std::filesystem::path> getCustomPaths();
/*!
* \brief getStandardPaths
* Returns a tuple of XDG-compliant standard paths names where to store config, data and cached files.
* The method therefore reads the environment variables:
* \list
* \li XDG_CONFIG_HOME
* \li XDG_DATA_HOME
* \li XDG_CACHE_HOME
* \endlist
*/
std::tuple<std::filesystem::path, std::filesystem::path, std::filesystem::path, std::filesystem::path> getStandardPaths();
private:
std::tuple<int, int> _currentVersion;
std::filesystem::path _home;
std::filesystem::path _temp;
std::filesystem::path _userCache;
std::filesystem::path _userConfig;
std::filesystem::path _userAppData;
std::filesystem::path _userMacro;
std::filesystem::path _resource;
std::filesystem::path _library;
std::filesystem::path _help;
};
} // App
#endif //SRC_APP_APPLICATIONDIRECTORIES_H_

View File

@@ -273,6 +273,7 @@ SET(FreeCADApp_CPP_SRCS
${Document_CPP_SRCS}
${Properties_CPP_SRCS}
Application.cpp
ApplicationDirectories.cpp
ApplicationPy.cpp
AutoTransaction.cpp
Branding.cpp
@@ -303,6 +304,7 @@ SET(FreeCADApp_HPP_SRCS
${Document_HPP_SRCS}
${Properties_HPP_SRCS}
Application.h
ApplicationDirectories.h
AutoTransaction.h
Branding.h
CleanupProcess.h

View File

@@ -70,6 +70,9 @@
// STL
#include <bitset>
#include <chrono>
#if defined(FC_OS_WIN32)
#include <codecvt>
#endif
#include <exception>
#include <functional>
#include <iterator>
@@ -101,6 +104,12 @@
#include <fmt/format.h>
// Qt -- only QtCore
#include <QDir>
#include <QProcessEnvironment>
#include <QStandardPaths>
#include <QString>
#endif //_PreComp_
#endif // APP_PRECOMPILED_H