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