28#include <QMutexLocker>
38#include <simgear/misc/ResourceManager.hxx>
39#include <simgear/props/props_io.hxx>
40#include <simgear/structure/exception.hxx>
46QDataStream&
operator<<(QDataStream& ds,
const AircraftItem::LocalizedStrings& ls)
48 ds << ls.locale << ls.strings;
52QDataStream&
operator>>(QDataStream& ds, AircraftItem::LocalizedStrings& ls)
54 ds >> ls.locale >> ls.strings;
61 readProperties(filePath.toStdString(), &root);
63 if (!root.hasChild(
"sim")) {
64 qWarning() <<
"-set.xml has no <sim> element" << filePath;
68 SGPropertyNode_ptr sim = root.getNode(
"sim");
72 if (sim->getBoolValue(
"exclude-from-gui",
false)) {
79 ls.strings[
"name"] = QString::fromStdString(sim->getStringValue(
"description")).trimmed();
80 authors = QString::fromStdString(sim->getStringValue(
"author"));
82 if (sim->hasChild(
"rating")) {
83 SGPropertyNode_ptr ratingsNode = sim->getNode(
"rating");
84 for (
int i=0;
i< 4; ++
i) {
89 if (sim->hasChild(
"long-description")) {
91 ls.strings[
"desc"] = QString::fromStdString(sim->getStringValue(
"long-description")).simplified();
94 if (sim->hasChild(
"variant-of")) {
95 variantOf = QString::fromStdString(sim->getStringValue(
"variant-of"));
100 if (sim->hasChild(
"primary-set")) {
101 isPrimary = sim->getBoolValue(
"primary-set");
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();
118 tags.push_back(QString::fromStdString(tagName));
123 if (sim->hasChild(
"previews")) {
124 SGPropertyNode_ptr previewsNode = sim->getChild(
"previews");
125 const QString aircraftPrefix =
"Aircraft/" + dir.dirName() +
"/";
127 for (
auto previewNode : previewsNode->getChildren(
"preview")) {
129 QString pathInXml = QString::fromStdString(previewNode->getStringValue(
"path"));
134 if (pathInXml.startsWith(aircraftPrefix)) {
135 pathInXml = pathInXml.mid(aircraftPrefix.length());
138 QString previewPath = dir.absoluteFilePath(pathInXml);
140 if (!QFile::exists(previewPath)) {
141 qWarning() <<
"Missing local preview file" << previewPath;
145 previews.append(QUrl::fromLocalFile(previewPath));
150 if (sim->hasChild(
"thumbnail")) {
151 thumbnailPath = QString::fromStdString(sim->getStringValue(
"thumbnail"));
156 if (sim->hasChild(
"minimum-fg-version")) {
157 minFGVersion = QString::fromStdString(sim->getStringValue(
"minimum-fg-version"));
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")));
164 _localized.push_front(ls);
165 readLocalizedStrings(sim);
171void AircraftItem::readLocalizedStrings(SGPropertyNode_ptr simNode)
173 if (!simNode->hasChild(
"localized"))
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);
182 ls.locale = QString::fromStdString(c->getNameString());
183 if (c->hasChild(
"description")) {
184 ls.strings[
"name"] = QString::fromStdString(c->getStringValue(
"description"));
186 if (c->hasChild(
"long-description")) {
187 ls.strings[
"desc"] = QString::fromStdString(c->getStringValue(
"long-description")).simplified();
190 _localized.push_back(ls);
194void AircraftItem::doLocalizeStrings()
197 _currentStrings = _localized.front().strings;
201 auto it = std::find_if(_localized.begin(), _localized.end(), [lang](
const LocalizedStrings& ls) {
202 return ls.locale == lang;
205 if (it == _localized.end())
209 if (it->strings.contains(t)) {
211 _currentStrings[t] = it->strings.value(t);
218 return _currentStrings.value(
"name");
223 return _currentStrings.value(
"desc");
228 QString fn = QFileInfo(
path).fileName();
229 fn.truncate(fn.size() - 8);
273 const QString
path = uri.toLocalFile();
294 const int c = simgear::strutils::compare_versions(FLIGHTGEAR_VERSION,
303static std::unique_ptr<LocalAircraftCache> static_cacheInstance;
308class ScanDirProvider :
public simgear::ResourceProvider
311 ScanDirProvider() :
simgear::ResourceProvider(
simgear::ResourceManager::PRIORITY_NORMAL) {}
313 ~ScanDirProvider() =
default;
315 SGPath resolve(
const std::string& aResource, SGPath& aContext)
const override
319 SGPath ap = _currentAircraftPath / aResource;
324 if ((pieces.size() < 3) || (pieces.front() !=
"Aircraft")) {
328 const std::string res(aResource, 9);
329 SGPath
p = _currentScanPath / res;
336 void setCurrentPath(
const SGPath&
p)
338 _currentScanPath =
p;
341 void setCurrentAircraftPath(
const SGPath&
p)
343 _currentAircraftPath =
p;
347 SGPath _currentScanPath;
348 SGPath _currentAircraftPath;
351class OtherAircraftDirsProvider :
public simgear::ResourceProvider
354 OtherAircraftDirsProvider() : simgear::ResourceProvider(simgear::ResourceManager::PRIORITY_NORMAL) {}
356 ~OtherAircraftDirsProvider() =
default;
358 SGPath resolve(
const std::string& aResource, SGPath& aContext)
const override
360 if (aResource.find(
"Aircraft/") != 0) {
364 const std::string res(aResource, 9);
368 Q_FOREACH(
auto p, paths) {
369 const auto sp = SGPath::fromUtf8(
p.toUtf8().toStdString()) / res;
379class AircraftScanThread :
public QThread
383 AircraftScanThread(QStringList dirsToScan) :
387 auto rm = simgear::ResourceManager::instance();
388 m_currentScanDir.reset(
new ScanDirProvider);
389 rm->addProvider(m_currentScanDir.get());
392 ~AircraftScanThread()
394 simgear::ResourceManager::instance()->removeProvider(m_currentScanDir.get());
398 QVector<AircraftItemPtr> items()
400 QVector<AircraftItemPtr> result;
401 QMutexLocker
g(&m_lock);
402 result.swap(m_items);
403 Q_ASSERT(m_items.empty());
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));
439 QByteArray cacheData = settings.value(
"aircraft-cache").toByteArray();
440 if (!cacheData.isEmpty()) {
441 QDataStream ds(cacheData);
442 quint32 count, cacheVersion;
443 ds >> cacheVersion >> count;
449 for (quint32
i=0;
i<count; ++
i) {
451 item->fromDataStream(ds);
453 QFileInfo finfo(item->path);
454 if (finfo.exists() && (finfo.lastModified() == item->pathModTime)) {
457 m_cachedItems[item->path] = item;
466 QByteArray cacheData;
468 QDataStream ds(&cacheData, QIODevice::WriteOnly);
469 quint32 count =
static_cast<quint32
>(m_nextCache.count());
473 item->toDataStream(ds);
477 settings.setValue(
"aircraft-cache", cacheData);
480 void scanAircraftDir(QDir path)
486 filters <<
"*-set.xml";
487 Q_FOREACH(QFileInfo child, path.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot)) {
492 QDir childDir(child.absoluteFilePath());
493 QMap<QString, AircraftItemPtr> baseAircraft;
494 QList<AircraftItemPtr> variants;
498 const auto p = SGPath::fromUtf8(child.absoluteFilePath().toUtf8().toStdString());
499 m_currentScanDir->setCurrentAircraftPath(
p);
501 Q_FOREACH(QFileInfo xmlChild, childDir.entryInfoList(filters, QDir::Files)) {
503 QString absolutePath = xmlChild.absoluteFilePath();
508 if (m_cachedItems.contains(absolutePath)) {
509 item = m_cachedItems.value(absolutePath);
513 bool ok = item->initFromFile(childDir, absolutePath);
519 m_nextCache[absolutePath] = item;
521 if (item->excluded) {
525 if (item->isPrimary) {
526 baseAircraft.insert(item->baseName(), item);
528 variants.append(item);
530 }
catch (sg_exception& e) {
531 qWarning() <<
"Problems occurred while parsing" << xmlChild.absoluteFilePath() <<
"(skipping)"
532 <<
"\n\t" << QString::fromStdString(e.what());
543 if (!baseAircraft.contains(item->variantOf)) {
544 qWarning() <<
"can't find principal aircraft " << item->variantOf <<
" for variant:" << item->path;
548 baseAircraft.value(item->variantOf)->variants.append(item);
552 bool didAddItems =
false;
554 QMutexLocker
g(&m_lock);
555 m_items+=(baseAircraft.values().toVector());
556 didAddItems = !m_items.isEmpty();
569 QVector<AircraftItemPtr> m_items;
571 QMap<QString, AircraftItemPtr > m_cachedItems;
572 QMap<QString, AircraftItemPtr > m_nextCache;
575 std::unique_ptr<ScanDirProvider> m_currentScanDir;
592 if (!static_cacheInstance) {
593 static_cacheInstance.reset(
new LocalAircraftCache);
596 return static_cacheInstance.get();
601 static_cacheInstance.
reset();
604LocalAircraftCache::LocalAircraftCache() :
605 d(new AircraftCachePrivate)
607 d->m_otherDirsProvider.reset(
new OtherAircraftDirsProvider);
608 auto rm = simgear::ResourceManager::instance();
609 rm->addProvider(d->m_otherDirsProvider.get());
614 abandonCurrentScan();
615 if (simgear::ResourceManager::haveInstance()) {
616 simgear::ResourceManager::instance()->removeProvider(d->m_otherDirsProvider.get());
620 d->m_otherDirsProvider.release();
626 if (
paths == d->m_paths) {
642 abandonCurrentScan();
645 QStringList dirs = d->m_paths;
647 for (SGPath ap :
globals->get_aircraft_paths()) {
648 dirs << QString::fromStdString(ap.utf8Str());
651 SGPath rootAircraft =
globals->get_fg_root() /
"Aircraft";
652 dirs << QString::fromStdString(rootAircraft.utf8Str());
654 d->m_scanThread.reset(
new AircraftScanThread(dirs));
655 connect(d->m_scanThread.get(), &AircraftScanThread::finished,
this,
656 &LocalAircraftCache::onScanFinished);
660 connect(d->m_scanThread.get(), &AircraftScanThread::addedItems,
661 this, &LocalAircraftCache::onScanResults,
662 Qt::QueuedConnection);
663 d->m_scanThread->start();
670 return d->m_items.size();
680 return d->m_items.at(index);
685 QString path = aircraftUri.toLocalFile();
686 for (
int row=0; row < d->m_items.size(); ++row) {
688 if (item->path == path) {
693 for (
int vr=0; vr < item->variants.size(); ++vr) {
694 if (item->variants.at(vr)->path == path) {
705 if (!item || item->variantOf.isEmpty())
708 for (
int row=0; row < d->m_items.size(); ++row) {
710 if (
p->baseName() == item->variantOf) {
722 return d->m_items.at(index);
728void LocalAircraftCache::abandonCurrentScan()
730 if (d->m_scanThread) {
732 disconnect(d->m_scanThread.get(), &AircraftScanThread::finished,
this,
733 &LocalAircraftCache::onScanFinished);
735 d->m_scanThread->setDone();
736 if (!d->m_scanThread->wait(32000)) {
737 qWarning() << Q_FUNC_INFO <<
"scan thread failed to terminate";
739 d->m_scanThread.reset();
744void LocalAircraftCache::onScanResults()
746 if (!d->m_scanThread) {
750 QVector<AircraftItemPtr> newItems = d->m_scanThread->items();
751 if (newItems.isEmpty())
754 d->m_items+=newItems;
758void LocalAircraftCache::onScanFinished()
760 d->m_scanThread.reset();
767 filters <<
"*-set.xml";
775 Q_FOREACH(QFileInfo child, d.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot)) {
776 QDir childDir(child.absoluteFilePath());
778 Q_FOREACH(QFileInfo xmlChild, childDir.entryInfoList(filters, QDir::Files)) {
782 if ((setXmlCount > 0) || (dirCount > 10)) {
787 return (setXmlCount > 0);
792 const char* names[] = {
"FDM",
"systems",
"cockpit",
"model"};
793 assert((ratingIndex >= 0) && (ratingIndex < 4));
794 return node->getIntValue(names[ratingIndex]);
803 if (d->m_scanThread) {
807 auto rm = simgear::ResourceManager::instance();
811 std::unique_ptr<ScanDirProvider> dp{
new ScanDirProvider};
812 rm->addProvider(dp.get());
820 const SGPath aircraftDirPath = setPath.dirPath().dirPath();
821 dp->setCurrentPath(aircraftDirPath);
823 dp->setCurrentAircraftPath(setPath);
828 readProperties(setPath, props);
830 }
catch (sg_exception& e) {
832 qWarning() <<
"Problems occurred while parsing" << QString::fromStdString(setPath.utf8Str())
833 <<
"\n\t" << QString::fromStdString(e.getFormattedMessage());
836 rm->removeProvider(dp.get());
840#include "LocalAircraftCache.moc"
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
std::string getPreferredLanguage() const
Return the preferred language according to user choice and/or settings.
QVector< AircraftItemPtr > m_items
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
@ AircraftNeedsNewerSimulator
std::vector< std::string > string_list
FlightGear Localization Support.
void addSentryBreadcrumb(const std::string &, const std::string &)
int indexOfVariant(QUrl uri) 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