1 // globals.cxx -- Global state that needs to be shared among the sim modules
2 //
3 // Written by Curtis Olson, started July 2000.
4 //
5 // Copyright (C) 2000  Curtis L. Olson - http://www.flightgear.org/~curt
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 Foundation,
19 // Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
20 //
21 // $Id$
22 
23 #include <config.h>
24 
25 #include <algorithm>
26 
27 #include <osgViewer/Viewer>
28 #include <osgDB/Registry>
29 
30 #include <simgear/structure/commands.hxx>
31 #include <simgear/structure/exception.hxx>
32 #include <simgear/misc/sg_path.hxx>
33 #include <simgear/misc/sg_dir.hxx>
34 #include <simgear/timing/sg_time.hxx>
35 #include <simgear/ephemeris/ephemeris.hxx>
36 #include <simgear/structure/subsystem_mgr.hxx>
37 #include <simgear/structure/event_mgr.hxx>
38 
39 #include <simgear/misc/ResourceManager.hxx>
40 #include <simgear/props/propertyObject.hxx>
41 #include <simgear/props/props_io.hxx>
42 #include <simgear/props/AtomicChangeListener.hxx>
43 #include <simgear/scene/model/modellib.hxx>
44 #include <simgear/package/Root.hxx>
45 
46 #include <Add-ons/AddonResourceProvider.hxx>
47 #include <Aircraft/controls.hxx>
48 #include <Airports/runways.hxx>
49 #include <Autopilot/route_mgr.hxx>
50 #include <Navaids/navlist.hxx>
51 
52 #include <GUI/gui.h>
53 #include <Main/sentryIntegration.hxx>
54 #include <Viewer/viewmgr.hxx>
55 
56 #include <Scenery/scenery.hxx>
57 #include <Scenery/tilemgr.hxx>
58 #include <Viewer/renderer.hxx>
59 #include <GUI/FGFontCache.hxx>
60 #include <GUI/MessageBox.hxx>
61 
62 #include <simgear/sound/soundmgr.hxx>
63 #include <simgear/scene/material/matlib.hxx>
64 
65 #include "globals.hxx"
66 #include "locale.hxx"
67 
68 #include "fg_props.hxx"
69 #include "fg_io.hxx"
70 
71 class AircraftResourceProvider : public simgear::ResourceProvider
72 {
73 public:
AircraftResourceProvider()74   AircraftResourceProvider() :
75     simgear::ResourceProvider(simgear::ResourceManager::PRIORITY_HIGH)
76   {
77   }
78 
resolve(const std::string & aResource,SGPath &) const79   virtual SGPath resolve(const std::string& aResource, SGPath&) const
80   {
81     string_list pieces(sgPathBranchSplit(aResource));
82     if ((pieces.size() < 3) || (pieces.front() != "Aircraft")) {
83       return SGPath(); // not an Aircraft path
84     }
85 
86   // test against the aircraft-dir property
87     const char* aircraftDir = fgGetString("/sim/aircraft-dir");
88     string_list aircraftDirPieces(sgPathBranchSplit(aircraftDir));
89     if (!aircraftDirPieces.empty() && (aircraftDirPieces.back() == pieces[1])) {
90         // current aircraft-dir matches resource aircraft
91         SGPath r(aircraftDir);
92         for (unsigned int i=2; i<pieces.size(); ++i) {
93           r.append(pieces[i]);
94         }
95 
96         if (r.exists()) {
97           return r;
98         } else {
99           // Stop here, otherwise we could end up returning a resource that
100           // belongs to an unrelated version of the same aircraft (from a
101           // different aircraft directory).
102           return SGPath();
103         }
104     }
105 
106   // try each aircraft dir in turn
107     std::string res(aResource, 9); // resource path with 'Aircraft/' removed
108     const PathList& dirs(globals->get_aircraft_paths());
109     PathList::const_iterator it = dirs.begin();
110     for (; it != dirs.end(); ++it) {
111         SGPath p(*it);
112         p.append(res);
113       if (p.exists()) {
114         return p;
115       }
116     } // of aircraft path iteration
117 
118     return SGPath(); // not found
119   }
120 };
121 
122 class CurrentAircraftDirProvider : public simgear::ResourceProvider
123 {
124 public:
CurrentAircraftDirProvider()125   CurrentAircraftDirProvider() :
126     simgear::ResourceProvider(simgear::ResourceManager::PRIORITY_HIGH)
127   {
128   }
129 
resolve(const std::string & aResource,SGPath &) const130   virtual SGPath resolve(const std::string& aResource, SGPath&) const
131   {
132       SGPath p = SGPath::fromUtf8(fgGetString("/sim/aircraft-dir"));
133     p.append(aResource);
134     return p.exists() ? p : SGPath();
135   }
136 };
137 
138 class CompositorEffectsProvider : public simgear::ResourceProvider {
139 public:
CompositorEffectsProvider()140     CompositorEffectsProvider() :
141         simgear::ResourceProvider(simgear::ResourceManager::PRIORITY_NORMAL) {
142     }
resolve(const std::string & aResource,SGPath &) const143     SGPath resolve(const std::string &aResource, SGPath&) const override {
144         const SGPath p = globals->get_fg_root() / "Compositor" / aResource;
145         return p.exists() ? p : SGPath();
146     }
147 };
148 
149 ////////////////////////////////////////////////////////////////////////
150 // Implementation of FGGlobals.
151 ////////////////////////////////////////////////////////////////////////
152 
153 // global global :-)
154 FGGlobals *globals = NULL;
155 
156 
157 // Constructor
FGGlobals()158 FGGlobals::FGGlobals() :
159     renderer( new FGRenderer ),
160     subsystem_mgr( new SGSubsystemMgr ),
161     event_mgr( new SGEventMgr ),
162     sim_time_sec( 0.0 ),
163     fg_root( "" ),
164     fg_home( "" ),
165     time_params( NULL ),
166     commands( SGCommandMgr::instance() ),
167     channel_options_list( NULL ),
168     initial_waypoints( NULL ),
169     channellist( NULL ),
170     haveUserSettings(false)
171 {
172     SGPropertyNode* root = new SGPropertyNode;
173     props = SGPropertyNode_ptr(root);
174     locale = new FGLocale(props);
175 
176     auto resMgr = simgear::ResourceManager::instance();
177     resMgr->addProvider(new AircraftResourceProvider());
178     resMgr->addProvider(new CurrentAircraftDirProvider());
179     resMgr->addProvider(new flightgear::addons::ResourceProvider());
180 #ifdef ENABLE_COMPOSITOR
181     resMgr->addProvider(new CompositorEffectsProvider());
182 #endif
183     initProperties();
184 }
185 
initProperties()186 void FGGlobals::initProperties()
187 {
188     simgear::PropertyObjectBase::setDefaultRoot(props);
189 
190     positionLon = props->getNode("position/longitude-deg", true);
191     positionLat = props->getNode("position/latitude-deg", true);
192     positionAlt = props->getNode("position/altitude-ft", true);
193 
194     viewLon = props->getNode("sim/current-view/viewer-lon-deg", true);
195     viewLat = props->getNode("sim/current-view/viewer-lat-deg", true);
196     viewAlt = props->getNode("sim/current-view/viewer-elev-ft", true);
197 
198     orientPitch = props->getNode("orientation/pitch-deg", true);
199     orientHeading = props->getNode("orientation/heading-deg", true);
200     orientRoll = props->getNode("orientation/roll-deg", true);
201 
202 }
203 
204 // Destructor
~FGGlobals()205 FGGlobals::~FGGlobals()
206 {
207     // save user settings (unless already saved)
208     saveUserSettings();
209 
210     // stop OSG threading first, to avoid thread races while we tear down
211     // scene-graph pieces
212     // there are some scenarios where renderer is already gone.
213     osg::ref_ptr<osgViewer::Viewer> vw;
214     if (renderer) {
215         vw = renderer->getViewer();
216         if (vw) {
217             // https://code.google.com/p/flightgear-bugs/issues/detail?id=1291
218             // explicitly stop trheading before we delete the renderer or
219             // viewMgr (which ultimately holds refs to the CameraGroup, and
220             // GraphicsContext)
221             vw->stopThreading();
222         }
223     }
224 
225     subsystem_mgr->shutdown();
226     subsystem_mgr->unbind();
227 
228     // don't cancel the pager until after shutdown, since AIModels (and
229     // potentially others) can queue delete requests on the pager.
230     if (vw && vw->getDatabasePager()) {
231         vw->getDatabasePager()->cancel();
232         vw->getDatabasePager()->clear();
233     }
234 
235     osgDB::Registry::instance()->clearObjectCache();
236     if (subsystem_mgr->get_subsystem(FGScenery::staticSubsystemClassId())) {
237         subsystem_mgr->remove(FGScenery::staticSubsystemClassId());
238     }
239 
240     // renderer touches subsystems during its destruction
241     set_renderer(nullptr);
242 
243     FGFontCache::shutdown();
244     fgCancelSnapShot();
245 
246     delete subsystem_mgr;
247     subsystem_mgr = nullptr; // important so ::get_subsystem returns NULL
248     vw = nullptr;
249     set_matlib(NULL);
250 
251     delete time_params;
252     delete channel_options_list;
253     delete initial_waypoints;
254     delete channellist;
255 
256     // delete commands before we release the property root
257     // this avoids crash where commands might be storing a propery
258     // ref/pointer.
259     // see https://sentry.io/organizations/flightgear/issues/1890563449
260     delete commands;
261     commands = nullptr;
262 
263     simgear::PropertyObjectBase::setDefaultRoot(NULL);
264     simgear::SGModelLib::resetPropertyRoot();
265     delete locale;
266     locale = NULL;
267 
268     cleanupListeners();
269 
270     props.clear();
271 
272     delete simgear::ResourceManager::instance();
273 }
274 
275 // set the fg_root path
set_fg_root(const SGPath & root)276 void FGGlobals::set_fg_root (const SGPath &root) {
277     SGPath tmp(root);
278     fg_root = tmp.realpath();
279 
280     // append /data to root if it exists
281     tmp.append( "data" );
282     tmp.append( "version" );
283     if ( tmp.exists() ) {
284         fgGetNode("BAD_FG_ROOT", true)->setStringValue(fg_root.utf8Str());
285         fg_root.append("data");
286         fgGetNode("GOOD_FG_ROOT", true)->setStringValue(fg_root.utf8Str());
287         SG_LOG(SG_GENERAL, SG_ALERT, "***\n***\n*** Warning: changing bad FG_ROOT/--fg-root to '"
288                 << fg_root << "'\n***\n***");
289     }
290 
291     // deliberately not a tied property, for fgValidatePath security
292     // write-protect to avoid accidents
293     SGPropertyNode *n = fgGetNode("/sim", true);
294     n->removeChild("fg-root", 0);
295     n = n->getChild("fg-root", 0, true);
296     n->setStringValue(fg_root.utf8Str());
297     n->setAttribute(SGPropertyNode::WRITE, false);
298 
299     simgear::ResourceManager::instance()->addBasePath(fg_root,
300       simgear::ResourceManager::PRIORITY_DEFAULT);
301 }
302 
303 // set the fg_home path
set_fg_home(const SGPath & home)304 void FGGlobals::set_fg_home (const SGPath &home)
305 {
306     fg_home = home.realpath();
307 }
308 
set_texture_cache_dir(const SGPath & textureCache)309 void FGGlobals::set_texture_cache_dir(const SGPath &textureCache)
310 {
311     texture_cache_dir = textureCache.realpath();
312     auto node = fgGetNode("/sim/rendering/texture-cache/dir", true);
313     node->setAttribute(SGPropertyNode::WRITE, true);
314     node->setStringValue(textureCache.utf8Str());
315     node->setAttribute(SGPropertyNode::WRITE, false);
316 }
317 
318 
get_data_paths() const319 PathList FGGlobals::get_data_paths() const
320 {
321     PathList r(additional_data_paths);
322     r.push_back(fg_root);
323     r.insert(r.end(), _dataPathsAfterFGRoot.begin(), _dataPathsAfterFGRoot.end());
324     return r;
325 }
326 
get_data_paths(const std::string & suffix) const327 PathList FGGlobals::get_data_paths(const std::string& suffix) const
328 {
329     PathList r;
330     for (SGPath p : get_data_paths()) {
331         p.append(suffix);
332         if (p.exists()) {
333             r.push_back(p);
334         }
335     }
336 
337     return r;
338 }
339 
append_data_path(const SGPath & path,bool afterFGRoot)340 void FGGlobals::append_data_path(const SGPath& path, bool afterFGRoot)
341 {
342     if (!path.exists()) {
343         SG_LOG(SG_GENERAL, SG_WARN, "adding non-existant data path:" << path);
344     }
345 
346     using RM = simgear::ResourceManager;
347     auto resManager = RM::instance();
348     if (afterFGRoot) {
349         _dataPathsAfterFGRoot.push_back(path);
350         // after FG_ROOT
351         resManager->addBasePath(path, static_cast<RM::Priority>(RM::PRIORITY_DEFAULT - 10));
352     } else {
353         additional_data_paths.push_back(path);
354         // after NORMAL prioirty, but ahead of FG_ROOT
355         resManager->addBasePath(path, static_cast<RM::Priority>(RM::PRIORITY_DEFAULT + 10));
356     }
357 }
358 
findDataPath(const std::string & pathSuffix) const359 SGPath FGGlobals::findDataPath(const std::string& pathSuffix) const
360 {
361     for (SGPath p : additional_data_paths) {
362         p.append(pathSuffix);
363         if (p.exists()) {
364             return p;
365         }
366     }
367 
368     SGPath rootPath(fg_root);
369     rootPath.append(pathSuffix);
370     if (rootPath.exists()) {
371         return rootPath;
372     }
373 
374     for (SGPath p : _dataPathsAfterFGRoot) {
375         p.append(pathSuffix);
376         if (p.exists()) {
377             return p;
378         }
379     }
380 
381     SG_LOG(SG_IO, SG_WARN, "not found in any data path: '" << pathSuffix << "'");
382     return SGPath{};
383 }
384 
append_fg_scenery(const PathList & paths)385 void FGGlobals::append_fg_scenery (const PathList &paths)
386 {
387     for (const SGPath& path : paths) {
388         append_fg_scenery(path);
389     }
390 }
391 
append_fg_scenery(const SGPath & path)392 void FGGlobals::append_fg_scenery (const SGPath &path)
393 {
394     SGPropertyNode* sim = fgGetNode("/sim", true);
395 
396     // find first unused fg-scenery property in /sim
397     int propIndex = 0;
398     while (sim->getChild("fg-scenery", propIndex) != NULL) {
399         ++propIndex;
400     }
401 
402     SGPath abspath(path.realpath());
403     if (!abspath.exists()) {
404         SG_LOG(SG_GENERAL, SG_WARN, "scenery path not found:" << abspath);
405         return;
406     }
407 
408     // check for duplicates
409     PathList::const_iterator ex = std::find(fg_scenery.begin(), fg_scenery.end(), abspath);
410     if (ex != fg_scenery.end()) {
411         SG_LOG(SG_GENERAL, SG_INFO, "skipping duplicate add of scenery path:" << abspath);
412         return;
413     }
414 
415     // tell the ResouceManager about the scenery path
416     // needed to load Models from this scenery path
417     simgear::ResourceManager::instance()->addBasePath(abspath, simgear::ResourceManager::PRIORITY_DEFAULT);
418 
419     fg_scenery.push_back(abspath);
420     extra_read_allowed_paths.push_back(abspath);
421 
422     // make scenery dirs available to Nasal
423     SGPropertyNode* n = sim->getChild("fg-scenery", propIndex++, true);
424     n->setStringValue(abspath.utf8Str());
425     n->setAttribute(SGPropertyNode::WRITE, false);
426 
427     // temporary fix so these values survive reset
428     n->setAttribute(SGPropertyNode::PRESERVE, true);
429 }
430 
append_read_allowed_paths(const SGPath & path)431 void FGGlobals::append_read_allowed_paths(const SGPath &path)
432 {
433     SGPath abspath(path.realpath());
434     if (!abspath.exists()) {
435         SG_LOG(SG_IO, SG_DEBUG, "read-allowed path not found:" << abspath);
436         return;
437     }
438     extra_read_allowed_paths.push_back(abspath);
439 }
440 
clear_fg_scenery()441 void FGGlobals::clear_fg_scenery()
442 {
443   fg_scenery.clear();
444   fgGetNode("/sim", true)->removeChildren("fg-scenery");
445 }
446 
447 // The 'path' argument to this method must come from trustworthy code, because
448 // the method grants read permissions to Nasal code for many files beneath
449 // 'path'. In particular, don't call this method with a 'path' value taken
450 // from the property tree or any other Nasal-writable place.
set_download_dir(const SGPath & path)451 void FGGlobals::set_download_dir(const SGPath& path)
452 {
453   SGPath abspath(path.realpath());
454   download_dir = abspath;
455 
456   append_read_allowed_paths(abspath / "Aircraft");
457   append_read_allowed_paths(abspath / "AI");
458   append_read_allowed_paths(abspath / "Liveries");
459   // If in use, abspath / TerraSync will be added to 'extra_read_allowed_paths'
460   // by FGGlobals::append_fg_scenery(), as any scenery path.
461 
462   SGPropertyNode *n = fgGetNode("/sim/paths/download-dir", true);
463   n->setAttribute(SGPropertyNode::WRITE, true);
464   n->setStringValue(abspath.utf8Str());
465   n->setAttribute(SGPropertyNode::WRITE, false);
466 }
467 
468 // The 'path' argument to this method must come from trustworthy code, because
469 // the method grants read permissions to Nasal code for all files beneath
470 // 'path'. In particular, don't call this method with a 'path' value taken
471 // from the property tree or any other Nasal-writable place.
set_terrasync_dir(const SGPath & path)472 void FGGlobals::set_terrasync_dir(const SGPath &path)
473 {
474   if (terrasync_dir.realpath() != SGPath(fgGetString("/sim/terrasync/scenery-dir")).realpath()) {
475     // if they don't match, /sim/terrasync/scenery-dir has been set by something else
476     SG_LOG(SG_GENERAL, SG_WARN, "/sim/terrasync/scenery-dir is no longer stored across runs: if you wish to keep using a non-standard Terrasync directory, use --terrasync-dir or the launcher's settings");
477   }
478   SGPath abspath(path.realpath());
479   terrasync_dir = abspath;
480   // deliberately not a tied property, for fgValidatePath security
481   // write-protect to avoid accidents
482   SGPropertyNode *n = fgGetNode("/sim/terrasync/scenery-dir", true);
483   n->setAttribute(SGPropertyNode::WRITE, true);
484   n->setStringValue(abspath.utf8Str());
485   n->setAttribute(SGPropertyNode::WRITE, false);
486   // don't add it to fg_scenery yet, as we want it ordered after explicit --fg-scenery
487 }
488 
489 
490 
set_catalog_aircraft_path(const SGPath & path)491 void FGGlobals::set_catalog_aircraft_path(const SGPath& path)
492 {
493     catalog_aircraft_dir = path;
494 }
495 
get_aircraft_paths() const496 PathList FGGlobals::get_aircraft_paths() const
497 {
498     PathList r;
499     if (!catalog_aircraft_dir.isNull()) {
500         r.push_back(catalog_aircraft_dir);
501     }
502 
503     r.insert(r.end(), fg_aircraft_dirs.begin(), fg_aircraft_dirs.end());
504     return r;
505 }
506 
append_aircraft_path(const SGPath & path)507 void FGGlobals::append_aircraft_path(const SGPath& path)
508 {
509   SGPath dirPath(path);
510   if (!dirPath.exists()) {
511     SG_LOG(SG_GENERAL, SG_WARN, "aircraft path not found:" << path);
512     return;
513   }
514 
515   SGPath acSubdir(dirPath);
516   acSubdir.append("Aircraft");
517   if (acSubdir.exists()) {
518       SG_LOG(
519         SG_GENERAL,
520         SG_WARN,
521         "Specified an aircraft-dir with an 'Aircraft' subdirectory:" << dirPath
522         << ", will instead use child directory:" << acSubdir
523       );
524       dirPath = acSubdir;
525   }
526 
527   fg_aircraft_dirs.push_back(dirPath.realpath());
528   extra_read_allowed_paths.push_back(dirPath.realpath());
529 }
530 
append_aircraft_paths(const PathList & paths)531 void FGGlobals::append_aircraft_paths(const PathList& paths)
532 {
533   for (unsigned int p = 0; p<paths.size(); ++p) {
534     append_aircraft_path(paths[p]);
535   }
536 }
537 
resolve_aircraft_path(const std::string & branch) const538 SGPath FGGlobals::resolve_aircraft_path(const std::string& branch) const
539 {
540   return simgear::ResourceManager::instance()->findPath(branch);
541 }
542 
resolve_maybe_aircraft_path(const std::string & branch) const543 SGPath FGGlobals::resolve_maybe_aircraft_path(const std::string& branch) const
544 {
545   return simgear::ResourceManager::instance()->findPath(branch);
546 }
547 
resolve_resource_path(const std::string & branch) const548 SGPath FGGlobals::resolve_resource_path(const std::string& branch) const
549 {
550   return simgear::ResourceManager::instance()
551     ->findPath(branch, SGPath(fgGetString("/sim/aircraft-dir")));
552 }
553 
554 FGRenderer *
get_renderer() const555 FGGlobals::get_renderer () const
556 {
557    return renderer;
558 }
559 
set_renderer(FGRenderer * render)560 void FGGlobals::set_renderer(FGRenderer *render)
561 {
562     if (render == renderer) {
563         return;
564     }
565 
566     delete renderer;
567     renderer = render;
568 }
569 
570 SGSubsystemMgr *
get_subsystem_mgr() const571 FGGlobals::get_subsystem_mgr () const
572 {
573     return subsystem_mgr;
574 }
575 
576 SGSubsystem *
get_subsystem(const char * name) const577 FGGlobals::get_subsystem (const char * name) const
578 {
579     if (!subsystem_mgr) {
580         return NULL;
581     }
582 
583     return subsystem_mgr->get_subsystem(name);
584 }
585 
586 void
add_subsystem(const char * name,SGSubsystem * subsystem,SGSubsystemMgr::GroupType type,double min_time_sec)587 FGGlobals::add_subsystem (const char * name,
588                           SGSubsystem * subsystem,
589                           SGSubsystemMgr::GroupType type,
590                           double min_time_sec)
591 {
592     subsystem_mgr->add(name, subsystem, type, min_time_sec);
593 }
594 
595 SGEventMgr *
get_event_mgr() const596 FGGlobals::get_event_mgr () const
597 {
598     return event_mgr;
599 }
600 
601 SGGeod
get_aircraft_position() const602 FGGlobals::get_aircraft_position() const
603 {
604   return SGGeod::fromDegFt(positionLon->getDoubleValue(),
605                            positionLat->getDoubleValue(),
606                            positionAlt->getDoubleValue());
607 }
608 
609 SGVec3d
get_aircraft_position_cart() const610 FGGlobals::get_aircraft_position_cart() const
611 {
612     return SGVec3d::fromGeod(get_aircraft_position());
613 }
614 
get_aircraft_orientation(double & heading,double & pitch,double & roll)615 void FGGlobals::get_aircraft_orientation(double& heading, double& pitch, double& roll)
616 {
617   heading = orientHeading->getDoubleValue();
618   pitch = orientPitch->getDoubleValue();
619   roll = orientRoll->getDoubleValue();
620 }
621 
622 SGGeod
get_view_position() const623 FGGlobals::get_view_position() const
624 {
625   return SGGeod::fromDegFt(viewLon->getDoubleValue(),
626                            viewLat->getDoubleValue(),
627                            viewAlt->getDoubleValue());
628 }
629 
630 SGVec3d
get_view_position_cart() const631 FGGlobals::get_view_position_cart() const
632 {
633   return SGVec3d::fromGeod(get_view_position());
634 }
635 
treeDumpRefCounts(int depth,SGPropertyNode * nd)636 static void treeDumpRefCounts(int depth, SGPropertyNode* nd)
637 {
638     for (int i=0; i<nd->nChildren(); ++i) {
639         SGPropertyNode* cp = nd->getChild(i);
640         if (SGReferenced::count(cp) > 1) {
641             SG_LOG(SG_GENERAL, SG_INFO, "\t" << cp->getPath() << " refcount:" << SGReferenced::count(cp));
642         }
643 
644         treeDumpRefCounts(depth + 1, cp);
645     }
646 }
647 
treeClearAliases(SGPropertyNode * nd)648 static void treeClearAliases(SGPropertyNode* nd)
649 {
650     if (nd->isAlias()) {
651         nd->unalias();
652     }
653 
654     for (int i=0; i<nd->nChildren(); ++i) {
655         SGPropertyNode* cp = nd->getChild(i);
656         treeClearAliases(cp);
657     }
658 }
659 
660 void
resetPropertyRoot()661 FGGlobals::resetPropertyRoot()
662 {
663     delete locale;
664 
665     // avoid a warning when we processOptions after reset
666     terrasync_dir = SGPath{};
667 
668     cleanupListeners();
669 
670     // we don't strictly need to clear these (they will be reset when we
671     // initProperties again), but trying to reduce false-positives when dumping
672     // ref-counts.
673     positionLon.clear();
674     positionLat.clear();
675     positionAlt.clear();
676     viewLon.clear();
677     viewLat.clear();
678     viewAlt.clear();
679     orientPitch.clear();
680     orientHeading.clear();
681     orientRoll.clear();
682 
683     // clear aliases so ref-counts are accurate when dumped
684     treeClearAliases(props);
685 
686     SG_LOG(SG_GENERAL, SG_INFO, "root props refcount:" << props.getNumRefs());
687     treeDumpRefCounts(0, props);
688 
689     //BaseStackSnapshot::dumpAll(std::cout);
690 
691     props = new SGPropertyNode;
692     initProperties();
693     locale = new FGLocale(props);
694 
695     // remove /sim/fg-root before writing to prevent hijacking
696     SGPropertyNode *n = props->getNode("/sim", true);
697     n->removeChild("fg-root", 0);
698     n = n->getChild("fg-root", 0, true);
699     n->setStringValue(fg_root.utf8Str());
700     n->setAttribute(SGPropertyNode::WRITE, false);
701 }
702 
autosaveName()703 static std::string autosaveName()
704 {
705     std::ostringstream os;
706     string_list versionParts = simgear::strutils::split(VERSION, ".");
707     if (versionParts.size() < 2) {
708         return "autosave.xml";
709     }
710 
711     os << "autosave_" << versionParts[0] << "_" << versionParts[1] << ".xml";
712     return os.str();
713 }
714 
autosaveFilePath(SGPath userDataPath) const715 SGPath FGGlobals::autosaveFilePath(SGPath userDataPath) const
716 {
717     if (userDataPath.isNull()) {
718         userDataPath = get_fg_home();
719     }
720 
721     return simgear::Dir(userDataPath).file(autosaveName());
722 }
723 
deleteProperties(SGPropertyNode * props,const string_list & blacklist)724 static void deleteProperties(SGPropertyNode* props, const string_list& blacklist)
725 {
726     const std::string path(props->getPath());
727     auto it = std::find_if(blacklist.begin(), blacklist.end(), [path](const std::string& black)
728                            { return simgear::strutils::matchPropPathToTemplate(path, black); });
729     if (it != blacklist.end()) {
730         SGPropertyNode* pr = props->getParent();
731         pr->removeChild(props);
732         return;
733     }
734 
735     // recurse
736     for (int c=0; c < props->nChildren(); ++c) {
737         deleteProperties(props->getChild(c), blacklist);
738     }
739 
740 }
741 
742 using VersionPair = std::pair<int, int>;
743 
operator <(const VersionPair & a,const VersionPair & b)744 static bool operator<(const VersionPair& a, const VersionPair& b)
745 {
746     if (a.first == b.first) {
747         return a.second < b.second;
748     }
749 
750     return a.first < b.first;
751 }
752 
tryAutosaveMigration(const SGPath & userDataPath,SGPropertyNode * props)753 static void tryAutosaveMigration(const SGPath& userDataPath, SGPropertyNode* props)
754 {
755     const string_list versionParts = simgear::strutils::split(VERSION, ".");
756     if (versionParts.size() < 2) {
757         return;
758     }
759 
760     simgear::Dir userDataDir(userDataPath);
761     SGPath migratePath;
762     VersionPair foundVersion(0, 0);
763     const VersionPair currentVersion(simgear::strutils::to_int(versionParts[0]),
764                                      simgear::strutils::to_int(versionParts[1]));
765 
766     for (auto previousSave : userDataDir.children(simgear::Dir::TYPE_FILE, ".xml")) {
767         const std::string base = previousSave.file_base();
768         VersionPair v;
769         // extract version from name
770         const int matches = ::sscanf(base.c_str(), "autosave_%d_%d", &v.first, &v.second);
771         if (matches != 2) {
772             continue;
773         }
774 
775         if (currentVersion < v) {
776             // ignore autosaves from more recent versions; this happens when
777             // running unsable and stable at the same time
778             continue;
779         }
780 
781         if (v.first < 2000) {
782             // ignore autosaves from older versions, too much change to deal
783             // with.
784             continue;
785         }
786 
787         if (foundVersion < v) {
788             foundVersion = v;
789             migratePath = previousSave;
790         }
791     }
792 
793     if (!migratePath.exists()) {
794         return;
795     }
796 
797     SG_LOG(SG_GENERAL, SG_INFO, "Migrating old autosave:" << migratePath);
798     SGPropertyNode oldProps;
799 
800     try {
801         readProperties(migratePath, &oldProps, SGPropertyNode::USERARCHIVE);
802     } catch (sg_exception& e) {
803         SG_LOG(SG_GENERAL, SG_WARN, "failed to read previous user settings:" << e.getMessage()
804                << "(from " << e.getOrigin() << ")");
805         return;
806     }
807 
808     // read migration blacklist
809     string_list blacklist;
810     SGPropertyNode_ptr blacklistNode = fgGetNode("/sim/autosave-migration/blacklist", true);
811     for (auto node : blacklistNode->getChildren("path")) {
812         blacklist.push_back(node->getStringValue());
813     }
814 
815     // apply migration filters for each property in turn
816     deleteProperties(&oldProps, blacklist);
817 
818     // manual migrations
819     // migrate pre-2019.1 sense of /sim/mouse/invert-mouse-wheel
820     if (foundVersion.first < 2019) {
821         SGPropertyNode_ptr wheelNode = oldProps.getNode("/sim/mouse/invert-mouse-wheel");
822         if (wheelNode) {
823             wheelNode->setBoolValue(!wheelNode->getBoolValue());
824         }
825     }
826     // copy remaining props out
827     copyProperties(&oldProps, props);
828 
829     // we can't inform the user yet, becuase embedded resources and the locale
830     // are not done. So we set a flag and check it once those things are done.
831     fgSetBool("/sim/autosave-migration/did-migrate", true);
832 }
833 
834 // Load user settings from the autosave file (normally in $FG_HOME)
835 void
loadUserSettings(SGPath userDataPath)836 FGGlobals::loadUserSettings(SGPath userDataPath)
837 {
838     if (userDataPath.isNull()) {
839         userDataPath = get_fg_home();
840     }
841 
842     // Remember that we have (tried) to load any existing autosave file
843     haveUserSettings = true;
844 
845     SGPath autosaveFile = autosaveFilePath(userDataPath);
846     SGPropertyNode autosave;
847     if (autosaveFile.exists()) {
848       SG_LOG(SG_INPUT, SG_INFO,
849              "Reading user settings from " << autosaveFile);
850       try {
851           readProperties(autosaveFile, &autosave, SGPropertyNode::USERARCHIVE);
852       } catch (sg_exception& e) {
853           SG_LOG(SG_INPUT, SG_WARN, "failed to read user settings:" << e.getMessage()
854             << "(from " << e.getOrigin() << ")");
855       }
856     } else {
857         tryAutosaveMigration(userDataPath, &autosave);
858     }
859     /* Before 2020-03-10, we could save portions of the /ai/models/ tree, which
860     confuses things when loaded again. So delete any such items if they have
861     been loaded. */
862     SGPropertyNode* ai = autosave.getNode("ai");
863     if (ai) {
864         ai->removeChildren("models");
865     }
866     copyProperties(&autosave, globals->get_props());
867 }
868 
869 // Save user settings to the autosave file.
870 //
871 // When calling this method, make sure the value of 'userDataPath' is
872 // trustworthy. In particular, make sure it can't be influenced by Nasal code,
873 // not even indirectly via a Nasal-writable place such as the property tree.
874 //
875 // Note: the default value, which causes the autosave file to be written to
876 //       $FG_HOME, is safe---if not, it would be a bug.
877 void
saveUserSettings(SGPath userDataPath)878 FGGlobals::saveUserSettings(SGPath userDataPath)
879 {
880     if (userDataPath.isNull()) userDataPath = get_fg_home();
881 
882     // only save settings when we have (tried) to load the previous
883     // settings (otherwise user data was lost)
884     if (!haveUserSettings)
885         return;
886 
887     if (fgGetBool("/sim/startup/save-on-exit")) {
888       // don't save settings more than once on shutdown
889       haveUserSettings = false;
890 
891       SGPath autosaveFile = autosaveFilePath(userDataPath);
892       autosaveFile.create_dir( 0700 );
893       SG_LOG(SG_IO, SG_INFO, "Saving user settings to " << autosaveFile);
894       try {
895         writeProperties(autosaveFile, globals->get_props(), false, SGPropertyNode::USERARCHIVE);
896       } catch (const sg_exception &e) {
897         guiErrorMessage("Error writing autosave:", e);
898       }
899       SG_LOG(SG_INPUT, SG_DEBUG, "Finished Saving user settings");
900     }
901 }
902 
get_warp() const903 long int FGGlobals::get_warp() const
904 {
905   return fgGetInt("/sim/time/warp");
906 }
907 
set_warp(long int w)908 void FGGlobals::set_warp( long int w )
909 {
910   fgSetInt("/sim/time/warp", w);
911 }
912 
get_warp_delta() const913 long int FGGlobals::get_warp_delta() const
914 {
915   return fgGetInt("/sim/time/warp-delta");
916 }
917 
set_warp_delta(long int d)918 void FGGlobals::set_warp_delta( long int d )
919 {
920   fgSetInt("/sim/time/warp-delta", d);
921 }
922 
get_scenery() const923 FGScenery* FGGlobals::get_scenery () const
924 {
925     return get_subsystem<FGScenery>();
926 }
927 
get_viewmgr() const928 FGViewMgr *FGGlobals::get_viewmgr() const
929 {
930     return get_subsystem<FGViewMgr>();
931 }
932 
get_current_view() const933 flightgear::View* FGGlobals::get_current_view () const
934 {
935     FGViewMgr* vm = get_viewmgr();
936     return vm ? vm->get_current_view() : 0;
937 }
938 
set_matlib(SGMaterialLib * m)939 void FGGlobals::set_matlib( SGMaterialLib *m )
940 {
941     matlib = m;
942 }
943 
get_controls() const944 FGControls *FGGlobals::get_controls() const
945 {
946     return get_subsystem<FGControls>();
947 }
948 
addListenerToCleanup(SGPropertyChangeListener * l)949 void FGGlobals::addListenerToCleanup(SGPropertyChangeListener* l)
950 {
951     _listeners_to_cleanup.push_back(l);
952 }
953 
cleanupListeners()954 void FGGlobals::cleanupListeners()
955 {
956     SGPropertyChangeListenerVec::iterator i = _listeners_to_cleanup.begin();
957     for (; i != _listeners_to_cleanup.end(); ++i) {
958         delete *i;
959     }
960     _listeners_to_cleanup.clear();
961 
962     simgear::AtomicChangeListener::clearPendingChanges();
963 }
964 
packageRoot()965 simgear::pkg::Root* FGGlobals::packageRoot()
966 {
967   return _packageRoot.get();
968 }
969 
setPackageRoot(const SGSharedPtr<simgear::pkg::Root> & p)970 void FGGlobals::setPackageRoot(const SGSharedPtr<simgear::pkg::Root>& p)
971 {
972   _packageRoot = p;
973 }
974 
is_headless()975 bool FGGlobals::is_headless()
976 {
977     return flightgear::isHeadlessMode();
978 }
979 
set_headless(bool mode)980 void FGGlobals::set_headless(bool mode)
981 {
982     flightgear::setHeadlessMode(mode);
983 }
984 
985 // end of globals.cxx
986