libQuotient
A Qt library for building matrix clients
util.h
Go to the documentation of this file.
1 // SPDX-FileCopyrightText: 2016 Kitsune Ral <kitsune-ral@users.sf.net>
2 // SPDX-FileCopyrightText: 2019 Alexey Andreyev <aa13q@ya.ru>
3 // SPDX-License-Identifier: LGPL-2.1-or-later
4 
5 #pragma once
6 
7 #include "quotient_export.h"
8 
9 #include <QtCore/QDebug>
10 #include <QtCore/QElapsedTimer>
11 #include <QtCore/QHashFunctions>
12 #include <QtCore/QLatin1String>
13 #include <QtCore/QUrl>
14 
15 #include <memory>
16 #include <optional>
17 #include <source_location>
18 #include <unordered_map>
19 
20 #define DECL_DEPRECATED_ENUMERATOR(Deprecated, Recommended)
21  Deprecated Q_DECL_ENUMERATOR_DEPRECATED_X("Use " #Recommended) = Recommended
22 
23 /// \brief Copy an object with slicing
24 ///
25 /// Unintended slicing is bad, which is why there's a C++ Core Guideline that
26 /// basically says "don't slice, or if you do, make it explicit". Sonar and
27 /// clang-tidy have warnings matching this guideline; unfortunately, those
28 /// warnings trigger even when you have a dedicated method (as the guideline
29 /// recommends) that makes a slicing copy.
30 ///
31 /// This macro is meant for cases when slicing is intended: the static cast
32 /// silences the static analysis warning, and the macro appearance itself makes
33 /// it very clear that slicing is wanted here. It is made as a macro
34 /// (not as a function template) to support the case of private inheritance
35 /// in which a function template would not be able to cast to the private base
36 /// (see Uri::toUrl() for an example of just that situation).
37 #define SLICE(Object, ToType) ToType{static_cast<const ToType&>(Object)}
38 
39 namespace Quotient {
40 
41 namespace _impl {
42  template <typename S>
43  constexpr inline auto toUtf8(S&& s)
44  {
45  if constexpr (std::convertible_to<S, std::string_view>)
46  return std::string_view(std::forward<S>(s));
47  else if constexpr (std::convertible_to<S, QByteArray>)
48  return QByteArray(std::forward<S>(s));
49  else //if constexpr (std::convertible_to<S, QString>)
50  return QString(std::forward<S>(s)).toUtf8();
51  }
52 }
53 
54 //! \brief q(Utf8)Printable that can handle more than just QStrings
55 //!
56 //! This macro accepts all kinds of string-like input, from const char* all the way to raw
57 //! QStringBuilder constructs. It returns a `const char*` pointer to a UTF-8 string; if the original
58 //! input was QChar/u16-based, it creates a temporary buffer to store the UTF-8 representation that
59 //! is destroyed once the statement containing QUO_CSTR() is done (therefore, ALWAYS copy the result
60 //! based on QUO_CSTR() contents if you need to store it).
61 #define QUO_CSTR(StringConvertible_) std::data(::Quotient::_impl::toUtf8(StringConvertible_))
62 
63 inline bool alarmX(bool alarmCondition, const auto& msg,
64  [[maybe_unused]] std::source_location loc = std::source_location::current())
65 {
66  if (alarmCondition) [[unlikely]] {
67  qt_assert_x(loc.function_name(), QUO_CSTR(msg), loc.file_name(), loc.line());
68  qCritical() << msg;
69  }
70  return alarmCondition;
71 }
72 
73 //! \brief A negative assertion facility that can be put in an if statement
74 //!
75 //! Unlike most other assertion functions or macros, this doesn't collapse to no-op in Release
76 //! builds; rather, it sends a critical level message to the log and returns true so that you could
77 //! immediately return or do some other damage control for Release builds. Also unlike most other
78 //! assertion functions, \p AlarmCondition is the condition for failure, not for health; in other
79 //! words, \p Message is sent to logs (and, in Debug configuration, the assertion fails)
80 //! if \p AlarmCondition holds, not the other way around.
81 //!
82 //! This macro is a trivial wrapper around alarmX(), provided for API uniformity with QUO_ALARM()
83 #define QUO_ALARM_X(...) ::Quotient::alarmX(__VA_ARGS__)
84 
85 #define QUO_ALARM(...) ::Quotient::alarmX((__VA_ARGS__) ? true : false, "Alarm: " #__VA_ARGS__)
86 
87 //! Evaluate the boolean expression and, in Debug mode, assert it to be true
88 #define QUO_CHECK(...)
89  !::Quotient::alarmX(!(__VA_ARGS__) ? true : false, "Failing expression: " #__VA_ARGS__)
90 
91 #if Quotient_VERSION_MAJOR == 0 && Quotient_VERSION_MINOR < 10
92 /// This is only to make UnorderedMap alias work until we get rid of it
93 template <typename T>
94 struct HashQ {
95  size_t operator()(const T& s) const Q_DECL_NOEXCEPT
96  {
97  return qHash(s, uint(QHashSeed::globalSeed()));
98  }
99 };
100 /// A wrapper around std::unordered_map compatible with types that have qHash
101 template <typename KeyT, typename ValT>
102 using UnorderedMap
103  [[deprecated("Use std::unordered_map directly")]] = std::unordered_map<KeyT, ValT, HashQ<KeyT>>;
104 
105 inline namespace Literals { using namespace Qt::Literals; }
106 
107 #if Quotient_VERSION_MAJOR == 0 && Quotient_VERSION_MINOR > 9
108 [[deprecated("Use operators from Qt::Literals (aka Quotient::Literals) instead")]]
109 #endif
110 constexpr auto operator""_ls(const char* s, std::size_t size)
111 {
112  return operator""_L1(s, size);
113 }
114 
115 template <typename ArrayT>
116 class [[deprecated("Use std::ranges::subrange instead")]] Range {
117  using iterator = typename ArrayT::iterator;
118  using const_iterator = typename ArrayT::const_iterator;
119  using size_type = typename ArrayT::size_type;
120 
121 public:
122  constexpr Range(ArrayT& arr) : from(std::begin(arr)), to(std::end(arr)) {}
123  constexpr Range(iterator from, iterator to) : from(from), to(to) {}
124 
125  constexpr size_type size() const
126  {
127  Q_ASSERT(std::distance(from, to) >= 0);
128  return size_type(std::distance(from, to));
129  }
130  constexpr bool empty() const { return from == to; }
131  constexpr const_iterator begin() const { return from; }
132  constexpr const_iterator end() const { return to; }
133  constexpr iterator begin() { return from; }
134  constexpr iterator end() { return to; }
135 
136 private:
137  iterator from;
138  iterator to;
139 };
140 
141 namespace _impl {
142  template <typename T>
143  concept Holds_NonConst_LValue_Ref =
144  std::is_lvalue_reference_v<T> && !std::is_const_v<std::remove_reference<T>>;
145 }
146 
147 //! \brief An adaptor for Qt (hash-)maps to make them iterable in STL style
148 //!
149 //! QMap/QHash container iterators returned by begin() and end() dereference
150 //! to values, unlike STL where similar iterators dereference to key-value
151 //! pairs. It is a problem in range-for if you want to also access map keys.
152 //! This adaptor allows to use range-for syntax with access to both keys and
153 //! values in QMap/QHash containers. Just use
154 //! `for (auto&& [key, value] : asKeyValueRange(myMap)` instead of
155 //! `for (auto&& value : myMap)`.
156 //! \note When an rvalue is passed as the constructor argument, asKeyValueRange
157 //! shallow-copies the map object to ensure its lifetime is maintained
158 //! throughout the loop; with lvalues, no copying occurs, assuming that
159 //! the map object outlives the range-for loop
160 template <typename T>
161 class [[deprecated(
162  "Use the member function with the same name of the respective container")]] asKeyValueRange {
163 public:
164  explicit asKeyValueRange(T data)
165  : m_data { data }
166  {}
167 
169  {
170  return m_data.keyValueBegin();
171  }
173  {
174  return m_data.keyValueEnd();
175  }
176  auto begin() const { return m_data.keyValueBegin(); }
177  auto end() const { return m_data.keyValueEnd(); }
178 
179 private:
180  T m_data;
181 };
182 
183 // GCC complains about a deprecation even on deduction hints, so here's to suppress noise
185 template <typename U>
186 asKeyValueRange(U&) -> asKeyValueRange<U&>;
187 
188 template <typename U>
189 asKeyValueRange(U&&) -> asKeyValueRange<U>;
190 )
191 #endif
192 
193 /** A replica of std::find_first_of that returns a pair of iterators
194  *
195  * Convenient for cases when you need to know which particular "first of"
196  * [sFirst, sLast) has been found in [first, last).
197  */
198 template <typename InputIt, typename ForwardIt, typename Pred>
199 inline std::pair<InputIt, ForwardIt> findFirstOf(InputIt first, InputIt last,
200  ForwardIt sFirst,
201  ForwardIt sLast, Pred pred)
202 {
203  for (; first != last; ++first)
204  for (auto it = sFirst; it != sLast; ++it)
205  if (pred(*first, *it))
206  return { first, it };
207 
208  return { last, sLast };
209 }
210 
211 //! \brief An owning implementation pointer
212 //!
213 //! This is basically std::unique_ptr<> to hold your pimpl's but without having
214 //! to define default constructors/operator=() out of line.
215 //! Thanks to https://oliora.github.io/2015/12/29/pimpl-and-rule-of-zero.html
216 //! for inspiration
217 template <typename ImplType, typename TypeToDelete = ImplType>
218 using ImplPtr = std::unique_ptr<ImplType, void (*)(TypeToDelete*)>;
219 
220 // Why this works (see also the link above): because this defers the moment
221 // of requiring sizeof of ImplType to the place where makeImpl is invoked
222 // (which is located, necessarily, in the .cpp file after ImplType definition).
223 // The stock unique_ptr deleter (std::default_delete) normally needs sizeof
224 // at the same spot - as long as you defer definition of the owning type
225 // constructors and operator='s to the .cpp file as well. Which means you
226 // have to explicitly declare and define them (even if with = default),
227 // formally breaking the rule of zero; informally, just adding boilerplate code.
228 // The custom deleter itself is instantiated at makeImpl invocation - there's
229 // no way earlier to even know how ImplType will be deleted and whether that
230 // will need sizeof(ImplType) earlier. In theory it's a tad slower because
231 // the deleter is called by the pointer; however, the difference will not
232 // be noticeable (if exist at all) for any class with non-trivial contents.
233 
234 //! \brief make_unique for ImplPtr
235 //!
236 //! Since std::make_unique is not compatible with ImplPtr, this should be used
237 //! in constructors of frontend classes to create implementation instances.
238 template <typename ImplType, typename TypeToDelete = ImplType, typename... ArgTs>
240 {
241  return ImplPtr<ImplType, TypeToDelete> {
242  new ImplType{std::forward<ArgTs>(args)...},
243  [](TypeToDelete* impl) { delete impl; }
244  };
245 }
246 
247 template <typename ImplType, typename TypeToDelete = ImplType>
249 {
250  return ImplPtr<ImplType, TypeToDelete> { from, [](TypeToDelete* impl) {
251  delete impl;
252  } };
253 }
254 
255 template <typename ImplType, typename TypeToDelete = ImplType>
257 {
258  return { nullptr, [](TypeToDelete*) { /* nullptr doesn't need deletion */ } };
259 }
260 
261 template <typename T>
263  size_t (*destructor)(T*);
264 
265  void operator()(T* toDelete)
266  {
267  destructor(toDelete);
268  delete[] reinterpret_cast<std::byte*>(toDelete);
269  }
270 };
271 
272 //! \brief An owning pointer to a C structure
273 //!
274 //! This is intented to ease lifecycle management of Olm structures.
275 //! \sa makeCStruct
276 template <typename T>
277 using CStructPtr = std::unique_ptr<T, CStructDeleter<T>>;
278 
279 //! \brief Create a C structure with pre-programmed deletion logic
280 //!
281 //! This facility function creates a CStructPtr that owns the pointer returned
282 //! by \p constructor. The memory passed to \p constructor is allocated
283 //! as an array of bytes; the size of that array is determined by calling
284 //! \p sizeFn. Finally, since the returned pointer is owning, it also stores
285 //! the corresponding CStructDeleter instance; when called at destruction of
286 //! the owning pointer, this deleter first calls \p destructor passing the
287 //! original C pointer returned by \p constructor; and then deletes the
288 //! allocated array of bytes.
289 template <typename T>
290 inline auto makeCStruct(T* (*constructor)(void*), size_t (*sizeFn)(),
291  auto destructor)
292 {
293  return CStructPtr<T>{ constructor(new std::byte[sizeFn()]), { destructor } };
294 }
295 
296 //! \brief Multiplex several functors in one
297 //!
298 //! This is a well-known trick to wrap several lambdas into a single functor
299 //! class that can be passed to std::visit.
300 //! \sa https://en.cppreference.com/w/cpp/utility/variant/visit
301 template <typename... FunctorTs>
302 struct Overloads : FunctorTs... {
303  using FunctorTs::operator()...;
304 };
305 
306 template <typename... FunctorTs>
307 Overloads(FunctorTs&&...) -> Overloads<FunctorTs...>;
308 
309 /** Convert what looks like a URL or a Matrix ID to an HTML hyperlink */
310 QUOTIENT_API void linkifyUrls(QString& htmlEscapedText);
311 
312 /** Sanitize the text before showing in HTML
313  *
314  * This does toHtmlEscaped() and removes Unicode BiDi marks.
315  */
316 QUOTIENT_API QString sanitized(const QString& plainText);
317 
318 /** Pretty-print plain text into HTML
319  *
320  * This includes HTML escaping of <,>,",& and calling linkifyUrls()
321  */
322 QUOTIENT_API QString prettyPrint(const QString& plainText);
323 
324 /** Return a path to cache directory after making sure that it exists
325  *
326  * The returned path has a trailing slash, clients don't need to append it.
327  * \param dirName path to cache directory relative to the standard cache path
328  */
329 QUOTIENT_API QString cacheLocation(QStringView dirName);
330 
331 /** Hue color component of based of the hash of the string.
332  *
333  * The implementation is based on XEP-0392:
334  * https://xmpp.org/extensions/xep-0392.html
335  * Naming and range are the same as QColor's hueF method:
336  * https://doc.qt.io/qt-5/qcolor.html#integer-vs-floating-point-precision
337  */
338 QUOTIENT_API qreal stringToHueF(const QString& s);
339 
340 /** Extract the serverpart from MXID */
341 QUOTIENT_API QString serverPart(const QString& mxId);
342 
343 QUOTIENT_API QString versionString();
344 QUOTIENT_API int majorVersion();
345 QUOTIENT_API int minorVersion();
346 QUOTIENT_API int patchVersion();
347 
348 // QDebug manipulators
349 
350 //! \brief QDebug manipulator to setup the stream for JSON output
351 //!
352 //! Originally made to encapsulate the change in QDebug behavior in Qt 5.4
353 //! and the respective addition of QDebug::noquote().
354 //! Together with the operator<<() helper, the proposed usage is
355 //! (similar to std:: I/O manipulators):
356 //! `qCDebug(MAIN) << formatJson << json_object; // (QJsonObject etc.)`
357 inline QDebug formatJson(QDebug dbg) { return dbg.noquote(); }
358 
359 //! Suppress full qualification of enums/QFlags when logging
360 inline QDebug terse(QDebug dbg)
361 {
362  return dbg.verbosity(QDebug::MinimumVerbosity);
363 }
364 
365 constexpr qint64 ProfilerMinNsecs =
366 #ifdef PROFILER_LOG_USECS
367  PROFILER_LOG_USECS
368 #else
369  200
370 #endif
371  * 1000;
372 } // namespace Quotient
373 
374 //! \brief A helper operator for QDebug manipulators, e.g. formatJson
375 //!
376 //! \param dbg to output the json to
377 //! \param manipFn a QDebug manipulator
378 //! \return a copy of dbg that has its mode altered by manipFn
379 inline QDebug operator<<(QDebug dbg, std::invocable<QDebug> auto manipFn)
380 {
381  return std::invoke(manipFn, dbg);
382 }
383 
384 inline QDebug operator<<(QDebug dbg, QElapsedTimer et)
385 {
386  // NOLINTNEXTLINE(bugprone-integer-division)
387  dbg << static_cast<double>(et.nsecsElapsed() / 1000) / 1000
388  << "ms"; // Show in ms with 3 decimal digits precision
389  return dbg;
390 }
391 
392 namespace Quotient {
393 //! \brief Lift an operation into dereferenceable types (std::optional or pointers)
394 //!
395 //! This is a more generic version of std::optional::and_then() that accepts an arbitrary number of
396 //! arguments of any type that is dereferenceable (i.e. unary operator*() can be applied to it) and
397 //! (explicitly or implicitly) convertible to bool. This allows to streamline checking for
398 //! nullptr/nullopt before applying the operation on the underlying types. \p fn is only invoked if
399 //! all \p args are "truthy" (i.e. <tt>(... && bool(args)) == true</tt>).
400 //! \param fn A callable that should accept the types stored inside optionals/pointers passed in
401 //! \p args (NOT optionals/pointers themselves; they are unwrapped)
402 //! \return Always an optional: if \p fn returns another type, lift() wraps it in std::optional;
403 //! if \p fn returns std::optional, that return value (or std::nullopt) is returned as is.
404 template <typename FnT>
405 inline auto lift(FnT&& fn, auto&&... args)
406 {
407  if constexpr (std::is_void_v<std::invoke_result_t<FnT, decltype(*args)...>>) {
408  if ((... && bool(args)))
409  std::invoke(std::forward<FnT>(fn), *args...);
410  } else
411  return (... && bool(args))
412  ? std::optional(std::invoke(std::forward<FnT>(fn), *args...))
413  : std::nullopt;
414 }
415 
416 //! \brief Merge the value from an optional
417 //!
418 //! Assigns the value stored at \p rhs to \p lhs if, and only if, \p rhs is not omitted and
419 //! `lhs != *rhs`. \p lhs can be either an optional or an ordinary variable.
420 //! \return `true` if \p rhs is not omitted and the \p lhs value was different, in other words,
421 //! if \p lhs has changed; `false` otherwise
422 template <typename T1, typename T2>
423  requires std::is_assignable_v<T1&, const T2&>
424 constexpr inline bool merge(T1& lhs, const std::optional<T2>& rhs)
425 {
426  if (!rhs || lhs == *rhs)
427  return false;
428  lhs = *rhs;
429  return true;
430 }
431 
432 //! \brief Merge structure-like types
433 //!
434 //! Merges fields in \p lhs from counterparts in \p rhs. The list of fields to merge is passed
435 //! in additional parameters (\p fields). E.g.:
436 //! \codeline mergeStruct(struct1, struct2, &Struct::field1, &Struct::field2, &Struct::field3)
437 //! \return the number of fields in \p lhs that were changed
438 template <typename StructT>
439 constexpr inline size_t mergeStruct(StructT& lhs, const StructT& rhs, const auto... fields)
440 {
441  return ((... + static_cast<size_t>(merge(lhs.*fields, rhs.*fields))));
442 }
443 
444 // These are meant to eventually become separate classes derived from QString (or perhaps
445 // QByteArray?), with their own construction and validation logic; for now they are just aliases
446 // for QString to make numerous IDs at least semantically different in the code.
447 
448 using UserId = QString;
449 using RoomId = QString;
450 using EventId = QString;
451 
452 QUOTIENT_API bool isGuestUserId(const UserId& uId);
453 
454 struct QUOTIENT_API HomeserverData {
455  QUrl baseUrl;
456  QByteArray accessToken = {};
457  QStringList supportedSpecVersions = {};
458 
459  bool checkMatrixSpecVersion(QStringView targetVersion) const;
460 };
461 } // namespace Quotient