FlightGear next
HTTPClient.cxx
Go to the documentation of this file.
1// HTTPClient.cxx -- Singleton HTTP client object
2//
3// Written by James Turner, started April 2012.
4//
5// Copyright (C) 2012 James Turner
6//
7// This program is free software; you can redistribute it and/or
8// modify it under the terms of the GNU General Public License as
9// published by the Free Software Foundation; either version 2 of the
10// License, or (at your option) any later version.
11//
12// This program is distributed in the hope that it will be useful, but
13// WITHOUT ANY WARRANTY; without even the implied warranty of
14// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15// General Public License for more details.
16//
17// You should have received a copy of the GNU General Public License
18// along with this program; if not, write to the Free Software
19// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20
21#include "config.h"
22
23#include "HTTPClient.hxx"
24
25#include <cassert>
26
27#include <Main/fg_props.hxx>
28
29#include <simgear/sg_inlines.h>
30
31#include <simgear/package/Root.hxx>
32#include <simgear/package/Catalog.hxx>
33#include <simgear/package/Delegate.hxx>
34#include <simgear/package/Install.hxx>
35#include <simgear/package/Package.hxx>
36
37#include <simgear/nasal/cppbind/from_nasal.hxx>
38#include <simgear/nasal/cppbind/to_nasal.hxx>
39#include <simgear/nasal/cppbind/NasalHash.hxx>
40#include <simgear/nasal/cppbind/Ghost.hxx>
41
43
44using namespace simgear;
45
46typedef nasal::Ghost<pkg::RootRef> NasalPackageRoot;
47typedef nasal::Ghost<pkg::PackageRef> NasalPackage;
48typedef nasal::Ghost<pkg::CatalogRef> NasalCatalog;
49typedef nasal::Ghost<pkg::InstallRef> NasalInstall;
50
51static const char* OFFICIAL_CATALOG_ID = "org.flightgear.fgaddon.trunk";
52
53// fallback URL is used when looking up a version-specific catalog fails
54static const char* FALLBACK_CATALOG_URL = "http://mirrors.ibiblio.org/flightgear/ftp/Aircraft-trunk/catalog.xml";
55
56namespace {
57
58 std::string _getDefaultCatalogId()
59 {
60 return fgGetString("/sim/package-system/default-catalog/id",
62 }
63
64 pkg::CatalogRef getDefaultCatalog()
65 {
66 if (!globals->packageRoot())
67 return pkg::CatalogRef();
68
69 return globals->packageRoot()->getCatalogById(_getDefaultCatalogId());
70 }
71
72 std::string _getDefaultCatalogUrl()
73 {
74 return fgGetString("/sim/package-system/default-catalog/url",
75 "http://mirrors.ibiblio.org/flightgear/ftp/" FLIGHTGEAR_VERSION "/catalog.xml");
76 }
77} // of anonymous namespace
78
79
81 _inited(false)
82{
83}
84
88
90{
91 auto ext = globals->get_subsystem<FGHTTPClient>();
92 if (ext) {
93 return ext;
94 }
95
96 ext = globals->get_subsystem_mgr()->add<FGHTTPClient>();
97 ext->init();
98 return ext;
99}
100
101
103{
104 // launcher may need to setup HTTP access abnormally early, so
105 // guard against duplicate inits
106 if (_inited) {
107 return;
108 }
109
110 _http.reset(new simgear::HTTP::Client);
111
112 std::string proxyHost(fgGetString("/sim/presets/proxy/host"));
113 int proxyPort(fgGetInt("/sim/presets/proxy/port"));
114 std::string proxyAuth(fgGetString("/sim/presets/proxy/auth"));
115
116 if (!proxyHost.empty()) {
117 _http->setProxy(proxyHost, proxyPort, proxyAuth);
118 }
119
120 pkg::Root* packageRoot = globals->packageRoot();
121 if (packageRoot) {
122 // package system needs access to the HTTP engine too
123 packageRoot->setHTTPClient(_http.get());
124
125 // start a refresh now
126 // setting 'force' true to work around the problem where a slightly stale
127 // catalog exists, but aircraft are modified - this causes an MD5 sum
128 // mismatch
129 packageRoot->refresh(true);
130 }
131
132 _inited = true;
133}
134
136{
137 return getDefaultCatalog().valid();
138}
139
141{
142 pkg::CatalogRef defaultCatalog = getDefaultCatalog();
143 if (!defaultCatalog.valid()) {
144 auto cat = pkg::Catalog::createFromUrl(globals->packageRoot(), getDefaultCatalogUrl());
145 return cat;
146 }
147
148 return defaultCatalog;
149}
150
152{
153 return _getDefaultCatalogId();
154}
155
157{
158 return _getDefaultCatalogUrl();
159}
160
162{
163 return std::string(FALLBACK_CATALOG_URL);
164}
165
166static naRef f_package_existingInstall( pkg::Package& pkg,
167 const nasal::CallContext& ctx )
168{
169 return ctx.to_nasal(
170 pkg.existingInstall( ctx.getArg<pkg::Package::InstallCallback>(0) )
171 );
172}
173
174static naRef f_package_uninstall(pkg::Package& pkg, const nasal::CallContext& ctx)
175{
176 pkg::InstallRef ins = pkg.existingInstall();
177 if (ins) {
178 ins->uninstall();
179 }
180
181 return naNil();
182}
183
184static SGPropertyNode_ptr queryPropsFromHash(const nasal::Hash& h)
185{
186 SGPropertyNode_ptr props(new SGPropertyNode);
187
188 for (nasal::Hash::const_iterator it = h.begin(); it != h.end(); ++it) {
189 std::string const key = it->getKey();
190 if ((key == "name") || (key == "description")) {
191 props->setStringValue(key, it->getValue<std::string>());
192 } else if (strutils::starts_with(key, "rating-")) {
193 props->setIntValue(key, it->getValue<int>());
194 } else if (key == "tags") {
195 string_list tags = it->getValue<string_list>();
196 string_list::const_iterator tagIt;
197 int tagCount = 0;
198 for (tagIt = tags.begin(); tagIt != tags.end(); ++tagIt) {
199 SGPropertyNode_ptr tag = props->getChild("tag", tagCount++, true);
200 tag->setStringValue(*tagIt);
201 }
202 } else if (key == "installed") {
203 props->setBoolValue(key, it->getValue<bool>());
204 } else {
205 SG_LOG(SG_GENERAL, SG_WARN, "unknown filter term in hash:" << key);
206 }
207 }
208
209 return props;
210}
211
212static naRef f_root_search(pkg::Root& root, const nasal::CallContext& ctx)
213{
214 SGPropertyNode_ptr query = queryPropsFromHash(ctx.requireArg<nasal::Hash>(0));
215 pkg::PackageList result = root.packagesMatching(query);
216 return ctx.to_nasal(result);
217}
218
219static naRef f_catalog_search(pkg::Catalog& cat, const nasal::CallContext& ctx)
220{
221 SGPropertyNode_ptr query = queryPropsFromHash(ctx.requireArg<nasal::Hash>(0));
222 pkg::PackageList result = cat.packagesMatching(query);
223 return ctx.to_nasal(result);
224}
225
226static naRef f_catalog_packages(pkg::Catalog& cat, const nasal::CallContext& ctx)
227{
228 // TODO: support package types
229 pkg::PackageList result = cat.packages();
230 return ctx.to_nasal(result);
231}
232
233
234static naRef f_catalog_installedPackages(pkg::Catalog& cat, naContext c)
235{
236 // TODO: support package types
237 pkg::PackageList result = cat.installedPackages();
238 return nasal::to_nasal(c, result);
239}
240
241static naRef f_package_variants(pkg::Package& pack, naContext c)
242{
243 nasal::Hash h(c);
244 string_list vars(pack.variants());
245 for (string_list_iterator it = vars.begin(); it != vars.end(); ++it) {
246 h.set(*it, pack.nameForVariant(*it));
247 }
248
249 return h.get_naRef();
250}
251
253{
254 NasalPackageRoot::init("PackageRoot")
255 .member("path", &pkg::Root::path)
256 .member("version", &pkg::Root::catalogVersion)
257 .method("refresh", &pkg::Root::refresh)
258 .method("catalogs", &pkg::Root::catalogs)
259 .method("packageById", &pkg::Root::getPackageById)
260 .method("catalogById", &pkg::Root::getCatalogById)
261 .method("search", &f_root_search);
262
263 NasalCatalog::init("Catalog")
264 .member("installRoot", &pkg::Catalog::installRoot)
265 .member("id", &pkg::Catalog::id)
266 .member("url", &pkg::Catalog::url)
267 .member("description", &pkg::Catalog::description)
268 .method("packages", &f_catalog_packages)
269 .method("packageById", &pkg::Catalog::getPackageById)
270 .method("refresh", &pkg::Catalog::refresh)
271 .method("needingUpdate", &pkg::Catalog::packagesNeedingUpdate)
272 .member("installed", &f_catalog_installedPackages)
273 .method("search", &f_catalog_search)
274 .member("enabled", &pkg::Catalog::isEnabled);
275
276 NasalPackage::init("Package")
277 .member("id", &pkg::Package::id)
278 .member("name", &pkg::Package::name)
279 .member("description", &pkg::Package::description)
280 .member("installed", &pkg::Package::isInstalled)
281 .member("thumbnails", &pkg::Package::thumbnailUrls)
282 .member("variants", &f_package_variants)
283 .member("revision", &pkg::Package::revision)
284 .member("catalog", &pkg::Package::catalog)
285 .method("install", &pkg::Package::install)
286 .method("uninstall", &f_package_uninstall)
287 .method("existingInstall", &f_package_existingInstall)
288 .method("lprop", &pkg::Package::getLocalisedProp)
289 .member("fileSize", &pkg::Package::fileSizeBytes);
290
291 typedef pkg::Install* (pkg::Install::*InstallCallback)
292 (const pkg::Install::Callback&);
293 typedef pkg::Install* (pkg::Install::*ProgressCallback)
294 (const pkg::Install::ProgressCallback&);
295 NasalInstall::init("Install")
296 .member("revision", &pkg::Install::revsion)
297 .member("pkg", &pkg::Install::package)
298 .member("path", &pkg::Install::path)
299 .member("hasUpdate", &pkg::Install::hasUpdate)
300 .method("startUpdate", &pkg::Install::startUpdate)
301 .method("uninstall", &pkg::Install::uninstall)
302 .method("done", static_cast<InstallCallback>(&pkg::Install::done))
303 .method("fail", static_cast<InstallCallback>(&pkg::Install::fail))
304 .method("always", static_cast<InstallCallback>(&pkg::Install::always))
305 .method("progress", static_cast<ProgressCallback>(&pkg::Install::progress));
306
307 pkg::Root* packageRoot = globals->packageRoot();
308 if (packageRoot) {
309 auto nasalSys = globals->get_subsystem<FGNasalSys>();
310 nasal::Hash nasalGlobals = nasalSys->getGlobals();
311 nasal::Hash nasalPkg = nasalGlobals.createHash("pkg"); // module
312 nasalPkg.set("root", packageRoot);
313 }
314}
315
317{
318 _http.reset();
319
320 _inited = false;
321}
322
324{
325 _http->update();
326}
327
328void FGHTTPClient::makeRequest(const simgear::HTTP::Request_ptr& req)
329{
330 _http->makeRequest(req);
331}
332
333
334// Register the subsystem.
335SGSubsystemMgr::Registrant<FGHTTPClient> registrantFGHTTPClient(
336 SGSubsystemMgr::GENERAL,
337 {{"nasal", SGSubsystemMgr::Dependency::HARD}});
static naRef f_catalog_packages(pkg::Catalog &cat, const nasal::CallContext &ctx)
static naRef f_root_search(pkg::Root &root, const nasal::CallContext &ctx)
static naRef f_package_existingInstall(pkg::Package &pkg, const nasal::CallContext &ctx)
static naRef f_catalog_installedPackages(pkg::Catalog &cat, naContext c)
nasal::Ghost< pkg::RootRef > NasalPackageRoot
static SGPropertyNode_ptr queryPropsFromHash(const nasal::Hash &h)
static naRef f_package_variants(pkg::Package &pack, naContext c)
static naRef f_catalog_search(pkg::Catalog &cat, const nasal::CallContext &ctx)
nasal::Ghost< pkg::PackageRef > NasalPackage
static const char * OFFICIAL_CATALOG_ID
static naRef f_package_uninstall(pkg::Package &pkg, const nasal::CallContext &ctx)
nasal::Ghost< pkg::InstallRef > NasalInstall
nasal::Ghost< pkg::CatalogRef > NasalCatalog
SGSubsystemMgr::Registrant< FGHTTPClient > registrantFGHTTPClient(SGSubsystemMgr::GENERAL, {{"nasal", SGSubsystemMgr::Dependency::HARD}})
static const char * FALLBACK_CATALOG_URL
static FGNasalSys * nasalSys
Definition NasalSys.cxx:82
bool isDefaultCatalogInstalled() const
void makeRequest(const simgear::HTTP::Request_ptr &req)
void postinit() override
static FGHTTPClient * getOrCreate()
std::string getDefaultCatalogId() const
void shutdown() override
void update(double) override
void init() override
simgear::pkg::CatalogRef addDefaultCatalog()
virtual ~FGHTTPClient()
std::string getDefaultCatalogFallbackUrl() const
std::string getDefaultCatalogUrl() const
int fgGetInt(const char *name, int defaultValue)
Get an int value for a property.
Definition fg_props.cxx:532
std::string fgGetString(const char *name, const char *defaultValue)
Get a string value for a property.
Definition fg_props.cxx:556
FGGlobals * globals
Definition globals.cxx:142
std::vector< std::string > string_list
Definition globals.hxx:36