Download src/App/ApplicationDirectories.cpp from AbdulElahGwaith/FreeCAD: direct link, hf CLI and curl.
- Browser
- Download file 28.4 kB
-
https://huggingface.co/AbdulElahGwaith/FreeCAD/resolve/main/src/App/ApplicationDirectories.cpp
- Command line
-
hf download hf://AbdulElahGwaith/FreeCAD/src/App/ApplicationDirectories.cpp
-
curl -L -o ApplicationDirectories.cpp https://huggingface.co/AbdulElahGwaith/FreeCAD/resolve/main/src/App/ApplicationDirectories.cpp
28.4 kB
| // 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/>. * | |
| * * | |
| **************************************************************************************************/ | |
| using namespace App; | |
| namespace fs = std::filesystem; | |
| fs::path qstringToPath(const QString& path) | |
| { | |
| return {path.toStdWString()}; | |
| return {path.toStdString()}; | |
| } | |
| ApplicationDirectories::ApplicationDirectories(std::map<std::string,std::string> &config) | |
| { | |
| _currentVersion = extractVersionFromConfigMap(config); | |
| configurePaths(config); | |
| configureResourceDirectory(config); | |
| configureLibraryDirectory(config); | |
| configureHelpDirectory(config); | |
| } | |
| const fs::path& ApplicationDirectories::getHomePath() const | |
| { | |
| return this->_home; | |
| } | |
| const fs::path& ApplicationDirectories::getUserHomePath() const | |
| { | |
| return this->_userHome; | |
| } | |
| const fs::path& ApplicationDirectories::getTempPath() const { | |
| return this->_temp; | |
| } | |
| fs::path ApplicationDirectories::getTempFileName(const std::string & filename) const { | |
| auto tempPath = Base::FileInfo::pathToString(getTempPath()); | |
| if (filename.empty()) { | |
| return Base::FileInfo::getTempFileName(nullptr, tempPath.c_str()); | |
| } | |
| return Base::FileInfo::getTempFileName(filename.c_str(), tempPath.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::appendVersionIfPossible(const fs::path& basePath, std::vector<std::string> &subdirs) const | |
| { | |
| fs::path pathToCheck = basePath; | |
| for (const auto& it : subdirs) { | |
| pathToCheck = pathToCheck / it; | |
| } | |
| if (isVersionedPath(pathToCheck)) { | |
| return; // Bail out if it's already versioned | |
| } | |
| if (fs::exists(pathToCheck)) { | |
| std::string version = mostRecentAvailableConfigVersion(pathToCheck); | |
| if (!version.empty()) { | |
| subdirs.emplace_back(std::move(version)); | |
| } | |
| } else { | |
| auto [major, minor] = _currentVersion; | |
| subdirs.emplace_back(versionStringForPath(major, minor)); | |
| } | |
| } | |
| 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(); | |
| _usingCustomDirectories = !customHome.empty() || !customData.empty(); | |
| // get the system standard paths | |
| auto [configHome, dataHome, cacheHome, tempPath] = getStandardPaths(); | |
| if (mConfig.contains("SafeMode")) { | |
| if (startSafeMode(mConfig)) { | |
| // If we're in safe mode, don't try to set any directories here, they've been overridden | |
| // by temp directories in the SafeMode setup. | |
| return; | |
| } | |
| } | |
| // User home path | |
| // | |
| fs::path homePath = findUserHomePath(customHome); | |
| mConfig["UserHomePath"] = Base::FileInfo::pathToString(homePath); | |
| _userHome = 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 | |
| // | |
| auto dataSubdirs = subdirs; | |
| appendVersionIfPossible(dataHome, dataSubdirs); | |
| fs::path data = findPath(dataHome, customData, dataSubdirs, true); | |
| _userAppData = data; | |
| mConfig["UserAppData"] = Base::FileInfo::pathToString(data) + PATHSEP; | |
| // User config path | |
| // | |
| auto configSubdirs = subdirs; | |
| appendVersionIfPossible(configHome, configSubdirs); | |
| fs::path config = findPath(configHome, customHome, configSubdirs, true); | |
| _userConfig = config; | |
| mConfig["UserConfigPath"] = Base::FileInfo::pathToString(config) + 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{"Macro"}; | |
| fs::path macro = findPath(_userAppData, customData, macrodirs, true); | |
| _userMacro = macro; | |
| mConfig["UserMacroPath"] = Base::FileInfo::pathToString(macro) + PATHSEP; | |
| } | |
| bool ApplicationDirectories::startSafeMode(std::map<std::string,std::string>& mConfig) | |
| { | |
| SafeMode::StartSafeMode(); | |
| if (SafeMode::SafeModeEnabled()) { | |
| _userAppData = mConfig["UserAppData"]; | |
| _userConfig = mConfig["UserConfigPath"]; | |
| _userCache = mConfig["UserCachePath"]; | |
| _temp = mConfig["AppTempPath"]; | |
| _userMacro = mConfig["UserMacroPath"]; | |
| _userHome = mConfig["UserHomePath"]; | |
| _usingCustomDirectories = true; | |
| return true; | |
| } | |
| return false; | |
| } | |
| std::filesystem::path ApplicationDirectories::sanitizePath(const std::string& pathAsString) | |
| { | |
| size_t positionOfFirstNull = pathAsString.find('\0'); | |
| if (positionOfFirstNull != std::string::npos) { | |
| return {pathAsString.substr(0, positionOfFirstNull)}; | |
| } | |
| return {pathAsString}; | |
| } | |
| void ApplicationDirectories::configureResourceDirectory(const std::map<std::string,std::string>& mConfig) { | |
| // #6892: Conda may inject null characters | |
| fs::path path = sanitizePath(RESOURCEDIR); | |
| if (path.is_absolute()) { | |
| _resource = path; | |
| } else { | |
| _resource = Base::FileInfo::stringToPath(mConfig.at("AppHomePath")) / path; | |
| } | |
| _resource = fs::path(mConfig.at("AppHomePath")); | |
| } | |
| void ApplicationDirectories::configureLibraryDirectory(const std::map<std::string,std::string>& mConfig) { | |
| // #6892: Conda may inject null characters | |
| fs::path path = sanitizePath(LIBRARYDIR); | |
| if (path.is_absolute()) { | |
| _library = path; | |
| } else { | |
| _library = Base::FileInfo::stringToPath(mConfig.at("AppHomePath")) / path; | |
| } | |
| _library = Base::FileInfo::stringToPath(mConfig.at("AppHomePath")) / "lib"; | |
| } | |
| void ApplicationDirectories::configureHelpDirectory(const std::map<std::string,std::string>& mConfig) | |
| { | |
| // #6892: Conda may inject null characters | |
| fs::path path = sanitizePath(DOCDIR); | |
| if (path.is_absolute()) { | |
| _help = path; | |
| } else { | |
| _help = Base::FileInfo::stringToPath(mConfig.at("AppHomePath")) / path; | |
| } | |
| _help = Base::FileInfo::stringToPath(mConfig.at("DocPath")); | |
| } | |
| fs::path ApplicationDirectories::getUserHome() | |
| { | |
| fs::path path; | |
| // 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!"); | |
| } | |
| std::string sanitizedPath = sanitizePath(pwd.pw_dir); | |
| path = Base::FileInfo::stringToPath(sanitizedPath); | |
| path = Base::FileInfo::stringToPath(QStandardPaths::writableLocation(QStandardPaths::HomeLocation).toStdString()); | |
| return path; | |
| } | |
| bool ApplicationDirectories::usingCustomDirectories() const | |
| { | |
| return _usingCustomDirectories; | |
| } | |
| 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 {}; | |
| } | |
| 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 '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")); | |
| } | |
| getSubDirectories(mConfig, appData); | |
| } | |
| 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 | |
| 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. | |
| tempPath = QString::fromStdString(Base::FileInfo::getTempPath()); | |
| cacheHome = tempPath; | |
| return {qstringToPath(configHome), | |
| qstringToPath(dataHome), | |
| qstringToPath(cacheHome), | |
| qstringToPath(tempPath)}; | |
| } | |
| std::string ApplicationDirectories::versionStringForPath(int major, int minor) | |
| { | |
| // NOTE: This is intended to be stable over time, so if the format changes, a condition should be added to check for | |
| // older versions and return this format for them, even if the new format differs. | |
| return fmt::format("v{}-{}", major, minor); | |
| } | |
| bool ApplicationDirectories::isVersionedPath(const fs::path &startingPath) const { | |
| for (int major = std::get<0>(_currentVersion); major >= 1; --major) { | |
| constexpr int largestPossibleMinor = 99; // We have to start someplace | |
| int startingMinor = largestPossibleMinor; | |
| if (major == std::get<0>(_currentVersion)) { | |
| startingMinor = std::get<1>(_currentVersion); | |
| } | |
| for (int minor = startingMinor; minor >= 0; --minor) { | |
| if (startingPath.filename() == versionStringForPath(major, minor)) { | |
| return true; | |
| } | |
| } | |
| } | |
| return false; | |
| } | |
| std::string ApplicationDirectories::mostRecentAvailableConfigVersion(const fs::path &startingPath) const { | |
| for (int major = std::get<0>(_currentVersion); major >= 1; --major) { | |
| constexpr int largestPossibleMinor = 99; // We have to start someplace | |
| int startingMinor = largestPossibleMinor; | |
| if (major == std::get<0>(_currentVersion)) { | |
| startingMinor = std::get<1>(_currentVersion); | |
| } | |
| for (int minor = startingMinor; minor >= 0; --minor) { | |
| auto version = startingPath / versionStringForPath(major, minor); | |
| if (fs::is_directory(version)) { | |
| return versionStringForPath(major, minor); | |
| } | |
| } | |
| } | |
| return ""; | |
| } | |
| fs::path ApplicationDirectories::mostRecentConfigFromBase(const fs::path &startingPath) const { | |
| // Starting in FreeCAD v1.1, we switched to using a versioned config path for the three configuration | |
| // directories: | |
| // UserAppData | |
| // UserConfigPath | |
| // UserMacroPath | |
| // | |
| // Migration to the versioned structured is NOT automatic: at the App level, we just find the most | |
| // recent directory and use it, regardless of which version of the program is currently running. | |
| // It is up to user-facing code in Gui to determine whether to ask a user if they want to migrate | |
| // and to call the App-level functions that do that work. | |
| // The simplest and most common case is if the current version subfolder already exists | |
| auto current = startingPath / versionStringForPath(std::get<0>(_currentVersion), std::get<1>(_currentVersion)); | |
| if (fs::is_directory(current)) { | |
| return current; | |
| } | |
| // If the current version doesn't exist, see if a previous version does | |
| std::string bestVersion = mostRecentAvailableConfigVersion(startingPath); | |
| if (!bestVersion.empty()) { | |
| return startingPath / bestVersion; | |
| } | |
| return startingPath; // No versioned config found | |
| } | |
| bool ApplicationDirectories::usingCurrentVersionConfig(fs::path config) const { | |
| if (config.filename().empty()) { | |
| config = config.parent_path(); | |
| } | |
| auto version = Base::FileInfo::pathToString(config.filename()); | |
| return version == versionStringForPath(std::get<0>(_currentVersion), std::get<1>(_currentVersion)); | |
| } | |
| void ApplicationDirectories::migrateConfig(const fs::path &oldPath, const fs::path &newPath) | |
| { | |
| fs::create_directories(newPath); | |
| for (auto& file : fs::directory_iterator(oldPath)) { | |
| if (file == newPath) { | |
| // Handle the case where newPath is a subdirectory of oldPath | |
| continue; | |
| } | |
| fs::copy(file.path(), | |
| newPath / file.path().filename(), | |
| fs::copy_options::recursive | fs::copy_options::copy_symlinks); | |
| } | |
| } | |
| void ApplicationDirectories::migrateAllPaths(const std::vector<fs::path> &paths) const { | |
| auto [major, minor] = _currentVersion; | |
| std::set<fs::path> uniquePaths (paths.begin(), paths.end()); | |
| for (auto path : uniquePaths) { | |
| if (path.filename().empty()) { | |
| // Handle the case where the path was constructed from a std::string with a trailing / | |
| path = path.parent_path(); | |
| } | |
| fs::path newPath; | |
| if (isVersionedPath(path)) { | |
| newPath = path.parent_path() / versionStringForPath(major, minor); | |
| } else { | |
| newPath = path / versionStringForPath(major, minor); | |
| } | |
| Base::Console().message("Migrating config from %s to %s\n", Base::FileInfo::pathToString(path), Base::FileInfo::pathToString(newPath)); | |
| if (fs::exists(newPath)) { | |
| continue; // Ignore an existing path: not an error, just a migration that was already done | |
| } | |
| fs::create_directories(newPath); | |
| migrateConfig(path, newPath); | |
| } | |
| } | |
| // TODO: Consider using this for all UNIX-like OSes | |
| 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); | |
| } | |
| 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]; | |
| 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); | |
| int nchars = readlink("/proc/self/exe", resolved, PATH_MAX); | |
| 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); | |
| } | |
| 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); | |
| } | |
| 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 | |
| QString str = QString::fromUtf16(reinterpret_cast<const ushort *>(homePath.c_str())); | |
| QString str = QString::fromStdWString(homePath); | |
| return qstringToPath(str); | |
| } | |
| std::tuple<int, int> ApplicationDirectories::extractVersionFromConfigMap(const std::map<std::string,std::string> &config) | |
| { | |
| try { | |
| int major = std::stoi(config.at("BuildVersionMajor")); | |
| int minor = std::stoi(config.at("BuildVersionMinor")); | |
| return std::make_tuple(major, minor); | |
| } catch (const std::exception& e) { | |
| throw Base::RuntimeError("Failed to parse version from config: " + std::string(e.what())); | |
| } | |
| } | |