#refs 686, used settings in readers

* moved threaded reader to BlackCore (settings are normally BlackCore aware)
* created ns/subfolder VATSIM
* prepared settings for the VATSIM readers
This commit is contained in:
Klaus Basan
2016-06-24 17:23:48 +02:00
parent 056841f9b1
commit 488d437a2a
24 changed files with 1472 additions and 1362 deletions

View File

@@ -0,0 +1,190 @@
/* Copyright (C) 2013
* swift project Community / Contributors
*
* This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level
* directory of this distribution and at http://www.swift-project.org/license.html. No part of swift project,
* including this file, may be copied, modified, propagated, or distributed except according to the terms
* contained in the LICENSE file.
*/
#include "blackcore/application.h"
#include "blackcore/data/globalsetup.h"
#include "blackcore/vatsim/vatsimbookingreader.h"
#include "blackmisc/aviation/atcstation.h"
#include "blackmisc/aviation/callsign.h"
#include "blackmisc/logmessage.h"
#include "blackmisc/network/entityflags.h"
#include "blackmisc/network/url.h"
#include "blackmisc/network/user.h"
#include "blackmisc/statusmessage.h"
#include <QByteArray>
#include <QDateTime>
#include <QDomDocument>
#include <QDomElement>
#include <QDomNode>
#include <QDomNodeList>
#include <QMetaObject>
#include <QNetworkReply>
#include <QScopedPointer>
#include <QScopedPointerDeleteLater>
#include <QString>
#include <QTimer>
#include <QUrl>
#include <Qt>
#include <QtGlobal>
using namespace BlackMisc;
using namespace BlackMisc::Aviation;
using namespace BlackMisc::Network;
namespace BlackCore
{
namespace Vatsim
{
CVatsimBookingReader::CVatsimBookingReader(QObject *owner) :
CThreadedReader(owner, "CVatsimBookingReader")
{
this->connect(this->m_updateTimer, &QTimer::timeout, this, &CVatsimBookingReader::ps_read);
}
void CVatsimBookingReader::readInBackgroundThread()
{
bool s = QMetaObject::invokeMethod(this, "ps_read");
Q_ASSERT(s);
Q_UNUSED(s);
}
void CVatsimBookingReader::cleanup()
{
// void
}
Settings::CSettingsReader CVatsimBookingReader::getSettings() const
{
return this->m_settings.get();
}
void CVatsimBookingReader::ps_read()
{
this->threadAssertCheck();
Q_ASSERT_X(sApp, Q_FUNC_INFO, "No application");
const QUrl url(sApp->getGlobalSetup().getVatsimBookingsUrl());
if (url.isEmpty()) { return; }
sApp->getFromNetwork(url, { this, &CVatsimBookingReader::ps_parseBookings});
}
void CVatsimBookingReader::ps_parseBookings(QNetworkReply *nwReplyPtr)
{
// wrap pointer, make sure any exit cleans up reply
// required to use delete later as object is created in a different thread
QScopedPointer<QNetworkReply, QScopedPointerDeleteLater> nwReply(nwReplyPtr);
this->threadAssertCheck();
// Worker thread, make sure to write no members here!
if (this->isAbandoned())
{
CLogMessage(this).debug() << Q_FUNC_INFO;
CLogMessage(this).info("terminated booking parsing process"); // for users
return; // stop, terminate straight away, ending thread
}
if (nwReply->error() == QNetworkReply::NoError)
{
static const QString timestampFormat("yyyy-MM-dd HH:mm:ss");
QString xmlData = nwReply->readAll();
nwReply->close(); // close asap
QDomDocument doc;
QDateTime updateTimestamp = QDateTime::currentDateTimeUtc();
if (doc.setContent(xmlData))
{
QDomNode timestamp = doc.elementsByTagName("timestamp").at(0);
QString ts = timestamp.toElement().text().trimmed();
Q_ASSERT(!ts.isEmpty());
if (!ts.isEmpty())
{
// normally the timestamp is always updated from backend
// if this changes in the future we're prepared
updateTimestamp = QDateTime::fromString(ts, timestampFormat);
updateTimestamp.setTimeSpec(Qt::UTC);
if (this->getUpdateTimestamp() == updateTimestamp) return; // nothing to do
}
QDomNode atc = doc.elementsByTagName("atcs").at(0);
QDomNodeList bookingNodes = atc.toElement().elementsByTagName("booking");
int size = bookingNodes.size();
CAtcStationList bookedStations;
for (int i = 0; i < size; i++)
{
if (this->isAbandoned())
{
CLogMessage(this).debug() << Q_FUNC_INFO;
CLogMessage(this).info("Terminated booking parsing process"); // for users
return; // stop, terminate straight away, ending thread
}
// pase nodes
QDomNode bookingNode = bookingNodes.at(i);
QDomNodeList bookingNodeValues = bookingNode.childNodes();
CAtcStation bookedStation;
CUser user;
for (int v = 0; v < bookingNodeValues.size(); v++)
{
QDomNode bookingNodeValue = bookingNodeValues.at(v);
QString name = bookingNodeValue.nodeName().toLower();
QString value = bookingNodeValue.toElement().text();
if (name == "id")
{
// could be used as unique key
}
else if (name == "callsign")
{
bookedStation.setCallsign(CCallsign(value, CCallsign::Atc));
}
else if (name == "name")
{
user.setRealName(value);
}
else if (name == "cid")
{
user.setId(value);
}
else if (name == "time_end")
{
QDateTime t = QDateTime::fromString(value, timestampFormat);
t.setTimeSpec(Qt::UTC);
bookedStation.setBookedUntilUtc(t);
}
else if (name == "time_start")
{
QDateTime t = QDateTime::fromString(value, timestampFormat);
t.setTimeSpec(Qt::UTC);
bookedStation.setBookedFromUtc(t);
}
}
// time checks
QDateTime now = QDateTime::currentDateTimeUtc();
if (now.msecsTo(bookedStation.getBookedUntilUtc()) < (1000 * 60 * 15)) { continue; } // until n mins in past
if (now.msecsTo(bookedStation.getBookedFromUtc()) > (1000 * 60 * 60 * 24)) { continue; } // to far in the future, n hours
bookedStation.setController(user);
bookedStations.push_back(bookedStation);
}
this->setUpdateTimestamp(updateTimestamp); // thread safe update
emit this->atcBookingsRead(bookedStations);
emit this->dataRead(CEntityFlags::BookingEntity, CEntityFlags::ReadFinished, bookedStations.size());
} // node
}
else
{
// network error
CLogMessage(this).warning("Reading bookings failed %1 %2") << nwReply->errorString() << nwReply->url().toString();
nwReply->abort();
emit this->dataRead(CEntityFlags::BookingEntity, CEntityFlags::ReadFailed, 0);
}
} // method
} // ns
} // ns

