mirror of
https://github.com/swift-project/pilotclient.git
synced 2026-03-31 04:25:35 +08:00
* request started/received * moved timestamp logic to Response class, like headerResponse.setValues(nwReply)
395 lines
19 KiB
C++
395 lines
19 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.
|
|
*/
|
|
|
|
//! \file
|
|
|
|
#ifndef BLACKCORE_DB_DATABASEREADER_H
|
|
#define BLACKCORE_DB_DATABASEREADER_H
|
|
|
|
#include "blackcore/blackcoreexport.h"
|
|
#include "blackcore/db/databasereaderconfig.h"
|
|
#include "blackmisc/db/dbinfolist.h"
|
|
#include "blackmisc/pq/time.h"
|
|
#include "blackmisc/network/url.h"
|
|
#include "blackmisc/statusmessage.h"
|
|
#include "blackcore/threadedreader.h"
|
|
#include "blackmisc/sequence.h"
|
|
#include "blackmisc/valueobject.h"
|
|
|
|
#include <QDateTime>
|
|
#include <QJsonArray>
|
|
#include <QMap>
|
|
#include <QObject>
|
|
#include <QReadWriteLock>
|
|
#include <QString>
|
|
#include <QtGlobal>
|
|
#include <QNetworkReply>
|
|
|
|
class QNetworkReply;
|
|
namespace BlackMisc { class CLogCategoryList; }
|
|
namespace BlackCore
|
|
{
|
|
namespace Db
|
|
{
|
|
//! Specialized version of threaded reader for DB data
|
|
class BLACKCORE_EXPORT CDatabaseReader : public BlackCore::CThreadedReader
|
|
{
|
|
Q_OBJECT
|
|
|
|
public:
|
|
//! Header response part
|
|
struct HeaderResponse
|
|
{
|
|
private:
|
|
QDateTime m_lastModified; //!< when was the latest update?
|
|
int m_httpStatusCode = -1; //!< HTTP status code
|
|
qulonglong m_contentLengthHeader = 0; //!< content length
|
|
qint64 m_requestStarted = -1; //!< when was request started
|
|
qint64 m_responseReceived = -1; //!< response received
|
|
qint64 m_loadTimeMs = -1; //!< how long did it take to load
|
|
BlackMisc::CStatusMessage m_message; //!< last error or warning
|
|
BlackMisc::Network::CUrl m_url; //!< loaded URL
|
|
|
|
public:
|
|
//! Any timestamp?
|
|
bool hasTimestamp() const { return m_lastModified.isValid(); }
|
|
|
|
//! Is response newer?
|
|
bool isNewer(const QDateTime &ts) const { return m_lastModified.toMSecsSinceEpoch() > ts.toMSecsSinceEpoch(); }
|
|
|
|
//! Is response newer?
|
|
bool isNewer(qint64 mSecsSinceEpoch) const { return m_lastModified.toMSecsSinceEpoch() > mSecsSinceEpoch; }
|
|
|
|
//! Get the "last-modified" timestamp
|
|
const QDateTime &getLastModifiedTimestamp() const { return m_lastModified; }
|
|
|
|
//! Set update timestamp, default normally "last-modified"
|
|
void setLastModifiedTimestamp(const QDateTime &updated) { m_lastModified = updated; }
|
|
|
|
//! Header content length
|
|
qulonglong getContentLengthHeader() const { return m_contentLengthHeader; }
|
|
|
|
//! Set the content length
|
|
void setContentLengthHeader(qulonglong size) { m_contentLengthHeader = size; }
|
|
|
|
//! Error message?
|
|
bool hasErrorMessage() const { return m_message.getSeverity() == BlackMisc::CStatusMessage::SeverityError; }
|
|
|
|
//! Warning or error message?
|
|
bool hasWarningOrAboveMessage() const { return m_message.isWarningOrAbove(); }
|
|
|
|
//! Last error or warning
|
|
const BlackMisc::CStatusMessage &lastWarningOrAbove() const { return m_message; }
|
|
|
|
//! Set the error/warning message
|
|
void setMessage(const BlackMisc::CStatusMessage &lastErrorOrWarning) { m_message = lastErrorOrWarning; }
|
|
|
|
//! URL loaded
|
|
const BlackMisc::Network::CUrl &getUrl() const { return m_url; }
|
|
|
|
//! URL loaded as string
|
|
QString getUrlString() const { return m_url.toQString(); }
|
|
|
|
//! Set the loaded URL
|
|
void setUrl(const BlackMisc::Network::CUrl &url) { m_url = url; }
|
|
|
|
//! Is a shared file?
|
|
bool isSharedFile() const;
|
|
|
|
//! Has HTTP status code?
|
|
bool hasHttpStatusCode() const { return m_httpStatusCode >= 0; }
|
|
|
|
//! HTTP status code
|
|
int getHttpStatusCode() const { return m_httpStatusCode; }
|
|
|
|
//! Load time in ms (from request to response)
|
|
qint64 getLoadTimeMs() const { return m_loadTimeMs; }
|
|
|
|
//! Load time as string
|
|
QString getLoadTimeString() const;
|
|
|
|
//! Load time as string
|
|
QString getLoadTimeStringWithStartedHint() const;
|
|
|
|
//! Set the load time (delta start -> response received)
|
|
void setLoadTimeMs(qint64 deltaTime) { m_loadTimeMs = deltaTime; }
|
|
|
|
//! Set reply values
|
|
void setValues(const QNetworkReply *nwReply);
|
|
};
|
|
|
|
//! Response from our database (depending on JSON DB backend generates)
|
|
struct JsonDatastoreResponse : public HeaderResponse
|
|
{
|
|
private:
|
|
QJsonArray m_jsonArray; //!< JSON array data
|
|
int m_arraySize = -1; //!< size of array, if applicable (copied to member for debugging purposes)
|
|
bool m_restricted = false; //!< restricted reponse, only changed data
|
|
|
|
public:
|
|
//! Any data?
|
|
bool isEmpty() const { return m_jsonArray.isEmpty(); }
|
|
|
|
//! Number of elements
|
|
int size() const { return m_jsonArray.size(); }
|
|
|
|
//! Incremental data, restricted by query?
|
|
bool isRestricted() const { return m_restricted; }
|
|
|
|
//! Is loaded from database
|
|
bool isLoadedFromDb() const;
|
|
|
|
//! Mark as restricted
|
|
void setRestricted(bool restricted) { m_restricted = restricted; }
|
|
|
|
//! Get the JSON array
|
|
QJsonArray getJsonArray() const { return m_jsonArray; }
|
|
|
|
//! Set the JSON array
|
|
void setJsonArray(const QJsonArray &value);
|
|
|
|
//! Implicit conversion
|
|
operator QJsonArray() const { return m_jsonArray; }
|
|
};
|
|
|
|
//! Start reading in own thread
|
|
//! \remark uses caches, info objects
|
|
void readInBackgroundThread(BlackMisc::Network::CEntityFlags::Entity entities, const QDateTime &newerThan);
|
|
|
|
//! Start loading from DB in own thread
|
|
//! \remark bypass caches/config
|
|
BlackMisc::Network::CEntityFlags::Entity triggerLoadingDirectlyFromDb(BlackMisc::Network::CEntityFlags::Entity entities, const QDateTime &newerThan);
|
|
|
|
//! Start loading from shared files in own thread
|
|
//! \remark bypass caches/config
|
|
BlackMisc::Network::CEntityFlags::Entity triggerLoadingDirectlyFromSharedFiles(BlackMisc::Network::CEntityFlags::Entity entities, bool checkCacheTsUpfront);
|
|
|
|
//! Has received Ok response from server at least once?
|
|
//! \threadsafe
|
|
bool hasReceivedOkReply() const;
|
|
|
|
//! Has received Ok response from server?
|
|
//! A message why connect failed can be obtained.
|
|
//! \threadsafe
|
|
bool hasReceivedOkReply(QString &message) const;
|
|
|
|
//! Has received 1st reply?
|
|
//! \threadsafe
|
|
bool hasReceivedFirstReply() const;
|
|
|
|
//! Supported entities by this reader
|
|
virtual BlackMisc::Network::CEntityFlags::Entity getSupportedEntities() const = 0;
|
|
|
|
//! Mask by supported entities
|
|
BlackMisc::Network::CEntityFlags::Entity maskBySupportedEntities(BlackMisc::Network::CEntityFlags::Entity entities) const;
|
|
|
|
//! Is any of the given entities supported here by this reader
|
|
bool supportsAnyOfEntities(BlackMisc::Network::CEntityFlags::Entity entities) const;
|
|
|
|
//! Get cache timestamp
|
|
virtual QDateTime getCacheTimestamp(BlackMisc::Network::CEntityFlags::Entity entity) const = 0;
|
|
|
|
//! Has entity a valid and newer timestamp
|
|
bool hasCacheTimestampNewerThan(BlackMisc::Network::CEntityFlags::Entity entity, const QDateTime &threshold) const;
|
|
|
|
//! Cache`s number of entities
|
|
//! \remark this only works if the cache is admitted, DB caches are read deferred
|
|
virtual int getCacheCount(BlackMisc::Network::CEntityFlags::Entity entity) const = 0;
|
|
|
|
//! Entities already having data in cache
|
|
//! \remark this only works if the cache is admitted, DB caches are read deferred
|
|
virtual BlackMisc::Network::CEntityFlags::Entity getEntitiesWithCacheCount() const = 0;
|
|
|
|
//! Entities already having data in cache (based on timestamp assumption)
|
|
//! \remark unlike getEntitiesWithCacheCount() this even works when the cache is not yet admitted
|
|
virtual BlackMisc::Network::CEntityFlags::Entity getEntitiesWithCacheTimestampNewerThan(const QDateTime &threshold) const = 0;
|
|
|
|
//! DB info objects available?
|
|
bool hasDbInfoObjects() const;
|
|
|
|
//! Shared info objects available?
|
|
bool hasSharedInfoObjects() const;
|
|
|
|
//! Header of shared file read (for single entity)?
|
|
bool hasSharedFileHeader(const BlackMisc::Network::CEntityFlags::Entity entity) const;
|
|
|
|
//! Headers of shared file read (for single entity)?
|
|
bool hasSharedFileHeaders(const BlackMisc::Network::CEntityFlags::Entity entities) const;
|
|
|
|
//! Obtain latest object timestamp from DB info objects
|
|
//! \sa BlackCore::Db::CInfoDataReader
|
|
QDateTime getLatestEntityTimestampFromDbInfoObjects(BlackMisc::Network::CEntityFlags::Entity entity) const;
|
|
|
|
//! Obtain latest object timestamp from shared info objects
|
|
//! \sa BlackCore::Db::CInfoDataReader
|
|
QDateTime getLatestEntityTimestampFromSharedInfoObjects(BlackMisc::Network::CEntityFlags::Entity entity) const;
|
|
|
|
//! Header timestamp (last-modified) for shared file
|
|
//! \deprecated use getLatestEntityTimestampFromSharedInfoObjects
|
|
QDateTime getLatestSharedFileHeaderTimestamp(BlackMisc::Network::CEntityFlags::Entity entity) const;
|
|
|
|
//! Is the file timestamp newer than cache timestamp?
|
|
//! \deprecated use isSharedInfoNewerThanCacheTimestamp
|
|
bool isSharedHeaderNewerThanCacheTimestamp(BlackMisc::Network::CEntityFlags::Entity entity) const;
|
|
|
|
//! Is the shared info timestamp newer than cache timestamp?
|
|
bool isSharedInfoObjectNewerThanCacheTimestamp(BlackMisc::Network::CEntityFlags::Entity entity) const;
|
|
|
|
//! Those entities where the timestamp of header is newer than the cache timestamp
|
|
BlackMisc::Network::CEntityFlags::Entity getEntitesWithNewerHeaderTimestamp(BlackMisc::Network::CEntityFlags::Entity entities) const;
|
|
|
|
//! Those entities where the timestamp of shared info obejct is newer than the cache timestamp
|
|
BlackMisc::Network::CEntityFlags::Entity getEntitesWithNewerSharedInfoObject(BlackMisc::Network::CEntityFlags::Entity entities) const;
|
|
|
|
//! Request headers of shared file
|
|
//! \return number of requested headers
|
|
int requestHeadersOfSharedFiles(BlackMisc::Network::CEntityFlags::Entity entities);
|
|
|
|
//! Status message (error message)
|
|
const QString &getStatusMessage() const;
|
|
|
|
//! Severity used for log messages in case of no URLs
|
|
void setSeverityNoWorkingUrl(BlackMisc::CStatusMessage::StatusSeverity s) { m_severityNoWorkingUrl = s; }
|
|
|
|
//! Log categories
|
|
static const BlackMisc::CLogCategoryList &getLogCategories();
|
|
|
|
//! Transform JSON data to response struct data
|
|
//! \private used also for samples, that`s why it is declared public
|
|
static void stringToDatastoreResponse(const QString &jsonContent, CDatabaseReader::JsonDatastoreResponse &datastoreResponse);
|
|
|
|
signals:
|
|
//! DB have been read
|
|
void swiftDbDataRead(bool success);
|
|
|
|
//! Combined read signal
|
|
void dataRead(BlackMisc::Network::CEntityFlags::Entity entities, BlackMisc::Network::CEntityFlags::ReadState state, int number);
|
|
|
|
//! Header of shared file read
|
|
void sharedFileHeaderRead(BlackMisc::Network::CEntityFlags::Entity entity, const QString &fileName, bool success);
|
|
|
|
protected:
|
|
CDatabaseReaderConfigList m_config; //!< DB reder configuration
|
|
QString m_statusMessage; //!< Returned status message from watchdog
|
|
bool m_1stReplyReceived = false; //!< Successful connection? Does not mean data / authorizations are correct
|
|
mutable QReadWriteLock m_statusLock; //!< Lock
|
|
QNetworkReply::NetworkError m_1stReplyStatus = QNetworkReply::UnknownServerError; //!< Successful connection?
|
|
QMap<BlackMisc::Network::CEntityFlags::Entity, HeaderResponse> m_sharedFileResponses; //!< file responses of the shared files
|
|
BlackMisc::CStatusMessage::StatusSeverity m_severityNoWorkingUrl = BlackMisc::CStatusMessage::SeverityError; //!< severity of message if there is no working URL
|
|
|
|
//! Constructor
|
|
CDatabaseReader(QObject *owner, const CDatabaseReaderConfigList &config, const QString &name);
|
|
|
|
//! Check if terminated or error, otherwise split into array of objects
|
|
CDatabaseReader::JsonDatastoreResponse setStatusAndTransformReplyIntoDatastoreResponse(QNetworkReply *nwReply);
|
|
|
|
//! DB Info list (latest data timestamps from DB web service)
|
|
//! \sa BlackCore::Db::CInfoDataReader
|
|
BlackMisc::Db::CDbInfoList getDbInfoObjects() const;
|
|
|
|
//! Shared info list (latest data timestamps from DB web service)
|
|
//! \sa BlackCore::Db::CInfoDataReader
|
|
BlackMisc::Db::CDbInfoList getSharedInfoObjects() const;
|
|
|
|
//! Config for given entity
|
|
CDatabaseReaderConfig getConfigForEntity(BlackMisc::Network::CEntityFlags::Entity entity) const;
|
|
|
|
//! Split into single entity and send dataRead signal
|
|
BlackMisc::Network::CEntityFlags::Entity emitReadSignalPerSingleCachedEntity(BlackMisc::Network::CEntityFlags::Entity cachedEntities, bool onlyIfHasData);
|
|
|
|
//! Emit signal and log when data have been read
|
|
void emitAndLogDataRead(BlackMisc::Network::CEntityFlags::Entity entity, int number, const JsonDatastoreResponse &res);
|
|
|
|
//! Get the service URL, individual for each reader
|
|
virtual BlackMisc::Network::CUrl getDbServiceBaseUrl() const = 0;
|
|
|
|
//! Log if no working URL exists, using m_noWorkingUrlSeverity
|
|
void logNoWorkingUrl(BlackMisc::Network::CEntityFlags::Entity entity);
|
|
|
|
//! Base URL for mode (either a shared or DB URL)
|
|
BlackMisc::Network::CUrl getBaseUrl(BlackMisc::Db::CDbFlags::DataRetrievalModeFlag mode) const;
|
|
|
|
//! DB base URL
|
|
static const BlackMisc::Network::CUrl &getDbUrl();
|
|
|
|
//! Working shared "dbdata" directory URL
|
|
static BlackMisc::Network::CUrl getWorkingSharedDbdataDirectoryUrl();
|
|
|
|
//! File name for given mode, either php service or shared file name
|
|
static QString fileNameForMode(BlackMisc::Network::CEntityFlags::Entity entity, BlackMisc::Db::CDbFlags::DataRetrievalModeFlag mode);
|
|
|
|
//! Name of latest timestamp
|
|
static const QString ¶meterLatestTimestamp();
|
|
|
|
//! Name of parameter for latest id
|
|
static const QString ¶meterLatestId();
|
|
|
|
//! A newer than value understood by swift DB
|
|
//! \sa CDatabaseReader::parameterLatestTimestamp
|
|
static QString dateTimeToDbLatestTs(const QDateTime &ts);
|
|
|
|
//! Latest timestamp query for DB
|
|
static QString queryLatestTimestamp(const QDateTime &ts);
|
|
|
|
//! \name Cache access
|
|
//! @{
|
|
//! Synchronize caches for given entities
|
|
virtual void synchronizeCaches(BlackMisc::Network::CEntityFlags::Entity entities) = 0;
|
|
|
|
//! Admit caches for given entities
|
|
virtual void admitCaches(BlackMisc::Network::CEntityFlags::Entity entities) = 0;
|
|
|
|
//! Invalidate the caches for given entities
|
|
virtual void invalidateCaches(BlackMisc::Network::CEntityFlags::Entity entities) = 0;
|
|
|
|
//! Changed URL, means the cache values have been read from elsewhere
|
|
//! \remark testing based on BlackMisc::Db::CDbFlags::DbReading
|
|
virtual bool hasChangedUrl(BlackMisc::Network::CEntityFlags::Entity entity,
|
|
BlackMisc::Network::CUrl &oldUrlInfo,
|
|
BlackMisc::Network::CUrl &newUrlInfo) const = 0;
|
|
|
|
//! Cache for given entity has changed
|
|
virtual void cacheHasChanged(BlackMisc::Network::CEntityFlags::Entity entities);
|
|
|
|
//! Has URL been changed? Means we load from a different server
|
|
static bool isChangedUrl(const BlackMisc::Network::CUrl &oldUrl, const BlackMisc::Network::CUrl ¤tUrl);
|
|
//! @}
|
|
|
|
//! Start reading in own thread (without config/caching)
|
|
//! \remarks can handle DB or shared file reads
|
|
void startReadFromBackendInBackgroundThread(BlackMisc::Network::CEntityFlags::Entity entities, BlackMisc::Db::CDbFlags::DataRetrievalModeFlag mode, const QDateTime &newerThan = QDateTime());
|
|
|
|
//! Received a reply of a header for a shared file
|
|
void receivedSharedFileHeader(QNetworkReply *nwReplyPtr);
|
|
|
|
//! Received a reply of a header for a shared file
|
|
void receivedSharedFileHeaderNonClosing(QNetworkReply *nwReplyPtr);
|
|
|
|
//! Check if terminated or error, otherwise split into array of objects
|
|
JsonDatastoreResponse transformReplyIntoDatastoreResponse(QNetworkReply *nwReply) const;
|
|
|
|
//! Check if terminated or error, otherwise set header information
|
|
HeaderResponse transformReplyIntoHeaderResponse(QNetworkReply *nwReply) const;
|
|
|
|
//! Set the header part
|
|
bool setHeaderInfoPart(HeaderResponse &headerResponse, QNetworkReply *nwReply) const;
|
|
|
|
//! Feedback about connection status
|
|
//! \threadsafe
|
|
void setReplyStatus(QNetworkReply::NetworkError status, const QString &message = "");
|
|
|
|
//! Feedback about connection status
|
|
//! \threadsafe
|
|
void setReplyStatus(QNetworkReply *nwReply);
|
|
};
|
|
} // ns
|
|
} // ns
|
|
|
|
#endif // guard
|