Files
pilotclient/src/blackcore/db/airportdatareader.cpp
Lars Toenning b4cbed107b refactor: Remove CNetworkWatchdog
The watchdog was used in a few places as a shortcut to skip reading
data. Further, it was used in some places in the UI to display
connectivity. But it also introduced quite some complexity. In some
cases it can be fragile: network accessibilty cannot be looked up on all
platforms/hardware constellations. The connectivity could change
between the last watchdog call and the real call. Hence all readers must
still handle the case where the connection fails.
To simplify swift and further reduce the dependency onto the project
infrastructure (pings etc.), this removes the watchdog.
This also removes the QNetworkConfigurationManager, which is deprecated
and not available with Qt6.
2024-04-15 22:02:11 +02:00

285 lines
10 KiB
C++

// SPDX-FileCopyrightText: Copyright (C) 2016 swift Project Community / Contributors
// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-swift-pilot-client-1
#include "blackcore/db/airportdatareader.h"
#include "blackcore/db/databaseutils.h"
#include "blackcore/application.h"
#include "blackmisc/network/networkutils.h"
#include "blackmisc/logmessage.h"
#include <QStringBuilder>
#include <QNetworkReply>
#include <QElapsedTimer>
#include <QFileInfo>
#include <QPointer>
using namespace BlackMisc;
using namespace BlackMisc::Aviation;
using namespace BlackMisc::Network;
using namespace BlackMisc::Db;
namespace BlackCore::Db
{
CAirportDataReader::CAirportDataReader(QObject *parent, const CDatabaseReaderConfigList &config) : CDatabaseReader(parent, config, QStringLiteral("CAirportDataReader"))
{
// void
}
BlackMisc::Aviation::CAirportList CAirportDataReader::getAirports() const
{
return m_airportCache.get();
}
CAirport CAirportDataReader::getAirportForIcaoDesignator(const QString &designator) const
{
return this->getAirports().findFirstByIcao(CAirportIcaoCode(designator));
}
CAirport CAirportDataReader::getAirportForNameOrLocation(const QString &nameOrLocation) const
{
return this->getAirports().findFirstByNameOrLocation(nameOrLocation);
}
int CAirportDataReader::getAirportsCount() const
{
return this->getAirports().size();
}
bool CAirportDataReader::readFromJsonFilesInBackground(const QString &dir, CEntityFlags::Entity whatToRead, bool overrideNewerOnly)
{
if (dir.isEmpty() || whatToRead == CEntityFlags::NoEntity) { return false; }
QPointer<CAirportDataReader> myself(this);
QTimer::singleShot(0, this, [=]() {
if (!myself) { return; }
const CStatusMessageList msgs = this->readFromJsonFiles(dir, whatToRead, overrideNewerOnly);
if (msgs.isFailure())
{
CLogMessage::preformatted(msgs);
}
});
return true;
}
CStatusMessageList CAirportDataReader::readFromJsonFiles(const QString &dir, CEntityFlags::Entity whatToRead, bool overrideNewerOnly)
{
const QDir directory(dir);
if (!directory.exists())
{
return CStatusMessage(this).error(u"Missing directory '%1'") << dir;
}
whatToRead &= CEntityFlags::AirportEntity; // can handle these entities
if (whatToRead == CEntityFlags::NoEntity)
{
return CStatusMessage(this).info(u"'%1' No entity for this reader") << CEntityFlags::flagToString(whatToRead);
}
int c = 0;
CEntityFlags::Entity reallyRead = CEntityFlags::NoEntity;
CStatusMessageList msgs;
const QString fileName = CFileUtils::appendFilePaths(dir, "airports.json");
const QFileInfo fi(fileName);
if (!fi.exists())
{
msgs.push_back(CStatusMessage(this).warning(u"File '%1' does not exist") << fileName);
}
else if (!this->overrideCacheFromFile(overrideNewerOnly, fi, CEntityFlags::AirportEntity, msgs))
{
// void
}
else
{
const QUrl url = QUrl::fromLocalFile(fi.absoluteFilePath());
const QJsonObject airportsJson(CDatabaseUtils::readQJsonObjectFromDatabaseFile(fileName));
if (!airportsJson.isEmpty())
{
try
{
const CAirportList airports = CAirportList::fromMultipleJsonFormats(airportsJson);
c = airports.size();
msgs.push_back(m_airportCache.set(airports, fi.birthTime().toUTC().toMSecsSinceEpoch()));
emit dataRead(CEntityFlags::AirportEntity, CEntityFlags::ReadFinished, c, url);
reallyRead |= CEntityFlags::AirportEntity;
}
catch (const CJsonException &ex)
{
emit dataRead(CEntityFlags::AirportEntity, CEntityFlags::ReadFailed, 0, url);
return CStatusMessage::fromJsonException(ex, this, QStringLiteral("Reading airports from '%1'").arg(fileName));
}
}
}
return msgs;
}
CEntityFlags::Entity CAirportDataReader::getSupportedEntities() const
{
return CEntityFlags::AirportEntity;
}
QDateTime CAirportDataReader::getCacheTimestamp(CEntityFlags::Entity entities) const
{
return entities == CEntityFlags::AirportEntity ? m_airportCache.getAvailableTimestamp() : QDateTime();
}
int CAirportDataReader::getCacheCount(CEntityFlags::Entity entity) const
{
return entity == CEntityFlags::AirportEntity ? m_airportCache.get().size() : 0;
}
CEntityFlags::Entity CAirportDataReader::getEntitiesWithCacheCount() const
{
CEntityFlags::Entity entities = CEntityFlags::NoEntity;
if (this->getCacheCount(CEntityFlags::AirportEntity) > 0) { entities |= CEntityFlags::AirportEntity; }
return entities;
}
CEntityFlags::Entity CAirportDataReader::getEntitiesWithCacheTimestampNewerThan(const QDateTime &threshold) const
{
CEntityFlags::Entity entities = CEntityFlags::NoEntity;
if (this->hasCacheTimestampNewerThan(CEntityFlags::AirportEntity, threshold)) { entities |= CEntityFlags::AirportEntity; }
return entities;
}
void CAirportDataReader::synchronizeCaches(CEntityFlags::Entity entities)
{
if (entities.testFlag(CEntityFlags::AirportEntity))
{
if (m_syncedAirportCache) { return; }
m_syncedAirportCache = true;
m_airportCache.synchronize();
}
}
void CAirportDataReader::admitCaches(CEntityFlags::Entity entities)
{
if (entities.testFlag(CEntityFlags::AirportEntity)) { m_airportCache.admit(); }
}
void CAirportDataReader::invalidateCaches(CEntityFlags::Entity entities)
{
if (entities.testFlag(CEntityFlags::AirportEntity)) { CDataCache::instance()->clearAllValues(m_airportCache.getKey()); }
}
bool CAirportDataReader::hasChangedUrl(CEntityFlags::Entity entity, CUrl &oldUrlInfo, CUrl &newUrlInfo) const
{
Q_UNUSED(entity)
oldUrlInfo = m_readerUrlCache.get();
newUrlInfo = this->getBaseUrl(CDbFlags::DbReading);
return CDatabaseReader::isChangedUrl(oldUrlInfo, newUrlInfo);
}
CUrl CAirportDataReader::getDbServiceBaseUrl() const
{
return sApp->getGlobalSetup().getDbAirportReaderUrl();
}
CUrl CAirportDataReader::getAirportsUrl(CDbFlags::DataRetrievalModeFlag mode) const
{
return this->getBaseUrl(mode).withAppendedPath(fileNameForMode(CEntityFlags::AirportEntity, mode));
}
void CAirportDataReader::parseAirportData(QNetworkReply *nwReplyPtr)
{
// wrap pointer, make sure any exit cleans up reply
// required to use delete later as object is created in a different thread
QScopedPointer<QNetworkReply, QScopedPointerDeleteLater> nwReply(nwReplyPtr);
if (!this->doWorkCheck()) { return; }
const CDatabaseReader::JsonDatastoreResponse res = this->setStatusAndTransformReplyIntoDatastoreResponse(nwReplyPtr);
const QUrl url = nwReply->url();
if (res.hasErrorMessage())
{
CLogMessage::preformatted(res.lastWarningOrAbove());
emit this->dataRead(CEntityFlags::AirportEntity, CEntityFlags::ReadFailed, 0, url);
return;
}
// parsing
emit this->dataRead(CEntityFlags::AirportEntity, CEntityFlags::ReadParsing, 0, url);
CAirportList airports;
CAirportList inconsistent;
if (res.isRestricted())
{
const CAirportList incrementalAirports(CAirportList::fromDatabaseJson(res, &inconsistent));
if (incrementalAirports.isEmpty()) { return; } // currently ignored
airports = this->getAirports();
airports.replaceOrAddObjectsByKey(incrementalAirports);
}
else
{
QElapsedTimer time;
time.start();
airports = CAirportList::fromDatabaseJson(res, &inconsistent);
this->logParseMessage("airports", airports.size(), static_cast<int>(time.elapsed()), res);
}
if (!inconsistent.isEmpty())
{
logInconsistentData(
CStatusMessage(this, CStatusMessage::SeverityInfo, u"Inconsistent airports: " % inconsistent.dbKeysAsString(", ")),
Q_FUNC_INFO);
}
if (!this->doWorkCheck()) { return; }
const int size = airports.size();
qint64 latestTimestamp = airports.latestTimestampMsecsSinceEpoch();
if (size > 0 && latestTimestamp < 0)
{
CLogMessage(this).error(u"No timestamp in airport list, setting to last modified value");
latestTimestamp = lastModifiedMsSinceEpoch(nwReply.data());
}
m_airportCache.set(airports, latestTimestamp);
this->updateReaderUrl(getBaseUrl(CDbFlags::DbReading));
this->emitAndLogDataRead(CEntityFlags::AirportEntity, size, res);
}
void CAirportDataReader::read(CEntityFlags::Entity entity, CDbFlags::DataRetrievalModeFlag mode, const QDateTime &newerThan)
{
this->threadAssertCheck();
if (!this->doWorkCheck()) { return; }
entity &= CEntityFlags::AirportEntity;
if (entity.testFlag(CEntityFlags::AirportEntity))
{
CUrl url = this->getAirportsUrl(mode);
if (!url.isEmpty())
{
url.appendQuery(queryLatestTimestamp(newerThan));
this->getFromNetworkAndLog(url, { this, &CAirportDataReader::parseAirportData });
emit dataRead(CEntityFlags::AirportEntity, CEntityFlags::ReadStarted, 0, url);
}
else
{
this->logNoWorkingUrl(CEntityFlags::AirportEntity);
}
}
}
void CAirportDataReader::airportCacheChanged()
{
this->cacheHasChanged(CEntityFlags::AirportEntity);
}
void CAirportDataReader::baseUrlCacheChanged()
{
// void
}
void CAirportDataReader::updateReaderUrl(const CUrl &url)
{
const CUrl current = m_readerUrlCache.get();
if (current == url) { return; }
const CStatusMessage m = m_readerUrlCache.set(url);
if (m.isFailure())
{
CLogMessage::preformatted(m);
}
}
} // ns