1
0
Fork 0
flightgear/src/Add-ons/Addon.cxx
Florent Rougon 109a59b393 Add-ons: const id; adjust the Addon constructors, remove Addon::setId()
Since the new _storagePath data member internally contains the add-on
id, changing _id after _storagePath has been initialized would make both
data members inconsistent. As changing the add-on id is probably not a
very useful operation, the simplest way to prevent such an inconsistency
from happening is to make Addon's _id data member const (as is already
the case for _storagePath), and thus remove Addon::setId().
Consequently, remove the Addon default constructor too, since add-ons
with an empty id would be ill-formed and couldn't be changed (_id being
const now). This leaves us with one Addon constructor:

Addon(std::string id, AddonVersion version = AddonVersion(),
      SGPath basePath = SGPath(), std::string minFGVersionRequired = "",
      std::string maxFGVersionRequired = "",
      SGPropertyNode* addonNode = nullptr);
2018-03-11 23:17:23 +01:00

509 lines
16 KiB
C++

// -*- 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 <Main/util.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 {
SGPath authorizedPath = fgValidatePath(_storagePath, true /* write */);
if (authorizedPath.isNull()) {
string msg =
"Unable to create add-on storage directory because of the FlightGear "
"security policy (refused by fgValidatePath()): '" +
_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 fgValidatePath() 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;
}
// 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