View File

@@ -0,0 +1,68 @@
/* Copyright (C) 2013
* swift project Community / Contributors
*
* This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level
* directory of this distribution and at http://www.swift-project.org/license.html. No part of swift project,
* including this file, may be copied, modified, propagated, or distributed except according to the terms
* contained in the LICENSE file.
*/
//! \file
#ifndef BLACKCORE_VATSIM_VATSIMBOOKINGREADER_H
#define BLACKCORE_VATSIM_VATSIMBOOKINGREADER_H
#include "blackcore/blackcoreexport.h"
#include "blackmisc/aviation/atcstationlist.h"
#include "blackmisc/network/entityflags.h"
#include "blackcore/threadedreader.h"
#include <QObject>
class QNetworkReply;
namespace BlackCore
{
namespace Vatsim
{
//! Read bookings from VATSIM
class BLACKCORE_EXPORT CVatsimBookingReader : public BlackCore::CThreadedReader
{
Q_OBJECT
public:
//! Constructor
explicit CVatsimBookingReader(QObject *owner);
//! Read / re-read bookings
void readInBackgroundThread();
signals:
//! Bookings have been read and converted to BlackMisc::Aviation::CAtcStationList
void atcBookingsRead(const BlackMisc::Aviation::CAtcStationList &bookedStations);
//! Data have been read
void dataRead(BlackMisc::Network::CEntityFlags::Entity entity, BlackMisc::Network::CEntityFlags::ReadState state, int number);
protected:
//! \name BlackCore::CThreadedReader overrides
//! @{
virtual void cleanup() override;
virtual BlackCore::Settings::CSettingsReader getSettings() const override;
//! @}
private slots:
//! Bookings have been read
//! \threadsafe
void ps_parseBookings(QNetworkReply *nwReply);
//! Do reading
void ps_read();
private:
BlackMisc::CSettingReadOnly<BlackCore::Settings::SettingsVatsimBookings> m_settings { this };
};
} // ns
} // ns
#endif // guard

View File

