Files
pilotclient/src/blackcore/setupreader.cpp
Lars Toenning bcc4bdd31e Add SPDX identifiers for REUSE compliance
Co-authored-by: Mat Sutcliffe <oktal3700@gmail.com>
2023-10-03 09:29:49 +02:00

335 lines
12 KiB
C++

// SPDX-FileCopyrightText: Copyright (C) 2015 swift Project Community / Contributors
// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-swift-pilot-client-1
#include "blackcore/application.h"
#include "blackcore/setupreader.h"
#include "blackmisc/verify.h"
#include "blackmisc/mixin/mixincompare.h"
#include "blackmisc/fileutils.h"
#include "blackmisc/swiftdirectories.h"
#include "blackmisc/directoryutils.h"
#include "blackmisc/logcategories.h"
#include "blackmisc/logmessage.h"
#include "blackmisc/network/networkutils.h"
#include "blackmisc/network/url.h"
#include "blackmisc/statusmessage.h"
#include "blackconfig/buildconfig.h"
#include <QStringBuilder>
#include <QByteArray>
#include <QFile>
#include <QPointer>
#include <QScopedPointer>
#include <QScopedPointerDeleteLater>
#include <QTimer>
#include <QUrl>
#include <QtGlobal>
using namespace BlackConfig;
using namespace BlackMisc;
using namespace BlackMisc::Db;
using namespace BlackMisc::Network;
using namespace BlackCore;
using namespace BlackCore::Data;
namespace BlackCore
{
CSetupReader::CSetupReader(QObject *parent) : QObject(parent),
m_cmdBootstrapUrl {
{ "url", "bootstrapurl" },
QCoreApplication::translate("application", "Bootstrap URL, e.g. https://datastore.swift-project.org/shared"),
"bootstrapurl",
(sApp->getApplicationInfo().isUnitTest()) ? unitTestBootstrapUrl() : ""
},
m_cmdBootstrapMode {
{ "bmode", "bootstrapmode" },
QCoreApplication::translate("application", "Bootstrap mode: explicit, implicit, cache(-only)"),
"bootstrapmode",
"explicit"
}
{}
QList<QCommandLineOption> CSetupReader::getCmdLineOptions() const
{
return QList<QCommandLineOption> { { m_cmdBootstrapUrl, m_cmdBootstrapMode } };
}
CStatusMessageList CSetupReader::asyncLoad()
{
CStatusMessageList msgs;
if (m_localSetupFileValue.isEmpty())
{
// TODO Check if file exists or is broken
msgs = this->readLocalBootstrapFile(CSwiftDirectories::shareDirectory() + "/shared/bootstrap/bootstrap.json");
}
else
{
msgs = this->readLocalBootstrapFile(m_localSetupFileValue);
}
msgs.push_back(this->manageSetupAvailability(false, msgs.isSuccess()));
return msgs;
}
bool CSetupReader::parseCmdLineArguments()
{
// copy vars at beginning to simplify a threadsafe version in the future
const QString cmdLineBootstrapUrl = this->getCmdLineBootstrapUrl();
BootstrapMode bootstrapMode = stringToEnum(sApp->getParserValue(m_cmdBootstrapMode));
const bool ignoreCmdBootstrapUrl = m_ignoreCmdBootstrapUrl;
const bool checkCmdBootstrapUrl = m_checkCmdBootstrapUrl;
const QString bootstrapUrlFileValue = CGlobalSetup::buildBootstrapFileUrl(cmdLineBootstrapUrl);
QString localSetupFileValue;
const QUrl url(bootstrapUrlFileValue);
const QString urlString(url.toString());
bool ok = false;
if (urlString.isEmpty() && bootstrapMode == Explicit)
{
bootstrapMode = Implicit; // no URL, we use implicit mode
}
do
{
// check on local file
if (url.isLocalFile())
{
localSetupFileValue = url.toLocalFile();
const QFile f(localSetupFileValue);
if (!f.exists())
{
sApp->cmdLineErrorMessage(QStringLiteral("File '%1' does not exist)").arg(localSetupFileValue));
break;
}
}
// check on explicit URL
if (bootstrapMode == Explicit)
{
if (!url.isLocalFile())
{
bool retry = false;
// "retry" possible in some cases
do
{
if (ignoreCmdBootstrapUrl || !checkCmdBootstrapUrl || CNetworkUtils::canConnect(url, CNetworkUtils::getLongTimeoutMs())) // cppcheck-suppress knownConditionTrueFalse
{
ok = true;
break;
}
retry = sApp->cmdLineErrorMessage(QStringLiteral("URL '%1' not reachable").arg(urlString), "", true);
}
while (retry);
}
}
}
while (false);
m_localSetupFileValue = localSetupFileValue;
m_bootstrapUrlFileValue = bootstrapUrlFileValue;
m_bootstrapMode = bootstrapMode;
return ok;
}
void CSetupReader::gracefulShutdown()
{
m_shutdown = true;
}
void CSetupReader::forceAvailabilityUpdate()
{
this->manageSetupAvailability(false, false); // fake a failed web read
}
CSetupReader::BootstrapMode CSetupReader::stringToEnum(const QString &s)
{
const QString bsm(s.toLower().trimmed());
if (bsm.startsWith("expl")) { return Explicit; }
if (bsm.startsWith("cache")) { return CacheOnly; }
return Implicit;
}
const QString &CSetupReader::unitTestBootstrapUrl()
{
static const QString url("https://datastore.swift-project.org/shared");
return url;
}
CStatusMessageList CSetupReader::readLocalBootstrapFile(const QString &fileName)
{
if (fileName.isEmpty()) { return CStatusMessage(this).error(u"No file name for local bootstrap file"); }
if (!sApp || sApp->isShuttingDown()) { return CStatusMessage(this).error(u"No sApp, shutting down?"); }
QString fn;
const QFile file(fileName);
if (!file.exists())
{
// relative name?
const QString dir(sApp->getCmdSwiftPrivateSharedDir());
if (dir.isEmpty()) { return CStatusMessage(this).error(u"Empty shared directory '%1' for bootstrap file") << dir; }
// no version for local files, as those come with the current code
fn = CFileUtils::appendFilePaths(dir, "bootstrap/" + CSwiftDirectories::bootstrapFileName());
}
else
{
fn = fileName;
}
const QString content(CFileUtils::readFileToString(fn));
if (content.isEmpty()) { return CStatusMessage(this).error(u"File '%1' not existing or empty") << fn; }
try
{
CGlobalSetup s;
s.convertFromJson(content);
s.markAsLoadedFromFile(true);
const CStatusMessage setMsg = m_setup.set(s);
const CStatusMessage setInfo = CStatusMessage(this).info(u"Setup cache updated from local file '%1'") << fn;
return setMsg.isSuccess() ? setInfo : setMsg;
}
catch (const CJsonException &ex)
{
return CStatusMessage::fromJsonException(ex, this, QStringLiteral("Parsing local setup file '%1'").arg(fn));
}
}
const QStringList &CSetupReader::getLogCategories()
{
static const QStringList cats({ "swift.setupreader", CLogCategories::webservice(), CLogCategories::startup() });
return cats;
}
bool CSetupReader::hasCmdLineBootstrapUrl() const
{
return !this->getCmdLineBootstrapUrl().isEmpty();
}
QString CSetupReader::getCmdLineBootstrapUrl() const
{
if (m_ignoreCmdBootstrapUrl) return {};
return sApp->getParserValue(m_cmdBootstrapUrl);
}
void CSetupReader::setIgnoreCmdLineBootstrapUrl(bool ignore)
{
m_ignoreCmdBootstrapUrl = ignore;
this->parseCmdLineArguments(); // T156 this part not threadsafe, currently not a real problem as setup reader runs in main thread
}
CGlobalSetup CSetupReader::getSetup() const
{
return m_setup.get();
}
bool CSetupReader::hasCachedSetup() const
{
const CGlobalSetup cachedSetup = m_setup.get();
const bool cacheAvailable = cachedSetup.wasLoaded();
return cacheAvailable;
}
QDateTime CSetupReader::getSetupCacheTimestamp() const
{
return m_setup.getTimestamp();
}
bool CSetupReader::prefillCacheWithLocalResourceBootstrapFile()
{
if (m_shutdown) { return false; }
m_setup.synchronize(); // make sure it is loaded
const CGlobalSetup cachedSetup = m_setup.get();
const bool cacheAvailable = cachedSetup.wasLoaded();
if (cacheAvailable)
{
CLogMessage(this).info(u"Setup cache prefill (bootstrap already cached, no prefill needed");
return false;
}
const QString fn = CSwiftDirectories::bootstrapResourceFilePath();
const CStatusMessageList msgs = this->readLocalBootstrapFile(fn);
CLogMessage::preformatted(msgs);
return true;
}
QString CSetupReader::getLastSuccessfulSetupUrl() const
{
QReadLocker l(&m_lockSetup);
return m_lastSuccessfulSetupUrl;
}
void CSetupReader::synchronize()
{
m_setup.synchronize();
}
CStatusMessageList CSetupReader::getLastSetupReadErrorMessages() const
{
QReadLocker l(&m_lockSetup);
return m_setupReadErrorMsgs;
}
const QString &CSetupReader::getBootstrapUrlFile() const
{
if (!m_localSetupFileValue.isEmpty()) { return m_localSetupFileValue; }
return m_bootstrapUrlFileValue;
}
QString CSetupReader::getBootstrapModeAsString() const
{
switch (m_bootstrapMode)
{
case CacheOnly: return QStringLiteral("cache only");
case Explicit: return QStringLiteral("explicit");
case Implicit: return QStringLiteral("implicit");
default: break;
}
return {};
}
void CSetupReader::setLastSetupReadErrorMessages(const CStatusMessageList &messages)
{
QWriteLocker l(&m_lockSetup);
m_setupReadErrorMsgs = messages.getErrorMessages();
}
void CSetupReader::networkReplyProgress(int logId, qint64 current, qint64 max, const QUrl &url)
{
Q_UNUSED(url)
Q_UNUSED(logId)
Q_UNUSED(current)
Q_UNUSED(max)
}
CStatusMessageList CSetupReader::manageSetupAvailability(bool webRead, bool localRead)
{
Q_ASSERT_X(!(webRead && localRead), Q_FUNC_INFO, "Local and web read together seems to be wrong");
CStatusMessageList msgs;
bool available = false;
if (webRead || localRead)
{
available = true;
}
else
{
const bool cacheAvailable = m_setup.get().wasLoaded(); // loaded from web or file
available = cacheAvailable && m_bootstrapMode != Explicit;
}
if (available && !webRead && !localRead)
{
msgs.push_back(CStatusMessage(this, CStatusMessage::SeverityInfo, u"Setup available, but not updated this time"));
}
else if (!available)
{
msgs.push_back(CStatusMessage(this, CStatusMessage::SeverityError, u"Setup not available"));
if (m_bootstrapMode == Explicit)
{
msgs.push_back(CStatusMessage(this, CStatusMessage::SeverityError, u"Mode is 'explicit', likely URL '" % m_bootstrapUrlFileValue % u"' is not reachable"));
}
}
m_setupAvailable = available;
emit this->setupHandlingCompleted(available);
return msgs;
}
} // namespace