From c3b1442546c83d366700ba4b08cce9357829bf0c Mon Sep 17 00:00:00 2001 From: Florent Rougon <f.rougon@free.fr> Date: Tue, 12 Dec 2017 07:51:05 +0100 Subject: [PATCH] Add-ons: new supported fields: authors, maintainers, license/*, url/*, tags - Parsing of the addon-metadata.xml file is now handled by a new static method of Addon: static Addon fromAddonDir(const SGPath& addonPath); This method will be reusable to gather all add-on metadata from a set of add-on directories (just call the method once per add-on). This change also simplifies AddonManager::registerAddonMetadata(). - New supported fields: authors maintainers license/{designation,file,url} url/{home-page,download,support,code-repository} tags See https://sourceforge.net/p/flightgear/fgdata/ci/next/tree/Docs/README.add-ons for documentation on these fields. Mailing-list discussion: around https://sourceforge.net/p/flightgear/mailman/message/36155660/ --- src/Add-ons/Addon.cxx | 341 +++++++++++++++++++++++++++++++++++ src/Add-ons/Addon.hxx | 48 +++++ src/Add-ons/AddonManager.cxx | 164 +---------------- 3 files changed, 394 insertions(+), 159 deletions(-) diff --git a/src/Add-ons/Addon.cxx b/src/Add-ons/Addon.cxx index be7e2d035..3bf5e42fe 100644 --- a/src/Add-ons/Addon.cxx +++ b/src/Add-ons/Addon.cxx @@ -18,14 +18,19 @@ // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. #include <ostream> +#include <regex> #include <sstream> #include <string> +#include <tuple> #include <utility> +#include <vector> #include <simgear/misc/sg_path.hxx> #include <simgear/nasal/cppbind/Ghost.hxx> #include <simgear/nasal/cppbind/NasalHash.hxx> #include <simgear/nasal/naref.h> +#include <simgear/props/props.hxx> +#include <simgear/props/props_io.hxx> #include <Main/globals.hxx> #include <Scripting/NasalSys.hxx> @@ -33,8 +38,12 @@ #include "addon_fwd.hxx" #include "Addon.hxx" #include "AddonVersion.hxx" +#include "exceptions.hxx" + +namespace strutils = simgear::strutils; using std::string; +using std::vector; namespace flightgear { @@ -82,6 +91,18 @@ AddonVersionRef Addon::getVersion() const void Addon::setVersion(const AddonVersion& addonVersion) { _version.reset(new AddonVersion(addonVersion)); } +std::string Addon::getAuthors() const +{ return _authors; } + +void Addon::setAuthors(const std::string& addonAuthors) +{ _authors = addonAuthors; } + +std::string Addon::getMaintainers() const +{ return _maintainers; } + +void Addon::setMaintainers(const std::string& addonMaintainers) +{ _maintainers = addonMaintainers; } + std::string Addon::getShortDescription() const { return _shortDescription; } @@ -94,6 +115,30 @@ std::string Addon::getLongDescription() const void Addon::setLongDescription(const std::string& addonLongDescription) { _longDescription = addonLongDescription; } +std::string Addon::getLicenseDesignation() const +{ return _licenseDesignation; } + +void Addon::setLicenseDesignation(const std::string& addonLicenseDesignation) +{ _licenseDesignation = addonLicenseDesignation; } + +SGPath Addon::getLicenseFile() const +{ return _licenseFile; } + +void Addon::setLicenseFile(const SGPath& addonLicenseFile) +{ _licenseFile = addonLicenseFile; } + +std::string Addon::getLicenseUrl() const +{ return _licenseUrl; } + +void Addon::setLicenseUrl(const std::string& addonLicenseUrl) +{ _licenseUrl = addonLicenseUrl; } + +std::vector<std::string> Addon::getTags() const +{ return _tags; } + +void Addon::setTags(const std::vector<std::string>& addonTags) +{ _tags = addonTags; } + SGPath Addon::getBasePath() const { return _basePath; } @@ -136,6 +181,12 @@ std::string Addon::getSupportUrl() const void Addon::setSupportUrl(const std::string& addonSupportUrl) { _supportUrl = addonSupportUrl; } +std::string Addon::getCodeRepositoryUrl() const +{ return _codeRepositoryUrl; } + +void Addon::setCodeRepositoryUrl(const std::string& addonCodeRepositoryUrl) +{ _codeRepositoryUrl = addonCodeRepositoryUrl; } + SGPropertyNode_ptr Addon::getAddonNode() const { return _addonNode; } @@ -170,6 +221,289 @@ std::string Addon::str() const return oss.str(); } +// Static method +SGPath Addon::getMetadataFile(const SGPath& addonPath) +{ + return addonPath / "addon-metadata.xml"; +} + +SGPath Addon::getMetadataFile() const +{ + return getMetadataFile(getBasePath()); +} + +// Static method +Addon Addon::fromAddonDir(const SGPath& addonPath) +{ + SGPath metadataFile = getMetadataFile(addonPath); + SGPropertyNode addonRoot; + + if (!metadataFile.exists()) { + throw addon_errors::no_metadata_file_found( + "unable to find add-on metadata file '" + metadataFile.utf8Str() + "'"); + } + + try { + readProperties(metadataFile, &addonRoot); + } catch (const sg_exception &e) { + throw addon_errors::error_loading_metadata_file( + "unable to load add-on metadata file '" + metadataFile.utf8Str() + "': " + + e.getFormattedMessage()); + } + + // Check the 'meta' section + SGPropertyNode *metaNode = addonRoot.getChild("meta"); + if (metaNode == nullptr) { + throw addon_errors::error_loading_metadata_file( + "no /meta node found in add-on metadata file '" + + metadataFile.utf8Str() + "'"); + } + + // Check the file type + SGPropertyNode *fileTypeNode = metaNode->getChild("file-type"); + if (fileTypeNode == nullptr) { + throw addon_errors::error_loading_metadata_file( + "no /meta/file-type node found in add-on metadata file '" + + metadataFile.utf8Str() + "'"); + } + + string fileType = fileTypeNode->getStringValue(); + if (fileType != "FlightGear add-on metadata") { + throw addon_errors::error_loading_metadata_file( + "Invalid /meta/file-type value for add-on metadata file '" + + metadataFile.utf8Str() + "': '" + fileType + "' " + "(expected 'FlightGear add-on metadata')"); + } + + // Check the format version + SGPropertyNode *fmtVersionNode = metaNode->getChild("format-version"); + if (fmtVersionNode == nullptr) { + throw addon_errors::error_loading_metadata_file( + "no /meta/format-version node found in add-on metadata file '" + + metadataFile.utf8Str() + "'"); + } + + int formatVersion = fmtVersionNode->getIntValue(); + if (formatVersion != 1) { + throw addon_errors::error_loading_metadata_file( + "unknown format version in add-on metadata file '" + + metadataFile.utf8Str() + "': " + std::to_string(formatVersion)); + } + + // Now the data we are really interested in + SGPropertyNode *addonNode = addonRoot.getChild("addon"); + if (addonNode == nullptr) { + throw addon_errors::error_loading_metadata_file( + "no /addon node found in add-on metadata file '" + + metadataFile.utf8Str() + "'"); + } + + SGPropertyNode *idNode = addonNode->getChild("identifier"); + if (idNode == nullptr) { + throw addon_errors::error_loading_metadata_file( + "no /addon/identifier node found in add-on metadata file '" + + metadataFile.utf8Str() + "'"); + } + string addonId = strutils::strip(idNode->getStringValue()); + + // Require a non-empty identifier for the add-on + if (addonId.empty()) { + throw addon_errors::error_loading_metadata_file( + "empty or whitespace-only value for the /addon/identifier node in " + "add-on metadata file '" + metadataFile.utf8Str() + "'"); + } else if (addonId.find('.') == string::npos) { + SG_LOG(SG_GENERAL, SG_WARN, + "Add-on identifier '" << addonId << "' does not use reverse DNS " + "style (e.g., org.flightgear.addons.MyAddon) in add-on metadata " + "file '" << metadataFile.utf8Str() + "'"); + } + + SGPropertyNode *nameNode = addonNode->getChild("name"); + if (nameNode == nullptr) { + throw addon_errors::error_loading_metadata_file( + "no /addon/name node found in add-on metadata file '" + + metadataFile.utf8Str() + "'"); + } + string addonName = strutils::strip(nameNode->getStringValue()); + + // Require a non-empty name for the add-on + if (addonName.empty()) { + throw addon_errors::error_loading_metadata_file( + "empty or whitespace-only value for the /addon/name node in add-on " + "metadata file '" + metadataFile.utf8Str() + "'"); + } + + SGPropertyNode *versionNode = addonNode->getChild("version"); + if (versionNode == nullptr) { + throw addon_errors::error_loading_metadata_file( + "no /addon/version node found in add-on metadata file '" + + metadataFile.utf8Str() + "'"); + } + AddonVersion addonVersion(versionNode->getStringValue()); + + string addonAuthors; + SGPropertyNode *authorsNode = addonNode->getChild("authors"); + if (authorsNode != nullptr) { + addonAuthors = strutils::strip(authorsNode->getStringValue()); + } + + string addonMaintainers; + SGPropertyNode *maintainersNode = addonNode->getChild("maintainers"); + if (maintainersNode != nullptr) { + addonMaintainers = strutils::strip(maintainersNode->getStringValue()); + } + + string addonShortDescription; + SGPropertyNode *shortDescNode = addonNode->getChild("short-description"); + if (shortDescNode != nullptr) { + addonShortDescription = strutils::strip(shortDescNode->getStringValue()); + } + + string addonLongDescription; + SGPropertyNode *longDescNode = addonNode->getChild("long-description"); + if (longDescNode != nullptr) { + addonLongDescription = strutils::strip(longDescNode->getStringValue()); + } + + string addonLicenseDesignation, addonLicenseUrl; + SGPath addonLicenseFile; + std::tie(addonLicenseDesignation, addonLicenseFile, addonLicenseUrl) = + parseLicenseNode(addonPath, addonNode); + + vector<string> addonTags; + SGPropertyNode *tagsNode = addonNode->getChild("tags"); + if (tagsNode != nullptr) { + auto tagNodes = tagsNode->getChildren("tag"); + for (const auto& node: tagNodes) { + addonTags.push_back(strutils::strip(node->getStringValue())); + } + } + + string addonMinFGVersionRequired; + SGPropertyNode *minNode = addonNode->getChild("min-FG-version"); + if (minNode != nullptr) { + addonMinFGVersionRequired = minNode->getStringValue(); + } + + string addonMaxFGVersionRequired; + SGPropertyNode *maxNode = addonNode->getChild("max-FG-version"); + if (maxNode != nullptr) { + addonMaxFGVersionRequired = maxNode->getStringValue(); + } + + string addonHomePage, addonDownloadUrl, addonSupportUrl, addonCodeRepoUrl; + SGPropertyNode *urlsNode = addonNode->getChild("urls"); + if (urlsNode != nullptr) { + SGPropertyNode *homePageNode = urlsNode->getChild("home-page"); + if (homePageNode != nullptr) { + addonHomePage = strutils::strip(homePageNode->getStringValue()); + } + + SGPropertyNode *downloadUrlNode = urlsNode->getChild("download"); + if (downloadUrlNode != nullptr) { + addonDownloadUrl = strutils::strip(downloadUrlNode->getStringValue()); + } + + SGPropertyNode *supportUrlNode = urlsNode->getChild("support"); + if (supportUrlNode != nullptr) { + addonSupportUrl = strutils::strip(supportUrlNode->getStringValue()); + } + + SGPropertyNode *codeRepoUrlNode = urlsNode->getChild("code-repository"); + if (codeRepoUrlNode != nullptr) { + addonCodeRepoUrl = strutils::strip(codeRepoUrlNode->getStringValue()); + } + } + + // Object holding all the add-on metadata + Addon addon{addonId, std::move(addonVersion), addonPath, + addonMinFGVersionRequired, addonMaxFGVersionRequired}; + addon.setName(addonName); + addon.setAuthors(addonAuthors); + addon.setMaintainers(addonMaintainers); + addon.setShortDescription(addonShortDescription); + addon.setLongDescription(addonLongDescription); + addon.setLicenseDesignation(addonLicenseDesignation); + addon.setLicenseFile(addonLicenseFile); + addon.setLicenseUrl(addonLicenseUrl); + addon.setTags(addonTags); + addon.setHomePage(addonHomePage); + addon.setDownloadUrl(addonDownloadUrl); + addon.setSupportUrl(addonSupportUrl); + addon.setCodeRepositoryUrl(addonCodeRepoUrl); + + return addon; +} + +// Static method +std::tuple<string, SGPath, string> +Addon::parseLicenseNode(const SGPath& addonPath, SGPropertyNode* addonNode) +{ + SGPath metadataFile = getMetadataFile(addonPath); + string licenseDesignation; + SGPath licenseFile; + string licenseUrl; + + SGPropertyNode *licenseNode = addonNode->getChild("license"); + if (licenseNode == nullptr) { + return std::tuple<string, SGPath, string>(); + } + + SGPropertyNode *licenseDesigNode = licenseNode->getChild("designation"); + if (licenseDesigNode != nullptr) { + licenseDesignation = strutils::strip(licenseDesigNode->getStringValue()); + } + + SGPropertyNode *licenseFileNode = licenseNode->getChild("file"); + if (licenseFileNode != nullptr) { + // This effectively disallows filenames starting or ending with whitespace + string licenseFile_s = strutils::strip(licenseFileNode->getStringValue()); + + if (!licenseFile_s.empty()) { + if (licenseFile_s.find('\\') != string::npos) { + throw errors::error_loading_metadata_file( + "in add-on metadata file '" + metadataFile.utf8Str() + "': the " + "value of /addon/license/file contains '\\'; please use '/' " + "separators only"); + } + + if (licenseFile_s.find_first_of("/\\") == 0) { + throw errors::error_loading_metadata_file( + "in add-on metadata file '" + metadataFile.utf8Str() + "': the " + "value of /addon/license/file must be relative to the add-on folder, " + "however it starts with '" + licenseFile_s[0] + "'"); + } + + std::regex winDriveRegexp("([a-zA-Z]:).*"); + std::smatch results; + + if (std::regex_match(licenseFile_s, results, winDriveRegexp)) { + string winDrive = results.str(1); + throw errors::error_loading_metadata_file( + "in add-on metadata file '" + metadataFile.utf8Str() + "': the " + "value of /addon/license/file must be relative to the add-on folder, " + "however it starts with a Windows drive letter (" + winDrive + ")"); + } + + licenseFile = addonPath / licenseFile_s; + if ( !(licenseFile.exists() && licenseFile.isFile()) ) { + throw errors::error_loading_metadata_file( + "in add-on metadata file '" + metadataFile.utf8Str() + "': the " + "value of /addon/license/file (pointing to '" + licenseFile.utf8Str() + + "') doesn't correspond to an existing file"); + } + } // of if (!licenseFile_s.empty()) + } // of if (licenseFileNode != nullptr) + + SGPropertyNode *licenseUrlNode = licenseNode->getChild("url"); + if (licenseUrlNode != nullptr) { + licenseUrl = strutils::strip(licenseUrlNode->getStringValue()); + } + + return std::make_tuple(licenseDesignation, licenseFile, licenseUrl); +} + + // Static method void Addon::setupGhost(nasal::Hash& addonsModule) { @@ -177,14 +511,21 @@ void Addon::setupGhost(nasal::Hash& addonsModule) .member("id", &Addon::getId) .member("name", &Addon::getName) .member("version", &Addon::getVersion) + .member("authors", &Addon::getAuthors) + .member("maintainers", &Addon::getMaintainers) .member("shortDescription", &Addon::getShortDescription) .member("longDescription", &Addon::getLongDescription) + .member("licenseDesignation", &Addon::getLicenseDesignation) + .member("licenseFile", &Addon::getLicenseFile) + .member("licenseUrl", &Addon::getLicenseUrl) + .member("tags", &Addon::getTags) .member("basePath", &Addon::getBasePath) .member("minFGVersionRequired", &Addon::getMinFGVersionRequired) .member("maxFGVersionRequired", &Addon::getMaxFGVersionRequired) .member("homePage", &Addon::getHomePage) .member("downloadUrl", &Addon::getDownloadUrl) .member("supportUrl", &Addon::getSupportUrl) + .member("codeRepositoryUrl", &Addon::getCodeRepositoryUrl) .member("node", &Addon::getAddonPropsNode) .member("loadSequenceNumber", &Addon::getLoadSequenceNumber); } diff --git a/src/Add-ons/Addon.hxx b/src/Add-ons/Addon.hxx index 06679d49e..04d083bf8 100644 --- a/src/Add-ons/Addon.hxx +++ b/src/Add-ons/Addon.hxx @@ -22,6 +22,8 @@ #include <ostream> #include <string> +#include <tuple> +#include <vector> #include <simgear/misc/sg_path.hxx> #include <simgear/nasal/cppbind/NasalHash.hxx> @@ -47,6 +49,10 @@ public: std::string maxFGVersionRequired = "", SGPropertyNode* addonNode = nullptr); + // Parse the add-on metadata file inside 'addonPath' (as defined by + // getMetadataFile()) and return the corresponding Addon instance. + static Addon fromAddonDir(const SGPath& addonPath); + std::string getId() const; void setId(const std::string& addonId); @@ -56,15 +62,37 @@ public: AddonVersionRef getVersion() const; void setVersion(const AddonVersion& addonVersion); + std::string getAuthors() const; + void setAuthors(const std::string& addonAuthors); + + std::string getMaintainers() const; + void setMaintainers(const std::string& addonMaintainers); + std::string getShortDescription() const; void setShortDescription(const std::string& addonShortDescription); std::string getLongDescription() const; void setLongDescription(const std::string& addonLongDescription); + std::string getLicenseDesignation() const; + void setLicenseDesignation(const std::string& addonLicenseDesignation); + + SGPath getLicenseFile() const; + void setLicenseFile(const SGPath& addonLicenseFile); + + std::string getLicenseUrl() const; + void setLicenseUrl(const std::string& addonLicenseUrl); + + std::vector<std::string> getTags() const; + void setTags(const std::vector<std::string>& addonTags); + SGPath getBasePath() const; void setBasePath(const SGPath& addonBasePath); + // “Compute” a path to the metadata file from the add-on base path + static SGPath getMetadataFile(const SGPath& addonPath); + SGPath getMetadataFile() const; + // Should be valid for use with simgear::strutils::compare_versions() std::string getMinFGVersionRequired() const; void setMinFGVersionRequired(const std::string& minFGVersionRequired); @@ -83,6 +111,9 @@ public: std::string getSupportUrl() const; void setSupportUrl(const std::string& addonSupportUrl); + std::string getCodeRepositoryUrl() const; + void setCodeRepositoryUrl(const std::string& addonCodeRepositoryUrl); + // Node pertaining to the add-on in the Global Property Tree SGPropertyNode_ptr getAddonNode() const; void setAddonNode(SGPropertyNode* addonNode); @@ -103,6 +134,9 @@ public: static void setupGhost(nasal::Hash& addonsModule); private: + static std::tuple<string, SGPath, string> + parseLicenseNode(const SGPath& addonPath, SGPropertyNode* addonNode); + // The add-on identifier, in reverse DNS style. The AddonManager refuses to // register two add-ons with the same id in a given FlightGear session. std::string _id; @@ -111,17 +145,31 @@ private: // Use a smart pointer to expose the AddonVersion instance to Nasal without // needing to copy the data every time. AddonVersionRef _version; + + std::string _authors; + std::string _maintainers; + // Strings describing what the add-on does std::string _shortDescription; std::string _longDescription; + + std::string _licenseDesignation; + SGPath _licenseFile; + std::string _licenseUrl; + + std::vector<std::string> _tags; SGPath _basePath; + // To be used with simgear::strutils::compare_versions() std::string _minFGVersionRequired; // Ditto, but there is a special value: "none" std::string _maxFGVersionRequired; + std::string _homePage; std::string _downloadUrl; std::string _supportUrl; + std::string _codeRepositoryUrl; + // Main node for the add-on in the Property Tree SGPropertyNode_ptr _addonNode; // Semantics explained above diff --git a/src/Add-ons/AddonManager.cxx b/src/Add-ons/AddonManager.cxx index 2ca96d4d1..13710e5cf 100644 --- a/src/Add-ons/AddonManager.cxx +++ b/src/Add-ons/AddonManager.cxx @@ -19,8 +19,8 @@ #include <algorithm> #include <memory> -#include <utility> #include <string> +#include <utility> #include <vector> #include <cstdlib> @@ -107,168 +107,14 @@ AddonManager::loadConfigFileIfExists(const SGPath& configFile) string AddonManager::registerAddonMetadata(const SGPath& addonPath) { - SGPropertyNode addonRoot; - const SGPath metadataFile = addonPath / "addon-metadata.xml"; - - if (!metadataFile.exists()) { - throw addon_errors::no_metadata_file_found( - "unable to find add-on metadata file '" + metadataFile.utf8Str() + "'"); - } - - try { - readProperties(metadataFile, &addonRoot); - } catch (const sg_exception &e) { - throw addon_errors::error_loading_metadata_file( - "unable to load add-on metadata file '" + metadataFile.utf8Str() + "': " + - e.getFormattedMessage()); - } - - // Check the 'meta' section - SGPropertyNode *metaNode = addonRoot.getChild("meta"); - if (metaNode == nullptr) { - throw addon_errors::error_loading_metadata_file( - "no /meta node found in add-on metadata file '" + - metadataFile.utf8Str() + "'"); - } - - // Check the file type - SGPropertyNode *fileTypeNode = metaNode->getChild("file-type"); - if (fileTypeNode == nullptr) { - throw addon_errors::error_loading_metadata_file( - "no /meta/file-type node found in add-on metadata file '" + - metadataFile.utf8Str() + "'"); - } - - string fileType = fileTypeNode->getStringValue(); - if (fileType != "FlightGear add-on metadata") { - throw addon_errors::error_loading_metadata_file( - "Invalid /meta/file-type value for add-on metadata file '" + - metadataFile.utf8Str() + "': '" + fileType + "' " - "(expected 'FlightGear add-on metadata')"); - } - - // Check the format version - SGPropertyNode *fmtVersionNode = metaNode->getChild("format-version"); - if (fmtVersionNode == nullptr) { - throw addon_errors::error_loading_metadata_file( - "no /meta/format-version node found in add-on metadata file '" + - metadataFile.utf8Str() + "'"); - } - - int formatVersion = fmtVersionNode->getIntValue(); - if (formatVersion != 1) { - throw addon_errors::error_loading_metadata_file( - "unknown format version in add-on metadata file '" + - metadataFile.utf8Str() + "': " + std::to_string(formatVersion)); - } - - // Now the data we are really interested in - SGPropertyNode *addonNode = addonRoot.getChild("addon"); - if (addonNode == nullptr) { - throw addon_errors::error_loading_metadata_file( - "no /addon node found in add-on metadata file '" + - metadataFile.utf8Str() + "'"); - } - - SGPropertyNode *idNode = addonNode->getChild("identifier"); - if (idNode == nullptr) { - throw addon_errors::error_loading_metadata_file( - "no /addon/identifier node found in add-on metadata file '" + - metadataFile.utf8Str() + "'"); - } - std::string addonId = strutils::strip(idNode->getStringValue()); - - // Require a non-empty identifier for the add-on - if (addonId.empty()) { - throw addon_errors::error_loading_metadata_file( - "empty or whitespace-only value for the /addon/identifier node in " - "add-on metadata file '" + metadataFile.utf8Str() + "'"); - } else if (addonId.find('.') == string::npos) { - SG_LOG(SG_GENERAL, SG_WARN, - "Add-on identifier '" << addonId << "' does not use reverse DNS " - "style (e.g., org.flightgear.addons.MyAddon) in add-on metadata " - "file '" << metadataFile.utf8Str() + "'"); - } - - SGPropertyNode *nameNode = addonNode->getChild("name"); - if (nameNode == nullptr) { - throw addon_errors::error_loading_metadata_file( - "no /addon/name node found in add-on metadata file '" + - metadataFile.utf8Str() + "'"); - } - std::string addonName = strutils::strip(nameNode->getStringValue()); - - // Require a non-empty name for the add-on - if (addonName.empty()) { - throw addon_errors::error_loading_metadata_file( - "empty or whitespace-only value for the /addon/name node in add-on " - "metadata file '" + metadataFile.utf8Str() + "'"); - } - - SGPropertyNode *versionNode = addonNode->getChild("version"); - if (versionNode == nullptr) { - throw addon_errors::error_loading_metadata_file( - "no /addon/version node found in add-on metadata file '" + - metadataFile.utf8Str() + "'"); - } - AddonVersion addonVersion(versionNode->getStringValue()); - - std::string addonShortDescription; - SGPropertyNode *shortDescNode = addonNode->getChild("short-description"); - if (shortDescNode != nullptr) { - addonShortDescription = strutils::strip(shortDescNode->getStringValue()); - } - - std::string addonLongDescription; - SGPropertyNode *longDescNode = addonNode->getChild("long-description"); - if (longDescNode != nullptr) { - addonLongDescription = strutils::strip(longDescNode->getStringValue()); - } - - std::string addonMinFGVersionRequired; - SGPropertyNode *minNode = addonNode->getChild("min-FG-version"); - if (minNode != nullptr) { - addonMinFGVersionRequired = minNode->getStringValue(); - } - - std::string addonMaxFGVersionRequired; - SGPropertyNode *maxNode = addonNode->getChild("max-FG-version"); - if (maxNode != nullptr) { - addonMaxFGVersionRequired = maxNode->getStringValue(); - } - - std::string addonHomePage; - SGPropertyNode *homePageNode = addonNode->getChild("home-page"); - if (homePageNode != nullptr) { - addonHomePage = strutils::strip(homePageNode->getStringValue()); - } - - std::string addonDownloadUrl; - SGPropertyNode *downloadUrlNode = addonNode->getChild("download-url"); - if (downloadUrlNode != nullptr) { - addonDownloadUrl = strutils::strip(downloadUrlNode->getStringValue()); - } - - std::string addonSupportUrl; - SGPropertyNode *supportUrlNode = addonNode->getChild("support-url"); - if (supportUrlNode != nullptr) { - addonSupportUrl = strutils::strip(supportUrlNode->getStringValue()); - } + AddonRef addon(new Addon(Addon::fromAddonDir(addonPath))); + SGPath metadataFile = addon->getMetadataFile(); + string addonId = addon->getId(); SGPropertyNode* addonPropNode = fgGetNode("addons", true) ->getChild("by-id", 0, 1) ->getChild(addonId, 0, 1); - // Object holding all the add-on metadata - AddonRef addon(new Addon( - addonId, std::move(addonVersion), addonPath, - addonMinFGVersionRequired, addonMaxFGVersionRequired, - addonPropNode)); - addon->setName(addonName); - addon->setShortDescription(addonShortDescription); - addon->setLongDescription(addonLongDescription); - addon->setHomePage(addonHomePage); - addon->setDownloadUrl(addonDownloadUrl); - addon->setSupportUrl(addonSupportUrl); + addon->setAddonNode(addonPropNode); addon->setLoadSequenceNumber(_loadSequenceNumber++); // Check that the FlightGear version satisfies the add-on requirements