@@ -0,0 +1,440 @@
/* Copyright (C) 2013
* swift project Community / Contributors
*
* This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level
* directory of this distribution and at http://www.swift-project.org/license.html. No part of swift project,
* including this file, may be copied, modified, propagated, or distributed except according to the terms
* contained in the LICENSE file.
*/
#include "blackcore/vatsim/vatsimdatafilereader.h"
#include "blackcore/application.h"
#include "blackmisc/aviation/aircraftsituation.h"
#include "blackmisc/aviation/altitude.h"
#include "blackmisc/aviation/atcstation.h"
#include "blackmisc/compare.h"
#include "blackmisc/geo/coordinategeodetic.h"
#include "blackmisc/logmessage.h"
#include "blackmisc/network/entityflags.h"
#include "blackmisc/network/server.h"
#include "blackmisc/network/url.h"
#include "blackmisc/network/urllist.h"
#include "blackmisc/network/user.h"
#include "blackmisc/pq/frequency.h"
#include "blackmisc/pq/length.h"
#include "blackmisc/pq/speed.h"
#include "blackmisc/pq/units.h"
#include "blackmisc/predicates.h"
#include "blackmisc/range.h"
#include "blackmisc/simulation/simulatedaircraft.h"
#include "blackmisc/statusmessage.h"
#include "blackmisc/verify.h"
#include <QByteArray>
#include <QDateTime>
#include <QMetaObject>
#include <QNetworkReply>
#include <QReadLocker>
#include <QRegExp>
#include <QRegularExpression>
#include <QScopedPointer>
#include <QScopedPointerDeleteLater>
#include <QTimer>
#include <QUrl>
#include <QWriteLocker>
#include <Qt>
#include <QtGlobal>
using namespace BlackMisc;
using namespace BlackMisc::Aviation;
using namespace BlackMisc::Network;
using namespace BlackMisc::Geo;
using namespace BlackMisc::Simulation;
using namespace BlackMisc::PhysicalQuantities;
using namespace BlackCore::Data;
using namespace BlackCore::Settings;
namespace BlackCore
{
namespace Vatsim
{
CVatsimDataFileReader::CVatsimDataFileReader(QObject *owner) :
CThreadedReader(owner, "CVatsimDataFileReader")
{
this->connect(this->m_updateTimer, &QTimer::timeout, this, &CVatsimDataFileReader::ps_read);
}
CSimulatedAircraftList CVatsimDataFileReader::getAircraft() const
{
QReadLocker rl(&this->m_lock);
return this->m_aircraft;
}
CAtcStationList CVatsimDataFileReader::getAtcStations() const
{
QReadLocker rl(&this->m_lock);
return this->m_atcStations;
}
CAtcStationList CVatsimDataFileReader::getAtcStationsForCallsign(const CCallsign &callsign) const
{
CCallsignSet cs({callsign});
return this->getAtcStationsForCallsigns(cs);
}
CAtcStationList CVatsimDataFileReader::getAtcStationsForCallsigns(const CCallsignSet &callsigns) const
{
return this->getAtcStations().findByCallsigns(callsigns);
}
CServerList CVatsimDataFileReader::getVoiceServers() const
{
return this->m_lastGoodSetup.get().getVoiceServers();
}
CServerList CVatsimDataFileReader::getFsdServers() const
{
return this->m_lastGoodSetup.get().getFsdServers();
}
CUserList CVatsimDataFileReader::getPilotsForCallsigns(const CCallsignSet &callsigns)
{
return this->getAircraft().findByCallsigns(callsigns).transform(Predicates::MemberTransform(&CSimulatedAircraft::getPilot));
}
CUserList CVatsimDataFileReader::getPilotsForCallsign(const CCallsign &callsign)
{
CCallsignSet callsigns({callsign});
return this->getPilotsForCallsigns(callsigns);
}
CAirlineIcaoCode CVatsimDataFileReader::getAirlineIcaoCode(const CCallsign &callsign)
{
CSimulatedAircraft aircraft = this->getAircraft().findFirstByCallsign(callsign);
return aircraft.getAirlineIcaoCode();
}
CAircraftIcaoCode CVatsimDataFileReader::getAircraftIcaoCode(const CCallsign &callsign)
{
CSimulatedAircraft aircraft = this->getAircraft().findFirstByCallsign(callsign);
return aircraft.getAircraftIcaoCode();
}
CVoiceCapabilities CVatsimDataFileReader::getVoiceCapabilityForCallsign(const CCallsign &callsign)
{
QReadLocker rl(&this->m_lock);
if (this->m_voiceCapabilities.contains(callsign))
{
return m_voiceCapabilities[callsign];
}
else
{
return CVoiceCapabilities::fromVoiceCapabilities(CVoiceCapabilities::Unknown);
}
}
void CVatsimDataFileReader::updateWithVatsimDataFileData(CSimulatedAircraft &aircraftToBeUdpated) const
{
this->getAircraft().updateWithVatsimDataFileData(aircraftToBeUdpated);
}
CUserList CVatsimDataFileReader::getControllersForCallsign(const CCallsign &callsign)
{
CCallsignSet cs({callsign});
return this->getControllersForCallsigns(cs);
}
CUserList CVatsimDataFileReader::getControllersForCallsigns(const CCallsignSet &callsigns)
{
return this->getAtcStations().findByCallsigns(callsigns).transform(Predicates::MemberTransform(&CAtcStation::getController));
}
CUserList CVatsimDataFileReader::getUsersForCallsign(const CCallsign &callsign)
{
CCallsignSet callsigns({callsign});
return this->getUsersForCallsigns(callsigns);
}
CUserList CVatsimDataFileReader::getUsersForCallsigns(const CCallsignSet &callsigns)
{
CUserList users;
if (callsigns.isEmpty()) { return users; }
for (const CCallsign &callsign : callsigns)
{
users.push_back(this->getPilotsForCallsign(callsign));
users.push_back(this->getControllersForCallsign(callsign));
}
return users;
}
void CVatsimDataFileReader::readInBackgroundThread()
{
bool s = QMetaObject::invokeMethod(this, "ps_read");
Q_ASSERT_X(s, Q_FUNC_INFO, "Invoke failed");
Q_UNUSED(s);
}
void CVatsimDataFileReader::cleanup()
{
// void
}
CSettingsReader CVatsimDataFileReader::getSettings() const
{
return this->m_settings.get();
}
void CVatsimDataFileReader::ps_read()
{
this->threadAssertCheck();
// round robin for load balancing
// remark: Don't use QThread to run network operations in the background
// see http://qt-project.org/doc/qt-4.7/qnetworkaccessmanager.html
Q_ASSERT_X(sApp, Q_FUNC_INFO, "Missing application");
CFailoverUrlList urls(sApp->getVatsimDataFileUrls());
const QUrl url(urls.obtainNextWorkingUrl(true));
if (url.isEmpty()) { return; }
sApp->getFromNetwork(url, { this, &CVatsimDataFileReader::ps_parseVatsimFile});
}
void CVatsimDataFileReader::ps_parseVatsimFile(QNetworkReply *nwReplyPtr)
{
// wrap pointer, make sure any exit cleans up reply
// required to use delete later as object is created in a different thread
QScopedPointer<QNetworkReply, QScopedPointerDeleteLater> nwReply(nwReplyPtr);
this->threadAssertCheck();
// Worker thread, make sure to write only synced here!
if (this->isAbandoned())
{
CLogMessage(this).debug() << Q_FUNC_INFO;
CLogMessage(this).info("Terminated VATSIM file parsing process"); // for users
return; // stop, terminate straight away, ending thread
}
QStringList illegalIcaoCodes;
if (nwReply->error() == QNetworkReply::NoError)
{
const QString dataFileData = nwReply->readAll();
nwReply->close(); // close asap
if (dataFileData.isEmpty()) return;
const QStringList lines = dataFileData.split(QRegExp("[\r\n]"), QString::SkipEmptyParts);
if (lines.isEmpty()) { return; }
// build on local vars for thread safety
CServerList voiceServers;
CServerList fsdServers;
CAtcStationList atcStations;
CSimulatedAircraftList aircraft;
QMap<CCallsign, CVoiceCapabilities> voiceCapabilities;
QDateTime updateTimestampFromFile;
QStringList clientSectionAttributes;
Section section = SectionNone;
for (const QString &cl : lines)
{
if (this->isAbandoned())
{
CLogMessage(this).debug() << Q_FUNC_INFO;
CLogMessage(this).info("Terminated booking parsing process"); // for users
return; // stop, terminate straight away, ending thread
}
// parse lines
QString currentLine(cl.trimmed());
if (currentLine.isEmpty()) continue;
if (currentLine.startsWith(";"))
{
if (clientSectionAttributes.isEmpty() && currentLine.contains("!CLIENTS SECTION", Qt::CaseInsensitive))
{
// ; !CLIENTS section
int i = currentLine.lastIndexOf(' ');
const QString attributes = currentLine.mid(i).trimmed();
clientSectionAttributes = attributes.split(':', QString::SkipEmptyParts);
section = SectionNone; // reset
}
continue;
}
else if (currentLine.startsWith("!"))
{
section = currentLineToSection(currentLine);
continue;
}
switch (section)
{
case SectionClients:
{
const QMap<QString, QString> clientPartsMap = clientPartsToMap(currentLine, clientSectionAttributes);
const CCallsign callsign = CCallsign(clientPartsMap["callsign"]);
if (callsign.isEmpty()) { break; }
const BlackMisc::Network::CUser user(clientPartsMap["cid"], clientPartsMap["realname"], callsign);
const QString clientType = clientPartsMap["clienttype"].toLower();
if (clientType.isEmpty()) { break; } // sometimes type is empty
const double lat = clientPartsMap["latitude"].toDouble();
const double lng = clientPartsMap["longitude"].toDouble();
const double alt = clientPartsMap["altitude"].toDouble();
const CFrequency frequency = CFrequency(clientPartsMap["frequency"].toDouble(), CFrequencyUnit::MHz());
CCoordinateGeodetic position(lat, lng, -1);
CAltitude altitude(alt, CAltitude::MeanSeaLevel, CLengthUnit::ft());
QString flightPlanRemarks = clientPartsMap["planned_remarks"];
// Voice capabilities
if (!flightPlanRemarks.isEmpty())
{
CVoiceCapabilities vc(flightPlanRemarks);
if (!vc.isUnknown())
{
voiceCapabilities.insert(callsign, vc);
}
}
// set as per ATC/pilot
if (clientType.startsWith('p'))
{
// Pilot section
const double groundspeed = clientPartsMap["groundspeed"].toDouble();
CAircraftSituation situation(position, altitude);
situation.setGroundSpeed(CSpeed(groundspeed, CSpeedUnit::kts()));
CSimulatedAircraft currentAircraft(user.getCallsign().getStringAsSet(), user, situation);
QString aircraftIcaoCode = clientPartsMap["planned_aircraft"];
if (!aircraftIcaoCode.isEmpty())
{
// http://uk.flightaware.com/about/faq_aircraft_flight_plan_suffix.rvt
// we expect something like H/B772/F B773 B773/F
static const QRegularExpression reg("/.");
aircraftIcaoCode = aircraftIcaoCode.replace(reg, "").trimmed().toUpper();
if (CAircraftIcaoCode::isValidDesignator(aircraftIcaoCode))
{
currentAircraft.setAircraftIcaoDesignator(aircraftIcaoCode);
}
else
{
illegalIcaoCodes.append(aircraftIcaoCode);
}
}
aircraft.push_back(currentAircraft);
}
else if (clientType.startsWith('a'))
{
// ATC section
CLength range;
position.setGeodeticHeight(altitude); // the altitude is elevation for a station
CAtcStation station(user.getCallsign().getStringAsSet(), user, frequency, position, range);
station.setOnline(true);
atcStations.push_back(station);
}
else
{
BLACK_VERIFY_X(false, Q_FUNC_INFO, "Wrong client type");
break;
}
}
break;
case SectionGeneral:
{
if (currentLine.contains("UPDATE"))
{
const QStringList updateParts = currentLine.replace(" ", "").split('=');
if (updateParts.length() < 2) break;
const QString dts = updateParts.at(1).trimmed();
updateTimestampFromFile = QDateTime::fromString(dts, "yyyyMMddHHmmss");
updateTimestampFromFile.setOffsetFromUtc(0);
bool alreadyRead = (updateTimestampFromFile == this->getUpdateTimestamp());
if (alreadyRead) { return; }// still same data, terminate
}
}
break;
case SectionFsdServers:
{
// ident:hostname_or_IP:location:name:clients_connection_allowed:
const QStringList fsdServerParts = currentLine.split(':');
if (fsdServerParts.size() < 5) break;
if (!fsdServerParts.at(4).trimmed().contains('1')) break; // allowed?
QString description(fsdServerParts.at(2)); // part(3) could be added
BlackMisc::Network::CServer fsdServer(fsdServerParts.at(0), description, fsdServerParts.at(1), 6809, CUser("id", "real name", "email", "password"));
fsdServers.push_back(fsdServer);
}
break;
case SectionVoiceServers:
{
// hostname_or_IP:location:name:clients_connection_allowed:type_of_voice_server:
const QStringList voiceServerParts = currentLine.split(':');
if (voiceServerParts.size() < 3) break;
if (!voiceServerParts.at(3).trimmed().contains('1')) break; // allowed?
BlackMisc::Network::CServer voiceServer(voiceServerParts.at(1), voiceServerParts.at(2), voiceServerParts.at(0), -1, CUser());
voiceServers.push_back(voiceServer);
}
break;
case SectionNone:
default:
break;
} // switch section
} // for each line
// this part needs to be synchronized
{
QWriteLocker wl(&this->m_lock);
this->setUpdateTimestamp(updateTimestampFromFile);
this->m_aircraft = aircraft;
this->m_atcStations = atcStations;
this->m_voiceCapabilities = voiceCapabilities;
CVatsimSetup vs(this->m_lastGoodSetup.getThreadLocal());
vs.setVoiceServers(voiceServers);
vs.setFsdServers(fsdServers);
vs.setUtcTimestamp(updateTimestampFromFile);
this->m_lastGoodSetup.set(vs);
}
// warnings, if required
if (!illegalIcaoCodes.isEmpty())
{
CLogMessage(this).info("Illegal / ignored ICAO code(s) in VATSIM data file: %1") << illegalIcaoCodes.join(", ");
}
// data read finished
emit this->dataFileRead(lines.count());
emit this->dataRead(CEntityFlags::VatsimDataFile, CEntityFlags::ReadFinished, lines.count());
}
else
{
// network error
CLogMessage(this).warning("Reading VATSIM data file failed %1 %2") << nwReply->errorString() << nwReply->url().toString();
nwReply->abort();
emit this->dataRead(CEntityFlags::VatsimDataFile, CEntityFlags::ReadFailed, 0);
}
}
const QMap<QString, QString> CVatsimDataFileReader::clientPartsToMap(const QString &currentLine, const QStringList &clientSectionAttributes)
{
QMap<QString, QString> parts;
if (currentLine.isEmpty()) { return parts; }
const QStringList clientParts = currentLine.split(':');
for (int i = 0; i < clientSectionAttributes.size(); i++)
{
BLACK_VERIFY_X(i < clientSectionAttributes.size(), Q_FUNC_INFO, "Wrong section attribute size");
BLACK_VERIFY_X(i < clientParts.size(), Q_FUNC_INFO, "Wrong parts size");
if (i < clientSectionAttributes.size() || i < clientParts.size()) { continue; }
parts.insert(clientSectionAttributes.at(i).toLower(), clientParts.at(i));
}
return parts;
}
CVatsimDataFileReader::Section CVatsimDataFileReader::currentLineToSection(const QString &currentLine)
{
if (currentLine.contains("!GENERAL", Qt::CaseInsensitive)) { return SectionGeneral; }
if (currentLine.contains("!VOICE SERVERS", Qt::CaseInsensitive)) { return SectionVoiceServers; }
if (currentLine.contains("!SERVERS", Qt::CaseInsensitive)) { return SectionFsdServers; }
if (currentLine.contains("!CLIENTS", Qt::CaseInsensitive)) { return SectionClients; }
return SectionNone;
}
} // ns
} // ns

