FlightGear next
AddonManager.cxx
Go to the documentation of this file.
1// -*- coding: utf-8 -*-
2//
3// AddonManager.cxx --- Manager class for FlightGear add-ons
4// Copyright (C) 2017 Florent Rougon
5//
6// This program is free software; you can redistribute it and/or modify
7// it under the terms of the GNU General Public License as published by
8// the Free Software Foundation; either version 2 of the License, or
9// (at your option) any later version.
10//
11// This program is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU General Public License for more details.
15//
16// You should have received a copy of the GNU General Public License along
17// with this program; if not, write to the Free Software Foundation, Inc.,
18// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19
20#include "config.h"
21
22#include <algorithm>
23#include <memory>
24#include <string>
25#include <utility>
26#include <vector>
27
28#include <cstdlib>
29#include <cassert>
30
31#include <simgear/debug/logstream.hxx>
32#include <simgear/misc/sg_path.hxx>
33#include <simgear/misc/strutils.hxx>
34#include <simgear/props/props.hxx>
35#include <simgear/props/props_io.hxx>
36#include <simgear/structure/exception.hxx>
37
38#include <Main/fg_props.hxx>
39#include <Main/globals.hxx>
40
41#include "addon_fwd.hxx"
42#include "Addon.hxx"
43#include "AddonManager.hxx"
44#include "AddonVersion.hxx"
45#include "exceptions.hxx"
46#include "pointer_traits.hxx"
47
48namespace strutils = simgear::strutils;
49
50using std::string;
51using std::vector;
52using std::shared_ptr;
53using std::unique_ptr;
54
55namespace flightgear
56{
57
58namespace addons
59{
60
61static unique_ptr<AddonManager> staticInstance;
62
63// ***************************************************************************
64// * AddonManager *
65// ***************************************************************************
66
67// Static method
68const unique_ptr<AddonManager>&
70{
71 SG_LOG(SG_GENERAL, SG_DEBUG, "Initializing the AddonManager");
72
73 staticInstance.reset(new AddonManager());
74 return staticInstance;
75}
76
77// Static method
78const unique_ptr<AddonManager>&
83
84// Static method
85void
87{
88 SG_LOG(SG_GENERAL, SG_DEBUG, "Resetting the AddonManager");
89 staticInstance.reset();
90}
91
92// Static method
93void
94AddonManager::loadConfigFileIfExists(const SGPath& configFile)
95{
96 if (!configFile.exists()) {
97 return;
98 }
99
100 SGPropertyNode_ptr configProps(new SGPropertyNode);
101 try {
102 readProperties(configFile, configProps);
103 } catch (const sg_exception &e) {
104 throw errors::error_loading_config_file(
105 "unable to load add-on config file '" + configFile.utf8Str() + "': " +
106 e.getFormattedMessage());
107 }
108
109 // bug https://sourceforge.net/p/flightgear/codetickets/2059/
110 // since we're loading this after autosave.xml is loaded, the defaults
111 // always take precedence. To fix this, only copy a value from the
112 // addon-config if it's not makred as ARCHIVE. (We assume ARCHIVE props
113 // came from autosave.xml)
114 copyPropertiesIf(configProps, globals->get_props(), [](const SGPropertyNode* src) {
115 if (src->nChildren() > 0)
116 return true;
117
118 // find the correspnding destination node
119 auto dstNode = globals->get_props()->getNode(src->getPath());
120 if (!dstNode)
121 return true; // easy, just copy it
122
123 // copy if it's NOT marked archive. In other words, we can replace
124 // values from defaults, but not autosave
125 return dstNode->getAttribute(SGPropertyNode::USERARCHIVE) == false;
126 });
127
128 SG_LOG(SG_GENERAL, SG_INFO,
129 "Loaded add-on config file: '" << configFile.utf8Str() + "'");
130}
131
132string
133AddonManager::registerAddonMetadata(const SGPath& addonPath)
134{
135 using ptr_traits = shared_ptr_traits<AddonRef>;
136 AddonRef addon = ptr_traits::makeStrongRef(Addon::fromAddonDir(addonPath));
137 string addonId = addon->getId();
138
139 SGPropertyNode* addonPropNode = fgGetNode("addons", true)
140 ->getChild("by-id", 0, 1)
141 ->getChild(addonId, 0, 1);
142 addon->setAddonNode(addonPropNode);
143 addon->setLoadSequenceNumber(_loadSequenceNumber++);
144
145 // Check that the FlightGear version satisfies the add-on requirements
146 std::string minFGversion = addon->getMinFGVersionRequired();
147 if (strutils::compare_versions(FLIGHTGEAR_VERSION, minFGversion) < 0) {
148 throw errors::fg_version_too_old(
149 "add-on '" + addonId + "' requires FlightGear " + minFGversion +
150 " or later, however this is FlightGear " + FLIGHTGEAR_VERSION);
151 }
152
153 std::string maxFGversion = addon->getMaxFGVersionRequired();
154 if (maxFGversion != "none" &&
155 strutils::compare_versions(FLIGHTGEAR_VERSION, maxFGversion) > 0) {
156 throw errors::fg_version_too_recent(
157 "add-on '" + addonId + "' requires FlightGear " + maxFGversion +
158 " or earlier, however this is FlightGear " + FLIGHTGEAR_VERSION);
159 }
160
161 // Store the add-on metadata in _idToAddonMap
162 auto emplaceRetval = _idToAddonMap.emplace(addonId, std::move(addon));
163
164 // Prevent registration of two add-ons with the same id
165 if (!emplaceRetval.second) {
166 auto existingElt = _idToAddonMap.find(addonId);
167 assert(existingElt != _idToAddonMap.end());
168 throw errors::duplicate_registration_attempt(
169 "attempt to register add-on '" + addonId + "' with base path '"
170 + addonPath.utf8Str() + "', however it is already registered with base "
171 "path '" + existingElt->second->getBasePath().utf8Str() + "'");
172 }
173
174 return addonId;
175}
176
177string
178AddonManager::registerAddon(const SGPath& addonPath)
179{
180 // Use realpath() as in FGGlobals::append_aircraft_path(), otherwise
181 // SGPath::validate() will deny access to resources under the add-on path
182 // if one of its components is a symlink.
183 const SGPath addonRealPath = addonPath.realpath();
184 const string addonId = registerAddonMetadata(addonRealPath);
185 loadConfigFileIfExists(addonRealPath / "addon-config.xml");
186 globals->append_aircraft_path(addonRealPath);
187
188 AddonRef addon{getAddon(addonId)};
189 addon->getLoadedFlagNode()->setBoolValue(false);
190 SGPropertyNode_ptr addonNode = addon->getAddonNode();
191
192 // Set a few properties for the add-on under this node
193 addonNode->getNode("id", true)->setStringValue(addonId);
194 addonNode->getNode("name", true)->setStringValue(addon->getName());
195 addonNode->getNode("version", true)
196 ->setStringValue(addonVersion(addonId)->str());
197 addonNode->getNode("path", true)->setStringValue(addonRealPath.utf8Str());
198 addonNode->getNode("load-seq-num", true)
199 ->setIntValue(addon->getLoadSequenceNumber());
200
201 // “Legacy node”. Should we remove these two lines?
202 SGPropertyNode* seqNumNode = fgGetNode("addons", true)->addChild("addon");
203 seqNumNode->getNode("path", true)->setStringValue(addonRealPath.utf8Str());
204
205 string msg = "Registered add-on '" + addon->getName() + "' (" + addonId +
206 ") version " + addonVersion(addonId)->str() + "; "
207 "base path is '" + addonRealPath.utf8Str() + "'";
208
209 auto dataPath = addonRealPath / "FGData";
210 if (dataPath.exists()) {
211 SG_LOG(SG_GENERAL, SG_INFO, "Registering data path for add-on: " << addon->getName());
212 globals->append_data_path(dataPath, true /* after FG_ROOT */);
213 }
214
215 // This preserves the registration order
216 _registeredAddons.push_back(addon);
217 SG_LOG(SG_GENERAL, SG_INFO, msg);
218
219 return addonId;
220}
221
222bool
223AddonManager::isAddonRegistered(const string& addonId) const
224{
225 return (_idToAddonMap.find(addonId) != _idToAddonMap.end());
226}
227
228bool
229AddonManager::isAddonLoaded(const string& addonId) const
230{
231 return (isAddonRegistered(addonId) &&
232 getAddon(addonId)->getLoadedFlagNode()->getBoolValue());
233}
234
235vector<AddonRef>
237{
238 return _registeredAddons;
239}
240
241vector<AddonRef>
243{
244 vector<AddonRef> v;
245 v.reserve(_idToAddonMap.size()); // will be the right size most of the times
246
247 for (const auto& elem: _idToAddonMap) {
248 if (isAddonLoaded(elem.first)) {
249 v.push_back(elem.second);
250 }
251 }
252
253 return v;
254}
255
257AddonManager::getAddon(const string& addonId) const
258{
259 const auto it = _idToAddonMap.find(addonId);
260
261 if (it == _idToAddonMap.end()) {
262 throw sg_exception("tried to get add-on '" + addonId + "', however no "
263 "such add-on has been registered.");
264 }
265
266 return it->second;
267}
268
270AddonManager::addonVersion(const string& addonId) const
271{
272 return getAddon(addonId)->getVersion();
273}
274
275SGPath
276AddonManager::addonBasePath(const string& addonId) const
277{
278 return getAddon(addonId)->getBasePath();
279}
280
281SGPropertyNode_ptr AddonManager::addonNode(const string& addonId) const
282{
283 return getAddon(addonId)->getAddonNode();
284}
285
286void
288{
289 for (const auto& addon: _registeredAddons) {
290 addon->addToFGMenubar();
291 }
292}
293
294} // of namespace addons
295
296} // of namespace flightgear
SGPropertyNode * get_props()
Definition globals.hxx:320
AddonRef getAddon(const std::string &addonId) const
std::vector< AddonRef > registeredAddons() const
static const std::unique_ptr< AddonManager > & createInstance()
SGPath addonBasePath(const std::string &addonId) const
SGPropertyNode_ptr addonNode(const std::string &addonId) const
AddonVersionRef addonVersion(const std::string &addonId) const
std::string registerAddon(const SGPath &addonPath)
AddonManager(const AddonManager &)=delete
bool isAddonLoaded(const std::string &addonId) const
static const std::unique_ptr< AddonManager > & instance()
bool isAddonRegistered(const std::string &addonId) const
std::vector< AddonRef > loadedAddons() const
static Addon fromAddonDir(const SGPath &addonPath)
Definition Addon.cxx:374
FGGlobals * globals
Definition globals.cxx:142
SGSharedPtr< Addon > AddonRef
Definition addon_fwd.hxx:46
static unique_ptr< AddonManager > staticInstance
SGSharedPtr< AddonVersion > AddonVersionRef
Definition addon_fwd.hxx:47
FlightPlan.hxx - defines a full flight-plan object, including departure, cruise, arrival information ...
Definition Addon.cxx:53
SGPropertyNode * fgGetNode(const char *path, bool create)
Get a property node.
Definition proptest.cpp:27