aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKitsune Ral <Kitsune-Ral@users.sf.net>2018-04-29 18:29:56 +0900
committerKitsune Ral <Kitsune-Ral@users.sf.net>2018-04-29 18:29:56 +0900
commitb5d962b0f5dcd3497db8a4b3bada46bb666d08e1 (patch)
tree10d6e3add6b73cf9e4cecdeca08c72b490c50f7a
parent4ced9792f27ed3d891c1f7772ae30d9fe452dd79 (diff)
parentf5af25428212f139c59941bb294a184242c8b5e0 (diff)
downloadlibquotient-b5d962b0f5dcd3497db8a4b3bada46bb666d08e1.tar.gz
libquotient-b5d962b0f5dcd3497db8a4b3bada46bb666d08e1.zip
Merge branch 'master' into kitsune-gtad
-rw-r--r--CMakeLists.txt2
-rw-r--r--lib/avatar.cpp44
-rw-r--r--lib/connection.cpp11
-rw-r--r--lib/connection.h6
-rw-r--r--lib/converters.h31
-rw-r--r--lib/jobs/basejob.cpp73
-rw-r--r--lib/jobs/basejob.h4
-rw-r--r--lib/room.cpp51
-rw-r--r--lib/room.h6
-rw-r--r--lib/user.cpp12
-rw-r--r--lib/user.h27
-rw-r--r--lib/util.h28
12 files changed, 205 insertions, 90 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 094ec488..b966476f 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -40,7 +40,7 @@ foreach (FLAG all "" pedantic extra error=return-type no-unused-parameter no-gnu
endif ()
endforeach ()
-find_package(Qt5 5.5.1 REQUIRED Network Gui)
+find_package(Qt5 5.4.1 REQUIRED Network Gui)
get_filename_component(Qt5_Prefix "${Qt5_DIR}/../../../.." ABSOLUTE)
message( STATUS )
diff --git a/lib/avatar.cpp b/lib/avatar.cpp
index 1ff2aae1..7e6dc50b 100644
--- a/lib/avatar.cpp
+++ b/lib/avatar.cpp
@@ -1,3 +1,5 @@
+#include <utility>
+
/******************************************************************************
* Copyright (C) 2017 Kitsune Ral <kitsune-ral@users.sf.net>
*
@@ -26,12 +28,13 @@
#include <QtCore/QPointer>
using namespace QMatrixClient;
+using std::move;
class Avatar::Private
{
public:
explicit Private(QIcon di, QUrl url = {})
- : _defaultIcon(di), _url(url)
+ : _defaultIcon(move(di)), _url(move(url))
{ }
QImage get(Connection* connection, QSize size,
get_callback_t callback) const;
@@ -51,7 +54,6 @@ class Avatar::Private
mutable QPointer<MediaThumbnailJob> _thumbnailRequest = nullptr;
mutable QPointer<BaseJob> _uploadRequest = nullptr;
mutable std::vector<get_callback_t> callbacks;
- mutable get_callback_t uploadCallback;
};
Avatar::Avatar(QIcon defaultIcon)
@@ -71,13 +73,13 @@ Avatar& Avatar::operator=(Avatar&&) = default;
QImage Avatar::get(Connection* connection, int dimension,
get_callback_t callback) const
{
- return d->get(connection, {dimension, dimension}, callback);
+ return d->get(connection, {dimension, dimension}, move(callback));
}
QImage Avatar::get(Connection* connection, int width, int height,
get_callback_t callback) const
{
- return d->get(connection, {width, height}, callback);
+ return d->get(connection, {width, height}, move(callback));
}
bool Avatar::upload(Connection* connection, const QString& fileName,
@@ -85,7 +87,7 @@ bool Avatar::upload(Connection* connection, const QString& fileName,
{
if (isJobRunning(d->_uploadRequest))
return false;
- return d->upload(connection->uploadFile(fileName), callback);
+ return d->upload(connection->uploadFile(fileName), move(callback));
}
bool Avatar::upload(Connection* connection, QIODevice* source,
@@ -93,7 +95,7 @@ bool Avatar::upload(Connection* connection, QIODevice* source,
{
if (isJobRunning(d->_uploadRequest) || !source->isReadable())
return false;
- return d->upload(connection->uploadContent(source), callback);
+ return d->upload(connection->uploadContent(source), move(callback));
}
QString Avatar::mediaId() const
@@ -104,6 +106,11 @@ QString Avatar::mediaId() const
QImage Avatar::Private::get(Connection* connection, QSize size,
get_callback_t callback) const
{
+ if (!callback)
+ {
+ qCCritical(MAIN) << "Null callbacks are not allowed in Avatar::get";
+ Q_ASSERT(false);
+ }
// FIXME: Alternating between longer-width and longer-height requests
// is a sure way to trick the below code into constantly getting another
// image from the server because the existing one is alleged unsatisfactory.
@@ -116,16 +123,19 @@ QImage Avatar::Private::get(Connection* connection, QSize size,
_requestedSize = size;
if (isJobRunning(_thumbnailRequest))
_thumbnailRequest->abandon();
- callbacks.emplace_back(std::move(callback));
+ if (callback)
+ callbacks.emplace_back(move(callback));
_thumbnailRequest = connection->getThumbnail(_url, size);
- QObject::connect( _thumbnailRequest, &MediaThumbnailJob::success, [this]
- {
- _fetched = true;
- _originalImage = _thumbnailRequest->scaledThumbnail(_requestedSize);
- _scaledImages.clear();
- for (auto n: callbacks)
- n();
- });
+ QObject::connect( _thumbnailRequest, &MediaThumbnailJob::success,
+ _thumbnailRequest, [this] {
+ _fetched = true;
+ _originalImage =
+ _thumbnailRequest->scaledThumbnail(_requestedSize);
+ _scaledImages.clear();
+ for (const auto& n: callbacks)
+ n();
+ callbacks.clear();
+ });
}
if( _originalImage.isNull() )
@@ -137,7 +147,7 @@ QImage Avatar::Private::get(Connection* connection, QSize size,
_defaultIcon.paint(&p, { QPoint(), _defaultIcon.actualSize(size) });
}
- for (auto p: _scaledImages)
+ for (const auto& p: _scaledImages)
if (p.first == size)
return p.second;
auto result = _originalImage.scaled(size,
@@ -151,7 +161,7 @@ bool Avatar::Private::upload(UploadContentJob* job, upload_callback_t callback)
_uploadRequest = job;
if (!isJobRunning(_uploadRequest))
return false;
- _uploadRequest->connect(_uploadRequest, &BaseJob::success,
+ _uploadRequest->connect(_uploadRequest, &BaseJob::success, _uploadRequest,
[job,callback] { callback(job->contentUri()); });
return true;
}
diff --git a/lib/connection.cpp b/lib/connection.cpp
index 600ab396..5f930d57 100644
--- a/lib/connection.cpp
+++ b/lib/connection.cpp
@@ -84,7 +84,7 @@ class Connection::Private
QVector<QString> roomIdsToForget;
QMap<QString, User*> userMap;
DirectChatsMap directChats;
- QHash<QString, QVariantHash> accountData;
+ QHash<QString, AccountDataMap> accountData;
QString userId;
SyncJob* syncJob = nullptr;
@@ -336,7 +336,7 @@ void Connection::onSyncSuccess(SyncData &&data) {
continue;
}
d->accountData[accountEvent->jsonType()] =
- accountEvent->contentJson().toVariantHash();
+ fromJson<AccountDataMap>(accountEvent->contentJson());
emit accountDataChanged(accountEvent->jsonType());
}
}
@@ -590,6 +590,7 @@ Room* Connection::invitation(const QString& roomId) const
User* Connection::user(const QString& userId)
{
+ Q_ASSERT(userId.startsWith('@') && userId.contains(':'));
if( d->userMap.contains(userId) )
return d->userMap.value(userId);
auto* user = userFactory(this, userId);
@@ -657,7 +658,7 @@ bool Connection::hasAccountData(const QString& type) const
return d->accountData.contains(type);
}
-QVariantHash Connection::accountData(const QString& type) const
+Connection::AccountDataMap Connection::accountData(const QString& type) const
{
return d->accountData.value(type);
}
@@ -847,7 +848,7 @@ void Connection::setHomeserver(const QUrl& url)
emit homeserverChanged(homeserver());
}
-static constexpr int CACHE_VERSION_MAJOR = 7;
+static constexpr int CACHE_VERSION_MAJOR = 8;
static constexpr int CACHE_VERSION_MINOR = 0;
void Connection::saveState(const QUrl &toFile) const
@@ -909,7 +910,7 @@ void Connection::saveState(const QUrl &toFile) const
for (auto it = d->accountData.begin(); it != d->accountData.end(); ++it)
accountDataEvents.append(QJsonObject {
{"type", it.key()},
- {"content", QJsonObject::fromVariantHash(it.value())}
+ {"content", QMatrixClient::toJson(it.value())}
});
rootObj.insert("account_data",
QJsonObject {{ QStringLiteral("events"), accountDataEvents }});
diff --git a/lib/connection.h b/lib/connection.h
index be414931..839371ef 100644
--- a/lib/connection.h
+++ b/lib/connection.h
@@ -66,6 +66,10 @@ namespace QMatrixClient
using DirectChatsMap = QMultiHash<const User*, QString>;
+ using AccountDataMap = std::conditional_t<
+ QT_VERSION >= QT_VERSION_CHECK(5, 5, 0),
+ QVariantHash, QVariantMap>;
+
enum RoomVisibility { PublishRoom, UnpublishRoom }; // FIXME: Should go inside CreateRoomJob
explicit Connection(QObject* parent = nullptr);
@@ -88,7 +92,7 @@ namespace QMatrixClient
* stored on the server. Direct chats map cannot be retrieved
* using this method _yet_; use directChats() instead.
*/
- QVariantHash accountData(const QString& type) const;
+ AccountDataMap accountData(const QString& type) const;
/** Get all Invited and Joined rooms grouped by tag
* \return a hashmap from tag name to a vector of room pointers,
diff --git a/lib/converters.h b/lib/converters.h
index bba298e0..22b22f25 100644
--- a/lib/converters.h
+++ b/lib/converters.h
@@ -21,6 +21,7 @@
#include <QtCore/QJsonObject>
#include <QtCore/QJsonArray> // Includes <QtCore/QJsonValue>
#include <QtCore/QDate>
+#include <QtCore/QVariant>
namespace QMatrixClient
{
@@ -51,6 +52,18 @@ namespace QMatrixClient
return QJsonValue(bytes.constData());
}
+ inline QJsonObject toJson(const QVariantMap& map)
+ {
+ return QJsonObject::fromVariantMap(map);
+ }
+
+#if (QT_VERSION >= QT_VERSION_CHECK(5, 5, 0))
+ inline QJsonObject toJson(const QVariantHash& hMap)
+ {
+ return QJsonObject::fromVariantHash(hMap);
+ }
+#endif
+
template <typename T>
inline QJsonObject toJson(const QHash<QString, T>& hashMap)
{
@@ -163,6 +176,24 @@ namespace QMatrixClient
}
};
+ template <> struct FromJson<QVariantMap>
+ {
+ inline auto operator()(const QJsonValue& jv) const
+ {
+ return jv.toObject().toVariantMap();
+ }
+ };
+
+#if (QT_VERSION >= QT_VERSION_CHECK(5, 5, 0))
+ template <> struct FromJson<QVariantHash>
+ {
+ inline auto operator()(const QJsonValue& jv) const
+ {
+ return jv.toObject().toVariantHash();
+ }
+ };
+#endif
+
template <typename T> struct FromJson<QHash<QString, T>>
{
QHash<QString, T> operator()(const QJsonValue& jv) const
diff --git a/lib/jobs/basejob.cpp b/lib/jobs/basejob.cpp
index 696861fb..2d41650a 100644
--- a/lib/jobs/basejob.cpp
+++ b/lib/jobs/basejob.cpp
@@ -336,39 +336,50 @@ bool checkContentType(const QByteArray& type, const QByteArrayList& patterns)
BaseJob::Status BaseJob::doCheckReply(QNetworkReply* reply) const
{
- const auto httpCode =
- reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
-
- if (httpCode == 429) // Qt doesn't know about it yet
- return { TooManyRequestsError, tr("Too many requests") };
-
- // Should we check httpCode instead? Maybe even use it in BaseJob::Status?
- // That would make codes in logs slightly more readable.
- switch( reply->error() )
+ // QNetworkReply error codes seem to be flawed when it comes to HTTP;
+ // see, e.g., https://github.com/QMatrixClient/libqmatrixclient/issues/200
+ // so check genuine HTTP codes. The below processing is based on
+ // https://en.wikipedia.org/wiki/List_of_HTTP_status_codes
+ const auto httpCodeHeader =
+ reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
+ if (!httpCodeHeader.isValid()) // Woah, we didn't even get HTTP headers
+ return { NetworkError, reply->errorString() };
+
+ const auto httpCode = httpCodeHeader.toInt();
+ if (httpCode / 100 == 2) // 2xx
{
- case QNetworkReply::NoError:
- if (checkContentType(reply->rawHeader("Content-Type"),
- d->expectedContentTypes))
- return NoError;
- else // A warning in the logs might be more proper instead
- return { IncorrectResponseError,
- "Incorrect content type of the response" };
-
- case QNetworkReply::AuthenticationRequiredError:
- case QNetworkReply::ContentAccessDenied:
- case QNetworkReply::ContentOperationNotPermittedError:
- return { ContentAccessError, reply->errorString() };
-
- case QNetworkReply::ProtocolInvalidOperationError:
- case QNetworkReply::UnknownContentError:
- return { IncorrectRequestError, reply->errorString() };
-
- case QNetworkReply::ContentNotFoundError:
- return { NotFoundError, reply->errorString() };
-
- default:
- return { NetworkError, reply->errorString() };
+ if (checkContentType(reply->rawHeader("Content-Type"),
+ d->expectedContentTypes))
+ return NoError;
+ else // A warning in the logs might be more proper instead
+ return { UnexpectedResponseTypeWarning,
+ "Unexpected content type of the response" };
}
+
+ return { [httpCode]() -> StatusCode {
+ if (httpCode / 10 == 41)
+ return httpCode == 410 ? IncorrectRequestError : NotFoundError;
+ switch (httpCode)
+ {
+ case 401: case 403: case 407:
+ return ContentAccessError;
+ case 404:
+ return NotFoundError;
+ case 400: case 405: case 406: case 426: case 428:
+ case 505:
+ case 494: // Unofficial nginx "Request header too large"
+ case 497: // Unofficial nginx "HTTP request sent to HTTPS port"
+ return IncorrectRequestError;
+ case 429:
+ return TooManyRequestsError;
+ case 501: case 510:
+ return RequestNotImplementedError;
+ case 511:
+ return NetworkAuthRequiredError;
+ default:
+ return NetworkError;
+ }
+ }(), reply->errorString() };
}
BaseJob::Status BaseJob::parseReply(QNetworkReply* reply)
diff --git a/lib/jobs/basejob.h b/lib/jobs/basejob.h
index f243066f..763ef75a 100644
--- a/lib/jobs/basejob.h
+++ b/lib/jobs/basejob.h
@@ -53,6 +53,8 @@ namespace QMatrixClient
enum StatusCode { NoError = 0 // To be compatible with Qt conventions
, Success = 0
, Pending = 1
+ , WarningLevel = 20
+ , UnexpectedResponseTypeWarning = 21
, Abandoned = 50 //< A very brief period between abandoning and object deletion
, ErrorLevel = 100 //< Errors have codes starting from this
, NetworkError = 100
@@ -63,6 +65,8 @@ namespace QMatrixClient
, IncorrectRequestError
, IncorrectResponseError
, TooManyRequestsError
+ , RequestNotImplementedError
+ , NetworkAuthRequiredError
, UserDefinedError = 200
};
diff --git a/lib/room.cpp b/lib/room.cpp
index edbc9266..1fa9212e 100644
--- a/lib/room.cpp
+++ b/lib/room.cpp
@@ -109,7 +109,7 @@ class Room::Private
QHash<const User*, QString> lastReadEventIds;
QString serverReadMarker;
TagsMap tags;
- QHash<QString, QVariantHash> accountData;
+ QHash<QString, AccountDataMap> accountData;
QString prevBatch;
QPointer<RoomMessagesJob> roomMessagesJob;
@@ -302,7 +302,8 @@ QImage Room::avatar(int dimension)
QImage Room::avatar(int width, int height)
{
if (!d->avatar.url().isEmpty())
- return d->avatar.get(connection(), width, height, [=] { emit avatarChanged(); });
+ return d->avatar.get(connection(), width, height,
+ [=] { emit avatarChanged(); });
// Use the other side's avatar for 1:1's
if (d->membersMap.size() == 2)
@@ -637,7 +638,7 @@ bool Room::hasAccountData(const QString& type) const
return d->accountData.contains(type);
}
-QVariantHash Room::accountData(const QString& type) const
+Room::AccountDataMap Room::accountData(const QString& type) const
{
return d->accountData.value(type);
}
@@ -991,26 +992,32 @@ QString Room::roomMembername(const User* u) const
if (username.isEmpty())
return u->id();
- // Get the list of users with the same display name. Most likely,
- // there'll be one, but there's a chance there are more.
- if (d->membersMap.count(username) == 1)
- return username;
+ auto namesakesIt = qAsConst(d->membersMap).find(username);
// We expect a user to be a member of the room - but technically it is
// possible to invoke roomMemberName() even for non-members. In such case
- // we return the name _with_ id, to stay on a safe side.
- // XXX: Causes a storm of false alarms when scrolling through older events
- // with left users; commented out until we have a proper backtracking of
- // room state ("room time machine").
-// if ( !namesakes.contains(u) )
-// {
-// qCWarning()
-// << "Room::roomMemberName(): user" << u->id()
-// << "is not a member of the room" << id();
-// }
+ // we return the full name, just in case.
+ if (namesakesIt == d->membersMap.cend())
+ return u->fullName(this);
+
+ auto nextUserIt = namesakesIt + 1;
+ if (nextUserIt == d->membersMap.cend() || nextUserIt.key() != username)
+ return username; // No disambiguation necessary
+
+ // Check if we can get away just attaching the bridge postfix
+ // (extension to the spec)
+ QVector<QString> bridges;
+ for (; namesakesIt != d->membersMap.cend() && namesakesIt.key() == username;
+ ++namesakesIt)
+ {
+ const auto bridgeName = (*namesakesIt)->bridged();
+ if (bridges.contains(bridgeName)) // Two accounts on the same bridge
+ return u->fullName(this); // Disambiguate fully
+ // Don't bother sorting, not so many bridges out there
+ bridges.push_back(bridgeName);
+ }
- // In case of more than one namesake, use the full name to disambiguate
- return u->fullName(this);
+ return u->rawName(this); // Disambiguate using the bridge postfix only
}
QString Room::roomMembername(const QString& userId) const
@@ -1653,7 +1660,7 @@ void Room::processAccountDataEvent(EventPtr event)
}
default:
d->accountData[event->jsonType()] =
- event->contentJson().toVariantHash();
+ fromJson<AccountDataMap>(event->contentJson());
emit accountDataChanged(event->jsonType());
}
}
@@ -1795,7 +1802,7 @@ QJsonObject Room::Private::toJson() const
for (const auto *m : membersMap)
appendStateEvent(stateEvents, QStringLiteral("m.room.member"),
{ { QStringLiteral("membership"), QStringLiteral("join") }
- , { QStringLiteral("displayname"), m->name(q) }
+ , { QStringLiteral("displayname"), m->rawName(q) }
, { QStringLiteral("avatar_url"), m->avatarUrl(q).toString() }
}, m->id());
@@ -1816,7 +1823,7 @@ QJsonObject Room::Private::toJson() const
{
for (auto it = accountData.begin(); it != accountData.end(); ++it)
appendEvent(accountDataEvents, it.key(),
- QJsonObject::fromVariantHash(it.value()));
+ QMatrixClient::toJson(it.value()));
}
result.insert("account_data", QJsonObject {{ "events", accountDataEvents }});
diff --git a/lib/room.h b/lib/room.h
index 39dee8f5..86a7b245 100644
--- a/lib/room.h
+++ b/lib/room.h
@@ -123,6 +123,10 @@ namespace QMatrixClient
using rev_iter_t = Timeline::const_reverse_iterator;
using timeline_iter_t = Timeline::const_iterator;
+ using AccountDataMap = std::conditional_t<
+ QT_VERSION >= QT_VERSION_CHECK(5, 5, 0),
+ QVariantHash, QVariantMap>;
+
Room(Connection* connection, QString id, JoinState initialJoinState);
~Room() override;
@@ -269,7 +273,7 @@ namespace QMatrixClient
* stored on the server. Tags and read markers cannot be retrieved
* using this method _yet_.
*/
- QVariantHash accountData(const QString& type) const;
+ AccountDataMap accountData(const QString& type) const;
QStringList tagNames() const;
TagsMap tags() const;
diff --git a/lib/user.cpp b/lib/user.cpp
index 6143a061..c4fbfe35 100644
--- a/lib/user.cpp
+++ b/lib/user.cpp
@@ -220,6 +220,12 @@ QString User::name(const Room* room) const
return d->nameForRoom(room);
}
+QString User::rawName(const Room* room) const
+{
+ return d->bridged.isEmpty() ? name(room) :
+ name(room) % " (" % d->bridged % ')';
+}
+
void User::updateName(const QString& newName, const Room* room)
{
updateName(newName, d->nameForRoom(room), room);
@@ -305,7 +311,7 @@ QString User::displayname(const Room* room) const
{
auto name = d->nameForRoom(room);
return name.isEmpty() ? d->userId :
- room ? room->roomMembername(name) : name;
+ room ? room->roomMembername(this) : name;
}
QString User::fullName(const Room* room) const
@@ -389,12 +395,12 @@ void User::processEvent(RoomMemberEvent* event, const Room* room)
// FIXME: the hint doesn't work for bridged users
auto oldNameHint =
d->nameForRoom(room, event->prevContent()->displayName);
- updateName(event->displayName(), oldNameHint, room);
+ updateName(newName, oldNameHint, room);
updateAvatarUrl(event->avatarUrl(),
d->avatarUrlForRoom(room, event->prevContent()->avatarUrl),
room);
} else {
- updateName(event->displayName(), room);
+ updateName(newName, room);
updateAvatarUrl(event->avatarUrl(), d->avatarUrlForRoom(room), room);
}
}
diff --git a/lib/user.h b/lib/user.h
index f76f9e0a..f94fbee4 100644
--- a/lib/user.h
+++ b/lib/user.h
@@ -50,24 +50,33 @@ namespace QMatrixClient
/** Get the name chosen by the user
* This may be empty if the user didn't choose the name or cleared
- * it.
- * \sa displayName
+ * it. If the user is bridged, the bridge postfix (such as '(IRC)')
+ * is stripped out. No disambiguation for the room is done.
+ * \sa displayName, rawName
*/
QString name(const Room* room = nullptr) const;
+ /** Get the user name along with the bridge postfix
+ * This function is similar to name() but appends the bridge postfix
+ * (such as '(IRC)') to the user name. No disambiguation is done.
+ * \sa name, displayName
+ */
+ QString rawName(const Room* room = nullptr) const;
+
/** Get the displayed user name
- * This method returns the result of name() if its non-empty;
- * otherwise it returns user id. This is convenient to show a user
- * name outside of a room context. In a room context, user names
- * should be disambiguated.
- * \sa name, id, fullName Room::roomMembername
+ * When \p room is null, this method returns result of name() if
+ * the name is non-empty; otherwise it returns user id.
+ * When \p room is non-null, this call is equivalent to
+ * Room::roomMembername invocation for the user (i.e. the user's
+ * disambiguated room-specific name is returned).
+ * \sa name, id, fullName, Room::roomMembername
*/
QString displayname(const Room* room = nullptr) const;
/** Get user name and id in one string
* The constructed string follows the format 'name (id)'
- * used for users disambiguation in a room context and in other
- * places.
+ * which the spec recommends for users disambiguation in
+ * a room context and in other places.
* \sa displayName, Room::roomMembername
*/
QString fullName(const Room* room = nullptr) const;
diff --git a/lib/util.h b/lib/util.h
index 92198b0b..7ab1e20d 100644
--- a/lib/util.h
+++ b/lib/util.h
@@ -20,6 +20,7 @@
#include <QtCore/QMetaEnum>
#include <QtCore/QDebug>
+#include <QtCore/QPointer>
#include <functional>
#include <memory>
@@ -41,6 +42,7 @@ namespace QMatrixClient
}
#endif
+ /** static_cast<> for unique_ptr's */
template <typename T1, typename PtrT2>
inline auto unique_ptr_cast(PtrT2&& p)
{
@@ -55,5 +57,31 @@ namespace QMatrixClient
template <typename T>
static void qAsConst(const T &&) Q_DECL_EQ_DELETE;
#endif
+
+ /** 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.
+ */
+ template <typename T>
+ class ConnectionsGuard : public QPointer<T>
+ {
+ public:
+ ConnectionsGuard(T* publisher, QObject* subscriber)
+ : QPointer<T>(publisher), subscriber(subscriber)
+ { }
+ ~ConnectionsGuard()
+ {
+ if (*this)
+ (*this)->disconnect(subscriber);
+ }
+ ConnectionsGuard(ConnectionsGuard&&) noexcept = default;
+ ConnectionsGuard& operator=(ConnectionsGuard&&) noexcept = default;
+ using QPointer<T>::operator=;
+
+ private:
+ QObject* subscriber;
+ };
} // namespace QMatrixClient