View File

@@ -0,0 +1,167 @@
/* Copyright (C) 2013
* swift project Community / Contributors
*
* This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level
* directory of this distribution and at http://www.swift-project.org/license.html. No part of swift project,
* including this file, may be copied, modified, propagated, or distributed except according to the terms
* contained in the LICENSE file.
*/
//! \file
#ifndef BLACKCORE_VATSIM_VATSIMDATAFILEREADER_H
#define BLACKCORE_VATSIM_VATSIMDATAFILEREADER_H
#include "blackcore/blackcoreexport.h"
#include "blackcore/data/vatsimsetup.h"
#include "blackmisc/aviation/aircrafticaocode.h"
#include "blackmisc/aviation/airlineicaocode.h"
#include "blackmisc/aviation/atcstationlist.h"
#include "blackmisc/aviation/callsign.h"
#include "blackmisc/aviation/callsignset.h"
#include "blackmisc/datacache.h"
#include "blackmisc/network/entityflags.h"
#include "blackmisc/network/serverlist.h"
#include "blackmisc/network/userlist.h"
#include "blackmisc/network/voicecapabilities.h"
#include "blackmisc/simulation/simulatedaircraftlist.h"
#include "blackcore/threadedreader.h"
#include <QMap>
#include <QObject>
#include <QString>
#include <QStringList>
class QNetworkReply;
namespace BlackMisc { namespace Simulation { class CSimulatedAircraft; } }
namespace BlackCore
{
namespace Vatsim
{
//! Read vatsim data file
//! \sa http://info.vroute.net/vatsim-data.txt
class BLACKCORE_EXPORT CVatsimDataFileReader : public BlackCore::CThreadedReader
{
Q_OBJECT
public:
//! Constructor
explicit CVatsimDataFileReader(QObject *owner);
//! Get aircraft
//! \threadsafe
BlackMisc::Simulation::CSimulatedAircraftList getAircraft() const;
//! Get ATC station
//! \threadsafe
BlackMisc::Aviation::CAtcStationList getAtcStations() const;
//! Get ATC stations for callsign
//! \threadsafe
BlackMisc::Aviation::CAtcStationList getAtcStationsForCallsign(const BlackMisc::Aviation::CCallsign &callsign) const;
//! Get ATC stations for callsigns
//! \threadsafe
BlackMisc::Aviation::CAtcStationList getAtcStationsForCallsigns(const BlackMisc::Aviation::CCallsignSet &callsigns) const;
//! Get all voice servers
//! \threadsafe
BlackMisc::Network::CServerList getVoiceServers() const;
//! Get all VATSIM FSD servers
//! \threadsafe
BlackMisc::Network::CServerList getFsdServers() const;
//! Users for callsign(s)
//! \threadsafe
BlackMisc::Network::CUserList getUsersForCallsigns(const BlackMisc::Aviation::CCallsignSet &callsigns);
//! User for callsign
//! \threadsafe
BlackMisc::Network::CUserList getUsersForCallsign(const BlackMisc::Aviation::CCallsign &callsign);
//! Controllers for callsigns
//! \threadsafe
BlackMisc::Network::CUserList getControllersForCallsigns(const BlackMisc::Aviation::CCallsignSet &callsigns);
//! Controllers for callsign
//! \threadsafe
BlackMisc::Network::CUserList getControllersForCallsign(const BlackMisc::Aviation::CCallsign &callsign);
//! Users for callsigns
//! \threadsafe
BlackMisc::Network::CUserList getPilotsForCallsigns(const BlackMisc::Aviation::CCallsignSet &callsigns);
//! Users for callsign
//! \threadsafe
BlackMisc::Network::CUserList getPilotsForCallsign(const BlackMisc::Aviation::CCallsign &callsign);
//! Aircraft ICAO info for callsign
//! \threadsafe
BlackMisc::Aviation::CAircraftIcaoCode getAircraftIcaoCode(const BlackMisc::Aviation::CCallsign &callsign);
//! Airline ICAO info for callsign
//! \threadsafe
BlackMisc::Aviation::CAirlineIcaoCode getAirlineIcaoCode(const BlackMisc::Aviation::CCallsign &callsign);
//! Voice capability for callsign
//! \threadsafe
BlackMisc::Network::CVoiceCapabilities getVoiceCapabilityForCallsign(const BlackMisc::Aviation::CCallsign &callsign);
//! Update aircraft with VATSIM aircraft data from data file
//! \threadsafe
void updateWithVatsimDataFileData(BlackMisc::Simulation::CSimulatedAircraft &aircraftToBeUdpated) const;
//! Start reading in own thread
void readInBackgroundThread();
signals:
//! Data have been read
void dataFileRead(int lines);
//! Data have been read
void dataRead(BlackMisc::Network::CEntityFlags::Entity entity, BlackMisc::Network::CEntityFlags::ReadState state, int number);
protected:
//! \name BlackCore::CThreadedReader overrides
//! @{
virtual void cleanup() override;
virtual BlackCore::Settings::CSettingsReader getSettings() const override;
//! @}
private slots:
//! Data have been read, parse VATSIM file
void ps_parseVatsimFile(QNetworkReply *nwReply);
//! Read / re-read data file
void ps_read();
private:
BlackMisc::Aviation::CAtcStationList m_atcStations;
BlackMisc::Simulation::CSimulatedAircraftList m_aircraft;
BlackMisc::CData<BlackCore::Data::VatsimSetup> m_lastGoodSetup { this };
BlackMisc::CSettingReadOnly<BlackCore::Settings::SettingsVatsimDataFile> m_settings { this };
QMap<BlackMisc::Aviation::CCallsign, BlackMisc::Network::CVoiceCapabilities> m_voiceCapabilities;
//! Split line and assign values to their corresponding attribute names
static const QMap<QString, QString> clientPartsToMap(const QString &currentLine, const QStringList &clientSectionAttributes);
//! Section in file
enum Section
{
SectionNone,
SectionFsdServers,
SectionVoiceServers,
SectionClients,
SectionGeneral
};
//! Get current section
static Section currentLineToSection(const QString &currentLine);
};
} // ns
} // ns
#endif // guard

