diff --git a/src/blackcore/vatsimbookingreader.cpp b/src/blackcore/vatsimbookingreader.cpp index 3c45f07a5..ad5627e72 100644 --- a/src/blackcore/vatsimbookingreader.cpp +++ b/src/blackcore/vatsimbookingreader.cpp @@ -10,6 +10,7 @@ #include "blackmisc/sequence.h" #include "blackmisc/avatcstation.h" #include "blackmisc/nwuser.h" +#include "blackmisc/logmessage.h" #include "vatsimbookingreader.h" #include @@ -56,12 +57,17 @@ namespace BlackCore /* * Parse bookings */ - void CVatsimBookingReader::parseBookings(QNetworkReply *nwReply) + void CVatsimBookingReader::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 nwReply(nwReplyPtr); + // Worker thread, make sure to write no members here! if (this->isStopped()) { - qDebug() << "terminated" << Q_FUNC_INFO; + CLogMessage(this).debug() << Q_FUNC_INFO; + CLogMessage(this).info("terminated booking parsing process"); // for users return; // stop, terminate straight away, ending thread } @@ -69,6 +75,7 @@ namespace BlackCore { static const QString timestampFormat("yyyy-MM-dd HH:mm:ss"); QString xmlData = nwReply->readAll(); + nwReply->close(); // close asap QDomDocument doc; QDateTime updateTimestamp = QDateTime::currentDateTimeUtc(); @@ -95,7 +102,8 @@ namespace BlackCore { if (this->isStopped()) { - qDebug() << "terminated" << Q_FUNC_INFO; + CLogMessage(this).debug() << Q_FUNC_INFO; + CLogMessage(this).info("terminated booking parsing process"); // for users return; // stop, terminate straight away, ending thread } @@ -148,11 +156,10 @@ namespace BlackCore this->setUpdateTimestamp(updateTimestamp); // thread safe update emit this->dataRead(bookedStations); } // node - } // content - - nwReply->close(); - nwReply->deleteLater(); - + } else { + // with errors + nwReply->abort(); + } } // method } // namespace diff --git a/src/blackcore/vatsimbookingreader.h b/src/blackcore/vatsimbookingreader.h index 5bd39eddd..18d035f28 100644 --- a/src/blackcore/vatsimbookingreader.h +++ b/src/blackcore/vatsimbookingreader.h @@ -46,7 +46,7 @@ namespace BlackCore //! Parse received bookings //! \threadsafe - void parseBookings(QNetworkReply *nwReply); + void parseBookings(QNetworkReply *nwReplyPtr); signals: //! Bookings have been read and converted to BlackMisc::Aviation::CAtcStationList diff --git a/src/blackcore/vatsimdatafilereader.cpp b/src/blackcore/vatsimdatafilereader.cpp index 618019a45..befe3cba4 100644 --- a/src/blackcore/vatsimdatafilereader.cpp +++ b/src/blackcore/vatsimdatafilereader.cpp @@ -11,6 +11,7 @@ #include "blackmisc/avatcstation.h" #include "blackmisc/nwuser.h" #include "blackmisc/nwserver.h" +#include "blackmisc/logmessage.h" #include "vatsimdatafilereader.h" #include @@ -69,6 +70,12 @@ namespace BlackCore return this->m_voiceServers; } + CServerList CVatsimDataFileReader::getFsdServers() const + { + QReadLocker rl(&this->m_lock); + return this->m_fsdServers; + } + CUserList CVatsimDataFileReader::getPilotsForCallsigns(const CCallsignList &callsigns) { return this->getAircrafts().findByCallsigns(callsigns).transform(Predicates::MemberTransform(&CAircraft::getPilot)); @@ -153,12 +160,17 @@ namespace BlackCore * Data file read from XML * Example: http://info.vroute.net/vatsim-data.txt */ - void CVatsimDataFileReader::parseVatsimFileInBackground(QNetworkReply *nwReply) + void CVatsimDataFileReader::parseVatsimFileInBackground(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 nwReply(nwReplyPtr); + // Worker thread, make sure to write only synced here! if (this->isStopped()) { - qDebug() << "terminated" << Q_FUNC_INFO; + CLogMessage(this).debug() << Q_FUNC_INFO; + CLogMessage(this).info("terminated VATSIM file parsing process"); // for users return; // stop, terminate straight away, ending thread } @@ -166,12 +178,15 @@ namespace BlackCore if (nwReply->error() == QNetworkReply::NoError) { const QString dataFileData = nwReply->readAll(); + nwReply->close(); // close asap + if (dataFileData.isEmpty()) return; 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; CAircraftList aircrafts; QMap voiceCapabilities; @@ -183,7 +198,8 @@ namespace BlackCore { if (this->isStopped()) { - qDebug() << "terminated" << Q_FUNC_INFO; + CLogMessage(this).debug() << Q_FUNC_INFO; + CLogMessage(this).info("terminated booking parsing process"); // for users return; // stop, terminate straight away, ending thread } @@ -204,24 +220,10 @@ namespace BlackCore } else if (currentLine.startsWith("!")) { - if (currentLine.contains("GENERAL", Qt::CaseInsensitive)) - { - section = SectionGeneral; - } - else if (currentLine.contains("VOICE SERVERS", Qt::CaseInsensitive)) - { - section = SectionVoiceServer; - } - else if (currentLine.contains("CLIENTS", Qt::CaseInsensitive)) - { - section = SectionClients; - } - else - { - section = SectionNone; - } + section = currentLineToSection(currentLine); continue; } + switch (section) { case SectionClients: @@ -305,42 +307,61 @@ namespace BlackCore bool alreadyRead = (updateTimestampFromFile == this->getUpdateTimestamp()); if (alreadyRead) { return; }// still same data, terminate } - break; - case SectionVoiceServer: - { - QStringList voiceServerParts = currentLine.split(':'); - if (voiceServerParts.size() < 3) continue; - BlackMisc::Network::CServer voiceServer(voiceServerParts.at(1), voiceServerParts.at(2), voiceServerParts.at(0), -1, CUser()); - voiceServers.push_back(voiceServer); - } - break; - case SectionNone: - default: - break; } - } // for each + break; + case SectionFsdServers: + { + // ident:hostname_or_IP:location:name:clients_connection_allowed: + QStringList fsdServerParts = currentLine.split(':'); + if (fsdServerParts.size() < 4) continue; + if (!fsdServerParts.at(3).trimmed().contains('1')) continue; // allowed? + BlackMisc::Network::CServer fsdServer(fsdServerParts.at(0), fsdServerParts.at(2), 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: + QStringList voiceServerParts = currentLine.split(':'); + if (voiceServerParts.size() < 3) continue; + if (!voiceServerParts.at(3).trimmed().contains('1')) continue; // 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 // this part needs to be synchronized { QWriteLocker wl(&this->m_lock); - this->m_updateTimestamp = updateTimestampFromFile; + this->setUpdateTimestamp(updateTimestampFromFile); this->m_aircrafts = aircrafts; this->m_atcStations = atcStations; this->m_voiceServers = voiceServers; + this->m_fsdServers = fsdServers; this->m_voiceCapabilities = voiceCapabilities; } - } // read success + } // for each line - nwReply->close(); - nwReply->deleteLater(); // we are responsible for deleting this - - emit this->dataRead(); // warnings, if required if (!illegalIcaoCodes.isEmpty()) { - qWarning() << "Illegal ICAO code(s) in VATSIM data file:" << illegalIcaoCodes.join(", "); + CLogMessage(this).info("Illegal / ignored ICAO code(s) in VATSIM data file: %1") << illegalIcaoCodes.join(", "); } + + // data read finished + emit this->dataRead(); } + else + { + // network error + nwReply->abort(); + } + } const QMap CVatsimDataFileReader::clientPartsToMap(const QString ¤tLine, const QStringList &clientSectionAttributes) @@ -355,4 +376,13 @@ namespace BlackCore } return parts; } + + CVatsimDataFileReader::Section CVatsimDataFileReader::currentLineToSection(const QString ¤tLine) + { + 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; + } } // namespace diff --git a/src/blackcore/vatsimdatafilereader.h b/src/blackcore/vatsimdatafilereader.h index ad5057bf1..c1c2a8e9f 100644 --- a/src/blackcore/vatsimdatafilereader.h +++ b/src/blackcore/vatsimdatafilereader.h @@ -53,6 +53,10 @@ namespace BlackCore //! \threadsafe BlackMisc::Network::CServerList getVoiceServers() const; + //! Get all FSD servers + //! \threadsafe + BlackMisc::Network::CServerList getFsdServers() const; + //! Users for callsign(s) //! \threadsafe BlackMisc::Network::CUserList getUsersForCallsigns(const BlackMisc::Aviation::CCallsignList &callsigns); @@ -98,6 +102,7 @@ namespace BlackCore int m_currentUrlIndex; QNetworkAccessManager *m_networkManager; BlackMisc::Network::CServerList m_voiceServers; + BlackMisc::Network::CServerList m_fsdServers; BlackMisc::Aviation::CAtcStationList m_atcStations; BlackMisc::Aviation::CAircraftList m_aircrafts; QMap m_voiceCapabilities; @@ -109,13 +114,17 @@ namespace BlackCore enum Section { SectionNone, - SectionVoiceServer, + SectionFsdServers, + SectionVoiceServers, SectionClients, SectionGeneral }; + //! Get current section + static Section currentLineToSection(const QString ¤tLine); + //! Parse the VATSIM data file in backgroun - void parseVatsimFileInBackground(QNetworkReply *nwReply); + void parseVatsimFileInBackground(QNetworkReply *nwReplyPtr); signals: //! Data have been read diff --git a/src/blackmisc/threadedreader.h b/src/blackmisc/threadedreader.h index 13e200304..767cdc55d 100644 --- a/src/blackmisc/threadedreader.h +++ b/src/blackmisc/threadedreader.h @@ -34,8 +34,8 @@ namespace BlackMisc //! Destructor virtual ~CThreadedReader() { - delete m_updateTimer; this->stop(); + delete m_updateTimer; } //! Thread safe, set update timestamp @@ -59,10 +59,13 @@ namespace BlackMisc virtual void stop() { if (this->isStopped()) return; + + // thread safe stopping timer and mark as stopped this->setStopFlag(); this->setInterval(0); // shutdown pending + QWriteLocker(&this->m_lock); if (this->m_pendingFuture.isRunning()) { // cancel does not work with all futures, especially not with QConcurrent::run @@ -78,7 +81,6 @@ namespace BlackMisc // cancel or stop flag above should terminate QFuture this->m_pendingFuture.waitForFinished(); - } //! Thread safe, is in state stopped? @@ -114,8 +116,7 @@ namespace BlackMisc protected: //! Constructor - CThreadedReader() : - m_updateTimer(nullptr), m_stopped(false), m_pendingNetworkReply(nullptr), m_lock(QReadWriteLock::Recursive) + CThreadedReader() : m_lock(QReadWriteLock::Recursive) { this->m_updateTimer = new QTimer(); } @@ -144,12 +145,14 @@ namespace BlackMisc this->m_stopped = true; } - QDateTime m_updateTimestamp; //!< when was file / resource read - QTimer *m_updateTimer; //!< update times - bool m_stopped; //!< mark as stopped, threads should terminate - QFuture m_pendingFuture; //!< optional future to be stopped - QNetworkReply *m_pendingNetworkReply; //!< optional future to be stopped - mutable QReadWriteLock m_lock; //!< lock + QTimer *m_updateTimer = nullptr; //!< update timer + mutable QReadWriteLock m_lock; //!< lock + + private: + QDateTime m_updateTimestamp; //!< when was file / resource read + bool m_stopped = false; //!< mark as stopped, threads should terminate + QFuture m_pendingFuture; //!< optional future to be stopped + QNetworkReply *m_pendingNetworkReply = nullptr; //!< optional network reply to be stopped }; } // namespace