swift
icaodatareader.cpp
1 // SPDX-FileCopyrightText: Copyright (C) 2015 swift Project Community / Contributors
2 // SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-swift-pilot-client-1
3 
5 
6 #include <QDateTime>
7 #include <QDir>
8 #include <QElapsedTimer>
9 #include <QFileInfo>
10 #include <QFlags>
11 #include <QJsonDocument>
12 #include <QNetworkReply>
13 #include <QPointer>
14 #include <QReadLocker>
15 #include <QScopedPointer>
16 #include <QScopedPointerDeleteLater>
17 #include <QStringBuilder>
18 #include <QTimer>
19 #include <QUrl>
20 #include <QWriteLocker>
21 #include <Qt>
22 #include <QtGlobal>
23 
24 #include "core/application.h"
25 #include "core/data/globalsetup.h"
26 #include "core/db/databaseutils.h"
28 #include "misc/fileutils.h"
29 #include "misc/json.h"
30 #include "misc/logmessage.h"
31 #include "misc/statusmessage.h"
32 
33 using namespace swift::misc;
34 using namespace swift::misc::db;
35 using namespace swift::misc::aviation;
36 using namespace swift::misc::network;
37 using namespace swift::core::data;
38 
39 namespace swift::core::db
40 {
41  CIcaoDataReader::CIcaoDataReader(QObject *owner, const CDatabaseReaderConfigList &config)
42  : CDatabaseReader(owner, config, "CIcaoDataReader")
43  {
44  // init to avoid threading issues
45  this->getBaseUrl(CDbFlags::DbReading);
46  }
47 
48  CAircraftIcaoCodeList CIcaoDataReader::getAircraftIcaoCodes() const { return m_aircraftIcaoCache.get(); }
49 
51  {
52  return this->getAircraftIcaoCodes().findFirstByDesignatorAndRank(designator);
53  }
54 
56  {
57  return this->getAircraftIcaoCodes().findByDesignator(designator);
58  }
59 
61  {
62  return this->getAircraftIcaoCodes().findByIataCode(iataCode);
63  }
64 
66  {
67  return this->getAircraftIcaoCodes().findByKey(key);
68  }
69 
70  bool CIcaoDataReader::containsAircraftIcaoDesignator(const QString &designator) const
71  {
72  return this->getAircraftIcaoCodes().containsDesignator(designator);
73  }
74 
75  CAirlineIcaoCodeList CIcaoDataReader::getAirlineIcaoCodes() const { return m_airlineIcaoCache.get(); }
76 
78  {
79  CAircraftIcaoCodeList codes(getAircraftIcaoCodes()); // thread safe copy
80  return codes.smartAircraftIcaoSelector(icaoPattern); // sorted by rank
81  }
82 
83  CCountryList CIcaoDataReader::getCountries() const { return m_countryCache.get(); }
84 
85  CCountry CIcaoDataReader::getCountryForIsoCode(const QString &isoCode) const
86  {
87  return this->getCountries().findByIsoCode(isoCode);
88  }
89 
90  CCountry CIcaoDataReader::getCountryForName(const QString &name) const
91  {
92  return this->getCountries().findBestMatchByCountryName(name);
93  }
94 
96  {
97  return this->getAirlineIcaoCodes().findByVDesignator(designator);
98  }
99 
100  bool CIcaoDataReader::containsAirlineIcaoDesignator(const QString &designator) const
101  {
102  return this->getAirlineIcaoCodes().containsVDesignator(designator);
103  }
104 
106  bool preferOperatingAirlines) const
107  {
108  return this->getAirlineIcaoCodes().findByUniqueVDesignatorOrDefault(designator, preferOperatingAirlines);
109  }
110 
112  {
113  return this->getAirlineIcaoCodes().findByIataCode(iataCode);
114  }
115 
117  {
118  return this->getAirlineIcaoCodes().findByUniqueIataCodeOrDefault(iataCode);
119  }
120 
122  {
123  return this->getAirlineIcaoCodes().findByKey(key);
124  }
125 
127  const CCallsign &callsign) const
128  {
129  const CAirlineIcaoCodeList codes(this->getAirlineIcaoCodes()); // thread safe copy
130  return codes.smartAirlineIcaoSelector(icaoPattern, callsign);
131  }
132 
134 
136 
138 
140 
142  {
143  return this->getCountriesCount() > 0 && getAirlineIcaoCodesCount() > 0 && getAircraftIcaoCodesCount() > 0;
144  }
145 
146  int CIcaoDataReader::getCountriesCount() const { return this->getCountries().size(); }
147 
148  void CIcaoDataReader::read(CEntityFlags::Entity entities, CDbFlags::DataRetrievalModeFlag mode,
149  const QDateTime &newerThan)
150  {
151  this->threadAssertCheck(); // runs in background thread
152  if (!this->doWorkCheck()) { return; }
153  entities &= CEntityFlags::AllIcaoCountriesCategory;
154 
155  CEntityFlags::Entity entitiesTriggered = CEntityFlags::NoEntity;
156  CUrl url;
157  if (entities.testFlag(CEntityFlags::AircraftIcaoEntity))
158  {
159  url = this->getAircraftIcaoUrl(mode);
160  if (!url.isEmpty())
161  {
162  url.appendQuery(queryLatestTimestamp(newerThan));
163  this->getFromNetworkAndLog(url, { this, &CIcaoDataReader::parseAircraftIcaoData });
164  entitiesTriggered |= CEntityFlags::AircraftIcaoEntity;
165  }
166  else { this->logNoWorkingUrl(CEntityFlags::AircraftIcaoEntity); }
167  }
168 
169  if (entities.testFlag(CEntityFlags::AirlineIcaoEntity))
170  {
171  url = this->getAirlineIcaoUrl(mode);
172  if (!url.isEmpty())
173  {
174  url.appendQuery(queryLatestTimestamp(newerThan));
175  this->getFromNetworkAndLog(url, { this, &CIcaoDataReader::parseAirlineIcaoData });
176  entitiesTriggered |= CEntityFlags::AirlineIcaoEntity;
177  }
178  else { this->logNoWorkingUrl(CEntityFlags::AirlineIcaoEntity); }
179  }
180 
181  if (entities.testFlag(CEntityFlags::CountryEntity))
182  {
183  url = this->getCountryUrl(mode);
184  if (!url.isEmpty())
185  {
186  url.appendQuery(queryLatestTimestamp(newerThan));
187  this->getFromNetworkAndLog(url, { this, &CIcaoDataReader::parseCountryData });
188  entitiesTriggered |= CEntityFlags::CountryEntity;
189  }
190  else { this->logNoWorkingUrl(CEntityFlags::CountryEntity); }
191  }
192 
193  if (entities.testFlag(CEntityFlags::AircraftCategoryEntity))
194  {
195  url = this->getAircraftCategoryUrl(mode);
196  if (!url.isEmpty())
197  {
198  url.appendQuery(queryLatestTimestamp(newerThan));
199  this->getFromNetworkAndLog(url, { this, &CIcaoDataReader::parseAircraftCategoryData });
200  entitiesTriggered |= CEntityFlags::AircraftCategoryEntity;
201  }
202  else { this->logNoWorkingUrl(CEntityFlags::AircraftCategoryEntity); }
203  }
204 
205  if (entitiesTriggered != CEntityFlags::NoEntity)
206  {
207  emit this->dataRead(entitiesTriggered, CEntityFlags::ReadStarted, 0, url);
208  }
209  }
210 
211  void CIcaoDataReader::aircraftIcaoCacheChanged() { this->cacheHasChanged(CEntityFlags::AircraftIcaoEntity); }
212 
213  void CIcaoDataReader::airlineIcaoCacheChanged() { this->cacheHasChanged(CEntityFlags::AirlineIcaoEntity); }
214 
215  void CIcaoDataReader::countryCacheChanged() { this->cacheHasChanged(CEntityFlags::CountryEntity); }
216 
217  void CIcaoDataReader::aircraftCategoryCacheChanged()
218  {
219  this->cacheHasChanged(CEntityFlags::AircraftCategoryEntity);
220  }
221 
222  void CIcaoDataReader::baseUrlCacheChanged()
223  {
224  // void
225  }
226 
227  void CIcaoDataReader::updateReaderUrl(const CUrl &url)
228  {
229  const CUrl current = m_readerUrlCache.get();
230  if (current == url) { return; }
231  const CStatusMessage m = m_readerUrlCache.set(url);
232  if (m.isFailure()) { CLogMessage::preformatted(m); }
233  }
234 
235  void CIcaoDataReader::parseAircraftIcaoData(QNetworkReply *nwReplyPtr)
236  {
237  // wrap pointer, make sure any exit cleans up reply
238  // required to use delete later as object is created in a different thread
239  QScopedPointer<QNetworkReply, QScopedPointerDeleteLater> nwReply(nwReplyPtr);
240  if (!this->doWorkCheck()) { return; }
241 
242  const CDatabaseReader::JsonDatastoreResponse res =
244  const QUrl url = nwReply->url();
245 
246  if (res.hasErrorMessage())
247  {
248  CLogMessage::preformatted(res.lastWarningOrAbove());
249  emit this->dataRead(CEntityFlags::AircraftIcaoEntity, CEntityFlags::ReadFailed, 0, url);
250  return;
251  }
252 
253  emit this->dataRead(CEntityFlags::AircraftIcaoEntity, CEntityFlags::ReadParsing, 0, url);
254  CAircraftIcaoCodeList codes;
255  CAircraftIcaoCodeList inconsistent;
256  const CAircraftCategoryList categories = this->getAircraftCategories();
257  if (res.isRestricted())
258  {
259  // create full list if it was just incremental
260  const CAircraftIcaoCodeList incrementalCodes(
261  CAircraftIcaoCodeList::fromDatabaseJson(res, categories, true, &inconsistent));
262  if (incrementalCodes.isEmpty()) { return; } // currently ignored
263  codes = this->getAircraftIcaoCodes();
264  codes.replaceOrAddObjectsByKey(incrementalCodes);
265  }
266  else
267  {
268  // normally read from special DB view which already filters incomplete
269  QElapsedTimer time;
270  time.start();
271  codes = CAircraftIcaoCodeList::fromDatabaseJson(res, categories, true, &inconsistent);
272  this->logParseMessage("aircraft ICAO", codes.size(), static_cast<int>(time.elapsed()), res);
273  }
274 
275  if (!inconsistent.isEmpty())
276  {
278  u"Inconsistent aircraft codes: " % inconsistent.dbKeysAsString(", ")),
279  Q_FUNC_INFO);
280  }
281 
282  if (!this->doWorkCheck()) { return; }
283  const int n = codes.size();
284  qint64 latestTimestamp = codes.latestTimestampMsecsSinceEpoch(); // ignores duplicates
285  if (n > 0 && latestTimestamp < 0)
286  {
287  CLogMessage(this).error(u"No timestamp in aircraft ICAO list, setting to last modified value");
288  latestTimestamp = this->lastModifiedMsSinceEpoch(nwReply.data());
289  }
290 
291  m_aircraftIcaoCache.set(codes, latestTimestamp);
292  this->updateReaderUrl(this->getBaseUrl(CDbFlags::DbReading));
293 
294  this->emitAndLogDataRead(CEntityFlags::AircraftIcaoEntity, n, res);
295  }
296 
297  void CIcaoDataReader::parseAirlineIcaoData(QNetworkReply *nwReplyPtr)
298  {
299  QScopedPointer<QNetworkReply, QScopedPointerDeleteLater> nwReply(nwReplyPtr);
300  if (!this->doWorkCheck()) { return; }
301 
302  const QUrl url = nwReply->url();
303  const CDatabaseReader::JsonDatastoreResponse res =
305  if (res.hasErrorMessage())
306  {
307  CLogMessage::preformatted(res.lastWarningOrAbove());
308  emit this->dataRead(CEntityFlags::AirlineIcaoEntity, CEntityFlags::ReadFailed, 0, url);
309  return;
310  }
311 
312  emit this->dataRead(CEntityFlags::AirlineIcaoEntity, CEntityFlags::ReadParsing, 0, url);
313  CAirlineIcaoCodeList codes;
314  CAirlineIcaoCodeList inconsistent;
315  if (res.isRestricted())
316  {
317  // create full list if it was just incremental
318  const CAirlineIcaoCodeList incrementalCodes(
319  CAirlineIcaoCodeList::fromDatabaseJson(res, true, &inconsistent));
320  if (incrementalCodes.isEmpty()) { return; } // currently ignored
321  codes = this->getAirlineIcaoCodes();
322  codes.replaceOrAddObjectsByKey(incrementalCodes);
323  }
324  else
325  {
326  // normally read from special DB view which already filters incomplete
327  QElapsedTimer time;
328  time.start();
329  codes = CAirlineIcaoCodeList::fromDatabaseJson(res, true, &inconsistent);
330  this->logParseMessage("airline ICAO", codes.size(), static_cast<int>(time.elapsed()), res);
331  }
332 
333  if (!inconsistent.isEmpty())
334  {
336  u"Inconsistent airline codes: " % inconsistent.dbKeysAsString(", ")),
337  Q_FUNC_INFO);
338  }
339 
340  if (!this->doWorkCheck()) { return; }
341  const int n = codes.size();
342  qint64 latestTimestamp = codes.latestTimestampMsecsSinceEpoch();
343  if (n > 0 && latestTimestamp < 0)
344  {
345  CLogMessage(this).error(u"No timestamp in airline ICAO list, setting to last modified value");
346  latestTimestamp = this->lastModifiedMsSinceEpoch(nwReply.data());
347  }
348 
349  m_airlineIcaoCache.set(codes, latestTimestamp);
350  this->updateReaderUrl(this->getBaseUrl(CDbFlags::DbReading));
351 
352  this->emitAndLogDataRead(CEntityFlags::AirlineIcaoEntity, n, res);
353  }
354 
355  void CIcaoDataReader::parseCountryData(QNetworkReply *nwReplyPtr)
356  {
357  QScopedPointer<QNetworkReply, QScopedPointerDeleteLater> nwReply(nwReplyPtr);
358  const CDatabaseReader::JsonDatastoreResponse res =
360  const QUrl url = nwReply->url();
361 
362  if (res.hasErrorMessage())
363  {
364  CLogMessage::preformatted(res.lastWarningOrAbove());
365  emit this->dataRead(CEntityFlags::CountryEntity, CEntityFlags::ReadFailed, 0, url);
366  return;
367  }
368 
369  emit this->dataRead(CEntityFlags::CountryEntity, CEntityFlags::ReadParsing, 0, url);
370  CCountryList countries;
371  if (res.isRestricted())
372  {
373  // create full list if it was just incremental
374  const CCountryList incrementalCountries(CCountryList::fromDatabaseJson(res));
375  if (incrementalCountries.isEmpty()) { return; } // currently ignored
376  countries = this->getCountries();
377  countries.replaceOrAddObjectsByKey(incrementalCountries);
378  }
379  else
380  {
381  // normally read from special DB view which already filters incomplete
382  QElapsedTimer time;
383  time.start();
384  countries = CCountryList::fromDatabaseJson(res);
385  this->logParseMessage("countries", countries.size(), static_cast<int>(time.elapsed()), res);
386  }
387 
388  if (!this->doWorkCheck()) { return; }
389  const int n = countries.size();
390  qint64 latestTimestamp = countries.latestTimestampMsecsSinceEpoch();
391  if (n > 0 && latestTimestamp < 0)
392  {
393  CLogMessage(this).error(u"No timestamp in country list, setting to last modified value");
394  latestTimestamp = this->lastModifiedMsSinceEpoch(nwReply.data());
395  }
396 
397  m_countryCache.set(countries, latestTimestamp);
398  this->updateReaderUrl(this->getBaseUrl(CDbFlags::DbReading));
399 
400  this->emitAndLogDataRead(CEntityFlags::CountryEntity, n, res);
401  }
402 
403  void CIcaoDataReader::parseAircraftCategoryData(QNetworkReply *nwReplyPtr)
404  {
405  QScopedPointer<QNetworkReply, QScopedPointerDeleteLater> nwReply(nwReplyPtr);
406  const CDatabaseReader::JsonDatastoreResponse res =
408  const QUrl url = nwReply->url();
409 
410  if (res.hasErrorMessage())
411  {
412  CLogMessage::preformatted(res.lastWarningOrAbove());
413  emit this->dataRead(CEntityFlags::AircraftCategoryEntity, CEntityFlags::ReadFailed, 0, url);
414  return;
415  }
416 
417  emit this->dataRead(CEntityFlags::AircraftCategoryEntity, CEntityFlags::ReadParsing, 0, url);
418  CAircraftCategoryList categories;
419  if (res.isRestricted())
420  {
421  // create full list if it was just incremental
422  const CAircraftCategoryList incrementalCategories(CAircraftCategoryList::fromDatabaseJson(res));
423  if (incrementalCategories.isEmpty()) { return; } // currently ignored
424  categories = this->getAircraftCategories();
425  categories.replaceOrAddObjectsByKey(incrementalCategories);
426  }
427  else
428  {
429  // normally read from special DB view which already filters incomplete
430  QElapsedTimer time;
431  time.start();
432  categories = CAircraftCategoryList::fromDatabaseJson(res);
433  this->logParseMessage("categories", categories.size(), static_cast<int>(time.elapsed()), res);
434  }
435 
436  if (!this->doWorkCheck()) { return; }
437  const int n = categories.size();
438  qint64 latestTimestamp = categories.latestTimestampMsecsSinceEpoch();
439  if (n > 0 && latestTimestamp < 0)
440  {
441  CLogMessage(this).error(u"No timestamp in category list, setting to last modified value");
442  latestTimestamp = this->lastModifiedMsSinceEpoch(nwReply.data());
443  }
444 
445  m_categoryCache.set(categories, latestTimestamp);
446  this->updateReaderUrl(this->getBaseUrl(CDbFlags::DbReading));
447 
448  this->emitAndLogDataRead(CEntityFlags::AircraftCategoryEntity, n, res);
449  }
450 
451  CStatusMessageList CIcaoDataReader::readFromJsonFiles(const QString &dir, CEntityFlags::Entity whatToRead,
452  bool overrideNewerOnly)
453  {
454  const QDir directory(dir);
455  if (!directory.exists()) { return CStatusMessage(this).error(u"Missing directory '%1'") << dir; }
456 
457  // Hint: Do not emit while locked -> deadlock
458  CStatusMessageList msgs;
459  whatToRead &= CEntityFlags::AllIcaoCountriesCategory;
460  CEntityFlags::Entity reallyRead = CEntityFlags::NoEntity;
461  if (whatToRead.testFlag(CEntityFlags::CountryEntity))
462  {
463  const QString fileName = CFileUtils::appendFilePaths(
464  directory.absolutePath(), CDbInfo::entityToSharedName(CEntityFlags::CountryEntity));
465  const QFileInfo fi(fileName);
466  if (!fi.exists()) { msgs.push_back(CStatusMessage(this).warning(u"File '%1' does not exist") << fileName); }
467  else if (!this->overrideCacheFromFile(overrideNewerOnly, fi, CEntityFlags::CountryEntity, msgs))
468  {
469  // void
470  }
471  else
472  {
473  const QUrl url = QUrl::fromLocalFile(fi.absoluteFilePath());
474  const QJsonObject countriesJson(CDatabaseUtils::readQJsonObjectFromDatabaseFile(fileName));
475  if (countriesJson.isEmpty())
476  {
477  msgs.push_back(CStatusMessage(this).error(u"Failed to read from file/empty file '%1'") << fileName);
478  }
479  else
480  {
481  try
482  {
483  const CCountryList countries = CCountryList::fromMultipleJsonFormats(countriesJson);
484  const int c = countries.size();
485  msgs.push_back(m_countryCache.set(countries, fi.birthTime().toUTC().toMSecsSinceEpoch()));
486  reallyRead |= CEntityFlags::CountryEntity;
487  emit this->dataRead(CEntityFlags::CountryEntity, CEntityFlags::ReadFinished, c, url);
488  }
489  catch (const CJsonException &ex)
490  {
491  emit this->dataRead(CEntityFlags::CountryEntity, CEntityFlags::ReadFailed, 0, url);
493  ex, this, QStringLiteral("Reading countries from '%1'").arg(fileName)));
494  }
495  }
496  }
497  } // country
498 
499  if (whatToRead.testFlag(CEntityFlags::AircraftIcaoEntity))
500  {
501  const QString fileName = CFileUtils::appendFilePaths(
502  directory.absolutePath(), CDbInfo::entityToSharedName(CEntityFlags::AircraftIcaoEntity));
503  const QFileInfo fi(fileName);
504  if (!fi.exists()) { msgs.push_back(CStatusMessage(this).warning(u"File '%1' does not exist") << fileName); }
505  else if (!this->overrideCacheFromFile(overrideNewerOnly, fi, CEntityFlags::AircraftIcaoEntity, msgs))
506  {
507  // void
508  }
509  else
510  {
511  const QUrl url = QUrl::fromLocalFile(fi.absoluteFilePath());
512  const QJsonObject aircraftJson(CDatabaseUtils::readQJsonObjectFromDatabaseFile(fileName));
513  if (aircraftJson.isEmpty())
514  {
515  msgs.push_back(CStatusMessage(this).error(u"Failed to read from file/empty file '%1'") << fileName);
516  }
517  else
518  {
519  try
520  {
521  const CAircraftIcaoCodeList aircraftIcaos =
522  CAircraftIcaoCodeList::fromMultipleJsonFormats(aircraftJson);
523  const int c = aircraftIcaos.size();
524  msgs.push_back(
525  m_aircraftIcaoCache.set(aircraftIcaos, fi.birthTime().toUTC().toMSecsSinceEpoch()));
526  reallyRead |= CEntityFlags::AircraftIcaoEntity;
527  emit this->dataRead(CEntityFlags::AircraftIcaoEntity, CEntityFlags::ReadFinished, c, url);
528  }
529  catch (const CJsonException &ex)
530  {
531  emit this->dataRead(CEntityFlags::AircraftIcaoEntity, CEntityFlags::ReadFailed, 0, url);
533  ex, this, QStringLiteral("Reading aircraft ICAOs from '%1'").arg(fileName)));
534  }
535  }
536  }
537  } // aircraft
538 
539  if (whatToRead.testFlag(CEntityFlags::AirlineIcaoEntity))
540  {
541  const QString fileName = CFileUtils::appendFilePaths(
542  directory.absolutePath(), CDbInfo::entityToSharedName(CEntityFlags::AirlineIcaoEntity));
543  const QFileInfo fi(fileName);
544  if (!fi.exists()) { msgs.push_back(CStatusMessage(this).warning(u"File '%1' does not exist") << fileName); }
545  else if (!this->overrideCacheFromFile(overrideNewerOnly, fi, CEntityFlags::AirlineIcaoEntity, msgs))
546  {
547  // void
548  }
549  else
550  {
551  const QUrl url = QUrl::fromLocalFile(fi.absoluteFilePath());
552  const QJsonObject airlineJson(CDatabaseUtils::readQJsonObjectFromDatabaseFile(fileName));
553  if (airlineJson.isEmpty())
554  {
555  msgs.push_back(CStatusMessage(this).error(u"Failed to read from file/empty file '%1'") << fileName);
556  }
557  else
558  {
559  try
560  {
561  const CAirlineIcaoCodeList airlineIcaos =
562  CAirlineIcaoCodeList::fromMultipleJsonFormats(airlineJson);
563  const int c = airlineIcaos.size();
564  msgs.push_back(
565  m_airlineIcaoCache.set(airlineIcaos, fi.birthTime().toUTC().toMSecsSinceEpoch()));
566  reallyRead |= CEntityFlags::AirlineIcaoEntity;
567  emit this->dataRead(CEntityFlags::AirlineIcaoEntity, CEntityFlags::ReadFinished, c, url);
568  }
569  catch (const CJsonException &ex)
570  {
571  emit this->dataRead(CEntityFlags::AirlineIcaoEntity, CEntityFlags::ReadFailed, 0, url);
573  ex, this, QStringLiteral("Reading airline ICAOs from '%1'").arg(fileName)));
574  }
575  }
576  }
577  } // airline
578 
579  if (whatToRead.testFlag(CEntityFlags::AircraftCategoryEntity))
580  {
581  const QString fileName = CFileUtils::appendFilePaths(
582  directory.absolutePath(), CDbInfo::entityToSharedName(CEntityFlags::AircraftCategoryEntity));
583  const QFileInfo fi(fileName);
584  if (!fi.exists()) { msgs.push_back(CStatusMessage(this).warning(u"File '%1' does not exist") << fileName); }
585  else if (!this->overrideCacheFromFile(overrideNewerOnly, fi, CEntityFlags::AircraftCategoryEntity, msgs))
586  {
587  // void
588  }
589  else
590  {
591  const QUrl url = QUrl::fromLocalFile(fi.absoluteFilePath());
592  const QJsonObject aircraftCategory(CDatabaseUtils::readQJsonObjectFromDatabaseFile(fileName));
593  if (aircraftCategory.isEmpty())
594  {
595  msgs.push_back(CStatusMessage(this).error(u"Failed to read from file/empty file '%1'") << fileName);
596  }
597  else
598  {
599  try
600  {
601  const CAircraftCategoryList aircraftCategories =
602  CAircraftCategoryList::fromMultipleJsonFormats(aircraftCategory);
603  const int c = aircraftCategories.size();
604  msgs.push_back(
605  m_categoryCache.set(aircraftCategories, fi.birthTime().toUTC().toMSecsSinceEpoch()));
606  reallyRead |= CEntityFlags::AircraftCategoryEntity;
607  emit this->dataRead(CEntityFlags::AircraftCategoryEntity, CEntityFlags::ReadFinished, c, url);
608  }
609  catch (const CJsonException &ex)
610  {
611  emit this->dataRead(CEntityFlags::AircraftCategoryEntity, CEntityFlags::ReadFailed, 0, url);
613  ex, this, QStringLiteral("Reading categories from '%1'").arg(fileName)));
614  }
615  }
616  }
617  } // categories
618 
619  return msgs;
620  }
621 
622  bool CIcaoDataReader::readFromJsonFilesInBackground(const QString &dir, CEntityFlags::Entity whatToRead,
623  bool overrideNewerOnly)
624  {
625  if (dir.isEmpty() || whatToRead == CEntityFlags::NoEntity) { return false; }
626 
627  QPointer<CIcaoDataReader> myself(this);
628  QTimer::singleShot(0, this, [=]() {
629  if (!myself) { return; }
630  const CStatusMessageList msgs = this->readFromJsonFiles(dir, whatToRead, overrideNewerOnly);
631  if (msgs.isFailure()) { CLogMessage::preformatted(msgs); }
632  });
633  return true;
634  }
635 
636  bool CIcaoDataReader::writeToJsonFiles(const QString &dir)
637  {
638  QDir directory(dir);
639  if (!directory.exists()) { return false; }
640  QList<QPair<CEntityFlags::EntityFlag, QString>> fileContents;
641  if (this->getCountriesCount() > 0)
642  {
643  const QString json(QJsonDocument(this->getCountries().toJson()).toJson());
644  fileContents.push_back({ CEntityFlags::CountryEntity, json });
645  }
646 
647  if (this->getAircraftIcaoCodesCount() > 0)
648  {
649  const QString json(QJsonDocument(this->getAircraftIcaoCodes().toJson()).toJson());
650  fileContents.push_back({ CEntityFlags::AircraftIcaoEntity, json });
651  }
652 
653  if (this->getAirlineIcaoCodesCount() > 0)
654  {
655  const QString json(QJsonDocument(this->getAirlineIcaoCodes().toJson()).toJson());
656  fileContents.push_back({ CEntityFlags::AirlineIcaoEntity, json });
657  }
658 
659  if (this->getAircraftCategoryCount() > 0)
660  {
661  const QString json(QJsonDocument(this->getAirlineIcaoCodes().toJson()).toJson());
662  fileContents.push_back({ CEntityFlags::AircraftCategoryEntity, json });
663  }
664 
665  for (const auto &pair : fileContents)
666  {
667  CWorker::fromTask(this, Q_FUNC_INFO, [pair, directory] {
669  CFileUtils::appendFilePaths(directory.absolutePath(), CDbInfo::entityToSharedName(pair.first)),
670  pair.second);
671  });
672  }
673  return true;
674  }
675 
676  CEntityFlags::Entity CIcaoDataReader::getSupportedEntities() const
677  {
678  return CEntityFlags::AllIcaoCountriesCategory;
679  }
680 
681  void CIcaoDataReader::synchronizeCaches(CEntityFlags::Entity entities)
682  {
683  if (entities.testFlag(CEntityFlags::AircraftIcaoEntity))
684  {
685  if (m_syncedAircraftIcaoCache) { return; }
686  m_syncedAircraftIcaoCache = true;
687  m_aircraftIcaoCache.synchronize();
688  }
689  if (entities.testFlag(CEntityFlags::AirlineIcaoEntity))
690  {
691  if (m_syncedAirlineIcaoCache) { return; }
692  m_syncedAirlineIcaoCache = true;
693  m_airlineIcaoCache.synchronize();
694  }
695  if (entities.testFlag(CEntityFlags::CountryEntity))
696  {
697  if (m_syncedCountryCache) { return; }
698  m_syncedCountryCache = true;
699  m_countryCache.synchronize();
700  }
701  if (entities.testFlag(CEntityFlags::AircraftCategoryEntity))
702  {
703  if (m_syncedCategories) { return; }
704  m_syncedCategories = true;
705  m_categoryCache.synchronize();
706  }
707  }
708 
709  void CIcaoDataReader::admitCaches(CEntityFlags::Entity entities)
710  {
711  if (entities.testFlag(CEntityFlags::AircraftIcaoEntity)) { m_aircraftIcaoCache.admit(); }
712  if (entities.testFlag(CEntityFlags::AirlineIcaoEntity)) { m_airlineIcaoCache.admit(); }
713  if (entities.testFlag(CEntityFlags::CountryEntity)) { m_countryCache.admit(); }
714  if (entities.testFlag(CEntityFlags::AircraftCategoryEntity)) { m_categoryCache.admit(); }
715  }
716 
717  void CIcaoDataReader::invalidateCaches(CEntityFlags::Entity entities)
718  {
719  if (entities.testFlag(CEntityFlags::AircraftIcaoEntity))
720  {
721  CDataCache::instance()->clearAllValues(m_aircraftIcaoCache.getKey());
722  }
723  if (entities.testFlag(CEntityFlags::AirlineIcaoEntity))
724  {
725  CDataCache::instance()->clearAllValues(m_airlineIcaoCache.getKey());
726  }
727  if (entities.testFlag(CEntityFlags::CountryEntity))
728  {
729  CDataCache::instance()->clearAllValues(m_countryCache.getKey());
730  }
731  if (entities.testFlag(CEntityFlags::AircraftCategoryEntity))
732  {
733  CDataCache::instance()->clearAllValues(m_categoryCache.getKey());
734  }
735  }
736 
737  QDateTime CIcaoDataReader::getCacheTimestamp(CEntityFlags::Entity entity) const
738  {
739  switch (entity)
740  {
741  case CEntityFlags::AircraftIcaoEntity: return m_aircraftIcaoCache.getAvailableTimestamp();
742  case CEntityFlags::AirlineIcaoEntity: return m_airlineIcaoCache.getAvailableTimestamp();
743  case CEntityFlags::CountryEntity: return m_countryCache.getAvailableTimestamp();
744  case CEntityFlags::AircraftCategoryEntity: return m_categoryCache.getAvailableTimestamp();
745  default: return QDateTime();
746  }
747  }
748 
749  int CIcaoDataReader::getCacheCount(CEntityFlags::Entity entity) const
750  {
751  switch (entity)
752  {
753  case CEntityFlags::AircraftIcaoEntity: return m_aircraftIcaoCache.get().size();
754  case CEntityFlags::AirlineIcaoEntity: return m_airlineIcaoCache.get().size();
755  case CEntityFlags::CountryEntity: return m_countryCache.get().size();
756  case CEntityFlags::AircraftCategoryEntity: return m_categoryCache.get().size();
757  default: return 0;
758  }
759  }
760 
761  CEntityFlags::Entity CIcaoDataReader::getEntitiesWithCacheCount() const
762  {
763  CEntityFlags::Entity entities = CEntityFlags::NoEntity;
764  if (this->getCacheCount(CEntityFlags::AircraftIcaoEntity) > 0) entities |= CEntityFlags::AircraftIcaoEntity;
765  if (this->getCacheCount(CEntityFlags::AirlineIcaoEntity) > 0) entities |= CEntityFlags::AirlineIcaoEntity;
766  if (this->getCacheCount(CEntityFlags::CountryEntity) > 0) entities |= CEntityFlags::CountryEntity;
767  if (this->getCacheCount(CEntityFlags::AircraftCategoryEntity) > 0)
768  entities |= CEntityFlags::AircraftCategoryEntity;
769  return entities;
770  }
771 
772  CEntityFlags::Entity CIcaoDataReader::getEntitiesWithCacheTimestampNewerThan(const QDateTime &threshold) const
773  {
774  CEntityFlags::Entity entities = CEntityFlags::NoEntity;
775  if (this->hasCacheTimestampNewerThan(CEntityFlags::AircraftIcaoEntity, threshold))
776  entities |= CEntityFlags::AircraftIcaoEntity;
777  if (this->hasCacheTimestampNewerThan(CEntityFlags::AirlineIcaoEntity, threshold))
778  entities |= CEntityFlags::AirlineIcaoEntity;
779  if (this->hasCacheTimestampNewerThan(CEntityFlags::CountryEntity, threshold))
780  entities |= CEntityFlags::CountryEntity;
781  if (this->hasCacheTimestampNewerThan(CEntityFlags::AircraftCategoryEntity, threshold))
782  entities |= CEntityFlags::CountryEntity;
783  return entities;
784  }
785 
786  bool CIcaoDataReader::hasChangedUrl(CEntityFlags::Entity entity, CUrl &oldUrlInfo, CUrl &newUrlInfo) const
787  {
788  Q_UNUSED(entity)
789  oldUrlInfo = m_readerUrlCache.get();
790  newUrlInfo = this->getBaseUrl(CDbFlags::DbReading);
791  return CDatabaseReader::isChangedUrl(oldUrlInfo, newUrlInfo);
792  }
793 
795 
796  CUrl CIcaoDataReader::getAircraftIcaoUrl(CDbFlags::DataRetrievalModeFlag mode) const
797  {
798  return this->getBaseUrl(mode).withAppendedPath(fileNameForMode(CEntityFlags::AircraftIcaoEntity, mode));
799  }
800 
801  CUrl CIcaoDataReader::getAirlineIcaoUrl(CDbFlags::DataRetrievalModeFlag mode) const
802  {
803  return this->getBaseUrl(mode).withAppendedPath(fileNameForMode(CEntityFlags::AirlineIcaoEntity, mode));
804  }
805 
806  CUrl CIcaoDataReader::getCountryUrl(CDbFlags::DataRetrievalModeFlag mode) const
807  {
808  return this->getBaseUrl(mode).withAppendedPath(fileNameForMode(CEntityFlags::CountryEntity, mode));
809  }
810 
811  CUrl CIcaoDataReader::getAircraftCategoryUrl(CDbFlags::DataRetrievalModeFlag mode) const
812  {
813  return this->getBaseUrl(mode).withAppendedPath(fileNameForMode(CEntityFlags::AircraftCategoryEntity, mode));
814  }
815 } // 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 getDbIcaoReaderUrl() const
ICAO reader URL.
Definition: globalsetup.cpp:33
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)
swift::misc::CCountry getCountryForName(const QString &name) const
Get country for ISO name.
swift::misc::CCountry getCountryForIsoCode(const QString &isoCode) const
Get country for ISO code.
virtual void read(swift::misc::network::CEntityFlags::Entity entities, swift::misc::db::CDbFlags::DataRetrievalModeFlag mode, const QDateTime &newerThan)
Read / re-read data file.
bool containsAirlineIcaoDesignator(const QString &designator) const
Contains given designator?
int getCountriesCount() const
Get countries count.
swift::misc::aviation::CAirlineIcaoCode getAirlineIcaoCodeForDbKey(int key) const
Get airline ICAO information for key.
swift::misc::aviation::CAirlineIcaoCode getAirlineIcaoCodeForUniqueIataCodeOrDefault(const QString &iataCode) const
Find by IATA code if this is unique, otherwise return default object.
virtual swift::misc::CStatusMessageList readFromJsonFiles(const QString &dir, swift::misc::network::CEntityFlags::Entity whatToRead, bool overrideNewerOnly)
Data read from local data.
int getAirlineIcaoCodesCount() const
Get airline ICAO information count.
bool writeToJsonFiles(const QString &dir)
Write to static DB data file.
int getAircraftCategoryCount() const
Get aircraft category count.
virtual int getCacheCount(swift::misc::network::CEntityFlags::Entity entity) const
Cache`s number of entities.
swift::misc::aviation::CAircraftIcaoCodeList getAircraftIcaoCodes() const
Get aircraft ICAO information.
virtual swift::misc::network::CEntityFlags::Entity getEntitiesWithCacheCount() const
Entities already having data in cache.
virtual QDateTime getCacheTimestamp(swift::misc::network::CEntityFlags::Entity entity) const
Get cache timestamp.
swift::misc::aviation::CAircraftIcaoCode getAircraftIcaoCodeForDesignator(const QString &designator) const
Get aircraft ICAO information for designator.
swift::misc::aviation::CAirlineIcaoCodeList getAirlineIcaoCodesForDesignator(const QString &designator) const
Find by v-designator, this should be unique.
swift::misc::aviation::CAirlineIcaoCodeList getAirlineIcaoCodes() const
Get airline ICAO information.
swift::misc::aviation::CAircraftIcaoCodeList getAircraftIcaoCodesForIataCode(const QString &iataCode) const
Get aircraft ICAO information for IATA code.
virtual swift::misc::network::CEntityFlags::Entity getSupportedEntities() const
Supported entities by this reader.
virtual bool readFromJsonFilesInBackground(const QString &dir, swift::misc::network::CEntityFlags::Entity whatToRead, bool overrideNewerOnly)
Data read from local data.
swift::misc::aviation::CAircraftIcaoCodeList getAircraftIcaoCodesForDesignator(const QString &designator) const
Get aircraft ICAO information for designator.
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.
virtual void admitCaches(swift::misc::network::CEntityFlags::Entity entities)
Admit caches for given entities.
swift::misc::aviation::CAircraftCategoryList getAircraftCategories() const
Get aircraft categories.
swift::misc::aviation::CAirlineIcaoCode getAirlineIcaoCodeForUniqueDesignatorOrDefault(const QString &designator, bool preferOperatingAirlines) const
Find by ICAO code if this is unique, otherwise return default object.
virtual swift::misc::network::CEntityFlags::Entity getEntitiesWithCacheTimestampNewerThan(const QDateTime &threshold) const
Entities already having data in cache (based on timestamp assumption)
bool areAllDataRead() const
All data read?
swift::misc::CCountryList getCountries() const
Get countries.
virtual void invalidateCaches(swift::misc::network::CEntityFlags::Entity entities)
Invalidate the caches for given entities.
int getAircraftIcaoCodesCount() const
Get aircraft ICAO information count.
bool containsAircraftIcaoDesignator(const QString &designator) const
Contains designator?
virtual void synchronizeCaches(swift::misc::network::CEntityFlags::Entity entities)
Admit caches for given entities.
swift::misc::aviation::CAirlineIcaoCode smartAirlineIcaoSelector(const swift::misc::aviation::CAirlineIcaoCode &icaoPattern, const swift::misc::aviation::CCallsign &callsign=swift::misc::aviation::CCallsign()) const
Get best match for airline ICAO code.
swift::misc::aviation::CAirlineIcaoCodeList getAirlineIcaoCodesForIataCode(const QString &iataCode) const
Find by IATA code Not unique because of virtual airlines and ceased airlines.
virtual swift::misc::network::CUrl getDbServiceBaseUrl() const
Get the service URL, individual for each reader.
swift::misc::aviation::CAircraftIcaoCode getAircraftIcaoCodeForDbKey(int key) const
Get aircraft ICAO information for key.
swift::misc::aviation::CAircraftIcaoCode smartAircraftIcaoSelector(const swift::misc::aviation::CAircraftIcaoCode &icaoPattern) const
Get best match for incomplete aircraft ICAO code.
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
Value object encapsulating a list of countries.
Definition: countrylist.h:32
static CCountryList fromDatabaseJson(const QJsonArray &array)
From our database JSON format.
CCountry findBestMatchByCountryName(const QString &candidate) const
Find "best match" by country.
Definition: countrylist.cpp:25
CCountry findByIsoCode(const QString &isoCode) const
Find by ISO code.
Definition: countrylist.cpp:18
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 bool writeStringToFile(const QString &content, const QString &fileNameAndPath)
Write string to text file.
Definition: fileutils.cpp:40
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.
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.
static CWorker * fromTask(QObject *owner, const QString &name, F &&task)
Returns a new worker object which lives in a new thread.
Definition: worker.h:201
qint64 latestTimestampMsecsSinceEpoch() const
Latest timestamp.
Value object encapsulating a list of ICAO codes.
Value object for ICAO classification.
Value object encapsulating a list of ICAO codes.
CAircraftIcaoCodeList findByDesignator(const QString &designator, int fuzzySearch=-1) const
Find by designator.
CAircraftIcaoCode findFirstByDesignatorAndRank(const QString &designator) const
Find by designator, then best match by rank.
bool containsDesignator(const QString &designator) const
Contains designator?
CAircraftIcaoCode smartAircraftIcaoSelector(const CAircraftIcaoCode &icaoPattern) const
Best selection by given pattern, also searches IATA and family information.
CAircraftIcaoCodeList findByIataCode(const QString &iata, int fuzzySearch=-1) const
Find by IATA code.
Value object for ICAO classification.
Value object encapsulating a list of ICAO codes.
CAirlineIcaoCode findByUniqueIataCodeOrDefault(const QString &iata) const
Find by IATA code if this is unique, otherwise return default object.
bool containsVDesignator(const QString &vDesignator) const
Contains given designator?
CAirlineIcaoCodeList findByIataCode(const QString &iata) const
Find by IATA code Not unique because of virtual airlines and ceased airlines.
CAirlineIcaoCode findByUniqueVDesignatorOrDefault(const QString &designator, bool preferOperatingAirlines) const
Find by ICAO code if this is unique, otherwise return default object.
CAirlineIcaoCode smartAirlineIcaoSelector(const CAirlineIcaoCode &icaoPattern, const CCallsign &callsign) const
Best selection by given pattern.
CAirlineIcaoCodeList findByVDesignator(const QString &designator) const
Find by v-designator, this should be unique.
Value object encapsulating information of a callsign.
Definition: callsign.h:30
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)
OBJ findByKey(KEYTYPE key, const OBJ &notFound=OBJ()) const
Object with key, notFound otherwise.
QString dbKeysAsString(const QString &separator) const
The DB keys as string.
static CCountryList fromMultipleJsonFormats(const QJsonObject &jsonObject)
From multiple JSON formats.
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
Core data traits (aka cached values) and classes.
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