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  QPointer<CVatsimServerFileReader> myself(this);
40  QTimer::singleShot(0, this, [=] {
41  if (!myself) { return; }
42  myself->read();
43  });
44  }
45 
46  void CVatsimServerFileReader::doWorkImpl() { this->read(); }
47 
48  void CVatsimServerFileReader::read()
49  {
50  this->threadAssertCheck();
51  if (!this->doWorkCheck()) { return; }
52 
53  Q_ASSERT_X(sApp, Q_FUNC_INFO, "Missing application");
54  const QUrl url = sApp->getVatsimServerFileUrl();
55  if (url.isEmpty()) { return; }
56  this->getFromNetworkAndLog(url, { this, &CVatsimServerFileReader::parseVatsimFile });
57  }
58 
59  void CVatsimServerFileReader::parseVatsimFile(QNetworkReply *nwReplyPtr)
60  {
61  // wrap pointer, make sure any exit cleans up reply
62  // required to use delete later as object is created in a different thread
63  QScopedPointer<QNetworkReply, QScopedPointerDeleteLater> nwReply(nwReplyPtr);
64  this->threadAssertCheck();
65 
66  // Worker thread, make sure to write only synced here!
67  if (!this->doWorkCheck())
68  {
69  CLogMessage(this).info(u"Terminated VATSIM file parsing process");
70  return; // stop, terminate straight away, ending thread
71  }
72 
73  this->logNetworkReplyReceived(nwReplyPtr);
74 
75  const QUrl url = nwReply->url();
76  const QString urlString = url.toString();
77 
78  if (nwReply->error() == QNetworkReply::NoError)
79  {
80  const QString dataFileData = nwReply->readAll();
81  nwReply->close(); // close asap
82 
83  if (dataFileData.isEmpty()) { return; }
84  if (!this->didContentChange(dataFileData)) // Quick check by hash
85  {
86  CLogMessage(this).info(u"VATSIM file '%1' has same content, skipped") << urlString;
87  return;
88  }
89  auto jsonDoc = QJsonDocument::fromJson(dataFileData.toUtf8());
90  if (jsonDoc.isEmpty()) { return; }
91 
92  // build on local vars for thread safety
93  CServerList fsdServers;
94 
95  for (QJsonValueRef server : jsonDoc.array())
96  {
97  if (!this->doWorkCheck())
98  {
99  CLogMessage(this).info(u"Terminated VATSIM file parsing process");
100  return;
101  }
102  fsdServers.push_back(parseServer(server.toObject()));
103  if (!fsdServers.back().hasName()) { fsdServers.pop_back(); }
104  }
105 
106  // Setup for VATSIM servers and sorting for comparison
107  fsdServers.sortBy(&CServer::getName, &CServer::getDescription);
108 
109  // update cache itself is thread safe
110  CVatsimSetup vs(m_lastGoodSetup.get());
111  const bool changedSetup = vs.setServers(fsdServers, {});
112  if (changedSetup)
113  {
114  vs.setCurrentUtcTime();
115  m_lastGoodSetup.set(vs);
116  }
117 
118  // data read finished
119  emit this->dataFileRead(dataFileData.size());
120  emit this->dataRead(CEntityFlags::VatsimDataFile, CEntityFlags::ReadFinished, dataFileData.size() / 1000,
121  url);
122  }
123  else
124  {
125  // network error
126  CLogMessage(this).warning(u"Reading VATSIM data file failed '%1' '%2'")
127  << nwReply->errorString() << urlString;
128  nwReply->abort();
129  emit this->dataRead(CEntityFlags::VatsimDataFile, CEntityFlags::ReadFailed, 0, url);
130  }
131  }
132 
133  CServer CVatsimServerFileReader::parseServer(const QJsonObject &server) const
134  {
135  return CServer(server["name"].toString(), server["location"].toString(), server["hostname_or_ip"].toString(),
136  6809, CUser("id", "real name", "email", "password"), CFsdSetup::vatsimStandard(),
137  CEcosystem::VATSIM, CServer::FSDServerVatsim, server["clients_connection_allowed"].toInt());
138  }
139 
140 } // 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 everthing 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.
virtual void doWorkImpl()
This method does the actual work in the derived class.
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
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