1
0
Fork 0

Seperate aircraft cache from the model.

Allows exposing aircraft data to QML (via a helper object) outside the
context of the model.
This commit is contained in:
James Turner 2017-10-09 22:53:26 +02:00
parent 78950fea57
commit 041b9527d3
12 changed files with 1111 additions and 553 deletions

View file

@ -0,0 +1,51 @@
import QtQuick 2.0
import FGLauncher 1.0
Item {
property alias aurcradftURI: aircraft.uri
AircraftInfo
{
id: aircraft
}
Column {
Text {
id: aircraftName
}
Image {
id: preview
// selector overlay
// left / right arrows
}
Timer {
id: previewCycleTimer
}
Text {
id: aircraftDescription
}
Text {
id: aircraftAuthors
}
// info button
// version warning!
// ratings box
// package size
// install / download / update button
} // main layout column
}

View file

@ -20,11 +20,6 @@
#include "AircraftModel.hxx"
#include <QDir>
#include <QThread>
#include <QMutex>
#include <QMutexLocker>
#include <QDataStream>
#include <QSettings>
#include <QDebug>
#include <QSharedPointer>
@ -41,317 +36,13 @@
#include <Main/globals.hxx>
#include <Include/version.h>
#include "QmlAircraftInfo.hxx"
const int STANDARD_THUMBNAIL_HEIGHT = 128;
const int STANDARD_THUMBNAIL_WIDTH = 172;
static quint32 CACHE_VERSION = 8;
using namespace simgear::pkg;
AircraftItem::AircraftItem()
{
}
AircraftItem::AircraftItem(QDir dir, QString filePath)
{
SGPropertyNode root;
readProperties(filePath.toStdString(), &root);
if (!root.hasChild("sim")) {
throw sg_io_exception(std::string("Malformed -set.xml file"), filePath.toStdString());
}
SGPropertyNode_ptr sim = root.getNode("sim");
path = filePath;
pathModTime = QFileInfo(path).lastModified();
if (sim->getBoolValue("exclude-from-gui", false)) {
excluded = true;
return;
}
description = sim->getStringValue("description");
authors = sim->getStringValue("author");
if (sim->hasChild("rating")) {
SGPropertyNode_ptr ratingsNode = sim->getNode("rating");
ratings[0] = ratingsNode->getIntValue("FDM");
ratings[1] = ratingsNode->getIntValue("systems");
ratings[2] = ratingsNode->getIntValue("cockpit");
ratings[3] = ratingsNode->getIntValue("model");
}
if (sim->hasChild("long-description")) {
// clean up any XML whitspace in the text.
longDescription = QString(sim->getStringValue("long-description")).simplified();
}
if (sim->hasChild("variant-of")) {
variantOf = sim->getStringValue("variant-of");
} else {
isPrimary = true;
}
if (sim->hasChild("primary-set")) {
isPrimary = sim->getBoolValue("primary-set");
}
if (sim->hasChild("tags")) {
SGPropertyNode_ptr tagsNode = sim->getChild("tags");
int nChildren = tagsNode->nChildren();
for (int i = 0; i < nChildren; i++) {
const SGPropertyNode* c = tagsNode->getChild(i);
if (strcmp(c->getName(), "tag") == 0) {
const char* tagName = c->getStringValue();
usesHeliports |= (strcmp(tagName, "helicopter") == 0);
// could also consider vtol tag?
usesSeaports |= (strcmp(tagName, "seaplane") == 0);
usesSeaports |= (strcmp(tagName, "floats") == 0);
needsMaintenance |= (strcmp(tagName, "needs-maintenance") == 0);
}
} // of tags iteration
} // of set-xml has tags
if (sim->hasChild("previews")) {
SGPropertyNode_ptr previewsNode = sim->getChild("previews");
for (auto previewNode : previewsNode->getChildren("preview")) {
// add file path as url
QString pathInXml = QString::fromStdString(previewNode->getStringValue("path"));
QString previewPath = dir.absoluteFilePath(pathInXml);
previews.append(QUrl::fromLocalFile(previewPath));
}
}
if (sim->hasChild("thumbnail")) {
thumbnailPath = sim->getStringValue("thumbnail");
} else {
thumbnailPath = "thumbnail.jpg";
}
if (sim->hasChild("minimum-fg-version")) {
minFGVersion = sim->getStringValue("minimum-fg-version");
}
}
QString AircraftItem::baseName() const
{
QString fn = QFileInfo(path).fileName();
fn.truncate(fn.count() - 8);
return fn;
}
void AircraftItem::fromDataStream(QDataStream& ds)
{
ds >> path >> pathModTime >> excluded;
if (excluded) {
return;
}
ds >> description >> longDescription >> authors >> variantOf >> isPrimary;
for (int i=0; i<4; ++i) ds >> ratings[i];
ds >> previews;
ds >> thumbnailPath;
ds >> minFGVersion;
ds >> needsMaintenance >> usesHeliports >> usesSeaports;
}
void AircraftItem::toDataStream(QDataStream& ds) const
{
ds << path << pathModTime << excluded;
if (excluded) {
return;
}
ds << description << longDescription << authors << variantOf << isPrimary;
for (int i=0; i<4; ++i) ds << ratings[i];
ds << previews;
ds << thumbnailPath;
ds << minFGVersion;
ds << needsMaintenance << usesHeliports << usesSeaports;
}
QPixmap AircraftItem::thumbnail(bool loadIfRequired) const
{
if (m_thumbnail.isNull() && loadIfRequired) {
QFileInfo info(path);
QDir dir = info.dir();
if (dir.exists(thumbnailPath)) {
m_thumbnail.load(dir.filePath(thumbnailPath));
// resize to the standard size
if (m_thumbnail.height() > STANDARD_THUMBNAIL_HEIGHT) {
m_thumbnail = m_thumbnail.scaledToHeight(STANDARD_THUMBNAIL_HEIGHT, Qt::SmoothTransformation);
}
}
}
return m_thumbnail;
}
class AircraftScanThread : public QThread
{
Q_OBJECT
public:
AircraftScanThread(QStringList dirsToScan) :
m_dirs(dirsToScan),
m_done(false)
{
}
~AircraftScanThread()
{
}
/** thread-safe access to items already scanned */
QVector<AircraftItemPtr> items()
{
QVector<AircraftItemPtr> result;
QMutexLocker g(&m_lock);
result.swap(m_items);
g.unlock();
return result;
}
void setDone()
{
m_done = true;
}
Q_SIGNALS:
void addedItems();
protected:
virtual void run()
{
readCache();
Q_FOREACH(QString d, m_dirs) {
scanAircraftDir(QDir(d));
if (m_done) {
return;
}
}
writeCache();
}
private:
void readCache()
{
QSettings settings;
QByteArray cacheData = settings.value("aircraft-cache").toByteArray();
if (!cacheData.isEmpty()) {
QDataStream ds(cacheData);
quint32 count, cacheVersion;
ds >> cacheVersion >> count;
if (cacheVersion != CACHE_VERSION) {
return; // mis-matched cache, version, drop
}
for (quint32 i=0; i<count; ++i) {
AircraftItemPtr item(new AircraftItem);
item->fromDataStream(ds);
QFileInfo finfo(item->path);
if (finfo.exists() && (finfo.lastModified() == item->pathModTime)) {
// corresponding -set.xml file still exists and is
// unmodified
m_cachedItems[item->path] = item;
}
} // of cached item iteration
}
}
void writeCache()
{
QSettings settings;
QByteArray cacheData;
{
QDataStream ds(&cacheData, QIODevice::WriteOnly);
quint32 count = m_nextCache.count();
ds << CACHE_VERSION << count;
Q_FOREACH(AircraftItemPtr item, m_nextCache.values()) {
item->toDataStream(ds);
}
}
settings.setValue("aircraft-cache", cacheData);
}
void scanAircraftDir(QDir path)
{
QTime t;
t.start();
QStringList filters;
filters << "*-set.xml";
Q_FOREACH(QFileInfo child, path.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot)) {
QDir childDir(child.absoluteFilePath());
QMap<QString, AircraftItemPtr> baseAircraft;
QList<AircraftItemPtr> variants;
Q_FOREACH(QFileInfo xmlChild, childDir.entryInfoList(filters, QDir::Files)) {
try {
QString absolutePath = xmlChild.absoluteFilePath();
AircraftItemPtr item;
if (m_cachedItems.contains(absolutePath)) {
item = m_cachedItems.value(absolutePath);
} else {
item = AircraftItemPtr(new AircraftItem(childDir, absolutePath));
}
m_nextCache[absolutePath] = item;
if (item->excluded) {
continue;
}
if (item->isPrimary) {
baseAircraft.insert(item->baseName(), item);
} else {
variants.append(item);
}
} catch (sg_exception& e) {
continue;
}
if (m_done) {
return;
}
} // of set.xml iteration
// bind variants to their principals
Q_FOREACH(AircraftItemPtr item, variants) {
if (!baseAircraft.contains(item->variantOf)) {
qWarning() << "can't find principal aircraft " << item->variantOf << " for variant:" << item->path;
continue;
}
baseAircraft.value(item->variantOf)->variants.append(item);
}
// lock mutex while we modify the items array
{
QMutexLocker g(&m_lock);
m_items+=(baseAircraft.values().toVector());
}
emit addedItems();
} // of subdir iteration
}
QMutex m_lock;
QStringList m_dirs;
QVector<AircraftItemPtr> m_items;
QMap<QString, AircraftItemPtr > m_cachedItems;
QMap<QString, AircraftItemPtr > m_nextCache;
bool m_done;
};
class PackageDelegate : public simgear::pkg::Delegate
{
public:
@ -473,7 +164,7 @@ private:
}
int offset = std::distance(m_model->m_packages.begin(), it);
return m_model->index(offset + m_model->m_items.size());
return m_model->index(offset + m_model->m_cachedLocalAircraftCount);
}
AircraftItemModel* m_model;
@ -482,11 +173,15 @@ private:
AircraftItemModel::AircraftItemModel(QObject* pr) :
QAbstractListModel(pr)
{
auto cache = LocalAircraftCache::instance();
connect(cache, &LocalAircraftCache::scanStarted,
this, &AircraftItemModel::onScanStarted);
connect(cache, &LocalAircraftCache::addedItems,
this, &AircraftItemModel::onScanAddedItems);
}
AircraftItemModel::~AircraftItemModel()
{
abandonCurrentScan();
delete m_delegate;
}
@ -506,56 +201,22 @@ void AircraftItemModel::setPackageRoot(const simgear::pkg::RootRef& root)
}
}
void AircraftItemModel::setPaths(QStringList paths)
void AircraftItemModel::onScanStarted()
{
m_paths = paths;
}
void AircraftItemModel::scanDirs()
{
abandonCurrentScan();
const int numToRemove = m_items.size();
if (numToRemove > 0) {
const int numToRemove = m_cachedLocalAircraftCount;
if (numToRemove > 0) {
int lastRow = numToRemove - 1;
beginRemoveRows(QModelIndex(), 0, lastRow);
m_items.remove(0, numToRemove);
m_delegateStates.remove(0, numToRemove);
endRemoveRows();
}
QStringList dirs = m_paths;
Q_FOREACH(SGPath ap, globals->get_aircraft_paths()) {
dirs << QString::fromStdString(ap.utf8Str());
}
SGPath rootAircraft(globals->get_fg_root());
rootAircraft.append("Aircraft");
dirs << QString::fromStdString(rootAircraft.utf8Str());
m_scanThread = new AircraftScanThread(dirs);
connect(m_scanThread, &AircraftScanThread::finished, this,
&AircraftItemModel::onScanFinished);
connect(m_scanThread, &AircraftScanThread::addedItems,
this, &AircraftItemModel::onScanResults);
m_scanThread->start();
}
void AircraftItemModel::abandonCurrentScan()
{
if (m_scanThread) {
m_scanThread->setDone();
m_scanThread->wait(1000);
delete m_scanThread;
m_scanThread = NULL;
m_cachedLocalAircraftCount = 0;
endRemoveRows();
}
}
void AircraftItemModel::refreshPackages()
{
simgear::pkg::PackageList newPkgs = m_packageRoot->allPackages();
const int firstRow = m_items.size();
const int firstRow = m_cachedLocalAircraftCount;
const int newSize = newPkgs.size();
const int newTotalSize = firstRow + newSize;
@ -589,7 +250,7 @@ void AircraftItemModel::refreshPackages()
int AircraftItemModel::rowCount(const QModelIndex& parent) const
{
return m_items.size() + m_packages.size();
return m_cachedLocalAircraftCount + m_packages.size();
}
QVariant AircraftItemModel::data(const QModelIndex& index, int role) const
@ -599,8 +260,8 @@ QVariant AircraftItemModel::data(const QModelIndex& index, int role) const
return m_delegateStates.at(row).variant;
}
if (row >= m_items.size()) {
quint32 packageIndex = row - m_items.size();
if (row >= m_cachedLocalAircraftCount) {
quint32 packageIndex = row - m_cachedLocalAircraftCount;
const PackageRef& pkg(m_packages[packageIndex]);
InstallRef ex = pkg->existingInstall();
@ -614,7 +275,7 @@ QVariant AircraftItemModel::data(const QModelIndex& index, int role) const
return dataFromPackage(pkg, m_delegateStates.at(row), role);
} else {
const AircraftItemPtr item(m_items.at(row));
const AircraftItemPtr item(LocalAircraftCache::instance()->itemAt(row));
return dataFromItem(item, m_delegateStates.at(row), role);
}
}
@ -694,7 +355,7 @@ QVariant AircraftItemModel::dataFromItem(AircraftItemPtr item, const DelegateSta
{
return 0;
} else if (role == AircraftStatusRole) {
return itemAircraftStatus(item, state);
return item->status(0 /* variant is always 0 */);
} else if (role == AircraftMinVersionRole) {
return item->minFGVersion;
}
@ -784,7 +445,7 @@ QVariant AircraftItemModel::dataFromPackage(const PackageRef& item, const Delega
}
return ratings->getChild(ratingIndex)->getIntValue();
} else if (role == AircraftStatusRole) {
return packageAircraftStatus(item, state);
return QmlAircraftInfo::packageAircraftStatus(item);
} else if (role == AircraftMinVersionRole) {
const std::string v = item->properties()->getStringValue("minimum-fg-version");
if (!v.empty()) {
@ -860,37 +521,6 @@ QVariant AircraftItemModel::packagePreviews(PackageRef p, const DelegateState& d
return result;
}
QVariant AircraftItemModel::itemAircraftStatus(AircraftItemPtr item, const DelegateState& ds) const
{
if (item->needsMaintenance) {
return AircraftUnmaintained;
}
if (item->minFGVersion.isEmpty()) {
return AircraftOk;
}
const int c = simgear::strutils::compare_versions(FLIGHTGEAR_VERSION,
item->minFGVersion.toStdString(), 2);
return (c < 0) ? AircraftNeedsNewerSimulator : AircraftOk;
}
QVariant AircraftItemModel::packageAircraftStatus(PackageRef p, const DelegateState& ds) const
{
if (p->hasTag("needs-maintenance")) {
return AircraftUnmaintained;
}
if (!p->properties()->hasChild("minimum-fg-version")) {
return AircraftOk;
}
const std::string minFGVersion = p->properties()->getStringValue("minimum-fg-version");
const int c = simgear::strutils::compare_versions(FLIGHTGEAR_VERSION, minFGVersion, 2);
return (c < 0) ? AircraftNeedsNewerSimulator : AircraftOk;
}
bool AircraftItemModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
int row = index.row();
@ -946,23 +576,13 @@ QModelIndex AircraftItemModel::indexOfAircraftURI(QUrl uri) const
}
if (uri.isLocalFile()) {
QString path = uri.toLocalFile();
for (int row=0; row <m_items.size(); ++row) {
const AircraftItemPtr item(m_items.at(row));
if (item->path == path) {
return index(row);
}
// check variants too
for (int vr=0; vr < item->variants.size(); ++vr) {
if (item->variants.at(vr)->path == path) {
return index(row);
}
}
int row = LocalAircraftCache::instance()->findIndexWithUri(uri);
if (row >= 0) {
return index(row);
}
} else if (uri.scheme() == "package") {
QString ident = uri.path();
int rowOffset = m_items.size();
int rowOffset = m_cachedLocalAircraftCount;
PackageRef pkg = m_packageRoot->getPackageById(ident.toStdString());
if (pkg) {
@ -991,27 +611,25 @@ void AircraftItemModel::selectVariantForAircraftURI(QUrl uri)
QModelIndex modelIndex;
if (uri.isLocalFile()) {
QString path = uri.toLocalFile();
for (int row=0; row <m_items.size(); ++row) {
const AircraftItemPtr item(m_items.at(row));
if (item->path == path) {
modelIndex = index(row);
variantIndex = 0;
break;
}
int row = LocalAircraftCache::instance()->findIndexWithUri(uri);
if (row < 0) {
return;
}
// check variants too
for (int vr=0; vr < item->variants.size(); ++vr) {
if (item->variants.at(vr)->path == path) {
modelIndex = index(row);
variantIndex = vr + 1;
break;
}
modelIndex = index(row);
// now check if we are actually selecting a variant
const AircraftItemPtr item = LocalAircraftCache::instance()->itemAt(row);
const QString path = uri.toLocalFile();
for (int vr=0; vr < item->variants.size(); ++vr) {
if (item->variants.at(vr)->path == path) {
variantIndex = vr + 1;
break;
}
}
} else if (uri.scheme() == "package") {
QString ident = uri.path();
int rowOffset = m_items.size();
int rowOffset = m_cachedLocalAircraftCount;
PackageRef pkg = m_packageRoot->getPackageById(ident.toStdString());
if (pkg) {
@ -1036,18 +654,16 @@ void AircraftItemModel::selectVariantForAircraftURI(QUrl uri)
QString AircraftItemModel::nameForAircraftURI(QUrl uri) const
{
if (uri.isLocalFile()) {
QString path = uri.toLocalFile();
for (int row=0; row <m_items.size(); ++row) {
const AircraftItemPtr item(m_items.at(row));
if (item->path == path) {
return item->description;
}
AircraftItemPtr item = LocalAircraftCache::instance()->findItemWithUri(uri);
const QString path = uri.toLocalFile();
if (item->path == path) {
return item->description;
}
// check variants too
for (int vr=0; vr < item->variants.size(); ++vr) {
if (item->variants.at(vr)->path == path) {
return item->description;
}
// check variants too
for (int vr=0; vr < item->variants.size(); ++vr) {
if (item->variants.at(vr)->path == path) {
return item->description;
}
}
} else if (uri.scheme() == "package") {
@ -1065,32 +681,25 @@ QString AircraftItemModel::nameForAircraftURI(QUrl uri) const
return QString();
}
void AircraftItemModel::onScanResults()
void AircraftItemModel::onScanAddedItems(int count)
{
QVector<AircraftItemPtr> newItems = m_scanThread->items();
QVector<AircraftItemPtr> newItems = LocalAircraftCache::instance()->newestItems(count);
if (newItems.isEmpty())
return;
int firstRow = m_items.count();
int lastRow = firstRow + newItems.count() - 1;
int firstRow = m_cachedLocalAircraftCount;
int lastRow = firstRow + count - 1;
beginInsertRows(QModelIndex(), firstRow, lastRow);
m_items+=newItems;
m_cachedLocalAircraftCount += count;
// default variants in all cases
for (int i=0; i< newItems.count(); ++i) {
m_delegateStates.insert(firstRow + i, DelegateState());
for (int i=0; i< count; ++i) {
m_delegateStates.insert(firstRow + i, {});
}
endInsertRows();
}
void AircraftItemModel::onScanFinished()
{
delete m_scanThread;
m_scanThread = NULL;
emit scanCompleted();
}
void AircraftItemModel::installFailed(QModelIndex index, simgear::pkg::Delegate::StatusCode reason)
{
QString msg;
@ -1120,11 +729,11 @@ void AircraftItemModel::installSucceeded(QModelIndex index)
bool AircraftItemModel::isIndexRunnable(const QModelIndex& index) const
{
if (index.row() < m_items.size()) {
if (index.row() < m_cachedLocalAircraftCount) {
return true; // local file, always runnable
}
quint32 packageIndex = index.row() - m_items.size();
quint32 packageIndex = index.row() - m_cachedLocalAircraftCount;
const PackageRef& pkg(m_packages[packageIndex]);
InstallRef ex = pkg->existingInstall();
if (!ex.valid()) {
@ -1134,29 +743,6 @@ bool AircraftItemModel::isIndexRunnable(const QModelIndex& index) const
return !ex->isDownloading();
}
bool AircraftItemModel::isCandidateAircraftPath(QString path)
{
QStringList filters;
filters << "*-set.xml";
int dirCount = 0,
setXmlCount = 0;
QDir d(path);
Q_FOREACH(QFileInfo child, d.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot)) {
QDir childDir(child.absoluteFilePath());
++dirCount;
Q_FOREACH(QFileInfo xmlChild, childDir.entryInfoList(filters, QDir::Files)) {
++setXmlCount;
}
if ((setXmlCount > 0) || (dirCount > 10)) {
break;
}
}
return (setXmlCount > 0);
}
int AircraftItemModel::aircraftNeedingUpdated() const
{
return m_cachedUpdateCount;
@ -1176,4 +762,3 @@ void AircraftItemModel::setShowUpdateAll(bool showUpdateAll)
emit aircraftNeedingUpdatedChanged();
}
#include "AircraftModel.moc"

View file

@ -22,13 +22,13 @@
#define FG_GUI_AIRCRAFT_MODEL
#include <QAbstractListModel>
#include <QDateTime>
#include <QDir>
#include <QPixmap>
#include <QStringList>
#include <QSharedPointer>
#include <QUrl>
#include "LocalAircraftCache.hxx"
#include <simgear/package/Delegate.hxx>
#include <simgear/package/Root.hxx>
#include <simgear/package/Catalog.hxx>
@ -62,49 +62,10 @@ const int AircraftHasPreviewsRole = Qt::UserRole + 24;
const int AircraftRatingRole = Qt::UserRole + 100;
const int AircraftVariantDescriptionRole = Qt::UserRole + 200;
class AircraftScanThread;
class QDataStream;
class PackageDelegate;
struct AircraftItem;
typedef QSharedPointer<AircraftItem> AircraftItemPtr;
Q_DECLARE_METATYPE(simgear::pkg::PackageRef)
struct AircraftItem
{
AircraftItem();
AircraftItem(QDir dir, QString filePath);
// the file-name without -set.xml suffix
QString baseName() const;
void fromDataStream(QDataStream& ds);
void toDataStream(QDataStream& ds) const;
QPixmap thumbnail(bool loadIfRequired = true) const;
bool excluded = false;
QString path;
QString description;
QString longDescription;
QString authors;
int ratings[4] = {0, 0, 0, 0};
QString variantOf;
QDateTime pathModTime;
QList<AircraftItemPtr> variants;
bool usesHeliports = false;
bool usesSeaports = false;
QList<QUrl> previews;
bool isPrimary = false;
QString thumbnailPath;
QString minFGVersion;
bool needsMaintenance = false;
private:
mutable QPixmap m_thumbnail;
};
class AircraftItemModel : public QAbstractListModel
{
Q_OBJECT
@ -129,10 +90,6 @@ public:
void setPackageRoot(const simgear::pkg::RootRef& root);
void setPaths(QStringList paths);
void scanDirs();
int rowCount(const QModelIndex& parent) const override;
QVariant data(const QModelIndex& index, int role) const override;
@ -166,21 +123,6 @@ public:
*/
QString nameForAircraftURI(QUrl uri) const;
/**
* @helper to determine if a particular path is likely to contain
* aircraft or not. Checks for -set.xml files one level down in the tree.
*
*/
static bool isCandidateAircraftPath(QString path);
enum AircraftStatus
{
AircraftOk = 0,
AircraftUnmaintained,
AircraftNeedsNewerSimulator,
AircraftNeedsOlderSimulator // won't ever occur for the moment
};
int aircraftNeedingUpdated() const;
bool showUpdateAll() const;
@ -190,17 +132,14 @@ signals:
void aircraftInstallCompleted(QModelIndex index);
void scanCompleted();
void aircraftNeedingUpdatedChanged();
public slots:
void setShowUpdateAll(bool showUpdateAll);
private slots:
void onScanResults();
void onScanFinished();
void onScanStarted();
void onScanAddedItems(int count);
private:
friend class PackageDelegate;
@ -212,11 +151,9 @@ private:
struct DelegateState
{
quint32 variant = 0;
quint32 thumbnail = 0;
};
QVariant dataFromItem(AircraftItemPtr item, const DelegateState& state, int role) const;
QVariant itemAircraftStatus(AircraftItemPtr item, const DelegateState& ds) const;
QVariant dataFromPackage(const simgear::pkg::PackageRef& item,
const DelegateState& state, int role) const;
@ -225,17 +162,12 @@ private:
const DelegateState& state, bool download = true) const;
QVariant packagePreviews(simgear::pkg::PackageRef p, const DelegateState &ds) const;
QVariant packageAircraftStatus(simgear::pkg::PackageRef p, const DelegateState &ds) const;
void abandonCurrentScan();
void refreshPackages();
void installSucceeded(QModelIndex index);
void installFailed(QModelIndex index, simgear::pkg::Delegate::StatusCode reason);
QStringList m_paths;
AircraftScanThread* m_scanThread = nullptr;
QVector<AircraftItemPtr> m_items;
PackageDelegate* m_delegate = nullptr;
QVector<DelegateState> m_delegateStates;
@ -245,6 +177,7 @@ private:
mutable QHash<QString, QPixmap> m_downloadedPixmapCache;
int m_cachedUpdateCount = 0;
int m_cachedLocalAircraftCount = 0;
bool m_showUpdateAll = true;
};

View file

@ -169,6 +169,11 @@ if (HAVE_QT)
FGQmlInstance.hxx
FGQmlPropertyNode.cxx
FGQmlPropertyNode.hxx
QmlAircraftInfo.cxx
QmlAircraftInfo.hxx
LocalAircraftCache.cxx
LocalAircraftCache.hxx
)
set_property(TARGET fgqmlui PROPERTY AUTOMOC ON)

View file

@ -42,6 +42,7 @@
#include "MPServersModel.h"
#include "ThumbnailImageItem.hxx"
#include "FlickableExtentQuery.hxx"
#include "LocalAircraftCache.hxx"
#include "ui_Launcher.h"
@ -173,7 +174,10 @@ LauncherMainWindow::LauncherMainWindow() :
this, &LauncherMainWindow::onAircraftInstalledCompleted);
connect(m_aircraftModel, &AircraftItemModel::aircraftInstallFailed,
this, &LauncherMainWindow::onAircraftInstallFailed);
connect(m_aircraftModel, &AircraftItemModel::scanCompleted,
connect(LocalAircraftCache::instance(),
&LocalAircraftCache::scanCompleted,
this, &LauncherMainWindow::updateSelectedAircraft);
AddOnsPage* addOnsPage = new AddOnsPage(NULL, globals->packageRoot());
@ -191,9 +195,9 @@ LauncherMainWindow::LauncherMainWindow() :
this, &LauncherMainWindow::delayedAircraftModelReset);
QSettings settings;
m_aircraftModel->setPaths(settings.value("aircraft-paths").toStringList());
LocalAircraftCache::instance()->setPaths(settings.value("aircraft-paths").toStringList());
LocalAircraftCache::instance()->scanDirs();
m_aircraftModel->setPackageRoot(globals->packageRoot());
m_aircraftModel->scanDirs();
buildSettingsSections();
buildEnvironmentSections();
@ -864,8 +868,10 @@ void LauncherMainWindow::downloadDirChanged(QString path)
QSettings settings;
// re-scan the aircraft list
m_aircraftModel->setPackageRoot(globals->packageRoot());
m_aircraftModel->setPaths(settings.value("aircraft-paths").toStringList());
m_aircraftModel->scanDirs();
auto aircraftCache = LocalAircraftCache::instance();
aircraftCache->setPaths(settings.value("aircraft-paths").toStringList());
aircraftCache->scanDirs();
emit showNoOfficialHangarChanged();
@ -909,8 +915,9 @@ simgear::pkg::PackageRef LauncherMainWindow::packageForAircraftURI(QUrl uri) con
void LauncherMainWindow::onAircraftPathsChanged()
{
QSettings settings;
m_aircraftModel->setPaths(settings.value("aircraft-paths").toStringList());
m_aircraftModel->scanDirs();
auto aircraftCache = LocalAircraftCache::instance();
aircraftCache->setPaths(settings.value("aircraft-paths").toStringList());
aircraftCache->scanDirs();
}
void LauncherMainWindow::onChangeDataDir()

View file

@ -0,0 +1,519 @@
// Written by James Turner, started October 2017
//
// Copyright (C) 2017 James Turner <zakalawe@mac.com>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include "config.h"
#include "LocalAircraftCache.hxx"
#include <QDir>
#include <QThread>
#include <QMutex>
#include <QMutexLocker>
#include <QDataStream>
#include <QMap>
#include <QSettings>
#include <QDebug>
#include <Main/globals.hxx>
#include <Include/version.h>
#include <simgear/props/props_io.hxx>
#include <simgear/structure/exception.hxx>
static quint32 CACHE_VERSION = 8;
const int STANDARD_THUMBNAIL_HEIGHT = 128;
const int STANDARD_THUMBNAIL_WIDTH = 172;
AircraftItem::AircraftItem()
{
}
AircraftItem::AircraftItem(QDir dir, QString filePath)
{
SGPropertyNode root;
readProperties(filePath.toStdString(), &root);
if (!root.hasChild("sim")) {
throw sg_io_exception(std::string("Malformed -set.xml file"), filePath.toStdString());
}
SGPropertyNode_ptr sim = root.getNode("sim");
path = filePath;
pathModTime = QFileInfo(path).lastModified();
if (sim->getBoolValue("exclude-from-gui", false)) {
excluded = true;
return;
}
description = sim->getStringValue("description");
authors = sim->getStringValue("author");
if (sim->hasChild("rating")) {
SGPropertyNode_ptr ratingsNode = sim->getNode("rating");
ratings[0] = ratingsNode->getIntValue("FDM");
ratings[1] = ratingsNode->getIntValue("systems");
ratings[2] = ratingsNode->getIntValue("cockpit");
ratings[3] = ratingsNode->getIntValue("model");
}
if (sim->hasChild("long-description")) {
// clean up any XML whitspace in the text.
longDescription = QString(sim->getStringValue("long-description")).simplified();
}
if (sim->hasChild("variant-of")) {
variantOf = sim->getStringValue("variant-of");
} else {
isPrimary = true;
}
if (sim->hasChild("primary-set")) {
isPrimary = sim->getBoolValue("primary-set");
}
if (sim->hasChild("tags")) {
SGPropertyNode_ptr tagsNode = sim->getChild("tags");
int nChildren = tagsNode->nChildren();
for (int i = 0; i < nChildren; i++) {
const SGPropertyNode* c = tagsNode->getChild(i);
if (strcmp(c->getName(), "tag") == 0) {
const char* tagName = c->getStringValue();
usesHeliports |= (strcmp(tagName, "helicopter") == 0);
// could also consider vtol tag?
usesSeaports |= (strcmp(tagName, "seaplane") == 0);
usesSeaports |= (strcmp(tagName, "floats") == 0);
needsMaintenance |= (strcmp(tagName, "needs-maintenance") == 0);
}
} // of tags iteration
} // of set-xml has tags
if (sim->hasChild("previews")) {
SGPropertyNode_ptr previewsNode = sim->getChild("previews");
for (auto previewNode : previewsNode->getChildren("preview")) {
// add file path as url
QString pathInXml = QString::fromStdString(previewNode->getStringValue("path"));
QString previewPath = dir.absoluteFilePath(pathInXml);
previews.append(QUrl::fromLocalFile(previewPath));
}
}
if (sim->hasChild("thumbnail")) {
thumbnailPath = sim->getStringValue("thumbnail");
} else {
thumbnailPath = "thumbnail.jpg";
}
if (sim->hasChild("minimum-fg-version")) {
minFGVersion = sim->getStringValue("minimum-fg-version");
}
}
QString AircraftItem::baseName() const
{
QString fn = QFileInfo(path).fileName();
fn.truncate(fn.count() - 8);
return fn;
}
void AircraftItem::fromDataStream(QDataStream& ds)
{
ds >> path >> pathModTime >> excluded;
if (excluded) {
return;
}
ds >> description >> longDescription >> authors >> variantOf >> isPrimary;
for (int i=0; i<4; ++i) ds >> ratings[i];
ds >> previews;
ds >> thumbnailPath;
ds >> minFGVersion;
ds >> needsMaintenance >> usesHeliports >> usesSeaports;
}
void AircraftItem::toDataStream(QDataStream& ds) const
{
ds << path << pathModTime << excluded;
if (excluded) {
return;
}
ds << description << longDescription << authors << variantOf << isPrimary;
for (int i=0; i<4; ++i) ds << ratings[i];
ds << previews;
ds << thumbnailPath;
ds << minFGVersion;
ds << needsMaintenance << usesHeliports << usesSeaports;
}
QPixmap AircraftItem::thumbnail(bool loadIfRequired) const
{
if (m_thumbnail.isNull() && loadIfRequired) {
QFileInfo info(path);
QDir dir = info.dir();
if (dir.exists(thumbnailPath)) {
m_thumbnail.load(dir.filePath(thumbnailPath));
// resize to the standard size
if (m_thumbnail.height() > STANDARD_THUMBNAIL_HEIGHT) {
m_thumbnail = m_thumbnail.scaledToHeight(STANDARD_THUMBNAIL_HEIGHT, Qt::SmoothTransformation);
}
}
}
return m_thumbnail;
}
QVariant AircraftItem::status(int variant)
{
if (needsMaintenance) {
return LocalAircraftCache::AircraftUnmaintained;
}
if (minFGVersion.isEmpty()) {
return LocalAircraftCache::AircraftOk;
}
const int c = simgear::strutils::compare_versions(FLIGHTGEAR_VERSION,
minFGVersion.toStdString(), 2);
return (c < 0) ? LocalAircraftCache::AircraftNeedsNewerSimulator
: LocalAircraftCache::AircraftOk;
}
class AircraftScanThread : public QThread
{
Q_OBJECT
public:
AircraftScanThread(QStringList dirsToScan) :
m_dirs(dirsToScan),
m_done(false)
{
}
~AircraftScanThread()
{
}
/** thread-safe access to items already scanned */
QVector<AircraftItemPtr> items()
{
QVector<AircraftItemPtr> result;
QMutexLocker g(&m_lock);
result.swap(m_items);
g.unlock();
return result;
}
void setDone()
{
m_done = true;
}
Q_SIGNALS:
void addedItems();
protected:
virtual void run()
{
readCache();
Q_FOREACH(QString d, m_dirs) {
scanAircraftDir(QDir(d));
if (m_done) {
return;
}
}
writeCache();
}
private:
void readCache()
{
QSettings settings;
QByteArray cacheData = settings.value("aircraft-cache").toByteArray();
if (!cacheData.isEmpty()) {
QDataStream ds(cacheData);
quint32 count, cacheVersion;
ds >> cacheVersion >> count;
if (cacheVersion != CACHE_VERSION) {
return; // mis-matched cache, version, drop
}
for (quint32 i=0; i<count; ++i) {
AircraftItemPtr item(new AircraftItem);
item->fromDataStream(ds);
QFileInfo finfo(item->path);
if (finfo.exists() && (finfo.lastModified() == item->pathModTime)) {
// corresponding -set.xml file still exists and is
// unmodified
m_cachedItems[item->path] = item;
}
} // of cached item iteration
}
}
void writeCache()
{
QSettings settings;
QByteArray cacheData;
{
QDataStream ds(&cacheData, QIODevice::WriteOnly);
quint32 count = m_nextCache.count();
ds << CACHE_VERSION << count;
Q_FOREACH(AircraftItemPtr item, m_nextCache.values()) {
item->toDataStream(ds);
}
}
settings.setValue("aircraft-cache", cacheData);
}
void scanAircraftDir(QDir path)
{
QTime t;
t.start();
QStringList filters;
filters << "*-set.xml";
Q_FOREACH(QFileInfo child, path.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot)) {
QDir childDir(child.absoluteFilePath());
QMap<QString, AircraftItemPtr> baseAircraft;
QList<AircraftItemPtr> variants;
Q_FOREACH(QFileInfo xmlChild, childDir.entryInfoList(filters, QDir::Files)) {
try {
QString absolutePath = xmlChild.absoluteFilePath();
AircraftItemPtr item;
if (m_cachedItems.contains(absolutePath)) {
item = m_cachedItems.value(absolutePath);
} else {
item = AircraftItemPtr(new AircraftItem(childDir, absolutePath));
}
m_nextCache[absolutePath] = item;
if (item->excluded) {
continue;
}
if (item->isPrimary) {
baseAircraft.insert(item->baseName(), item);
} else {
variants.append(item);
}
} catch (sg_exception& e) {
continue;
}
if (m_done) {
return;
}
} // of set.xml iteration
// bind variants to their principals
Q_FOREACH(AircraftItemPtr item, variants) {
if (!baseAircraft.contains(item->variantOf)) {
qWarning() << "can't find principal aircraft " << item->variantOf << " for variant:" << item->path;
continue;
}
baseAircraft.value(item->variantOf)->variants.append(item);
}
// lock mutex while we modify the items array
{
QMutexLocker g(&m_lock);
m_items+=(baseAircraft.values().toVector());
}
emit addedItems();
} // of subdir iteration
}
QMutex m_lock;
QStringList m_dirs;
QVector<AircraftItemPtr> m_items;
QMap<QString, AircraftItemPtr > m_cachedItems;
QMap<QString, AircraftItemPtr > m_nextCache;
bool m_done;
};
std::unique_ptr<LocalAircraftCache> static_cacheInstance;
LocalAircraftCache* LocalAircraftCache::instance()
{
if (!static_cacheInstance) {
static_cacheInstance.reset(new LocalAircraftCache);
}
return static_cacheInstance.get();
}
LocalAircraftCache::LocalAircraftCache()
{
}
LocalAircraftCache::~LocalAircraftCache()
{
abandonCurrentScan();
}
void LocalAircraftCache::setPaths(QStringList paths)
{
m_paths = paths;
}
void LocalAircraftCache::scanDirs()
{
abandonCurrentScan();
QStringList dirs = m_paths;
Q_FOREACH(SGPath ap, globals->get_aircraft_paths()) {
dirs << QString::fromStdString(ap.utf8Str());
}
SGPath rootAircraft(globals->get_fg_root());
rootAircraft.append("Aircraft");
dirs << QString::fromStdString(rootAircraft.utf8Str());
m_scanThread = new AircraftScanThread(dirs);
connect(m_scanThread, &AircraftScanThread::finished, this,
&LocalAircraftCache::onScanFinished);
connect(m_scanThread, &AircraftScanThread::addedItems,
this, &LocalAircraftCache::onScanResults);
m_scanThread->start();
emit scanStarted();
}
int LocalAircraftCache::itemCount() const
{
return m_items.size();
}
AircraftItemPtr LocalAircraftCache::itemAt(int index) const
{
return 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));
if (item->path == path) {
return row;
}
// check variants too
for (int vr=0; vr < item->variants.size(); ++vr) {
if (item->variants.at(vr)->path == path) {
return row;
}
}
}
return -1;
}
QVector<AircraftItemPtr> LocalAircraftCache::newestItems(int count)
{
QVector<AircraftItemPtr> r;
r.reserve(count);
int total = m_items.size();
for (int i = total - count; i < count; ++i) {
r.push_back(m_items.at(i));
}
return r;
}
AircraftItemPtr LocalAircraftCache::findItemWithUri(QUrl aircraftUri) const
{
int index = findIndexWithUri(aircraftUri);
if (index >= 0) {
return m_items.at(index);
}
return {};
}
void LocalAircraftCache::abandonCurrentScan()
{
if (m_scanThread) {
m_scanThread->setDone();
m_scanThread->wait(1000);
delete m_scanThread;
m_scanThread = NULL;
}
}
void LocalAircraftCache::onScanResults()
{
QVector<AircraftItemPtr> newItems = m_scanThread->items();
if (newItems.isEmpty())
return;
m_items+=newItems;
emit addedItems(newItems.size());
}
void LocalAircraftCache::onScanFinished()
{
delete m_scanThread;
m_scanThread = nullptr;
emit scanCompleted();
}
bool LocalAircraftCache::isCandidateAircraftPath(QString path)
{
QStringList filters;
filters << "*-set.xml";
int dirCount = 0,
setXmlCount = 0;
QDir d(path);
Q_FOREACH(QFileInfo child, d.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot)) {
QDir childDir(child.absoluteFilePath());
++dirCount;
Q_FOREACH(QFileInfo xmlChild, childDir.entryInfoList(filters, QDir::Files)) {
++setXmlCount;
}
if ((setXmlCount > 0) || (dirCount > 10)) {
break;
}
}
return (setXmlCount > 0);
}
#include "LocalAircraftCache.moc"

View file

@ -0,0 +1,138 @@
// Written by James Turner, started October 2017
//
// Copyright (C) 2017 James Turner <zakalawe@mac.com>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#ifndef LOCALAIRCRAFTCACHE_HXX
#define LOCALAIRCRAFTCACHE_HXX
#include <QObject>
#include <QPixmap>
#include <QDateTime>
#include <QUrl>
#include <QSharedPointer>
#include <QDir>
#include <QVariant>
class QDataStream;
struct AircraftItem;
class AircraftScanThread;
typedef QSharedPointer<AircraftItem> AircraftItemPtr;
struct AircraftItem
{
AircraftItem();
AircraftItem(QDir dir, QString filePath);
// the file-name without -set.xml suffix
QString baseName() const;
void fromDataStream(QDataStream& ds);
void toDataStream(QDataStream& ds) const;
QPixmap thumbnail(bool loadIfRequired = true) const;
bool excluded = false;
QString path;
QString description;
QString longDescription;
QString authors;
int ratings[4] = {0, 0, 0, 0};
QString variantOf;
QDateTime pathModTime;
QList<AircraftItemPtr> variants;
bool usesHeliports = false;
bool usesSeaports = false;
QList<QUrl> previews;
bool isPrimary = false;
QString thumbnailPath;
QString minFGVersion;
bool needsMaintenance = false;
QVariant status(int variant);
private:
mutable QPixmap m_thumbnail;
};
class LocalAircraftCache : public QObject
{
Q_OBJECT
public:
~LocalAircraftCache();
static LocalAircraftCache* instance();
void setPaths(QStringList paths);
void scanDirs();
/**
* @helper to determine if a particular path is likely to contain
* aircraft or not. Checks for -set.xml files one level down in the tree.
*
*/
static bool isCandidateAircraftPath(QString path);
int itemCount() const;
AircraftItemPtr itemAt(int index) const;
AircraftItemPtr findItemWithUri(QUrl aircraftUri) const;
int findIndexWithUri(QUrl aircraftUri) const;
QVector<AircraftItemPtr> newestItems(int count);
QVariant aircraftStatus(AircraftItemPtr item) const;
enum AircraftStatus
{
AircraftOk = 0,
AircraftUnmaintained,
AircraftNeedsNewerSimulator,
AircraftNeedsOlderSimulator // won't ever occur for the moment
};
signals:
void scanStarted();
void scanCompleted();
void addedItems(int count);
public slots:
private slots:
void onScanResults();
void onScanFinished();
private:
explicit LocalAircraftCache();
void abandonCurrentScan();
QStringList m_paths;
AircraftScanThread* m_scanThread = nullptr;
QVector<AircraftItemPtr> m_items;
};
#endif // LOCALAIRCRAFTCACHE_HXX

View file

@ -12,6 +12,7 @@
#include "AircraftModel.hxx"
#include "InstallSceneryDialog.hxx"
#include "QtLauncher.hxx"
#include "LocalAircraftCache.hxx"
#include <Main/options.hxx>
#include <Main/globals.hxx>
@ -137,7 +138,7 @@ void AddOnsPage::onAddAircraftPath()
// to check for that case and handle it gracefully.
bool pathOk = false;
if (AircraftItemModel::isCandidateAircraftPath(path)) {
if (LocalAircraftCache::isCandidateAircraftPath(path)) {
m_ui->aircraftPathsList->addItem(path);
pathOk = true;
} else {
@ -145,7 +146,7 @@ void AddOnsPage::onAddAircraftPath()
QDir d(path);
if (d.exists("Aircraft")) {
QString p2 = d.filePath("Aircraft");
if (AircraftItemModel::isCandidateAircraftPath(p2)) {
if (LocalAircraftCache::isCandidateAircraftPath(p2)) {
m_ui->aircraftPathsList->addItem(p2);
pathOk = true;
}

View file

@ -25,7 +25,7 @@ public:
public slots:
void setPath(QString path)
{
if (_propertyPath == path.toStdString());
if (_propertyPath == path.toStdString())
return;
_propertyPath = path.toStdString();

224
src/GUI/QmlAircraftInfo.cxx Normal file
View file

@ -0,0 +1,224 @@
#include "QmlAircraftInfo.hxx"
#include <QVariant>
#include <QDebug>
#include <simgear/package/Install.hxx>
#include <Include/version.h>
#include "LocalAircraftCache.hxx"
QmlAircraftInfo::QmlAircraftInfo(QObject *parent) : QObject(parent)
{
}
QmlAircraftInfo::~QmlAircraftInfo()
{
}
int QmlAircraftInfo::numPreviews() const
{
return 0;
}
int QmlAircraftInfo::numVariants() const
{
if (_item) {
return _item->variants.size();
} else if (_package) {
return _package->variants().size();
}
return 0;
}
QString QmlAircraftInfo::name() const
{
if (_item) {
return resolveItem()->description;
} else if (_package) {
return QString::fromStdString(_package->nameForVariant(_variant));
}
return {};
}
QString QmlAircraftInfo::description() const
{
if (_item) {
return resolveItem()->longDescription;
} else if (_package) {
std::string longDesc = _package->getLocalisedProp("description", _variant);
return QString::fromStdString(longDesc).simplified();
}
return {};
}
QString QmlAircraftInfo::authors() const
{
if (_item) {
return resolveItem()->authors;
} else if (_package) {
std::string authors = _package->getLocalisedProp("author", _variant);
return QString::fromStdString(authors);
}
return {};
}
QVariantList QmlAircraftInfo::ratings() const
{
if (_item) {
QVariantList result;
auto actualItem = resolveItem();
for (int i=0; i<4; ++i) {
result << actualItem->ratings[i];
}
return result;
} else if (_package) {
SGPropertyNode* ratings = _package->properties()->getChild("rating");
if (!ratings) {
return {};
}
QVariantList result;
for (int i=0; i<4; ++i) {
result << ratings->getChild(i)->getIntValue();
}
return result;
}
return {};
}
QUrl QmlAircraftInfo::thumbnail() const
{
if (_item) {
return QUrl::fromLocalFile(resolveItem()->thumbnailPath);
} else if (_package) {
auto t = _package->thumbnailForVariant(_variant);
if (QFileInfo::exists(QString::fromStdString(t.path))) {
return QUrl::fromLocalFile(QString::fromStdString(t.path));
}
return QUrl(QString::fromStdString(t.url));
}
return {};
}
QString QmlAircraftInfo::pathOnDisk() const
{
if (_item) {
return resolveItem()->path;
} else if (_package) {
auto install = _package->existingInstall();
if (install.valid()) {
return QString::fromStdString(install->primarySetPath().utf8Str());
}
}
return {};
}
QString QmlAircraftInfo::packageId() const
{
if (_package) {
return QString::fromStdString(_package->variants()[_variant]);
}
return {};
}
int QmlAircraftInfo::packageSize() const
{
if (_package) {
return _package->fileSizeBytes();
}
return 0;
}
int QmlAircraftInfo::downloadedBytes() const
{
return 0;
}
QVariant QmlAircraftInfo::status() const
{
if (_item) {
return _item->status(_variant);
} else if (_package) {
return packageAircraftStatus(_package);
}
return {};
}
QString QmlAircraftInfo::minimumFGVersion() const
{
if (_item) {
return resolveItem()->minFGVersion;
} else if (_package) {
const std::string v = _package->properties()->getStringValue("minimum-fg-version");
if (!v.empty()) {
return QString::fromStdString(v);
}
}
return {};
}
AircraftItemPtr QmlAircraftInfo::resolveItem() const
{
if (_variant > 0) {
return _item->variants.at(_variant - 1);
}
return _item;
}
void QmlAircraftInfo::setUri(QUrl uri)
{
if (_uri == uri)
return;
_uri = uri;
emit uriChanged();
emit infoChanged();
}
void QmlAircraftInfo::requestInstallUpdate()
{
}
void QmlAircraftInfo::requestUninstall()
{
}
void QmlAircraftInfo::requestInstallCancel()
{
}
QVariant QmlAircraftInfo::packageAircraftStatus(simgear::pkg::PackageRef p)
{
if (p->hasTag("needs-maintenance")) {
return LocalAircraftCache::AircraftUnmaintained;
}
if (!p->properties()->hasChild("minimum-fg-version")) {
return LocalAircraftCache::AircraftOk;
}
const std::string minFGVersion = p->properties()->getStringValue("minimum-fg-version");
const int c = simgear::strutils::compare_versions(FLIGHTGEAR_VERSION, minFGVersion, 2);
return (c < 0) ? LocalAircraftCache::AircraftNeedsNewerSimulator :
LocalAircraftCache::AircraftOk;
}

View file

@ -0,0 +1,94 @@
#ifndef QMLAIRCRAFTINFO_HXX
#define QMLAIRCRAFTINFO_HXX
#include <QObject>
#include <QUrl>
#include <QSharedPointer>
#include <simgear/package/Catalog.hxx>
#include <simgear/package/Package.hxx>
struct AircraftItem;
typedef QSharedPointer<AircraftItem> AircraftItemPtr;
class QmlAircraftInfo : public QObject
{
Q_OBJECT
Q_PROPERTY(QUrl uri READ uri WRITE setUri NOTIFY uriChanged)
Q_PROPERTY(int numPreviews READ numPreviews NOTIFY infoChanged)
Q_PROPERTY(int numVariants READ numVariants NOTIFY infoChanged)
Q_PROPERTY(QString name READ name NOTIFY infoChanged)
Q_PROPERTY(QString description READ description NOTIFY infoChanged)
Q_PROPERTY(QString authors READ authors NOTIFY infoChanged)
Q_PROPERTY(QUrl thumbnail READ thumbnail NOTIFY infoChanged)
Q_PROPERTY(QString pathOnDisk READ pathOnDisk NOTIFY infoChanged)
Q_PROPERTY(QString packageId READ packageId NOTIFY infoChanged)
Q_PROPERTY(int packageSize READ packageSize NOTIFY infoChanged)
Q_PROPERTY(int downloadedBytes READ downloadedBytes NOTIFY downloadChanged)
Q_PROPERTY(QVariant status READ status NOTIFY infoChanged)
Q_PROPERTY(QString minimumFGVersion READ minimumFGVersion NOTIFY infoChanged)
Q_INVOKABLE void requestInstallUpdate();
Q_INVOKABLE void requestUninstall();
Q_INVOKABLE void requestInstallCancel();
Q_PROPERTY(QVariantList ratings READ ratings NOTIFY infoChanged)
public:
explicit QmlAircraftInfo(QObject *parent = nullptr);
virtual ~QmlAircraftInfo();
QUrl uri() const
{
return _uri;
}
int numPreviews() const;
int numVariants() const;
QString name() const;
QString description() const;
QString authors() const;
QVariantList ratings() const;
QUrl thumbnail() const;
QString pathOnDisk() const;
QString packageId() const;
int packageSize() const;
int downloadedBytes() const;
QVariant status() const;
QString minimumFGVersion() const;
static QVariant packageAircraftStatus(simgear::pkg::PackageRef p);
signals:
void uriChanged();
void infoChanged();
void downloadChanged();
public slots:
void setUri(QUrl uri);
private:
QUrl _uri;
simgear::pkg::PackageRef _package;
AircraftItemPtr _item;
int _variant = 0;
AircraftItemPtr resolveItem() const;
};
#endif // QMLAIRCRAFTINFO_HXX

View file

@ -35,6 +35,7 @@
<file>Button.qml</file>
<file>AircraftWarningPanel.qml</file>
<file>Scrollbar.qml</file>
<file>AircraftDetailsView.qml</file>
</qresource>
<qresource prefix="/preview">
<file alias="close-icon">preview-close.png</file>