View File

@@ -0,0 +1,158 @@
/* Copyright (C) 2015
* swift project Community / Contributors
*
* This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level
* directory of this distribution and at http://www.swift-project.org/license.html. No part of swift project,
* including this file, may be copied, modified, propagated, or distributed except according to the terms
* contained in the LICENSE file.
*/
#include "blackcore/vatsim/vatsimmetarreader.h"
#include "blackcore/application.h"
#include "blackmisc/compare.h"
#include "blackmisc/logmessage.h"
#include "blackmisc/network/entityflags.h"
#include "blackmisc/network/url.h"
#include "blackmisc/network/urllist.h"
#include "blackmisc/statusmessage.h"
#include <QByteArray>
#include <QMetaObject>
#include <QNetworkReply>
#include <QReadLocker>
#include <QScopedPointer>
#include <QScopedPointerDeleteLater>
#include <QString>
#include <QTextStream>
#include <QTimer>
#include <QUrl>
#include <QWriteLocker>
#include <QtGlobal>
using namespace BlackMisc;
using namespace BlackMisc::Network;
using namespace BlackMisc::Weather;
using namespace BlackCore::Data;
namespace BlackCore
{
namespace Vatsim
{
CVatsimMetarReader::CVatsimMetarReader(QObject *owner) :
CThreadedReader(owner, "CVatsimMetarReader")
{
this->connect(this->m_updateTimer, &QTimer::timeout, this, &CVatsimMetarReader::ps_readMetars);
}
void CVatsimMetarReader::readInBackgroundThread()
{
bool s = QMetaObject::invokeMethod(this, "ps_readMetars");
Q_ASSERT_X(s, Q_FUNC_INFO, "Cannot invoke");
Q_UNUSED(s);
}
CMetarSet CVatsimMetarReader::getMetars() const
{
QReadLocker l(&m_lock);
return m_metars;
}
CMetar CVatsimMetarReader::getMetarForAirport(const Aviation::CAirportIcaoCode &icao) const
{
QReadLocker l(&m_lock);
return m_metars.getMetarForAirport(icao);
}
int CVatsimMetarReader::getMetarsCount() const
{
QReadLocker l(&m_lock);
return m_metars.size();
}
void CVatsimMetarReader::cleanup()
{
// void
}
Settings::CSettingsReader CVatsimMetarReader::getSettings() const
{
return m_settings.get();
}
void CVatsimMetarReader::ps_readMetars()
{
this->threadAssertCheck();
CFailoverUrlList urls(sApp->getVatsimMetarUrls());
const CUrl url(urls.obtainNextWorkingUrl(true));
if (url.isEmpty()) { return; }
Q_ASSERT_X(sApp, Q_FUNC_INFO, "No Application");
sApp->getFromNetwork(url.withAppendedQuery("id=all"), { this, &CVatsimMetarReader::ps_decodeMetars});
}
void CVatsimMetarReader::ps_decodeMetars(QNetworkReply *nwReplyPtr)
{
// wrap pointer, make sure any exit cleans up reply
// required to use delete later as object is created in a different thread
QScopedPointer<QNetworkReply, QScopedPointerDeleteLater> nwReply(nwReplyPtr);
this->threadAssertCheck();
// Worker thread, make sure to write thread safe!
if (this->isAbandoned())
{
CLogMessage(this).debug() << Q_FUNC_INFO;
CLogMessage(this).info("terminated METAR decoding process"); // for users
return; // stop, terminate straight away, ending thread
}
if (nwReply->error() == QNetworkReply::NoError)
{
QString metarData = nwReply->readAll();
nwReply->close(); // close asap
CMetarSet metars;
QString invalidMetars;
int invalidLineCount = 0;
QTextStream lineReader(&metarData);
while (!lineReader.atEnd())
{
if (this->isAbandoned()) { return; }
QString line = lineReader.readLine();
CMetar metar = m_metarDecoder.decode(line);
if (metar != CMetar())
{
metars.push_back(metar);
}
else
{
invalidMetars += line;
invalidMetars += "\n";
invalidLineCount++;
}
}
{
QWriteLocker l(&m_lock);
m_metars = metars;
}
// I could use those for logging, etc.
Q_UNUSED(invalidMetars);
if (invalidLineCount > 0)
{
// Regular issue, log it, but do not show to user
CLogMessage(this).debug() << "Reading METARs failed for entries" << invalidLineCount;
}
emit metarsRead(metars);
emit dataRead(CEntityFlags::MetarEntity, CEntityFlags::ReadFinished, metars.size());
}
else
{
// network error
CLogMessage(this).warning("Reading METARs failed %1 %2") << nwReply->errorString() << nwReply->url().toString();
nwReply->abort();
emit dataRead(CEntityFlags::MetarEntity, CEntityFlags::ReadFailed, 0);
}
} // method
} // ns
} // ns

