aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.appveyor.yml6
-rw-r--r--.clang-format2
-rw-r--r--CMakeLists.txt3
-rw-r--r--lib/connection.cpp49
-rw-r--r--lib/connection.h29
-rw-r--r--lib/events/event.h30
-rw-r--r--lib/jobs/basejob.cpp28
-rw-r--r--lib/jobs/downloadfilejob.cpp2
-rw-r--r--lib/networksettings.cpp6
-rw-r--r--lib/networksettings.h6
-rw-r--r--lib/qt_connection_util.h114
-rw-r--r--lib/room.cpp59
-rw-r--r--lib/room.h24
-rw-r--r--lib/settings.cpp11
-rw-r--r--lib/settings.h10
-rw-r--r--lib/user.cpp6
-rw-r--r--lib/util.cpp38
-rw-r--r--lib/util.h22
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);
}
diff --git a/lib/room.h b/lib/room.h
index 9113654b..cded7eb9 100644
--- a/lib/room.h
+++ b/lib/room.h
@@ -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 {};
diff --git a/lib/util.h b/lib/util.h
index 788ce5bc..4631570f 100644
--- a/lib/util.h
+++ b/lib/util.h
@@ -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));