diff options
Diffstat (limited to 'lib/user.cpp')
-rw-r--r-- | lib/user.cpp | 256 |
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; } |