swift
airportdatareader.cpp
1 // SPDX-FileCopyrightText: Copyright (C) 2016 swift Project Community / Contributors
2 // SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-swift-pilot-client-1
3 
5 
6 #include <QElapsedTimer>
7 #include <QFileInfo>
8 #include <QNetworkReply>
9 #include <QPointer>
10 #include <QStringBuilder>
11 
12 #include "core/application.h"
13 #include "core/db/databaseutils.h"
14 #include "misc/logmessage.h"
16 
17 using namespace swift::misc;
18 using namespace swift::misc::aviation;
19 using namespace swift::misc::network;
20 using namespace swift::misc::db;
21 
22 namespace swift::core::db
23 {
24  CAirportDataReader::CAirportDataReader(QObject *parent, const CDatabaseReaderConfigList &config)
25  : CDatabaseReader(parent, config, QStringLiteral("CAirportDataReader"))
26  {
27  // void
28  }
29 
31 
33  {
34  return this->getAirports().findFirstByIcao(CAirportIcaoCode(designator));
35  }
36 
37  CAirport CAirportDataReader::getAirportForNameOrLocation(const QString &nameOrLocation) const
38  {
39  return this->getAirports().findFirstByNameOrLocation(nameOrLocation);
40  }
41 
42  int CAirportDataReader::getAirportsCount() const { return this->getAirports().size(); }
43 
44  bool CAirportDataReader::readFromJsonFilesInBackground(const QString &dir, CEntityFlags::Entity whatToRead,
45  bool overrideNewerOnly)
46  {
47  if (dir.isEmpty() || whatToRead == CEntityFlags::NoEntity) { return false; }
48 
49  QPointer<CAirportDataReader> myself(this);
50  QTimer::singleShot(0, this, [=]() {
51  if (!myself) { return; }
52  const CStatusMessageList msgs = this->readFromJsonFiles(dir, whatToRead, overrideNewerOnly);
53  if (msgs.isFailure()) { CLogMessage::preformatted(msgs); }
54  });
55  return true;
56  }
57 
58  CStatusMessageList CAirportDataReader::readFromJsonFiles(const QString &dir, CEntityFlags::Entity whatToRead,
59  bool overrideNewerOnly)
60  {
61  const QDir directory(dir);
62  if (!directory.exists()) { return CStatusMessage(this).error(u"Missing directory '%1'") << dir; }
63 
64  whatToRead &= CEntityFlags::AirportEntity; // can handle these entities
65  if (whatToRead == CEntityFlags::NoEntity)
66  {
67  return CStatusMessage(this).info(u"'%1' No entity for this reader")
68  << CEntityFlags::entitiesToString(whatToRead);
69  }
70 
71  int c = 0;
72  CEntityFlags::Entity reallyRead = CEntityFlags::NoEntity;
73 
74  CStatusMessageList msgs;
75  const QString fileName = CFileUtils::appendFilePaths(dir, "airports.json");
76  const QFileInfo fi(fileName);
77  if (!fi.exists()) { msgs.push_back(CStatusMessage(this).warning(u"File '%1' does not exist") << fileName); }
78  else if (!this->overrideCacheFromFile(overrideNewerOnly, fi, CEntityFlags::AirportEntity, msgs))
79  {
80  // void
81  }
82  else
83  {
84  const QUrl url = QUrl::fromLocalFile(fi.absoluteFilePath());
85  const QJsonObject airportsJson(CDatabaseUtils::readQJsonObjectFromDatabaseFile(fileName));
86  if (!airportsJson.isEmpty())
87  {
88  try
89  {
90  const CAirportList airports = CAirportList::fromMultipleJsonFormats(airportsJson);
91  c = airports.size();
92  msgs.push_back(m_airportCache.set(airports, fi.birthTime().toUTC().toMSecsSinceEpoch()));
93 
94  emit dataRead(CEntityFlags::AirportEntity, CEntityFlags::ReadFinished, c, url);
95  reallyRead |= CEntityFlags::AirportEntity;
96  }
97  catch (const CJsonException &ex)
98  {
99  emit dataRead(CEntityFlags::AirportEntity, CEntityFlags::ReadFailed, 0, url);
101  ex, this, QStringLiteral("Reading airports from '%1'").arg(fileName));
102  }
103  }
104  }
105  return msgs;
106  }
107 
108  CEntityFlags::Entity CAirportDataReader::getSupportedEntities() const { return CEntityFlags::AirportEntity; }
109 
110  QDateTime CAirportDataReader::getCacheTimestamp(CEntityFlags::Entity entities) const
111  {
112  return entities == CEntityFlags::AirportEntity ? m_airportCache.getAvailableTimestamp() : QDateTime();
113  }
114 
115  int CAirportDataReader::getCacheCount(CEntityFlags::Entity entity) const
116  {
117  return entity == CEntityFlags::AirportEntity ? m_airportCache.get().size() : 0;
118  }
119 
121  {
122  CEntityFlags::Entity entities = CEntityFlags::NoEntity;
123  if (this->getCacheCount(CEntityFlags::AirportEntity) > 0) { entities |= CEntityFlags::AirportEntity; }
124  return entities;
125  }
126 
127  CEntityFlags::Entity CAirportDataReader::getEntitiesWithCacheTimestampNewerThan(const QDateTime &threshold) const
128  {
129  CEntityFlags::Entity entities = CEntityFlags::NoEntity;
130  if (this->hasCacheTimestampNewerThan(CEntityFlags::AirportEntity, threshold))
131  {
132  entities |= CEntityFlags::AirportEntity;
133  }
134  return entities;
135  }
136 
137  void CAirportDataReader::synchronizeCaches(CEntityFlags::Entity entities)
138  {
139  if (entities.testFlag(CEntityFlags::AirportEntity))
140  {
141  if (m_syncedAirportCache) { return; }
142  m_syncedAirportCache = true;
143  m_airportCache.synchronize();
144  }
145  }
146 
147  void CAirportDataReader::admitCaches(CEntityFlags::Entity entities)
148  {
149  if (entities.testFlag(CEntityFlags::AirportEntity)) { m_airportCache.admit(); }
150  }
151 
152  void CAirportDataReader::invalidateCaches(CEntityFlags::Entity entities)
153  {
154  if (entities.testFlag(CEntityFlags::AirportEntity))
155  {
156  CDataCache::instance()->clearAllValues(m_airportCache.getKey());
157  }
158  }
159 
160  bool CAirportDataReader::hasChangedUrl(CEntityFlags::Entity entity, CUrl &oldUrlInfo, CUrl &newUrlInfo) const
161  {
162  Q_UNUSED(entity)
163  oldUrlInfo = m_readerUrlCache.get();
164  newUrlInfo = this->getBaseUrl(CDbFlags::DbReading);
165  return CDatabaseReader::isChangedUrl(oldUrlInfo, newUrlInfo);
166  }
167 
169 
170  CUrl CAirportDataReader::getAirportsUrl(CDbFlags::DataRetrievalModeFlag mode) const
171  {
172  return this->getBaseUrl(mode).withAppendedPath(fileNameForMode(CEntityFlags::AirportEntity, mode));
173  }
174 
175  void CAirportDataReader::parseAirportData(QNetworkReply *nwReplyPtr)
176  {
177  // wrap pointer, make sure any exit cleans up reply
178  // required to use delete later as object is created in a different thread
179  QScopedPointer<QNetworkReply, QScopedPointerDeleteLater> nwReply(nwReplyPtr);
180  if (!this->doWorkCheck()) { return; }
181 
182  const CDatabaseReader::JsonDatastoreResponse res =
184  const QUrl url = nwReply->url();
185 
186  if (res.hasErrorMessage())
187  {
188  CLogMessage::preformatted(res.lastWarningOrAbove());
189  emit this->dataRead(CEntityFlags::AirportEntity, CEntityFlags::ReadFailed, 0, url);
190  return;
191  }
192 
193  // parsing
194  emit this->dataRead(CEntityFlags::AirportEntity, CEntityFlags::ReadParsing, 0, url);
195  CAirportList airports;
196  CAirportList inconsistent;
197  if (res.isRestricted())
198  {
199  const CAirportList incrementalAirports(CAirportList::fromDatabaseJson(res, &inconsistent));
200  if (incrementalAirports.isEmpty()) { return; } // currently ignored
201  airports = this->getAirports();
202  airports.replaceOrAddObjectsByKey(incrementalAirports);
203  }
204  else
205  {
206  QElapsedTimer time;
207  time.start();
208  airports = CAirportList::fromDatabaseJson(res, &inconsistent);
209  this->logParseMessage("airports", airports.size(), static_cast<int>(time.elapsed()), res);
210  }
211 
212  if (!inconsistent.isEmpty())
213  {
215  u"Inconsistent airports: " % inconsistent.dbKeysAsString(", ")),
216  Q_FUNC_INFO);
217  }
218 
219  if (!this->doWorkCheck()) { return; }
220  const int size = airports.size();
221  qint64 latestTimestamp = airports.latestTimestampMsecsSinceEpoch();
222  if (size > 0 && latestTimestamp < 0)
223  {
224  CLogMessage(this).error(u"No timestamp in airport list, setting to last modified value");
225  latestTimestamp = lastModifiedMsSinceEpoch(nwReply.data());
226  }
227 
228  m_airportCache.set(airports, latestTimestamp);
229  this->updateReaderUrl(getBaseUrl(CDbFlags::DbReading));
230 
231  this->emitAndLogDataRead(CEntityFlags::AirportEntity, size, res);
232  }
233 
234  void CAirportDataReader::read(CEntityFlags::Entity entity, CDbFlags::DataRetrievalModeFlag mode,
235  const QDateTime &newerThan)
236  {
237  this->threadAssertCheck();
238  if (!this->doWorkCheck()) { return; }
239  entity &= CEntityFlags::AirportEntity;
240 
241  if (entity.testFlag(CEntityFlags::AirportEntity))
242  {
243  CUrl url = this->getAirportsUrl(mode);
244  if (!url.isEmpty())
245  {
246  url.appendQuery(queryLatestTimestamp(newerThan));
247  this->getFromNetworkAndLog(url, { this, &CAirportDataReader::parseAirportData });
248  emit dataRead(CEntityFlags::AirportEntity, CEntityFlags::ReadStarted, 0, url);
249  }
250  else { this->logNoWorkingUrl(CEntityFlags::AirportEntity); }
251  }
252  }
253 
254  void CAirportDataReader::airportCacheChanged() { this->cacheHasChanged(CEntityFlags::AirportEntity); }
255 
256  void CAirportDataReader::baseUrlCacheChanged()
257  {
258  // void
259  }
260 
261  void CAirportDataReader::updateReaderUrl(const CUrl &url)
262  {
263  const CUrl current = m_readerUrlCache.get();
264  if (current == url) { return; }
265  const CStatusMessage m = m_readerUrlCache.set(url);
266  if (m.isFailure()) { CLogMessage::preformatted(m); }
267  }
268 } // namespace swift::core::db
SWIFT_CORE_EXPORT swift::core::CApplication * sApp
Single instance of application object.
Definition: application.cpp:71
data::CGlobalSetup getGlobalSetup() const
Global setup.
static void logInconsistentData(const swift::misc::CStatusMessage &msg, const char *funcInfo=nullptr)
Use this to log inconsistent data.
void threadAssertCheck() const
Make sure everthing runs correctly in own thread.
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.?
qint64 lastModifiedMsSinceEpoch(QNetworkReply *nwReply) const
When was reply last modified, -1 if N/A.
swift::misc::network::CUrl getDbAirportReaderUrl() const
Airport reader URL.
Definition: globalsetup.cpp:37
virtual bool readFromJsonFilesInBackground(const QString &dir, swift::misc::network::CEntityFlags::Entity whatToRead, bool overrideNewerOnly)
Data read from local data.
virtual swift::misc::network::CEntityFlags::Entity getSupportedEntities() const
Supported entities by this reader.
swift::misc::aviation::CAirport getAirportForIcaoDesignator(const QString &designator) const
Returns airport for designator (or default)
virtual swift::misc::network::CUrl getDbServiceBaseUrl() const
Get the service URL, individual for each reader.
virtual swift::misc::network::CEntityFlags::Entity getEntitiesWithCacheTimestampNewerThan(const QDateTime &threshold) const
Entities already having data in cache (based on timestamp assumption)
virtual void invalidateCaches(swift::misc::network::CEntityFlags::Entity entities)
Invalidate the caches for given entities.
int getAirportsCount() const
Returns a list of all airports in the database.
swift::misc::aviation::CAirport getAirportForNameOrLocation(const QString &location) const
Get airports for location.
virtual void synchronizeCaches(swift::misc::network::CEntityFlags::Entity entities)
Admit caches for given entities.
virtual bool hasChangedUrl(swift::misc::network::CEntityFlags::Entity entity, swift::misc::network::CUrl &oldUrlInfo, swift::misc::network::CUrl &newUrlInfo) const
Changed URL, means the cache values have been read from elsewhere.
swift::misc::aviation::CAirportList getAirports() const
Returns a list of all airports in the database.
virtual int getCacheCount(swift::misc::network::CEntityFlags::Entity entity) const
Cache`s number of entities.
virtual swift::misc::CStatusMessageList readFromJsonFiles(const QString &dir, swift::misc::network::CEntityFlags::Entity whatToRead, bool overrideNewerOnly)
Data read from local data.
virtual void admitCaches(swift::misc::network::CEntityFlags::Entity entities)
Admit caches for given entities.
virtual QDateTime getCacheTimestamp(swift::misc::network::CEntityFlags::Entity entities) const
Get cache timestamp.
virtual swift::misc::network::CEntityFlags::Entity getEntitiesWithCacheCount() const
Entities already having data in cache.
Value object encapsulating a list of reader configs.
Specialized version of threaded reader for DB data.
void emitAndLogDataRead(swift::misc::network::CEntityFlags::Entity entity, int number, const JsonDatastoreResponse &res)
Emit signal and log when data have been read.
static QString queryLatestTimestamp(const QDateTime &ts)
Latest timestamp query for DB.
virtual void cacheHasChanged(swift::misc::network::CEntityFlags::Entity entities)
Cache for given entity has changed.
void logNoWorkingUrl(swift::misc::network::CEntityFlags::Entity entity)
Log if no working URL exists, using m_noWorkingUrlSeverity.
bool overrideCacheFromFile(bool overrideNewerOnly, const QFileInfo &fileInfo, swift::misc::network::CEntityFlags::Entity entity, swift::misc::CStatusMessageList &msgs) const
Override cache from file.
swift::misc::network::CUrl getBaseUrl(swift::misc::db::CDbFlags::DataRetrievalModeFlag mode) const
Base URL for mode (either a shared or DB URL)
void dataRead(swift::misc::network::CEntityFlags::Entity entities, swift::misc::network::CEntityFlags::ReadState state, int number, const QUrl &url)
Combined read signal.
static bool isChangedUrl(const swift::misc::network::CUrl &oldUrl, const swift::misc::network::CUrl &currentUrl)
Has URL been changed? Means we load from a different server.
static QString fileNameForMode(swift::misc::network::CEntityFlags::Entity entity, swift::misc::db::CDbFlags::DataRetrievalModeFlag mode)
File name for given mode, either php service or shared file name.
CDatabaseReader::JsonDatastoreResponse setStatusAndTransformReplyIntoDatastoreResponse(QNetworkReply *nwReply)
Check if terminated or error, otherwise split into array of objects.
void logParseMessage(const QString &entity, int size, int msElapsed, const JsonDatastoreResponse &response) const
Parsing info message.
bool hasCacheTimestampNewerThan(swift::misc::network::CEntityFlags::Entity entity, const QDateTime &threshold) const
Has entity a valid and newer timestamp.
static QJsonObject readQJsonObjectFromDatabaseFile(const QString &filename)
QJsonObject from database JSON file (normally shared file)
const QString & getKey() const
Get the key string of this value.
Definition: valuecache.h:445
T get() const
Get a copy of the current value.
Definition: valuecache.h:408
static CDataCache * instance()
Return the singleton instance.
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
void admit()
If the value is load-deferred, trigger the deferred load (async).
Definition: datacache.h:393
QDateTime getAvailableTimestamp() const
Get the timestamp of the value, or of the deferred value that is available to be loaded.
Definition: datacache.h:383
void synchronize()
If the value is currently being loaded, wait for it to finish loading, and call the notification slot...
Definition: datacache.h:400
static QString appendFilePaths(const QString &path1, const QString &path2)
Append file paths.
Definition: fileutils.cpp:95
Thrown when a convertFromJson method encounters an unrecoverable error in JSON data.
Definition: jsonexception.h:24
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 & error(const char16_t(&format)[N])
Set the severity to error, providing a format string.
Derived & info(const char16_t(&format)[N])
Set the severity to info, providing a format string.
size_type size() const
Returns number of elements in the sequence.
Definition: sequence.h:273
void push_back(const T &value)
Appends an element at the end of the sequence.
Definition: sequence.h:305
bool isEmpty() const
Synonym for empty.
Definition: sequence.h:285
Streamable status message, e.g.
static CStatusMessage fromJsonException(const CJsonException &ex, const CLogCategoryList &categories, const QString &prefix)
Object from JSON exception message.
constexpr static auto SeverityInfo
Status severities.
bool isFailure() const
Operation considered unsuccessful.
Status messages, e.g. from Core -> GUI.
bool isFailure() const
Any message is marked as failure.
void clearAllValues(const QString &keyPrefix={})
Clear all values from the cache.
qint64 latestTimestampMsecsSinceEpoch() const
Latest timestamp.
Value object encapsulating information about an airpot.
Definition: airport.h:36
Value object encapsulating information of airport ICAO data.
Value object for a list of airports.
Definition: airportlist.h:29
CAirport findFirstByIcao(const CAirportIcaoCode &icao) const
Find first station by callsign, if not return default.
Definition: airportlist.cpp:46
CAirport findFirstByNameOrLocation(const QString &nameOrLocation) const
Find first by name or location, if not return default.
Definition: airportlist.cpp:51
DataRetrievalModeFlag
Which data to read, requires corresponding readers.
Definition: dbflags.h:25
int replaceOrAddObjectsByKey(const CONTAINER &container)
Update or insert data (based on DB key)
QString dbKeysAsString(const QString &separator) const
The DB keys as string.
Value object encapsulating information of a location, kind of simplified CValueObject compliant versi...
Definition: url.h:27
bool isEmpty() const
Empty.
Definition: url.cpp:54
CUrl withAppendedPath(const QString &path) const
Append path.
Definition: url.cpp:115
void appendQuery(const QString &queryToAppend)
Append query.
Definition: url.cpp:66
Classes interacting with the swift database (aka "datastore").
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