// SPDX-License-Identifier: LGPL-2.1-or-later /*************************************************************************************************** * * * Copyright (c) 2002 Jürgen Riegel * * 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 . * * * **************************************************************************************************/ #ifndef SRC_APP_APPLICATIONDIRECTORIES_H_ #define SRC_APP_APPLICATIONDIRECTORIES_H_ #include "FCConfig.h" #include "FCGlobal.h" #include #include #include #include #ifdef FC_OS_WIN32 #include #endif namespace App { /// A helper class to handle application-wide directory management on behalf of the main /// App::Application class. Most of this class's methods were originally in Application, and /// were extracted here to be more easily testable (and to better follow the single- /// responsibility principle, though further work is required in that area). class AppExport ApplicationDirectories { public: /// Constructor /// \param config The base configuration dictionary. Used to create the appropriate /// directories, and updated to reflect their locations. New code should not directly access /// elements of this dictionary to determine directory locations, but should instead use /// the public methods of this class to determine the locations needed. explicit ApplicationDirectories(std::map &config); /// Given argv[0], figure out the home path. Used to initialize the AppHomePath element of /// the configuration map prior to instantiating this class. 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; /// Get the user's home directory. This should not be used for many things, use caution when /// deciding to use it for anything, there are usually better places. const std::filesystem::path& getUserHomePath() 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(); /// Returns true if the current active directory set is custom, and false if it's the /// standard system default. bool usingCustomDirectories() const; // Versioned-Path Management Methods: /// Determine if a given config path is for the current version of the program /// \param config The path to check bool usingCurrentVersionConfig(std::filesystem::path config) const; /// Migrate a set of versionable configuration directories from the given path to a new /// version. The new version's directories cannot exist yet, and the old ones *must* exist. /// If the old paths are themselves versioned, then the new paths will be placed at the same /// level in the directory structure (e.g., they will be siblings of each entry in paths). /// If paths are NOT versioned, the new (versioned) copies will be placed *inside* the /// original paths. void migrateAllPaths(const std::vector &paths) const; /// A utility method to generate the versioned directory name for a given version. This only /// returns the version string, not an entire path. As of FreeCAD 1.1, the string is of the /// form "vX-Y" where X is the major version and Y is the minor, but external code should /// not assume that is always the form, and should instead use this method, which is /// guaranteed stable (that is, given "1" and "1" as the major and minor, it will always /// return the string "v1-1", even if in later versions the format changes. static std::string versionStringForPath(int major, int minor); /// Given an arbitrary path, determine if it has the correct form to be a versioned path /// (e.g. is the last component a recognized version of the code). DOES NOT recognize /// versions *newer* than the current version, even if the directory name matches the path /// naming convention used by the previous version. bool isVersionedPath(const std::filesystem::path &startingPath) const; /// Given a base path that is expected to contained versioned subdirectories, locate the /// directory name (*not* the path, only the final component, the version string itself) /// corresponding to the most recent version of the software, up to and including the /// current version, but NOT exceeding it. std::string mostRecentAvailableConfigVersion(const std::filesystem::path &startingPath) const; /// Given a base path that is expected to contained versioned subdirectories, locate the /// directory corresponding to the most recent version of the software, up to and including /// the current version, but NOT exceeding it. std::filesystem::path mostRecentConfigFromBase(const std::filesystem::path &startingPath) const; /// A utility method to copy all files and directories from oldPath to newPath, handling the /// case where newPath might itself be a subdirectory of oldPath (and *not* attempting that /// otherwise-recursive copy). static void migrateConfig(const std::filesystem::path &oldPath, const std::filesystem::path &newPath); #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& mConfig, std::vector& 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& mConfig, std::vector& 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); protected: /// Override all application directories with temp directories. Returns true on success and /// false if the temp directory creation failed. bool startSafeMode(std::map& mConfig); /// Take a path and add a version to it, if it's possible to do so. A version can be /// appended only if a) the versioned subdirectory already exists, or b) pathToCheck/subdirs /// does NOT yet exist. This does not actually create any directories, just determines /// if we can append the versioned directory name to subdirs. void appendVersionIfPossible(const std::filesystem::path& basePath, std::vector &subdirs) const; static std::filesystem::path findPath( const std::filesystem::path& stdHome, const std::filesystem::path& customHome, const std::vector& paths, bool create); void configurePaths(std::map &config); void configureResourceDirectory(const std::map& mConfig); void configureLibraryDirectory(const std::map& mConfig); void configureHelpDirectory(const std::map& mConfig); /*! * 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 getCustomPaths(); /*! * 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 getStandardPaths(); /// Find the BuildVersionMajor, BuildVersionMinor pair in the config map, convert them to an int tuple, and /// return it. If the pair is not found, or cannot be converted to integers, a RuntimeError is raised. /// \param config The config map to search. /// \return The version tuple. static std::tuple extractVersionFromConfigMap(const std::map &config); /// A utility method to remove any stray null characters from a path (Conda sometimes /// injects these for unknown reasons -- see #6892 in the bug tracker). /// \param pathAsString The std::string path to sanitize /// \returns A path with any stray nulls removed static std::filesystem::path sanitizePath(const std::string& pathAsString); private: std::tuple _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 _userHome; std::filesystem::path _resource; std::filesystem::path _library; std::filesystem::path _help; bool _usingCustomDirectories {false}; }; } // App #endif //SRC_APP_APPLICATIONDIRECTORIES_H_