View File

@@ -0,0 +1,84 @@
/* Copyright (C) 2015
* swift project Community / Contributors
*
* This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level
* directory of this distribution and at http://www.swift-project.org/license.html. No part of swift project,
* including this file, may be copied, modified, propagated, or distributed except according to the terms
* contained in the LICENSE file.
*/
//! \file
#ifndef BLACKCORE_VATSIM_VATSIMMETARREADER_H
#define BLACKCORE_VATSIM_VATSIMMETARREADER_H
#include "blackcore/blackcoreexport.h"
#include "blackmisc/aviation/airporticaocode.h"
#include "blackmisc/network/entityflags.h"
#include "blackcore/threadedreader.h"
#include "blackmisc/weather/metar.h"
#include "blackmisc/weather/metardecoder.h"
#include "blackmisc/weather/metarset.h"
#include <QObject>
class QNetworkReply;
namespace BlackCore
{
namespace Vatsim
{
//! Read bookings from VATSIM
class BLACKCORE_EXPORT CVatsimMetarReader : public BlackCore::CThreadedReader
{
Q_OBJECT
public:
//! Constructor
explicit CVatsimMetarReader(QObject *owner);
//! Read / re-read bookings
void readInBackgroundThread();
//! Get METARs
//! \threadsafe
virtual BlackMisc::Weather::CMetarSet getMetars() const;
//! Get METAR for airport
//! \threadsafe
virtual BlackMisc::Weather::CMetar getMetarForAirport(const BlackMisc::Aviation::CAirportIcaoCode &icao) const;
//! Get METARs count
//! \threadsafe
virtual int getMetarsCount() const;
signals:
//! METARs have been read and converted to BlackMisc::Weather::CMetarSet
void metarsRead(const BlackMisc::Weather::CMetarSet &metars);
//! Data have been read
void dataRead(BlackMisc::Network::CEntityFlags::Entity entity, BlackMisc::Network::CEntityFlags::ReadState state, int number);
protected:
//! \name BlackCore::CThreadedReader overrides
//! @{
virtual void cleanup() override;
virtual BlackCore::Settings::CSettingsReader getSettings() const override;
//! @}
private slots:
//! Decode METARs
//! \threadsafe
void ps_decodeMetars(QNetworkReply *nwReply);
//! Do reading
void ps_readMetars();
private:
BlackMisc::Weather::CMetarDecoder m_metarDecoder;
BlackMisc::Weather::CMetarSet m_metars;
BlackMisc::CSettingReadOnly<BlackCore::Settings::SettingsVatsimMetars> m_settings { this };
};
} // ns
} // ns
#endif // guard

