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  if (entities.testFlag(CEntityFlags::AircraftIcaoEntity))
157  {
158  CUrl url = this->getAircraftIcaoUrl(mode);
159  if (!url.isEmpty())
160  {
161  url.appendQuery(queryLatestTimestamp(newerThan));
162  this->getFromNetworkAndLog(url, { this, &CIcaoDataReader::parseAircraftIcaoData });
163  entitiesTriggered |= CEntityFlags::AircraftIcaoEntity;
164  }
165  else { this->logNoWorkingUrl(CEntityFlags::AircraftIcaoEntity); }
166  }
167 
168  if (entities.testFlag(CEntityFlags::AirlineIcaoEntity))
169  {
170  CUrl url = this->getAirlineIcaoUrl(mode);
171  if (!url.isEmpty())
172  {
173  url.appendQuery(queryLatestTimestamp(newerThan));
174  this->getFromNetworkAndLog(url, { this, &CIcaoDataReader::parseAirlineIcaoData });
175  entitiesTriggered |= CEntityFlags::AirlineIcaoEntity;
176  }
177  else { this->logNoWorkingUrl(CEntityFlags::AirlineIcaoEntity); }
178  }
179 
180  if (entities.testFlag(CEntityFlags::CountryEntity))
181  {
182  CUrl url = this->getCountryUrl(mode);
183  if (!url.isEmpty())
184  {
185  url.appendQuery(queryLatestTimestamp(newerThan));
186  this->getFromNetworkAndLog(url, { this, &CIcaoDataReader::parseCountryData });
187  entitiesTriggered |= CEntityFlags::CountryEntity;
188  }
189  else { this->logNoWorkingUrl(CEntityFlags::CountryEntity); }
190  }
191 
192  if (entities.testFlag(CEntityFlags::AircraftCategoryEntity))
193  {
194  CUrl url = this->getAircraftCategoryUrl(mode);
195  if (!url.isEmpty())
196  {
197  url.appendQuery(queryLatestTimestamp(newerThan));
198  this->getFromNetworkAndLog(url, { this, &CIcaoDataReader::parseAircraftCategoryData });
199  entitiesTriggered |= CEntityFlags::AircraftCategoryEntity;
200  }
201  else { this->logNoWorkingUrl(CEntityFlags::AircraftCategoryEntity); }
202  }
203 
204  if (entitiesTriggered != CEntityFlags::NoEntity)
205  {
206  emit this->dataRead(entitiesTriggered, CEntityFlags::ReadStarted, 0, getBaseUrl(mode));
207  }
208  }
209 
210  void CIcaoDataReader::aircraftIcaoCacheChanged() { this->cacheHasChanged(CEntityFlags::AircraftIcaoEntity); }
211 
212  void CIcaoDataReader::airlineIcaoCacheChanged() { this->cacheHasChanged(CEntityFlags::AirlineIcaoEntity); }
213 
214  void CIcaoDataReader::countryCacheChanged() { this->cacheHasChanged(CEntityFlags::CountryEntity); }
215 
216  void CIcaoDataReader::aircraftCategoryCacheChanged()
217  {
218  this->cacheHasChanged(CEntityFlags::AircraftCategoryEntity);
219  }
220 
221  void CIcaoDataReader::baseUrlCacheChanged()
222  {
223  // void
224  }
225 
226  void CIcaoDataReader::updateReaderUrl(const CUrl &url)
227  {
228  const CUrl current = m_readerUrlCache.get();
229  if (current == url) { return; }
230  const CStatusMessage m = m_readerUrlCache.set(url);
231  if (m.isFailure()) { CLogMessage::preformatted(m); }
232  }
233 
234  void CIcaoDataReader::parseAircraftIcaoData(QNetworkReply *nwReplyPtr)
235  {
236  // wrap pointer, make sure any exit cleans up reply
237  // required to use delete later as object is created in a different thread
238  QScopedPointer<QNetworkReply, QScopedPointerDeleteLater> nwReply(nwReplyPtr);
239  if (!this->doWorkCheck()) { return; }
240 
241  const CDatabaseReader::JsonDatastoreResponse res =
243  const QUrl url = nwReply->url();
244 
245  if (res.hasErrorMessage())
246  {
247  CLogMessage::preformatted(res.lastWarningOrAbove());
248  emit this->dataRead(CEntityFlags::AircraftIcaoEntity, CEntityFlags::ReadFailed, 0, url);
249  return;
250  }
251 
252  emit this->dataRead(CEntityFlags::AircraftIcaoEntity, CEntityFlags::ReadParsing, 0, url);
253  CAircraftIcaoCodeList codes;
254  CAircraftIcaoCodeList inconsistent;
255  const CAircraftCategoryList categories = this->getAircraftCategories();
256  if (res.isRestricted())
257  {
258  // create full list if it was just incremental
259  const CAircraftIcaoCodeList incrementalCodes(
260  CAircraftIcaoCodeList::fromDatabaseJson(res, categories, true, &inconsistent));
261  if (incrementalCodes.isEmpty()) { return; } // currently ignored
262  codes = this->getAircraftIcaoCodes();
263  codes.replaceOrAddObjectsByKey(incrementalCodes);
264  }
265  else
266  {
267  // normally read from special DB view which already filters incomplete
268  QElapsedTimer time;
269  time.start();
270  codes = CAircraftIcaoCodeList::fromDatabaseJson(res, categories, true, &inconsistent);
271  this->logParseMessage("aircraft ICAO", codes.size(), static_cast<int>(time.elapsed()), res);
272  }
273 
274  if (!inconsistent.isEmpty())
275  {
277  u"Inconsistent aircraft codes: " % inconsistent.dbKeysAsString(", ")),
278  Q_FUNC_INFO);
279  }
280 
281  if (!this->doWorkCheck()) { return; }
282  const int n = codes.size();
283  qint64 latestTimestamp = codes.latestTimestampMsecsSinceEpoch(); // ignores duplicates
284  if (n > 0 && latestTimestamp < 0)
285  {
286  CLogMessage(this).error(u"No timestamp in aircraft ICAO list, setting to last modified value");
287  latestTimestamp = this->lastModifiedMsSinceEpoch(nwReply.data());
288  }
289 
290  m_aircraftIcaoCache.set(codes, latestTimestamp);
291  this->updateReaderUrl(this->getBaseUrl(CDbFlags::DbReading));
292 
293  this->emitAndLogDataRead(CEntityFlags::AircraftIcaoEntity, n, res);
294  }
295 
296  void CIcaoDataReader::parseAirlineIcaoData(QNetworkReply *nwReplyPtr)
297  {
298  QScopedPointer<QNetworkReply, QScopedPointerDeleteLater> nwReply(nwReplyPtr);
299  if (!this->doWorkCheck()) { return; }
300 
301  const QUrl url = nwReply->url();
302  const CDatabaseReader::JsonDatastoreResponse res =
304  if (res.hasErrorMessage())
305  {
306  CLogMessage::preformatted(res.lastWarningOrAbove());
307  emit this->dataRead(CEntityFlags::AirlineIcaoEntity, CEntityFlags::ReadFailed, 0, url);
308  return;
309  }
310 
311  emit this->dataRead(CEntityFlags::AirlineIcaoEntity, CEntityFlags::ReadParsing, 0, url);
312  CAirlineIcaoCodeList codes;
313  CAirlineIcaoCodeList inconsistent;
314  if (res.isRestricted())
315  {
316  // create full list if it was just incremental
317  const CAirlineIcaoCodeList incrementalCodes(
318  CAirlineIcaoCodeList::fromDatabaseJson(res, true, &inconsistent));
319  if (incrementalCodes.isEmpty()) { return; } // currently ignored
320  codes = this->getAirlineIcaoCodes();
321  codes.replaceOrAddObjectsByKey(incrementalCodes);
322  }
323  else
324  {
325  // normally read from special DB view which already filters incomplete
326  QElapsedTimer time;
327  time.start();
328  codes = CAirlineIcaoCodeList::fromDatabaseJson(res, true, &inconsistent);
329  this->logParseMessage("airline ICAO", codes.size(), static_cast<int>(time.elapsed()), res);
330  }
331 
332  if (!inconsistent.isEmpty())
333  {
335  u"Inconsistent airline codes: " % inconsistent.dbKeysAsString(", ")),
336  Q_FUNC_INFO);
337  }
338 
339  if (!this->doWorkCheck()) { return; }
340  const int n = codes.size();
341  qint64 latestTimestamp = codes.latestTimestampMsecsSinceEpoch();
342  if (n > 0 && latestTimestamp < 0)
343  {
344  CLogMessage(this).error(u"No timestamp in airline ICAO list, setting to last modified value");
345  latestTimestamp = this->lastModifiedMsSinceEpoch(nwReply.data());
346  }
347 
348  m_airlineIcaoCache.set(codes, latestTimestamp);
349  this->updateReaderUrl(this->getBaseUrl(CDbFlags::DbReading));
350 
351  this->emitAndLogDataRead(CEntityFlags::AirlineIcaoEntity, n, res);
352  }
353 
354  void CIcaoDataReader::parseCountryData(QNetworkReply *nwReplyPtr)
355  {
356  QScopedPointer<QNetworkReply, QScopedPointerDeleteLater> nwReply(nwReplyPtr);
357  const CDatabaseReader::JsonDatastoreResponse res =
359  const QUrl url = nwReply->url();
360 
361  if (res.hasErrorMessage())
362  {
363  CLogMessage::preformatted(res.lastWarningOrAbove());
364  emit this->dataRead(CEntityFlags::CountryEntity, CEntityFlags::ReadFailed, 0, url);
365  return;
366  }
367 
368  emit this->dataRead(CEntityFlags::CountryEntity, CEntityFlags::ReadParsing, 0, url);
369  CCountryList countries;
370  if (res.isRestricted())
371  {
372  // create full list if it was just incremental
373  const CCountryList incrementalCountries(CCountryList::fromDatabaseJson(res));
374  if (incrementalCountries.isEmpty()) { return; } // currently ignored
375  countries = this->getCountries();
376  countries.replaceOrAddObjectsByKey(incrementalCountries);
377  }
378  else
379  {
380  // normally read from special DB view which already filters incomplete
381  QElapsedTimer time;
382  time.start();
383  countries = CCountryList::fromDatabaseJson(res);
384  this->logParseMessage("countries", countries.size(), static_cast<int>(time.elapsed()), res);
385  }
386 
387  if (!this->doWorkCheck()) { return; }
388  const int n = countries.size();
389  qint64 latestTimestamp = countries.latestTimestampMsecsSinceEpoch();
390  if (n > 0 && latestTimestamp < 0)
391  {
392  CLogMessage(this).error(u"No timestamp in country list, setting to last modified value");
393  latestTimestamp = this->lastModifiedMsSinceEpoch(nwReply.data());
394  }
395 
396  m_countryCache.set(countries, latestTimestamp);
397  this->updateReaderUrl(this->getBaseUrl(CDbFlags::DbReading));
398 
399  this->emitAndLogDataRead(CEntityFlags::CountryEntity, n, res);
400  }
401 
402  void CIcaoDataReader::parseAircraftCategoryData(QNetworkReply *nwReplyPtr)
403  {
404  QScopedPointer<QNetworkReply, QScopedPointerDeleteLater> nwReply(nwReplyPtr);
405  const CDatabaseReader::JsonDatastoreResponse res =
407  const QUrl url = nwReply->url();
408 
409  if (res.hasErrorMessage())
410  {
411  CLogMessage::preformatted(res.lastWarningOrAbove());
412  emit this->dataRead(CEntityFlags::AircraftCategoryEntity, CEntityFlags::ReadFailed, 0, url);
413  return;
414  }
415 
416  emit this->dataRead(CEntityFlags::AircraftCategoryEntity, CEntityFlags::ReadParsing, 0, url);
417  CAircraftCategoryList categories;
418  if (res.isRestricted())
419  {
420  // create full list if it was just incremental
421  const CAircraftCategoryList incrementalCategories(CAircraftCategoryList::fromDatabaseJson(res));
422  if (incrementalCategories.isEmpty()) { return; } // currently ignored
423  categories = this->getAircraftCategories();
424  categories.replaceOrAddObjectsByKey(incrementalCategories);
425  }
426  else
427  {
428  // normally read from special DB view which already filters incomplete
429  QElapsedTimer time;
430  time.start();
431  categories = CAircraftCategoryList::fromDatabaseJson(res);
432  this->logParseMessage("categories", categories.size(), static_cast<int>(time.elapsed()), res);
433  }
434 
435  if (!this->doWorkCheck()) { return; }
436  const int n = categories.size();
437  qint64 latestTimestamp = categories.latestTimestampMsecsSinceEpoch();
438  if (n > 0 && latestTimestamp < 0)
439  {
440  CLogMessage(this).error(u"No timestamp in category list, setting to last modified value");
441  latestTimestamp = this->lastModifiedMsSinceEpoch(nwReply.data());
442  }
443 
444  m_categoryCache.set(categories, latestTimestamp);
445  this->updateReaderUrl(this->getBaseUrl(CDbFlags::DbReading));
446 
447  this->emitAndLogDataRead(CEntityFlags::AircraftCategoryEntity, n, res);
448  }
449 
450  CStatusMessageList CIcaoDataReader::readFromJsonFiles(const QString &dir, CEntityFlags::Entity whatToRead,
451  bool overrideNewerOnly)
452  {
453  const QDir directory(dir);
454  if (!directory.exists()) { return CStatusMessage(this).error(u"Missing directory '%1'") << dir; }
455 
456  // Hint: Do not emit while locked -> deadlock
457  CStatusMessageList msgs;
458  whatToRead &= CEntityFlags::AllIcaoCountriesCategory;
459  CEntityFlags::Entity reallyRead = CEntityFlags::NoEntity;
460  if (whatToRead.testFlag(CEntityFlags::CountryEntity))
461  {
462  const QString fileName = CFileUtils::appendFilePaths(
463  directory.absolutePath(), CDbInfo::entityToSharedName(CEntityFlags::CountryEntity));
464  const QFileInfo fi(fileName);
465  if (!fi.exists()) { msgs.push_back(CStatusMessage(this).warning(u"File '%1' does not exist") << fileName); }
466  else if (!this->overrideCacheFromFile(overrideNewerOnly, fi, CEntityFlags::CountryEntity, msgs))
467  {
468  // void
469  }
470  else
471  {
472  const QUrl url = QUrl::fromLocalFile(fi.absoluteFilePath());
473  const QJsonObject countriesJson(CDatabaseUtils::readQJsonObjectFromDatabaseFile(fileName));
474  if (countriesJson.isEmpty())
475  {
476  msgs.push_back(CStatusMessage(this).error(u"Failed to read from file/empty file '%1'") << fileName);
477  }
478  else
479  {
480  try
481  {
482  const CCountryList countries = CCountryList::fromMultipleJsonFormats(countriesJson);
483  const int c = countries.size();
484  msgs.push_back(m_countryCache.set(countries, fi.birthTime().toUTC().toMSecsSinceEpoch()));
485  reallyRead |= CEntityFlags::CountryEntity;
486  emit this->dataRead(CEntityFlags::CountryEntity, CEntityFlags::ReadFinished, c, url);
487  }
488  catch (const CJsonException &ex)
489  {
490  emit this->dataRead(CEntityFlags::CountryEntity, CEntityFlags::ReadFailed, 0, url);
492  ex, this, QStringLiteral("Reading countries from '%1'").arg(fileName)));
493  }
494  }
495  }
496  } // country
497 
498  if (whatToRead.testFlag(CEntityFlags::AircraftIcaoEntity))
499  {
500  const QString fileName = CFileUtils::appendFilePaths(
501  directory.absolutePath(), CDbInfo::entityToSharedName(CEntityFlags::AircraftIcaoEntity));
502  const QFileInfo fi(fileName);
503  if (!fi.exists()) { msgs.push_back(CStatusMessage(this).warning(u"File '%1' does not exist") << fileName); }
504  else if (!this->overrideCacheFromFile(overrideNewerOnly, fi, CEntityFlags::AircraftIcaoEntity, msgs))
505  {
506  // void
507  }
508  else
509  {
510  const QUrl url = QUrl::fromLocalFile(fi.absoluteFilePath());
511  const QJsonObject aircraftJson(CDatabaseUtils::readQJsonObjectFromDatabaseFile(fileName));
512  if (aircraftJson.isEmpty())
513  {
514  msgs.push_back(CStatusMessage(this).error(u"Failed to read from file/empty file '%1'") << fileName);
515  }
516  else
517  {
518  try
519  {
520  const CAircraftIcaoCodeList aircraftIcaos =
521  CAircraftIcaoCodeList::fromMultipleJsonFormats(aircraftJson);
522  const int c = aircraftIcaos.size();
523  msgs.push_back(
524  m_aircraftIcaoCache.set(aircraftIcaos, fi.birthTime().toUTC().toMSecsSinceEpoch()));
525  reallyRead |= CEntityFlags::AircraftIcaoEntity;
526  emit this->dataRead(CEntityFlags::AircraftIcaoEntity, CEntityFlags::ReadFinished, c, url);
527  }
528  catch (const CJsonException &ex)
529  {
530  emit this->dataRead(CEntityFlags::AircraftIcaoEntity, CEntityFlags::ReadFailed, 0, url);
532  ex, this, QStringLiteral("Reading aircraft ICAOs from '%1'").arg(fileName)));
533  }
534  }
535  }
536  } // aircraft
537 
538  if (whatToRead.testFlag(CEntityFlags::AirlineIcaoEntity))
539  {
540  const QString fileName = CFileUtils::appendFilePaths(
541  directory.absolutePath(), CDbInfo::entityToSharedName(CEntityFlags::AirlineIcaoEntity));
542  const QFileInfo fi(fileName);
543  if (!fi.exists()) { msgs.push_back(CStatusMessage(this).warning(u"File '%1' does not exist") << fileName); }
544  else if (!this->overrideCacheFromFile(overrideNewerOnly, fi, CEntityFlags::AirlineIcaoEntity, msgs))
545  {
546  // void
547  }
548  else
549  {
550  const QUrl url = QUrl::fromLocalFile(fi.absoluteFilePath());
551  const QJsonObject airlineJson(CDatabaseUtils::readQJsonObjectFromDatabaseFile(fileName));
552  if (airlineJson.isEmpty())
553  {
554  msgs.push_back(CStatusMessage(this).error(u"Failed to read from file/empty file '%1'") << fileName);
555  }
556  else
557  {
558  try
559  {
560  const CAirlineIcaoCodeList airlineIcaos =
561  CAirlineIcaoCodeList::fromMultipleJsonFormats(airlineJson);
562  const int c = airlineIcaos.size();
563  msgs.push_back(
564  m_airlineIcaoCache.set(airlineIcaos, fi.birthTime().toUTC().toMSecsSinceEpoch()));
565  reallyRead |= CEntityFlags::AirlineIcaoEntity;
566  emit this->dataRead(CEntityFlags::AirlineIcaoEntity, CEntityFlags::ReadFinished, c, url);
567  }
568  catch (const CJsonException &ex)
569  {
570  emit this->dataRead(CEntityFlags::AirlineIcaoEntity, CEntityFlags::ReadFailed, 0, url);
572  ex, this, QStringLiteral("Reading airline ICAOs from '%1'").arg(fileName)));
573  }
574  }
575  }
576  } // airline
577 
578  if (whatToRead.testFlag(CEntityFlags::AircraftCategoryEntity))
579  {
580  const QString fileName = CFileUtils::appendFilePaths(
581  directory.absolutePath(), CDbInfo::entityToSharedName(CEntityFlags::AircraftCategoryEntity));
582  const QFileInfo fi(fileName);
583  if (!fi.exists()) { msgs.push_back(CStatusMessage(this).warning(u"File '%1' does not exist") << fileName); }
584  else if (!this->overrideCacheFromFile(overrideNewerOnly, fi, CEntityFlags::AircraftCategoryEntity, msgs))
585  {
586  // void
587  }
588  else
589  {
590  const QUrl url = QUrl::fromLocalFile(fi.absoluteFilePath());
591  const QJsonObject aircraftCategory(CDatabaseUtils::readQJsonObjectFromDatabaseFile(fileName));
592  if (aircraftCategory.isEmpty())
593  {
594  msgs.push_back(CStatusMessage(this).error(u"Failed to read from file/empty file '%1'") << fileName);
595  }
596  else
597  {
598  try
599  {
600  const CAircraftCategoryList aircraftCategories =
601  CAircraftCategoryList::fromMultipleJsonFormats(aircraftCategory);
602  const int c = aircraftCategories.size();
603  msgs.push_back(
604  m_categoryCache.set(aircraftCategories, fi.birthTime().toUTC().toMSecsSinceEpoch()));
605  reallyRead |= CEntityFlags::AircraftCategoryEntity;
606  emit this->dataRead(CEntityFlags::AircraftCategoryEntity, CEntityFlags::ReadFinished, c, url);
607  }
608  catch (const CJsonException &ex)
609  {
610  emit this->dataRead(CEntityFlags::AircraftCategoryEntity, CEntityFlags::ReadFailed, 0, url);
612  ex, this, QStringLiteral("Reading categories from '%1'").arg(fileName)));
613  }
614  }
615  }
616  } // categories
617 
618  return msgs;
619  }
620 
621  bool CIcaoDataReader::readFromJsonFilesInBackground(const QString &dir, CEntityFlags::Entity whatToRead,
622  bool overrideNewerOnly)
623  {
624  if (dir.isEmpty() || whatToRead == CEntityFlags::NoEntity) { return false; }
625 
626  QPointer<CIcaoDataReader> myself(this);
627  QTimer::singleShot(0, this, [=]() {
628  if (!myself) { return; }
629  const CStatusMessageList msgs = this->readFromJsonFiles(dir, whatToRead, overrideNewerOnly);
630  if (msgs.isFailure()) { CLogMessage::preformatted(msgs); }
631  });
632  return true;
633  }
634 
635  bool CIcaoDataReader::writeToJsonFiles(const QString &dir)
636  {
637  QDir directory(dir);
638  if (!directory.exists()) { return false; }
639  QList<QPair<CEntityFlags::EntityFlag, QString>> fileContents;
640  if (this->getCountriesCount() > 0)
641  {
642  const QString json(QJsonDocument(this->getCountries().toJson()).toJson());
643  fileContents.push_back({ CEntityFlags::CountryEntity, json });
644  }
645 
646  if (this->getAircraftIcaoCodesCount() > 0)
647  {
648  const QString json(QJsonDocument(this->getAircraftIcaoCodes().toJson()).toJson());
649  fileContents.push_back({ CEntityFlags::AircraftIcaoEntity, json });
650  }
651 
652  if (this->getAirlineIcaoCodesCount() > 0)
653  {
654  const QString json(QJsonDocument(this->getAirlineIcaoCodes().toJson()).toJson());
655  fileContents.push_back({ CEntityFlags::AirlineIcaoEntity, json });
656  }
657 
658  if (this->getAircraftCategoryCount() > 0)
659  {
660  const QString json(QJsonDocument(this->getAirlineIcaoCodes().toJson()).toJson());
661  fileContents.push_back({ CEntityFlags::AircraftCategoryEntity, json });
662  }
663 
664  for (const auto &pair : fileContents)
665  {
666  CWorker::fromTask(this, Q_FUNC_INFO, [pair, directory] {
668  CFileUtils::appendFilePaths(directory.absolutePath(), CDbInfo::entityToSharedName(pair.first)),
669  pair.second);
670  });
671  }
672  return true;
673  }
674 
675  CEntityFlags::Entity CIcaoDataReader::getSupportedEntities() const
676  {
677  return CEntityFlags::AllIcaoCountriesCategory;
678  }
679 
680  void CIcaoDataReader::synchronizeCaches(CEntityFlags::Entity entities)
681  {
682  if (entities.testFlag(CEntityFlags::AircraftIcaoEntity))
683  {
684  if (m_syncedAircraftIcaoCache) { return; }
685  m_syncedAircraftIcaoCache = true;
686  m_aircraftIcaoCache.synchronize();
687  }
688  if (entities.testFlag(CEntityFlags::AirlineIcaoEntity))
689  {
690  if (m_syncedAirlineIcaoCache) { return; }
691  m_syncedAirlineIcaoCache = true;
692  m_airlineIcaoCache.synchronize();
693  }
694  if (entities.testFlag(CEntityFlags::CountryEntity))
695  {
696  if (m_syncedCountryCache) { return; }
697  m_syncedCountryCache = true;
698  m_countryCache.synchronize();
699  }
700  if (entities.testFlag(CEntityFlags::AircraftCategoryEntity))
701  {
702  if (m_syncedCategories) { return; }
703  m_syncedCategories = true;
704  m_categoryCache.synchronize();
705  }
706  }
707 
708  void CIcaoDataReader::admitCaches(CEntityFlags::Entity entities)
709  {
710  if (entities.testFlag(CEntityFlags::AircraftIcaoEntity)) { m_aircraftIcaoCache.admit(); }
711  if (entities.testFlag(CEntityFlags::AirlineIcaoEntity)) { m_airlineIcaoCache.admit(); }
712  if (entities.testFlag(CEntityFlags::CountryEntity)) { m_countryCache.admit(); }
713  if (entities.testFlag(CEntityFlags::AircraftCategoryEntity)) { m_categoryCache.admit(); }
714  }
715 
716  void CIcaoDataReader::invalidateCaches(CEntityFlags::Entity entities)
717  {
718  if (entities.testFlag(CEntityFlags::AircraftIcaoEntity))
719  {
720  CDataCache::instance()->clearAllValues(m_aircraftIcaoCache.getKey());
721  }
722  if (entities.testFlag(CEntityFlags::AirlineIcaoEntity))
723  {
724  CDataCache::instance()->clearAllValues(m_airlineIcaoCache.getKey());
725  }
726  if (entities.testFlag(CEntityFlags::CountryEntity))
727  {
728  CDataCache::instance()->clearAllValues(m_countryCache.getKey());
729  }
730  if (entities.testFlag(CEntityFlags::AircraftCategoryEntity))
731  {
732  CDataCache::instance()->clearAllValues(m_categoryCache.getKey());
733  }
734  }
735 
736  QDateTime CIcaoDataReader::getCacheTimestamp(CEntityFlags::Entity entity) const
737  {
738  switch (entity)
739  {
740  case CEntityFlags::AircraftIcaoEntity: return m_aircraftIcaoCache.getAvailableTimestamp();
741  case CEntityFlags::AirlineIcaoEntity: return m_airlineIcaoCache.getAvailableTimestamp();
742  case CEntityFlags::CountryEntity: return m_countryCache.getAvailableTimestamp();
743  case CEntityFlags::AircraftCategoryEntity: return m_categoryCache.getAvailableTimestamp();
744  default: return QDateTime();
745  }
746  }
747 
748  int CIcaoDataReader::getCacheCount(CEntityFlags::Entity entity) const
749  {
750  switch (entity)
751  {
752  case CEntityFlags::AircraftIcaoEntity: return m_aircraftIcaoCache.get().size();
753  case CEntityFlags::AirlineIcaoEntity: return m_airlineIcaoCache.get().size();
754  case CEntityFlags::CountryEntity: return m_countryCache.get().size();
755  case CEntityFlags::AircraftCategoryEntity: return m_categoryCache.get().size();
756  default: return 0;
757  }
758  }
759 
760  CEntityFlags::Entity CIcaoDataReader::getEntitiesWithCacheCount() const
761  {
762  CEntityFlags::Entity entities = CEntityFlags::NoEntity;
763  if (this->getCacheCount(CEntityFlags::AircraftIcaoEntity) > 0) entities |= CEntityFlags::AircraftIcaoEntity;
764  if (this->getCacheCount(CEntityFlags::AirlineIcaoEntity) > 0) entities |= CEntityFlags::AirlineIcaoEntity;
765  if (this->getCacheCount(CEntityFlags::CountryEntity) > 0) entities |= CEntityFlags::CountryEntity;
766  if (this->getCacheCount(CEntityFlags::AircraftCategoryEntity) > 0)
767  entities |= CEntityFlags::AircraftCategoryEntity;
768  return entities;
769  }
770 
771  CEntityFlags::Entity CIcaoDataReader::getEntitiesWithCacheTimestampNewerThan(const QDateTime &threshold) const
772  {
773  CEntityFlags::Entity entities = CEntityFlags::NoEntity;
774  if (this->hasCacheTimestampNewerThan(CEntityFlags::AircraftIcaoEntity, threshold))
775  entities |= CEntityFlags::AircraftIcaoEntity;
776  if (this->hasCacheTimestampNewerThan(CEntityFlags::AirlineIcaoEntity, threshold))
777  entities |= CEntityFlags::AirlineIcaoEntity;
778  if (this->hasCacheTimestampNewerThan(CEntityFlags::CountryEntity, threshold))
779  entities |= CEntityFlags::CountryEntity;
780  if (this->hasCacheTimestampNewerThan(CEntityFlags::AircraftCategoryEntity, threshold))
781  entities |= CEntityFlags::CountryEntity;
782  return entities;
783  }
784 
785  bool CIcaoDataReader::hasChangedUrl(CEntityFlags::Entity entity, CUrl &oldUrlInfo, CUrl &newUrlInfo) const
786  {
787  Q_UNUSED(entity)
788  oldUrlInfo = m_readerUrlCache.get();
789  newUrlInfo = this->getBaseUrl(CDbFlags::DbReading);
790  return CDatabaseReader::isChangedUrl(oldUrlInfo, newUrlInfo);
791  }
792 
794 
795  CUrl CIcaoDataReader::getAircraftIcaoUrl(CDbFlags::DataRetrievalModeFlag mode) const
796  {
797  return this->getBaseUrl(mode).withAppendedPath(fileNameForMode(CEntityFlags::AircraftIcaoEntity, mode));
798  }
799 
800  CUrl CIcaoDataReader::getAirlineIcaoUrl(CDbFlags::DataRetrievalModeFlag mode) const
801  {
802  return this->getBaseUrl(mode).withAppendedPath(fileNameForMode(CEntityFlags::AirlineIcaoEntity, mode));
803  }
804 
805  CUrl CIcaoDataReader::getCountryUrl(CDbFlags::DataRetrievalModeFlag mode) const
806  {
807  return this->getBaseUrl(mode).withAppendedPath(fileNameForMode(CEntityFlags::CountryEntity, mode));
808  }
809 
810  CUrl CIcaoDataReader::getAircraftCategoryUrl(CDbFlags::DataRetrievalModeFlag mode) const
811  {
812  return this->getBaseUrl(mode).withAppendedPath(fileNameForMode(CEntityFlags::AircraftCategoryEntity, mode));
813  }
814 } // 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 everything 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:225
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