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 
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 
86  {
87  return this->getCountries().findByIsoCode(isoCode);
88  }
89 
91  {
92  return this->getCountries().findBestMatchByCountryName(name);
93  }
94 
96  {
97  return this->getAirlineIcaoCodes().findByVDesignator(designator);
98  }
99 
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
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  {
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  {
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  {
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());
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 
636  {
637  QDir directory(dir);
638  if (!directory.exists()) { return false; }
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.
qint64 toMSecsSinceEpoch() const const
QDateTime toUTC() const const
QString absolutePath() const const
bool exists() const const
qint64 elapsed() const const
QString absoluteFilePath() const const
QDateTime birthTime() const const
bool exists(const QString &path)
bool isEmpty() const const
void push_back(QList< T >::parameter_type value)
bool isEmpty() const const
QUrl fromLocalFile(const QString &localFile)
QString url(QUrl::FormattingOptions options) const const