diff --git a/CMakeLists.txt b/CMakeLists.txt index 5071d42ef..afef3bceb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -233,7 +233,7 @@ else(EVENT_INPUT) endif(EVENT_INPUT) # check required dependencies -find_package(Boost REQUIRED) +find_package(Boost REQUIRED) find_package(OpenGL REQUIRED) find_package(OpenSceneGraph 3.2.0 REQUIRED osgText diff --git a/CMakeModules/ConfigureMsvc3rdParty.cmake b/CMakeModules/ConfigureMsvc3rdParty.cmake index 9bf10ada2..dd06badec 100644 --- a/CMakeModules/ConfigureMsvc3rdParty.cmake +++ b/CMakeModules/ConfigureMsvc3rdParty.cmake @@ -24,10 +24,8 @@ if (MSVC AND MSVC_3RDPARTY_ROOT) set( OSG_MSVC ${OSG_MSVC}140 ) elseif (${MSVC_VERSION} EQUAL 1800) set( OSG_MSVC ${OSG_MSVC}120 ) - elseif (${MSVC_VERSION} EQUAL 1700) - set( OSG_MSVC ${OSG_MSVC}110 ) - elseif (${MSVC_VERSION} EQUAL 1600) - set( OSG_MSVC ${OSG_MSVC}100 ) + else () + message(FATAL_ERROR "Visual Studio 2013/2015 is required now") endif () if (CMAKE_CL_64) @@ -39,24 +37,18 @@ if (MSVC AND MSVC_3RDPARTY_ROOT) set( BOOST_LIB lib ) endif (CMAKE_CL_64) - GET_FILENAME_COMPONENT(MSVC_ROOT_PARENT_DIR ${MSVC_3RDPARTY_ROOT} PATH) - set (CMAKE_LIBRARY_PATH ${MSVC_3RDPARTY_ROOT}/${MSVC_3RDPARTY_DIR}/lib ${MSVC_3RDPARTY_ROOT}/install/${OSG_MSVC}/OpenScenegraph/lib ${MSVC_3RDPARTY_ROOT}/install/${OSG_MSVC}/OpenRTI/lib ${MSVC_3RDPARTY_ROOT}/install/${OSG_MSVC}/SimGear/lib $(BOOST_ROOT)/$(BOOST_LIB) ) + set (CMAKE_LIBRARY_PATH ${MSVC_3RDPARTY_ROOT}/${MSVC_3RDPARTY_DIR}/lib ${MSVC_3RDPARTY_ROOT}/install/${OSG_MSVC}/OpenScenegraph/lib ${MSVC_3RDPARTY_ROOT}/install/${OSG_MSVC}/OpenRTI/lib ${MSVC_3RDPARTY_ROOT}/install/${OSG_MSVC}/SimGear/lib ) set (CMAKE_INCLUDE_PATH ${MSVC_3RDPARTY_ROOT}/${MSVC_3RDPARTY_DIR}/include ${MSVC_3RDPARTY_ROOT}/install/${OSG_MSVC}/OpenScenegraph/include ${MSVC_3RDPARTY_ROOT}/install/${OSG_MSVC}/OpenRTI/include ${MSVC_3RDPARTY_ROOT}/install/${OSG_MSVC}/SimGear/include) - find_path(BOOST_ROOT boost/version.hpp - ${MSVC_ROOT_PARENT_DIR} - ${MSVC_3RDPARTY_ROOT}/boost - ${MSVC_3RDPARTY_ROOT}/boost_1_52_0 - ${MSVC_3RDPARTY_ROOT}/boost_1_51_0 - ${MSVC_3RDPARTY_ROOT}/boost_1_50_0 - ${MSVC_3RDPARTY_ROOT}/boost_1_49_0 - ${MSVC_3RDPARTY_ROOT}/boost_1_48_0 - ${MSVC_3RDPARTY_ROOT}/boost_1_47_0 - ${MSVC_3RDPARTY_ROOT}/boost_1_46_1 - ${MSVC_3RDPARTY_ROOT}/boost_1_46_0 - ${MSVC_3RDPARTY_ROOT}/boost_1_45_0 - ${MSVC_3RDPARTY_ROOT}/boost_1_44_0 - ) - message(STATUS "BOOST_ROOT is ${BOOST_ROOT}") + + if(NOT BOOST_INCLUDEDIR) + # if this variable was not set by the user, set it to 3rdparty root's + # parent dir, which is the normal location for people using our + # windows-3rd-party repo + GET_FILENAME_COMPONENT(MSVC_ROOT_PARENT_DIR ${MSVC_3RDPARTY_ROOT} PATH) + set(BOOST_INCLUDEDIR ${MSVC_ROOT_PARENT_DIR}) + message(STATUS "BOOST_INCLUDEDIR is ${BOOST_INCLUDEDIR}") + endif() + if (USE_AEONWAVE) find_package(AAX COMPONENTS aax REQUIRED) else() diff --git a/src/FDM/YASim/FGFDM.cpp b/src/FDM/YASim/FGFDM.cpp index 36eec1534..039e286f8 100644 --- a/src/FDM/YASim/FGFDM.cpp +++ b/src/FDM/YASim/FGFDM.cpp @@ -127,7 +127,9 @@ void FGFDM::init() _turb_magnitude_norm = fgGetNode("/environment/turbulence/magnitude-norm", true); _turb_rate_hz = fgGetNode("/environment/turbulence/rate-hz", true); _gross_weight_lbs = fgGetNode("/yasim/gross-weight-lbs", true); - + _cg_x = fgGetNode("/yasim/cg-x-m", true); + _cg_y = fgGetNode("/yasim/cg-y-m", true); + _cg_z = fgGetNode("/yasim/cg-z-m", true); // Allows the user to start with something other than full fuel _airplane.setFuelFraction(fgGetFloat("/sim/fuel-fraction", 1)); @@ -610,6 +612,12 @@ void FGFDM::setOutputProperties(float dt) float grossWgt = _airplane.getModel()->getBody()->getTotalMass() * KG2LBS; _gross_weight_lbs->setFloatValue(grossWgt); + float cg[3]; + _airplane.getModel()->getBody()->getCG(cg); + _cg_x->setFloatValue(cg[0]); + _cg_y->setFloatValue(cg[1]); + _cg_z->setFloatValue(cg[2]); + ControlMap* cm = _airplane.getControlMap(); for(int i=0; i<_controlProps.size(); i++) { PropOut* p = (PropOut*)_controlProps.get(i); diff --git a/src/FDM/YASim/FGFDM.hpp b/src/FDM/YASim/FGFDM.hpp index 3f5465cae..b8fb8ca18 100644 --- a/src/FDM/YASim/FGFDM.hpp +++ b/src/FDM/YASim/FGFDM.hpp @@ -104,6 +104,9 @@ private: SGPropertyNode_ptr _turb_magnitude_norm, _turb_rate_hz; SGPropertyNode_ptr _gross_weight_lbs; + SGPropertyNode_ptr _cg_x; + SGPropertyNode_ptr _cg_y; + SGPropertyNode_ptr _cg_z; std::vector _tank_level_lbs; std::vector _thrust_props; std::vector _fuel_props; diff --git a/src/GUI/AircraftModel.cxx b/src/GUI/AircraftModel.cxx index a6c4d5ac6..a9c35f18f 100644 --- a/src/GUI/AircraftModel.cxx +++ b/src/GUI/AircraftModel.cxx @@ -144,9 +144,9 @@ void AircraftItem::toDataStream(QDataStream& ds) const for (int i=0; i<4; ++i) ds << ratings[i]; } -QPixmap AircraftItem::thumbnail() const +QPixmap AircraftItem::thumbnail(bool loadIfRequired) const { - if (m_thumbnail.isNull()) { + if (m_thumbnail.isNull() && loadIfRequired) { QFileInfo info(path); QDir dir = info.dir(); if (dir.exists("thumbnail.jpg")) { @@ -346,7 +346,7 @@ protected: virtual void catalogRefreshed(CatalogRef aCatalog, StatusCode aReason) { if (aReason == STATUS_IN_PROGRESS) { - qDebug() << "doing refresh of" << QString::fromStdString(aCatalog->url()); + // nothing to do } else if ((aReason == STATUS_REFRESHED) || (aReason == STATUS_SUCCESS)) { m_model->refreshPackages(); } else { @@ -402,19 +402,15 @@ protected: if (pix.height() > STANDARD_THUMBNAIL_HEIGHT) { pix = pix.scaledToHeight(STANDARD_THUMBNAIL_HEIGHT); } - m_model->m_thumbnailPixmapCache.insert(QString::fromStdString(aThumbnailUrl), - pix); + m_model->m_thumbnailPixmapCache.insert(QString::fromStdString(aThumbnailUrl), pix); // 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); + for (auto pkg : m_model->m_packages) { + const string_list& urls(pkg->thumbnailUrls()); + auto cit = std::find(urls.begin(), urls.end(), aThumbnailUrl); if (cit != urls.end()) { - QModelIndex mi(m_model->index(i + m_model->m_items.size())); + QModelIndex mi = indexForPackage(pkg); m_model->dataChanged(mi, mi); } } // of packages iteration @@ -423,9 +419,8 @@ protected: private: QModelIndex indexForPackage(const PackageRef& ref) const { - PackageList::const_iterator it = std::find(m_model->m_packages.begin(), - m_model->m_packages.end(), - ref); + auto it = std::find(m_model->m_packages.begin(), + m_model->m_packages.end(), ref); if (it == m_model->m_packages.end()) { return QModelIndex(); } @@ -480,12 +475,12 @@ void AircraftItemModel::setMessageWidgetVisible(bool vis) if (vis) { beginInsertRows(QModelIndex(), 0, 0); m_items.prepend(AircraftItemPtr(new AircraftItem)); - m_activeVariant.prepend(0); + m_delegateStates.prepend(DelegateState()); endInsertRows(); } else { beginRemoveRows(QModelIndex(), 0, 0); m_items.removeAt(0); - m_activeVariant.removeAt(0); + m_delegateStates.removeAt(0); endRemoveRows(); } } @@ -510,7 +505,7 @@ void AircraftItemModel::scanDirs() beginRemoveRows(QModelIndex(), firstRow, lastRow); m_items.remove(firstRow, numToRemove); - m_activeVariant.remove(firstRow, numToRemove); + m_delegateStates.remove(firstRow, numToRemove); endRemoveRows(); } @@ -545,8 +540,9 @@ void AircraftItemModel::abandonCurrentScan() void AircraftItemModel::refreshPackages() { simgear::pkg::PackageList newPkgs = m_packageRoot->allPackages(); - int firstRow = m_items.size(); - int newSize = newPkgs.size(); + const int firstRow = m_items.size(); + const int newSize = newPkgs.size(); + const int newTotalSize = firstRow + newSize; if (m_packages.size() != newPkgs.size()) { int oldSize = m_packages.size(); @@ -556,7 +552,7 @@ void AircraftItemModel::refreshPackages() int lastNewRow = firstRow + newSize - 1; beginInsertRows(QModelIndex(), firstNewRow, lastNewRow); m_packages = newPkgs; - m_packageVariant.resize(newSize); + m_delegateStates.resize(newTotalSize); endInsertRows(); } else { // shrinking @@ -564,7 +560,7 @@ void AircraftItemModel::refreshPackages() int lastOldRow = firstRow + oldSize - 1; beginRemoveRows(QModelIndex(), firstOldRow, lastOldRow); m_packages = newPkgs; - m_packageVariant.resize(newSize); + m_delegateStates.resize(newTotalSize); endRemoveRows(); } } else { @@ -593,13 +589,16 @@ QVariant AircraftItemModel::data(const QModelIndex& index, int role) const } } + if (role == AircraftVariantRole) { + return m_delegateStates.at(row).variant; + } + + if (role == AircraftCurrentThumbnailRole) { + return m_delegateStates.at(row).thumbnail; + } + if (row >= m_items.size()) { quint32 packageIndex = row - m_items.size(); - - if (role == AircraftVariantRole) { - return m_packageVariant.at(packageIndex); - } - const PackageRef& pkg(m_packages[packageIndex]); InstallRef ex = pkg->existingInstall(); @@ -609,20 +608,14 @@ QVariant AircraftItemModel::data(const QModelIndex& index, int role) const return static_cast(ex.valid() ? ex->downloadedBytes() : 0); } - quint32 variantIndex = m_packageVariant.at(packageIndex); - return dataFromPackage(pkg, variantIndex, role); + return dataFromPackage(pkg, m_delegateStates.at(row), role); } else { - if (role == AircraftVariantRole) { - return m_activeVariant.at(row); - } - - quint32 variantIndex = m_activeVariant.at(row); const AircraftItemPtr item(m_items.at(row)); - return dataFromItem(item, variantIndex, role); + return dataFromItem(item, m_delegateStates.at(row), role); } } -QVariant AircraftItemModel::dataFromItem(AircraftItemPtr item, quint32 variantIndex, int role) const +QVariant AircraftItemModel::dataFromItem(AircraftItemPtr item, const DelegateState& state, int role) const { if (role == AircraftVariantCountRole) { return item->variants.count(); @@ -634,7 +627,7 @@ QVariant AircraftItemModel::dataFromItem(AircraftItemPtr item, quint32 variantIn } if (role == AircraftThumbnailSizeRole) { - return item->thumbnail().size(); + return item->thumbnail(false).size(); } if ((role >= AircraftVariantDescriptionRole) && (role < AircraftThumbnailRole)) { @@ -642,10 +635,10 @@ QVariant AircraftItemModel::dataFromItem(AircraftItemPtr item, quint32 variantIn return item->variants.at(variantIndex)->description; } - if (variantIndex) { - if (variantIndex <= static_cast(item->variants.count())) { + if (state.variant) { + if (state.variant <= static_cast(item->variants.count())) { // show the selected variant - item = item->variants.at(variantIndex - 1); + item = item->variants.at(state.variant - 1); } } @@ -690,7 +683,7 @@ QVariant AircraftItemModel::dataFromItem(AircraftItemPtr item, quint32 variantIn return QVariant(); } -QVariant AircraftItemModel::dataFromPackage(const PackageRef& item, quint32 variantIndex, int role) const +QVariant AircraftItemModel::dataFromPackage(const PackageRef& item, const DelegateState& state, int role) const { if (role == Qt::DecorationRole) { role = AircraftThumbnailRole; // use first thumbnail @@ -706,7 +699,7 @@ QVariant AircraftItemModel::dataFromPackage(const PackageRef& item, quint32 vari } if (role == Qt::DisplayRole) { - QString desc = QString::fromStdString(item->nameForVariant(variantIndex)); + QString desc = QString::fromStdString(item->nameForVariant(state.variant)); if (desc.isEmpty()) { desc = tr("Missing description for: %1").arg(QString::fromStdString(item->id())); } @@ -717,7 +710,7 @@ QVariant AircraftItemModel::dataFromPackage(const PackageRef& item, quint32 vari return QString::fromStdString(i->primarySetPath().utf8Str()); } } else if (role == AircraftPackageIdRole) { - return QString::fromStdString(item->variants()[variantIndex]); + return QString::fromStdString(item->variants()[state.variant]); } else if (role == AircraftPackageStatusRole) { InstallRef i = item->existingInstall(); if (i.valid()) { @@ -740,12 +733,15 @@ QVariant AircraftItemModel::dataFromPackage(const PackageRef& item, quint32 vari // including the primary. Hence the -1 term. return static_cast(item->variants().size() - 1); } else if (role == AircraftThumbnailSizeRole) { - QPixmap pm = packageThumbnail(item, 0, false).value(); + QPixmap pm = packageThumbnail(item, state, false).value(); if (pm.isNull()) return QSize(STANDARD_THUMBNAIL_WIDTH, STANDARD_THUMBNAIL_HEIGHT); return pm.size(); } else if (role >= AircraftThumbnailRole) { - return packageThumbnail(item , role - AircraftThumbnailRole); + DelegateState changedState(state); + // override the current thumbnail as required + changedState.thumbnail = (role - AircraftThumbnailRole); + return packageThumbnail(item, changedState); } else if (role == AircraftAuthorsRole) { SGPropertyNode* authors = item->properties()->getChild("author"); if (authors) { @@ -756,7 +752,7 @@ QVariant AircraftItemModel::dataFromPackage(const PackageRef& item, quint32 vari } else if (role == AircraftPackageSizeRole) { return static_cast(item->fileSizeBytes()); } else if (role == AircraftURIRole) { - return QUrl("package:" + QString::fromStdString(item->qualifiedVariantId(variantIndex))); + return QUrl("package:" + QString::fromStdString(item->qualifiedVariantId(state.variant))); } else if (role == AircraftHasRatingsRole) { return item->properties()->hasChild("rating"); } else if ((role >= AircraftRatingRole) && (role < AircraftVariantDescriptionRole)) { @@ -771,14 +767,14 @@ QVariant AircraftItemModel::dataFromPackage(const PackageRef& item, quint32 vari return QVariant(); } -QVariant AircraftItemModel::packageThumbnail(PackageRef p, int index, bool download) const +QVariant AircraftItemModel::packageThumbnail(PackageRef p, const DelegateState& ds, bool download) const { const string_list& thumbnails(p->thumbnailUrls()); - if (index >= static_cast(thumbnails.size())) { + if (ds.thumbnail >= static_cast(thumbnails.size())) { return QVariant(); } - std::string thumbnailUrl = thumbnails.at(index); + std::string thumbnailUrl = thumbnails.at(ds.thumbnail); QString urlQString(QString::fromStdString(thumbnailUrl)); if (m_thumbnailPixmapCache.contains(urlQString)) { // cache hit, easy @@ -792,7 +788,7 @@ QVariant AircraftItemModel::packageThumbnail(PackageRef p, int index, bool downl const string_list& thumbNames(p->thumbnails()); if (!thumbNames.empty()) { SGPath path(ex->path()); - path.append(p->thumbnails()[index]); + path.append(p->thumbnails()[ds.thumbnail]); if (path.exists()) { QPixmap pix; pix.load(QString::fromStdString(path.utf8Str())); @@ -809,20 +805,31 @@ QVariant AircraftItemModel::packageThumbnail(PackageRef p, int index, bool downl if (download) { m_packageRoot->requestThumbnailData(thumbnailUrl); } + return QVariant(); } bool AircraftItemModel::setData(const QModelIndex &index, const QVariant &value, int role) { int row = index.row(); + int newValue = value.toInt(); + if (role == AircraftVariantRole) { - if (row >= m_activeVariant.size()) { - row -= m_activeVariant.size(); - m_packageVariant[row] = value.toInt(); - } else { - m_activeVariant[row] = value.toInt(); + if (m_delegateStates[row].variant == newValue) { + return true; } + m_delegateStates[row].variant = newValue; + emit dataChanged(index, index); + return true; + } + + if (role == AircraftCurrentThumbnailRole) { + if (m_delegateStates[row].thumbnail == newValue) { + return true; + } + + m_delegateStates[row].thumbnail = newValue; emit dataChanged(index, index); return true; } @@ -885,8 +892,9 @@ void AircraftItemModel::onScanResults() // default variants in all cases for (int i=0; i< newItems.count(); ++i) { - m_activeVariant.append(0); + m_delegateStates.insert(firstRow + i, DelegateState()); } + endInsertRows(); } diff --git a/src/GUI/AircraftModel.hxx b/src/GUI/AircraftModel.hxx index ade464572..71fc8fe4e 100644 --- a/src/GUI/AircraftModel.hxx +++ b/src/GUI/AircraftModel.hxx @@ -49,6 +49,7 @@ const int AircraftURIRole = Qt::UserRole + 14; const int AircraftThumbnailSizeRole = Qt::UserRole + 15; const int AircraftIsHelicopterRole = Qt::UserRole + 16; const int AircraftIsSeaplaneRole = Qt::UserRole + 17; +const int AircraftCurrentThumbnailRole = Qt::UserRole + 18; const int AircraftRatingRole = Qt::UserRole + 100; const int AircraftVariantDescriptionRole = Qt::UserRole + 200; @@ -73,7 +74,7 @@ struct AircraftItem void toDataStream(QDataStream& ds) const; - QPixmap thumbnail() const; + QPixmap thumbnail(bool loadIfRequired = true) const; bool excluded; QString path; @@ -163,12 +164,23 @@ private slots: private: friend class PackageDelegate; - QVariant dataFromItem(AircraftItemPtr item, quint32 variantIndex, int role) const; + /** + * struct to record persistent state from the item-delegate, about the + * currently visible variant, thumbnail and similar. + */ + struct DelegateState + { + quint32 variant = 0; + quint32 thumbnail = 0; + }; + + QVariant dataFromItem(AircraftItemPtr item, const DelegateState& state, int role) const; QVariant dataFromPackage(const simgear::pkg::PackageRef& item, - quint32 variantIndex, int role) const; + const DelegateState& state, int role) const; - QVariant packageThumbnail(simgear::pkg::PackageRef p, int index, bool download = true) const; + QVariant packageThumbnail(simgear::pkg::PackageRef p, + const DelegateState& state, bool download = true) const; void abandonCurrentScan(); void refreshPackages(); @@ -182,9 +194,9 @@ private: PackageDelegate* m_delegate = nullptr; bool m_showMessageWidget = false; - QVector m_activeVariant; - QVector m_packageVariant; - + + QVector m_delegateStates; + simgear::pkg::RootRef m_packageRoot; simgear::pkg::PackageList m_packages; diff --git a/src/Main/fg_init.cxx b/src/Main/fg_init.cxx index ea9d9b5ef..4072ae9ba 100644 --- a/src/Main/fg_init.cxx +++ b/src/Main/fg_init.cxx @@ -131,6 +131,7 @@ #include #include #include +#include #include #include #include @@ -735,6 +736,7 @@ void fgCreateSubsystems(bool duringReset) { if (!globals->get_subsystem()) { globals->add_new_subsystem(); } + globals->add_new_subsystem(); //////////////////////////////////////////////////////////////////// // Initialize the flight model subsystem. diff --git a/src/MultiPlayer/CMakeLists.txt b/src/MultiPlayer/CMakeLists.txt index 1d5c7e992..c2d0f59b5 100644 --- a/src/MultiPlayer/CMakeLists.txt +++ b/src/MultiPlayer/CMakeLists.txt @@ -3,11 +3,13 @@ include(FlightGearComponent) set(SOURCES multiplaymgr.cxx tiny_xdr.cxx + MPServerResolver.cxx ) set(HEADERS multiplaymgr.hxx tiny_xdr.hxx + MPServerResolver.hxx ) -flightgear_component(MultiPlayer "${SOURCES}" "${HEADERS}") \ No newline at end of file +flightgear_component(MultiPlayer "${SOURCES}" "${HEADERS}") diff --git a/src/MultiPlayer/MPServerResolver.cxx b/src/MultiPlayer/MPServerResolver.cxx new file mode 100644 index 000000000..dae17975d --- /dev/null +++ b/src/MultiPlayer/MPServerResolver.cxx @@ -0,0 +1,194 @@ + +/* + MPServerResolver.cxx - mpserver names lookup via DNS + Written and copyright by Torsten Dreyer - November 2016 + + This file is part of FlightGear. + + FlightGear 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. + + FlightGear 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 FlightGear. If not, see . + */#include "MPServerResolver.hxx" + +#include +#include
+#include <3rdparty/cjson/cJSON.h> +#include + +using namespace simgear; + +/** + * Build a name=value map from base64 encoded JSON string + */ +class MPServerProperties : public std::map { +public: + MPServerProperties (string b64) + { + std::vector b64dec; + simgear::strutils::decodeBase64 (b64, b64dec); + auto jsonString = string ((char*) b64dec.data (), b64dec.size ()); + cJSON * json = ::cJSON_Parse (jsonString.c_str ()); + if (json) { + for (int i = 0; i < ::cJSON_GetArraySize (json); i++) { + cJSON * cj = ::cJSON_GetArrayItem (json, i); + if (cj->string && cj->valuestring) + emplace (cj->string, cj->valuestring); + } + ::cJSON_Delete (json); + } else { + SG_LOG(SG_NETWORK,SG_WARN, "MPServerResolver: Can't parse JSON string '" << jsonString << "'" ); + } + } +}; + +class MPServerResolver::MPServerResolver_priv { +public: + enum { + INIT, LOADING_SRV_RECORDS, LOAD_NEXT_TXT_RECORD, LOADING_TXT_RECORDS, DONE, + } _state = INIT; + + FGDNSClient * _dnsClient = globals->get_subsystem (); + DNS::Request_ptr _dnsRequest; + PropertyList _serverNodes; + PropertyList::const_iterator _serverNodes_it; +}; + +MPServerResolver::~MPServerResolver () +{ + delete _priv; +} + +MPServerResolver::MPServerResolver () : + _priv (new MPServerResolver_priv ()) +{ +} + +void +MPServerResolver::run () +{ + //SG_LOG(SG_NETWORK, SG_DEBUG, "MPServerResolver::run() with state=" << _priv->_state ); + switch (_priv->_state) { + // First call - fire DNS lookup for SRV records + case MPServerResolver_priv::INIT: + if (!_priv->_dnsClient) { + SG_LOG(SG_NETWORK, SG_WARN, "MPServerResolver: DNS subsystem not available."); + onFailure (); + return; + } + + _priv->_dnsRequest = new DNS::SRVRequest (_dnsName, _service, _protocol); + SG_LOG(SG_NETWORK, SG_INFO, "MPServerResolver: sending DNS request for " << _priv->_dnsRequest->getDn()); + _priv->_dnsClient->makeRequest (_priv->_dnsRequest); + _priv->_state = MPServerResolver_priv::LOADING_SRV_RECORDS; + break; + + // Check if response from SRV Query + case MPServerResolver_priv::LOADING_SRV_RECORDS: + if (_priv->_dnsRequest->isTimeout ()) { + SG_LOG(SG_NETWORK, SG_WARN, "Timeout waiting for DNS response. Query was: " << _priv->_dnsRequest->getDn()); + onFailure (); + return; + } + if (_priv->_dnsRequest->isComplete ()) { + // Create a child node under _targetNode for each SRV entry of the response + SG_LOG(SG_NETWORK, SG_INFO, "MPServerResolver: got DNS response for " << _priv->_dnsRequest->getDn()); + int idx = 0; + for (DNS::SRVRequest::SRV_ptr entry : dynamic_cast (_priv->_dnsRequest.get ())->entries) { + SG_LOG(SG_NETWORK, SG_DEBUG, + "MPServerResolver: SRV " << entry->priority << " " << entry->weight << " " << entry->port << " " << entry->target); + if( 0 == entry->port ) { + SG_LOG(SG_NETWORK, SG_INFO, "MPServerResolver: Skipping offline host " << entry->target ); + continue; + } + SGPropertyNode * serverNode = _targetNode->getNode ("server", idx++, true); + serverNode->getNode ("hostname", true)->setStringValue (entry->target); + serverNode->getNode ("priority", true)->setIntValue (entry->priority); + serverNode->getNode ("weight", true)->setIntValue (entry->weight); + serverNode->getNode ("port", true)->setIntValue (entry->port); + } + + // prepare an iterator over the server-nodes to be used later when loading the TXT records + _priv->_serverNodes = _targetNode->getChildren ("server"); + _priv->_serverNodes_it = _priv->_serverNodes.begin (); + if (_priv->_serverNodes_it == _priv->_serverNodes.end ()) { + // No SRV records found - flag failure + SG_LOG(SG_NETWORK, SG_WARN, "MPServerResolver: no multiplayer servers defined via DNS"); + onFailure (); + return; + } + _priv->_state = MPServerResolver_priv::LOAD_NEXT_TXT_RECORD; + break; + } + break; + + // get the next TXT record + case MPServerResolver_priv::LOAD_NEXT_TXT_RECORD: + if (_priv->_serverNodes_it == _priv->_serverNodes.end ()) { + // we are done with all servers + _priv->_state = MPServerResolver_priv::DONE; + break; + } + + // send the DNS query for the hostnames TXT record + _priv->_dnsRequest = new DNS::TXTRequest ((*_priv->_serverNodes_it)->getStringValue ("hostname")); + SG_LOG(SG_NETWORK, SG_INFO, "MPServerResolver: sending DNS request for " << _priv->_dnsRequest->getDn()); + _priv->_dnsClient->makeRequest (_priv->_dnsRequest); + _priv->_state = MPServerResolver_priv::LOADING_TXT_RECORDS; + break; + + // check if response for TXT query + case MPServerResolver_priv::LOADING_TXT_RECORDS: + if (_priv->_dnsRequest->isTimeout ()) { + // on timeout, try proceeding with next server + SG_LOG(SG_NETWORK, SG_WARN, "Timeout waiting for DNS response. Query was: " << _priv->_dnsRequest->getDn()); + _priv->_state = MPServerResolver_priv::LOAD_NEXT_TXT_RECORD; + ++_priv->_serverNodes_it; + break; + } + if (_priv->_dnsRequest->isComplete ()) { + SG_LOG(SG_NETWORK, SG_INFO, "MPServerResolver: got DNS response for " << _priv->_dnsRequest->getDn()); + // DNS::TXTRequest automatically extracts name=value entries for us, lets retrieve them + auto attributes = dynamic_cast (_priv->_dnsRequest.get ())->attributes; + auto mpserverAttribute = attributes["flightgear-mpserver"]; + if (!mpserverAttribute.empty ()) { + // we are only interested in the 'flightgear-mpserver=something' entry, this is a base64 encoded + // JSON string, convert this into a map + MPServerProperties mpserverProperties (mpserverAttribute); + for (auto prop : mpserverProperties) { + // and store each as a node under our servers node. + SG_LOG(SG_NETWORK, SG_DEBUG, "MPServerResolver: TXT record attribute " << prop.first << "=" << prop.second); + // sanitize property name, don't allow dots or forward slash + auto propertyName = prop.first; + std::replace( propertyName.begin(), propertyName.end(), '.', '_'); + std::replace( propertyName.begin(), propertyName.end(), '/', '_'); + (*_priv->_serverNodes_it)->setStringValue (propertyName, prop.second); + } + } else { + SG_LOG(SG_NETWORK, SG_INFO, "MPServerResolver: TXT record attributes empty"); + } + + // procede with the net node + ++_priv->_serverNodes_it; + _priv->_state = MPServerResolver_priv::LOAD_NEXT_TXT_RECORD; + break; + } + break; + + case MPServerResolver_priv::DONE: + onSuccess (); + return; + } + + // Relinguish control, call me back on the next frame + globals->get_event_mgr ()->addEvent ("MPServerResolver_update", this, &MPServerResolver::run, .0); +} + diff --git a/src/MultiPlayer/MPServerResolver.hxx b/src/MultiPlayer/MPServerResolver.hxx new file mode 100644 index 000000000..942426dba --- /dev/null +++ b/src/MultiPlayer/MPServerResolver.hxx @@ -0,0 +1,83 @@ +/* + MPServerResolver.hxx - mpserver names lookup via DNS + Written and copyright by Torsten Dreyer - November 2016 + + This file is part of FlightGear. + + FlightGear 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. + + FlightGear 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 FlightGear. If not, see . + */ +#ifndef __FG_MPSERVERRESOLVER_HXX +#define __FG_MPSERVERRESOLVER_HXX + +#include +#include + +class MPServerResolver { +public: + MPServerResolver(); + virtual ~MPServerResolver(); + void run(); + + /** + * Set the target property where the server-list gets stored + * + * \param value the property node to use as a target + */ + void setTarget( SGPropertyNode_ptr value ) { _targetNode = value; } + + /** + * Set the dns domain name to query. This could be either a full qualified name including the + * service and the protocol like _fgms._udp.flightgear.org or just the domain name like + * flightgear.org. Use setService() and setProtocol() in the latter case. + * + * \param value the dnsname to use for the query. + */ + void setDnsName( const std::string & value ) { _dnsName = value; } + + /** Set the service name to use for the query. Don't add the underscore, this gets added + * automatically. This builds the fully qualified DNS name to query, together with + * setProtocol() and setDnsName(). + * + * \param value the service name to use for the query sans the leading underscore + */ + void setService( const std::string & value ) { _service = value; } + + /** Set the protocol name to use for the query. Don't add the underscore, this gets added + * automatically. This builds the fully qualified DNS name to query, together with + * setService() and setDnsName(). + * + * \param value the protocol name to use for the query sans the leading underscore + */ + void setProtocol( const std::string & value ) { _protocol = value; } + + /** Handler to be called if the resolver process finishes with success. Does nothing by + * default and should be overridden be the user. + */ + virtual void onSuccess() {}; + + /** Handler to be called if the resolver process terminates with an error. Does nothing by + * default and should be overridden be the user. + */ + virtual void onFailure() {}; + +private: + class MPServerResolver_priv; + std::string _dnsName; + std::string _service; + std::string _protocol; + SGPropertyNode_ptr _targetNode; + MPServerResolver_priv * _priv; +}; + +#endif // __FG_MPSERVERRESOLVER_HXX diff --git a/src/MultiPlayer/multiplaymgr.cxx b/src/MultiPlayer/multiplaymgr.cxx index b78c452a0..64cf5ec5f 100644 --- a/src/MultiPlayer/multiplaymgr.cxx +++ b/src/MultiPlayer/multiplaymgr.cxx @@ -41,14 +41,14 @@ #include #include #include +#include #include #include #include
-#include -#include #include "multiplaymgr.hxx" #include "mpmessages.hxx" +#include "MPServerResolver.hxx" #include using namespace std; @@ -428,40 +428,60 @@ static bool do_multiplayer_disconnect(const SGPropertyNode * arg) { // none // ////////////////////////////////////////////////////////////////////// -static bool do_multiplayer_refreshserverlist(const SGPropertyNode * arg) { - FGMultiplayMgr * self = (FGMultiplayMgr*) globals->get_subsystem("mp"); - if (!self) { - SG_LOG(SG_NETWORK, SG_WARN, "Multiplayer subsystem not available."); - return false; - } - FGHTTPClient* http = globals->get_subsystem(); - if (!http) { - SG_LOG(SG_IO, SG_ALERT, - "do_multiplayer.refreshserverlist: HTTP client not running"); - return false; - } +static bool +do_multiplayer_refreshserverlist (const SGPropertyNode * arg) +{ + using namespace simgear; - string url( - fgGetString("/sim/multiplay/serverlist-url", - "http://liveries.flightgear.org/mpstatus/mpservers.xml")); + FGMultiplayMgr * self = (FGMultiplayMgr*) globals->get_subsystem ("mp"); + if (!self) { + SG_LOG(SG_NETWORK, SG_WARN, "Multiplayer subsystem not available."); + return false; + } - if (url.empty()) { - SG_LOG(SG_IO, SG_ALERT, - "do_multiplayer.refreshserverlist: no URL given"); - return false; - } + // MPServerResolver implementation to fill the mp server list + // deletes itself when done + class MyMPServerResolver : public MPServerResolver { + public: + MyMPServerResolver () : + MPServerResolver () + { + setTarget (fgGetNode ("/sim/multiplay/server-list", true)); + setDnsName (fgGetString ("/sim/multiplay/dns/query-dn", "flightgear.org")); + setService (fgGetString ("/sim/multiplay/dns/query-srv-service", "fgms")); + setProtocol (fgGetString ("/sim/multiplay/dns/query-srv-protocol", "udp")); + _completeNode->setBoolValue (false); + _failureNode->setBoolValue (false); + } - SGPropertyNode *targetnode = fgGetNode("/sim/multiplay/server-list", true); - SGPropertyNode *completeNode = fgGetNode("/sim/multiplay/got-servers", true); - SGPropertyNode *failureNode = fgGetNode("/sim/multiplay/get-servers-failure", true); - RemoteXMLRequest* req = new RemoteXMLRequest(url, targetnode); - req->setCompletionProp(completeNode); - req->setFailedProp(failureNode); - completeNode->setBoolValue(false); - failureNode->setBoolValue(false); - http->makeRequest(req); - return true; + ~MyMPServerResolver () + { + } + + virtual void + onSuccess () + { + SG_LOG(SG_NETWORK, SG_DEBUG, "MyMPServerResolver: trigger success"); + _completeNode->setBoolValue (true); + delete this; + } + virtual void + onFailure () + { + SG_LOG(SG_NETWORK, SG_DEBUG, "MyMPServerResolver: trigger failure"); + _failureNode->setBoolValue (true); + delete this; + } + + private: + SGPropertyNode *_completeNode = fgGetNode ("/sim/multiplay/got-servers", true); + SGPropertyNode *_failureNode = fgGetNode ("/sim/multiplay/get-servers-failure", true); + }; + + MyMPServerResolver * mpServerResolver = new MyMPServerResolver (); + mpServerResolver->run (); + return true; } ////////////////////////////////////////////////////////////////////// diff --git a/src/Network/CMakeLists.txt b/src/Network/CMakeLists.txt index 3a53cbfd7..f36cfafe5 100644 --- a/src/Network/CMakeLists.txt +++ b/src/Network/CMakeLists.txt @@ -11,6 +11,7 @@ set(SOURCES garmin.cxx generic.cxx HTTPClient.cxx + DNSClient.cxx igc.cxx joyclient.cxx jsclient.cxx @@ -39,6 +40,7 @@ set(HEADERS garmin.hxx generic.hxx HTTPClient.hxx + DNSClient.hxx igc.hxx joyclient.hxx jsclient.hxx diff --git a/src/Network/DNSClient.cxx b/src/Network/DNSClient.cxx new file mode 100644 index 000000000..fff953a20 --- /dev/null +++ b/src/Network/DNSClient.cxx @@ -0,0 +1,71 @@ +// HDNSClient.cxx -- Singleton DNS client object +// +// Written by James Turner, started April 2012. +// Based on HTTPClient from James Turner +// Copyright (C) 2012 James Turner +// +// 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 "DNSClient.hxx" + +#include + +#include
+#include + +#include + + +using namespace simgear; + +FGDNSClient::FGDNSClient() : + _inited(false) +{ +} + +FGDNSClient::~FGDNSClient() +{ +} + +void FGDNSClient::init() +{ + if (_inited) { + return; + } + + _dns.reset( new simgear::DNS::Client() ); + + _inited = true; +} + +void FGDNSClient::postinit() +{ +} + +void FGDNSClient::shutdown() +{ + _dns.reset(); + _inited = false; +} + +void FGDNSClient::update(double) +{ + _dns->update(); +} + +void FGDNSClient::makeRequest(const simgear::DNS::Request_ptr& req) +{ + _dns->makeRequest(req); +} diff --git a/src/Network/DNSClient.hxx b/src/Network/DNSClient.hxx new file mode 100644 index 000000000..cdccb4e79 --- /dev/null +++ b/src/Network/DNSClient.hxx @@ -0,0 +1,52 @@ +// DNSClient.hxx -- Singleton DNS client object +// +// Written by Torsten Dreyer, started November 2016. +// Based on HTTPClient from James Turner +// Copyright (C) 2012 James Turner +// +// 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 FG_DNS_CLIENT_HXX +#define FG_DNS_CLIENT_HXX + +#include +#include +#include + +class FGDNSClient : public SGSubsystem +{ +public: + FGDNSClient(); + virtual ~FGDNSClient(); + + void makeRequest(const simgear::DNS::Request_ptr& req); + +// simgear::HTTP::Client* client() { return _http.get(); } +// simgear::HTTP::Client const* client() const { return _http.get(); } + + virtual void init(); + virtual void postinit(); + virtual void shutdown(); + virtual void update(double); + + static const char* subsystemName() { return "dns"; } +private: + bool _inited; + std::unique_ptr _dns; +}; + +#endif // FG_DNS_CLIENT_HXX + + diff --git a/version b/version index 859132a9b..0b7b08a18 100644 --- a/version +++ b/version @@ -1 +1 @@ -2016.4.0 +2017.1.0