swift
vatsimserverfilereader.cpp
1 // SPDX-FileCopyrightText: Copyright (C) 2023 swift Project Community / Contributors
2 // SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-swift-pilot-client-1
3 
5 
6 #include <QMetaObject>
7 #include <QNetworkReply>
8 #include <QPointer>
9 #include <QScopedPointerDeleteLater>
10 #include <QStringBuilder>
11 #include <QTimer>
12 #include <QUrl>
13 #include <QtGlobal>
14 
15 #include "core/application.h"
16 #include "misc/logmessage.h"
18 #include "misc/network/server.h"
19 #include "misc/network/user.h"
20 #include "misc/pq/units.h"
21 
22 using namespace swift::misc;
23 using namespace swift::misc::aviation;
24 using namespace swift::misc::network;
25 using namespace swift::misc::geo;
26 using namespace swift::misc::simulation;
27 using namespace swift::misc::physical_quantities;
28 using namespace swift::core::data;
29 
30 namespace swift::core::vatsim
31 {
32  CVatsimServerFileReader::CVatsimServerFileReader(QObject *owner) : CThreadedReader(owner, "CVatsimServerFileReader")
33  {}
34 
35  CServerList CVatsimServerFileReader::getFsdServers() const { return m_lastGoodSetup.get().getFsdServers(); }
36 
38  {
39  Q_ASSERT_X(hasStarted(), Q_FUNC_INFO, "Thread was not started yet!");
40  QPointer<CVatsimServerFileReader> myself(this);
41  QTimer::singleShot(0, this, [=] {
42  if (!myself) { return; }
43  myself->read();
44  });
45  }
46 
47  void CVatsimServerFileReader::read()
48  {
49  this->threadAssertCheck();
50  if (!this->doWorkCheck()) { return; }
51 
52  Q_ASSERT_X(sApp, Q_FUNC_INFO, "Missing application");
53  const QUrl url = sApp->getVatsimServerFileUrl();
54  if (url.isEmpty()) { return; }
55  this->getFromNetworkAndLog(url, { this, &CVatsimServerFileReader::parseVatsimFile });
56  }
57 
58  void CVatsimServerFileReader::parseVatsimFile(QNetworkReply *nwReplyPtr)
59  {
60  // wrap pointer, make sure any exit cleans up reply
61  // required to use delete later as object is created in a different thread
62  QScopedPointer<QNetworkReply, QScopedPointerDeleteLater> nwReply(nwReplyPtr);
63  this->threadAssertCheck();
64 
65  // Worker thread, make sure to write only synced here!
66  if (!this->doWorkCheck())
67  {
68  CLogMessage(this).info(u"Terminated VATSIM file parsing process");
69  return; // stop, terminate straight away, ending thread
70  }
71 
72  this->logNetworkReplyReceived(nwReplyPtr);
73 
74  const QUrl url = nwReply->url();
75  const QString urlString = url.toString();
76 
77  if (nwReply->error() == QNetworkReply::NoError)
78  {
79  const QString dataFileData = nwReply->readAll();
80  nwReply->close(); // close asap
81 
82  if (dataFileData.isEmpty()) { return; }
83  if (!this->didContentChange(dataFileData)) // Quick check by hash
84  {
85  CLogMessage(this).info(u"VATSIM file '%1' has same content, skipped") << urlString;
86  return;
87  }
88  auto jsonDoc = QJsonDocument::fromJson(dataFileData.toUtf8());
89  if (jsonDoc.isEmpty()) { return; }
90 
91  // build on local vars for thread safety
92  CServerList fsdServers;
93 
94  for (QJsonValueRef server : jsonDoc.array())
95  {
96  if (!this->doWorkCheck())
97  {
98  CLogMessage(this).info(u"Terminated VATSIM file parsing process");
99  return;
100  }
101  fsdServers.push_back(parseServer(server.toObject()));
102  if (!fsdServers.back().hasName()) { fsdServers.pop_back(); }
103  }
104 
105  // Setup for VATSIM servers and sorting for comparison
106  fsdServers.sortBy(&CServer::getName, &CServer::getDescription);
107 
108  // update cache itself is thread safe
109  CVatsimSetup vs(m_lastGoodSetup.get());
110  const bool changedSetup = vs.setServers(fsdServers, {});
111  if (changedSetup)
112  {
113  vs.setCurrentUtcTime();
114  m_lastGoodSetup.set(vs);
115  }
116 
117  // data read finished
118  emit this->dataFileRead(dataFileData.size());
119  emit this->dataRead(CEntityFlags::VatsimDataFile, CEntityFlags::ReadFinished, dataFileData.size() / 1000,
120  url);
121  }
122  else
123  {
124  // network error
125  CLogMessage(this).warning(u"Reading VATSIM data file failed '%1' '%2'")
126  << nwReply->errorString() << urlString;
127  nwReply->abort();
128  emit this->dataRead(CEntityFlags::VatsimDataFile, CEntityFlags::ReadFailed, 0, url);
129  }
130  }
131 
132  CServer CVatsimServerFileReader::parseServer(const QJsonObject &server) const
133  {
134  return CServer(server["name"].toString(), server["location"].toString(), server["hostname_or_ip"].toString(),
135  6809, CUser("id", "real name", "email", "password"), CFsdSetup::vatsimStandard(),
136  CEcosystem::VATSIM, CServer::FSDServerVatsim, server["clients_connection_allowed"].toInt());
137  }
138 
139 } // namespace swift::core::vatsim
SWIFT_CORE_EXPORT swift::core::CApplication * sApp
Single instance of application object.
Definition: application.cpp:71
swift::misc::network::CUrl getVatsimServerFileUrl() const
Get URL to file which contains the list of VATSIM servers.
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.
bool didContentChange(const QString &content, int startPosition=-1)
Stores new content hash and returns if content changed (based on hash value.
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.?
VATSIM data (servers, URLs) cached as last known good setup.
Definition: vatsimsetup.h:31
void dataRead(swift::misc::network::CEntityFlags::Entity entity, swift::misc::network::CEntityFlags::ReadState state, int number, const QUrl &url)
Data have been read.
void readInBackgroundThread()
Start reading in own thread.
void dataFileRead(int bytes)
Data have been read.
swift::misc::network::CServerList getFsdServers() const
Get all VATSIM FSD servers.
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
Derived & warning(const char16_t(&format)[N])
Set the severity to warning, providing a format string.
Derived & info(const char16_t(&format)[N])
Set the severity to info, providing a format string.
void sortBy(K1 key1, Keys... keys)
In-place sort by some particular key(s).
Definition: sequence.h:576
void pop_back()
Removes an element at the end of the sequence.
Definition: sequence.h:367
void push_back(const T &value)
Appends an element at the end of the sequence.
Definition: sequence.h:305
reference back()
Access the last element.
Definition: sequence.h:249
bool hasStarted() const
True if the worker has started.
Definition: worker.h:182
Value object encapsulating information of a server.
Definition: server.h:28
bool hasName() const
Has name?
Definition: server.h:95
Value object encapsulating a list of servers.
Definition: serverlist.h:23
Value object encapsulating information of a user.
Definition: user.h:28
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