View File

@@ -0,0 +1,181 @@
/* Copyright (C) 2013
* swift project Community / Contributors
*
* This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level
* directory of this distribution and at http://www.swift-project.org/license.html. No part of swift project,
* including this file, may be copied, modified, propagated, or distributed except according to the terms
* contained in the LICENSE file.
*/
#include "blackcore/vatsim/vatsimstatusfilereader.h"
#include "blackcore/application.h"
#include "blackcore/data/globalsetup.h"
#include "blackmisc/logmessage.h"
#include "blackmisc/network/url.h"
#include "blackmisc/network/urllist.h"
#include "blackmisc/statusmessage.h"
#include <QByteArray>
#include <QDateTime>
#include <QList>
#include <QMetaObject>
#include <QNetworkReply>
#include <QRegExp>
#include <QScopedPointer>
#include <QScopedPointerDeleteLater>
#include <QString>
#include <QStringList>
#include <QTimer>
#include <QUrl>
#include <QtGlobal>
using namespace BlackMisc;
using namespace BlackMisc::Aviation;
using namespace BlackMisc::Network;
using namespace BlackMisc::Simulation;
using namespace BlackMisc::PhysicalQuantities;
using namespace BlackCore::Data;
using namespace BlackCore::Settings;
namespace BlackCore
{
namespace Vatsim
{
CVatsimStatusFileReader::CVatsimStatusFileReader(QObject *owner) :
CThreadedReader(owner, "CVatsimStatusFileReader")
{
// do not connect with time, will be read once at startup
}
void CVatsimStatusFileReader::readInBackgroundThread()
{
bool s = QMetaObject::invokeMethod(this, "ps_read");
Q_ASSERT_X(s, Q_FUNC_INFO, "Invoke failed");
Q_UNUSED(s);
}
CUrlList CVatsimStatusFileReader::getMetarFileUrls() const
{
return this->m_lastGoodSetup.get().getMetarFileUrls();
}
CUrlList CVatsimStatusFileReader::getDataFileUrls() const
{
return this->m_lastGoodSetup.get().getDataFileUrls();
}
void CVatsimStatusFileReader::cleanup()
{
// void
}
void CVatsimStatusFileReader::ps_read()
{
this->threadAssertCheck();
Q_ASSERT_X(sApp, Q_FUNC_INFO, "Missing application");
CFailoverUrlList urls(sApp->getGlobalSetup().getVatsimStatusFileUrls());
const CUrl url(urls.obtainNextWorkingUrl(true)); // random working URL
if (url.isEmpty()) { return; }
sApp->getFromNetwork(url, { this, &CVatsimStatusFileReader::ps_parseVatsimFile});
}
void CVatsimStatusFileReader::ps_parseVatsimFile(QNetworkReply *nwReplyPtr)
{
// wrap pointer, make sure any exit cleans up reply
// required to use delete later as object is created in a different thread
QScopedPointer<QNetworkReply, QScopedPointerDeleteLater> nwReply(nwReplyPtr);
this->threadAssertCheck();
// Worker thread, make sure to write only synced here!
if (this->isAbandoned())
{
CLogMessage(this).debug() << Q_FUNC_INFO;
CLogMessage(this).info("Terminated VATSIM status file parsing process"); // for users
return; // stop, terminate straight away, ending thread
}
QStringList illegalIcaoCodes;
if (nwReply->error() == QNetworkReply::NoError)
{
const QString dataFileData = nwReply->readAll();
nwReply->close(); // close asap
if (dataFileData.isEmpty()) return;
const QStringList lines = dataFileData.split(QRegExp("[\r\n]"), QString::SkipEmptyParts);
if (lines.isEmpty()) { return; }
CUrlList dataFiles;
CUrlList serverFiles;
CUrlList metarFiles;
for (const QString &cl : lines)
{
if (this->isAbandoned())
{
CLogMessage(this).debug() << Q_FUNC_INFO;
CLogMessage(this).info("Terminated status parsing process"); // for users
return; // stop, terminate straight away, ending thread
}
// parse lines
const QString currentLine(cl.trimmed());
if (currentLine.isEmpty()) { continue; }
if (currentLine.startsWith(";")) { continue; }
if (!currentLine.contains("=")) { continue; }
const QStringList parts(currentLine.split('='));
if (parts.length() != 2) { continue; }
const QString key(parts[0].trimmed().toLower());
const QString value(parts[1].trimmed());
const CUrl url(value);
if (key.startsWith("url0"))
{
dataFiles.push_back(url);
}
else if (key.startsWith("url1"))
{
serverFiles.push_back(url);
}
else if (key.startsWith("metar"))
{
metarFiles.push_back(url);
}
else if (key.startsWith("atis"))
{
// not yet used
}
} // for each line
// this part needs to be synchronized
{
// cache itself is thread safe
CVatsimSetup vs(this->m_lastGoodSetup.get());
vs.setDataFileUrls(dataFiles);
vs.setMetarFileUrls(metarFiles);
vs.setServerFileUrls(serverFiles);
vs.setUtcTimestamp(QDateTime::currentDateTime());
this->m_lastGoodSetup.set(vs);
}
// warnings, if required
if (!illegalIcaoCodes.isEmpty())
{
CLogMessage(this).info("Illegal / ignored ICAO code(s) in VATSIM data file: %1") << illegalIcaoCodes.join(", ");
}
// data read finished
emit this->dataFileRead(lines.count());
emit this->dataRead(CEntityFlags::VatsimStatusFile, CEntityFlags::ReadFinished, lines.count());
}
else
{
// network error
CLogMessage(this).warning("Reading VATSIM status file failed %1 %2") << nwReply->errorString() << nwReply->url().toString();
nwReply->abort();
emit this->dataRead(CEntityFlags::VatsimStatusFile, CEntityFlags::ReadFailed, 0);
}
}
} // ns
} // ns

