first commit

This commit is contained in:
Your Name
2022-10-20 20:29:11 +08:00
commit 4d531f8044
3238 changed files with 1387862 additions and 0 deletions

516
src/Add-ons/Addon.cxx Normal file
View File

@@ -0,0 +1,516 @@
// -*- coding: utf-8 -*-
//
// Addon.cxx --- FlightGear class holding add-on metadata
// Copyright (C) 2017, 2018 Florent Rougon
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#include <map>
#include <ostream>
#include <sstream>
#include <string>
#include <utility>
#include <vector>
#include <simgear/misc/sg_dir.hxx>
#include <simgear/misc/sg_path.hxx>
#include <simgear/misc/strutils.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/fg_props.hxx>
#include <Main/globals.hxx>
#include <Scripting/NasalSys.hxx>
#include "addon_fwd.hxx"
#include "Addon.hxx"
#include "AddonMetadataParser.hxx"
#include "AddonVersion.hxx"
#include "exceptions.hxx"
#include "pointer_traits.hxx"
namespace strutils = simgear::strutils;
using std::string;
using std::vector;
namespace flightgear
{
namespace addons
{
// ***************************************************************************
// * QualifiedUrl *
// ***************************************************************************
QualifiedUrl::QualifiedUrl(UrlType type, string url, string detail)
: _type(type),
_url(std::move(url)),
_detail(std::move(detail))
{ }
UrlType QualifiedUrl::getType() const
{ return _type; }
void QualifiedUrl::setType(UrlType type)
{ _type = type; }
std::string QualifiedUrl::getUrl() const
{ return _url; }
void QualifiedUrl::setUrl(const std::string& url)
{ _url = url; }
std::string QualifiedUrl::getDetail() const
{ return _detail; }
void QualifiedUrl::setDetail(const std::string& detail)
{ _detail = detail; }
// ***************************************************************************
// * Addon *
// ***************************************************************************
Addon::Addon(std::string id, AddonVersion version, SGPath basePath,
std::string minFGVersionRequired, std::string maxFGVersionRequired,
SGPropertyNode* addonNode)
: _id(std::move(id)),
_version(
shared_ptr_traits<AddonVersionRef>::makeStrongRef(std::move(version))),
_basePath(std::move(basePath)),
_storagePath(globals->get_fg_home() / ("Export/Addons/" + _id)),
_minFGVersionRequired(std::move(minFGVersionRequired)),
_maxFGVersionRequired(std::move(maxFGVersionRequired)),
_addonNode(addonNode)
{
if (_minFGVersionRequired.empty()) {
// This add-on metadata class appeared in FlightGear 2017.4.0
_minFGVersionRequired = "2017.4.0";
}
if (_maxFGVersionRequired.empty()) {
_maxFGVersionRequired = "none"; // special value
}
}
std::string Addon::getId() const
{ return _id; }
std::string Addon::getName() const
{ return _name; }
void Addon::setName(const std::string& addonName)
{ _name = addonName; }
AddonVersionRef Addon::getVersion() const
{ return _version; }
void Addon::setVersion(const AddonVersion& addonVersion)
{
using ptr_traits = shared_ptr_traits<AddonVersionRef>;
_version.reset(ptr_traits::makeStrongRef(addonVersion));
}
std::vector<AuthorRef> Addon::getAuthors() const
{ return _authors; }
void Addon::setAuthors(const std::vector<AuthorRef>& addonAuthors)
{ _authors = addonAuthors; }
std::vector<MaintainerRef> Addon::getMaintainers() const
{ return _maintainers; }
void Addon::setMaintainers(const std::vector<MaintainerRef>& addonMaintainers)
{ _maintainers = addonMaintainers; }
std::string Addon::getShortDescription() const
{ return _shortDescription; }
void Addon::setShortDescription(const std::string& addonShortDescription)
{ _shortDescription = addonShortDescription; }
std::string Addon::getLongDescription() const
{ return _longDescription; }
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; }
void Addon::setBasePath(const SGPath& addonBasePath)
{ _basePath = addonBasePath; }
SGPath Addon::getStoragePath() const
{ return _storagePath; }
SGPath Addon::createStorageDir() const
{
if (_storagePath.exists()) {
if (!_storagePath.isDir()) {
string msg =
"Unable to create add-on storage directory because the entry already "
"exists, but is not a directory: '" + _storagePath.utf8Str() + "'";
// Log + throw, because if called from Nasal, only throwing would cause
// the exception message to 1) be truncated and 2) only appear in the
// log, not stopping the sim. Then users would have to figure out why
// their add-on doesn't work...
SG_LOG(SG_GENERAL, SG_POPUP, msg);
throw errors::unable_to_create_addon_storage_dir(msg);
}
} else {
const SGPath authorizedPath = SGPath(_storagePath).validate(/* write */
true);
if (authorizedPath.isNull()) {
string msg =
"Unable to create add-on storage directory because of the FlightGear "
"security policy (refused by SGPath::validate()): '" +
_storagePath.utf8Str() + "'";
SG_LOG(SG_GENERAL, SG_POPUP, msg);
throw errors::unable_to_create_addon_storage_dir(msg);
} else {
simgear::Dir(authorizedPath).create(0777);
}
}
// The sensitive operation (creating the directory) is behind us; return
// _storagePath instead of authorizedPath for consistency with the
// getStoragePath() method (_storagePath and authorizedPath could be
// different in case the former contains symlink components). Further
// sensitive operations beneath _storagePath must use SGPath::validate()
// again every time, of course (otherwise, attackers could use symlinks in
// _storagePath to bypass the security policy).
return _storagePath;
}
std::string Addon::resourcePath(const std::string& relativePath) const
{
if (strutils::starts_with(relativePath, "/")) {
throw errors::invalid_resource_path(
"addon-specific resource path '" + relativePath + "' shouldn't start "
"with a '/'");
}
return "[addon=" + getId() + "]" + relativePath;
}
std::string Addon::getMinFGVersionRequired() const
{ return _minFGVersionRequired; }
void Addon::setMinFGVersionRequired(const string& minFGVersionRequired)
{ _minFGVersionRequired = minFGVersionRequired; }
std::string Addon::getMaxFGVersionRequired() const
{ return _maxFGVersionRequired; }
void Addon::setMaxFGVersionRequired(const string& maxFGVersionRequired)
{
if (maxFGVersionRequired.empty()) {
_maxFGVersionRequired = "none"; // special value
} else {
_maxFGVersionRequired = maxFGVersionRequired;
}
}
std::string Addon::getHomePage() const
{ return _homePage; }
void Addon::setHomePage(const std::string& addonHomePage)
{ _homePage = addonHomePage; }
std::string Addon::getDownloadUrl() const
{ return _downloadUrl; }
void Addon::setDownloadUrl(const std::string& addonDownloadUrl)
{ _downloadUrl = addonDownloadUrl; }
std::string Addon::getSupportUrl() const
{ return _supportUrl; }
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; }
std::string Addon::getTriggerProperty() const
{ return _triggerProperty; }
void Addon::setTriggerProperty(const std::string& addonTriggerProperty)
{ _triggerProperty = addonTriggerProperty; }
SGPropertyNode_ptr Addon::getAddonNode() const
{ return _addonNode; }
void Addon::setAddonNode(SGPropertyNode* addonNode)
{ _addonNode = SGPropertyNode_ptr(addonNode); }
naRef Addon::getAddonPropsNode() const
{
FGNasalSys* nas = globals->get_subsystem<FGNasalSys>();
return nas->wrappedPropsNode(_addonNode.get());
}
SGPropertyNode_ptr Addon::getLoadedFlagNode() const
{
return { _addonNode->getChild("loaded", 0, 1) };
}
int Addon::getLoadSequenceNumber() const
{ return _loadSequenceNumber; }
void Addon::setLoadSequenceNumber(int num)
{ _loadSequenceNumber = num; }
std::multimap<UrlType, QualifiedUrl> Addon::getUrls() const
{
std::multimap<UrlType, QualifiedUrl> res;
auto appendIfNonEmpty = [&res](UrlType type, string url, string detail = "") {
if (!url.empty()) {
res.emplace(type, QualifiedUrl(type, std::move(url), std::move(detail)));
}
};
for (const auto& author: _authors) {
appendIfNonEmpty(UrlType::author, author->getUrl(), author->getName());
}
for (const auto& maint: _maintainers) {
appendIfNonEmpty(UrlType::maintainer, maint->getUrl(), maint->getName());
}
appendIfNonEmpty(UrlType::homePage, getHomePage());
appendIfNonEmpty(UrlType::download, getDownloadUrl());
appendIfNonEmpty(UrlType::support, getSupportUrl());
appendIfNonEmpty(UrlType::codeRepository, getCodeRepositoryUrl());
appendIfNonEmpty(UrlType::license, getLicenseUrl());
return res;
}
std::vector<SGPropertyNode_ptr> Addon::getMenubarNodes() const
{ return _menubarNodes; }
void Addon::setMenubarNodes(const std::vector<SGPropertyNode_ptr>& menubarNodes)
{ _menubarNodes = menubarNodes; }
void Addon::addToFGMenubar() const
{
SGPropertyNode* menuRootNode = fgGetNode("/sim/menubar/default", true);
for (const auto& node: getMenubarNodes()) {
SGPropertyNode* childNode = menuRootNode->addChild("menu");
::copyProperties(node.ptr(), childNode);
}
}
std::string Addon::str() const
{
std::ostringstream oss;
oss << "addon '" << _id << "' (version = " << *_version
<< ", base path = '" << _basePath.utf8Str()
<< "', minFGVersionRequired = '" << _minFGVersionRequired
<< "', maxFGVersionRequired = '" << _maxFGVersionRequired << "')";
return oss.str();
}
// Static method
SGPath Addon::getMetadataFile(const SGPath& addonPath)
{
return MetadataParser::getMetadataFile(addonPath);
}
SGPath Addon::getMetadataFile() const
{
return getMetadataFile(getBasePath());
}
// Static method
Addon Addon::fromAddonDir(const SGPath& addonPath)
{
Addon::Metadata metadata = MetadataParser::parseMetadataFile(addonPath);
// Object holding all the add-on metadata
Addon addon{std::move(metadata.id), std::move(metadata.version), addonPath,
std::move(metadata.minFGVersionRequired),
std::move(metadata.maxFGVersionRequired)};
addon.setName(std::move(metadata.name));
addon.setAuthors(std::move(metadata.authors));
addon.setMaintainers(std::move(metadata.maintainers));
addon.setShortDescription(std::move(metadata.shortDescription));
addon.setLongDescription(std::move(metadata.longDescription));
addon.setLicenseDesignation(std::move(metadata.licenseDesignation));
addon.setLicenseFile(std::move(metadata.licenseFile));
addon.setLicenseUrl(std::move(metadata.licenseUrl));
addon.setTags(std::move(metadata.tags));
addon.setHomePage(std::move(metadata.homePage));
addon.setDownloadUrl(std::move(metadata.downloadUrl));
addon.setSupportUrl(std::move(metadata.supportUrl));
addon.setCodeRepositoryUrl(std::move(metadata.codeRepositoryUrl));
SGPath menuFile = addonPath / "addon-menubar-items.xml";
if (menuFile.exists()) {
addon.setMenubarNodes(readMenubarItems(menuFile));
}
return addon;
}
// Static method
std::vector<SGPropertyNode_ptr>
Addon::readMenubarItems(const SGPath& menuFile)
{
SGPropertyNode rootNode;
try {
readProperties(menuFile, &rootNode);
} catch (const sg_exception &e) {
throw errors::error_loading_menubar_items_file(
"unable to load add-on menu bar items from file '" +
menuFile.utf8Str() + "': " + e.getFormattedMessage());
}
// Check the 'meta' section
SGPropertyNode *metaNode = rootNode.getChild("meta");
if (metaNode == nullptr) {
throw errors::error_loading_menubar_items_file(
"no /meta node found in add-on menu bar items file '" +
menuFile.utf8Str() + "'");
}
// Check the file type
SGPropertyNode *fileTypeNode = metaNode->getChild("file-type");
if (fileTypeNode == nullptr) {
throw errors::error_loading_menubar_items_file(
"no /meta/file-type node found in add-on menu bar items file '" +
menuFile.utf8Str() + "'");
}
string fileType = fileTypeNode->getStringValue();
if (fileType != "FlightGear add-on menu bar items") {
throw errors::error_loading_menubar_items_file(
"Invalid /meta/file-type value for add-on menu bar items file '" +
menuFile.utf8Str() + "': '" + fileType + "' "
"(expected 'FlightGear add-on menu bar items')");
}
// Check the format version
SGPropertyNode *fmtVersionNode = metaNode->getChild("format-version");
if (fmtVersionNode == nullptr) {
throw errors::error_loading_menubar_items_file(
"no /meta/format-version node found in add-on menu bar items file '" +
menuFile.utf8Str() + "'");
}
int formatVersion = fmtVersionNode->getIntValue();
if (formatVersion != 1) {
throw errors::error_loading_menubar_items_file(
"unknown format version in add-on menu bar items file '" +
menuFile.utf8Str() + "': " + std::to_string(formatVersion));
}
SG_LOG(SG_GENERAL, SG_DEBUG,
"Loaded add-on menu bar items from '" << menuFile.utf8Str() + "'");
SGPropertyNode *menubarItemsNode = rootNode.getChild("menubar-items");
std::vector<SGPropertyNode_ptr> res;
if (menubarItemsNode != nullptr) {
res = menubarItemsNode->getChildren("menu");
}
return res;
}
void Addon::retranslate()
{
Addon::Metadata metadata = MetadataParser::parseMetadataFile(_basePath);
setName(std::move(metadata.name));
setShortDescription(std::move(metadata.shortDescription));
setLongDescription(std::move(metadata.longDescription));
}
// Static method
void Addon::setupGhost(nasal::Hash& addonsModule)
{
nasal::Ghost<AddonRef>::init("addons.Addon")
.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("storagePath", &Addon::getStoragePath)
.method("createStorageDir", &Addon::createStorageDir)
.method("resourcePath", &Addon::resourcePath)
.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("triggerProperty", &Addon::getTriggerProperty)
.member("node", &Addon::getAddonPropsNode)
.member("loadSequenceNumber", &Addon::getLoadSequenceNumber);
}
std::ostream& operator<<(std::ostream& os, const Addon& addon)
{
return os << addon.str();
}
} // of namespace addons
} // of namespace flightgear

