From 18a898f5f97588ec249e2ddc272e95f1b5b3bff0 Mon Sep 17 00:00:00 2001 From: James Turner Date: Mon, 3 Aug 2015 15:53:56 -0500 Subject: [PATCH] Lots of work on aircraft package support --- src/GUI/AddCatalogDialog.cxx | 10 +- src/GUI/AircraftItemDelegate.cxx | 91 ++++++-- src/GUI/AircraftItemDelegate.hxx | 6 +- src/GUI/AircraftModel.cxx | 344 +++++++++++++++++++++++++++---- src/GUI/AircraftModel.hxx | 56 ++++- src/GUI/CatalogListModel.cxx | 2 +- src/GUI/PathsDialog.cxx | 13 +- src/GUI/QtLauncher.cxx | 138 ++++++++++--- src/GUI/QtLauncher.hxx | 23 ++- src/Main/fg_init.cxx | 8 +- src/Network/HTTPClient.cxx | 64 +++--- src/Network/HTTPClient.hxx | 3 + 12 files changed, 621 insertions(+), 137 deletions(-) diff --git a/src/GUI/AddCatalogDialog.cxx b/src/GUI/AddCatalogDialog.cxx index 6d7665a6e..abca2c51d 100644 --- a/src/GUI/AddCatalogDialog.cxx +++ b/src/GUI/AddCatalogDialog.cxx @@ -88,7 +88,7 @@ void AddCatalogDialog::updateUi() "%2 aircraft are included in this hangar.").arg(catDesc).arg(m_result->packages().size()); ui->resultsSummaryLabel->setText(s); } else if (m_state == STATE_DOWNLOAD_FAILED) { - Delegate::FailureCode code = m_result->status(); + Delegate::StatusCode code = m_result->status(); qWarning() << Q_FUNC_INFO << "failed with code" << code; QString s; switch (code) { @@ -98,7 +98,7 @@ void AddCatalogDialog::updateUi() break; case Delegate::FAIL_VERSION: - s = tr("The provided hangar is for a different version of FLightGear. " + s = tr("The provided hangar is for a different version of FlightGear. " "(This is version %1)").arg(QString::fromUtf8(FLIGHTGEAR_VERSION)); break; @@ -152,14 +152,14 @@ void AddCatalogDialog::reject() void AddCatalogDialog::onCatalogStatusChanged(Catalog* cat) { - Delegate::FailureCode s = cat->status(); + Delegate::StatusCode s = cat->status(); qDebug() << Q_FUNC_INFO << "cat status:" << s; switch (s) { - case Delegate::CATALOG_REFRESHED: + case Delegate::STATUS_REFRESHED: m_state = STATE_FINISHED; break; - case Delegate::FAIL_IN_PROGRESS: + case Delegate::STATUS_IN_PROGRESS: // don't jump to STATE_FINISHED return; diff --git a/src/GUI/AircraftItemDelegate.cxx b/src/GUI/AircraftItemDelegate.cxx index 51a3701be..73d1d5602 100644 --- a/src/GUI/AircraftItemDelegate.cxx +++ b/src/GUI/AircraftItemDelegate.cxx @@ -143,29 +143,79 @@ void AircraftItemDelegate::paint(QPainter * painter, const QStyleOptionViewItem QVariant v = index.data(AircraftPackageStatusRole); AircraftItemStatus status = static_cast(v.toInt()); - // status = PackageNotInstalled; + double downloadFraction = 0.0; + if (status != PackageInstalled) { + QString buttonText, infoText; + QColor buttonColor(27, 122, 211); + + double sizeInMBytes = index.data(AircraftPackageSizeRole).toInt(); + sizeInMBytes /= 0x100000; + + if (status == PackageDownloading) { + buttonText = tr("Cancel"); + double downloadedMB = index.data(AircraftInstallDownloadedSizeRole).toInt(); + downloadedMB /= 0x100000; + infoText = QStringLiteral("%1 MB of %2 MB").arg(downloadedMB, 0, 'f', 1).arg(sizeInMBytes, 0, 'f', 1); + buttonColor = QColor(0xcf, 0xcf, 0xcf); + downloadFraction = downloadedMB / sizeInMBytes; + } else if (status == PackageQueued) { + buttonText = tr("Cancel"); + infoText = tr("Waiting to download %1 MB").arg(sizeInMBytes, 0, 'f', 1); + buttonColor = QColor(0xcf, 0xcf, 0xcf); + } else { + infoText = QStringLiteral("%1MB").arg(sizeInMBytes, 0, 'f', 1); + if (status == PackageNotInstalled) { + buttonText = "Install"; + } else if (status == PackageUpdateAvailable) { + buttonText = "Update"; + } + } + painter->setBrush(Qt::NoBrush); QRect buttonRect = packageButtonRect(option.rect, index); painter->setPen(Qt::NoPen); - painter->setBrush(QColor(27, 122, 211)); + painter->setBrush(buttonColor); painter->drawRoundedRect(buttonRect, 5, 5); painter->setPen(Qt::white); - - if (status == PackageNotInstalled) { - painter->drawText(buttonRect, Qt::AlignCenter, "Install"); - } else if (status == PackageUpdateAvailable) { - painter->drawText(buttonRect, Qt::AlignCenter, "Update"); + painter->drawText(buttonRect, Qt::AlignCenter, buttonText); + + QRect infoTextRect = buttonRect; + infoTextRect.setLeft(buttonRect.right() + MARGIN); + infoTextRect.setWidth(200); + + if (status == PackageDownloading) { + QRect progressRect = infoTextRect; + progressRect.setHeight(6); + painter->setPen(QPen(QColor(0xcf, 0xcf, 0xcf), 0)); + painter->setBrush(Qt::NoBrush); + painter->drawRoundedRect(progressRect, 3, 3); + infoTextRect.setTop(progressRect.bottom() + 1); + + QRect progressBarRect = progressRect.marginsRemoved(QMargins(2, 2, 2, 2)); + + progressBarRect.setWidth(static_cast(progressBarRect.width() * downloadFraction)); + + painter->setBrush(QColor(27, 122, 211)); + painter->setPen(Qt::NoPen); + painter->drawRoundedRect(progressBarRect, 2, 2); } - } + + painter->setPen(Qt::black); + painter->drawText(infoTextRect, Qt::AlignLeft | Qt::AlignVCenter, infoText); + } // of update / install / download status } QSize AircraftItemDelegate::sizeHint(const QStyleOptionViewItem & option, const QModelIndex & index) const { QRect contentRect = option.rect.adjusted(MARGIN, MARGIN, -MARGIN, -MARGIN); - QPixmap thumbnail = index.data(Qt::DecorationRole).value(); - contentRect.setLeft(contentRect.left() + MARGIN + thumbnail.width()); + const int THUMBNAIL_WIDTH = 172; + // don't request the thumbnail here for remote sources. Assume the default + //QPixmap thumbnail = index.data(Qt::DecorationRole).value(); + //contentRect.setLeft(contentRect.left() + MARGIN + thumbnail.width()); + contentRect.setLeft(contentRect.left() + MARGIN + THUMBNAIL_WIDTH); + QFont f; f.setPointSize(18); QFontMetrics metrics(f); @@ -209,10 +259,10 @@ bool AircraftItemDelegate::eventFilter( QObject*, QEvent* event ) QModelIndex index = m_view->indexAt( me->pos() ); int variantCount = index.data(AircraftVariantCountRole).toInt(); int variantIndex = index.data(AircraftVariantRole).toInt(); + QRect vr = m_view->visualRect(index); if ( (event->type() == QEvent::MouseButtonRelease) && (variantCount > 0) ) { - QRect vr = m_view->visualRect(index); QRect leftCycleRect = leftCycleArrowRect(vr, index), rightCycleRect = rightCycleArrowRect(vr, index); @@ -226,6 +276,22 @@ bool AircraftItemDelegate::eventFilter( QObject*, QEvent* event ) return true; } } + + if ((event->type() == QEvent::MouseButtonRelease) && + packageButtonRect(vr, index).contains(me->pos())) + { + QVariant v = index.data(AircraftPackageStatusRole); + AircraftItemStatus status = static_cast(v.toInt()); + if (status == PackageNotInstalled) { + emit requestInstall(index); + } else if ((status == PackageDownloading) || (status == PackageQueued)) { + emit cancelDownload(index); + } else if (status == PackageUpdateAvailable) { + emit requestInstall(index); + } + + return true; + } } else if ( event->type() == QEvent::MouseMove ) { QMouseEvent* me = static_cast< QMouseEvent* >( event ); QModelIndex index = m_view->indexAt( me->pos() ); @@ -273,7 +339,8 @@ QRect AircraftItemDelegate::packageButtonRect(const QRect& visualRect, const QMo QPixmap thumbnail = index.data(Qt::DecorationRole).value(); contentRect.setLeft(contentRect.left() + MARGIN + thumbnail.width()); - return QRect(contentRect.left() + ARROW_SIZE, contentRect.bottom() - 24, 60, BUTTON_HEIGHT); + return QRect(contentRect.left() + ARROW_SIZE, contentRect.bottom() - 24, + BUTTON_WIDTH, BUTTON_HEIGHT); } void AircraftItemDelegate::drawRating(QPainter* painter, QString label, const QRect& box, int value) const diff --git a/src/GUI/AircraftItemDelegate.hxx b/src/GUI/AircraftItemDelegate.hxx index f2a398872..436e49c35 100644 --- a/src/GUI/AircraftItemDelegate.hxx +++ b/src/GUI/AircraftItemDelegate.hxx @@ -32,7 +32,8 @@ public: static const int MARGIN = 4; static const int ARROW_SIZE = 20; static const int BUTTON_HEIGHT = 24; - + static const int BUTTON_WIDTH = 80; + AircraftItemDelegate(QListView* view); virtual void paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const; @@ -44,6 +45,9 @@ public: Q_SIGNALS: void variantChanged(const QModelIndex& index); + void requestInstall(const QModelIndex& index); + + void cancelDownload(const QModelIndex& index); private: QRect leftCycleArrowRect(const QRect& visualRect, const QModelIndex& index) const; QRect rightCycleArrowRect(const QRect& visualRect, const QModelIndex& index) const; diff --git a/src/GUI/AircraftModel.cxx b/src/GUI/AircraftModel.cxx index 918a6b04b..85735c059 100644 --- a/src/GUI/AircraftModel.cxx +++ b/src/GUI/AircraftModel.cxx @@ -27,6 +27,7 @@ #include #include #include +#include // Simgear #include @@ -39,6 +40,8 @@ // FlightGear #include
+const int STANDARD_THUMBNAIL_HEIGHT = 128; + using namespace simgear::pkg; AircraftItem::AircraftItem() : @@ -123,8 +126,8 @@ QPixmap AircraftItem::thumbnail() const if (dir.exists("thumbnail.jpg")) { m_thumbnail.load(dir.filePath("thumbnail.jpg")); // resize to the standard size - if (m_thumbnail.height() > 128) { - m_thumbnail = m_thumbnail.scaledToHeight(128); + if (m_thumbnail.height() > STANDARD_THUMBNAIL_HEIGHT) { + m_thumbnail = m_thumbnail.scaledToHeight(STANDARD_THUMBNAIL_HEIGHT); } } } @@ -150,9 +153,9 @@ public: } /** thread-safe access to items already scanned */ - QList items() + QVector items() { - QList result; + QVector result; QMutexLocker g(&m_lock); result.swap(m_items); g.unlock(); @@ -196,13 +199,11 @@ private: } for (int i=0; ifromDataStream(ds); QFileInfo finfo(item->path); - if (!finfo.exists() || (finfo.lastModified() != item->pathModTime)) { - delete item; - } else { + if (finfo.exists() && (finfo.lastModified() == item->pathModTime)) { // corresponding -set.xml file still exists and is // unmodified m_cachedItems[item->path] = item; @@ -220,7 +221,7 @@ private: quint32 count = m_nextCache.count(); ds << CACHE_VERSION << count; - Q_FOREACH(AircraftItem* item, m_nextCache.values()) { + Q_FOREACH(AircraftItemPtr item, m_nextCache.values()) { item->toDataStream(ds); } } @@ -237,18 +238,18 @@ private: filters << "*-set.xml"; Q_FOREACH(QFileInfo child, path.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot)) { QDir childDir(child.absoluteFilePath()); - QMap baseAircraft; - QList variants; + QMap baseAircraft; + QList variants; Q_FOREACH(QFileInfo xmlChild, childDir.entryInfoList(filters, QDir::Files)) { try { QString absolutePath = xmlChild.absoluteFilePath(); - AircraftItem* item = NULL; + AircraftItemPtr item; if (m_cachedItems.contains(absolutePath)) { item = m_cachedItems.value(absolutePath); } else { - item = new AircraftItem(childDir, absolutePath); + item = AircraftItemPtr(new AircraftItem(childDir, absolutePath)); } m_nextCache[absolutePath] = item; @@ -272,10 +273,9 @@ private: } // of set.xml iteration // bind variants to their principals - Q_FOREACH(AircraftItem* item, variants) { + Q_FOREACH(AircraftItemPtr item, variants) { if (!baseAircraft.contains(item->variantOf)) { qWarning() << "can't find principal aircraft " << item->variantOf << " for variant:" << item->path; - delete item; continue; } @@ -285,7 +285,7 @@ private: // lock mutex while we modify the items array { QMutexLocker g(&m_lock); - m_items.append(baseAircraft.values()); + m_items.append(baseAircraft.values().toVector()); } emit addedItems(); @@ -294,19 +294,122 @@ private: QMutex m_lock; QStringList m_dirs; - QList m_items; + QVector m_items; - QMap m_cachedItems; - QMap m_nextCache; + QMap m_cachedItems; + QMap m_nextCache; bool m_done; }; +class PackageDelegate : public simgear::pkg::Delegate +{ +public: + PackageDelegate(AircraftItemModel* model) : + m_model(model) + { + m_model->m_packageRoot->addDelegate(this); + } + + ~PackageDelegate() + { + m_model->m_packageRoot->removeDelegate(this); + } + +protected: + virtual void catalogRefreshed(CatalogRef aCatalog, StatusCode aReason) + { + if (aReason == STATUS_IN_PROGRESS) { + qDebug() << "doing refresh of" << QString::fromStdString(aCatalog->url()); + } else if ((aReason == STATUS_REFRESHED) || (aReason == STATUS_SUCCESS)) { + m_model->refreshPackages(); + } else { + qWarning() << "failed refresh of " + << QString::fromStdString(aCatalog->url()) << ":" << aReason << endl; + } + } + + virtual void startInstall(InstallRef aInstall) + { + QModelIndex mi(indexForPackage(aInstall->package())); + m_model->dataChanged(mi, mi); + } + + virtual void installProgress(InstallRef aInstall, unsigned int bytes, unsigned int total) + { + Q_UNUSED(bytes); + Q_UNUSED(total); + QModelIndex mi(indexForPackage(aInstall->package())); + m_model->dataChanged(mi, mi); + } + + virtual void finishInstall(InstallRef aInstall, StatusCode aReason) + { + QModelIndex mi(indexForPackage(aInstall->package())); + m_model->dataChanged(mi, mi); + + if ((aReason != USER_CANCELLED) && (aReason != STATUS_SUCCESS)) { + m_model->installFailed(mi, aReason); + } + + if (aReason == STATUS_SUCCESS) { + m_model->installSucceeded(mi); + } + } + + virtual void dataForThumbnail(const std::string& aThumbnailUrl, + size_t length, const uint8_t* bytes) + { + QImage img = QImage::fromData(QByteArray::fromRawData(reinterpret_cast(bytes), length)); + if (img.isNull()) { + qWarning() << "failed to load image data for URL:" << + QString::fromStdString(aThumbnailUrl); + return; + } + + m_model->m_thumbnailPixmapCache.insert(QString::fromStdString(aThumbnailUrl), + QPixmap::fromImage(img)); + + // notify any affected items. Linear scan here avoids another map/dict + // structure. + PackageList::const_iterator it; + int i = 0; + + for (it=m_model->m_packages.begin(); it != m_model->m_packages.end(); ++it, ++i) { + const string_list& urls((*it)->thumbnailUrls()); + string_list::const_iterator cit = std::find(urls.begin(), urls.end(), aThumbnailUrl); + if (cit != urls.end()) { + QModelIndex mi(m_model->index(i + m_model->m_items.size())); + m_model->dataChanged(mi, mi); + } + } // of packages iteration + } + +private: + QModelIndex indexForPackage(const PackageRef& ref) const + { + PackageList::const_iterator it = std::find(m_model->m_packages.begin(), + m_model->m_packages.end(), + ref); + if (it == m_model->m_packages.end()) { + return QModelIndex(); + } + + size_t offset = it - m_model->m_packages.begin(); + return m_model->index(offset + m_model->m_items.size()); + } + + AircraftItemModel* m_model; +}; + AircraftItemModel::AircraftItemModel(QObject* pr, simgear::pkg::RootRef& rootRef) : QAbstractListModel(pr), m_scanThread(NULL), m_packageRoot(rootRef) { + new PackageDelegate(this); + // packages may already be refreshed, so pull now + refreshPackages(); } AircraftItemModel::~AircraftItemModel() @@ -324,7 +427,6 @@ void AircraftItemModel::scanDirs() abandonCurrentScan(); beginResetModel(); - qDeleteAll(m_items); m_items.clear(); m_activeVariant.clear(); endResetModel(); @@ -345,7 +447,6 @@ void AircraftItemModel::scanDirs() connect(m_scanThread, &AircraftScanThread::addedItems, this, &AircraftItemModel::onScanResults); m_scanThread->start(); - } void AircraftItemModel::abandonCurrentScan() @@ -358,18 +459,51 @@ void AircraftItemModel::abandonCurrentScan() } } +void AircraftItemModel::refreshPackages() +{ + beginResetModel(); + m_packages = m_packageRoot->allPackages(); + m_packageVariant.resize(m_packages.size()); + endResetModel(); +} + +int AircraftItemModel::rowCount(const QModelIndex& parent) const +{ + return m_items.size() + m_packages.size(); +} + QVariant AircraftItemModel::data(const QModelIndex& index, int role) const { - if (role == AircraftVariantRole) { - return m_activeVariant.at(index.row()); - } + if (index.row() >= m_items.size()) { + quint32 packageIndex = index.row() - m_items.size(); + + if (role == AircraftVariantRole) { + return m_packageVariant.at(packageIndex); + } + + const PackageRef& pkg(m_packages[packageIndex]); + InstallRef ex = pkg->existingInstall(); + + if (role == AircraftInstallPercentRole) { + return ex.valid() ? ex->downloadedPercent() : 0; + } else if (role == AircraftInstallDownloadedSizeRole) { + return static_cast(ex.valid() ? ex->downloadedBytes() : 0); + } + + quint32 variantIndex = m_packageVariant.at(packageIndex); + return dataFromPackage(pkg, variantIndex, role); + } else { + if (role == AircraftVariantRole) { + return m_activeVariant.at(index.row()); + } - const AircraftItem* item(m_items.at(index.row())); - quint32 variantIndex = m_activeVariant.at(index.row()); - return dataFromItem(item, variantIndex, role); + quint32 variantIndex = m_activeVariant.at(index.row()); + const AircraftItemPtr item(m_items.at(index.row())); + return dataFromItem(item, variantIndex, role); + } } -QVariant AircraftItemModel::dataFromItem(const AircraftItem* item, quint32 variantIndex, int role) const +QVariant AircraftItemModel::dataFromItem(AircraftItemPtr item, quint32 variantIndex, int role) const { if (role == AircraftVariantCountRole) { return item->variants.count(); @@ -410,6 +544,8 @@ QVariant AircraftItemModel::dataFromItem(const AircraftItem* item, quint32 varia return PackageInstalled; // always the case } else if (role == Qt::ToolTipRole) { return item->path; + } else if (role == AircraftURIRole) { + return QUrl::fromLocalFile(item->path); } else if (role == AircraftHasRatingsRole) { bool have = false; for (int i=0; i<4; ++i) { @@ -430,19 +566,28 @@ QVariant AircraftItemModel::dataFromItem(const AircraftItem* item, quint32 varia QVariant AircraftItemModel::dataFromPackage(const PackageRef& item, quint32 variantIndex, int role) const { + if (role == Qt::DecorationRole) { + role = AircraftThumbnailRole; // use first thumbnail + } + if (role == Qt::DisplayRole) { return QString::fromStdString(item->name()); } else if (role == AircraftPathRole) { - // can we return the theoretical path? + InstallRef i = item->existingInstall(); + if (i.valid()) { + return QString::fromStdString(i->primarySetPath().str()); + } } else if (role == AircraftPackageIdRole) { return QString::fromStdString(item->id()); } else if (role == AircraftPackageStatusRole) { - bool installed = item->isInstalled(); - if (installed) { - InstallRef i = item->existingInstall(); + InstallRef i = item->existingInstall(); + if (i.valid()) { if (i->isDownloading()) { return PackageDownloading; } + if (i->isQueued()) { + return PackageQueued; + } if (i->hasUpdate()) { return PackageUpdateAvailable; } @@ -451,13 +596,72 @@ QVariant AircraftItemModel::dataFromPackage(const PackageRef& item, quint32 vari } else { return PackageNotInstalled; } + } else if (role >= AircraftThumbnailRole) { + return packageThumbnail(item , role - AircraftThumbnailRole); + } else if (role == AircraftAuthorsRole) { + SGPropertyNode* authors = item->properties()->getChild("author"); + if (authors) { + return QString::fromStdString(authors->getStringValue()); + } } else if (role == AircraftLongDescriptionRole) { return QString::fromStdString(item->description()); + } else if (role == AircraftPackageSizeRole) { + return static_cast(item->fileSizeBytes()); + } else if (role == AircraftURIRole) { + return QUrl("package:" + QString::fromStdString(item->qualifiedId())); + } else if (role == AircraftHasRatingsRole) { + return item->properties()->hasChild("rating"); + } else if ((role >= AircraftRatingRole) && (role < AircraftVariantDescriptionRole)) { + int ratingIndex = role - AircraftRatingRole; + SGPropertyNode* ratings = item->properties()->getChild("rating"); + if (!ratings) { + return QVariant(); + } + return ratings->getChild(ratingIndex)->getIntValue(); } return QVariant(); } +QVariant AircraftItemModel::packageThumbnail(PackageRef p, int index) const +{ + const string_list& thumbnails(p->thumbnailUrls()); + if (index >= thumbnails.size()) { + return QVariant(); + } + + std::string thumbnailUrl = thumbnails.at(index); + QString urlQString(QString::fromStdString(thumbnailUrl)); + if (m_thumbnailPixmapCache.contains(urlQString)) { + // cache hit, easy + return m_thumbnailPixmapCache.value(urlQString); + } + +// check the on-disk store. This relies on the order of thumbnails in the +// results of thumbnailUrls and thumbnails corresponding + InstallRef ex = p->existingInstall(); + if (ex.valid()) { + const string_list& thumbNames(p->thumbnails()); + if (!thumbNames.empty()) { + SGPath path(ex->path()); + path.append(p->thumbnails()[index]); + if (path.exists()) { + QPixmap pix; + pix.load(QString::fromStdString(path.str())); + // resize to the standard size + if (pix.height() > STANDARD_THUMBNAIL_HEIGHT) { + pix = pix.scaledToHeight(STANDARD_THUMBNAIL_HEIGHT); + } + m_thumbnailPixmapCache[urlQString] = pix; + return pix; + } + } // of have thumbnail file names + } // of have existing install + + m_packageRoot->requestThumbnailData(thumbnailUrl); + return QVariant(); +} + bool AircraftItemModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (role == AircraftVariantRole) { @@ -469,21 +673,36 @@ bool AircraftItemModel::setData(const QModelIndex &index, const QVariant &value, return false; } -QModelIndex AircraftItemModel::indexOfAircraftPath(QString path) const +QModelIndex AircraftItemModel::indexOfAircraftURI(QUrl uri) const { - for (int row=0; row path == path) { - return index(row); + if (uri.isLocalFile()) { + QString path = uri.toLocalFile(); + for (int row=0; row path == path) { + return index(row); + } + } + } else if (uri.scheme() == "package") { + QString ident = uri.path(); + PackageRef pkg = m_packageRoot->getPackageById(ident.toStdString()); + if (pkg) { + for (int i=0; i < m_packages.size(); ++i) { + if (m_packages[i] == pkg) { + return index(m_items.size() + i); + } + } // of linear package scan } + } else { + qWarning() << "Unknown aircraft URI scheme" << uri << uri.scheme(); } - + return QModelIndex(); } void AircraftItemModel::onScanResults() { - QList newItems = m_scanThread->items(); + QVector newItems = m_scanThread->items(); if (newItems.isEmpty()) return; @@ -505,4 +724,51 @@ void AircraftItemModel::onScanFinished() m_scanThread = NULL; } +void AircraftItemModel::installFailed(QModelIndex index, simgear::pkg::Delegate::StatusCode reason) +{ + Q_ASSERT(index.row() >= m_items.size()); + + QString msg; + switch (reason) { + case Delegate::FAIL_CHECKSUM: + msg = tr("Invalid package checksum"); break; + case Delegate::FAIL_DOWNLOAD: + msg = tr("Download failed"); break; + case Delegate::FAIL_EXTRACT: + msg = tr("Package could not be extracted"); break; + case Delegate::FAIL_FILESYSTEM: + msg = tr("A local file-system error occurred"); break; + case Delegate::FAIL_UNKNOWN: + default: + msg = tr("Unknown reason"); + } + + quint32 packageIndex = index.row() - m_items.size(); + const PackageRef& pkg(m_packages[packageIndex]); + QString packageName = QString::fromStdString(pkg->description()); + emit aircraftInstallFailed(index, tr("Failed installation of package '%1': %2").arg(packageName).arg(msg)); +} + +void AircraftItemModel::installSucceeded(QModelIndex index) +{ + qDebug() << Q_FUNC_INFO << index; + emit aircraftInstallCompleted(index); +} + +bool AircraftItemModel::isIndexRunnable(const QModelIndex& index) const +{ + if (index.row() < m_items.size()) { + return true; // local file, always runnable + } + + quint32 packageIndex = index.row() - m_items.size(); + const PackageRef& pkg(m_packages[packageIndex]); + InstallRef ex = pkg->existingInstall(); + if (!ex.valid()) { + return false; // not installed + } + + return !ex->isDownloading(); +} + #include "AircraftModel.moc" diff --git a/src/GUI/AircraftModel.hxx b/src/GUI/AircraftModel.hxx index c1bb77cf0..bb85158c8 100644 --- a/src/GUI/AircraftModel.hxx +++ b/src/GUI/AircraftModel.hxx @@ -26,6 +26,8 @@ #include #include #include +#include +#include #include @@ -39,6 +41,10 @@ const int AircraftPackageStatusRole = Qt::UserRole + 7; const int AircraftPackageProgressRole = Qt::UserRole + 8; const int AircraftLongDescriptionRole = Qt::UserRole + 9; const int AircraftHasRatingsRole = Qt::UserRole + 10; +const int AircraftInstallPercentRole = Qt::UserRole + 11; +const int AircraftPackageSizeRole = Qt::UserRole + 12; +const int AircraftInstallDownloadedSizeRole = Qt::UserRole + 13; +const int AircraftURIRole = Qt::UserRole + 14; const int AircraftRatingRole = Qt::UserRole + 100; const int AircraftVariantDescriptionRole = Qt::UserRole + 200; @@ -47,6 +53,9 @@ const int AircraftThumbnailRole = Qt::UserRole + 300; class AircraftScanThread; class QDataStream; +struct AircraftItem; +typedef QSharedPointer AircraftItemPtr; + struct AircraftItem { AircraftItem(); @@ -70,7 +79,7 @@ struct AircraftItem QString variantOf; QDateTime pathModTime; - QList variants; + QList variants; private: mutable QPixmap m_thumbnail; }; @@ -80,6 +89,7 @@ enum AircraftItemStatus { PackageNotInstalled, PackageInstalled, PackageUpdateAvailable, + PackageQueued, PackageDownloading }; @@ -95,11 +105,8 @@ public: void scanDirs(); - virtual int rowCount(const QModelIndex& parent) const - { - return m_items.size(); - } - + virtual int rowCount(const QModelIndex& parent) const; + virtual QVariant data(const QModelIndex& index, int role) const; virtual bool setData(const QModelIndex &index, const QVariant &value, int role); @@ -108,26 +115,53 @@ public: * given a -set.xml path, return the corresponding model index, if one * exists. */ - QModelIndex indexOfAircraftPath(QString path) const; + // QModelIndex indexOfAircraftPath(QString path) const; + QModelIndex indexOfAircraftURI(QUrl uri) const; + + /** + * return if a given aircraft is ready to be run, or not. Aircraft which + * are not installed, or are downloading, are not runnable. + */ + bool isIndexRunnable(const QModelIndex& index) const; + +signals: + void aircraftInstallFailed(QModelIndex index, QString errorMessage); + + void aircraftInstallCompleted(QModelIndex index); + private slots: void onScanResults(); void onScanFinished(); private: - QVariant dataFromItem(const AircraftItem* item, quint32 variantIndex, int role) const; + friend class PackageDelegate; + + QVariant dataFromItem(AircraftItemPtr item, quint32 variantIndex, int role) const; QVariant dataFromPackage(const simgear::pkg::PackageRef& item, quint32 variantIndex, int role) const; + QVariant packageThumbnail(simgear::pkg::PackageRef p, int index) const; + void abandonCurrentScan(); - + void refreshPackages(); + + void installSucceeded(QModelIndex index); + void installFailed(QModelIndex index, simgear::pkg::Delegate::StatusCode reason); + QStringList m_paths; AircraftScanThread* m_scanThread; - QList m_items; - QList m_activeVariant; + QVector m_items; + + QVector m_activeVariant; + QVector m_packageVariant; + simgear::pkg::RootRef m_packageRoot; + simgear::pkg::PackageList m_packages; + + mutable QHash m_thumbnailPixmapCache; }; #endif // of FG_GUI_AIRCRAFT_MODEL diff --git a/src/GUI/CatalogListModel.cxx b/src/GUI/CatalogListModel.cxx index a6533efb9..9fd830293 100644 --- a/src/GUI/CatalogListModel.cxx +++ b/src/GUI/CatalogListModel.cxx @@ -59,7 +59,7 @@ QVariant CatalogListModel::data(const QModelIndex& index, int role) const simgear::pkg::CatalogRef cat = m_packageRoot->catalogs().at(index.row()); if (role == Qt::DisplayRole) { - return QString::fromStdString(cat->description()); + return QString::fromStdString(cat->description()).trimmed(); } else if (role == Qt::ToolTipRole) { return QString::fromStdString(cat->url()); } else if (role == CatalogUrlRole) { diff --git a/src/GUI/PathsDialog.cxx b/src/GUI/PathsDialog.cxx index 725d38d90..3abc281a8 100644 --- a/src/GUI/PathsDialog.cxx +++ b/src/GUI/PathsDialog.cxx @@ -3,6 +3,7 @@ #include #include +#include #include "CatalogListModel.hxx" #include "AddCatalogDialog.hxx" @@ -128,7 +129,17 @@ void PathsDialog::onAddCatalog() void PathsDialog::onRemoveCatalog() { - + QModelIndex mi = m_ui->catalogsList->currentIndex(); + if (mi.isValid()) { + QMessageBox mb; + mb.setText(QStringLiteral("Remove aircraft hangar '%1'?").arg(mi.data(Qt::DisplayRole).toString())); + mb.setStandardButtons(QMessageBox::Yes | QMessageBox::No); + mb.setDefaultButton(QMessageBox::No); + mb.exec(); + + QString pkgId = mi.data(CatalogIdRole).toString(); + m_packageRoot->removeCatalogById(pkgId.toStdString()); + } } void PathsDialog::onChangeDownloadDir() diff --git a/src/GUI/QtLauncher.cxx b/src/GUI/QtLauncher.cxx index b67ea3639..adcac2637 100644 --- a/src/GUI/QtLauncher.cxx +++ b/src/GUI/QtLauncher.cxx @@ -48,6 +48,9 @@ #include #include #include +#include +#include +#include #include "ui_Launcher.h" #include "EditRatingsFilterDialog.hxx" @@ -65,6 +68,7 @@ #include using namespace flightgear; +using namespace simgear::pkg; const int MAX_RECENT_AIRPORTS = 32; const int MAX_RECENT_AIRCRAFT = 20; @@ -457,8 +461,6 @@ QtLauncher::QtLauncher() : connect(m_ui->aircraftHistory, &QPushButton::clicked, this, &QtLauncher::onPopupAircraftHistory); - restoreSettings(); - QAction* qa = new QAction(this); qa->setShortcut(QKeySequence("Ctrl+Q")); connect(qa, &QAction::triggered, this, &QtLauncher::onQuit); @@ -495,7 +497,7 @@ QtLauncher::QtLauncher() : updateSettingsSummary(); fgInitPackageRoot(); - simgear::pkg::RootRef r(globals->packageRoot()); + RootRef r(globals->packageRoot()); FGHTTPClient* http = new FGHTTPClient; globals->add_subsystem("http", http); @@ -521,10 +523,21 @@ QtLauncher::QtLauncher() : this, &QtLauncher::onAircraftSelected); connect(delegate, &AircraftItemDelegate::variantChanged, this, &QtLauncher::onAircraftSelected); - + connect(delegate, &AircraftItemDelegate::requestInstall, + this, &QtLauncher::onRequestPackageInstall); + connect(delegate, &AircraftItemDelegate::cancelDownload, + this, &QtLauncher::onCancelDownload); + + connect(m_aircraftModel, &AircraftItemModel::aircraftInstallCompleted, + this, &QtLauncher::onAircraftInstalledCompleted); + connect(m_aircraftModel, &AircraftItemModel::aircraftInstallFailed, + this, &QtLauncher::onAircraftInstallFailed); + connect(m_ui->pathsButton, &QPushButton::clicked, this, &QtLauncher::onEditPaths); + restoreSettings(); + QSettings settings; m_aircraftModel->setPaths(settings.value("aircraft-paths").toStringList()); m_aircraftModel->scanDirs(); @@ -566,6 +579,7 @@ void QtLauncher::initApp(int& argc, char** argv) bool QtLauncher::runLauncherDialog() { + sglog().setLogLevels( SG_ALL, SG_INFO ); Q_INIT_RESOURCE(resources); // startup the nav-cache now. This pre-empts normal startup of @@ -598,7 +612,7 @@ void QtLauncher::restoreSettings() m_ui->seasonCombo->setCurrentIndex(settings.value("season", 0).toInt()); // full paths to -set.xml files - m_recentAircraft = settings.value("recent-aircraft").toStringList(); + m_recentAircraft = QUrl::fromStringList(settings.value("recent-aircraft").toStringList()); if (!m_recentAircraft.empty()) { m_selectedAircraft = m_recentAircraft.front(); @@ -638,7 +652,7 @@ void QtLauncher::saveSettings() settings.setValue("enable-realwx", m_ui->fetchRealWxrCheckbox->isChecked()); settings.setValue("start-paused", m_ui->startPausedCheck->isChecked()); settings.setValue("ratings-filter", m_ui->ratingsFilterCheck->isChecked()); - settings.setValue("recent-aircraft", m_recentAircraft); + settings.setValue("recent-aircraft", QUrl::toStringList(m_recentAircraft)); settings.setValue("recent-airports", m_recentAirports); settings.setValue("timeofday", m_ui->timeOfDayCombo->currentIndex()); settings.setValue("season", m_ui->seasonCombo->currentIndex()); @@ -679,13 +693,22 @@ void QtLauncher::onRun() // aircraft if (!m_selectedAircraft.isEmpty()) { - QFileInfo setFileInfo(m_selectedAircraft); - opt->addOption("aircraft-dir", setFileInfo.dir().absolutePath().toStdString()); - QString setFile = setFileInfo.fileName(); - Q_ASSERT(setFile.endsWith("-set.xml")); - setFile.truncate(setFile.count() - 8); // drop the '-set.xml' portion - opt->addOption("aircraft", setFile.toStdString()); - + if (m_selectedAircraft.isLocalFile()) { + QFileInfo setFileInfo(m_selectedAircraft.toLocalFile()); + opt->addOption("aircraft-dir", setFileInfo.dir().absolutePath().toStdString()); + QString setFile = setFileInfo.fileName(); + Q_ASSERT(setFile.endsWith("-set.xml")); + setFile.truncate(setFile.count() - 8); // drop the '-set.xml' portion + opt->addOption("aircraft", setFile.toStdString()); + } else if (m_selectedAircraft.scheme() == "package") { + PackageRef pkg = packageForAircraftURI(m_selectedAircraft); + // no need to set aircraft-dir, handled by the corresponding code + // in fgInitAircraft + opt->addOption("aircraft", pkg->qualifiedId()); + } else { + qWarning() << "unsupported aircraft launch URL" << m_selectedAircraft; + } + // manage aircraft history if (m_recentAircraft.contains(m_selectedAircraft)) m_recentAircraft.removeOne(m_selectedAircraft); @@ -902,6 +925,29 @@ void QtLauncher::onToggleTerrasync(bool enabled) } // of is enabled } +void QtLauncher::onAircraftInstalledCompleted(QModelIndex index) +{ + qDebug() << Q_FUNC_INFO; + QUrl u = index.data(AircraftURIRole).toUrl(); + if (u == m_selectedAircraft) { + // potentially enable the run button now! + updateSelectedAircraft(); + qDebug() << "updating selected aircraft" << index.data(); + } +} + +void QtLauncher::onAircraftInstallFailed(QModelIndex index, QString errorMessage) +{ + qWarning() << Q_FUNC_INFO << index.data(AircraftURIRole) << errorMessage; + + QMessageBox msg; + msg.setWindowTitle(tr("Aircraft insallation failed")); + msg.setText(tr("An error occurred installing the aircraft %1: %2"). + arg(index.data(Qt::DisplayRole).toString()).arg(errorMessage)); + msg.addButton(QMessageBox::Ok); + msg.exec(); +} + void QtLauncher::updateAirportDescription() { if (!m_selectedAirport) { @@ -944,20 +990,42 @@ void QtLauncher::onAirportChoiceSelected(const QModelIndex& index) void QtLauncher::onAircraftSelected(const QModelIndex& index) { - m_selectedAircraft = index.data(AircraftPathRole).toString(); + m_selectedAircraft = index.data(AircraftURIRole).toUrl(); updateSelectedAircraft(); } +void QtLauncher::onRequestPackageInstall(const QModelIndex& index) +{ + QString pkg = index.data(AircraftPackageIdRole).toString(); + qDebug() << "request install of" << pkg; + simgear::pkg::PackageRef pref = globals->packageRoot()->getPackageById(pkg.toStdString()); + pref->install(); +} + +void QtLauncher::onCancelDownload(const QModelIndex& index) +{ + QString pkg = index.data(AircraftPackageIdRole).toString(); + qDebug() << "cancel download of" << pkg; + simgear::pkg::PackageRef pref = globals->packageRoot()->getPackageById(pkg.toStdString()); + simgear::pkg::InstallRef i = pref->existingInstall(); + i->cancelDownload(); +} + void QtLauncher::updateSelectedAircraft() { - try { - QFileInfo info(m_selectedAircraft); - AircraftItem item(info.dir(), m_selectedAircraft); - m_ui->thumbnail->setPixmap(item.thumbnail()); - m_ui->aircraftDescription->setText(item.description); - } catch (sg_exception& e) { + QModelIndex index = m_aircraftModel->indexOfAircraftURI(m_selectedAircraft); + if (index.isValid()) { + QPixmap pm = index.data(Qt::DecorationRole).value(); + m_ui->thumbnail->setPixmap(pm); + m_ui->aircraftDescription->setText(index.data(Qt::DisplayRole).toString()); + + int status = index.data(AircraftPackageStatusRole).toInt(); + bool canRun = (status == PackageInstalled); + m_ui->runButton->setEnabled(canRun); + } else { m_ui->thumbnail->setPixmap(QPixmap()); m_ui->aircraftDescription->setText(""); + m_ui->runButton->setEnabled(false); } } @@ -985,16 +1053,16 @@ void QtLauncher::onPopupAirportHistory() } } -QModelIndex QtLauncher::proxyIndexForAircraftPath(QString path) const +QModelIndex QtLauncher::proxyIndexForAircraftURI(QUrl uri) const { - return m_aircraftProxy->mapFromSource(sourceIndexForAircraftPath(path)); + return m_aircraftProxy->mapFromSource(sourceIndexForAircraftURI(uri)); } -QModelIndex QtLauncher::sourceIndexForAircraftPath(QString path) const +QModelIndex QtLauncher::sourceIndexForAircraftURI(QUrl uri) const { AircraftItemModel* sourceModel = qobject_cast(m_aircraftProxy->sourceModel()); Q_ASSERT(sourceModel); - return sourceModel->indexOfAircraftPath(path); + return sourceModel->indexOfAircraftURI(uri); } void QtLauncher::onPopupAircraftHistory() @@ -1004,21 +1072,21 @@ void QtLauncher::onPopupAircraftHistory() } QMenu m; - Q_FOREACH(QString path, m_recentAircraft) { - QModelIndex index = sourceIndexForAircraftPath(path); + Q_FOREACH(QUrl uri, m_recentAircraft) { + QModelIndex index = sourceIndexForAircraftURI(uri); if (!index.isValid()) { // not scanned yet continue; } QAction* act = m.addAction(index.data(Qt::DisplayRole).toString()); - act->setData(path); + act->setData(uri); } QPoint popupPos = m_ui->aircraftHistory->mapToGlobal(m_ui->aircraftHistory->rect().bottomLeft()); QAction* triggered = m.exec(popupPos); if (triggered) { - m_selectedAircraft = triggered->data().toString(); - QModelIndex index = proxyIndexForAircraftPath(m_selectedAircraft); + m_selectedAircraft = triggered->data().toUrl(); + QModelIndex index = proxyIndexForAircraftURI(m_selectedAircraft); m_ui->aircraftList->selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect); m_ui->aircraftFilter->clear(); @@ -1129,5 +1197,17 @@ void QtLauncher::onEditPaths() } } +simgear::pkg::PackageRef QtLauncher::packageForAircraftURI(QUrl uri) const +{ + if (uri.scheme() != "package") { + qWarning() << "invalid URL scheme:" << uri; + return simgear::pkg::PackageRef(); + } + + QString ident = uri.path(); + qDebug() << Q_FUNC_INFO << uri << ident; + return globals->packageRoot()->getPackageById(ident.toStdString()); +} + #include "QtLauncher.moc" diff --git a/src/GUI/QtLauncher.hxx b/src/GUI/QtLauncher.hxx index 3e582813d..3dad6b4bb 100644 --- a/src/GUI/QtLauncher.hxx +++ b/src/GUI/QtLauncher.hxx @@ -26,8 +26,12 @@ #include #include #include +#include + #include +#include +#include namespace Ui { @@ -62,7 +66,9 @@ private slots: void onAirportChoiceSelected(const QModelIndex& index); void onAircraftSelected(const QModelIndex& index); - + void onRequestPackageInstall(const QModelIndex& index); + void onCancelDownload(const QModelIndex& index); + void onPopupAirportHistory(); void onPopupAircraftHistory(); @@ -81,6 +87,9 @@ private slots: void onEditPaths(); void onAirportDiagramClicked(FGRunwayRef rwy); + + void onAircraftInstalledCompleted(QModelIndex index); + void onAircraftInstallFailed(QModelIndex index, QString errorMessage); private: void setAirport(FGAirportRef ref); void updateSelectedAircraft(); @@ -88,20 +97,22 @@ private: void restoreSettings(); void saveSettings(); - QModelIndex proxyIndexForAircraftPath(QString path) const; - QModelIndex sourceIndexForAircraftPath(QString path) const; + QModelIndex proxyIndexForAircraftURI(QUrl uri) const; + QModelIndex sourceIndexForAircraftURI(QUrl uri) const; void setEnableDisableOptionFromCheckbox(QCheckBox* cbox, QString name) const; + simgear::pkg::PackageRef packageForAircraftURI(QUrl uri) const; + QScopedPointer m_ui; AirportSearchModel* m_airportsModel; AircraftProxyModel* m_aircraftProxy; AircraftItemModel* m_aircraftModel; FGAirportRef m_selectedAirport; - QString m_selectedAircraft; - QStringList m_recentAircraft, - m_recentAirports; + QUrl m_selectedAircraft; + QList m_recentAircraft; + QStringList m_recentAirports; QTimer* m_subsystemIdleTimer; int m_ratingFilters[4]; diff --git a/src/Main/fg_init.cxx b/src/Main/fg_init.cxx index 30ffa0e67..3f5682f03 100644 --- a/src/Main/fg_init.cxx +++ b/src/Main/fg_init.cxx @@ -546,11 +546,9 @@ int fgInitAircraft(bool reinit) // code in FindAndCacheAircraft works as normal // note since we may be using a variant, we can't use the package ID size_t lastDot = aircraftId.rfind('.'); - if (lastDot != std::string::npos) { - aircraftId = aircraftId.substr(lastDot + 1); - aircraftProp->setStringValue(aircraftId); - - } + assert(lastDot != std::string::npos); + aircraftId = aircraftId.substr(lastDot + 1); + aircraftProp->setStringValue(aircraftId); // run the traditional-code path below } else { #if 0 diff --git a/src/Network/HTTPClient.cxx b/src/Network/HTTPClient.cxx index f60817877..19c963797 100644 --- a/src/Network/HTTPClient.cxx +++ b/src/Network/HTTPClient.cxx @@ -47,9 +47,8 @@ typedef nasal::Ghost NasalPackage; typedef nasal::Ghost NasalCatalog; typedef nasal::Ghost NasalInstall; -namespace { - - class FGDelegate : public pkg::Delegate + +class FGHTTPClient::FGDelegate : public pkg::Delegate { public: virtual void refreshComplete() @@ -67,48 +66,52 @@ public: r->scheduleToUpdate((*it)->install()); } } - - virtual void failedRefresh(pkg::Catalog* aCat, FailureCode aReason) + + virtual void catalogRefreshed(pkg::CatalogRef aCat, StatusCode aReason) { + if (aCat.ptr() == NULL) { + SG_LOG(SG_IO, SG_INFO, "refresh of all catalogs done"); + return; + } + switch (aReason) { - case pkg::Delegate::FAIL_SUCCESS: - SG_LOG(SG_IO, SG_WARN, "refresh of Catalog done"); + case pkg::Delegate::STATUS_SUCCESS: + case pkg::Delegate::STATUS_REFRESHED: + SG_LOG(SG_IO, SG_INFO, "refresh of Catalog done:" << aCat->url()); break; - + + case pkg::Delegate::STATUS_IN_PROGRESS: + SG_LOG(SG_IO, SG_INFO, "refresh of Catalog started:" << aCat->url()); + break; + default: SG_LOG(SG_IO, SG_WARN, "refresh of Catalog " << aCat->url() << " failed:" << aReason); } } - virtual void startInstall(pkg::Install* aInstall) + virtual void startInstall(pkg::InstallRef aInstall) { SG_LOG(SG_IO, SG_INFO, "beginning install of:" << aInstall->package()->id() << " to local path:" << aInstall->path()); } - virtual void installProgress(pkg::Install* aInstall, unsigned int aBytes, unsigned int aTotal) + virtual void installProgress(pkg::InstallRef aInstall, unsigned int aBytes, unsigned int aTotal) { - SG_LOG(SG_IO, SG_INFO, "installing:" << aInstall->package()->id() << ":" - << aBytes << " of " << aTotal); } - virtual void finishInstall(pkg::Install* aInstall) + virtual void finishInstall(pkg::InstallRef aInstall, StatusCode aReason) { + if (aReason == STATUS_SUCCESS) { SG_LOG(SG_IO, SG_INFO, "finished install of:" << aInstall->package()->id() << " to local path:" << aInstall->path()); + } else { + SG_LOG(SG_IO, SG_WARN, "install failed of:" << aInstall->package()->id() + << " to local path:" << aInstall->path()); + } } - - virtual void failedInstall(pkg::Install* aInstall, FailureCode aReason) - { - SG_LOG(SG_IO, SG_WARN, "install failed of:" << aInstall->package()->id() - << " to local path:" << aInstall->path()); - } - -}; - -} // of anonymous namespace +}; // of FGHTTPClient::FGDelegate FGHTTPClient::FGHTTPClient() : _inited(false) @@ -142,11 +145,12 @@ void FGHTTPClient::init() // package system needs access to the HTTP engine too packageRoot->setHTTPClient(_http.get()); - packageRoot->setDelegate(new FGDelegate); + _packageDelegate.reset(new FGDelegate); + packageRoot->addDelegate(_packageDelegate.get()); - const char * defaultCatalogId = fgGetString("/sim/package-system/default-catalog/id", "org.flightgear.default" ); + const char * defaultCatalogId = fgGetString("/sim/package-system/default-catalog/id", "org.flightgear.official" ); const char * defaultCatalogUrl = fgGetString("/sim/package-system/default-catalog/url", - "http://fgfs.goneabitbursar.com/pkg/" FLIGHTGEAR_VERSION "/default-catalog.xml"); + "http://fgfs.goneabitbursar.com/pkg/" FLIGHTGEAR_VERSION "/catalog.xml"); // setup default catalog if not present pkg::Catalog* defaultCatalog = packageRoot->getCatalogById( defaultCatalogId ); if (!defaultCatalog) { @@ -299,7 +303,13 @@ void FGHTTPClient::postinit() void FGHTTPClient::shutdown() { - _http.reset(); + pkg::Root* packageRoot = globals->packageRoot(); + if (packageRoot && _packageDelegate.get()) { + packageRoot->removeDelegate(_packageDelegate.get()); + } + + _packageDelegate.reset(); + _http.reset(); } void FGHTTPClient::update(double) diff --git a/src/Network/HTTPClient.hxx b/src/Network/HTTPClient.hxx index 262a2e40b..a203501f0 100644 --- a/src/Network/HTTPClient.hxx +++ b/src/Network/HTTPClient.hxx @@ -42,8 +42,11 @@ public: virtual void update(double); private: + class FGDelegate; + bool _inited; std::auto_ptr _http; + std::auto_ptr _packageDelegate; }; #endif // FG_HTTP_CLIENT_HXX -- 2.39.5