/* Copyright (C) 2013 * 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. 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 "statusmessage.h" #include "statusexception.h" #include "propertyindex.h" #include "iconlist.h" #include "loghandler.h" #include "logmessage.h" #include "comparefunctions.h" #include "stringutils.h" #include #include #include namespace BlackMisc { namespace Private { QThreadStorage t_tempBuffer; // thread_local would be destroyed before function-scope statics, see T495 QString arg(QStringView format, const QStringList &args) { if (format.isEmpty()) { return args.join(u' '); } QString &temp = t_tempBuffer.localData(); temp.resize(0); // unlike clear(), resize(0) doesn't release the capacity if there are no implicitly shared copies quint64 unusedArgs = (1ULL << std::min(63, args.size())) - 1; for (auto it = format.begin(); ; ) { const auto pc = std::find(it, format.end(), u'%'); temp.append(&*it, std::distance(it, pc)); if ((it = pc) == format.end()) { break; } if (++it == format.end()) { temp += u'%'; break; } if (*it == u'%') { temp += u'%'; ++it; continue; } if (is09(*it)) { int n = it->unicode() - u'0'; Q_ASSERT(n >= 0 && n <= 9); if (++it != format.end() && is09(*it)) { n = n * 10 + it->unicode() - u'0'; ++it; } Q_ASSERT(n >= 0 && n <= 99); if (n <= args.size()) { temp += args[n - 1]; unusedArgs &= ~(1ULL << (n - 1)); } else { temp += u'%' % QString::number(n); } } else { temp += u'%'; } } if (unusedArgs) { temp += QStringLiteral(" [SOME MESSAGE ARGUMENT(S) UNUSED]"); } QString result = temp; result.squeeze(); // release unused capacity and implicitly detach so temp keeps its capacity for next time return result; } } // needed because these constants are odr-used (just like traditional C++98 static const) // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=54483 const StatusSeverity CStatusMessage::SeverityDebug; const StatusSeverity CStatusMessage::SeverityInfo; const StatusSeverity CStatusMessage::SeverityWarning; const StatusSeverity CStatusMessage::SeverityError; CStatusMessage::CStatusMessage(const CLogCategory &category) : CMessageBase(category), ITimestampBased(QDateTime::currentMSecsSinceEpoch()) {} CStatusMessage::CStatusMessage(const CLogCategoryList &categories) : CMessageBase(categories), ITimestampBased(QDateTime::currentMSecsSinceEpoch()) {} CStatusMessage::CStatusMessage(const CLogCategoryList &categories, const CLogCategory &extra) : CMessageBase(categories, extra), ITimestampBased(QDateTime::currentMSecsSinceEpoch()) {} CStatusMessage::CStatusMessage(const CLogCategoryList &categories, const CLogCategoryList &extra) : CMessageBase(categories, extra), ITimestampBased(QDateTime::currentMSecsSinceEpoch()) {} CStatusMessage::CStatusMessage(): ITimestampBased(QDateTime::currentMSecsSinceEpoch()) {} CStatusMessage::CStatusMessage(const CStatusMessage &other) : CValueObject(other), CMessageBase(other), ITimestampBased(other), IOrderable(other) { QReadLocker lock(&other.m_lock); m_handledByObjects = other.m_handledByObjects; } CStatusMessage &CStatusMessage::operator =(const CStatusMessage &other) { // locks because of mutable members if (this == &other) { return *this; } static_cast(*this) = other; QReadLocker readLock(&other.m_lock); const auto handledBy = other.m_handledByObjects; const qint64 ts = other.m_timestampMSecsSinceEpoch; const int order = other.m_order; readLock.unlock(); // avoid deadlock QWriteLocker writeLock(&m_lock); m_handledByObjects = handledBy; m_timestampMSecsSinceEpoch = ts; m_order = order; return *this; } CStatusMessage::CStatusMessage(QStringView message) : ITimestampBased(QDateTime::currentMSecsSinceEpoch()) { m_message = CStrongStringView(message.trimmed()); } CStatusMessage::CStatusMessage(const QString &message) : ITimestampBased(QDateTime::currentMSecsSinceEpoch()) { m_message = message.trimmed(); } CStatusMessage::CStatusMessage(StatusSeverity severity, QStringView message) : CStatusMessage(message) { m_severity = severity; } CStatusMessage::CStatusMessage(StatusSeverity severity, const QString &message) : CStatusMessage(message) { m_severity = severity; } CStatusMessage::CStatusMessage(const CLogCategoryList &categories, StatusSeverity severity, QStringView message, bool validation) : CStatusMessage(severity, message) { m_categories = categories; if (validation) { this->addValidationCategory(); } } CStatusMessage::CStatusMessage(const CLogCategoryList &categories, StatusSeverity severity, const QString &message, bool validation) : CStatusMessage(severity, message) { m_categories = categories; if (validation) { this->addValidationCategory(); } } CStatusMessage::CStatusMessage(QtMsgType type, const QMessageLogContext &context, const QString &message) : CStatusMessage(message.trimmed()) { m_categories = CLogCategoryList::fromQString(context.category); switch (type) { default: case QtDebugMsg: m_severity = SeverityDebug; break; case QtInfoMsg: m_severity = SeverityInfo; break; case QtWarningMsg: m_severity = SeverityWarning; break; case QtCriticalMsg: case QtFatalMsg: m_severity = SeverityError; break; } } void CStatusMessage::toQtLogTriple(QtMsgType *o_type, QString *o_category, QString *o_message) const { *o_category = m_categories.toQString(); *o_message = this->getMessage(); switch (m_severity) { default: case SeverityDebug: *o_type = QtDebugMsg; break; case SeverityInfo: *o_type = QtInfoMsg; break; case SeverityWarning: *o_type = QtWarningMsg; break; case SeverityError: *o_type = QtCriticalMsg; break; } } CStatusException CStatusMessage::asException() const { return CStatusException(*this); } void CStatusMessage::maybeThrow() const { if (! this->isEmpty()) { throw this->asException(); } } QString CStatusMessage::getCategoriesAsString() const { return m_categories.toQString(); } QString CStatusMessage::getHumanReadablePattern() const { const QStringList patternNames(getHumanReadablePatterns()); return patternNames.isEmpty() ? QString() : patternNames.join(", "); } QStringList CStatusMessage::getHumanReadablePatterns() const { QStringList patternNames; for (const QString &name : CLogPattern::allHumanReadableNames()) { if (CLogPattern::fromHumanReadableName(name).match(*this)) { patternNames.push_back(name); } } return patternNames; } QString CStatusMessage::getHumanOrTechnicalCategoriesAsString() const { if (m_categories.isEmpty()) { return {}; } const QString c(getHumanReadablePattern()); return c.isEmpty() ? this->getCategoriesAsString() : c; } bool CStatusMessage::clampSeverity(CStatusMessage::StatusSeverity severity) { if (this->getSeverity() <= severity) { return false; } this->setSeverity(severity); return true; } bool CStatusMessage::isSeverityHigherOrEqual(CStatusMessage::StatusSeverity severity) const { return this->getSeverity() >= severity; } bool CStatusMessage::isSuccess() const { return !this->isFailure(); } bool CStatusMessage::isFailure() const { return this->getSeverity() == SeverityError; } QString CStatusMessage::getMessageNoLineBreaks() const { const QString m = this->getMessage(); if (!containsLineBreakOrTab(m)) { return m; } // by far most messages will NOT contain tabs/CR return removeLineBreakAndTab(m); } void CStatusMessage::prependMessage(const QString &msg) { if (msg.isEmpty()) { return; } m_message = QString(msg % m_message.view()); } void CStatusMessage::appendMessage(const QString &msg) { if (msg.isEmpty()) { return; } m_message = QString(m_message.view() % msg); } void CStatusMessage::markAsHandledBy(const QObject *object) const { QWriteLocker lock(&m_lock); m_handledByObjects.push_back(quintptr(object)); } bool CStatusMessage::wasHandledBy(const QObject *object) const { QReadLocker lock(&m_lock); return m_handledByObjects.contains(quintptr(object)); } QString CStatusMessage::convertToQString(bool /** i18n */) const { return u"Category: " % m_categories.toQString() % u" Severity: " % severityToString(m_severity) % u" when: " % this->getFormattedUtcTimestampYmdhms() % u' ' % this->getMessage(); } const CIcon &CStatusMessage::convertToIcon(const CStatusMessage &statusMessage) { return convertToIcon(statusMessage.getSeverity()); } const CIcon &CStatusMessage::convertToIcon(CStatusMessage::StatusSeverity severity) { switch (severity) { case SeverityDebug: return CIcon::iconByIndex(CIcons::StandardIconUnknown16); // TODO case SeverityInfo: return CIcon::iconByIndex(CIcons::StandardIconInfo16); case SeverityWarning: return CIcon::iconByIndex(CIcons::StandardIconWarning16); case SeverityError: return CIcon::iconByIndex(CIcons::StandardIconError16); default: return CIcon::iconByIndex(CIcons::StandardIconInfo16); } } const QString &CStatusMessage::convertToIconResource(CStatusMessage::StatusSeverity severity) { static const QString d; static const QString i(":/pastel/icons/pastel/16/infomation.png"); static const QString w(":/pastel/icons/pastel/16/bullet-error.png"); static const QString e(":/pastel/icons/pastel/16/close-red.png"); switch (severity) { case SeverityDebug: return d; case SeverityInfo: return i; case SeverityWarning: return w; case SeverityError: return e; default: return d; } } CStatusMessage CStatusMessage::fromDatabaseJson(const QJsonObject &json) { const QString msgText(json.value("text").toString()); const QString severityText(json.value("severity").toString()); QString typeText(json.value("type").toString()); StatusSeverity severity = stringToSeverity(severityText); typeText = u"swift.db.type." % typeText.toLower().remove(' '); const CStatusMessage m({ CLogCategory::swiftDbWebservice(), CLogCategory(typeText)}, severity, msgText); return m; } void CStatusMessage::registerMetadata() { CValueObject::registerMetadata(); qRegisterMetaType(); qDBusRegisterMetaType(); qRegisterMetaTypeStreamOperators(); } CStatusMessage::StatusSeverity CStatusMessage::stringToSeverity(const QString &severity) { // pre-check QString severityString(severity.trimmed().toLower()); if (severityString.isEmpty()) { return SeverityInfo; } // hard check if (severityString.compare(severityToString(SeverityDebug), Qt::CaseInsensitive) == 0) { return SeverityDebug; } if (severityString.compare(severityToString(SeverityInfo), Qt::CaseInsensitive) == 0) { return SeverityInfo; } if (severityString.compare(severityToString(SeverityWarning), Qt::CaseInsensitive) == 0) { return SeverityWarning; } if (severityString.compare(severityToString(SeverityError), Qt::CaseInsensitive) == 0) { return SeverityError; } // not found yet, lenient checks QChar s = severityString.at(0); if (s == 'd') { return SeverityDebug; } if (s == 'i') { return SeverityInfo; } if (s == 'w') { return SeverityWarning; } if (s == 'e') { return SeverityError; } return SeverityInfo; } const QString &CStatusMessage::severityToString(CStatusMessage::StatusSeverity severity) { switch (severity) { case SeverityDebug: { static const QString d("debug"); return d; } case SeverityInfo: { static const QString i("info"); return i; } case SeverityWarning: { static const QString w("warning"); return w; } case SeverityError: { static const QString e("error"); return e; } default: { static const QString x("unknown severity"); qFatal("Unknown severity"); return x; // just for compiler warning } } } QString CStatusMessage::severitiesToString(const QSet &severities) { if (severities.isEmpty()) { return {}; } auto minmax = std::minmax_element(severities.begin(), severities.end()); auto min = *minmax.first; auto max = *minmax.second; if (min == SeverityDebug && max == SeverityError) { static const QString all("all severities"); return all; } if (min == SeverityDebug) { return u"at or below " % severityToString(max); } if (max == SeverityError) { return u"at or above " % severityToString(min); } auto list = severities.toList(); std::sort(list.begin(), list.end()); QStringList ret; std::transform(list.cbegin(), list.cend(), std::back_inserter(ret), severityToString); return ret.join("|"); } const QString &CStatusMessage::getSeverityAsString() const { return severityToString(m_severity); } const CIcon &CStatusMessage::getSeverityAsIcon() const { return convertToIcon(m_severity); } const QStringList &CStatusMessage::allSeverityStrings() { static const QStringList all { severityToString(SeverityDebug), severityToString(SeverityInfo), severityToString(SeverityWarning), severityToString(SeverityError) }; return all; } CVariant CStatusMessage::propertyByIndex(const CPropertyIndex &index) const { if (index.isMyself()) { return CVariant::from(*this); } if (ITimestampBased::canHandleIndex(index)) { return ITimestampBased::propertyByIndex(index); } if (IOrderable::canHandleIndex(index)) { return IOrderable::propertyByIndex(index); } const ColumnIndex i = index.frontCasted(); switch (i) { case IndexMessage: return CVariant::from(this->getMessage()); case IndexSeverity: return CVariant::from(m_severity); case IndexSeverityAsString: return CVariant::from(this->getSeverityAsString()); case IndexSeverityAsIcon: return CVariant::from(this->getSeverityAsIcon()); case IndexCategoriesAsString: return CVariant::from(m_categories.toQString()); case IndexCategoriesHumanReadableAsString: return CVariant::from(this->getHumanReadablePattern()); case IndexCategoryHumanReadableOrTechnicalAsString: return CVariant::from(this->getHumanOrTechnicalCategoriesAsString()); case IndexMessageAsHtml: return CVariant::from(this->toHtml(false, true)); default: return CValueObject::propertyByIndex(index); } } void CStatusMessage::setPropertyByIndex(const CPropertyIndex &index, const CVariant &variant) { if (index.isMyself()) { (*this) = variant.to(); return; } if (ITimestampBased::canHandleIndex(index)) { ITimestampBased::setPropertyByIndex(index, variant); return; } if (IOrderable::canHandleIndex(index)) { IOrderable::setPropertyByIndex(index, variant); return; } const ColumnIndex i = index.frontCasted(); switch (i) { case IndexMessage: m_message = variant.value(); m_args.clear(); break; case IndexSeverity: m_severity = variant.value(); break; case IndexCategoriesAsString: m_categories = variant.value(); break; default: CValueObject::setPropertyByIndex(index, variant); break; } } int CStatusMessage::comparePropertyByIndex(const CPropertyIndex &index, const CStatusMessage &compareValue) const { if (index.isMyself()) { return Compare::compare(this->getSeverity(), compareValue.getSeverity()); } if (ITimestampBased::canHandleIndex(index)) { return ITimestampBased::comparePropertyByIndex(index, compareValue); } if (IOrderable::canHandleIndex(index)) { return IOrderable::comparePropertyByIndex(index, compareValue); } const ColumnIndex i = index.frontCasted(); switch (i) { case IndexMessageAsHtml: case IndexMessage: return this->getMessage().compare(compareValue.getMessage()); case IndexSeverityAsString: case IndexSeverityAsIcon: case IndexSeverity: return Compare::compare(this->getSeverity(), compareValue.getSeverity()); case IndexCategoriesAsString: return this->getCategoriesAsString().compare(compareValue.getCategoriesAsString()); case IndexCategoriesHumanReadableAsString: return this->getHumanReadablePattern().compare(compareValue.getHumanReadablePattern()); case IndexCategoryHumanReadableOrTechnicalAsString: return this->getHumanOrTechnicalCategoriesAsString().compare(compareValue.getHumanOrTechnicalCategoriesAsString()); default: break; } Q_ASSERT_X(false, Q_FUNC_INFO, "Comapre failed"); return 0; } QString CStatusMessage::toHtml(bool withIcon, bool withColors) const { QString img; if (withIcon) { const QString r = convertToIconResource(this->getSeverity()); if (!r.isEmpty()) { img = QStringLiteral(" ").arg(r); } } if (withColors) { switch (this->getSeverity()) { case SeverityWarning: return img % u"" % this->getMessage() % u""; case SeverityError: return img % u"" % this->getMessage() % u""; case SeverityDebug: break; default: break; } } return img % this->getMessage(); } } // ns