diff options
-rw-r--r-- | .appveyor.yml | 6 | ||||
-rw-r--r-- | .clang-format | 2 | ||||
-rw-r--r-- | CMakeLists.txt | 3 | ||||
-rw-r--r-- | lib/connection.cpp | 49 | ||||
-rw-r--r-- | lib/connection.h | 29 | ||||
-rw-r--r-- | lib/events/event.h | 30 | ||||
-rw-r--r-- | lib/jobs/basejob.cpp | 28 | ||||
-rw-r--r-- | lib/jobs/downloadfilejob.cpp | 2 | ||||
-rw-r--r-- | lib/networksettings.cpp | 6 | ||||
-rw-r--r-- | lib/networksettings.h | 6 | ||||
-rw-r--r-- | lib/qt_connection_util.h | 114 | ||||
-rw-r--r-- | lib/room.cpp | 59 | ||||
-rw-r--r-- | lib/room.h | 24 | ||||
-rw-r--r-- | lib/settings.cpp | 11 | ||||
-rw-r--r-- | lib/settings.h | 10 | ||||
-rw-r--r-- | lib/user.cpp | 6 | ||||
-rw-r--r-- | lib/util.cpp | 38 | ||||
-rw-r--r-- | lib/util.h | 22 |
18 files changed, 273 insertions, 172 deletions
diff --git a/.appveyor.yml b/.appveyor.yml index 10eeafd0..725adc8c 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -1,7 +1,7 @@ image: Visual Studio 2017 environment: - #DEPLOY_DIR: libqmatrixclient-%APPVEYOR_BUILD_VERSION% + #DEPLOY_DIR: quotient-%APPVEYOR_BUILD_VERSION% matrix: - QTDIR: C:\Qt\5.13\msvc2017_64 VCVARS: "C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Community\\VC\\Auxiliary\\Build\\vcvars64.bat" @@ -33,7 +33,7 @@ build_script: #after_build: #- cmake --build build --target install -#- 7z a libqmatrixclient.zip "%DEPLOY_DIR%\" +#- 7z a quotient.zip "%DEPLOY_DIR%\" # Uncomment this to connect to the AppVeyor build worker #on_finish: @@ -42,4 +42,4 @@ build_script: test: off #artifacts: -#- path: libqmatrixclient.zip +#- path: quotient.zip diff --git a/.clang-format b/.clang-format index e114580e..1869dfe6 100644 --- a/.clang-format +++ b/.clang-format @@ -115,7 +115,7 @@ SortUsingDeclarations: false #SpaceBeforeCtorInitializerColon: true #SpaceBeforeInheritanceColon: true #SpaceBeforeParens: ControlStatements -#SpaceBeforeRangeBasedForLoopColon: true +SpaceBeforeRangeBasedForLoopColon: false #SpaceInEmptyParentheses: false #SpacesBeforeTrailingComments: 1 #SpacesInAngles: false diff --git a/CMakeLists.txt b/CMakeLists.txt index 951ef8c9..ce4af9a8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -225,11 +225,12 @@ set_target_properties(${PROJECT_NAME} PROPERTIES VERSION "${PROJECT_VERSION}" SOVERSION ${API_VERSION} INTERFACE_${PROJECT_NAME}_MAJOR_VERSION ${API_VERSION} - CXX_STANDARD 17 ) set_property(TARGET ${PROJECT_NAME} APPEND PROPERTY COMPATIBLE_INTERFACE_STRING ${PROJECT_NAME}_MAJOR_VERSION) +target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_17) + target_include_directories(${PROJECT_NAME} PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/lib> $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}> diff --git a/lib/connection.cpp b/lib/connection.cpp index c3e46356..2c5bf574 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -955,6 +955,33 @@ QHash<QPair<QString, bool>, Room*> Connection::roomMap() const return roomMap; } +QVector<Room*> Connection::allRooms() const +{ + QVector<Room*> result; + result.resize(d->roomMap.size()); + std::copy(d->roomMap.cbegin(), d->roomMap.cend(), result.begin()); + return result; +} + +QVector<Room*> Connection::rooms(JoinStates joinStates) const +{ + QVector<Room*> result; + for (auto* r: qAsConst(d->roomMap)) + if (joinStates.testFlag(r->joinState())) + result.push_back(r); + return result; +} + +int Connection::roomsCount(JoinStates joinStates) const +{ + // Using int to maintain compatibility with QML + // (consider also that QHash<>::size() returns int anyway). + return int(std::count_if(d->roomMap.begin(), d->roomMap.end(), + [joinStates](Room* r) { + return joinStates.testFlag(r->joinState()); + })); +} + bool Connection::hasAccountData(const QString& type) const { return d->accountData.find(type) != d->accountData.cend(); @@ -1262,18 +1289,20 @@ void Connection::saveState() const { QStringLiteral("minor"), SyncData::cacheVersion().second } } } }; { - QJsonObject rooms; - QJsonObject inviteRooms; - const auto& rs = roomMap(); // Pass on rooms in Leave state - for (const auto* i : rs) - (i->joinState() == JoinState::Invite ? inviteRooms : rooms) - .insert(i->id(), QJsonValue::Null); + QJsonObject roomsJson; + QJsonObject inviteRoomsJson; + for (const auto* r: qAsConst(d->roomMap)) { + if (r->joinState() == JoinState::Leave) + continue; + (r->joinState() == JoinState::Invite ? inviteRoomsJson : roomsJson) + .insert(r->id(), QJsonValue::Null); + } QJsonObject roomObj; - if (!rooms.isEmpty()) - roomObj.insert(QStringLiteral("join"), rooms); - if (!inviteRooms.isEmpty()) - roomObj.insert(QStringLiteral("invite"), inviteRooms); + if (!roomsJson.isEmpty()) + roomObj.insert(QStringLiteral("join"), roomsJson); + if (!inviteRoomsJson.isEmpty()) + roomObj.insert(QStringLiteral("invite"), inviteRoomsJson); rootObj.insert(QStringLiteral("next_batch"), d->data->lastEvent()); rootObj.insert(QStringLiteral("rooms"), roomObj); diff --git a/lib/connection.h b/lib/connection.h index b4b16679..1f1d4cd5 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -135,12 +135,39 @@ public: explicit Connection(const QUrl& server, QObject* parent = nullptr); ~Connection() override; - /** Get all Invited and Joined rooms + /// Get all Invited and Joined rooms + /*! * \return a hashmap from a composite key - room name and whether * it's an Invite rather than Join - to room pointers + * \sa allRooms, rooms, roomsWithTag */ + [[deprecated("Use allRooms(), roomsWithTag() or rooms(joinStates) instead")]] QHash<QPair<QString, bool>, Room*> roomMap() const; + /// Get all rooms known within this Connection + /*! + * This includes Invite, Join and Leave rooms, in no particular order. + * \note Leave rooms will only show up in the list if they have been left + * in the same running session. The library doesn't cache left rooms + * between runs and it doesn't retrieve the full list of left rooms + * from the server. + * \sa rooms, room, roomsWithTag + */ + Q_INVOKABLE QVector<Room*> allRooms() const; + + /// Get rooms that have either of the given join state(s) + /*! + * This method returns, in no particular order, rooms which join state + * matches the mask passed in \p joinStates. + * \note Similar to allRooms(), this won't retrieve the full list of + * Leave rooms from the server. + * \sa allRooms, room, roomsWithTag + */ + Q_INVOKABLE QVector<Room*> rooms(JoinStates joinStates) const; + + /// Get the total number of rooms in the given join state(s) + Q_INVOKABLE int roomsCount(JoinStates joinStates) const; + /** Check whether the account has data of the given type * Direct chats map is not supported by this method _yet_. */ diff --git a/lib/events/event.h b/lib/events/event.h index 25362786..f985ae92 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -334,22 +334,18 @@ inline auto visit(const BaseEventT& event, FnT&& visitor) return visitor(event); } -template <typename T> -constexpr auto is_event() -{ - return std::is_base_of<Event, std::decay_t<T>>::value; -} - -template <typename T, typename FnT> -constexpr auto needs_cast() -{ - return !std::is_convertible<T, fn_arg_t<FnT>>::value; +namespace _impl { + template <typename T, typename FnT> + constexpr auto needs_downcast() + { + return !std::is_convertible_v<T, fn_arg_t<FnT>>; + } } // A single type-specific void visitor template <typename BaseEventT, typename FnT> -inline std::enable_if_t<is_event<BaseEventT>() && needs_cast<BaseEventT, FnT>() - && std::is_void<fn_return_t<FnT>>::value> +inline std::enable_if_t<_impl::needs_downcast<BaseEventT, FnT>() + && std::is_void_v<fn_return_t<FnT>>> visit(const BaseEventT& event, FnT&& visitor) { using event_type = fn_arg_t<FnT>; @@ -358,10 +354,9 @@ visit(const BaseEventT& event, FnT&& visitor) } // A single type-specific non-void visitor with an optional default value +// non-voidness is guarded by defaultValue type template <typename BaseEventT, typename FnT> -inline std::enable_if_t<is_event<BaseEventT>() && needs_cast<BaseEventT, FnT>(), - fn_return_t<FnT>> // non-voidness is guarded by - // defaultValue type +inline std::enable_if_t<_impl::needs_downcast<BaseEventT, FnT>(), fn_return_t<FnT>> visit(const BaseEventT& event, FnT&& visitor, fn_return_t<FnT>&& defaultValue = {}) { @@ -373,9 +368,8 @@ visit(const BaseEventT& event, FnT&& visitor, // A chain of 2 or more visitors template <typename BaseEventT, typename FnT1, typename FnT2, typename... FnTs> -inline std::enable_if_t<is_event<BaseEventT>(), fn_return_t<FnT1>> -visit(const BaseEventT& event, FnT1&& visitor1, FnT2&& visitor2, - FnTs&&... visitors) +inline fn_return_t<FnT1> visit(const BaseEventT& event, FnT1&& visitor1, + FnT2&& visitor2, FnTs&&... visitors) { using event_type1 = fn_arg_t<FnT1>; if (is<std::decay_t<event_type1>>(event)) diff --git a/lib/jobs/basejob.cpp b/lib/jobs/basejob.cpp index 54931c83..6a70bc40 100644 --- a/lib/jobs/basejob.cpp +++ b/lib/jobs/basejob.cpp @@ -24,6 +24,7 @@ #include <QtCore/QJsonObject> #include <QtCore/QRegularExpression> #include <QtCore/QTimer> +#include <QtCore/QStringBuilder> #include <QtNetwork/QNetworkAccessManager> #include <QtNetwork/QNetworkReply> #include <QtNetwork/QNetworkRequest> @@ -105,11 +106,17 @@ public: errorStrategy.size() - 1)]; } - QString urlForLog() const + QString dumpRequest() const { - return reply - ? reply->url().toString(QUrl::RemoveQuery) - : makeRequestUrl(connection->baseUrl(), apiEndpoint).toString(); + // Thanks to C++17, std::array's type and bounds are deduced + static const auto verbs = + std::array { QStringLiteral("GET"), QStringLiteral("PUT"), + QStringLiteral("POST"), QStringLiteral("DELETE") }; + const auto verbWord = verbs.at(size_t(verb)); + return verbWord % ' ' + % (reply ? reply->url().toString(QUrl::RemoveQuery) + : makeRequestUrl(connection->baseUrl(), apiEndpoint) + .toString()); } }; @@ -260,7 +267,7 @@ void BaseJob::sendRequest() if (status().code == Abandoned) return; Q_ASSERT(d->connection && status().code == Pending); - qCDebug(d->logCat) << "Making request to" << d->urlForLog(); + qCDebug(d->logCat).noquote() << "Making" << d->dumpRequest(); emit aboutToSendRequest(); d->sendRequest(); Q_ASSERT(d->reply); @@ -273,12 +280,12 @@ void BaseJob::sendRequest() connect(d->reply.data(), &QNetworkReply::downloadProgress, this, &BaseJob::downloadProgress); d->timer.start(getCurrentTimeout()); - qCInfo(d->logCat).noquote() << "Request sent to" << d->urlForLog(); + qCInfo(d->logCat).noquote() << "Sent" << d->dumpRequest(); onSentRequest(d->reply.data()); emit sentRequest(); } else qCWarning(d->logCat).noquote() - << "Request could not start:" << d->urlForLog(); + << "Request could not start:" << d->dumpRequest(); } void BaseJob::checkReply() { setStatus(doCheckReply(d->reply.data())); } @@ -378,7 +385,8 @@ BaseJob::Status BaseJob::doCheckReply(QNetworkReply* reply) const const auto httpCodeHeader = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute); if (!httpCodeHeader.isValid()) { - qCWarning(d->logCat) << "No valid HTTP headers from" << d->urlForLog(); + qCWarning(d->logCat).noquote() + << "No valid HTTP headers from" << d->dumpRequest(); return { NetworkError, reply->errorString() }; } @@ -386,7 +394,7 @@ BaseJob::Status BaseJob::doCheckReply(QNetworkReply* reply) const if (httpCode / 100 == 2) // 2xx { if (reply->isFinished()) - qCInfo(d->logCat) << httpCode << "<-" << d->urlForLog(); + qCInfo(d->logCat).noquote() << httpCode << "<-" << d->dumpRequest(); if (!checkContentType(reply->rawHeader("Content-Type"), d->expectedContentTypes)) return { UnexpectedResponseTypeWarning, @@ -394,7 +402,7 @@ BaseJob::Status BaseJob::doCheckReply(QNetworkReply* reply) const return NoError; } if (reply->isFinished()) - qCWarning(d->logCat) << httpCode << "<-" << d->urlForLog(); + qCWarning(d->logCat).noquote() << httpCode << "<-" << d->dumpRequest(); auto message = reply->errorString(); if (message.isEmpty()) diff --git a/lib/jobs/downloadfilejob.cpp b/lib/jobs/downloadfilejob.cpp index 4e997326..3e037680 100644 --- a/lib/jobs/downloadfilejob.cpp +++ b/lib/jobs/downloadfilejob.cpp @@ -12,7 +12,7 @@ public: explicit Private(const QString& localFilename) : targetFile(new QFile(localFilename)) - , tempFile(new QFile(targetFile->fileName() + ".qmcdownload")) + , tempFile(new QFile(targetFile->fileName() + ".qtntdownload")) {} QScopedPointer<QFile> targetFile; diff --git a/lib/networksettings.cpp b/lib/networksettings.cpp index cb071483..40ecba11 100644 --- a/lib/networksettings.cpp +++ b/lib/networksettings.cpp @@ -26,9 +26,9 @@ void NetworkSettings::setupApplicationProxy() const { proxyType(), proxyHostName(), proxyPort() }); } -QMC_DEFINE_SETTING(NetworkSettings, QNetworkProxy::ProxyType, proxyType, +QTNT_DEFINE_SETTING(NetworkSettings, QNetworkProxy::ProxyType, proxyType, "proxy_type", QNetworkProxy::DefaultProxy, setProxyType) -QMC_DEFINE_SETTING(NetworkSettings, QString, proxyHostName, "proxy_hostname", +QTNT_DEFINE_SETTING(NetworkSettings, QString, proxyHostName, "proxy_hostname", {}, setProxyHostName) -QMC_DEFINE_SETTING(NetworkSettings, quint16, proxyPort, "proxy_port", -1, +QTNT_DEFINE_SETTING(NetworkSettings, quint16, proxyPort, "proxy_port", -1, setProxyPort) diff --git a/lib/networksettings.h b/lib/networksettings.h index a6a13f93..2399cf5f 100644 --- a/lib/networksettings.h +++ b/lib/networksettings.h @@ -27,9 +27,9 @@ Q_DECLARE_METATYPE(QNetworkProxy::ProxyType) namespace Quotient { class NetworkSettings : public SettingsGroup { Q_OBJECT - QMC_DECLARE_SETTING(QNetworkProxy::ProxyType, proxyType, setProxyType) - QMC_DECLARE_SETTING(QString, proxyHostName, setProxyHostName) - QMC_DECLARE_SETTING(quint16, proxyPort, setProxyPort) + QTNT_DECLARE_SETTING(QNetworkProxy::ProxyType, proxyType, setProxyType) + QTNT_DECLARE_SETTING(QString, proxyHostName, setProxyHostName) + QTNT_DECLARE_SETTING(quint16, proxyPort, setProxyPort) Q_PROPERTY(QString proxyHost READ proxyHostName WRITE setProxyHostName) public: template <typename... ArgTs> diff --git a/lib/qt_connection_util.h b/lib/qt_connection_util.h index 159e7522..9baf8c69 100644 --- a/lib/qt_connection_util.h +++ b/lib/qt_connection_util.h @@ -24,10 +24,15 @@ namespace Quotient { namespace _impl { + template <typename... ArgTs> + using decorated_slot_tt = + std::function<void(QMetaObject::Connection&, const ArgTs&...)>; + template <typename SenderT, typename SignalT, typename ContextT, typename... ArgTs> inline QMetaObject::Connection - connectUntil(SenderT* sender, SignalT signal, ContextT* context, - std::function<bool(ArgTs...)> slot, Qt::ConnectionType connType) + connectDecorated(SenderT* sender, SignalT signal, ContextT* context, + decorated_slot_tt<ArgTs...> decoratedSlot, + Qt::ConnectionType connType) { // See https://bugreports.qt.io/browse/QTBUG-60339 #if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) @@ -36,52 +41,105 @@ namespace _impl { auto pc = std::make_unique<QMetaObject::Connection>(); #endif auto& c = *pc; // Resolve a reference before pc is moved to lambda - c = QObject::connect( - sender, signal, context, - [pc = std::move(pc), slot](ArgTs... args) { + + // Perfect forwarding doesn't work through signal-slot connections - + // arguments are always copied (at best - COWed) to the context of + // the slot. Therefore the slot decorator receives const ArgTs&... + // rather than ArgTs&&... + // TODO: std::bind_front() instead of lambda. + c = QObject::connect(sender, signal, context, + [pc = std::move(pc), + decoratedSlot = std::move(decoratedSlot)](const ArgTs&... args) { Q_ASSERT(*pc); // If it's been triggered, it should exist - if (slot(std::forward<ArgTs>(args)...)) - QObject::disconnect(*pc); + decoratedSlot(*pc, args...); }, connType); return c; } + template <typename SenderT, typename SignalT, typename ContextT, + typename... ArgTs> + inline QMetaObject::Connection + connectUntil(SenderT* sender, SignalT signal, ContextT* context, + std::function<bool(ArgTs...)> functor, + Qt::ConnectionType connType) + { + return connectDecorated(sender, signal, context, + decorated_slot_tt<ArgTs...>( + [functor = std::move(functor)](QMetaObject::Connection& c, + const ArgTs&... args) { + if (functor(args...)) + QObject::disconnect(c); + }), + connType); + } + template <typename SenderT, typename SignalT, typename ContextT, + typename... ArgTs> + inline QMetaObject::Connection + connectSingleShot(SenderT* sender, SignalT signal, ContextT* context, + std::function<void(ArgTs...)> slot, + Qt::ConnectionType connType) + { + return connectDecorated(sender, signal, context, + decorated_slot_tt<ArgTs...>( + [slot = std::move(slot)](QMetaObject::Connection& c, + const ArgTs&... args) { + QObject::disconnect(c); + slot(args...); + }), + connType); + } } // namespace _impl +/// Create a connection that self-disconnects when its "slot" returns true +/*! A slot accepted by connectUntil() is different from classic Qt slots + * in that its return value must be bool, not void. The slot's return value + * controls whether the connection should be kept; if the slot returns false, + * the connection remains; upon returning true, the slot is disconnected from + * the signal. Because of a different slot signature connectUntil() doesn't + * accept member functions as QObject::connect or Quotient::connectSingleShot + * do; you should pass a lambda or a pre-bound member function to it. + */ template <typename SenderT, typename SignalT, typename ContextT, typename FunctorT> inline auto connectUntil(SenderT* sender, SignalT signal, ContextT* context, const FunctorT& slot, Qt::ConnectionType connType = Qt::AutoConnection) { - return _impl::connectUntil( - sender, signal, context, - typename function_traits<FunctorT>::function_type(slot), connType); + return _impl::connectUntil(sender, signal, context, std::function(slot), + connType); } -/** Create a single-shot connection that triggers on the signal and - * then self-disconnects - * - * Only supports DirectConnection type. - */ -template <typename SenderT, typename SignalT, typename ReceiverT, typename SlotT> +/// Create a connection that self-disconnects after triggering on the signal +template <typename SenderT, typename SignalT, typename ContextT, typename FunctorT> +inline auto connectSingleShot(SenderT* sender, SignalT signal, + ContextT* context, const FunctorT& slot, + Qt::ConnectionType connType = Qt::AutoConnection) +{ + return _impl::connectSingleShot( + sender, signal, context, std::function(slot), connType); +} + +// Specialisation for usual Qt slots passed as pointers-to-members. +template <typename SenderT, typename SignalT, typename ReceiverT, + typename SlotObjectT, typename... ArgTs> inline auto connectSingleShot(SenderT* sender, SignalT signal, - ReceiverT* receiver, SlotT slot) + ReceiverT* receiver, + void (SlotObjectT::*slot)(ArgTs...), + Qt::ConnectionType connType = Qt::AutoConnection) { - QMetaObject::Connection connection; - connection = QObject::connect(sender, signal, receiver, slot, - Qt::DirectConnection); - Q_ASSERT(connection); - QObject::connect( - sender, signal, receiver, - [connection] { QObject::disconnect(connection); }, Qt::DirectConnection); - return connection; + // TODO: when switching to C++20, use std::bind_front() instead + return _impl::connectSingleShot(sender, signal, receiver, + std::function( + [receiver, slot](const ArgTs&... args) { + (receiver->*slot)(args...); + }), + connType); } -/** A guard pointer that disconnects an interested object upon destruction - * It's almost QPointer<> except that you have to initialise it with one +/// A guard pointer that disconnects an interested object upon destruction +/*! It's almost QPointer<> except that you have to initialise it with one * more additional parameter - a pointer to a QObject that will be * disconnected from signals of the underlying pointer upon the guard's - * destruction. + * destruction. Note that destructing the guide doesn't destruct either QObject. */ template <typename T> class ConnectionsGuard : public QPointer<T> { diff --git a/lib/room.cpp b/lib/room.cpp index 2f697589..aae84416 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -202,25 +202,31 @@ public: void getPreviousContent(int limit = 10); - template <typename EventT> - const EventT* getCurrentState(const QString& stateKey = {}) const + const StateEventBase* getCurrentState(const StateEventKey& evtKey) const { - const StateEventKey evtKey { EventT::matrixTypeId(), stateKey }; const auto* evt = currentState.value(evtKey, nullptr); if (!evt) { if (stubbedState.find(evtKey) == stubbedState.end()) { // In the absence of a real event, make a stub as-if an event // with empty content has been received. Event classes should be // prepared for empty/invalid/malicious content anyway. - stubbedState.emplace(evtKey, - loadStateEvent(EventT::matrixTypeId(), {}, - stateKey)); + stubbedState.emplace(evtKey, loadStateEvent(evtKey.first, {}, + evtKey.second)); qCDebug(STATE) << "A new stub event created for key {" << evtKey.first << evtKey.second << "}"; } evt = stubbedState[evtKey].get(); Q_ASSERT(evt); } + Q_ASSERT(evt->matrixType() == evtKey.first + && evt->stateKey() == evtKey.second); + return evt; + } + + template <typename EventT> + const EventT* getCurrentState(const QString& stateKey = {}) const + { + const auto* evt = getCurrentState({ EventT::matrixTypeId(), stateKey }); Q_ASSERT(evt->type() == EventT::typeId() && evt->matrixType() == EventT::matrixTypeId()); return static_cast<const EventT*>(evt); @@ -327,7 +333,7 @@ public: * content passed in \p newMessage. * \return true if the event has been found and replaced; false otherwise */ - bool processReplacement(const RoomMessageEvent& newMessage); + bool processReplacement(const RoomMessageEvent& newEvent); void setTags(TagsMap newTags); @@ -879,10 +885,10 @@ std::pair<bool, QString> validatedTag(QString name) if (name.contains('.')) return { false, name }; - qWarning(MAIN) << "The tag" << name + qCWarning(MAIN) << "The tag" << name << "doesn't follow the CS API conventions"; name.prepend("u."); - qWarning(MAIN) << "Using " << name << "instead"; + qCWarning(MAIN) << "Using " << name << "instead"; return { true, name }; } @@ -916,7 +922,7 @@ void Room::removeTag(const QString& name) } else if (!name.startsWith("u.")) removeTag("u." + name); else - qWarning(MAIN) << "Tag" << name << "on room" << objectName() + qCWarning(MAIN) << "Tag" << name << "on room" << objectName() << "not found, nothing to remove"; } @@ -971,7 +977,7 @@ Room::Private::getEventWithFile(const QString& eventId) const if (event->hasFileContent()) return event; } - qWarning() << "No files to download in event" << eventId; + qCWarning(MAIN) << "No files to download in event" << eventId; return nullptr; } @@ -1014,7 +1020,7 @@ QUrl Room::urlToThumbnail(const QString& eventId) const thumbnail->url, thumbnail->imageSize); } - qDebug() << "Event" << eventId << "has no thumbnail"; + qCDebug(MAIN) << "Event" << eventId << "has no thumbnail"; return {}; } @@ -1105,6 +1111,12 @@ bool Room::usesEncryption() const return !d->getCurrentState<EncryptionEvent>()->algorithm().isEmpty(); } +const StateEventBase* Room::getCurrentState(const QString& evtType, + const QString& stateKey) const +{ + return d->getCurrentState({ evtType, stateKey }); +} + RoomEventPtr Room::decryptMessage(EncryptedEvent* encryptedEvent) { if (encryptedEvent->algorithm() == OlmV1Curve25519AesSha2AlgoKey) { @@ -1149,18 +1161,18 @@ QString Room::decryptMessage(QJsonObject personalCipherObject, try { decrypted = session->decrypt(&preKeyMessage); } catch (std::runtime_error& e) { - qWarning(EVENTS) << "Decrypt failed:" << e.what(); + qCWarning(EVENTS) << "Decrypt failed:" << e.what(); } } else if (type == 1) { Message message { body }; if (!session->matches(&preKeyMessage, senderKey)) { - qWarning(EVENTS) << "Invalid encrypted message"; + qCWarning(EVENTS) << "Invalid encrypted message"; } try { decrypted = session->decrypt(&message); } catch (std::runtime_error& e) { - qWarning(EVENTS) << "Decrypt failed:" << e.what(); + qCWarning(EVENTS) << "Decrypt failed:" << e.what(); } } @@ -1443,11 +1455,12 @@ QString Room::Private::doSendEvent(const RoomEvent* pEvent) Room::connect(call, &BaseJob::sentRequest, q, [this, txnId] { auto it = q->findPendingEvent(txnId); if (it == unsyncedEvents.end()) { - qWarning(EVENTS) << "Pending event for transaction" << txnId + qCWarning(EVENTS) << "Pending event for transaction" << txnId << "not found - got synced so soon?"; return; } it->setDeparted(); + qCDebug(EVENTS) << "Event txn" << txnId << "has departed"; emit q->pendingEventChanged(int(it - unsyncedEvents.begin())); }); Room::connect(call, &BaseJob::failure, q, @@ -1457,7 +1470,7 @@ QString Room::Private::doSendEvent(const RoomEvent* pEvent) emit q->messageSent(txnId, call->eventId()); auto it = q->findPendingEvent(txnId); if (it == unsyncedEvents.end()) { - qDebug(EVENTS) << "Pending event for transaction" << txnId + qCDebug(EVENTS) << "Pending event for transaction" << txnId << "already merged"; return; } @@ -1489,7 +1502,7 @@ QString Room::retryMessage(const QString& txnId) { const auto it = findPendingEvent(txnId); Q_ASSERT(it != d->unsyncedEvents.end()); - qDebug(EVENTS) << "Retrying transaction" << txnId; + qCDebug(EVENTS) << "Retrying transaction" << txnId; const auto& transferIt = d->fileTransfers.find(txnId); if (transferIt != d->fileTransfers.end()) { Q_ASSERT(transferIt->isUpload); @@ -1527,7 +1540,7 @@ void Room::discardMessage(const QString& txnId) return evt->transactionId() == txnId; }); Q_ASSERT(it != d->unsyncedEvents.end()); - qDebug(EVENTS) << "Discarding transaction" << txnId; + qCDebug(EVENTS) << "Discarding transaction" << txnId; const auto& transferIt = d->fileTransfers.find(txnId); if (transferIt != d->fileTransfers.end()) { Q_ASSERT(transferIt->isUpload); @@ -2192,7 +2205,7 @@ Room::Changes Room::Private::addNewMessageEvents(RoomEvents&& events) emit q->pendingEventChanged(pendingEvtIdx); } emit q->pendingEventAboutToMerge(nextPendingEvt, pendingEvtIdx); - qDebug(MESSAGES) << "Merging pending event from transaction" + qCDebug(MESSAGES) << "Merging pending event from transaction" << nextPendingEvt->transactionId() << "into" << nextPendingEvt->id(); auto transfer = fileTransfers.take(nextPendingEvt->transactionId()); @@ -2323,13 +2336,13 @@ Room::Changes Room::processStateEvent(const RoomEvent& e) , [this,oldStateEvent] (const RoomAliasesEvent& ae) { // clang-format on if (ae.aliases().isEmpty()) { - qDebug(STATE).noquote() + qCDebug(STATE).noquote() << ae.stateKey() << "no more has aliases for room" << objectName(); d->aliasServers.remove(ae.stateKey()); } else { d->aliasServers.insert(ae.stateKey()); - qDebug(STATE).nospace().noquote() + qCDebug(STATE).nospace().noquote() << "New server with aliases for room " << objectName() << ": " << ae.stateKey(); } @@ -2665,7 +2678,7 @@ void Room::Private::updateDisplayname() if (swappedName != displayname) { emit q->displaynameAboutToChange(q); swap(displayname, swappedName); - qDebug(MAIN) << q->objectName() << "has changed display name from" + qCDebug(MAIN) << q->objectName() << "has changed display name from" << swappedName << "to" << displayname; emit q->displaynameChanged(q, swappedName); } @@ -187,8 +187,8 @@ public: Q_INVOKABLE QList<User*> users() const; QStringList memberNames() const; - [[deprecated("Use joinedCount(), invitedCount(), totalMemberCount()")]] int - memberCount() const; + [[deprecated("Use joinedCount(), invitedCount(), totalMemberCount()")]] + int memberCount() const; int timelineSize() const; bool usesEncryption() const; RoomEventPtr decryptMessage(EncryptedEvent* encryptedEvent); @@ -434,6 +434,24 @@ public: Q_INVOKABLE bool supportsCalls() const; + /// Get a state event with the given event type and state key + /*! This method returns a (potentially empty) state event corresponding + * to the pair of event type \p evtType and state key \p stateKey. + */ + Q_INVOKABLE const StateEventBase* + getCurrentState(const QString& evtType, const QString& stateKey = {}) const; + + template <typename EvT> + const EvT* getCurrentState(const QString& stateKey = {}) const + { + const auto* evt = + eventCast<const EvT>(getCurrentState(EvT::matrixTypeId(), stateKey)); + Q_ASSERT(evt); + Q_ASSERT(evt->matrixTypeId() == EvT::matrixTypeId() + && evt->stateKey() == stateKey); + return evt; + } + template <typename EvT, typename... ArgTs> auto setState(ArgTs&&... args) const { @@ -557,7 +575,7 @@ signals: * Aside from all changes in the room state * @param changes a set of flags describing what changes occurred * upon the last sync - * \sa StateChange + * \sa Changes */ void changed(Changes changes); /** diff --git a/lib/settings.cpp b/lib/settings.cpp index 3bf61605..0349e699 100644 --- a/lib/settings.cpp +++ b/lib/settings.cpp @@ -49,7 +49,10 @@ bool Settings::contains(const QString& key) const QStringList Settings::childGroups() const { auto l = QSettings::childGroups(); - return !l.isEmpty() ? l : legacySettings.childGroups(); + for (const auto& g: legacySettings.childGroups()) + if (!l.contains(g)) + l.push_back(g); + return l; } void SettingsGroup::setValue(const QString& key, const QVariant& value) @@ -88,11 +91,11 @@ void SettingsGroup::remove(const QString& key) Settings::remove(fullKey); } -QMC_DEFINE_SETTING(AccountSettings, QString, deviceId, "device_id", {}, +QTNT_DEFINE_SETTING(AccountSettings, QString, deviceId, "device_id", {}, setDeviceId) -QMC_DEFINE_SETTING(AccountSettings, QString, deviceName, "device_name", {}, +QTNT_DEFINE_SETTING(AccountSettings, QString, deviceName, "device_name", {}, setDeviceName) -QMC_DEFINE_SETTING(AccountSettings, bool, keepLoggedIn, "keep_logged_in", false, +QTNT_DEFINE_SETTING(AccountSettings, bool, keepLoggedIn, "keep_logged_in", false, setKeepLoggedIn) static const auto HomeserverKey = QStringLiteral("homeserver"); diff --git a/lib/settings.h b/lib/settings.h index d317ec2a..79ebba6c 100644 --- a/lib/settings.h +++ b/lib/settings.h @@ -119,7 +119,7 @@ private: QString groupPath; }; -#define QMC_DECLARE_SETTING(type, propname, setter) \ +#define QTNT_DECLARE_SETTING(type, propname, setter) \ Q_PROPERTY(type propname READ propname WRITE setter) \ public: \ type propname() const; \ @@ -127,7 +127,7 @@ public: \ \ private: -#define QMC_DEFINE_SETTING(classname, type, propname, qsettingname, \ +#define QTNT_DEFINE_SETTING(classname, type, propname, qsettingname, \ defaultValue, setter) \ type classname::propname() const \ { \ @@ -142,9 +142,9 @@ private: class AccountSettings : public SettingsGroup { Q_OBJECT Q_PROPERTY(QString userId READ userId CONSTANT) - QMC_DECLARE_SETTING(QString, deviceId, setDeviceId) - QMC_DECLARE_SETTING(QString, deviceName, setDeviceName) - QMC_DECLARE_SETTING(bool, keepLoggedIn, setKeepLoggedIn) + QTNT_DECLARE_SETTING(QString, deviceId, setDeviceId) + QTNT_DECLARE_SETTING(QString, deviceName, setDeviceName) + QTNT_DECLARE_SETTING(bool, keepLoggedIn, setKeepLoggedIn) /** \deprecated \sa setAccessToken */ Q_PROPERTY(QString accessToken READ accessToken WRITE setAccessToken) Q_PROPERTY(QByteArray encryptionAccountPickle READ encryptionAccountPickle diff --git a/lib/user.cpp b/lib/user.cpp index 641f6a6b..67cd1117 100644 --- a/lib/user.cpp +++ b/lib/user.cpp @@ -111,8 +111,7 @@ void User::Private::setNameForRoom(const Room* r, QString newName, et.start(); } - const auto& roomMap = connection->roomMap(); - for (auto* r1 : roomMap) + for (auto* r1: connection->allRooms()) if (nameForRoom(r1) == mostUsedName) otherNames.insert(mostUsedName, r1); @@ -176,8 +175,7 @@ void User::Private::setAvatarForRoom(const Room* r, const QUrl& newUrl, nextMostUsedIt = otherAvatars.end() - 1; } std::swap(mostUsedAvatar, *nextMostUsedIt); - const auto& roomMap = connection->roomMap(); - for (const auto* r1 : roomMap) + for (const auto* r1: connection->allRooms()) if (avatarUrlForRoom(r1) == nextMostUsedIt->url()) avatarsToRooms.insert(nextMostUsedIt->url(), r1); diff --git a/lib/util.cpp b/lib/util.cpp index cc18d9ab..041a8aba 100644 --- a/lib/util.cpp +++ b/lib/util.cpp @@ -141,63 +141,29 @@ int f(); static_assert(std::is_same<fn_return_t<decltype(f)>, int>::value, "Test fn_return_t<>"); -void f1(int); -static_assert(function_traits<decltype(f1)>::arg_number == 1, - "Test fn_arg_number"); - -void f2(int, QString); -static_assert(std::is_same<fn_arg_t<decltype(f2), 1>, QString>::value, +void f1(int, QString); +static_assert(std::is_same<fn_arg_t<decltype(f1), 1>, QString>::value, "Test fn_arg_t<>"); -struct S { - int mf(); -}; -static_assert(is_callable_v<decltype(&S::mf)>, "Test member function"); -static_assert(returns<int, decltype(&S::mf)>(), - "Test returns<> with member function"); - struct Fo { int operator()(); }; -static_assert(is_callable_v<Fo>, "Test is_callable<> with function object"); -static_assert(function_traits<Fo>::arg_number == 0, "Test function object"); static_assert(std::is_same<fn_return_t<Fo>, int>::value, "Test return type of function object"); struct Fo1 { void operator()(int); }; -static_assert(function_traits<Fo1>::arg_number == 1, "Test function object 1"); -static_assert(is_callable_v<Fo1>, "Test is_callable<> with function object 1"); static_assert(std::is_same<fn_arg_t<Fo1>, int>(), "Test fn_arg_t defaulting to first argument"); #if (!defined(_MSC_VER) || _MSC_VER >= 1910) static auto l = [] { return 1; }; -static_assert(is_callable_v<decltype(l)>, "Test is_callable_v<> with lambda"); static_assert(std::is_same<fn_return_t<decltype(l)>, int>::value, "Test fn_return_t<> with lambda"); #endif template <typename T> -struct fn_object { - static int smf(double) { return 0; } -}; -template <> -struct fn_object<QString> { - void operator()(QString); -}; -static_assert(is_callable_v<fn_object<QString>>, "Test function object"); -static_assert(returns<void, fn_object<QString>>(), - "Test returns<> with function object"); -static_assert(!is_callable_v<fn_object<int>>, "Test non-function object"); -// FIXME: These two don't work -// static_assert(is_callable_v<decltype(&fn_object<int>::smf)>, -// "Test static member function"); -// static_assert(returns<int, decltype(&fn_object<int>::smf)>(), -// "Test returns<> with static member function"); - -template <typename T> QString ft(T&&) { return {}; @@ -154,23 +154,19 @@ struct function_traits : public _impl::fn_traits<void, T> {}; // Specialisation for a function template <typename ReturnT, typename... ArgTs> struct function_traits<ReturnT(ArgTs...)> { - static constexpr auto is_callable = true; using return_type = ReturnT; using arg_types = std::tuple<ArgTs...>; - using function_type = std::function<ReturnT(ArgTs...)>; - static constexpr auto arg_number = std::tuple_size<arg_types>::value; }; namespace _impl { template <typename AlwaysVoid, typename T> - struct fn_traits { - static constexpr auto is_callable = false; - }; + struct fn_traits; + // Specialisation for function objects with (non-overloaded) operator() + // (this includes non-generic lambdas) template <typename T> struct fn_traits<decltype(void(&T::operator())), T> - : public fn_traits<void, decltype(&T::operator())> { - }; // A generic function object that has (non-overloaded) operator() + : public fn_traits<void, decltype(&T::operator())> {}; // Specialisation for a member function template <typename ReturnT, typename ClassT, typename... ArgTs> @@ -190,16 +186,6 @@ template <typename FnT, int ArgN = 0> using fn_arg_t = std::tuple_element_t<ArgN, typename function_traits<FnT>::arg_types>; -template <typename R, typename FnT> -constexpr bool returns() -{ - return std::is_same<fn_return_t<FnT>, R>::value; -} - -// Poor-man's is_invokable -template <typename T> -constexpr auto is_callable_v = function_traits<T>::is_callable; - inline auto operator"" _ls(const char* s, std::size_t size) { return QLatin1String(s, int(size)); |