swift
vatsimstatusfilereader.cpp
1 // SPDX-FileCopyrightText: Copyright (C) 2013 swift Project Community / Contributors
2 // SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-swift-pilot-client-1
3 
5 
6 #include <QByteArray>
7 #include <QDateTime>
8 #include <QList>
9 #include <QMetaObject>
10 #include <QNetworkReply>
11 #include <QScopedPointer>
12 #include <QScopedPointerDeleteLater>
13 #include <QString>
14 #include <QStringList>
15 #include <QTimer>
16 #include <QUrl>
17 #include <QtGlobal>
18 
19 #include "core/application.h"
20 #include "core/data/globalsetup.h"
21 #include "misc/logmessage.h"
22 #include "misc/network/url.h"
23 #include "misc/statusmessage.h"
24 
25 using namespace swift::misc;
26 using namespace swift::misc::aviation;
27 using namespace swift::misc::network;
28 using namespace swift::misc::simulation;
29 using namespace swift::misc::physical_quantities;
30 using namespace swift::core::data;
31 
32 namespace swift::core::vatsim
33 {
34  CVatsimStatusFileReader::CVatsimStatusFileReader(QObject *owner) : CThreadedReader(owner, "CVatsimStatusFileReader")
35  {
36  // do not connect with time, will be read once at startup
37  }
38 
40  {
41  Q_ASSERT_X(hasStarted(), Q_FUNC_INFO, "Thread was not started yet!");
42  QPointer<CVatsimStatusFileReader> myself(this);
43  QTimer::singleShot(0, this, [=] {
44  if (!myself) { return; }
45  myself->read();
46  });
47  }
48 
49  CUrl CVatsimStatusFileReader::getMetarFileUrl() const { return m_lastGoodSetup.get().getMetarFileUrl(); }
50 
51  CUrl CVatsimStatusFileReader::getDataFileUrl() const { return m_lastGoodSetup.get().getDataFileUrl(); }
52 
53  void CVatsimStatusFileReader::read()
54  {
55  this->threadAssertCheck();
56  if (!this->doWorkCheck()) { return; }
57 
58  Q_ASSERT_X(sApp, Q_FUNC_INFO, "Missing application");
60  if (url.isEmpty()) { return; }
61  CLogMessage(this).info(u"Trigger read of VATSIM status file from '%1'") << url.toQString(true);
62  this->getFromNetworkAndLog(url, { this, &CVatsimStatusFileReader::parseVatsimFile });
63  }
64 
65  void CVatsimStatusFileReader::parseVatsimFile(QNetworkReply *nwReplyPtr)
66  {
67  // wrap pointer, make sure any exit cleans up reply
68  // required to use delete later as object is created in a different thread
69  QScopedPointer<QNetworkReply, QScopedPointerDeleteLater> nwReply(nwReplyPtr);
70 
71  // Worker thread, make sure to write only synced here!
72  this->threadAssertCheck();
73  if (!this->doWorkCheck())
74  {
75  CLogMessage(this).debug() << Q_FUNC_INFO;
76  CLogMessage(this).info(u"Terminated VATSIM status file parsing process"); // for users
77  return; // stop, terminate straight away, ending thread
78  }
79 
80  this->logNetworkReplyReceived(nwReplyPtr);
81  const QString urlString = nwReply->url().toString();
82 
83  if (nwReply->error() == QNetworkReply::NoError)
84  {
85  const QString statusFileData = nwReply->readAll();
86  nwReply->close(); // close asap
87 
88  if (statusFileData.isEmpty()) return;
89  auto jsonDoc = QJsonDocument::fromJson(statusFileData.toUtf8());
90  if (jsonDoc.isEmpty()) { return; }
91 
92  CUrl dataFileUrl;
93  CUrl serverFileUrl;
94  CUrl metarFileUrl;
95 
96  // Always taking the first URL from the file
97  // (at the time of writing, also only one URL per service is available anyway)
98  if (const QJsonArray dataUrls = jsonDoc["data"]["v3"].toArray(); !dataUrls.empty())
99  {
100  dataFileUrl = QUrl(dataUrls.at(0).toString());
101  }
102 
103  if (const QJsonArray serverFileUrls = jsonDoc["data"]["servers"].toArray(); !serverFileUrls.empty())
104  {
105  serverFileUrl = QUrl(serverFileUrls.at(0).toString());
106  }
107 
108  if (const QJsonArray metarUrls = jsonDoc["metar"].toArray(); !metarUrls.empty())
109  {
110  metarFileUrl = QUrl(metarUrls.at(0).toString());
111  }
112 
113  // cache itself is thread safe, avoid writing with unchanged data
114  CVatsimSetup vs(m_lastGoodSetup.get());
115  const bool changed = vs.setUrls(dataFileUrl, serverFileUrl, metarFileUrl);
116  vs.setUtcTimestamp(QDateTime::currentDateTime());
117  const CStatusMessage cacheMsg = m_lastGoodSetup.set(vs);
118  if (cacheMsg.isFailure()) { CLogMessage::preformatted(cacheMsg); }
119  else { CLogMessage(this).info(u"Read VATSIM status file from '%1'") << urlString; }
120  Q_UNUSED(changed);
121 
122  // data read finished
123  emit this->statusFileRead(statusFileData.count());
124  emit this->dataRead(CEntityFlags::VatsimStatusFile, CEntityFlags::ReadFinished, statusFileData.count());
125  }
126  else
127  {
128  // network error
129  CLogMessage(this).warning(u"Reading VATSIM status file failed '%1' '%2'")
130  << nwReply->errorString() << urlString;
131  nwReply->abort();
132  emit this->dataRead(CEntityFlags::VatsimStatusFile, CEntityFlags::ReadFailed, 0);
133  }
134  }
135 } // namespace swift::core::vatsim
SWIFT_CORE_EXPORT swift::core::CApplication * sApp
Single instance of application object.
Definition: application.cpp:71
data::CGlobalSetup getGlobalSetup() const
Global setup.
Support for threaded based reading and parsing tasks such as data files via http, or file system and ...
void threadAssertCheck() const
Make sure everything runs correctly in own thread.
void logNetworkReplyReceived(QNetworkReply *reply)
Network reply received, mark in m_urlReadLog.
QNetworkReply * getFromNetworkAndLog(const swift::misc::network::CUrl &url, const swift::misc::CSlot< void(QNetworkReply *)> &callback)
Get request from network, and log with m_urlReadLog.
bool doWorkCheck() const
Still enabled etc.?
const swift::misc::network::CUrl & getVatsimStatusFileUrl() const
VATSIM status file URL.
Definition: globalsetup.h:102
VATSIM data (servers, URLs) cached as last known good setup.
Definition: vatsimsetup.h:31
swift::misc::network::CUrl getDataFileUrl() const
Data file URL.
swift::misc::network::CUrl getMetarFileUrl() const
METAR URL.
void readInBackgroundThread()
Start reading in own thread.
void dataRead(swift::misc::network::CEntityFlags::Entity entity, swift::misc::network::CEntityFlags::ReadState state, int bytes)
Data have been read.
void statusFileRead(int bytes)
Data have been read.
T get() const
Get a copy of the current value.
Definition: valuecache.h:408
CStatusMessage set(const typename Trait::type &value, qint64 timestamp=0)
Write a new value. Must be called from the thread in which the owner lives.
Definition: datacache.h:350
Class for emitting a log message.
Definition: logmessage.h:27
static void preformatted(const CStatusMessage &statusMessage)
Sends a verbatim, preformatted message to the log.
Derived & warning(const char16_t(&format)[N])
Set the severity to warning, providing a format string.
Derived & debug()
Set the severity to debug.
Derived & info(const char16_t(&format)[N])
Set the severity to info, providing a format string.
Streamable status message, e.g.
bool isFailure() const
Operation considered unsuccessful.
bool hasStarted() const
True if the worker has started.
Definition: worker.h:182
Value object encapsulating information of a location, kind of simplified CValueObject compliant versi...
Definition: url.h:27
Core data traits (aka cached values) and classes.
Free functions in swift::misc.
auto singleShot(int msec, QObject *target, F &&task)
Starts a single-shot timer which will call a task in the thread of the given object when it times out...
Definition: threadutils.h:30