1
0
Fork 0

Launcher: overhaul local -set.xml parsing

Fix the include context when -set.xml are parsed by the launcher to
extract states and other data. Ensure aircraft paths are available
and handled consistently with normal -set.xml parsing. Without this,
many -set.xmls fail to parse.

Sentry-Id: FLIGHTGEAR-4H
Sentry-Id: FLIGHTGEAR-3B
Sentry-Id: FLIGHTGEAR-3R
Sentry-Id: FLIGHTGEAR-3Q
Sentry-Id: FLIGHTGEAR-3F
Sentry-Id: FLIGHTGEAR-2R
Sentry-Id: FLIGHTGEAR-65
Sentry-Id: FLIGHTGEAR-42
This commit is contained in:
Automatic Release Builder 2020-10-06 11:39:21 +01:00 committed by James Turner
parent 2b54078023
commit 582d539a1a
4 changed files with 238 additions and 98 deletions

View file

@ -277,6 +277,11 @@ QVariant AircraftItem::status(int variant)
}
namespace {
static std::unique_ptr<LocalAircraftCache> static_cacheInstance;
// ensure references to Aircraft/foo and <my-aircraft-dir>/foo are resolved. This happens when
// aircraft reference a path (probably to themselves) in their -set.xml
class ScanDirProvider : public simgear::ResourceProvider
@ -322,6 +327,34 @@ private:
SGPath _currentAircraftPath;
};
class OtherAircraftDirsProvider : public simgear::ResourceProvider
{
public:
OtherAircraftDirsProvider() : simgear::ResourceProvider(simgear::ResourceManager::PRIORITY_NORMAL) {}
~OtherAircraftDirsProvider() = default;
SGPath resolve(const std::string& aResource, SGPath& aContext) const override
{
if (aResource.find("Aircraft/") != 0) {
return SGPath{}; // not an aircraft path
}
const std::string res(aResource, 9); // resource path with 'Aircraft/' removed
Q_UNUSED(aContext)
QStringList paths = LocalAircraftCache::instance()->paths();
Q_FOREACH(auto p, paths) {
const auto sp = SGPath::fromUtf8(p.toUtf8().toStdString()) / res;
// qWarning() << "OADP: trying:" << QString::fromStdString(sp.utf8Str());
if (sp.exists())
return sp;
}
return SGPath{};
}
};
class AircraftScanThread : public QThread
{
Q_OBJECT
@ -511,7 +544,17 @@ private:
std::unique_ptr<ScanDirProvider> m_currentScanDir;
};
static std::unique_ptr<LocalAircraftCache> static_cacheInstance;
} // of anonymous namespace
class LocalAircraftCache::AircraftCachePrivate
{
public:
QStringList m_paths;
std::unique_ptr<AircraftScanThread> m_scanThread;
QVector<AircraftItemPtr> m_items;
std::unique_ptr<OtherAircraftDirsProvider> m_otherDirsProvider;
};
LocalAircraftCache* LocalAircraftCache::instance()
{
@ -522,33 +565,48 @@ LocalAircraftCache* LocalAircraftCache::instance()
return static_cacheInstance.get();
}
LocalAircraftCache::LocalAircraftCache()
LocalAircraftCache::LocalAircraftCache() :
d(new AircraftCachePrivate)
{
d->m_otherDirsProvider.reset(new OtherAircraftDirsProvider);
auto rm = simgear::ResourceManager::instance();
rm->addProvider(d->m_otherDirsProvider.get());
}
LocalAircraftCache::~LocalAircraftCache()
{
abandonCurrentScan();
if (simgear::ResourceManager::haveInstance()) {
simgear::ResourceManager::instance()->removeProvider(d->m_otherDirsProvider.get());
} else {
// resource manager will already have destroyed the provider. Awkward
// ownership model :(
d->m_otherDirsProvider.release();
}
}
void LocalAircraftCache::setPaths(QStringList paths)
{
if (paths == m_paths) {
if (paths == d->m_paths) {
return;
}
m_items.clear();
d->m_items.clear();
emit cleared();
m_paths = paths;
d->m_paths = paths;
}
QStringList LocalAircraftCache::paths() const
{
return d->m_paths;
}
void LocalAircraftCache::scanDirs()
{
abandonCurrentScan();
m_items.clear();
d->m_items.clear();
QStringList dirs = m_paths;
QStringList dirs = d->m_paths;
for (SGPath ap : globals->get_aircraft_paths()) {
dirs << QString::fromStdString(ap.utf8Str());
@ -557,40 +615,40 @@ void LocalAircraftCache::scanDirs()
SGPath rootAircraft = globals->get_fg_root() / "Aircraft";
dirs << QString::fromStdString(rootAircraft.utf8Str());
m_scanThread.reset(new AircraftScanThread(dirs));
connect(m_scanThread.get(), &AircraftScanThread::finished, this,
d->m_scanThread.reset(new AircraftScanThread(dirs));
connect(d->m_scanThread.get(), &AircraftScanThread::finished, this,
&LocalAircraftCache::onScanFinished);
// force a queued connection here since we the scan thread object still
// belongs to the same thread as us, and hence this would otherwise be
// a direct connection
connect(m_scanThread.get(), &AircraftScanThread::addedItems,
connect(d->m_scanThread.get(), &AircraftScanThread::addedItems,
this, &LocalAircraftCache::onScanResults,
Qt::QueuedConnection);
m_scanThread->start();
d->m_scanThread->start();
emit scanStarted();
}
int LocalAircraftCache::itemCount() const
{
return m_items.size();
return d->m_items.size();
}
QVector<AircraftItemPtr> LocalAircraftCache::allItems() const
{
return m_items;
return d->m_items;
}
AircraftItemPtr LocalAircraftCache::itemAt(int index) const
{
return m_items.at(index);
return d->m_items.at(index);
}
int LocalAircraftCache::findIndexWithUri(QUrl aircraftUri) const
{
QString path = aircraftUri.toLocalFile();
for (int row=0; row < m_items.size(); ++row) {
const AircraftItemPtr item(m_items.at(row));
for (int row=0; row < d->m_items.size(); ++row) {
const AircraftItemPtr item(d->m_items.at(row));
if (item->path == path) {
return row;
}
@ -611,8 +669,8 @@ AircraftItemPtr LocalAircraftCache::primaryItemFor(AircraftItemPtr item) const
if (!item || item->variantOf.isEmpty())
return item;
for (int row=0; row < m_items.size(); ++row) {
const AircraftItemPtr p(m_items.at(row));
for (int row=0; row < d->m_items.size(); ++row) {
const AircraftItemPtr p(d->m_items.at(row));
if (p->baseName() == item->variantOf) {
return p;
}
@ -625,7 +683,7 @@ AircraftItemPtr LocalAircraftCache::findItemWithUri(QUrl aircraftUri) const
{
int index = findIndexWithUri(aircraftUri);
if (index >= 0) {
return m_items.at(index);
return d->m_items.at(index);
}
return {};
@ -633,37 +691,37 @@ AircraftItemPtr LocalAircraftCache::findItemWithUri(QUrl aircraftUri) const
void LocalAircraftCache::abandonCurrentScan()
{
if (m_scanThread) {
if (d->m_scanThread) {
// don't fire onScanFinished when the thread ends
disconnect(m_scanThread.get(), &AircraftScanThread::finished, this,
disconnect(d->m_scanThread.get(), &AircraftScanThread::finished, this,
&LocalAircraftCache::onScanFinished);
m_scanThread->setDone();
if (!m_scanThread->wait(2000)) {
d->m_scanThread->setDone();
if (!d->m_scanThread->wait(2000)) {
qWarning() << Q_FUNC_INFO << "scan thread failed to terminate";
}
m_scanThread.reset();
d->m_scanThread.reset();
}
}
void LocalAircraftCache::onScanResults()
{
if (!m_scanThread) {
if (!d->m_scanThread) {
return;
}
QVector<AircraftItemPtr> newItems = m_scanThread->items();
QVector<AircraftItemPtr> newItems = d->m_scanThread->items();
if (newItems.isEmpty())
return;
m_items+=newItems;
d->m_items+=newItems;
emit addedItems(newItems.size());
}
void LocalAircraftCache::onScanFinished()
{
m_scanThread.reset();
d->m_scanThread.reset();
emit scanCompleted();
}
@ -700,4 +758,46 @@ int LocalAircraftCache::ratingFromProperties(SGPropertyNode* node, int ratingInd
return node->getIntValue(names[ratingIndex]);
}
LocalAircraftCache::ParseSetXMLResult
LocalAircraftCache::readAircraftProperties(const SGPath &setPath, SGPropertyNode_ptr props)
{
// it woudld be race-y to touch the reosurce provider while the scan thread is running
// and our provider would confuse current-aircraft-dir lookups as well. Since we
// can't do thread-specific reosurce providers, we just bail here.
if (d->m_scanThread) {
return ParseSetXMLResult::Retry;
}
auto rm = simgear::ResourceManager::instance();
// ensure aircraft relative paths in the -set.xml parsing work
std::unique_ptr<ScanDirProvider> dp{new ScanDirProvider};
rm->addProvider(dp.get());
// we want to know the aircraft directory the aircraft lives in. This might
// be a manually added path (for local aircraft) or the install dir for the
// hangar (for packaged aircraft). Becuase -set.xml files are always found at
// /some/path/foobarAircraft/<aircraft-name>/some-set.xml, and the path we
// we want here is /some/path/foodbarAircraft, we use dirPath twice, and this
// works any kind of installed aircraft
const SGPath aircraftDirPath = setPath.dirPath().dirPath();
dp->setCurrentPath(aircraftDirPath);
dp->setCurrentAircraftPath(setPath);
ParseSetXMLResult result = ParseSetXMLResult::Failed;
try {
readProperties(setPath, props);
result = ParseSetXMLResult::Ok;
} catch (sg_exception& e) {
// malformed include or XML
qWarning() << "Problems occurred while parsing" << QString::fromStdString(setPath.utf8Str())
<< "\n\t" << QString::fromStdString(e.getFormattedMessage());
}
rm->removeProvider(dp.get());
return result;
}
#include "LocalAircraftCache.moc"

View file

@ -33,7 +33,6 @@
class QDataStream;
struct AircraftItem;
class AircraftScanThread;
class SGPropertyNode;
typedef QSharedPointer<AircraftItem> AircraftItemPtr;
@ -114,6 +113,7 @@ public:
void setPaths(QStringList paths);
QStringList paths() const;
void scanDirs();
@ -161,6 +161,20 @@ public:
// rating order is FDM, Systems, Cockpit, External model
static int ratingFromProperties(SGPropertyNode* node, int ratingIndex);
enum class ParseSetXMLResult {
Ok,
Failed,
Retry ///< aircraft scan in progress, try again later
};
/**
* @brief readAircraftProperties - helper to parse a -set.xml, but with the correct
* path setup (root, aircradft dirs, current aircraft dir)
* @param path : full path to the -set.xml file
* @param props : property node to be populated
* @return status indication
*/
ParseSetXMLResult readAircraftProperties(const SGPath& path, SGPropertyNode_ptr props);
signals:
void scanStarted();
@ -180,10 +194,8 @@ private:
void abandonCurrentScan();
QStringList m_paths;
std::unique_ptr<AircraftScanThread> m_scanThread;
QVector<AircraftItemPtr> m_items;
class AircraftCachePrivate;
std::unique_ptr<AircraftCachePrivate> d;
};
#endif // LOCALAIRCRAFTCACHE_HXX

View file

@ -6,6 +6,7 @@
#include <QDebug>
#include <QQmlEngine>
#include <QQmlComponent>
#include <QTimer>
#include <simgear/package/Install.hxx>
#include <simgear/package/Root.hxx>
@ -79,16 +80,6 @@ private:
QmlAircraftInfo* p;
};
void QmlAircraftInfo::Delegate::finishInstall(InstallRef aInstall, StatusCode aReason)
{
Q_UNUSED(aReason)
if (aInstall->package() == p->packageRef()) {
p->_cachedProps.reset();
p->checkForStates();
p->infoChanged();
}
}
////////////////////////////////////////////////////////////////////////////
@ -104,22 +95,14 @@ struct StateInfo
using AircraftStateVec = std::vector<StateInfo>;
static AircraftStateVec readAircraftStates(const SGPath& setXMLPath)
static bool readAircraftStates(const SGPropertyNode_ptr root, AircraftStateVec& result)
{
SGPropertyNode_ptr root(new SGPropertyNode);
try {
readProperties(setXMLPath, root);
} catch (sg_exception&) {
// malformed include or XML, just bail
return {};
}
result.clear();
if (!root->getNode("sim/state")) {
return {};
return false; // no states
}
auto nodes = root->getNode("sim")->getChildren("state");
AircraftStateVec result;
result.reserve(nodes.size());
for (auto cn : nodes) {
string_list stateNames;
@ -138,7 +121,7 @@ static AircraftStateVec readAircraftStates(const SGPath& setXMLPath)
});
}
return result;
return true;
}
QString humanNameFromStateTag(const std::string& tag)
@ -169,15 +152,27 @@ class StatesModel : public QAbstractListModel
Q_OBJECT
public:
StatesModel()
StatesModel(QObject* pr) : QAbstractListModel(pr)
{
}
StatesModel(const AircraftStateVec& states) :
_data(states)
void clear()
{
beginResetModel();
_data.clear();
_explicitAutoState = false;
endResetModel();
}
void initWithStates(const AircraftStateVec& states)
{
beginResetModel();
_data = states;
_explicitAutoState = false;
// we use an empty model for aircraft with no states defined
if (states.empty()) {
endResetModel();
return;
}
@ -195,6 +190,8 @@ public:
} else {
_data.insert(_data.begin(), {{"auto"}, {}, tr("Select state based on startup position.")});
}
endResetModel();
}
Q_INVOKABLE int indexForTag(QString s) const
@ -300,6 +297,22 @@ private:
bool _explicitAutoState = false;
};
////////////////////////////////////////////////////////////////////////////
void QmlAircraftInfo::Delegate::finishInstall(InstallRef aInstall, StatusCode aReason)
{
Q_UNUSED(aReason)
if (aInstall->package() == p->packageRef()) {
p->_cachedProps.reset();
if (p->_statesModel) {
p->_statesModel->clear();
}
p->_statesChecked = false;
p->infoChanged();
}
}
////////////////////////////////////////////////////////////////////////////
QmlAircraftInfo::QmlAircraftInfo(QObject *parent)
@ -592,53 +605,49 @@ AircraftItemPtr QmlAircraftInfo::resolveItem() const
return _item;
}
void QmlAircraftInfo::checkForStates()
void QmlAircraftInfo::validateStates() const
{
QString path = pathOnDisk();
if (path.isEmpty()) {
_statesModel.reset(new StatesModel);
emit infoChanged();
return;
// after calling this, StatesModel must exist, but can be empty
if (!_statesModel) {
_statesModel = new StatesModel(const_cast<QmlAircraftInfo*>(this));
}
const auto sgp = SGPath::fromUtf8(path.toUtf8().toStdString());
if (!sgp.exists()) {
_statesModel.reset(new StatesModel);
emit infoChanged();
if (_statesChecked)
return;
}
auto states = readAircraftStates(sgp);
if (states.empty()) {
_statesModel.reset(new StatesModel);
emit infoChanged();
validateLocalProps();
if (!_cachedProps)
return;
}
_statesModel.reset(new StatesModel(states));
emit infoChanged();
AircraftStateVec statesData;
readAircraftStates(_cachedProps, statesData);
_statesModel->initWithStates(statesData);
_statesChecked = true;
}
void QmlAircraftInfo::validateLocalProps() const
{
if (!_cachedProps) {
SGPath path;
if (_item) {
path = resolveItem()->path.toUtf8().toStdString();
} else if (_package) {
auto install = _package->existingInstall();
if (!install.valid())
return;
// clear any previous states if we're reusing this object
if (_statesModel)
_statesModel->clear();
path = install->path();
}
_statesChecked = false;
SGPath path = SGPath::fromUtf8(pathOnDisk().toStdString());
if (!path.exists())
return;
_cachedProps = new SGPropertyNode;
try {
readProperties(path, _cachedProps);
} catch (sg_exception&) {
auto r = LocalAircraftCache::instance()->readAircraftProperties(path, _cachedProps);
if (r == LocalAircraftCache::ParseSetXMLResult::Retry) {
_cachedProps.reset();
// give the AircraftScsn threa d abit more time
QTimer::singleShot(2000, this, &QmlAircraftInfo::retryValidateLocalProps);
} else if (r == LocalAircraftCache::ParseSetXMLResult::Ok) {
// we're good
} else {
// failure
_cachedProps.reset();
}
}
@ -651,8 +660,10 @@ void QmlAircraftInfo::setUri(QUrl u)
_item.clear();
_package.clear();
_statesModel.reset(new StatesModel);
_cachedProps.clear();
_statesChecked = false;
if (_statesModel)
_statesModel->clear();
if (u.isLocalFile()) {
_item = LocalAircraftCache::instance()->findItemWithUri(u);
@ -679,8 +690,6 @@ void QmlAircraftInfo::setUri(QUrl u)
}
}
checkForStates();
emit uriChanged();
emit infoChanged();
emit downloadChanged();
@ -702,8 +711,9 @@ void QmlAircraftInfo::setVariant(quint32 variant)
_variant = variant;
_cachedProps.clear();
checkForStates();
_statesChecked = false;
if (_statesModel)
_statesModel->clear();
emit infoChanged();
emit variantChanged(_variant);
@ -722,6 +732,12 @@ void QmlAircraftInfo::onFavouriteChanged(QUrl u)
emit favouriteChanged();
}
void QmlAircraftInfo::retryValidateLocalProps()
{
qInfo() << Q_FUNC_INFO << "Retrying validation of local props for:" << uri();
validateLocalProps();
}
QVariant QmlAircraftInfo::packageAircraftStatus(simgear::pkg::PackageRef p)
{
if (p->hasTag("needs-maintenance")) {
@ -806,22 +822,26 @@ bool QmlAircraftInfo::isPackaged() const
bool QmlAircraftInfo::hasStates() const
{
validateStates();
return !_statesModel->isEmpty();
}
bool QmlAircraftInfo::hasState(QString name) const
{
validateStates();
return _statesModel->hasState(name);
}
bool QmlAircraftInfo::haveExplicitAutoState() const
{
validateStates();
return _statesModel->hasExplicitAuto();
}
StatesModel *QmlAircraftInfo::statesModel()
{
return _statesModel.data();
validateStates();
return _statesModel;
}
QuantityValue QmlAircraftInfo::cruiseSpeed() const
@ -886,11 +906,13 @@ QString QmlAircraftInfo::icaoType() const
bool QmlAircraftInfo::isSpeedBelowLimits(QuantityValue speed) const
{
Q_UNUSED(speed)
return true;
}
bool QmlAircraftInfo::isAltitudeBelowLimits(QuantityValue speed) const
{
Q_UNUSED(speed)
return true;
}

View file

@ -57,7 +57,7 @@ class QmlAircraftInfo : public QObject
Q_PROPERTY(QString icaoType READ icaoType NOTIFY infoChanged)
Q_PROPERTY(bool hasStates READ hasStates NOTIFY infoChanged)
Q_PROPERTY(StatesModel* statesModel READ statesModel NOTIFY infoChanged)
Q_PROPERTY(StatesModel* statesModel READ statesModel CONSTANT)
Q_PROPERTY(bool favourite READ favourite WRITE setFavourite NOTIFY favouriteChanged)
public:
@ -147,10 +147,12 @@ public slots:
private slots:
void onFavouriteChanged(QUrl u);
void retryValidateLocalProps();
private:
AircraftItemPtr resolveItem() const;
void checkForStates();
void validateStates() const;
void validateLocalProps() const;
class Delegate;
@ -160,7 +162,11 @@ private:
AircraftItemPtr _item;
quint32 _variant = 0;
quint64 _downloadBytes = 0;
QScopedPointer<StatesModel> _statesModel;
/// either null or owned by us
mutable StatesModel* _statesModel = nullptr;
mutable bool _statesChecked = false;
/// if the aircraft is locally installed, this is the cached
/// parsed contents of the -set.xml.