Files
pilotclient/src/blackcore/db/databasereader.cpp
2016-06-08 18:20:35 +02:00

270 lines
11 KiB
C++

/* Copyright (C) 2015
* swift project Community / Contributors
*
* This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level
* directory of this distribution and at http://www.swift-project.org/license.html. No part of swift project,
* including this file, may be copied, modified, propagated, or distributed except according to the terms
* contained in the LICENSE file.
*/
#include "blackcore/db/databasereader.h"
#include "blackcore/db/infodatareader.h"
#include "blackcore/webdataservices.h"
#include "blackcore/application.h"
#include "blackmisc/db/datastoreutility.h"
#include "blackmisc/network/networkutils.h"
#include "blackmisc/logcategory.h"
#include "blackmisc/logcategorylist.h"
#include "blackmisc/logmessage.h"
#include <QByteArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonValueRef>
#include <QMetaObject>
#include <QNetworkReply>
#include <QReadLocker>
#include <QUrl>
#include <QWriteLocker>
using namespace BlackMisc;
using namespace BlackMisc::Db;
using namespace BlackMisc::Network;
using namespace BlackCore;
namespace BlackCore
{
namespace Db
{
CDatabaseReader::CDatabaseReader(QObject *owner, const CDatabaseReaderConfigList &config, const QString &name) :
BlackMisc::CThreadedReader(owner, name), m_config(config)
{
this->m_sharedUrl = sApp->getGlobalSetup().getSwiftSharedUrls().getRandomWorkingUrl();
}
void CDatabaseReader::readInBackgroundThread(CEntityFlags::Entity entities, const QDateTime &newerThan)
{
if (isAbandoned()) { return; }
// we accept cached cached data
Q_ASSERT_X(!entities.testFlag(CEntityFlags::InfoObjectEntity), Q_FUNC_INFO, "Read info objects directly");
CEntityFlags::Entity allEntities = entities;
CEntityFlags::Entity currentEntity = CEntityFlags::iterateDbEntities(allEntities); // CEntityFlags::InfoObjectEntity will be ignored
const bool hasInfoObjects = this->hasInfoObjects();
const bool changedUrl = this->hasChangedUrl(currentEntity);
while (currentEntity)
{
const CDatabaseReaderConfig config(this->getConfigForEntity(currentEntity));
if (config.getRetrievalMode().testFlag(CDbFlags::Cached))
{
if (hasInfoObjects)
{
const QDateTime cacheTs(this->getCacheTimestamp(currentEntity));
const QDateTime latestEntityTs(this->getLatestEntityTimestamp(currentEntity));
const qint64 cacheTimestamp = cacheTs.isValid() ? cacheTs.toMSecsSinceEpoch() : -1;
const qint64 latestEntityTimestamp = latestEntityTs.isValid() ? latestEntityTs.toMSecsSinceEpoch() : -1;
Q_ASSERT_X(latestEntityTimestamp > 0, Q_FUNC_INFO, "Missing timestamp");
if (!changedUrl && cacheTimestamp >= latestEntityTimestamp && cacheTimestamp > 0)
{
this->syncronizeCaches(currentEntity);
entities &= ~currentEntity; // do not load from web
CLogMessage(this).info("Using cache for %1 (%2, %3)")
<< CEntityFlags::flagToString(currentEntity)
<< cacheTs.toString() << cacheTimestamp;
}
else
{
if (changedUrl)
{
CLogMessage(this).info("Data location changed, will override cache");
}
else
{
CLogMessage(this).info("Cache for %1 outdated, latest entity (%2, %3)")
<< CEntityFlags::flagToString(currentEntity)
<< latestEntityTs.toString() << latestEntityTimestamp;
}
}
}
else
{
// no info objects, server down
this->syncronizeCaches(currentEntity);
CLogMessage(this).info("No info object for %1, using cache") << CEntityFlags::flagToString(currentEntity);
}
}
currentEntity = CEntityFlags::iterateDbEntities(allEntities);
}
// ps_read is implemented in the derived classes
if (entities == CEntityFlags::NoEntity) { return; }
const bool s = QMetaObject::invokeMethod(this, "ps_read",
Q_ARG(BlackMisc::Network::CEntityFlags::Entity, entities),
Q_ARG(QDateTime, newerThan));
Q_ASSERT_X(s, Q_FUNC_INFO, "Invoke failed");
Q_UNUSED(s);
}
CDatabaseReader::JsonDatastoreResponse CDatabaseReader::transformReplyIntoDatastoreResponse(QNetworkReply *nwReply) const
{
this->threadAssertCheck();
static const CLogCategoryList cats(CLogCategoryList(this).join({ CLogCategory::webservice()}));
JsonDatastoreResponse datastoreResponse;
if (this->isAbandoned())
{
nwReply->abort();
datastoreResponse.setMessage(CStatusMessage(cats, CStatusMessage::SeverityError, "Terminated data parsing process"));
return datastoreResponse; // stop, terminate straight away, ending thread
}
if (nwReply->error() == QNetworkReply::NoError)
{
const QString dataFileData = nwReply->readAll().trimmed();
nwReply->close(); // close asap
if (dataFileData.isEmpty())
{
datastoreResponse.setMessage(CStatusMessage(cats, CStatusMessage::SeverityError, "Empty response, no data"));
datastoreResponse.m_updated = QDateTime::currentDateTimeUtc();
return datastoreResponse;
}
QJsonDocument jsonResponse = QJsonDocument::fromJson(dataFileData.toUtf8());
if (jsonResponse.isArray())
{
// directly an array, no further info
datastoreResponse.m_jsonArray = jsonResponse.array();
datastoreResponse.m_updated = QDateTime::currentDateTimeUtc();
}
else
{
QJsonObject responseObject(jsonResponse.object());
datastoreResponse.m_jsonArray = responseObject["data"].toArray();
QString ts(responseObject["latest"].toString());
datastoreResponse.m_updated = ts.isEmpty() ? QDateTime::currentDateTimeUtc() : CDatastoreUtility::parseTimestamp(ts);
datastoreResponse.m_restricted = responseObject["restricted"].toBool();
}
return datastoreResponse;
}
// no valid response
QString error(nwReply->errorString());
QString url(nwReply->url().toString());
nwReply->abort();
datastoreResponse.setMessage(CStatusMessage(cats, CStatusMessage::SeverityError,
QString("Reading data failed: " + error + " " + url)));
return datastoreResponse;
}
CDatabaseReader::JsonDatastoreResponse CDatabaseReader::setStatusAndTransformReplyIntoDatastoreResponse(QNetworkReply *nwReply)
{
this->setConnectionStatus(nwReply);
return this->transformReplyIntoDatastoreResponse(nwReply);
}
CDbInfoList CDatabaseReader::infoList() const
{
static const CDbInfoList e;
if (!sApp->hasWebDataServices()) { return e; }
if (!sApp->getWebDataServices()->getInfoDataReader()) { return e; }
return sApp->getWebDataServices()->getInfoDataReader()->getDbInfoObjects();
}
bool CDatabaseReader::hasInfoObjects() const
{
return infoList().size() > 0;
}
QDateTime CDatabaseReader::getLatestEntityTimestamp(CEntityFlags::Entity entity) const
{
static const QDateTime e;
const CDbInfoList il(infoList());
if (il.isEmpty() || entity == CEntityFlags::NoEntity) { return e; }
CDbInfo info = il.findFirstByEntityOrDefault(entity);
if (!info.isValid()) { return e; }
return info.getUtcTimestamp();
}
CDatabaseReaderConfig CDatabaseReader::getConfigForEntity(CEntityFlags::Entity entity) const
{
return this->m_config.findFirstOrDefaultForEntity(entity);
}
bool CDatabaseReader::isChangedUrl(const CUrl &oldUrl, const CUrl &currentUrl)
{
if (oldUrl.isEmpty()) { return true; }
Q_ASSERT_X(!currentUrl.isEmpty(), Q_FUNC_INFO, "No base URL");
const QString oldS(oldUrl.getFullUrl(false));
const QString currentS(currentUrl.getFullUrl(false));
return oldS != currentS;
}
bool CDatabaseReader::canConnect() const
{
QReadLocker rl(&this->m_statusLock);
return m_canConnect;
}
bool CDatabaseReader::canConnect(QString &message) const
{
QReadLocker rl(&this->m_statusLock);
message = m_statusMessage;
return m_canConnect;
}
CUrl CDatabaseReader::getWorkingSharedUrl() const
{
return this->m_sharedUrl;
}
const QString &CDatabaseReader::getStatusMessage() const
{
return this->m_statusMessage;
}
void CDatabaseReader::setConnectionStatus(bool ok, const QString &message)
{
{
QWriteLocker wl(&this->m_statusLock);
this->m_statusMessage = message;
this->m_canConnect = ok;
}
}
void CDatabaseReader::setConnectionStatus(QNetworkReply *nwReply)
{
Q_ASSERT_X(nwReply, Q_FUNC_INFO, "Missing network reply");
if (nwReply->isFinished())
{
if (nwReply->error() == QNetworkReply::NoError)
{
setConnectionStatus(true);
}
else
{
setConnectionStatus(false, nwReply->errorString());
}
}
}
const CLogCategoryList &CDatabaseReader::getLogCategories()
{
static const BlackMisc::CLogCategoryList cats { BlackMisc::CLogCategory::swiftDbWebservice(), BlackMisc::CLogCategory::mapping() };
return cats;
}
const QString &CDatabaseReader::parameterLatestTimestamp()
{
static const QString p("latestTimestamp");
return p;
}
const QString &CDatabaseReader::parameterLatestId()
{
static const QString p("latestId");
return p;
}
} // ns
} // ns