267
src/Add-ons/Addon.hxx Normal file
View File

@@ -0,0 +1,267 @@
// -*- coding: utf-8 -*-
//
// Addon.hxx --- FlightGear class holding add-on metadata
// Copyright (C) 2017, 2018 Florent Rougon
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#ifndef FG_ADDON_HXX
#define FG_ADDON_HXX
#include <map>
#include <ostream>
#include <string>
#include <vector>
#include <simgear/misc/sg_path.hxx>
#include <simgear/nasal/cppbind/NasalHash.hxx>
#include <simgear/nasal/naref.h>
#include <simgear/props/props.hxx>
#include <simgear/structure/SGReferenced.hxx>
#include "addon_fwd.hxx"
#include "contacts.hxx"
#include "AddonVersion.hxx"
#include "pointer_traits.hxx"
namespace flightgear
{
namespace addons
{
enum class UrlType {
author,
maintainer,
homePage,
download,
support,
codeRepository,
license
};
class QualifiedUrl
{
public:
QualifiedUrl(UrlType type, std::string url, std::string detail = "");
UrlType getType() const;
void setType(UrlType type);
std::string getUrl() const;
void setUrl(const std::string& url);
std::string getDetail() const;
void setDetail(const std::string& detail);
private:
UrlType _type;
std::string _url;
// Used to store the author or maintainer name when _type is UrlType::author
// or UrlType::maintainer. Could be used to record details about a website
// too (e.g., for a UrlType::support, something like “official forum”).
std::string _detail;
};
class Addon : public SGReferenced
{
public:
// An empty value for 'minFGVersionRequired' is translated into "2017.4.0".
// An empty value for 'maxFGVersionRequired' is translated into "none".
Addon(std::string id, AddonVersion version = AddonVersion(),
SGPath basePath = SGPath(), std::string minFGVersionRequired = "",
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);
template<class T>
static T fromAddonDir(const SGPath& addonPath)
{
using ptr_traits = shared_ptr_traits<T>;
return ptr_traits::makeStrongRef(fromAddonDir(addonPath));
}
std::string getId() const;
std::string getName() const;
void setName(const std::string& addonName);
AddonVersionRef getVersion() const;
void setVersion(const AddonVersion& addonVersion);
std::vector<AuthorRef> getAuthors() const;
void setAuthors(const std::vector<AuthorRef>& addonAuthors);
std::vector<MaintainerRef> getMaintainers() const;
void setMaintainers(const std::vector<MaintainerRef>& 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);
// Return $FG_HOME/Export/Addons/ADDON_ID as an SGPath instance.
SGPath getStoragePath() const;
// Create directory $FG_HOME/Export/Addons/ADDON_ID, including any parent,
// if it doesn't already exist. Throw an exception in case of problems.
// Return an SGPath instance for the directory (same as getStoragePath()).
SGPath createStorageDir() const;
// Return a resource path suitable for use with the simgear::ResourceManager.
// 'relativePath' is relative to the add-on base path, and should not start
// with a '/'.
std::string resourcePath(const std::string& relativePath) const;
// Should be valid for use with simgear::strutils::compare_versions()
std::string getMinFGVersionRequired() const;
void setMinFGVersionRequired(const std::string& minFGVersionRequired);
// Should be valid for use with simgear::strutils::compare_versions(),
// except for the special value "none".
std::string getMaxFGVersionRequired() const;
void setMaxFGVersionRequired(const std::string& maxFGVersionRequired);
std::string getHomePage() const;
void setHomePage(const std::string& addonHomePage);
std::string getDownloadUrl() const;
void setDownloadUrl(const std::string& addonDownloadUrl);
std::string getSupportUrl() const;
void setSupportUrl(const std::string& addonSupportUrl);
std::string getCodeRepositoryUrl() const;
void setCodeRepositoryUrl(const std::string& addonCodeRepositoryUrl);
std::string getTriggerProperty() const;
void setTriggerProperty(const std::string& addonTriggerProperty);
// Node pertaining to the add-on in the Global Property Tree
SGPropertyNode_ptr getAddonNode() const;
void setAddonNode(SGPropertyNode* addonNode);
// For Nasal: result as a props.Node object
naRef getAddonPropsNode() const;
// Property node indicating whether the add-on is fully loaded
SGPropertyNode_ptr getLoadedFlagNode() const;
// 0 for the first loaded add-on, 1 for the second, etc.
// -1 means “not set” (as done by the default constructor)
int getLoadSequenceNumber() const;
void setLoadSequenceNumber(int num);
// Get all non-empty URLs pertaining to this add-on
std::multimap<UrlType, QualifiedUrl> getUrls() const;
// Getter and setter for the menu bar item nodes of the add-on
std::vector<SGPropertyNode_ptr> getMenubarNodes() const;
void setMenubarNodes(const std::vector<SGPropertyNode_ptr>& menubarNodes);
// Add the menus defined in addon-menubar-items.xml to /sim/menubar/default
void addToFGMenubar() const;
// Simple string representation
std::string str() const;
static void setupGhost(nasal::Hash& addonsModule);
/**
* @brief update string values (description, etc) based on the active locale
*/
void retranslate();
private:
class Metadata;
class MetadataParser;
// “Compute” a path to the metadata file from the add-on base path
static SGPath getMetadataFile(const SGPath& addonPath);
SGPath getMetadataFile() const;
// Read all menus from addon-menubar-items.xml (under the add-on base path)
static std::vector<SGPropertyNode_ptr>
readMenubarItems(const SGPath& menuFile);
// 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.
const std::string _id;
// Pretty name for the add-on (not constrained to reverse DNS style)
std::string _name;
// Use a smart pointer to expose the AddonVersion instance to Nasal without
// needing to copy the data every time.
AddonVersionRef _version;
std::vector<AuthorRef> _authors;
std::vector<MaintainerRef> _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;
// $FG_HOME/Export/Addons/ADDON_ID
const SGPath _storagePath;
// 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;
// The add-on will be loaded when the property referenced by
// _triggerProperty is written to.
std::string _triggerProperty;
// Semantics explained above
int _loadSequenceNumber = -1;
std::vector<SGPropertyNode_ptr> _menubarNodes;
};
std::ostream& operator<<(std::ostream& os, const Addon& addon);
} // of namespace addons
} // of namespace flightgear
#endif // of FG_ADDON_HXX

View File

@@ -0,0 +1,296 @@
// -*- coding: utf-8 -*-
//
// AddonManager.cxx --- Manager class for FlightGear add-ons
// Copyright (C) 2017 Florent Rougon
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#include "config.h"
#include <algorithm>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include <cstdlib>
#include <cassert>
#include <simgear/debug/logstream.hxx>
#include <simgear/misc/sg_path.hxx>
#include <simgear/misc/strutils.hxx>
#include <simgear/props/props.hxx>
#include <simgear/props/props_io.hxx>
#include <simgear/structure/exception.hxx>
#include <Main/fg_props.hxx>
#include <Main/globals.hxx>
#include "addon_fwd.hxx"
#include "Addon.hxx"
#include "AddonManager.hxx"
#include "AddonVersion.hxx"
#include "exceptions.hxx"
#include "pointer_traits.hxx"
namespace strutils = simgear::strutils;
using std::string;
using std::vector;
using std::shared_ptr;
using std::unique_ptr;
namespace flightgear
{
namespace addons
{
static unique_ptr<AddonManager> staticInstance;
// ***************************************************************************
// * AddonManager *
// ***************************************************************************
// Static method
const unique_ptr<AddonManager>&
AddonManager::createInstance()
{
SG_LOG(SG_GENERAL, SG_DEBUG, "Initializing the AddonManager");
staticInstance.reset(new AddonManager());
return staticInstance;
}
// Static method
const unique_ptr<AddonManager>&
AddonManager::instance()
{
return staticInstance;
}
// Static method
void
AddonManager::reset()
{
SG_LOG(SG_GENERAL, SG_DEBUG, "Resetting the AddonManager");
staticInstance.reset();
}
// Static method
void
AddonManager::loadConfigFileIfExists(const SGPath& configFile)
{
if (!configFile.exists()) {
return;
}
SGPropertyNode_ptr configProps(new SGPropertyNode);
try {
readProperties(configFile, configProps);
} catch (const sg_exception &e) {
throw errors::error_loading_config_file(
"unable to load add-on config file '" + configFile.utf8Str() + "': " +
e.getFormattedMessage());
}
// bug https://sourceforge.net/p/flightgear/codetickets/2059/
// since we're loading this after autosave.xml is loaded, the defaults
// always take precedence. To fix this, only copy a value from the
// addon-config if it's not makred as ARCHIVE. (We assume ARCHIVE props
// came from autosave.xml)
copyPropertiesIf(configProps, globals->get_props(), [](const SGPropertyNode* src) {
if (src->nChildren() > 0)
return true;
// find the correspnding destination node
auto dstNode = globals->get_props()->getNode(src->getPath());
if (!dstNode)
return true; // easy, just copy it
// copy if it's NOT marked archive. In other words, we can replace
// values from defaults, but not autosave
return dstNode->getAttribute(SGPropertyNode::USERARCHIVE) == false;
});
SG_LOG(SG_GENERAL, SG_INFO,
"Loaded add-on config file: '" << configFile.utf8Str() + "'");
}
string
AddonManager::registerAddonMetadata(const SGPath& addonPath)
{
using ptr_traits = shared_ptr_traits<AddonRef>;
AddonRef addon = ptr_traits::makeStrongRef(Addon::fromAddonDir(addonPath));
string addonId = addon->getId();
SGPropertyNode* addonPropNode = fgGetNode("addons", true)
->getChild("by-id", 0, 1)
->getChild(addonId, 0, 1);
addon->setAddonNode(addonPropNode);
addon->setLoadSequenceNumber(_loadSequenceNumber++);
// Check that the FlightGear version satisfies the add-on requirements
std::string minFGversion = addon->getMinFGVersionRequired();
if (strutils::compare_versions(FLIGHTGEAR_VERSION, minFGversion) < 0) {
throw errors::fg_version_too_old(
"add-on '" + addonId + "' requires FlightGear " + minFGversion +
" or later, however this is FlightGear " + FLIGHTGEAR_VERSION);
}
std::string maxFGversion = addon->getMaxFGVersionRequired();
if (maxFGversion != "none" &&
strutils::compare_versions(FLIGHTGEAR_VERSION, maxFGversion) > 0) {
throw errors::fg_version_too_recent(
"add-on '" + addonId + "' requires FlightGear " + maxFGversion +
" or earlier, however this is FlightGear " + FLIGHTGEAR_VERSION);
}
// Store the add-on metadata in _idToAddonMap
auto emplaceRetval = _idToAddonMap.emplace(addonId, std::move(addon));
// Prevent registration of two add-ons with the same id
if (!emplaceRetval.second) {
auto existingElt = _idToAddonMap.find(addonId);
assert(existingElt != _idToAddonMap.end());
throw errors::duplicate_registration_attempt(
"attempt to register add-on '" + addonId + "' with base path '"
+ addonPath.utf8Str() + "', however it is already registered with base "
"path '" + existingElt->second->getBasePath().utf8Str() + "'");
}
return addonId;
}
string
AddonManager::registerAddon(const SGPath& addonPath)
{
// Use realpath() as in FGGlobals::append_aircraft_path(), otherwise
// SGPath::validate() will deny access to resources under the add-on path
// if one of its components is a symlink.
const SGPath addonRealPath = addonPath.realpath();
const string addonId = registerAddonMetadata(addonRealPath);
loadConfigFileIfExists(addonRealPath / "addon-config.xml");
globals->append_aircraft_path(addonRealPath);
AddonRef addon{getAddon(addonId)};
addon->getLoadedFlagNode()->setBoolValue(false);
SGPropertyNode_ptr addonNode = addon->getAddonNode();
// Set a few properties for the add-on under this node
addonNode->getNode("id", true)->setStringValue(addonId);
addonNode->getNode("name", true)->setStringValue(addon->getName());
addonNode->getNode("version", true)
->setStringValue(addonVersion(addonId)->str());
addonNode->getNode("path", true)->setStringValue(addonRealPath.utf8Str());
addonNode->getNode("load-seq-num", true)
->setIntValue(addon->getLoadSequenceNumber());
// “Legacy node”. Should we remove these two lines?
SGPropertyNode* seqNumNode = fgGetNode("addons", true)->addChild("addon");
seqNumNode->getNode("path", true)->setStringValue(addonRealPath.utf8Str());
string msg = "Registered add-on '" + addon->getName() + "' (" + addonId +
") version " + addonVersion(addonId)->str() + "; "
"base path is '" + addonRealPath.utf8Str() + "'";
auto dataPath = addonRealPath / "FGData";
if (dataPath.exists()) {
SG_LOG(SG_GENERAL, SG_INFO, "Registering data path for add-on: " << addon->getName());
globals->append_data_path(dataPath, true /* after FG_ROOT */);
}
// This preserves the registration order
_registeredAddons.push_back(addon);
SG_LOG(SG_GENERAL, SG_INFO, msg);
return addonId;
}
bool
AddonManager::isAddonRegistered(const string& addonId) const
{
return (_idToAddonMap.find(addonId) != _idToAddonMap.end());
}
bool
AddonManager::isAddonLoaded(const string& addonId) const
{
return (isAddonRegistered(addonId) &&
getAddon(addonId)->getLoadedFlagNode()->getBoolValue());
}
vector<AddonRef>
AddonManager::registeredAddons() const
{
return _registeredAddons;
}
vector<AddonRef>
AddonManager::loadedAddons() const
{
vector<AddonRef> v;
v.reserve(_idToAddonMap.size()); // will be the right size most of the times
for (const auto& elem: _idToAddonMap) {
if (isAddonLoaded(elem.first)) {
v.push_back(elem.second);
}
}
return v;
}
AddonRef
AddonManager::getAddon(const string& addonId) const
{
const auto it = _idToAddonMap.find(addonId);
if (it == _idToAddonMap.end()) {
throw sg_exception("tried to get add-on '" + addonId + "', however no "
"such add-on has been registered.");
}
return it->second;
}
AddonVersionRef
AddonManager::addonVersion(const string& addonId) const
{
return getAddon(addonId)->getVersion();
}
SGPath
AddonManager::addonBasePath(const string& addonId) const
{
return getAddon(addonId)->getBasePath();
}
SGPropertyNode_ptr AddonManager::addonNode(const string& addonId) const
{
return getAddon(addonId)->getAddonNode();
}
void
AddonManager::addAddonMenusToFGMenubar() const
{
for (const auto& addon: _registeredAddons) {
addon->addToFGMenubar();
}
}
} // of namespace addons
} // of namespace flightgear

View File

@@ -0,0 +1,114 @@
// -*- coding: utf-8 -*-
//
// AddonManager.hxx --- Manager class for FlightGear add-ons
// Copyright (C) 2017 Florent Rougon
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#ifndef FG_ADDONMANAGER_HXX
#define FG_ADDONMANAGER_HXX
#include <string>
#include <map>
#include <memory> // std::unique_ptr, std::shared_ptr
#include <vector>
#include <simgear/misc/sg_path.hxx>
#include <simgear/props/props.hxx>
#include "addon_fwd.hxx"
#include "Addon.hxx"
#include "AddonVersion.hxx"
namespace flightgear
{
namespace addons
{
class AddonManager
{
public:
AddonManager(const AddonManager&) = delete;
AddonManager& operator=(const AddonManager&) = delete;
AddonManager(AddonManager&&) = delete;
AddonManager& operator=(AddonManager&&) = delete;
// The instance is created by createInstance() -> private constructor
// but it should be deleted by its owning std::unique_ptr -> public destructor
~AddonManager() = default;
// Static creator
static const std::unique_ptr<AddonManager>& createInstance();
// Singleton accessor
static const std::unique_ptr<AddonManager>& instance();
// Reset the static smart pointer, i.e., shut down the AddonManager.
static void reset();
// Register an add-on and return its id.
// 'addonPath': directory containing the add-on to register
//
// This comprises the following steps, where $path = addonPath.realpath():
// - load add-on metadata from $path/addon-metadata.xml and register it
// inside _idToAddonMap (this step is done via registerAddonMetadata());
// - load $path/addon-config.xml into the Property Tree;
// - append $path to the list of aircraft paths;
// - make part of the add-on metadata available in the Property Tree under
// the /addons node (/addons/by-id/<addonId>/{id,version,path,...});
// - append a ref to the Addon instance to _registeredAddons.
std::string registerAddon(const SGPath& addonPath);
// Return the list of registered add-ons in registration order (which, BTW,
// is the same as load order).
std::vector<AddonRef> registeredAddons() const;
bool isAddonRegistered(const std::string& addonId) const;
// A loaded add-on is one whose addon-main.nas file has been loaded. The
// returned vector is sorted by add-on id (cheap sorting based on UTF-8 code
// units, only guaranteed correct for ASCII chars).
std::vector<AddonRef> loadedAddons() const;
bool isAddonLoaded(const std::string& addonId) const;
AddonRef getAddon(const std::string& addonId) const;
AddonVersionRef addonVersion(const std::string& addonId) const;
SGPath addonBasePath(const std::string& addonId) const;
// Base node pertaining to the add-on in the Global Property Tree
SGPropertyNode_ptr addonNode(const std::string& addonId) const;
// Add the 'menu' nodes defined by each registered add-on to
// /sim/menubar/default
void addAddonMenusToFGMenubar() const;
private:
// Constructor called from createInstance() only
explicit AddonManager() = default;
static void loadConfigFileIfExists(const SGPath& configFile);
// Register add-on metadata inside _idToAddonMap and return the add-on id
std::string registerAddonMetadata(const SGPath& addonPath);
// Map each add-on id to the corresponding Addon instance.
std::map<std::string, AddonRef> _idToAddonMap;
// The order in _registeredAddons is the registration order.
std::vector<AddonRef> _registeredAddons;
// 0 for the first loaded add-on, 1 for the second, etc.
// Also note that add-ons are loaded in their registration order.
int _loadSequenceNumber = 0;
};
} // of namespace addons
} // of namespace flightgear
#endif // of FG_ADDONMANAGER_HXX

View File

@@ -0,0 +1,416 @@
// -*- coding: utf-8 -*-
//
// AddonMetadataParser.cxx --- Parser for FlightGear add-on metadata files
// Copyright (C) 2018 Florent Rougon
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#include <regex>
#include <string>
#include <tuple>
#include <vector>
#include <simgear/debug/logstream.hxx>
#include <simgear/misc/sg_path.hxx>
#include <simgear/misc/strutils.hxx>
#include <simgear/props/props.hxx>
#include <simgear/props/props_io.hxx>
#include "addon_fwd.hxx"
#include "AddonMetadataParser.hxx"
#include "AddonVersion.hxx"
#include "contacts.hxx"
#include "exceptions.hxx"
#include "pointer_traits.hxx"
#include <Main/globals.hxx>
#include <Main/locale.hxx>
namespace strutils = simgear::strutils;
using std::string;
using std::vector;
namespace flightgear
{
namespace addons
{
// Static method
SGPath
Addon::MetadataParser::getMetadataFile(const SGPath& addonPath)
{
return addonPath / "addon-metadata.xml";
}
static string getMaybeLocalized(const string& tag, SGPropertyNode* base, SGPropertyNode* lang)
{
if (lang) {
auto n = lang->getChild(tag);
if (n) {
return strutils::strip(n->getStringValue());
}
}
auto n = base->getChild(tag);
if (n) {
return strutils::strip(n->getStringValue());
}
return {};
}
static SGPropertyNode* getAndCheckLocalizedNode(SGPropertyNode* addonNode,
const SGPath& metadataFile)
{
const auto localizedNode = addonNode->getChild("localized");
if (!localizedNode) {
return nullptr;
}
for (int i = 0; i < localizedNode->nChildren(); ++i) {
const auto node = localizedNode->getChild(i);
const string& name = node->getNameString();
if (name.find('_') != string::npos) {
throw errors::error_loading_metadata_file(
"underscores not allowed in names of children of <localized> "
"(in add-on metadata file '" + metadataFile.utf8Str() + "'); "
"hyphens should be used, as in 'fr-FR' or 'en-GB'");
}
}
return localizedNode;
}
// Static method
Addon::Metadata
Addon::MetadataParser::parseMetadataFile(const SGPath& addonPath)
{
SGPath metadataFile = getMetadataFile(addonPath);
SGPropertyNode addonRoot;
Addon::Metadata metadata;
if (!metadataFile.exists()) {
throw errors::no_metadata_file_found(
"unable to find add-on metadata file '" + metadataFile.utf8Str() + "'");
}
try {
readProperties(metadataFile, &addonRoot);
} catch (const sg_exception &e) {
throw 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 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 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 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 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 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 errors::error_loading_metadata_file(
"no /addon node found in add-on metadata file '" +
metadataFile.utf8Str() + "'");
}
const auto localizedNode = getAndCheckLocalizedNode(addonNode, metadataFile);
SGPropertyNode* langStringsNode = globals->get_locale()->selectLanguageNode(localizedNode);
SGPropertyNode *idNode = addonNode->getChild("identifier");
if (idNode == nullptr) {
throw errors::error_loading_metadata_file(
"no /addon/identifier node found in add-on metadata file '" +
metadataFile.utf8Str() + "'");
}
metadata.id = strutils::strip(idNode->getStringValue());
// Require a non-empty identifier for the add-on
if (metadata.id.empty()) {
throw errors::error_loading_metadata_file(
"empty or whitespace-only value for the /addon/identifier node in "
"add-on metadata file '" + metadataFile.utf8Str() + "'");
} else if (metadata.id.find('.') == string::npos) {
SG_LOG(SG_GENERAL, SG_WARN,
"Add-on identifier '" << metadata.id << "' 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 errors::error_loading_metadata_file(
"no /addon/name node found in add-on metadata file '" +
metadataFile.utf8Str() + "'");
}
metadata.name = getMaybeLocalized("name", addonNode, langStringsNode);
// Require a non-empty name for the add-on
if (metadata.name.empty()) {
throw 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 errors::error_loading_metadata_file(
"no /addon/version node found in add-on metadata file '" +
metadataFile.utf8Str() + "'");
}
metadata.version = AddonVersion{
strutils::strip(versionNode->getStringValue())};
metadata.authors = parseContactsNode<Author>(metadataFile,
addonNode->getChild("authors"));
metadata.maintainers = parseContactsNode<Maintainer>(
metadataFile, addonNode->getChild("maintainers"));
metadata.shortDescription = getMaybeLocalized("short-description", addonNode, langStringsNode);
metadata.longDescription = getMaybeLocalized("long-description", addonNode, langStringsNode);
std::tie(metadata.licenseDesignation, metadata.licenseFile,
metadata.licenseUrl) = parseLicenseNode(addonPath, addonNode);
SGPropertyNode *tagsNode = addonNode->getChild("tags");
if (tagsNode != nullptr) {
auto tagNodes = tagsNode->getChildren("tag");
for (const auto& node: tagNodes) {
metadata.tags.push_back(strutils::strip(node->getStringValue()));
}
}
SGPropertyNode *minNode = addonNode->getChild("min-FG-version");
if (minNode != nullptr) {
metadata.minFGVersionRequired = strutils::strip(minNode->getStringValue());
} else {
metadata.minFGVersionRequired = string();
}
SGPropertyNode *maxNode = addonNode->getChild("max-FG-version");
if (maxNode != nullptr) {
metadata.maxFGVersionRequired = strutils::strip(maxNode->getStringValue());
} else {
metadata.maxFGVersionRequired = string();
}
metadata.homePage = metadata.downloadUrl = metadata.supportUrl =
metadata.codeRepositoryUrl = string(); // defaults
SGPropertyNode *urlsNode = addonNode->getChild("urls");
if (urlsNode != nullptr) {
SGPropertyNode *homePageNode = urlsNode->getChild("home-page");
if (homePageNode != nullptr) {
metadata.homePage = strutils::strip(homePageNode->getStringValue());
}
SGPropertyNode *downloadUrlNode = urlsNode->getChild("download");
if (downloadUrlNode != nullptr) {
metadata.downloadUrl = strutils::strip(downloadUrlNode->getStringValue());
}
SGPropertyNode *supportUrlNode = urlsNode->getChild("support");
if (supportUrlNode != nullptr) {
metadata.supportUrl = strutils::strip(supportUrlNode->getStringValue());
}
SGPropertyNode *codeRepoUrlNode = urlsNode->getChild("code-repository");
if (codeRepoUrlNode != nullptr) {
metadata.codeRepositoryUrl =
strutils::strip(codeRepoUrlNode->getStringValue());
}
}
SG_LOG(SG_GENERAL, SG_DEBUG,
"Parsed add-on metadata file: '" << metadataFile.utf8Str() + "'");
return metadata;
}
// Utility function for Addon::MetadataParser::parseContactsNode<>()
//
// Read a node such as "name", "email" or "url", child of a contact node (e.g.,
// of an "author" or "maintainer" node).
static string
parseContactsNode_readNode(const SGPath& metadataFile,
SGPropertyNode* contactNode,
string subnodeName, bool allowEmpty)
{
SGPropertyNode *node = contactNode->getChild(subnodeName);
string contents;
if (node != nullptr) {
contents = simgear::strutils::strip(node->getStringValue());
}
if (!allowEmpty && contents.empty()) {
throw errors::error_loading_metadata_file(
"in add-on metadata file '" + metadataFile.utf8Str() + "': "
"when the node " + contactNode->getPath(true) + " exists, it must have "
"a non-empty '" + subnodeName + "' child node");
}
return contents;
};
// Static method template (private and only used in this file)
template <class T>
vector<typename contact_traits<T>::strong_ref>
Addon::MetadataParser::parseContactsNode(const SGPath& metadataFile,
SGPropertyNode* mainNode)
{
using contactTraits = contact_traits<T>;
vector<typename contactTraits::strong_ref> res;
if (mainNode != nullptr) {
auto contactNodes = mainNode->getChildren(contactTraits::xmlNodeName());
res.reserve(contactNodes.size());
for (const auto& contactNode: contactNodes) {
string name, email, url;
name = parseContactsNode_readNode(metadataFile, contactNode.get(),
"name", false /* allowEmpty */);
email = parseContactsNode_readNode(metadataFile, contactNode.get(),
"email", true);
url = parseContactsNode_readNode(metadataFile, contactNode.get(),
"url", true);
using ptr_traits = shared_ptr_traits<typename contactTraits::strong_ref>;
res.push_back(ptr_traits::makeStrongRef(name, email, url));
}
}
return res;
};
// Static method
std::tuple<string, SGPath, string>
Addon::MetadataParser::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] + "'");
}
#ifdef HAVE_WORKING_STD_REGEX
std::regex winDriveRegexp("([a-zA-Z]:).*");
std::smatch results;
if (std::regex_match(licenseFile_s, results, winDriveRegexp)) {
string winDrive = results.str(1);
#else // all this 'else' clause should be removed once we actually require C++11
if (licenseFile_s.size() >= 2 &&
(('a' <= licenseFile_s[0] && licenseFile_s[0] <= 'z') ||
('A' <= licenseFile_s[0] && licenseFile_s[0] <= 'Z')) &&
licenseFile_s[1] == ':') {
string winDrive = licenseFile_s.substr(0, 2);
#endif
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);
}
} // of namespace addons
} // of namespace flightgear

View File

@@ -0,0 +1,97 @@
// -*- coding: utf-8 -*-
//
// AddonMetadataParser.hxx --- Parser for FlightGear add-on metadata files
// Copyright (C) 2018 Florent Rougon
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#ifndef FG_ADDON_METADATA_PARSER_HXX
#define FG_ADDON_METADATA_PARSER_HXX
#include <string>
#include <tuple>
#include <vector>
#include <simgear/misc/sg_path.hxx>
#include "addon_fwd.hxx"
#include "Addon.hxx"
#include "AddonVersion.hxx"
#include "contacts.hxx"
class SGPropertyNode;
namespace flightgear
{
namespace addons
{
class Addon::Metadata
{
public:
// Comments about these fields can be found in Addon.hxx
std::string id;
std::string name;
AddonVersion version;
std::vector<AuthorRef> authors;
std::vector<MaintainerRef> maintainers;
std::string shortDescription;
std::string longDescription;
std::string licenseDesignation;
SGPath licenseFile;
std::string licenseUrl;
std::vector<std::string> tags;
std::string minFGVersionRequired;
std::string maxFGVersionRequired;
std::string homePage;
std::string downloadUrl;
std::string supportUrl;
std::string codeRepositoryUrl;
};
class Addon::MetadataParser
{
public:
// “Compute” a path to the metadata file from the add-on base path
static SGPath getMetadataFile(const SGPath& addonPath);
// Parse the add-on metadata file inside 'addonPath' (as defined by
// getMetadataFile()) and return the corresponding Addon::Metadata instance.
static Addon::Metadata parseMetadataFile(const SGPath& addonPath);
private:
static std::tuple<string, SGPath, string>
parseLicenseNode(const SGPath& addonPath, SGPropertyNode* addonNode);
// Parse an addon-metadata.xml node such as <authors> or <maintainers>.
// Return the corresponding vector<AuthorRef> or vector<MaintainerRef>. If
// the 'mainNode' argument is nullptr, return an empty vector.
template <class T>
static std::vector<typename contact_traits<T>::strong_ref>
parseContactsNode(const SGPath& metadataFile, SGPropertyNode* mainNode);
};
} // of namespace addons
} // of namespace flightgear
#endif // of FG_ADDON_METADATA_PARSER_HXX

View File

@@ -0,0 +1,78 @@
// -*- coding: utf-8 -*-
//
// AddonResourceProvider.cxx --- ResourceProvider subclass for add-on files
// Copyright (C) 2018 Florent Rougon
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#include <string>
#include <simgear/misc/ResourceManager.hxx>
#include <simgear/misc/sg_path.hxx>
#include <simgear/misc/strutils.hxx>
#include "AddonManager.hxx"
#include "AddonResourceProvider.hxx"
namespace strutils = simgear::strutils;
using std::string;
namespace flightgear
{
namespace addons
{
ResourceProvider::ResourceProvider()
: simgear::ResourceProvider(simgear::ResourceManager::PRIORITY_DEFAULT)
{ }
SGPath
ResourceProvider::resolve(const string& resource, SGPath& context) const
{
if (!strutils::starts_with(resource, "[addon=")) {
return SGPath();
}
string rest = resource.substr(7); // what follows '[addon='
auto endOfAddonId = rest.find(']');
if (endOfAddonId == string::npos) {
return SGPath();
}
string addonId = rest.substr(0, endOfAddonId);
// Extract what follows '[addon=ADDON_ID]'
string relPath = rest.substr(endOfAddonId + 1);
if (relPath.empty()) {
return SGPath();
}
const auto& addonMgr = AddonManager::instance();
SGPath addonDir = addonMgr->addonBasePath(addonId);
SGPath candidate = addonDir / relPath;
if (!candidate.isFile()) {
return SGPath();
}
return SGPath(candidate).validate(/* write */ false);
}
} // of namespace addons
} // of namespace flightgear

View File

@@ -0,0 +1,47 @@
// -*- coding: utf-8 -*-
//
// AddonResourceProvider.hxx --- ResourceProvider subclass for add-on files
// Copyright (C) 2018 Florent Rougon
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#ifndef FG_ADDON_RESOURCE_PROVIDER_HXX
#define FG_ADDON_RESOURCE_PROVIDER_HXX
#include <string>
#include <simgear/misc/ResourceManager.hxx>
#include <simgear/misc/sg_path.hxx>
namespace flightgear
{
namespace addons
{
class ResourceProvider : public simgear::ResourceProvider
{
public:
ResourceProvider();
virtual SGPath resolve(const std::string& resource, SGPath& context) const
override;
};
} // of namespace addons
} // of namespace flightgear
#endif // of FG_ADDON_RESOURCE_PROVIDER_HXX

View File

@@ -0,0 +1,569 @@
// -*- coding: utf-8 -*-
//
// AddonVersion.cxx --- Version class for FlightGear add-ons
// Copyright (C) 2017 Florent Rougon
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#include <numeric> // std::accumulate()
#include <ostream>
#include <regex>
#include <string>
#include <tuple>
#include <type_traits>
#include <utility>
#include <vector>
#include <cassert>
#include <simgear/misc/strutils.hxx>
#include <simgear/nasal/cppbind/Ghost.hxx>
#include <simgear/nasal/cppbind/NasalCallContext.hxx>
#include <simgear/nasal/cppbind/NasalHash.hxx>
#include <simgear/sg_inlines.h>
#include <simgear/structure/exception.hxx>
#include "addon_fwd.hxx"
#include "AddonVersion.hxx"
using std::string;
using std::vector;
using simgear::enumValue;
namespace strutils = simgear::strutils;
namespace flightgear
{
namespace addons
{
// ***************************************************************************
// * AddonVersionSuffix *
// ***************************************************************************
AddonVersionSuffix::AddonVersionSuffix(
AddonVersionSuffixPrereleaseType preReleaseType, int preReleaseNum,
bool developmental, int devNum)
: _preReleaseType(preReleaseType),
_preReleaseNum(preReleaseNum),
_developmental(developmental),
_devNum(devNum)
{ }
// Construct an AddonVersionSuffix instance from a tuple (preReleaseType,
// preReleaseNum, developmental, devNum). This would be nicer with
// std::apply(), but it requires C++17.
AddonVersionSuffix::AddonVersionSuffix(
const std::tuple<AddonVersionSuffixPrereleaseType, int, bool, int>& t)
: AddonVersionSuffix(std::get<0>(t), std::get<1>(t), std::get<2>(t),
std::get<3>(t))
{ }
AddonVersionSuffix::AddonVersionSuffix(const std::string& suffix)
: AddonVersionSuffix(suffixStringToTuple(suffix))
{ }
AddonVersionSuffix::AddonVersionSuffix(const char* suffix)
: AddonVersionSuffix(string(suffix))
{ }
// Static method
string
AddonVersionSuffix::releaseTypeStr(AddonVersionSuffixPrereleaseType releaseType)
{
switch (releaseType) {
case AddonVersionSuffixPrereleaseType::alpha:
return string("a");
case AddonVersionSuffixPrereleaseType::beta:
return string("b");
case AddonVersionSuffixPrereleaseType::candidate:
return string("rc");
case AddonVersionSuffixPrereleaseType::none:
return string();
default:
throw sg_error("unexpected value for member of "
"flightgear::addons::AddonVersionSuffixPrereleaseType: " +
std::to_string(enumValue(releaseType)));
}
}
string
AddonVersionSuffix::str() const
{
string res = releaseTypeStr(_preReleaseType);
if (!res.empty()) {
res += std::to_string(_preReleaseNum);
}
if (_developmental) {
res += ".dev" + std::to_string(_devNum);
}
return res;
}
// Static method
std::tuple<AddonVersionSuffixPrereleaseType, int, bool, int>
AddonVersionSuffix::suffixStringToTuple(const std::string& suffix)
{
#ifdef HAVE_WORKING_STD_REGEX
// Use a simplified variant of the syntax described in PEP 440
// <https://www.python.org/dev/peps/pep-0440/>: for the version suffix, only
// allow a pre-release segment and a development release segment, but no
// post-release segment.
std::regex versionSuffixRegexp(R"((?:(a|b|rc)(\d+))?(?:\.dev(\d+))?)");
std::smatch results;
if (std::regex_match(suffix, results, versionSuffixRegexp)) {
const string preReleaseType_s = results.str(1);
const string preReleaseNum_s = results.str(2);
const string devNum_s = results.str(3);
AddonVersionSuffixPrereleaseType preReleaseType = AddonVersionSuffixPrereleaseType::none;
int preReleaseNum = 0;
int devNum = 0;
if (preReleaseType_s.empty()) {
preReleaseType = AddonVersionSuffixPrereleaseType::none;
} else {
if (preReleaseType_s == "a") {
preReleaseType = AddonVersionSuffixPrereleaseType::alpha;
} else if (preReleaseType_s == "b") {
preReleaseType = AddonVersionSuffixPrereleaseType::beta;
} else if (preReleaseType_s == "rc") {
preReleaseType = AddonVersionSuffixPrereleaseType::candidate;
} else {
assert(false); // the regexp should prevent this
}
assert(!preReleaseNum_s.empty());
preReleaseNum = strutils::readNonNegativeInt<int>(preReleaseNum_s);
if (preReleaseNum < 1) {
string msg = "invalid add-on version suffix: '" + suffix + "' "
"(prerelease number must be greater than or equal to 1, but got " +
preReleaseNum_s + ")";
throw sg_format_exception(msg, suffix);
}
}
if (!devNum_s.empty()) {
devNum = strutils::readNonNegativeInt<int>(devNum_s);
if (devNum < 1) {
string msg = "invalid add-on version suffix: '" + suffix + "' "
"(development release number must be greater than or equal to 1, "
"but got " + devNum_s + ")";
throw sg_format_exception(msg, suffix);
}
}
return std::make_tuple(preReleaseType, preReleaseNum, !devNum_s.empty(),
devNum);
#else // all this 'else' clause should be removed once we actually require C++11
bool isMatch;
AddonVersionSuffixPrereleaseType preReleaseType;
int preReleaseNum;
bool developmental;
int devNum;
std::tie(isMatch, preReleaseType, preReleaseNum, developmental, devNum) =
parseVersionSuffixString_noRegexp(suffix);
if (isMatch) {
return std::make_tuple(preReleaseType, preReleaseNum, developmental,
devNum);
#endif // HAVE_WORKING_STD_REGEX
} else { // the regexp didn't match
string msg = "invalid add-on version suffix: '" + suffix + "' "
"(expected form is [{a|b|rc}N1][.devN2] where N1 and N2 are positive "
"integers)";
throw sg_format_exception(msg, suffix);
}
}
// Static method, only needed for compilers that are not C++11-compliant
// (gcc 4.8 pretends to support <regex> as required by C++11 but doesn't, see
// <https://stackoverflow.com/a/12665408/4756009>).
std::tuple<bool, AddonVersionSuffixPrereleaseType, int, bool, int>
AddonVersionSuffix::parseVersionSuffixString_noRegexp(const string& suffix)
{
AddonVersionSuffixPrereleaseType preReleaseType;
string rest;
int preReleaseNum = 0; // alpha, beta or release candidate number, or
// 0 when absent
bool developmental = false; // whether 'suffix' has a .devN2 part
int devNum = 0; // the N2 in question, or 0 when absent
std::tie(preReleaseType, rest) = popPrereleaseTypeFromBeginning(suffix);
if (preReleaseType != AddonVersionSuffixPrereleaseType::none) {
std::size_t startPrerelNum = rest.find_first_of("0123456789");
if (startPrerelNum != 0) { // no prerelease num -> no match
return std::make_tuple(false, preReleaseType, preReleaseNum, false,
devNum);
}
std::size_t endPrerelNum = rest.find_first_not_of("0123456789", 1);
// Works whether endPrerelNum is string::npos or not
string preReleaseNum_s = rest.substr(0, endPrerelNum);
preReleaseNum = strutils::readNonNegativeInt<int>(preReleaseNum_s);
if (preReleaseNum < 1) {
string msg = "invalid add-on version suffix: '" + suffix + "' "
"(prerelease number must be greater than or equal to 1, but got " +
preReleaseNum_s + ")";
throw sg_format_exception(msg, suffix);
}
rest = (endPrerelNum == string::npos) ? "" : rest.substr(endPrerelNum);
}
if (strutils::starts_with(rest, ".dev")) {
rest = rest.substr(4);
std::size_t startDevNum = rest.find_first_of("0123456789");
if (startDevNum != 0) { // no dev num -> no match
return std::make_tuple(false, preReleaseType, preReleaseNum, false,
devNum);
}
std::size_t endDevNum = rest.find_first_not_of("0123456789", 1);
if (endDevNum != string::npos) {
// There is trailing garbage after the development release number
// -> no match
return std::make_tuple(false, preReleaseType, preReleaseNum, false,
devNum);
}
devNum = strutils::readNonNegativeInt<int>(rest);
if (devNum < 1) {
string msg = "invalid add-on version suffix: '" + suffix + "' "
"(development release number must be greater than or equal to 1, "
"but got " + rest + ")";
throw sg_format_exception(msg, suffix);
}
developmental = true;
}
return std::make_tuple(true, preReleaseType, preReleaseNum, developmental,
devNum);
}
// Static method
std::tuple<AddonVersionSuffixPrereleaseType, string>
AddonVersionSuffix::popPrereleaseTypeFromBeginning(const string& s)
{
if (s.empty()) {
return std::make_tuple(AddonVersionSuffixPrereleaseType::none, s);
} else if (s[0] == 'a') {
return std::make_tuple(AddonVersionSuffixPrereleaseType::alpha,
s.substr(1));
} else if (s[0] == 'b') {
return std::make_tuple(AddonVersionSuffixPrereleaseType::beta, s.substr(1));
} else if (strutils::starts_with(s, "rc")) {
return std::make_tuple(AddonVersionSuffixPrereleaseType::candidate,
s.substr(2));
}
return std::make_tuple(AddonVersionSuffixPrereleaseType::none, s);
}
// Beware, this is not suitable for sorting! cf. genSortKey() below.
std::tuple<AddonVersionSuffixPrereleaseType, int, bool, int>
AddonVersionSuffix::makeTuple() const
{
return std::make_tuple(_preReleaseType, _preReleaseNum, _developmental,
_devNum);
}
std::tuple<int,
std::underlying_type<AddonVersionSuffixPrereleaseType>::type,
int, int, int>
AddonVersionSuffix::genSortKey() const
{
using AddonRelType = AddonVersionSuffixPrereleaseType;
// The first element means that a plain .devN is lower than everything else,
// except .devM with M <= N (namely: all dev and non-dev alpha, beta,
// candidates, as well as the empty suffix).
return std::make_tuple(
((_developmental && _preReleaseType == AddonRelType::none) ? 0 : 1),
enumValue(_preReleaseType),
_preReleaseNum,
(_developmental ? 0 : 1), // e.g., 1.0.3a2.devN < 1.0.3a2 for all N
_devNum);
}
bool operator==(const AddonVersionSuffix& lhs, const AddonVersionSuffix& rhs)
{ return lhs.genSortKey() == rhs.genSortKey(); }
bool operator!=(const AddonVersionSuffix& lhs, const AddonVersionSuffix& rhs)
{ return !operator==(lhs, rhs); }
bool operator< (const AddonVersionSuffix& lhs, const AddonVersionSuffix& rhs)
{ return lhs.genSortKey() < rhs.genSortKey(); }
bool operator> (const AddonVersionSuffix& lhs, const AddonVersionSuffix& rhs)
{ return operator<(rhs, lhs); }
bool operator<=(const AddonVersionSuffix& lhs, const AddonVersionSuffix& rhs)
{ return !operator>(lhs, rhs); }
bool operator>=(const AddonVersionSuffix& lhs, const AddonVersionSuffix& rhs)
{ return !operator<(lhs, rhs); }
std::ostream& operator<<(std::ostream& os,
const AddonVersionSuffix& addonVersionSuffix)
{ return os << addonVersionSuffix.str(); }
// ***************************************************************************
// * AddonVersion *
// ***************************************************************************
AddonVersion::AddonVersion(int major, int minor, int patchLevel,
AddonVersionSuffix suffix)
: _major(major),
_minor(minor),
_patchLevel(patchLevel),
_suffix(std::move(suffix))
{ }
// Construct an AddonVersion instance from a tuple (major, minor, patchLevel,
// suffix). This would be nicer with std::apply(), but it requires C++17.
AddonVersion::AddonVersion(
const std::tuple<int, int, int, AddonVersionSuffix>& t)
: AddonVersion(std::get<0>(t), std::get<1>(t), std::get<2>(t), std::get<3>(t))
{ }
AddonVersion::AddonVersion(const std::string& versionStr)
: AddonVersion(versionStringToTuple(versionStr))
{ }
AddonVersion::AddonVersion(const char* versionStr)
: AddonVersion(string(versionStr))
{ }
// Static method
std::tuple<int, int, int, AddonVersionSuffix>
AddonVersion::versionStringToTuple(const std::string& versionStr)
{
#ifdef HAVE_WORKING_STD_REGEX
// Use a simplified variant of the syntax described in PEP 440
// <https://www.python.org/dev/peps/pep-0440/> (always 3 components in the
// release segment, pre-release segment + development release segment; no
// post-release segment allowed).
std::regex versionRegexp(R"((\d+)\.(\d+).(\d+)(.*))");
std::smatch results;
if (std::regex_match(versionStr, results, versionRegexp)) {
const string majorNumber_s = results.str(1);
const string minorNumber_s = results.str(2);
const string patchLevel_s = results.str(3);
const string suffix_s = results.str(4);
int major = strutils::readNonNegativeInt<int>(majorNumber_s);
int minor = strutils::readNonNegativeInt<int>(minorNumber_s);
int patchLevel = strutils::readNonNegativeInt<int>(patchLevel_s);
return std::make_tuple(major, minor, patchLevel,
AddonVersionSuffix(suffix_s));
#else // all this 'else' clause should be removed once we actually require C++11
bool isMatch;
int major, minor, patchLevel;
AddonVersionSuffix suffix;
std::tie(isMatch, major, minor, patchLevel, suffix) =
parseVersionString_noRegexp(versionStr);
if (isMatch) {
return std::make_tuple(major, minor, patchLevel, suffix);
#endif // HAVE_WORKING_STD_REGEX
} else { // the regexp didn't match
string msg = "invalid add-on version number: '" + versionStr + "' "
"(expected form is MAJOR.MINOR.PATCHLEVEL[{a|b|rc}N1][.devN2] where "
"N1 and N2 are positive integers)";
throw sg_format_exception(msg, versionStr);
}
}
// Static method, only needed for compilers that are not C++11-compliant
// (gcc 4.8 pretends to support <regex> as required by C++11 but doesn't, see
// <https://stackoverflow.com/a/12665408/4756009>).
std::tuple<bool, int, int, int, AddonVersionSuffix>
AddonVersion::parseVersionString_noRegexp(const string& versionStr)
{
int major = 0, minor = 0, patchLevel = 0;
AddonVersionSuffix suffix{};
// Major version number
std::size_t endMajor = versionStr.find_first_not_of("0123456789");
if (endMajor == 0 || endMajor == string::npos) { // no match
return std::make_tuple(false, major, minor, patchLevel, suffix);
}
major = strutils::readNonNegativeInt<int>(versionStr.substr(0, endMajor));
// Dot separating the major and minor version numbers
if (versionStr.size() < endMajor + 1 || versionStr[endMajor] != '.') {
return std::make_tuple(false, major, minor, patchLevel, suffix);
}
string rest = versionStr.substr(endMajor + 1);
// Minor version number
std::size_t endMinor = rest.find_first_not_of("0123456789");
if (endMinor == 0 || endMinor == string::npos) { // no match
return std::make_tuple(false, major, minor, patchLevel, suffix);
}
minor = strutils::readNonNegativeInt<int>(rest.substr(0, endMinor));
// Dot separating the minor version number and the patch level
if (rest.size() < endMinor + 1 || rest[endMinor] != '.') {
return std::make_tuple(false, major, minor, patchLevel, suffix);
}
rest = rest.substr(endMinor + 1);
// Patch level
std::size_t endPatchLevel = rest.find_first_not_of("0123456789");
if (endPatchLevel == 0) { // no patch level, therefore no match
return std::make_tuple(false, major, minor, patchLevel, suffix);
}
patchLevel = strutils::readNonNegativeInt<int>(rest.substr(0, endPatchLevel));
if (endPatchLevel != string::npos) { // there is a version suffix, parse it
suffix = AddonVersionSuffix(rest.substr(endPatchLevel));
}
return std::make_tuple(true, major, minor, patchLevel, suffix);
}
int AddonVersion::majorNumber() const
{ return _major; }
int AddonVersion::minorNumber() const
{ return _minor; }
int AddonVersion::patchLevel() const
{ return _patchLevel; }
AddonVersionSuffix AddonVersion::suffix() const
{ return _suffix; }
std::string AddonVersion::suffixStr() const
{ return suffix().str(); }
std::tuple<int, int, int, AddonVersionSuffix> AddonVersion::makeTuple() const
{
return std::make_tuple(majorNumber(), minorNumber(), patchLevel(), suffix());
}
string AddonVersion::str() const
{
// Assemble the major.minor.patchLevel string
vector<int> v({majorNumber(), minorNumber(), patchLevel()});
string relSeg = std::accumulate(std::next(v.begin()), v.end(),
std::to_string(v[0]),
[](string s, int num) {
return s + '.' + std::to_string(num);
});
// Concatenate with the suffix string
return relSeg + suffixStr();
}
bool operator==(const AddonVersion& lhs, const AddonVersion& rhs)
{ return lhs.makeTuple() == rhs.makeTuple(); }
bool operator!=(const AddonVersion& lhs, const AddonVersion& rhs)
{ return !operator==(lhs, rhs); }
bool operator< (const AddonVersion& lhs, const AddonVersion& rhs)
{ return lhs.makeTuple() < rhs.makeTuple(); }
bool operator> (const AddonVersion& lhs, const AddonVersion& rhs)
{ return operator<(rhs, lhs); }
bool operator<=(const AddonVersion& lhs, const AddonVersion& rhs)
{ return !operator>(lhs, rhs); }
bool operator>=(const AddonVersion& lhs, const AddonVersion& rhs)
{ return !operator<(lhs, rhs); }
std::ostream& operator<<(std::ostream& os, const AddonVersion& addonVersion)
{ return os << addonVersion.str(); }
// ***************************************************************************
// * For the Nasal bindings *
// ***************************************************************************
bool AddonVersion::equal(const nasal::CallContext& ctx) const
{
auto other = ctx.requireArg<AddonVersionRef>(0);
return *this == *other;
}
bool AddonVersion::nonEqual(const nasal::CallContext& ctx) const
{
auto other = ctx.requireArg<AddonVersionRef>(0);
return *this != *other;
}
bool AddonVersion::lowerThan(const nasal::CallContext& ctx) const
{
auto other = ctx.requireArg<AddonVersionRef>(0);
return *this < *other;
}
bool AddonVersion::lowerThanOrEqual(const nasal::CallContext& ctx) const
{
auto other = ctx.requireArg<AddonVersionRef>(0);
return *this <= *other;
}
bool AddonVersion::greaterThan(const nasal::CallContext& ctx) const
{
auto other = ctx.requireArg<AddonVersionRef>(0);
return *this > *other;
}
bool AddonVersion::greaterThanOrEqual(const nasal::CallContext& ctx) const
{
auto other = ctx.requireArg<AddonVersionRef>(0);
return *this >= *other;
}
// Static method
void AddonVersion::setupGhost(nasal::Hash& addonsModule)
{
nasal::Ghost<AddonVersionRef>::init("addons.AddonVersion")
.member("majorNumber", &AddonVersion::majorNumber)
.member("minorNumber", &AddonVersion::minorNumber)
.member("patchLevel", &AddonVersion::patchLevel)
.member("suffix", &AddonVersion::suffixStr)
.method("str", &AddonVersion::str)
.method("equal", &AddonVersion::equal)
.method("nonEqual", &AddonVersion::nonEqual)
.method("lowerThan", &AddonVersion::lowerThan)
.method("lowerThanOrEqual", &AddonVersion::lowerThanOrEqual)
.method("greaterThan", &AddonVersion::greaterThan)
.method("greaterThanOrEqual", &AddonVersion::greaterThanOrEqual);
}
} // of namespace addons
} // of namespace flightgear

View File

@@ -0,0 +1,212 @@
// -*- coding: utf-8 -*-
//
// AddonVersion.hxx --- Version class for FlightGear add-ons
// Copyright (C) 2017 Florent Rougon
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#ifndef FG_ADDONVERSION_HXX
#define FG_ADDONVERSION_HXX
#include <ostream>
#include <string>
#include <tuple>
#include <type_traits>
#include <simgear/nasal/cppbind/NasalCallContext.hxx>
#include <simgear/nasal/cppbind/NasalHash.hxx>
#include <simgear/structure/SGReferenced.hxx>
#include "addon_fwd.hxx"
namespace flightgear
{
namespace addons
{
// Order matters for the sorting/comparison functions
enum class AddonVersionSuffixPrereleaseType {
alpha = 0,
beta,
candidate,
none
};
// ***************************************************************************
// * AddonVersionSuffix *
// ***************************************************************************
class AddonVersionSuffix
{
public:
AddonVersionSuffix(AddonVersionSuffixPrereleaseType _preReleaseType
= AddonVersionSuffixPrereleaseType::none,
int preReleaseNum = 0, bool developmental = false,
int devNum = 0);
// Construct from a string. The empty string is a valid input.
AddonVersionSuffix(const std::string& suffix);
AddonVersionSuffix(const char* suffix);
// Construct from a tuple
explicit AddonVersionSuffix(
const std::tuple<AddonVersionSuffixPrereleaseType, int, bool, int>& t);
// Return all components of an AddonVersionSuffix instance as a tuple.
// Beware, this is not suitable for sorting! cf. genSortKey() below.
std::tuple<AddonVersionSuffixPrereleaseType, int, bool, int> makeTuple() const;
// String representation of an AddonVersionSuffix
std::string str() const;
private:
// String representation of the release type component: "a", "b", "rc" or "".
static std::string releaseTypeStr(AddonVersionSuffixPrereleaseType);
// If 's' starts with a non-empty release type ('a', 'b' or 'rc'), return
// the corresponding enum value along with the remainder of 's' (that is,
// everything after the release type). Otherwise, return
// AddonVersionSuffixPrereleaseType::none along with a copy of 's'.
static std::tuple<AddonVersionSuffixPrereleaseType, std::string>
popPrereleaseTypeFromBeginning(const std::string& s);
// Extract all components from a string representing a version suffix.
// The components of the return value are, in this order:
//
// preReleaseType, preReleaseNum, developmental, devNum
//
// Note: the empty string is a valid input.
static std::tuple<AddonVersionSuffixPrereleaseType, int, bool, int>
suffixStringToTuple(const std::string& suffix);
// Used to implement suffixStringToTuple() for compilers that are not
// C++11-compliant (gcc 4.8 pretends to support <regex> as required by C++11
// but doesn't, see <https://stackoverflow.com/a/12665408/4756009>).
//
// The bool in the first component of the result is true iff 'suffix' is a
// valid version suffix string. The bool is false when 'suffix' is invalid
// in such a way that the generic sg_format_exception thrown at the end of
// suffixStringToTuple() is appropriate. In all other cases, a specific
// exception is thrown.
static std::tuple<bool, AddonVersionSuffixPrereleaseType, int, bool, int>
parseVersionSuffixString_noRegexp(const std::string& suffix);
// Useful for comparisons/sorting purposes
std::tuple<int,
std::underlying_type<AddonVersionSuffixPrereleaseType>::type,
int, int, int> genSortKey() const;
friend bool operator==(const AddonVersionSuffix& lhs,
const AddonVersionSuffix& rhs);
friend bool operator<(const AddonVersionSuffix& lhs,
const AddonVersionSuffix& rhs);
AddonVersionSuffixPrereleaseType _preReleaseType;
int _preReleaseNum; // integer >= 1 (0 when not applicable)
bool _developmental; // whether the suffix ends with '.devN'
int _devNum; // integer >= 1 (0 when not applicable)
};
// operator==() and operator<() are declared above.
bool operator!=(const AddonVersionSuffix& lhs, const AddonVersionSuffix& rhs);
bool operator> (const AddonVersionSuffix& lhs, const AddonVersionSuffix& rhs);
bool operator<=(const AddonVersionSuffix& lhs, const AddonVersionSuffix& rhs);
bool operator>=(const AddonVersionSuffix& lhs, const AddonVersionSuffix& rhs);
std::ostream& operator<<(std::ostream&, const AddonVersionSuffix&);
// ***************************************************************************
// * AddonVersion *
// ***************************************************************************
// I suggest to use either the year-based FlightGear-type versioning, or
// semantic versioning (<http://semver.org/>). For the suffix, we allow things
// like "a1" (alpha1), "b2" (beta2), "rc4" (release candidate 4), "a1.dev3"
// (development release for "a1", which sorts before "a1"), etc. It's a subset
// of the syntax allowed in <https://www.python.org/dev/peps/pep-0440/>.
class AddonVersion : public SGReferenced
{
public:
AddonVersion(int major = 0, int minor = 0, int patchLevel = 0,
AddonVersionSuffix suffix = AddonVersionSuffix());
AddonVersion(const std::string& version);
AddonVersion(const char* version);
explicit AddonVersion(const std::tuple<int, int, int, AddonVersionSuffix>& t);
// Using the method names major() and minor() can lead to incomprehensible
// errors such as "major is not a member of flightgear::addons::AddonVersion"
// because of a hideous glibc bug[1]: major() and minor() are defined by
// standard headers as *macros*!
//
// [1] https://bugzilla.redhat.com/show_bug.cgi?id=130601
int majorNumber() const;
int minorNumber() const;
int patchLevel() const;
AddonVersionSuffix suffix() const;
std::string suffixStr() const;
std::string str() const;
// For the Nasal bindings (otherwise, we have operator==(), etc.)
bool equal(const nasal::CallContext& ctx) const;
bool nonEqual(const nasal::CallContext& ctx) const;
bool lowerThan(const nasal::CallContext& ctx) const;
bool lowerThanOrEqual(const nasal::CallContext& ctx) const;
bool greaterThan(const nasal::CallContext& ctx) const;
bool greaterThanOrEqual(const nasal::CallContext& ctx) const;
static void setupGhost(nasal::Hash& addonsModule);
private:
// Useful for comparisons/sorting purposes
std::tuple<int, int, int, AddonVersionSuffix> makeTuple() const;
static std::tuple<int, int, int, AddonVersionSuffix>
versionStringToTuple(const std::string& versionStr);
// Used to implement versionStringToTuple() for compilers that are not
// C++11-compliant (gcc 4.8 pretends to support <regex> as required by C++11
// but doesn't, see <https://stackoverflow.com/a/12665408/4756009>).
//
// The bool in the first component of the result is true iff 'versionStr' is
// a valid version string. The bool is false when 'versionStr' is invalid in
// such a way that the generic sg_format_exception thrown at the end of
// versionStringToTuple() is appropriate. In all other cases, a specific
// exception is thrown.
static std::tuple<bool, int, int, int, AddonVersionSuffix>
parseVersionString_noRegexp(const std::string& versionStr);
friend bool operator==(const AddonVersion& lhs, const AddonVersion& rhs);
friend bool operator<(const AddonVersion& lhs, const AddonVersion& rhs);
int _major;
int _minor;
int _patchLevel;
AddonVersionSuffix _suffix;
};
// operator==() and operator<() are declared above.
bool operator!=(const AddonVersion& lhs, const AddonVersion& rhs);
bool operator> (const AddonVersion& lhs, const AddonVersion& rhs);
bool operator<=(const AddonVersion& lhs, const AddonVersion& rhs);
bool operator>=(const AddonVersion& lhs, const AddonVersion& rhs);
std::ostream& operator<<(std::ostream&, const AddonVersion&);
} // of namespace addons
} // of namespace flightgear
#endif // of FG_ADDONVERSION_HXX

View File

@@ -0,0 +1,23 @@
include(FlightGearComponent)
set(SOURCES Addon.cxx
AddonManager.cxx
AddonMetadataParser.cxx
AddonResourceProvider.cxx
AddonVersion.cxx
contacts.cxx
exceptions.cxx
)
set(HEADERS addon_fwd.hxx
Addon.hxx
AddonManager.hxx
AddonMetadataParser.hxx
AddonResourceProvider.hxx
AddonVersion.hxx
contacts.hxx
exceptions.hxx
pointer_traits.hxx
)
flightgear_component(AddonManagement "${SOURCES}" "${HEADERS}")

72
src/Add-ons/addon_fwd.hxx Normal file
View File

@@ -0,0 +1,72 @@
// -*- coding: utf-8 -*-
//
// addon_fwd.hxx --- Forward declarations for the FlightGear add-on
// infrastructure
// Copyright (C) 2017 Florent Rougon
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#ifndef FG_ADDON_FWD_HXX
#define FG_ADDON_FWD_HXX
#include <simgear/structure/SGSharedPtr.hxx>
namespace flightgear
{
namespace addons
{
class Addon;
class AddonManager;
class AddonVersion;
class AddonVersionSuffix;
class ResourceProvider;
enum class UrlType;
class QualifiedUrl;
enum class ContactType;
class Contact;
class Author;
class Maintainer;
using AddonRef = SGSharedPtr<Addon>;
using AddonVersionRef = SGSharedPtr<AddonVersion>;
using ContactRef = SGSharedPtr<Contact>;
using AuthorRef = SGSharedPtr<Author>;
using MaintainerRef = SGSharedPtr<Maintainer>;
namespace errors
{
class error;
class error_loading_config_file;
class no_metadata_file_found;
class error_loading_metadata_file;
class error_loading_menubar_items_file;
class duplicate_registration_attempt;
class fg_version_too_old;
class fg_version_too_recent;
class invalid_resource_path;
class unable_to_create_addon_storage_dir;
} // of namespace errors
} // of namespace addons
} // of namespace flightgear
#endif // of FG_ADDON_FWD_HXX

127
src/Add-ons/contacts.cxx Normal file
View File

@@ -0,0 +1,127 @@
// -*- coding: utf-8 -*-
//
// contacts.cxx --- FlightGear classes holding add-on contact metadata
// Copyright (C) 2018 Florent Rougon
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#include <string>
#include <utility>
#include <simgear/nasal/cppbind/Ghost.hxx>
#include <simgear/nasal/cppbind/NasalHash.hxx>
#include <simgear/sg_inlines.h>
#include <simgear/structure/exception.hxx>
#include "addon_fwd.hxx"
#include "contacts.hxx"
using std::string;
using simgear::enumValue;
namespace flightgear
{
namespace addons
{
// ***************************************************************************
// * Contact *
// ***************************************************************************
Contact::Contact(ContactType type, string name, string email, string url)
: _type(type),
_name(std::move(name)),
_email(std::move(email)),
_url(std::move(url))
{ }
ContactType Contact::getType() const
{ return _type; }
string Contact::getTypeString() const
{
switch (getType()) {
case ContactType::author:
return "author";
case ContactType::maintainer:
return "maintainer";
default:
throw sg_error("unexpected value for member of "
"flightgear::addons::ContactType: " +
std::to_string(enumValue(getType())));
}
}
string Contact::getName() const
{ return _name; }
void Contact::setName(const string& name)
{ _name = name; }
string Contact::getEmail() const
{ return _email; }
void Contact::setEmail(const string& email)
{ _email = email; }
string Contact::getUrl() const
{ return _url; }
void Contact::setUrl(const string& url)
{ _url = url; }
// Static method
void Contact::setupGhost(nasal::Hash& addonsModule)
{
nasal::Ghost<ContactRef>::init("addons.Contact")
.member("name", &Contact::getName)
.member("email", &Contact::getEmail)
.member("url", &Contact::getUrl);
}
// ***************************************************************************
// * Author *
// ***************************************************************************
Author::Author(string name, string email, string url)
: Contact(ContactType::author, name, email, url)
{ }
// Static method
void Author::setupGhost(nasal::Hash& addonsModule)
{
nasal::Ghost<AuthorRef>::init("addons.Author")
.bases<ContactRef>();
}
// ***************************************************************************
// * Maintainer *
// ***************************************************************************
Maintainer::Maintainer(string name, string email, string url)
: Contact(ContactType::maintainer, name, email, url)
{ }
// Static method
void Maintainer::setupGhost(nasal::Hash& addonsModule)
{
nasal::Ghost<MaintainerRef>::init("addons.Maintainer")
.bases<ContactRef>();
}
} // of namespace addons
} // of namespace flightgear

126
src/Add-ons/contacts.hxx Normal file
View File

@@ -0,0 +1,126 @@
// -*- coding: utf-8 -*-
//
// contacts.hxx --- FlightGear classes holding add-on contact metadata
// Copyright (C) 2018 Florent Rougon
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#ifndef FG_ADDON_CONTACTS_HXX
#define FG_ADDON_CONTACTS_HXX
#include <string>
#include <simgear/structure/SGReferenced.hxx>
#include "addon_fwd.hxx"
namespace nasal
{
class Hash; // forward declaration
};
namespace flightgear
{
namespace addons
{
enum class ContactType {
author,
maintainer
};
// Class used to store info about an author or maintainer (possibly also a
// mailing-list, things like that)
class Contact : public SGReferenced
{
public:
Contact(ContactType type, std::string name, std::string email = "",
std::string url = "");
virtual ~Contact() = default;
ContactType getType() const;
std::string getTypeString() const;
std::string getName() const;
void setName(const std::string& name);
std::string getEmail() const;
void setEmail(const std::string& email);
std::string getUrl() const;
void setUrl(const std::string& url);
static void setupGhost(nasal::Hash& addonsModule);
private:
const ContactType _type;
std::string _name;
std::string _email;
std::string _url;
};
class Author : public Contact
{
public:
Author(std::string name, std::string email = "", std::string url = "");
static void setupGhost(nasal::Hash& addonsModule);
};
class Maintainer : public Contact
{
public:
Maintainer(std::string name, std::string email = "", std::string url = "");
static void setupGhost(nasal::Hash& addonsModule);
};
// ***************************************************************************
// * contact_traits *
// ***************************************************************************
template <typename T>
struct contact_traits;
template<>
struct contact_traits<Author>
{
using contact_type = Author;
using strong_ref = AuthorRef;
static std::string xmlNodeName()
{
return "author";
}
};
template<>
struct contact_traits<Maintainer>
{
using contact_type = Maintainer;
using strong_ref = MaintainerRef;
static std::string xmlNodeName()
{
return "maintainer";
}
};
} // of namespace addons
} // of namespace flightgear
#endif // of FG_ADDON_CONTACTS_HXX

View File

@@ -0,0 +1,55 @@
// -*- coding: utf-8 -*-
//
// exceptions.cxx --- Exception classes for the FlightGear add-on infrastructure
// Copyright (C) 2017 Florent Rougon
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#include <string>
#include <simgear/structure/exception.hxx>
#include "exceptions.hxx"
using std::string;
namespace flightgear
{
namespace addons
{
namespace errors
{
// ***************************************************************************
// * Base class for add-on exceptions *
// ***************************************************************************
// Prepending a prefix such as "Add-on error: " would be redundant given the
// messages used in, e.g., the Addon class code.
error::error(const string& message, const string& origin)
: sg_exception(message, origin)
{ }
error::error(const char* message, const char* origin)
: error(string(message), string(origin))
{ }
} // of namespace errors
} // of namespace addons
} // of namespace flightgear

View File

@@ -0,0 +1,77 @@
// -*- coding: utf-8 -*-
//
// exceptions.hxx --- Exception classes for the FlightGear add-on infrastructure
// Copyright (C) 2017 Florent Rougon
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#ifndef FG_ADDON_EXCEPTIONS_HXX
#define FG_ADDON_EXCEPTIONS_HXX
#include <string>
#include <simgear/structure/exception.hxx>
namespace flightgear
{
namespace addons
{
namespace errors
{
class error : public sg_exception
{
public:
explicit error(const std::string& message,
const std::string& origin = std::string());
explicit error(const char* message, const char* origin = nullptr);
};
class error_loading_config_file : public error
{ using error::error; /* inherit all constructors */ };
class no_metadata_file_found : public error
{ using error::error; };
class error_loading_metadata_file : public error
{ using error::error; };
class error_loading_menubar_items_file : public error
{ using error::error; };
class duplicate_registration_attempt : public error
{ using error::error; };
class fg_version_too_old : public error
{ using error::error; };
class fg_version_too_recent : public error
{ using error::error; };
class invalid_resource_path : public error
{ using error::error; };
class unable_to_create_addon_storage_dir : public error
{ using error::error; };
} // of namespace errors
} // of namespace addons
} // of namespace flightgear
#endif // of FG_ADDON_EXCEPTIONS_HXX

View File

@@ -0,0 +1,67 @@
// -*- coding: utf-8 -*-
//
// pointer_traits.hxx --- Pointer traits classes
// Copyright (C) 2018 Florent Rougon
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#ifndef FG_ADDON_POINTER_TRAITS_HXX
#define FG_ADDON_POINTER_TRAITS_HXX
#include <memory>
#include <utility>
#include <simgear/structure/SGSharedPtr.hxx>
namespace flightgear
{
namespace addons
{
template <typename T>
struct shared_ptr_traits;
template <typename T>
struct shared_ptr_traits<SGSharedPtr<T>>
{
using element_type = T;
using strong_ref = SGSharedPtr<T>;
template <typename ...Args>
static strong_ref makeStrongRef(Args&& ...args)
{
return strong_ref(new T(std::forward<Args>(args)...));
}
};
template <typename T>
struct shared_ptr_traits<std::shared_ptr<T>>
{
using element_type = T;
using strong_ref = std::shared_ptr<T>;
template <typename ...Args>
static strong_ref makeStrongRef(Args&& ...args)
{
return std::make_shared<T>(std::forward<Args>(args)...);
}
};
} // of namespace addons
} // of namespace flightgear
#endif // of FG_ADDON_POINTER_TRAITS_HXX