first commit
This commit is contained in:
516
src/Add-ons/Addon.cxx
Normal file
516
src/Add-ons/Addon.cxx
Normal 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
267
src/Add-ons/Addon.hxx
Normal 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
|
||||
296
src/Add-ons/AddonManager.cxx
Normal file
296
src/Add-ons/AddonManager.cxx
Normal 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
|
||||
114
src/Add-ons/AddonManager.hxx
Normal file
114
src/Add-ons/AddonManager.hxx
Normal 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
|
||||
416
src/Add-ons/AddonMetadataParser.cxx
Normal file
416
src/Add-ons/AddonMetadataParser.cxx
Normal 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
|
||||
97
src/Add-ons/AddonMetadataParser.hxx
Normal file
97
src/Add-ons/AddonMetadataParser.hxx
Normal 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
|
||||
78
src/Add-ons/AddonResourceProvider.cxx
Normal file
78
src/Add-ons/AddonResourceProvider.cxx
Normal 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
|
||||
47
src/Add-ons/AddonResourceProvider.hxx
Normal file
47
src/Add-ons/AddonResourceProvider.hxx
Normal 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
|
||||
569
src/Add-ons/AddonVersion.cxx
Normal file
569
src/Add-ons/AddonVersion.cxx
Normal 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
|
||||
212
src/Add-ons/AddonVersion.hxx
Normal file
212
src/Add-ons/AddonVersion.hxx
Normal 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
|
||||
23
src/Add-ons/CMakeLists.txt
Normal file
23
src/Add-ons/CMakeLists.txt
Normal 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
72
src/Add-ons/addon_fwd.hxx
Normal 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
127
src/Add-ons/contacts.cxx
Normal 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
126
src/Add-ons/contacts.hxx
Normal 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
|
||||
55
src/Add-ons/exceptions.cxx
Normal file
55
src/Add-ons/exceptions.cxx
Normal 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
|
||||
77
src/Add-ons/exceptions.hxx
Normal file
77
src/Add-ons/exceptions.hxx
Normal 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
|
||||
67
src/Add-ons/pointer_traits.hxx
Normal file
67
src/Add-ons/pointer_traits.hxx
Normal 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
|
||||
Reference in New Issue
Block a user