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/
This commit is contained in:
parent
d81599efd1
commit
c3b1442546
3 changed files with 394 additions and 159 deletions
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue