FlightGear next
LocalAircraftCache.cxx
Go to the documentation of this file.
1// Written by James Turner, started October 2017
2//
3// Copyright (C) 2017 James Turner <zakalawe@mac.com>
4//
5// This program is free software; you can redistribute it and/or
6// modify it under the terms of the GNU General Public License as
7// published by the Free Software Foundation; either version 2 of the
8// License, or (at your option) any later version.
9//
10// This program is distributed in the hope that it will be useful, but
11// WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13// General Public License for more details.
14//
15// You should have received a copy of the GNU General Public License
16// along with this program; if not, write to the Free Software
17// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18
19#include "config.h"
20
22
23#include <cassert>
24
25#include <QDir>
26#include <QThread>
27#include <QMutex>
28#include <QMutexLocker>
29#include <QDataStream>
30#include <QMap>
31#include <QSettings>
32#include <QDebug>
33
34#include <Main/globals.hxx>
35#include <Main/locale.hxx>
37
38#include <simgear/misc/ResourceManager.hxx>
39#include <simgear/props/props_io.hxx>
40#include <simgear/structure/exception.hxx>
41
42static quint32 CACHE_VERSION = 13;
43
44const std::vector<QByteArray> static_localizedStringTags = {"name", "desc"};
45
46QDataStream& operator<<(QDataStream& ds, const AircraftItem::LocalizedStrings& ls)
47{
48 ds << ls.locale << ls.strings;
49 return ds;
50}
51
52QDataStream& operator>>(QDataStream& ds, AircraftItem::LocalizedStrings& ls)
53{
54 ds >> ls.locale >> ls.strings;
55 return ds;
56}
57
58bool AircraftItem::initFromFile(QDir dir, QString filePath)
59{
60 SGPropertyNode root;
61 readProperties(filePath.toStdString(), &root);
62
63 if (!root.hasChild("sim")) {
64 qWarning() << "-set.xml has no <sim> element" << filePath;
65 return false;
66 }
67
68 SGPropertyNode_ptr sim = root.getNode("sim");
69
70 path = filePath;
71 pathModTime = QFileInfo(path).lastModified();
72 if (sim->getBoolValue("exclude-from-gui", false)) {
73 excluded = true;
74 return false;
75 }
76
77 LocalizedStrings ls;
78 ls.locale = "en";
79 ls.strings["name"] = QString::fromStdString(sim->getStringValue("description")).trimmed();
80 authors = QString::fromStdString(sim->getStringValue("author"));
81
82 if (sim->hasChild("rating")) {
83 SGPropertyNode_ptr ratingsNode = sim->getNode("rating");
84 for (int i=0; i< 4; ++i) {
86 }
87 }
88
89 if (sim->hasChild("long-description")) {
90 // clean up any XML whitspace in the text.
91 ls.strings["desc"] = QString::fromStdString(sim->getStringValue("long-description")).simplified();
92 }
93
94 if (sim->hasChild("variant-of")) {
95 variantOf = QString::fromStdString(sim->getStringValue("variant-of"));
96 } else {
97 isPrimary = true;
98 }
99
100 if (sim->hasChild("primary-set")) {
101 isPrimary = sim->getBoolValue("primary-set");
102 }
103
104 if (sim->hasChild("tags")) {
105 SGPropertyNode_ptr tagsNode = sim->getChild("tags");
106 int nChildren = tagsNode->nChildren();
107 for (int i = 0; i < nChildren; i++) {
108 const SGPropertyNode* c = tagsNode->getChild(i);
109 if (c->getNameString() == "tag") {
110 std::string tagName = c->getStringValue();
111 usesHeliports |= (tagName == "helicopter");
112 // could also consider vtol tag?
113 usesSeaports |= (tagName == "seaplane");
114 usesSeaports |= (tagName == "floats");
115 needsMaintenance |= (tagName == "needs-maintenance");
116
117 // and actually store the tags
118 tags.push_back(QString::fromStdString(tagName));
119 }
120 } // of tags iteration
121 } // of set-xml has tags
122
123 if (sim->hasChild("previews")) {
124 SGPropertyNode_ptr previewsNode = sim->getChild("previews");
125 const QString aircraftPrefix = "Aircraft/" + dir.dirName() + "/";
126
127 for (auto previewNode : previewsNode->getChildren("preview")) {
128 // add file path as url
129 QString pathInXml = QString::fromStdString(previewNode->getStringValue("path"));
130
131 // https://sourceforge.net/p/flightgear/codetickets/2644/
132 // allow Aircraft/ prefixed paths in previews; what we're actually doing here
133 // is trimming them back to the style we expect
134 if (pathInXml.startsWith(aircraftPrefix)) {
135 pathInXml = pathInXml.mid(aircraftPrefix.length());
136 }
137
138 QString previewPath = dir.absoluteFilePath(pathInXml);
139
140 if (!QFile::exists(previewPath)) {
141 qWarning() << "Missing local preview file" << previewPath;
142 continue;
143 }
144
145 previews.append(QUrl::fromLocalFile(previewPath));
146
147 }
148 }
149
150 if (sim->hasChild("thumbnail")) {
151 thumbnailPath = QString::fromStdString(sim->getStringValue("thumbnail"));
152 } else {
153 thumbnailPath = "thumbnail.jpg";
154 }
155
156 if (sim->hasChild("minimum-fg-version")) {
157 minFGVersion = QString::fromStdString(sim->getStringValue("minimum-fg-version"));
158 }
159
160 homepageUrl = QUrl(QString::fromStdString(sim->getStringValue("urls/home-page")));
161 supportUrl = QUrl(QString::fromStdString(sim->getStringValue("urls/support")));
162 wikipediaUrl = QUrl(QString::fromStdString(sim->getStringValue("urls/wikipedia")));
163
164 _localized.push_front(ls);
165 readLocalizedStrings(sim);
166 doLocalizeStrings();
167
168 return true;
169}
170
171void AircraftItem::readLocalizedStrings(SGPropertyNode_ptr simNode)
172{
173 if (!simNode->hasChild("localized"))
174 return;
175
176 auto localeNode = simNode->getChild("localized");
177 const auto num = localeNode->nChildren();
178 for (int i = 0; i < num; i++) {
179 const SGPropertyNode* c = localeNode->getChild(i);
180
181 LocalizedStrings ls;
182 ls.locale = QString::fromStdString(c->getNameString());
183 if (c->hasChild("description")) {
184 ls.strings["name"] = QString::fromStdString(c->getStringValue("description"));
185 }
186 if (c->hasChild("long-description")) {
187 ls.strings["desc"] = QString::fromStdString(c->getStringValue("long-description")).simplified();
188 }
189
190 _localized.push_back(ls);
191 }
192}
193
194void AircraftItem::doLocalizeStrings()
195{
196 // default strings are always at the front
197 _currentStrings = _localized.front().strings;
198
199 const auto lang = QString::fromStdString(globals->get_locale()->getPreferredLanguage());
200 // find the matching locale
201 auto it = std::find_if(_localized.begin(), _localized.end(), [lang](const LocalizedStrings& ls) {
202 return ls.locale == lang;
203 });
204
205 if (it == _localized.end())
206 return; // nothing else to do
207
208 for (auto t : static_localizedStringTags) {
209 if (it->strings.contains(t)) {
210 // copy the value we found
211 _currentStrings[t] = it->strings.value(t);
212 }
213 } // of strings iteration
214}
215
216QString AircraftItem::name() const
217{
218 return _currentStrings.value("name");
219}
220
222{
223 return _currentStrings.value("desc");
224}
225
227{
228 QString fn = QFileInfo(path).fileName();
229 fn.truncate(fn.size() - 8);
230 return fn;
231}
232
233void AircraftItem::fromDataStream(QDataStream& ds)
234{
235 ds >> path >> pathModTime >> excluded;
236 if (excluded) {
237 return;
238 }
239
240 ds >> authors >> variantOf >> isPrimary;
241 for (int i=0; i<4; ++i) ds >> ratings[i];
242 ds >> previews;
243 ds >> thumbnailPath;
244 ds >> minFGVersion;
247 ds >> tags;
248 ds >> _localized;
249
250 doLocalizeStrings();
251}
252
253void AircraftItem::toDataStream(QDataStream& ds) const
254{
255 ds << path << pathModTime << excluded;
256 if (excluded) {
257 return;
258 }
259
260 ds << authors << variantOf << isPrimary;
261 for (int i=0; i<4; ++i) ds << ratings[i];
262 ds << previews;
263 ds << thumbnailPath;
264 ds << minFGVersion;
267 ds << tags;
268 ds << _localized;
269}
270
272{
273 const QString path = uri.toLocalFile();
274 for (int i=0; i< variants.size(); ++i) {
275 if (variants.at(i)->path == path) {
276 return i;
277 }
278 }
279
280 return -1;
281}
282
283QVariant AircraftItem::status(int variant)
284{
285 Q_UNUSED(variant)
286 if (needsMaintenance) {
288 }
289
290 if (minFGVersion.isEmpty()) {
292 }
293
294 const int c = simgear::strutils::compare_versions(FLIGHTGEAR_VERSION,
295 minFGVersion.toStdString(), 2);
298
299}
300
301namespace {
302
303static std::unique_ptr<LocalAircraftCache> static_cacheInstance;
304
305
306// ensure references to Aircraft/foo and <my-aircraft-dir>/foo are resolved. This happens when
307// aircraft reference a path (probably to themselves) in their -set.xml
308class ScanDirProvider : public simgear::ResourceProvider
309{
310public:
311 ScanDirProvider() : simgear::ResourceProvider(simgear::ResourceManager::PRIORITY_NORMAL) {}
312
313 ~ScanDirProvider() = default;
314
315 SGPath resolve(const std::string& aResource, SGPath& aContext) const override
316 {
317 Q_UNUSED(aContext)
318
319 SGPath ap = _currentAircraftPath / aResource;
320 if (ap.exists())
321 return ap;
322
323 string_list pieces(sgPathBranchSplit(aResource));
324 if ((pieces.size() < 3) || (pieces.front() != "Aircraft")) {
325 return SGPath{}; // not an Aircraft path
326 }
327
328 const std::string res(aResource, 9); // resource path with 'Aircraft/' removed
329 SGPath p = _currentScanPath / res;
330 if (p.exists())
331 return p;
332
333 return SGPath{};
334 }
335
336 void setCurrentPath(const SGPath& p)
337 {
338 _currentScanPath = p;
339 }
340
341 void setCurrentAircraftPath(const SGPath& p)
342 {
343 _currentAircraftPath = p;
344 }
345
346private:
347 SGPath _currentScanPath;
348 SGPath _currentAircraftPath;
349};
350
351class OtherAircraftDirsProvider : public simgear::ResourceProvider
352{
353public:
354 OtherAircraftDirsProvider() : simgear::ResourceProvider(simgear::ResourceManager::PRIORITY_NORMAL) {}
355
356 ~OtherAircraftDirsProvider() = default;
357
358 SGPath resolve(const std::string& aResource, SGPath& aContext) const override
359 {
360 if (aResource.find("Aircraft/") != 0) {
361 return SGPath{}; // not an aircraft path
362 }
363
364 const std::string res(aResource, 9); // resource path with 'Aircraft/' removed
365
366 Q_UNUSED(aContext)
367 QStringList paths = LocalAircraftCache::instance()->paths();
368 Q_FOREACH(auto p, paths) {
369 const auto sp = SGPath::fromUtf8(p.toUtf8().toStdString()) / res;
370 // qWarning() << "OADP: trying:" << QString::fromStdString(sp.utf8Str());
371 if (sp.exists())
372 return sp;
373 }
374
375 return SGPath{};
376 }
377};
378
379class AircraftScanThread : public QThread
380{
381 Q_OBJECT
382public:
383 AircraftScanThread(QStringList dirsToScan) :
384 m_dirs(dirsToScan),
385 m_done(false)
386 {
387 auto rm = simgear::ResourceManager::instance();
388 m_currentScanDir.reset(new ScanDirProvider);
389 rm->addProvider(m_currentScanDir.get());
390 }
391
392 ~AircraftScanThread()
393 {
394 simgear::ResourceManager::instance()->removeProvider(m_currentScanDir.get());
395 }
396
398 QVector<AircraftItemPtr> items()
399 {
400 QVector<AircraftItemPtr> result;
401 QMutexLocker g(&m_lock);
402 result.swap(m_items);
403 Q_ASSERT(m_items.empty());
404 g.unlock();
405 return result;
406 }
407
408 void setDone()
409 {
410 m_done = true;
411 }
412
413Q_SIGNALS:
414 void addedItems();
415
416protected:
417 void run() override
418 {
419 flightgear::addSentryBreadcrumb("AircraftScan started", "info");
420 readCache();
421
422 Q_FOREACH(QString d, m_dirs) {
423 const auto p = SGPath::fromUtf8(d.toUtf8().toStdString());
424 m_currentScanDir->setCurrentPath(p);
425 scanAircraftDir(QDir(d));
426 if (m_done) {
427 return;
428 }
429 }
430
431 writeCache();
432 flightgear::addSentryBreadcrumb("AircraftScan finished", "info");
433 }
434
435private:
436 void readCache()
437 {
438 QSettings settings;
439 QByteArray cacheData = settings.value("aircraft-cache").toByteArray();
440 if (!cacheData.isEmpty()) {
441 QDataStream ds(cacheData);
442 quint32 count, cacheVersion;
443 ds >> cacheVersion >> count;
444
445 if (cacheVersion != CACHE_VERSION) {
446 return; // mis-matched cache, version, drop
447 }
448
449 for (quint32 i=0; i<count; ++i) {
450 AircraftItemPtr item(new AircraftItem);
451 item->fromDataStream(ds);
452
453 QFileInfo finfo(item->path);
454 if (finfo.exists() && (finfo.lastModified() == item->pathModTime)) {
455 // corresponding -set.xml file still exists and is
456 // unmodified
457 m_cachedItems[item->path] = item;
458 }
459 } // of cached item iteration
460 }
461 }
462
463 void writeCache()
464 {
465 QSettings settings;
466 QByteArray cacheData;
467 {
468 QDataStream ds(&cacheData, QIODevice::WriteOnly);
469 quint32 count = static_cast<quint32>(m_nextCache.count());
470 ds << CACHE_VERSION << count;
471
472 Q_FOREACH(AircraftItemPtr item, m_nextCache.values()) {
473 item->toDataStream(ds);
474 }
475 }
476
477 settings.setValue("aircraft-cache", cacheData);
478 }
479
480 void scanAircraftDir(QDir path)
481 {
482 //QTime t;
483 //t.start();
484
485 QStringList filters;
486 filters << "*-set.xml";
487 Q_FOREACH(QFileInfo child, path.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot)) {
488 if (m_done) { // thread termination bail-out
489 return;
490 }
491
492 QDir childDir(child.absoluteFilePath());
493 QMap<QString, AircraftItemPtr> baseAircraft;
494 QList<AircraftItemPtr> variants;
495
496 // ensure aircraft dir is available to simgear::ResourceProvider
497 // otherwise some aircraft -set.xml includes fail
498 const auto p = SGPath::fromUtf8(child.absoluteFilePath().toUtf8().toStdString());
499 m_currentScanDir->setCurrentAircraftPath(p);
500
501 Q_FOREACH(QFileInfo xmlChild, childDir.entryInfoList(filters, QDir::Files)) {
502 try {
503 QString absolutePath = xmlChild.absoluteFilePath();
504 AircraftItemPtr item;
505
506 // should we re-stat here? But we already did so when loading
507 // the cache and dropped any non-valid entries
508 if (m_cachedItems.contains(absolutePath)) {
509 item = m_cachedItems.value(absolutePath);
510 } else {
511 // scan the cached item
512 item = AircraftItemPtr(new AircraftItem);
513 bool ok = item->initFromFile(childDir, absolutePath);
514 if (!ok) {
515 continue;
516 }
517 }
518
519 m_nextCache[absolutePath] = item;
520
521 if (item->excluded) {
522 continue;
523 }
524
525 if (item->isPrimary) {
526 baseAircraft.insert(item->baseName(), item);
527 } else {
528 variants.append(item);
529 }
530 } catch (sg_exception& e) {
531 qWarning() << "Problems occurred while parsing" << xmlChild.absoluteFilePath() << "(skipping)"
532 << "\n\t" << QString::fromStdString(e.what());
533 continue;
534 }
535
536 if (m_done) { // thread termination bail-out
537 return;
538 }
539 } // of set.xml iteration
540
541 // bind variants to their principals
542 Q_FOREACH(AircraftItemPtr item, variants) {
543 if (!baseAircraft.contains(item->variantOf)) {
544 qWarning() << "can't find principal aircraft " << item->variantOf << " for variant:" << item->path;
545 continue;
546 }
547
548 baseAircraft.value(item->variantOf)->variants.append(item);
549 }
550
551 // lock mutex while we modify the items array
552 bool didAddItems = false;
553 {
554 QMutexLocker g(&m_lock);
555 m_items+=(baseAircraft.values().toVector());
556 didAddItems = !m_items.isEmpty();
557 }
558
559 if (didAddItems) {
560 emit addedItems();
561 }
562 } // of subdir iteration
563
564 //qInfo() << "scanning" << path.absolutePath() << "took" << t.elapsed();
565 }
566
567 QMutex m_lock;
568 QStringList m_dirs;
569 QVector<AircraftItemPtr> m_items;
570
571 QMap<QString, AircraftItemPtr > m_cachedItems;
572 QMap<QString, AircraftItemPtr > m_nextCache;
573
574 bool m_done;
575 std::unique_ptr<ScanDirProvider> m_currentScanDir;
576};
577
578} // of anonymous namespace
579
580
582{
583public:
584 QStringList m_paths;
585 std::unique_ptr<AircraftScanThread> m_scanThread;
586 QVector<AircraftItemPtr> m_items;
587 std::unique_ptr<OtherAircraftDirsProvider> m_otherDirsProvider;
588};
589
590LocalAircraftCache* LocalAircraftCache::instance()
591{
592 if (!static_cacheInstance) {
593 static_cacheInstance.reset(new LocalAircraftCache);
594 }
595
596 return static_cacheInstance.get();
597}
598
600{
601 static_cacheInstance.reset();
602}
603
604LocalAircraftCache::LocalAircraftCache() :
605 d(new AircraftCachePrivate)
606{
607 d->m_otherDirsProvider.reset(new OtherAircraftDirsProvider);
608 auto rm = simgear::ResourceManager::instance();
609 rm->addProvider(d->m_otherDirsProvider.get());
610}
611
613{
614 abandonCurrentScan();
615 if (simgear::ResourceManager::haveInstance()) {
616 simgear::ResourceManager::instance()->removeProvider(d->m_otherDirsProvider.get());
617 } else {
618 // resource manager will already have destroyed the provider. Awkward
619 // ownership model :(
620 d->m_otherDirsProvider.release();
621 }
622}
623
625{
626 if (paths == d->m_paths) {
627 return;
628 }
629
630 d->m_items.clear();
631 emit cleared();
632 d->m_paths = paths;
633}
634
635QStringList LocalAircraftCache::paths() const
636{
637 return d->m_paths;
638}
639
641{
642 abandonCurrentScan();
643 d->m_items.clear();
644
645 QStringList dirs = d->m_paths;
646
647 for (SGPath ap : globals->get_aircraft_paths()) {
648 dirs << QString::fromStdString(ap.utf8Str());
649 }
650
651 SGPath rootAircraft = globals->get_fg_root() / "Aircraft";
652 dirs << QString::fromStdString(rootAircraft.utf8Str());
653
654 d->m_scanThread.reset(new AircraftScanThread(dirs));
655 connect(d->m_scanThread.get(), &AircraftScanThread::finished, this,
656 &LocalAircraftCache::onScanFinished);
657 // force a queued connection here since we the scan thread object still
658 // belongs to the same thread as us, and hence this would otherwise be
659 // a direct connection
660 connect(d->m_scanThread.get(), &AircraftScanThread::addedItems,
661 this, &LocalAircraftCache::onScanResults,
662 Qt::QueuedConnection);
663 d->m_scanThread->start();
664
665 emit scanStarted();
666}
667
669{
670 return d->m_items.size();
671}
672
673QVector<AircraftItemPtr> LocalAircraftCache::allItems() const
674{
675 return d->m_items;
676}
677
679{
680 return d->m_items.at(index);
681}
682
683int LocalAircraftCache::findIndexWithUri(QUrl aircraftUri) const
684{
685 QString path = aircraftUri.toLocalFile();
686 for (int row=0; row < d->m_items.size(); ++row) {
687 const AircraftItemPtr item(d->m_items.at(row));
688 if (item->path == path) {
689 return row;
690 }
691
692 // check variants too
693 for (int vr=0; vr < item->variants.size(); ++vr) {
694 if (item->variants.at(vr)->path == path) {
695 return row;
696 }
697 }
698 }
699
700 return -1;
701}
702
704{
705 if (!item || item->variantOf.isEmpty())
706 return item;
707
708 for (int row=0; row < d->m_items.size(); ++row) {
709 const AircraftItemPtr p(d->m_items.at(row));
710 if (p->baseName() == item->variantOf) {
711 return p;
712 }
713 }
714
715 return {};
716}
717
719{
720 int index = findIndexWithUri(aircraftUri);
721 if (index >= 0) {
722 return d->m_items.at(index);
723 }
724
725 return {};
726}
727
728void LocalAircraftCache::abandonCurrentScan()
729{
730 if (d->m_scanThread) {
731 // don't fire onScanFinished when the thread ends
732 disconnect(d->m_scanThread.get(), &AircraftScanThread::finished, this,
733 &LocalAircraftCache::onScanFinished);
734
735 d->m_scanThread->setDone();
736 if (!d->m_scanThread->wait(32000)) {
737 qWarning() << Q_FUNC_INFO << "scan thread failed to terminate";
738 }
739 d->m_scanThread.reset();
740 }
741}
742
743
744void LocalAircraftCache::onScanResults()
745{
746 if (!d->m_scanThread) {
747 return;
748 }
749
750 QVector<AircraftItemPtr> newItems = d->m_scanThread->items();
751 if (newItems.isEmpty())
752 return;
753
754 d->m_items+=newItems;
755 emit addedItems(newItems.size());
756}
757
758void LocalAircraftCache::onScanFinished()
759{
760 d->m_scanThread.reset();
761 emit scanCompleted();
762}
763
765{
766 QStringList filters;
767 filters << "*-set.xml";
768 // count of child dirs, if we visited more than ten children without
769 // finding a -set.xml file, let's assume we are done. This avoids an
770 // exhaustive search of huge directory trees
771 int dirCount = 0,
772 setXmlCount = 0;
773
774 QDir d(path);
775 Q_FOREACH(QFileInfo child, d.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot)) {
776 QDir childDir(child.absoluteFilePath());
777 ++dirCount;
778 Q_FOREACH(QFileInfo xmlChild, childDir.entryInfoList(filters, QDir::Files)) {
779 ++setXmlCount;
780 }
781
782 if ((setXmlCount > 0) || (dirCount > 10)) {
783 break;
784 }
785 }
786
787 return (setXmlCount > 0);
788}
789
790int LocalAircraftCache::ratingFromProperties(SGPropertyNode* node, int ratingIndex)
791{
792 const char* names[] = {"FDM", "systems", "cockpit", "model"};
793 assert((ratingIndex >= 0) && (ratingIndex < 4));
794 return node->getIntValue(names[ratingIndex]);
795}
796
798LocalAircraftCache::readAircraftProperties(const SGPath &setPath, SGPropertyNode_ptr props)
799{
800 // it woudld be race-y to touch the reosurce provider while the scan thread is running
801 // and our provider would confuse current-aircraft-dir lookups as well. Since we
802 // can't do thread-specific reosurce providers, we just bail here.
803 if (d->m_scanThread) {
805 }
806
807 auto rm = simgear::ResourceManager::instance();
808
809 // ensure aircraft relative paths in the -set.xml parsing work
810
811 std::unique_ptr<ScanDirProvider> dp{new ScanDirProvider};
812 rm->addProvider(dp.get());
813
814 // we want to know the aircraft directory the aircraft lives in. This might
815 // be a manually added path (for local aircraft) or the install dir for the
816 // hangar (for packaged aircraft). Becuase -set.xml files are always found at
817 // /some/path/foobarAircraft/<aircraft-name>/some-set.xml, and the path we
818 // we want here is /some/path/foodbarAircraft, we use dirPath twice, and this
819 // works any kind of installed aircraft
820 const SGPath aircraftDirPath = setPath.dirPath().dirPath();
821 dp->setCurrentPath(aircraftDirPath);
822
823 dp->setCurrentAircraftPath(setPath);
824
826
827 try {
828 readProperties(setPath, props);
829 result = ParseSetXMLResult::Ok;
830 } catch (sg_exception& e) {
831 // malformed include or XML
832 qWarning() << "Problems occurred while parsing" << QString::fromStdString(setPath.utf8Str())
833 << "\n\t" << QString::fromStdString(e.getFormattedMessage());
834 }
835
836 rm->removeProvider(dp.get());
837 return result;
838}
839
840#include "LocalAircraftCache.moc"
#define p(x)
const std::vector< QByteArray > static_localizedStringTags
QDataStream & operator<<(QDataStream &ds, const AircraftItem::LocalizedStrings &ls)
QDataStream & operator>>(QDataStream &ds, AircraftItem::LocalizedStrings &ls)
static quint32 CACHE_VERSION
QSharedPointer< AircraftItem > AircraftItemPtr
#define i(x)
FGLocale * get_locale()
Definition globals.hxx:328
std::string getPreferredLanguage() const
Return the preferred language according to user choice and/or settings.
Definition locale.cxx:450
std::unique_ptr< OtherAircraftDirsProvider > m_otherDirsProvider
std::unique_ptr< AircraftScanThread > m_scanThread
ParseSetXMLResult readAircraftProperties(const SGPath &path, SGPropertyNode_ptr props)
readAircraftProperties - helper to parse a -set.xml, but with the correct path setup (root,...
AircraftItemPtr itemAt(int index) const
static int ratingFromProperties(SGPropertyNode *node, int ratingIndex)
static bool isCandidateAircraftPath(QString path)
@helper to determine if a particular path is likely to contain aircraft or not.
QStringList paths() const
@ Retry
aircraft scan in progress, try again later
void addedItems(int count)
AircraftItemPtr primaryItemFor(AircraftItemPtr item) const
QVector< AircraftItemPtr > allItems() const
int findIndexWithUri(QUrl aircraftUri) const
void setPaths(QStringList paths)
static LocalAircraftCache * instance()
AircraftItemPtr findItemWithUri(QUrl aircraftUri) const
FGGlobals * globals
Definition globals.cxx:142
std::vector< std::string > string_list
Definition globals.hxx:36
FlightGear Localization Support.
const double g(9.80665)
void addSentryBreadcrumb(const std::string &, const std::string &)
int indexOfVariant(QUrl uri) const
QList< QUrl > previews
QString name() const
QVariant status(int variant)
bool initFromFile(QDir dir, QString filePath)
void toDataStream(QDataStream &ds) const
QList< AircraftItemPtr > variants
void fromDataStream(QDataStream &ds)
QString description() const
QString baseName() const