refs #597, use CMD arguments in setup reader.

* Setup reader will now be initialized and handles by CApplication
* Setup reader no longer threaded reader as it will be initialized and loade upfront
This commit is contained in:
Klaus Basan
2016-02-18 19:56:35 +01:00
committed by Mathew Sutcliffe
parent 6a06aa0460
commit 9ff322ae25
8 changed files with 281 additions and 136 deletions

View File

@@ -122,6 +122,11 @@ namespace BlackCore
QString s("timestamp: "); QString s("timestamp: ");
s.append(this->getFormattedUtcTimestampYmdhms()); s.append(this->getFormattedUtcTimestampYmdhms());
s.append(separator); s.append(separator);
s.append("Loaded: ");
s.append(boolToYesNo(this->wasLoaded()));
s.append(separator);
s.append("For development: "); s.append("For development: ");
s.append(boolToYesNo(isDevelopment())); s.append(boolToYesNo(isDevelopment()));
s.append(separator); s.append(separator);
@@ -200,6 +205,8 @@ namespace BlackCore
return CVariant::fromValue(this->swiftDbDataFileLocationUrls()); return CVariant::fromValue(this->swiftDbDataFileLocationUrls());
case IndexShared: case IndexShared:
return CVariant::fromValue(this->m_sharedUrls); return CVariant::fromValue(this->m_sharedUrls);
case IndexWasLoaded:
return CVariant::fromValue(this->m_wasLoaded);
default: default:
return CValueObject::propertyByIndex(index); return CValueObject::propertyByIndex(index);
} }
@@ -240,6 +247,9 @@ namespace BlackCore
case IndexShared: case IndexShared:
this->m_sharedUrls = variant.value<CUrlList>(); this->m_sharedUrls = variant.value<CUrlList>();
break; break;
case IndexWasLoaded:
this->m_wasLoaded = variant.toBool();
break;
default: default:
CValueObject::setPropertyByIndex(variant, index); CValueObject::setPropertyByIndex(variant, index);
break; break;

View File

