swift
statusmessage.cpp
1 // SPDX-FileCopyrightText: Copyright (C) 2013 swift Project Community / Contributors
2 // SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-swift-pilot-client-1
3 
4 #include "misc/statusmessage.h"
5 
6 #include <QMetaEnum>
7 #include <QStringBuilder>
8 #include <QThreadStorage>
9 
10 #include "misc/comparefunctions.h"
11 #include "misc/iconlist.h"
12 #include "misc/propertyindexref.h"
13 #include "misc/stringutils.h"
14 #include "misc/verify.h"
15 
17 
18 namespace swift::misc
19 {
20  namespace private_ns
21  {
22  QThreadStorage<QString> t_tempBuffer; // thread_local would be destroyed before function-scope statics, see T495
23 
24  QString arg(QStringView format, const QStringList &args)
25  {
26  if (format.isEmpty()) { return args.join(u' '); }
27 
28  QString &temp = t_tempBuffer.localData();
29  temp.resize(
30  0); // unlike clear(), resize(0) doesn't release the capacity if there are no implicitly shared copies
31 
32  quint64 unusedArgs = (1ULL << std::min(qsizetype(63), args.size())) - 1;
33  for (auto it = format.begin();;)
34  {
35  const auto pc = std::find(it, format.end(), u'%');
36  temp.append(&*it, std::distance(it, pc));
37  if ((it = pc) == format.end()) { break; }
38  if (++it == format.end())
39  {
40  temp += u'%';
41  break;
42  }
43 
44  if (*it == u'%')
45  {
46  temp += u'%';
47  ++it;
48  continue;
49  }
50  if (is09(*it))
51  {
52  int n = it->unicode() - u'0';
53  SWIFT_VERIFY(n >= 0 && n <= 9);
54  if (++it != format.end() && is09(*it))
55  {
56  n = n * 10 + it->unicode() - u'0';
57  ++it;
58  }
59  SWIFT_VERIFY(n > 0 && n <= 99);
60  if (n > 0 && n <= args.size())
61  {
62  temp += args[n - 1];
63  unusedArgs &= ~(1ULL << (n - 1));
64  }
65  else { temp += u'%' % QString::number(n); }
66  }
67  else { temp += u'%'; }
68  }
69  if (unusedArgs) { temp += QStringLiteral(" [SOME MESSAGE ARGUMENT(S) UNUSED]"); }
70 
71  QString result = temp;
72  result.squeeze(); // release unused capacity and implicitly detach so temp keeps its capacity for next time
73  return result;
74  }
75  } // namespace private_ns
76 
78  : CMessageBase(category), ITimestampBased(QDateTime::currentMSecsSinceEpoch())
79  {}
80 
82  : CMessageBase(categories), ITimestampBased(QDateTime::currentMSecsSinceEpoch())
83  {}
84 
86  : CMessageBase(categories, extra), ITimestampBased(QDateTime::currentMSecsSinceEpoch())
87  {}
88 
90  : CMessageBase(categories, extra), ITimestampBased(QDateTime::currentMSecsSinceEpoch())
91  {}
92 
93  CStatusMessage::CStatusMessage() : ITimestampBased(QDateTime::currentMSecsSinceEpoch()) {}
94 
96  : CValueObject(other), CMessageBase(other), ITimestampBased(other), IOrderable(other)
97  {
98  QReadLocker lock(&other.m_lock);
99  m_handledByObjects = other.m_handledByObjects;
100  }
101 
103  {
104  if (this == &other) { return *this; }
105 
106  static_cast<CMessageBase &>(*this) = other;
107  static_cast<ITimestampBased &>(*this) = other;
108  static_cast<IOrderable &>(*this) = other;
109 
110  // locks because of mutable member
111  QReadLocker readLock(&other.m_lock);
112  const auto handledBy = other.m_handledByObjects;
113  readLock.unlock(); // avoid deadlock
114 
115  QWriteLocker writeLock(&m_lock);
116  m_handledByObjects = handledBy;
117  return *this;
118  }
119 
120  CStatusMessage::CStatusMessage(QStringView message) : ITimestampBased(QDateTime::currentMSecsSinceEpoch())
121  {
122  m_message = CStrongStringView(message.trimmed());
123  }
124 
125  CStatusMessage::CStatusMessage(const QString &message) : ITimestampBased(QDateTime::currentMSecsSinceEpoch())
126  {
127  m_message = message.trimmed();
128  }
129 
130  CStatusMessage::CStatusMessage(StatusSeverity severity, QStringView message) : CStatusMessage(message)
131  {
132  m_severity = severity;
133  }
134 
135  CStatusMessage::CStatusMessage(StatusSeverity severity, const QString &message) : CStatusMessage(message)
136  {
137  m_severity = severity;
138  }
139 
140  CStatusMessage::CStatusMessage(const CLogCategoryList &categories, StatusSeverity severity, QStringView message,
141  bool validation)
142  : CStatusMessage(severity, message)
143  {
144  m_categories = categories;
145  if (validation) { this->addValidationCategory(); }
146  }
147 
148  CStatusMessage::CStatusMessage(const CLogCategoryList &categories, StatusSeverity severity, const QString &message,
149  bool validation)
150  : CStatusMessage(severity, message)
151  {
152  m_categories = categories;
153  if (validation) { this->addValidationCategory(); }
154  }
155 
156  CStatusMessage::CStatusMessage(QtMsgType type, const QMessageLogContext &context, const QString &message)
157  : CStatusMessage(message.trimmed())
158  {
159  m_categories = CLogCategoryList::fromQString(context.category);
160 
161  switch (type)
162  {
163  default:
164  case QtDebugMsg: m_severity = SeverityDebug; break;
165  case QtInfoMsg: m_severity = SeverityInfo; break;
166  case QtWarningMsg: m_severity = SeverityWarning; break;
167  case QtCriticalMsg:
168  case QtFatalMsg: m_severity = SeverityError; break;
169  }
170  }
171 
172  void CStatusMessage::toQtLogTriple(QtMsgType *o_type, QString *o_category, QString *o_message) const
173  {
174  *o_category = m_categories.toQString();
175  *o_message = this->getMessage();
176 
177  switch (m_severity)
178  {
179  default:
180  case SeverityDebug: *o_type = QtDebugMsg; break;
181  case SeverityInfo: *o_type = QtInfoMsg; break;
182  case SeverityWarning: *o_type = QtWarningMsg; break;
183  case SeverityError: *o_type = QtCriticalMsg; break;
184  }
185  }
186 
188 
190  {
191  if (this->getSeverity() <= severity) { return false; }
192  this->setSeverity(severity);
193  return true;
194  }
195 
197  {
198  return this->getSeverity() >= severity;
199  }
200 
201  bool CStatusMessage::isSuccess() const { return !this->isFailure(); }
202 
203  bool CStatusMessage::isFailure() const { return this->getSeverity() == SeverityError; }
204 
206  {
207  const QString m = this->getMessage();
208  if (!containsLineBreakOrTab(m)) { return m; } // by far most messages will NOT contain tabs/CR
209  return removeLineBreakAndTab(m);
210  }
211 
212  void CStatusMessage::prependMessage(const QString &msg)
213  {
214  if (msg.isEmpty()) { return; }
215  m_message = QString(msg % m_message.view());
216  }
217 
218  void CStatusMessage::appendMessage(const QString &msg)
219  {
220  if (msg.isEmpty()) { return; }
221  m_message = QString(m_message.view() % msg);
222  }
223 
224  void CStatusMessage::markAsHandledBy(const QObject *object) const
225  {
226  QWriteLocker lock(&m_lock);
227  m_handledByObjects.push_back(quintptr(object));
228  }
229 
230  bool CStatusMessage::wasHandledBy(const QObject *object) const
231  {
232  QReadLocker lock(&m_lock);
233  return m_handledByObjects.contains(quintptr(object));
234  }
235 
236  QString CStatusMessage::convertToQString(bool ) const
237  {
238  return u"Category: " % m_categories.toQString() %
239 
240  u" Severity: " % severityToString(m_severity) %
241 
242  u" when: " % this->getFormattedUtcTimestampYmdhms() %
243 
244  u' ' % this->getMessage();
245  }
246 
248  {
249  return convertToIcon(statusMessage.getSeverity());
250  }
251 
253  {
254  switch (severity)
255  {
256  case SeverityDebug: return CIcon::iconByIndex(CIcons::StandardIconUnknown16); // TODO
257  case SeverityInfo: return CIcon::iconByIndex(CIcons::StandardIconInfo16);
258  case SeverityWarning: return CIcon::iconByIndex(CIcons::StandardIconWarning16);
259  case SeverityError: return CIcon::iconByIndex(CIcons::StandardIconError16);
260  default: return CIcon::iconByIndex(CIcons::StandardIconInfo16);
261  }
262  }
263 
265  {
266  static const QString d;
267  static const QString i(":/pastel/icons/pastel/16/infomation.png");
268  static const QString w(":/pastel/icons/pastel/16/bullet-error.png");
269  static const QString e(":/pastel/icons/pastel/16/close-red.png");
270 
271  switch (severity)
272  {
273  case SeverityDebug: return d;
274  case SeverityInfo: return i;
275  case SeverityWarning: return w;
276  case SeverityError: return e;
277  default: return d;
278  }
279  }
280 
282  {
283  const QString msgText(json.value("text").toString());
284  const QString severityText(json.value("severity").toString());
285  QString typeText(json.value("type").toString());
286  StatusSeverity severity = stringToSeverity(severityText);
287 
288  typeText = u"swift.db.type." % typeText.toLower().remove(' ');
289  const CStatusMessage m({ CLogCategories::swiftDbWebservice(), typeText }, severity, msgText);
290  return m;
291  }
292 
294  const QString &prefix)
295  {
296  return CStatusMessage(categories).validationError(ex.toString(prefix));
297  }
298 
300  {
302  qRegisterMetaType<CStatusMessage::StatusSeverity>();
303  qDBusRegisterMetaType<CStatusMessage::StatusSeverity>();
304  }
305 
307  {
308  // pre-check
309  QString severityString(severity.trimmed().toLower());
310  if (severityString.isEmpty()) { return SeverityInfo; }
311 
312  // hard check
313  if (severityString.compare(severityToString(SeverityDebug), Qt::CaseInsensitive) == 0) { return SeverityDebug; }
314  if (severityString.compare(severityToString(SeverityInfo), Qt::CaseInsensitive) == 0) { return SeverityInfo; }
315  if (severityString.compare(severityToString(SeverityWarning), Qt::CaseInsensitive) == 0)
316  {
317  return SeverityWarning;
318  }
319  if (severityString.compare(severityToString(SeverityError), Qt::CaseInsensitive) == 0) { return SeverityError; }
320 
321  // not found yet, lenient checks
322  QChar s = severityString.at(0);
323  if (s == 'd') { return SeverityDebug; }
324  if (s == 'i') { return SeverityInfo; }
325  if (s == 'w') { return SeverityWarning; }
326  if (s == 'e') { return SeverityError; }
327 
328  return SeverityInfo;
329  }
330 
332  {
333  switch (severity)
334  {
335  case SeverityDebug:
336  {
337  static const QString d("debug");
338  return d;
339  }
340  case SeverityInfo:
341  {
342  static const QString i("info");
343  return i;
344  }
345  case SeverityWarning:
346  {
347  static const QString w("warning");
348  return w;
349  }
350  case SeverityError:
351  {
352  static const QString e("error");
353  return e;
354  }
355  default:
356  {
357  static const QString x("unknown severity");
358  qFatal("Unknown severity");
359  return x; // just for compiler warning
360  }
361  }
362  }
363 
364  QString CStatusMessage::severitiesToString(const QSet<CStatusMessage::StatusSeverity> &severities)
365  {
366  if (severities.isEmpty()) { return {}; }
367  auto minmax = std::minmax_element(severities.begin(), severities.end());
368  auto min = *minmax.first;
369  auto max = *minmax.second;
370  if (min == SeverityDebug && max == SeverityError)
371  {
372  static const QString all("all severities");
373  return all;
374  }
375  if (min == SeverityDebug) { return u"at or below " % severityToString(max); }
376  if (max == SeverityError) { return u"at or above " % severityToString(min); }
377  auto list = severities.values();
378  std::sort(list.begin(), list.end());
379  QStringList ret;
380  std::transform(list.cbegin(), list.cend(), std::back_inserter(ret), severityToString);
381  return ret.join("|");
382  }
383 
385 
387 
389  {
390  static const QStringList all { severityToString(SeverityDebug), severityToString(SeverityInfo),
392  return all;
393  }
394 
396  {
397  if (index.isMyself()) { return QVariant::fromValue(*this); }
399  if (IOrderable::canHandleIndex(index)) { return IOrderable::propertyByIndex(index); }
400  const ColumnIndex i = index.frontCasted<ColumnIndex>();
401  switch (i)
402  {
403  case IndexMessage: return QVariant::fromValue(this->getMessage());
404  case IndexSeverity: return QVariant::fromValue(m_severity);
405  case IndexSeverityAsString: return QVariant::fromValue(this->getSeverityAsString());
406  case IndexSeverityAsIcon: return QVariant::fromValue(this->getSeverityAsIcon());
407  case IndexCategoriesAsString: return QVariant::fromValue(m_categories.toQString());
408  case IndexMessageAsHtml: return QVariant::fromValue(this->toHtml(false, true));
409  default: return CValueObject::propertyByIndex(index);
410  }
411  }
412 
413  void CStatusMessage::setPropertyByIndex(CPropertyIndexRef index, const QVariant &variant)
414  {
415  if (index.isMyself())
416  {
417  (*this) = variant.value<CStatusMessage>();
418  return;
419  }
421  {
422  ITimestampBased::setPropertyByIndex(index, variant);
423  return;
424  }
425  if (IOrderable::canHandleIndex(index))
426  {
427  IOrderable::setPropertyByIndex(index, variant);
428  return;
429  }
430  const ColumnIndex i = index.frontCasted<ColumnIndex>();
431  switch (i)
432  {
433  case IndexMessage:
434  m_message = variant.value<QString>();
435  m_args.clear();
436  break;
437  case IndexSeverity: m_severity = variant.value<StatusSeverity>(); break;
438  case IndexCategoriesAsString: m_categories = variant.value<CLogCategoryList>(); break;
439  default: CValueObject::setPropertyByIndex(index, variant); break;
440  }
441  }
442 
444  {
445  if (index.isMyself()) { return Compare::compare(this->getSeverity(), compareValue.getSeverity()); }
447  {
448  return ITimestampBased::comparePropertyByIndex(index, compareValue);
449  }
450  if (IOrderable::canHandleIndex(index)) { return IOrderable::comparePropertyByIndex(index, compareValue); }
451  const ColumnIndex i = index.frontCasted<ColumnIndex>();
452  switch (i)
453  {
454  case IndexMessageAsHtml:
455  case IndexMessage: return this->getMessage().compare(compareValue.getMessage());
456  case IndexSeverityAsString:
457  case IndexSeverityAsIcon:
458  case IndexSeverity: return Compare::compare(this->getSeverity(), compareValue.getSeverity());
459  case IndexCategoriesAsString:
460  return this->getCategoriesAsString().compare(compareValue.getCategoriesAsString());
461  default: break;
462  }
463  return CValueObject::comparePropertyByIndex(index, compareValue);
464  }
465 
466  QString CStatusMessage::toHtml(bool withIcon, bool withColors) const
467  {
468  QString img;
469  if (withIcon)
470  {
471  const QString r = convertToIconResource(this->getSeverity());
472  if (!r.isEmpty()) { img = QStringLiteral("<img src=\"%1\"> ").arg(r); }
473  }
474 
475  if (withColors)
476  {
477  switch (this->getSeverity())
478  {
479  case SeverityWarning: return img % u"<font color=\"yellow\">" % this->getMessage() % u"</font>";
480  case SeverityError: return img % u"<font color=\"red\">" % this->getMessage() % u"</font>";
481  case SeverityDebug: break;
482  default: break;
483  }
484  }
485  return img % this->getMessage();
486  }
487 } // namespace swift::misc
Value object for icons. An icon is stored in the global icon repository and identified by its index....
Definition: icon.h:39
static const CIcon & iconByIndex(CIcons::IconIndex index)
Icon for given index.
Definition: icon.cpp:54
Thrown when a convertFromJson method encounters an unrecoverable error in JSON data.
Definition: jsonexception.h:24
QString toString(const QString &prefix) const
As string info.
static const QString & swiftDbWebservice()
Webservice with swift DB.
A log category is an arbitrary string tag which can be attached to log messages.
Definition: logcategory.h:28
A sequence of log categories.
static CLogCategoryList fromQString(const QString &string)
Convert a string, such as that returned by toQString(), into a CLogCategoryList.
QString toQString(bool i18n=false) const
Cast as QString.
Base class for CStatusMessage and CLogMessage.
CStatusMessage & validation(StatusSeverity s, const char16_t(&format)[N])
Set the severity to s, providing a format string, and adding the validation category.
Non-owning reference to a CPropertyIndex with a subset of its features.
CastType frontCasted() const
First element casted to given type, usually the PropertIndex enum.
bool isMyself() const
Myself index, used with nesting.
Streamable status message, e.g.
void markAsHandledBy(const QObject *object) const
Mark the message as having been handled by the given object.
QString getCategoriesAsString() const
Message categories as string.
static CStatusMessage fromJsonException(const CJsonException &ex, const CLogCategoryList &categories, const QString &prefix)
Object from JSON exception message.
bool isSuccess() const
Operation considered successful.
static const QString & convertToIconResource(CStatusMessage::StatusSeverity severity)
Representing icon.
const QString & getSeverityAsString() const
Severity as string.
constexpr static auto SeverityDebug
Status severities.
const CIcon & getSeverityAsIcon() const
Severity as icon.
void appendMessage(const QString &msg)
Append message.
bool isSeverityHigherOrEqual(CStatusMessage::StatusSeverity severity) const
Is this message's severity higher or equal.
constexpr static auto SeverityError
Status severities.
void toQtLogTriple(QtMsgType *o_type, QString *o_category, QString *o_message) const
Convert to a Qt logging triple.
QVariant propertyByIndex(swift::misc::CPropertyIndexRef index) const
Property by index.
void addValidationCategory()
Adds validation as category.
static void registerMetadata()
Register metadata.
static const QString & severityToString(StatusSeverity severity)
Severity as string.
static const CIcon & convertToIcon(const CStatusMessage &statusMessage)
Representing icon.
static StatusSeverity stringToSeverity(const QString &severity)
Severity as string, if not possible to convert.
bool clampSeverity(StatusSeverity severity)
Clip/reduce severity if higher (more critical)
StatusSeverity getSeverity() const
Message severity.
void setSeverity(StatusSeverity severity)
Severity.
QString getMessage() const
Message.
CStatusMessage & operator=(const CStatusMessage &other)
Copy assignment (because of mutex)
static const QStringList & allSeverityStrings()
Severities as strings.
QString convertToQString(bool i18n=false) const
Cast as QString.
int comparePropertyByIndex(CPropertyIndexRef index, const CStatusMessage &compareValue) const
Compare for index.
void prependMessage(const QString &msg)
Prepend message.
bool wasHandledBy(const QObject *object) const
Returns true if the message was marked as having been handled by the given object.
constexpr static auto SeverityInfo
Status severities.
constexpr static auto SeverityWarning
Status severities.
void setPropertyByIndex(swift::misc::CPropertyIndexRef index, const QVariant &variant)
Set property by index.
bool isFailure() const
Operation considered unsuccessful.
static CStatusMessage fromDatabaseJson(const QJsonObject &json)
Object from JSON.
QString toHtml(bool withIcon, bool withColors) const
To HTML.
static QString severitiesToString(const QSet< StatusSeverity > &severities)
Severity set as string.
QString getMessageNoLineBreaks() const
Message without line breaks.
Special-purpose string class used by CMessageBase.
Definition: statusmessage.h:56
QStringView view() const
Return as a QStringView.
Mix of the most commonly used mixin classes.
Definition: valueobject.h:114
Entity with order attribute (can be manually ordered in views)
Definition: orderable.h:19
static bool canHandleIndex(CPropertyIndexRef index)
Can given index be handled.
Definition: orderable.cpp:33
int comparePropertyByIndex(CPropertyIndexRef index, const IOrderable &compareValue) const
Compare for index.
Definition: orderable.cpp:73
void setPropertyByIndex(CPropertyIndexRef index, const QVariant &variant)
Set property by index.
Definition: orderable.cpp:57
QVariant propertyByIndex(CPropertyIndexRef index) const
Property by index.
Definition: orderable.cpp:40
Entity with timestamp.
void setPropertyByIndex(CPropertyIndexRef index, const QVariant &variant)
Set property by index.
int comparePropertyByIndex(CPropertyIndexRef index, const ITimestampBased &compareValue) const
Compare for index.
QString getFormattedUtcTimestampYmdhms() const
As yyyy MM dd HH mm ss.
static bool canHandleIndex(CPropertyIndexRef index)
Can given index be handled.
QVariant propertyByIndex(CPropertyIndexRef index) const
Property by index.
ColumnIndex
Base class enums.
Definition: mixinindex.h:44
int comparePropertyByIndex(CPropertyIndexRef index, const Derived &compareValue) const
Compare for index.
Definition: mixinindex.h:187
void setPropertyByIndex(CPropertyIndexRef index, const QVariant &variant)
Set property by index.
Definition: mixinindex.h:160
QVariant propertyByIndex(CPropertyIndexRef index) const
Property by index.
Definition: mixinindex.h:167
static void registerMetadata()
Register metadata.
Definition: mixinmetatype.h:56
Free functions in swift::misc.
bool is09(const QChar &c)
Is 0-9 char, isDigit allows a bunch of more characters.
Definition: stringutils.h:154
bool containsLineBreakOrTab(const QString &s)
Contains a line break or tab.
Definition: stringutils.h:71
StatusSeverity
Status severities.
Definition: statusmessage.h:35
QString removeLineBreakAndTab(const QString &s)
Remove line breaks and tabs.
Definition: stringutils.h:55
#define SWIFT_DEFINE_VALUEOBJECT_MIXINS(Namespace, Class)
Explicit template definition of mixins for a CValueObject subclass.
Definition: valueobject.h:67
#define SWIFT_VERIFY(COND)
A weaker kind of assert.
Definition: verify.h:29