aboutsummaryrefslogtreecommitdiff
path: root/lib/user.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'lib/user.cpp')
-rw-r--r--lib/user.cpp256
1 files changed, 74 insertions, 182 deletions
diff --git a/lib/user.cpp b/lib/user.cpp
index 4e369a4f..4c3fc9e2 100644
--- a/lib/user.cpp
+++ b/lib/user.cpp
@@ -1,20 +1,6 @@
-/******************************************************************************
- * Copyright (C) 2015 Felix Rohrbach <kde@fxrh.de>
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- */
+// SPDX-FileCopyrightText: 2015 Felix Rohrbach <kde@fxrh.de>
+// SPDX-FileCopyrightText: 2016 Kitsune Ral <Kitsune-Ral@users.sf.net>
+// SPDX-License-Identifier: LGPL-2.1-or-later
#include "user.h"
@@ -47,47 +33,26 @@ public:
QString id;
qreal hueF;
- // In the following two, isNull/nullopt mean they are uninitialised;
- // isEmpty/Avatar::url().isEmpty() mean they are initialised but empty.
QString defaultName;
- std::optional<Avatar> defaultAvatar;
-
+ Avatar defaultAvatar;
// NB: This container is ever-growing. Even if the user no more scrolls
// the timeline that far back, historical avatars are still kept around.
// This is consistent with the rest of Quotient, as room timelines
- // are never rotated either. This will probably change in the future.
+ // are never vacuumed either. This will probably change in the future.
/// Map of mediaId to Avatar objects
static UnorderedMap<QString, Avatar> otherAvatars;
-
- void fetchProfile(const User* q);
-
- template <typename SourceT>
- bool doSetAvatar(SourceT&& source, User* q);
};
decltype(User::Private::otherAvatars) User::Private::otherAvatars {};
-void User::Private::fetchProfile(const User* q)
-{
- defaultAvatar.emplace(Avatar {});
- defaultName = "";
- auto* j =
- q->connection()->callApi<GetUserProfileJob>(BackgroundRequest,
- QUrl::toPercentEncoding(id));
- // FIXME: accepting const User* and const_cast'ing it here is only
- // until we get a better User API in 0.7
- QObject::connect(j, &BaseJob::success, q,
- [this, q = const_cast<User*>(q), j] {
- q->updateName(j->displayname());
- defaultAvatar->updateUrl(j->avatarUrl());
- emit q->avatarChanged(q, nullptr);
- });
-}
-
User::User(QString userId, Connection* connection)
- : QObject(connection), d(new Private(move(userId)))
+ : QObject(connection), d(makeImpl<Private>(move(userId)))
{
setObjectName(id());
+ if (connection->userId() == id()) {
+ // Load profile information for local user.
+ load();
+ }
}
Connection* User::connection() const
@@ -96,7 +61,17 @@ Connection* User::connection() const
return static_cast<Connection*>(parent());
}
-User::~User() = default;
+void User::load()
+{
+ auto* profileJob =
+ connection()->callApi<GetUserProfileJob>(id());
+ connect(profileJob, &BaseJob::result, this, [this, profileJob] {
+ d->defaultName = profileJob->displayname();
+ d->defaultAvatar = Avatar(QUrl(profileJob->avatarUrl()));
+ emit defaultNameChanged();
+ emit defaultAvatarChanged();
+ });
+}
QString User::id() const { return d->id; }
@@ -105,52 +80,16 @@ bool User::isGuest() const
Q_ASSERT(!d->id.isEmpty() && d->id.startsWith('@'));
auto it = std::find_if_not(d->id.cbegin() + 1, d->id.cend(),
[](QChar c) { return c.isDigit(); });
- Q_ASSERT(it != d->id.end());
+ Q_ASSERT(it != d->id.cend());
return *it == ':';
}
int User::hue() const { return int(hueF() * 359); }
-/// \sa https://github.com/matrix-org/matrix-doc/issues/1375
-///
-/// Relies on untrusted prevContent so can't be put to RoomMemberEvent and
-/// in general should rather be remade in terms of the room's eventual "state
-/// time machine"
-QString getBestKnownName(const RoomMemberEvent* event)
-{
- const auto& jv = event->contentJson().value("displayname"_ls);
- return !jv.isUndefined()
- ? jv.toString()
- : event->prevContent() ? event->prevContent()->displayName
- : QString();
-}
-
QString User::name(const Room* room) const
{
- if (room)
- return getBestKnownName(room->getCurrentState<RoomMemberEvent>(id()));
-
- if (d->defaultName.isNull())
- d->fetchProfile(this);
-
- return d->defaultName;
-}
-
-QString User::rawName(const Room* room) const { return name(room); }
-
-void User::updateName(const QString& newName, const Room* r)
-{
- Q_ASSERT(r == nullptr);
- if (newName == d->defaultName)
- return;
-
- emit nameAboutToChange(newName, d->defaultName, nullptr);
- const auto& oldName =
- std::exchange(d->defaultName, newName);
- emit nameChanged(d->defaultName, oldName, nullptr);
+ return room ? room->memberName(id()) : d->defaultName;
}
-void User::updateName(const QString&, const QString&, const Room*) {}
-void User::updateAvatarUrl(const QUrl&, const QUrl&, const Room*) {}
void User::rename(const QString& newName)
{
@@ -160,12 +99,18 @@ void User::rename(const QString& newName)
connect(connection()->callApi<SetDisplayNameJob>(id(), actualNewName),
&BaseJob::success, this, [this, actualNewName] {
- d->fetchProfile(this);
- updateName(actualNewName);
+ // Check again, it could have changed meanwhile
+ if (actualNewName != d->defaultName) {
+ d->defaultName = actualNewName;
+ emit defaultNameChanged();
+ } else
+ qCWarning(MAIN)
+ << "User" << id() << "already has profile name set to"
+ << actualNewName;
});
}
-void User::rename(const QString& newName, const Room* r)
+void User::rename(const QString& newName, Room* r)
{
if (!r) {
qCWarning(MAIN) << "Passing a null room to two-argument User::rename()"
@@ -174,51 +119,51 @@ void User::rename(const QString& newName, const Room* r)
return;
}
// #481: take the current state and update it with the new name
- auto evtC = r->getCurrentState<RoomMemberEvent>(id())->content();
- Q_ASSERT_X(evtC.membership == MembershipType::Join, __FUNCTION__,
- "Attempt to rename a user that's not a room member");
- evtC.displayName = sanitized(newName);
- r->setState<RoomMemberEvent>(id(), move(evtC));
- // The state will be updated locally after it arrives with sync
+ if (const auto& maybeEvt = r->currentState().get<RoomMemberEvent>(id())) {
+ auto content = maybeEvt->content();
+ if (content.membership == Membership::Join) {
+ content.displayName = sanitized(newName);
+ r->setState<RoomMemberEvent>(id(), move(content));
+ // The state will be updated locally after it arrives with sync
+ return;
+ }
+ }
+ qCCritical(MEMBERS)
+ << "Attempt to rename a non-member in a room context - ignored";
}
template <typename SourceT>
-bool User::Private::doSetAvatar(SourceT&& source, User* q)
-{
- if (!defaultAvatar) {
- defaultName = "";
- defaultAvatar.emplace(Avatar {});
- }
- return defaultAvatar->upload(
- q->connection(), source, [this, q](const QString& contentUri) {
- auto* j =
- q->connection()->callApi<SetAvatarUrlJob>(id, contentUri);
- QObject::connect(j, &BaseJob::success, q,
- [this, q, newUrl = QUrl(contentUri)] {
- // Fetch displayname to complete the profile
- fetchProfile(q);
- if (newUrl == defaultAvatar->url()) {
- qCWarning(MAIN)
- << "User" << id
- << "already has avatar URL set to"
- << newUrl.toDisplayString();
- return;
- }
-
- defaultAvatar->updateUrl(newUrl);
- emit q->avatarChanged(q, nullptr);
- });
+inline bool User::doSetAvatar(SourceT&& source)
+{
+ return d->defaultAvatar.upload(
+ connection(), source, [this](const QUrl& contentUri) {
+ auto* j = connection()->callApi<SetAvatarUrlJob>(id(), contentUri);
+ connect(j, &BaseJob::success, this,
+ [this, contentUri] {
+ if (contentUri == d->defaultAvatar.url()) {
+ d->defaultAvatar.updateUrl(contentUri);
+ emit defaultAvatarChanged();
+ } else
+ qCWarning(MAIN) << "User" << id()
+ << "already has avatar URL set to"
+ << contentUri.toDisplayString();
+ });
});
}
bool User::setAvatar(const QString& fileName)
{
- return d->doSetAvatar(fileName, this);
+ return doSetAvatar(fileName);
}
bool User::setAvatar(QIODevice* source)
{
- return d->doSetAvatar(source, this);
+ return doSetAvatar(source);
+}
+
+void User::removeAvatar()
+{
+ connection()->callApi<SetAvatarUrlJob>(id(), QUrl());
}
void User::requestDirectChat() { connection()->requestDirectChat(this); }
@@ -231,13 +176,8 @@ bool User::isIgnored() const { return connection()->isIgnored(this); }
QString User::displayname(const Room* room) const
{
- if (room)
- return room->roomMembername(this);
-
- if (auto n = name(); !n.isEmpty())
- return n;
-
- return d->id;
+ return room ? room->safeMemberName(id())
+ : d->defaultName.isEmpty() ? d->id : d->defaultName;
}
QString User::fullName(const Room* room) const
@@ -246,50 +186,30 @@ QString User::fullName(const Room* room) const
return displayName.isEmpty() ? id() : (displayName % " (" % id() % ')');
}
-QString User::bridged() const { return {}; }
-
-/// \sa getBestKnownName, https://github.com/matrix-org/matrix-doc/issues/1375
-QUrl getBestKnownAvatarUrl(const RoomMemberEvent* event)
-{
- const auto& jv = event->contentJson().value("avatar_url"_ls);
- return !jv.isUndefined()
- ? jv.toString()
- : event->prevContent() ? event->prevContent()->avatarUrl
- : QUrl();
-}
-
const Avatar& User::avatarObject(const Room* room) const
{
- if (!room) {
- if (!d->defaultAvatar) {
- d->fetchProfile(this);
- }
- return *d->defaultAvatar;
- }
+ if (!room)
+ return d->defaultAvatar;
- const auto& url =
- getBestKnownAvatarUrl(room->getCurrentState<RoomMemberEvent>(id()));
+ const auto& url = room->memberAvatarUrl(id());
const auto& mediaId = url.authority() + url.path();
return d->otherAvatars.try_emplace(mediaId, url).first->second;
}
-QImage User::avatar(int dimension, const Room* room)
+QImage User::avatar(int dimension, const Room* room) const
{
return avatar(dimension, dimension, room);
}
-QImage User::avatar(int width, int height, const Room* room)
+QImage User::avatar(int width, int height, const Room* room) const
{
return avatar(width, height, room, [] {});
}
QImage User::avatar(int width, int height, const Room* room,
- const Avatar::get_callback_t& callback)
+ const Avatar::get_callback_t& callback) const
{
- return avatarObject(room).get(connection(), width, height, [=] {
- emit avatarChanged(this, room);
- callback();
- });
+ return avatarObject(room).get(connection(), width, height, callback);
}
QString User::avatarMediaId(const Room* room) const
@@ -302,32 +222,4 @@ QUrl User::avatarUrl(const Room* room) const
return avatarObject(room).url();
}
-void User::processEvent(const RoomMemberEvent& event, const Room* room,
- bool firstMention)
-{
- Q_ASSERT(room);
-
- // This is prone to abuse if prevContent is forged; only here until 0.7
- // (and the whole method, actually).
- const auto& oldName = event.prevContent() ? event.prevContent()->displayName
- : QString();
- const auto& newName = getBestKnownName(&event);
- // A hacky way to find out if it's about to change or already changed;
- // making it a lambda allows to omit stub event creation when unneeded
- const auto& isAboutToChange = [&event, room, this] {
- return room->getCurrentState<RoomMemberEvent>(id()) != &event;
- };
- if (firstMention || newName != oldName) {
- if (isAboutToChange())
- emit nameAboutToChange(newName, oldName, room);
- else
- emit nameChanged(newName, oldName, room);
- }
- const auto& oldAvatarUrl =
- event.prevContent() ? event.prevContent()->avatarUrl : QUrl();
- const auto& newAvatarUrl = getBestKnownAvatarUrl(&event);
- if ((firstMention || newAvatarUrl != oldAvatarUrl) && !isAboutToChange())
- emit avatarChanged(this, room);
-}
-
qreal User::hueF() const { return d->hueF; }