@@ -43,6 +43,7 @@ namespace BlackCore
IndexSwiftDbFiles, IndexSwiftDbFiles,
IndexBootstrap, IndexBootstrap,
IndexUpdateInfo, IndexUpdateInfo,
IndexWasLoaded,
IndexShared IndexShared
}; };
@@ -52,6 +53,12 @@ namespace BlackCore
//! Destructor. //! Destructor.
~CGlobalSetup() {} ~CGlobalSetup() {}
//! Has data loaded from web
bool wasLoaded() const { return m_wasLoaded; }
//! Mark as loaded
void markAsLoaded(bool loaded) { this->m_wasLoaded = loaded; }
//! Http port //! Http port
int dbHttpPort() const { return m_dbHttpPort; } int dbHttpPort() const { return m_dbHttpPort; }
@@ -133,6 +140,7 @@ namespace BlackCore
private: private:
BLACK_ENABLE_TUPLE_CONVERSION(BlackCore::Data::CGlobalSetup) BLACK_ENABLE_TUPLE_CONVERSION(BlackCore::Data::CGlobalSetup)
bool m_wasLoaded = false; //!< Loaded from web
int m_dbHttpPort = 80; //!< port int m_dbHttpPort = 80; //!< port
int m_dbHttpsPort = 443; //!< SSL port int m_dbHttpsPort = 443; //!< SSL port
bool m_development = false; //!< dev. version? bool m_development = false; //!< dev. version?
@@ -167,6 +175,7 @@ namespace BlackCore
Q_DECLARE_METATYPE(BlackCore::Data::CGlobalSetup) Q_DECLARE_METATYPE(BlackCore::Data::CGlobalSetup)
BLACK_DECLARE_TUPLE_CONVERSION(BlackCore::Data::CGlobalSetup, ( BLACK_DECLARE_TUPLE_CONVERSION(BlackCore::Data::CGlobalSetup, (
attr(o.m_wasLoaded),
attr(o.m_timestampMSecsSinceEpoch), attr(o.m_timestampMSecsSinceEpoch),
attr(o.m_dbRootDirectoryUrl), attr(o.m_dbRootDirectoryUrl),
attr(o.m_dbHttpPort), attr(o.m_dbHttpPort),

View File

@@ -30,6 +30,5 @@ namespace BlackCore
BlackCore::Data::CGlobalSetup::registerMetadata(); BlackCore::Data::CGlobalSetup::registerMetadata();
BlackCore::Data::CUpdateInfo::registerMetadata(); BlackCore::Data::CUpdateInfo::registerMetadata();
BlackCore::CSetupReader::instance(); //! \todo will go into new runtime
} }
} // namespace } // namespace

View File

@@ -7,9 +7,11 @@
* contained in the LICENSE file. * contained in the LICENSE file.
*/ */
#include "blackcore/application.h"
#include "blackmisc/network/networkutils.h" #include "blackmisc/network/networkutils.h"
#include "blackmisc/sequence.h" #include "blackmisc/sequence.h"
#include "blackmisc/logmessage.h" #include "blackmisc/logmessage.h"
#include "blackmisc/logcategory.h"
#include "blackmisc/json.h" #include "blackmisc/json.h"
#include "blackmisc/project.h" #include "blackmisc/project.h"
#include "blackmisc/fileutils.h" #include "blackmisc/fileutils.h"
@@ -26,93 +28,145 @@ using namespace BlackCore::Data;
namespace BlackCore namespace BlackCore
{ {
CSetupReader::CSetupReader(QObject *owner) : CSetupReader::CSetupReader(QObject *parent) :
CThreadedReader(owner, "CSetupReader") QObject(parent)
{ {
connect(this, &CSetupReader::setupSynchronized, this, &CSetupReader::ps_setupSyncronized); connect(this, &CSetupReader::setupSynchronized, this, &CSetupReader::ps_setupSyncronized);
QString localFileName; connect(this, &CSetupReader::updateInfoSynchronized, this, &CSetupReader::ps_versionInfoSyncronized);
if (this->localBootstrapFile(localFileName)) }
QList<QCommandLineOption> CSetupReader::getCmdLineOptions() const
{
return QList<QCommandLineOption>
{
{
this->m_cmdBootstrapUrl,
this->m_cmdBootstrapMode
}
};
}
CStatusMessage CSetupReader::asyncLoad()
{
if (this->readLocalBootstrapFile(this->m_localSetupFileValue))
{ {
// initialized by local file for testing // initialized by local file for testing
// I do not even need to start in background here emit this->setupSynchronized(true);
CLogMessage(this).info("Using local bootstrap file: %1") << localFileName; return CStatusMessage(cats(), CStatusMessage::SeverityInfo, "Using local bootstrap file: " + this->m_localSetupFileValue);
BlackMisc::singleShot(1000, QThread::currentThread(), [ = ]() }
{ else if (this->m_bootstrapMode == CacheOnly)
emit this->setupSynchronized(true); {
}); m_setup.synchronize();
CGlobalSetup currentSetup = m_setup.get();
this->m_updateInfoUrls = currentSetup.updateInfoFileUrls();
emit this->setupSynchronized(true);
emit this->updateInfoSynchronized(true);
return CStatusMessage(cats(), CStatusMessage::SeverityInfo, "Cache only setup, using it as it is");
} }
else else
{ {
this->m_bootstrapUrls.uniqueWrite()->push_back(m_setup.get().bootstrapUrls()); // web URL
this->m_updateInfoUrls.uniqueWrite()->push_back(m_setup.get().updateInfoUrls()); if (!this->m_bootsrapUrlFileValue.isEmpty())
{
// start with the one from cmd args
this->m_bootstrapUrls.push_front(CUrl(this->m_bootsrapUrlFileValue));
}
this->m_networkManagerBootstrap = new QNetworkAccessManager(this); // if ever loaded add those URLs
this->connect(this->m_networkManagerBootstrap, &QNetworkAccessManager::finished, this, &CSetupReader::ps_parseSetupFile); m_setup.synchronize();
CGlobalSetup currentSetup = m_setup.get();
if (currentSetup.wasLoaded())
{
if (this->m_bootstrapMode != Explicit || this->m_bootstrapUrls.isEmpty())
{
// also use previously cached URLS
this->m_bootstrapUrls.push_back(currentSetup.bootstrapFileUrls());
}
}
this->m_networkManagerUpdateInfo = new QNetworkAccessManager(this); if (this->m_bootstrapUrls.isEmpty())
this->connect(this->m_networkManagerUpdateInfo, &QNetworkAccessManager::finished, this, &CSetupReader::ps_parseUpdateInfoFile); {
return CStatusMessage(cats(), CStatusMessage::SeverityError, "No bootstrap URLs, cannot load setup");
this->start(QThread::LowPriority); }
else
{
this->m_bootstrapUrls.removeDuplicates();
this->ps_readSetup(); // start reading
return CStatusMessage(cats(), CStatusMessage::SeverityInfo, "Will start loading setup");
}
} }
} }
CSetupReader &CSetupReader::instance() bool CSetupReader::parseCmdLineArguments()
{ {
static CSetupReader reader(QCoreApplication::instance()); this->m_bootsrapUrlFileValue = CGlobalSetup::buildBootstrapFileUrl(
return reader; sApp->getParserOptionValue(this->m_cmdBootstrapUrl)
);
this->m_bootstrapMode = stringToEnum(sApp->getParserOptionValue(this->m_cmdBootstrapMode));
QUrl url(this->m_bootsrapUrlFileValue);
// check on local file
if (url.isLocalFile())
{
this->m_localSetupFileValue = url.toLocalFile();
QFile f(this->m_localSetupFileValue);
if (!f.exists())
{
sApp->errorMessage("File " + this->m_localSetupFileValue + " does not exist");
return false;
}
}
// check on explicit URL
if (this->m_bootstrapMode == Explicit)
{
if (!url.isLocalFile())
{
if (!CNetworkUtils::canConnect(url))
{
sApp->errorMessage("URL " + url.toString() + " not reachable");
return false;
}
}
}
return true;
} }
void CSetupReader::initialize() void CSetupReader::gracefulShutdown()
{ {
// start to read by myself this->m_shutdown = true;
CThreadedReader::initialize();
QTimer::singleShot(500, this, &CSetupReader::ps_readSetup);
}
void CSetupReader::cleanup()
{
delete this->m_networkManagerBootstrap;
this->m_networkManagerBootstrap = nullptr;
delete this->m_networkManagerUpdateInfo;
this->m_networkManagerUpdateInfo = nullptr;
} }
void CSetupReader::ps_readSetup() void CSetupReader::ps_readSetup()
{ {
this->threadAssertCheck(); CUrl url(this->m_bootstrapUrls.getNextWorkingUrl());
Q_ASSERT_X(this->m_networkManagerBootstrap, Q_FUNC_INFO, "Missing network manager");
CUrl url(this->m_bootstrapUrls.uniqueWrite()->getNextWorkingUrl());
if (url.isEmpty()) if (url.isEmpty())
{ {
CLogMessage(this).warning("Cannot read setup, failed URLs: %1") << this->m_bootstrapUrls.read()->getFailedUrls(); CLogMessage(this).warning("Cannot read setup, failed URLs: %1") << this->m_bootstrapUrls.getFailedUrls();
emit setupSynchronized(false); emit setupSynchronized(false);
return; return;
} }
QNetworkRequest request(url); if (m_shutdown) { return; }
CNetworkUtils::ignoreSslVerification(request); sApp->requestNetworkResource(url.toNetworkRequest(), { this, &CSetupReader::ps_parseSetupFile });
this->m_networkManagerBootstrap->get(request);
} }
void CSetupReader::ps_readUpdateInfo() void CSetupReader::ps_readUpdateInfo()
{ {
this->threadAssertCheck(); CUrl url(this->m_updateInfoUrls.getNextWorkingUrl());
Q_ASSERT_X(this->m_networkManagerUpdateInfo, Q_FUNC_INFO, "Missing network manager");
CUrl url(this->m_updateInfoUrls.uniqueWrite()->getNextWorkingUrl());
if (url.isEmpty()) if (url.isEmpty())
{ {
CLogMessage(this).warning("Cannot read update info, failed URLs: %1") << this->m_updateInfoUrls.read()->getFailedUrls(); CLogMessage(this).warning("Cannot read update info, failed URLs: %1") << this->m_updateInfoUrls.getFailedUrls();
emit versionSynchronized(false); emit updateInfoSynchronized(false);
return; return;
} }
QNetworkRequest request(url); if (m_shutdown) { return; }
CNetworkUtils::ignoreSslVerification(request); sApp->requestNetworkResource(url.toNetworkRequest(), { this, &CSetupReader::ps_parseUpdateInfoFile});
this->m_networkManagerUpdateInfo->get(request);
} }
void CSetupReader::ps_setupSyncronized(bool success) void CSetupReader::ps_setupSyncronized(bool success)
{ {
// trigger // trigger consecutive read
this->m_setupSyncronized = success;
if (success) if (success)
{ {
CLogMessage(this).info("Setup synchronized, will trigger read of update information"); CLogMessage(this).info("Setup synchronized, will trigger read of update information");
@@ -124,19 +178,44 @@ namespace BlackCore
} }
} }
void CSetupReader::ps_versionInfoSyncronized(bool success)
{
this->m_updateInfoSyncronized = success;
}
void CSetupReader::ps_setupChanged() void CSetupReader::ps_setupChanged()
{ {
// settings have changed on disk // settings have changed on disk
} }
bool CSetupReader::localBootstrapFile(QString &fileName) CSetupReader::BootsrapMode CSetupReader::stringToEnum(const QString &s)
{ {
QString dir(CProject::getSwiftPrivateResourceDir()); const QString bsm(s.toLower().trimmed());
if (dir.isEmpty()) { return false; } if (bsm.startsWith("expl")) return Explicit;
if (bsm.startsWith("cache")) return CacheOnly;
return Default;
}
// no version for local files, as those come withe the current code bool CSetupReader::readLocalBootstrapFile(QString &fileName)
fileName = CFileUtils::appendFilePaths(dir, "bootstrap/bootstrap.json"); {
QString content(CFileUtils::readFileToString(fileName)); if (fileName.isEmpty()) { return false; }
QString fn;
QFile file(fileName);
if (!file.exists())
{
// relative name?
QString dir(CProject::getSwiftPrivateResourceDir());
if (dir.isEmpty()) { return false; }
// no version for local files, as those come withe the current code
fn = CFileUtils::appendFilePaths(dir, "bootstrap/bootstrap.json");
}
else
{
fn = fileName;
}
QString content(CFileUtils::readFileToString(fn));
if (content.isEmpty()) { return false; } if (content.isEmpty()) { return false; }
CGlobalSetup s; CGlobalSetup s;
s.convertFromJson(content); s.convertFromJson(content);
@@ -155,22 +234,15 @@ namespace BlackCore
// wrap pointer, make sure any exit cleans up reply // wrap pointer, make sure any exit cleans up reply
// required to use delete later as object is created in a different thread // required to use delete later as object is created in a different thread
QScopedPointer<QNetworkReply, QScopedPointerDeleteLater> nwReply(nwReplyPtr); QScopedPointer<QNetworkReply, QScopedPointerDeleteLater> nwReply(nwReplyPtr);
this->threadAssertCheck(); if (m_shutdown) { return; }
QUrl url(nwReply->url()); QUrl url(nwReply->url());
QString urlString(url.toString()); QString urlString(url.toString());
QString replyMessage(nwReply->errorString()); QString replyMessage(nwReply->errorString());
if (this->isAbandoned())
{
CLogMessage(this).info("Terminated loading bootstrap files");
nwReply->abort();
emit setupSynchronized(false);
return; // stop, terminate straight away, ending thread
}
if (nwReply->error() == QNetworkReply::NoError) if (nwReply->error() == QNetworkReply::NoError)
{ {
qint64 lastModified = this->lastModifiedMsSinceEpoch(nwReply.data()); qint64 lastModified = CNetworkUtils::lastModifiedMsSinceEpoch(nwReply.data());
QString setupJson(nwReplyPtr->readAll()); QString setupJson(nwReplyPtr->readAll());
nwReplyPtr->close(); nwReplyPtr->close();
if (setupJson.isEmpty()) if (setupJson.isEmpty())
@@ -180,13 +252,11 @@ namespace BlackCore
} }
else else
{ {
CGlobalSetup currentSetup(m_setup.get()); // from cache CGlobalSetup currentSetup = m_setup.get();
CGlobalSetup loadedSetup; CGlobalSetup loadedSetup;
loadedSetup.convertFromJson(Json::jsonObjectFromString(setupJson)); loadedSetup.convertFromJson(Json::jsonObjectFromString(setupJson));
loadedSetup.setDevelopment(isForDevelopment()); // always update, regardless what persistent setting says loadedSetup.markAsLoaded(true);
if (loadedSetup.getMSecsSinceEpoch() == 0 && lastModified > 0) { loadedSetup.setMSecsSinceEpoch(lastModified); } if (lastModified > 0 && lastModified > loadedSetup.getMSecsSinceEpoch()) { loadedSetup.setMSecsSinceEpoch(lastModified); }
qint64 currentVersionTimestamp = currentSetup.getMSecsSinceEpoch();
qint64 newVersionTimestamp = loadedSetup.getMSecsSinceEpoch();
bool sameVersionLoaded = (loadedSetup == currentSetup); bool sameVersionLoaded = (loadedSetup == currentSetup);
if (sameVersionLoaded) if (sameVersionLoaded)
{ {
@@ -195,24 +265,27 @@ namespace BlackCore
return; // success return; // success
} }
bool sameType = loadedSetup.hasSameType(currentSetup); qint64 currentVersionTimestamp = currentSetup.getMSecsSinceEpoch();
bool outdatedVersionLoaded = sameType && (newVersionTimestamp < currentVersionTimestamp); qint64 newVersionTimestamp = loadedSetup.getMSecsSinceEpoch();
if (outdatedVersionLoaded) bool outdatedVersionLoaded = (newVersionTimestamp < currentVersionTimestamp);
if (this->m_bootstrapMode != Explicit && outdatedVersionLoaded)
{ {
CLogMessage(this).info("Setup loaded from %1 outdated, older than version in data cache %2") << urlString << m_setup.getFilename(); CLogMessage(this).info("Setup loaded from %1 outdated, older than version in data cache %2") << urlString << m_setup.getFilename();
// try next URL // try next URL
} }
else else
{ {
CStatusMessage m = m_setup.set(loadedSetup); CStatusMessage m = m_setup.set(loadedSetup, loadedSetup.getMSecsSinceEpoch());
if (!m.isEmpty()) if (m.isWarningOrAbove())
{ {
m.setCategories(cats());
CLogMessage(this).preformatted(m); CLogMessage(this).preformatted(m);
emit setupSynchronized(false); emit setupSynchronized(false);
return; // issue with cache return; // issue with cache
} }
else else
{ {
this->m_updateInfoUrls = loadedSetup.updateInfoFileUrls();
CLogMessage(this).info("Setup: Updated data cache in %1") << this->m_setup.getFilename(); CLogMessage(this).info("Setup: Updated data cache in %1") << this->m_setup.getFilename();
emit setupSynchronized(true); emit setupSynchronized(true);
return; // success return; // success
@@ -229,7 +302,7 @@ namespace BlackCore
} }
// try next one if any // try next one if any
if (this->m_bootstrapUrls.uniqueWrite()->addFailedUrl(url)) if (this->m_bootstrapUrls.addFailedUrl(url))
{ {
QTimer::singleShot(500, this, &CSetupReader::ps_readSetup); QTimer::singleShot(500, this, &CSetupReader::ps_readSetup);
} }
@@ -244,22 +317,15 @@ namespace BlackCore
// wrap pointer, make sure any exit cleans up reply // wrap pointer, make sure any exit cleans up reply
// required to use delete later as object is created in a different thread // required to use delete later as object is created in a different thread
QScopedPointer<QNetworkReply, QScopedPointerDeleteLater> nwReply(nwReplyPtr); QScopedPointer<QNetworkReply, QScopedPointerDeleteLater> nwReply(nwReplyPtr);
this->threadAssertCheck(); if (m_shutdown) { return; }
QUrl url(nwReply->url()); QUrl url(nwReply->url());
QString urlString(url.toString()); QString urlString(url.toString());
QString replyMessage(nwReply->errorString()); QString replyMessage(nwReply->errorString());
if (this->isAbandoned())
{
CLogMessage(this).info("Terminated loading of update info");
nwReply->abort();
emit versionSynchronized(false);
return; // stop, terminate straight away, ending thread
}
if (nwReply->error() == QNetworkReply::NoError) if (nwReply->error() == QNetworkReply::NoError)
{ {
qint64 lastModified = this->lastModifiedMsSinceEpoch(nwReply.data()); qint64 lastModified = CNetworkUtils::lastModifiedMsSinceEpoch(nwReply.data());
QString setupJson(nwReplyPtr->readAll()); QString setupJson(nwReplyPtr->readAll());
nwReplyPtr->close(); nwReplyPtr->close();
if (setupJson.isEmpty()) if (setupJson.isEmpty())
@@ -273,20 +339,18 @@ namespace BlackCore
CUpdateInfo loadedUpdateInfo; CUpdateInfo loadedUpdateInfo;
loadedUpdateInfo.convertFromJson(Json::jsonObjectFromString(setupJson)); loadedUpdateInfo.convertFromJson(Json::jsonObjectFromString(setupJson));
loadedUpdateInfo.setDevelopment(isForDevelopment()); // always update, regardless what persistent setting says loadedUpdateInfo.setDevelopment(isForDevelopment()); // always update, regardless what persistent setting says
if (loadedUpdateInfo.getMSecsSinceEpoch() == 0 && lastModified > 0) { loadedUpdateInfo.setMSecsSinceEpoch(lastModified); } if (lastModified > 0 && lastModified > loadedUpdateInfo.getMSecsSinceEpoch()) { loadedUpdateInfo.setMSecsSinceEpoch(lastModified); }
qint64 currentVersionTimestamp = currentUpdateInfo.getMSecsSinceEpoch();
qint64 newVersionTimestamp = loadedUpdateInfo.getMSecsSinceEpoch();
bool sameVersionLoaded = (loadedUpdateInfo == currentUpdateInfo); bool sameVersionLoaded = (loadedUpdateInfo == currentUpdateInfo);
if (sameVersionLoaded) if (sameVersionLoaded)
{ {
CLogMessage(this).info("Same update info loaded from %1 as already in data cache %2") << urlString << m_updateInfo.getFilename(); CLogMessage(this).info("Same update info version loaded from %1 as already in data cache %2") << urlString << m_setup.getFilename();
this->setUpdateTimestamp(); emit updateInfoSynchronized(true);
emit versionSynchronized(true);
return; // success return; // success
} }
bool sameType = loadedUpdateInfo.hasSameType(currentUpdateInfo); qint64 currentVersionTimestamp = currentUpdateInfo.getMSecsSinceEpoch();
bool outdatedVersionLoaded = sameType && (newVersionTimestamp < currentVersionTimestamp); qint64 newVersionTimestamp = loadedUpdateInfo.getMSecsSinceEpoch();
bool outdatedVersionLoaded = (newVersionTimestamp < currentVersionTimestamp);
if (outdatedVersionLoaded) if (outdatedVersionLoaded)
{ {
CLogMessage(this).info("Update info loaded from %1 outdated, older than version in data cache %2") << urlString << m_updateInfo.getFilename(); CLogMessage(this).info("Update info loaded from %1 outdated, older than version in data cache %2") << urlString << m_updateInfo.getFilename();
@@ -294,21 +358,21 @@ namespace BlackCore
} }
else else
{ {
CStatusMessage m = m_updateInfo.set(loadedUpdateInfo); CStatusMessage m = m_updateInfo.set(loadedUpdateInfo, loadedUpdateInfo.getMSecsSinceEpoch());
if (!m.isEmpty()) if (!m.isEmpty())
{ {
m.setCategories(cats());
CLogMessage(this).preformatted(m); CLogMessage(this).preformatted(m);
emit versionSynchronized(false); emit updateInfoSynchronized(false);
return; // issue with cache return; // issue with cache
} }
else else
{ {
CLogMessage(this).info("Update info: Updated data cache in %1") << m_updateInfo.getFilename(); CLogMessage(this).info("Update info: Updated data cache in %1") << m_updateInfo.getFilename();
this->setUpdateTimestamp(); emit updateInfoSynchronized(true);
emit versionSynchronized(true);
return; // success return; // success
} // cache } // cache
} // outdated? } // outdated
} // json empty } // json empty
} // no error } // no error
@@ -320,14 +384,20 @@ namespace BlackCore
} }
// try next one if any // try next one if any
if (this->m_updateInfoUrls.uniqueWrite()->addFailedUrl(url)) if (this->m_updateInfoUrls.addFailedUrl(url))
{ {
QTimer::singleShot(500, this, &CSetupReader::ps_readSetup); QTimer::singleShot(500, this, &CSetupReader::ps_readSetup);
} }
else else
{ {
emit versionSynchronized(false); emit updateInfoSynchronized(false);
} }
} // method } // function
const CLogCategoryList &CSetupReader::cats()
{
static const CLogCategoryList cats(CLogCategoryList(this).join({ CLogCategory::webservice()}));
return cats;
}
} // namespace } // namespace

View File

@@ -21,39 +21,56 @@
#include <QObject> #include <QObject>
#include <QTimer> #include <QTimer>
#include <QNetworkReply> #include <QNetworkReply>
#include <QCommandLineOption>
namespace BlackCore namespace BlackCore
{ {
//! Read the central URLs / locations of our data / setup //! Read the central URLs / locations of our data / setup.
class BLACKCORE_EXPORT CSetupReader : public BlackMisc::CThreadedReader //! This should be only used in BlackCore::CApplication
//! \note This class is no(!) BlackCore::CThreadedReader as it will be loaded once during startup
//! and is usually fast.
//! \sa BlackCore::Data::GlobalSetup
//! \sa BlackCore::Data::UpdateInfo
class BLACKCORE_EXPORT CSetupReader : public QObject
{ {
Q_OBJECT Q_OBJECT
public: friend class CApplication; //!< only using class
//! Single instance
static CSetupReader &instance(); protected:
//! Constructor
explicit CSetupReader(QObject *parent);
//! Load the data
BlackMisc::CStatusMessage asyncLoad();
//! Parse cmd line arguments
bool parseCmdLineArguments();
//! Add cmd line arguments to BlackCore::CApplication
QList<QCommandLineOption> getCmdLineOptions() const;
//! Terminate
void gracefulShutdown();
//! Setup loaded?
bool isSetupSyncronized() const { return m_setupSyncronized; }
//! Version info loaded?
bool isUpdateSyncronized() const { return m_updateInfoSyncronized; }
signals: signals:
//! Setup has been read //! Setup has been read
void setupSynchronized(bool success); void setupSynchronized(bool success);
//! Version bas been read //! Version bas been read
void versionSynchronized(bool success); void updateInfoSynchronized(bool success);
protected slots:
//! \copydoc BlackMisc::CThreadedReader::initialize
virtual void initialize() override;
//! \copydoc BlackMisc::CThreadedReader::cleanup
virtual void cleanup() override;
private slots: private slots:
//! Setup has been read //! Setup has been read
//! \threadsafe
void ps_parseSetupFile(QNetworkReply *nwReply); void ps_parseSetupFile(QNetworkReply *nwReply);
//! Update info has been read //! Update info has been read
//! \threadsafe
void ps_parseUpdateInfoFile(QNetworkReply *nwReplyPtr); void ps_parseUpdateInfoFile(QNetworkReply *nwReplyPtr);
//! Do reading //! Do reading
@@ -65,25 +82,56 @@ namespace BlackCore
//! Setup has beem syncronized //! Setup has beem syncronized
void ps_setupSyncronized(bool success); void ps_setupSyncronized(bool success);
//! Version info has beem syncronized
void ps_versionInfoSyncronized(bool success);
//! Setup has been changed //! Setup has been changed
void ps_setupChanged(); void ps_setupChanged();
private: private:
QNetworkAccessManager *m_networkManagerBootstrap = nullptr; //! Bootstrap mode
QNetworkAccessManager *m_networkManagerUpdateInfo = nullptr; enum BootsrapMode
BlackMisc::LockFree<BlackMisc::Network::CFailoverUrlList> m_bootstrapUrls; {
BlackMisc::LockFree<BlackMisc::Network::CFailoverUrlList> m_updateInfoUrls; Default,
Explicit,
CacheOnly
};
bool m_shutdown = false;
bool m_setupSyncronized = false;
bool m_updateInfoSyncronized = false;
QString m_localSetupFileValue;
QString m_bootsrapUrlFileValue;
BootsrapMode m_bootstrapMode;
BlackMisc::Network::CFailoverUrlList m_bootstrapUrls;
BlackMisc::Network::CFailoverUrlList m_updateInfoUrls;
BlackMisc::CData<BlackCore::Data::GlobalSetup> m_setup {this, &CSetupReader::ps_setupChanged}; //!< data cache setup BlackMisc::CData<BlackCore::Data::GlobalSetup> m_setup {this, &CSetupReader::ps_setupChanged}; //!< data cache setup
BlackMisc::CData<BlackCore::Data::UpdateInfo> m_updateInfo {this}; //!< data cache update info BlackMisc::CData<BlackCore::Data::UpdateInfo> m_updateInfo {this}; //!< data cache update info
//! Constructor QCommandLineOption m_cmdBootstrapUrl
explicit CSetupReader(QObject *owner); {
{ "url", "bootstrap-url", "bootstrapurl" },
QCoreApplication::translate("application", "bootsrap URL, e.g. datastore.swift-project.org"),
"bootstrapurl"
}; //!< bootstrap URL
QCommandLineOption m_cmdBootstrapMode
{
{ "bmode", "bootstrap-mode", "bootstrapmode" },
QCoreApplication::translate("application", "bootstrap mode: (e)xplicit, d(default), (c)ache-only"),
"bootstrapmode", "default"
}; //!< bootstrap mode
//! Read by local individual file //! Read by local individual file
bool localBootstrapFile(QString &fileName); bool readLocalBootstrapFile(QString &fileName);
//! Convert string to mode
static BootsrapMode stringToEnum(const QString &s);
//! Read for development environment? //! Read for development environment?
static bool isForDevelopment(); static bool isForDevelopment();
//! Categories
const BlackMisc::CLogCategoryList &cats();
}; };
} // ns } // ns

View File

@@ -246,5 +246,18 @@ namespace BlackMisc
CNetworkUtils::ignoreSslVerification(request); CNetworkUtils::ignoreSslVerification(request);
return request; return request;
} }
qint64 CNetworkUtils::lastModifiedMsSinceEpoch(QNetworkReply *nwReply)
{
if (nwReply)
{
QVariant lastModifiedQv = nwReply->header(QNetworkRequest::LastModifiedHeader);
if (lastModifiedQv.isValid() && lastModifiedQv.canConvert<QDateTime>())
{
return lastModifiedQv.value<QDateTime>().toMSecsSinceEpoch();
}
}
return -1;
}
} // namespace } // namespace
} // namespacee } // namespacee

View File

@@ -20,6 +20,7 @@
#include <QStringList> #include <QStringList>
#include <QUrlQuery> #include <QUrlQuery>
#include <QNetworkRequest> #include <QNetworkRequest>
#include <QNetworkReply>
#include <QJsonObject> #include <QJsonObject>
namespace BlackMisc namespace BlackMisc
@@ -110,10 +111,12 @@ namespace BlackMisc
//! Our tweakes network request //! Our tweakes network request
static QNetworkRequest getNetworkRequest(const CUrl &url, RequestType type = Get); static QNetworkRequest getNetworkRequest(const CUrl &url, RequestType type = Get);
//! Last modified from reply
static qint64 lastModifiedMsSinceEpoch(QNetworkReply *nwReply);
private: private:
//! Hidden constructor //! Hidden constructor
CNetworkUtils() {} CNetworkUtils() {}
}; };
} // namespace } // namespace
} // namespace } // namespace

View File

@@ -9,6 +9,7 @@
#include "threadedreader.h" #include "threadedreader.h"
#include "blackmisc/threadutils.h" #include "blackmisc/threadutils.h"
#include "blackmisc/network/networkutils.h"
using namespace BlackMisc; using namespace BlackMisc;
using namespace BlackMisc::Network; using namespace BlackMisc::Network;
@@ -26,15 +27,7 @@ namespace BlackMisc
qint64 CThreadedReader::lastModifiedMsSinceEpoch(QNetworkReply *nwReply) const qint64 CThreadedReader::lastModifiedMsSinceEpoch(QNetworkReply *nwReply) const
{ {
if (nwReply) return CNetworkUtils::lastModifiedMsSinceEpoch(nwReply);
{
QVariant lastModifiedQv = nwReply->header(QNetworkRequest::LastModifiedHeader);
if (lastModifiedQv.isValid() && lastModifiedQv.canConvert<QDateTime>())
{
return lastModifiedQv.value<QDateTime>().toMSecsSinceEpoch();
}
}
return -1;
} }
QDateTime CThreadedReader::getUpdateTimestamp() const QDateTime CThreadedReader::getUpdateTimestamp() const