1
0
Fork 0

Merge branch 'next' of ssh://git.code.sf.net/p/flightgear/flightgear into next

This commit is contained in:
Durk Talsma 2016-11-20 12:54:56 +01:00
commit b0d707c3f4
15 changed files with 569 additions and 120 deletions

View file

@ -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()

View file

@ -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);

View file

@ -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<SGPropertyNode_ptr> _tank_level_lbs;
std::vector<ThrusterProps> _thrust_props;
std::vector<FuelProps> _fuel_props;

View file

@ -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 (row >= m_items.size()) {
quint32 packageIndex = row - m_items.size();
if (role == AircraftVariantRole) {
return m_packageVariant.at(packageIndex);
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();
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<quint64>(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<quint32>(item->variants.count())) {
if (state.variant) {
if (state.variant <= static_cast<quint32>(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<quint32>(item->variants().size() - 1);
} else if (role == AircraftThumbnailSizeRole) {
QPixmap pm = packageThumbnail(item, 0, false).value<QPixmap>();
QPixmap pm = packageThumbnail(item, state, false).value<QPixmap>();
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<int>(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<int>(thumbnails.size())) {
if (ds.thumbnail >= static_cast<int>(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();
}

View file

@ -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,8 +194,8 @@ private:
PackageDelegate* m_delegate = nullptr;
bool m_showMessageWidget = false;
QVector<quint32> m_activeVariant;
QVector<quint32> m_packageVariant;
QVector<DelegateState> m_delegateStates;
simgear::pkg::RootRef m_packageRoot;
simgear::pkg::PackageList m_packages;

View file

@ -131,6 +131,7 @@
#include <Instrumentation/HUD/HUD.hxx>
#include <Cockpit/cockpitDisplayManager.hxx>
#include <Network/HTTPClient.hxx>
#include <Network/DNSClient.hxx>
#include <Network/fgcom.hxx>
#include <Network/http/httpd.hxx>
#include <Include/version.h>
@ -735,6 +736,7 @@ void fgCreateSubsystems(bool duringReset) {
if (!globals->get_subsystem<FGHTTPClient>()) {
globals->add_new_subsystem<FGHTTPClient>();
}
globals->add_new_subsystem<FGDNSClient>();
////////////////////////////////////////////////////////////////////
// Initialize the flight model subsystem.

View file

@ -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}")

View file

@ -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 <http://www.gnu.org/licenses/>.
*/#include "MPServerResolver.hxx"
#include <Network/DNSClient.hxx>
#include <Main/fg_props.hxx>
#include <3rdparty/cjson/cJSON.h>
#include <cstdlib>
using namespace simgear;
/**
* Build a name=value map from base64 encoded JSON string
*/
class MPServerProperties : public std::map<string, string> {
public:
MPServerProperties (string b64)
{
std::vector<unsigned char> 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<FGDNSClient> ();
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<DNS::SRVRequest*> (_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<DNS::TXTRequest*> (_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<string,string>
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);
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef __FG_MPSERVERRESOLVER_HXX
#define __FG_MPSERVERRESOLVER_HXX
#include <string>
#include <simgear/props/props.hxx>
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

View file

@ -41,14 +41,14 @@
#include <simgear/debug/logstream.hxx>
#include <simgear/props/props.hxx>
#include <simgear/structure/commands.hxx>
#include <simgear/structure/event_mgr.hxx>
#include <AIModel/AIManager.hxx>
#include <AIModel/AIMultiplayer.hxx>
#include <Main/fg_props.hxx>
#include <Network/RemoteXMLRequest.hxx>
#include <Network/HTTPClient.hxx>
#include "multiplaymgr.hxx"
#include "mpmessages.hxx"
#include "MPServerResolver.hxx"
#include <FDM/flightProperties.hxx>
using namespace std;
@ -428,39 +428,59 @@ static bool do_multiplayer_disconnect(const SGPropertyNode * arg) {
// none
//
//////////////////////////////////////////////////////////////////////
static bool do_multiplayer_refreshserverlist(const SGPropertyNode * arg) {
static bool
do_multiplayer_refreshserverlist (const SGPropertyNode * arg)
{
using namespace simgear;
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<FGHTTPClient>();
if (!http) {
SG_LOG(SG_IO, SG_ALERT,
"do_multiplayer.refreshserverlist: HTTP client not running");
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);
}
string url(
fgGetString("/sim/multiplay/serverlist-url",
"http://liveries.flightgear.org/mpstatus/mpservers.xml"));
if (url.empty()) {
SG_LOG(SG_IO, SG_ALERT,
"do_multiplayer.refreshserverlist: no URL given");
return false;
~MyMPServerResolver ()
{
}
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);
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;
}

View file

@ -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

71
src/Network/DNSClient.cxx Normal file
View file

@ -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 <cassert>
#include <Main/fg_props.hxx>
#include <Include/version.h>
#include <simgear/sg_inlines.h>
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);
}

52
src/Network/DNSClient.hxx Normal file
View file

@ -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 <simgear/structure/subsystem_mgr.hxx>
#include <simgear/io/DNSClient.hxx>
#include <memory>
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<simgear::DNS::Client> _dns;
};
#endif // FG_DNS_CLIENT_HXX

View file

@ -1 +1 @@
2016.4.0
2017.1.0