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