View File

@@ -0,0 +1,78 @@
/* Copyright (C) 2013
* swift project Community / Contributors
*
* This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level
* directory of this distribution and at http://www.swift-project.org/license.html. No part of swift project,
* including this file, may be copied, modified, propagated, or distributed except according to the terms
* contained in the LICENSE file.
*/
//! \file
#ifndef BLACKCORE_VATSIM_VATSIMSTATUSFILEREADER_H
#define BLACKCORE_VATSIM_VATSIMSTATUSFILEREADER_H
#include "blackcore/blackcoreexport.h"
#include "blackcore/data/vatsimsetup.h"
#include "blackmisc/datacache.h"
#include "blackmisc/network/entityflags.h"
#include "blackmisc/network/urllist.h"
#include "blackcore/threadedreader.h"
#include <QObject>
class QNetworkReply;
namespace BlackCore
{
namespace Vatsim
{
//! Sole purpose is to read the URLs where VATSIM data can be downloaded
//! \sa https://status.vatsim.net/
class BLACKCORE_EXPORT CVatsimStatusFileReader : public BlackCore::CThreadedReader
{
Q_OBJECT
public:
//! Constructor
explicit CVatsimStatusFileReader(QObject *owner);
//! METAR URLs
//! \threadsafe
BlackMisc::Network::CUrlList getMetarFileUrls() const;
//! Data file URLs
//! \threadsafe
BlackMisc::Network::CUrlList getDataFileUrls() const;
public slots:
//! Start reading in own thread
void readInBackgroundThread();
signals:
//! Data have been read
void dataFileRead(int lines);
//! Data have been read
void dataRead(BlackMisc::Network::CEntityFlags::Entity entity, BlackMisc::Network::CEntityFlags::ReadState state, int number);
protected:
//! \name BlackCore::CThreadedReader overrides
//! @{
virtual void cleanup() override;
//! @}
private slots:
//! Data have been read, parse VATSIM file
void ps_parseVatsimFile(QNetworkReply *nwReply);
//! Read / re-read data file
void ps_read();
private:
BlackMisc::CData<BlackCore::Data::VatsimSetup> m_lastGoodSetup { this };
};
} // ns
} // ns
#endif // guard