aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKitsune Ral <Kitsune-Ral@users.sf.net>2020-08-04 11:38:20 +0200
committerKitsune Ral <Kitsune-Ral@users.sf.net>2020-08-04 11:38:20 +0200
commit5849686e96b4a7277f2b2152ff3aa76cc6d0f18e (patch)
tree43dbfbe05e9825a03cd4dbd0ddaaa4240f1c7d5f
parent83476d31854f403b7017003a560c2d5545360487 (diff)
downloadlibquotient-5849686e96b4a7277f2b2152ff3aa76cc6d0f18e.tar.gz
libquotient-5849686e96b4a7277f2b2152ff3aa76cc6d0f18e.zip
User: optimise names/avatars storing and updating
The current mechanism relied on a complicated and fragile machinery around setNameForRoom() and setAvatarForRoom() that maintained the "most used" entity for a given user along with "other" ones. Given that per-room avatars are pretty rare in Matrix, it's also been inefficient as commit c69f100e shows. The new mechanism stores the "default" (as per user profile) name and avatar and maintains a singleton map of avatar objects across all users. Per-user profile only (normally) exists for the local user so there's yet another inefficiency - this will be fixed in 0.7 by introducing a special class for a user profile.
-rw-r--r--lib/room.cpp140
-rw-r--r--lib/user.cpp282
-rw-r--r--lib/user.h34
3 files changed, 176 insertions, 280 deletions
diff --git a/lib/room.cpp b/lib/room.cpp
index 970fe56b..1e3eb603 100644
--- a/lib/room.cpp
+++ b/lib/room.cpp
@@ -192,7 +192,6 @@ public:
// void inviteUser(User* u); // We might get it at some point in time.
void insertMemberIntoMap(User* u);
- void renameMember(User* u, const QString& oldName);
void removeMemberFromMap(const QString& username, User* u);
// This updates the room displayname field (which is the way a room
@@ -238,6 +237,15 @@ public:
return static_cast<const EventT*>(evt);
}
+// template <typename EventT>
+// const auto& getCurrentStateContent(const QString& stateKey = {}) const
+// {
+// if (const auto* evt =
+// currentState.value({ EventT::matrixTypeId(), stateKey }, nullptr))
+// return evt->content();
+// return EventT::content_type()
+// }
+
bool isEventNotable(const TimelineItem& ti) const
{
return !ti->isRedacted() && ti->senderId() != connection->userId()
@@ -1344,18 +1352,6 @@ void Room::Private::insertMemberIntoMap(User* u)
emit q->memberRenamed(namesakes.front());
}
-void Room::Private::renameMember(User* u, const QString& oldName)
-{
- if (u->name(q) == oldName) {
- qCWarning(MAIN) << "Room::Private::renameMember(): the user "
- << u->fullName(q)
- << "is already known in the room under a new name.";
- } else if (membersMap.contains(oldName, u)) {
- removeMemberFromMap(oldName, u);
- insertMemberIntoMap(u);
- }
-}
-
void Room::Private::removeMemberFromMap(const QString& username, User* u)
{
User* namesake = nullptr;
@@ -2396,14 +2392,69 @@ Room::Changes Room::processStateEvent(const RoomEvent& e)
if (!e.isStateEvent())
return Change::NoChange;
- const auto* oldStateEvent =
- std::exchange(d->currentState[{ e.matrixType(), e.stateKey() }],
- static_cast<const StateEventBase*>(&e));
+ // Find a value (create empty if necessary) and get a reference to it
+ // getCurrentState<> is not used here because it (creates and) returns
+ // a stub if a value is not found, and what's needed here is a "real" event
+ // or nullptr.
+ const auto*& curStateEvent =
+ d->currentState[{ e.matrixType(), e.stateKey() }];
+ // Prepare for the state change
+ visit(e, [this, oldRme = static_cast<const RoomMemberEvent*>(curStateEvent)](
+ const RoomMemberEvent& rme) {
+ auto* u = user(rme.userId());
+ if (!u) { // ???
+ qCCritical(MAIN)
+ << "Could not get a user object for" << rme.userId();
+ return;
+ }
+ // TODO: remove along with User::processEvent() in 0.7
+ const auto prevMembership = oldRme ? oldRme->membership()
+ : MembershipType::Leave;
+ u->processEvent(rme, this, oldRme == nullptr);
+
+ switch (prevMembership) {
+ case MembershipType::Invite:
+ if (rme.membership() != prevMembership) {
+ d->usersInvited.removeOne(u);
+ Q_ASSERT(!d->usersInvited.contains(u));
+ }
+ break;
+ case MembershipType::Join:
+ switch (rme.membership()) {
+ case MembershipType::Join: // rename/avatar change or no-op
+ if (rme.displayName() != oldRme->displayName()) {
+ emit memberAboutToRename(u, rme.displayName());
+ d->removeMemberFromMap(u->name(this), u);
+ }
+ break;
+ case MembershipType::Invite:
+ qCWarning(MAIN) << "Membership change from Join to Invite:"
+ << rme;
+ [[fallthrough]];
+ default: // whatever the new membership, it's no more Join
+ d->removeMemberFromMap(u->name(this), u);
+ emit userRemoved(u);
+ }
+ break;
+ default:
+ if (rme.membership() == MembershipType::Invite
+ || rme.membership() == MembershipType::Join) {
+ d->membersLeft.removeOne(u);
+ Q_ASSERT(!d->membersLeft.contains(u));
+ }
+ }
+ });
+
+ // Change the state
+ const auto* const oldStateEvent =
+ std::exchange(curStateEvent, static_cast<const StateEventBase*>(&e));
Q_ASSERT(!oldStateEvent
|| (oldStateEvent->matrixType() == e.matrixType()
&& oldStateEvent->stateKey() == e.stateKey()));
if (!is<RoomMemberEvent>(e)) // Room member events are too numerous
- qCDebug(STATE) << "Room state event:" << e;
+ qCDebug(STATE) << "Updated room state:" << e;
+
+ // Update internal structures as per the change and work out the return value
// clang-format off
return visit(e
@@ -2411,10 +2462,7 @@ Room::Changes Room::processStateEvent(const RoomEvent& e)
return NameChange;
}
, [] (const RoomAliasesEvent&) {
- // clang-format on
- // This event has been removed by MSC-2432
- return NoChange;
- // clang-format off
+ return NoChange; // This event has been removed by MSC2432
}
, [this, oldStateEvent] (const RoomCanonicalAliasEvent& cae) {
// clang-format on
@@ -2460,63 +2508,27 @@ Room::Changes Room::processStateEvent(const RoomEvent& e)
auto* u = user(evt.userId());
const auto* oldMemberEvent =
static_cast<const RoomMemberEvent*>(oldStateEvent);
+ // TODO: remove in 0.7
u->processEvent(evt, this, oldMemberEvent == nullptr);
+
const auto prevMembership = oldMemberEvent
? oldMemberEvent->membership()
: MembershipType::Leave;
- if (u == localUser() && evt.membership() == MembershipType::Invite
- && evt.isDirect())
- connection()->addToDirectChats(this, user(evt.senderId()));
-
- switch (prevMembership) {
- case MembershipType::Invite:
- if (evt.membership() != prevMembership) {
- d->usersInvited.removeOne(u);
- Q_ASSERT(!d->usersInvited.contains(u));
- }
- break;
- case MembershipType::Join:
- if (evt.membership() == MembershipType::Invite)
- qCWarning(MAIN) << "Invalid membership change from "
- "Join to Invite:"
- << evt;
- if (evt.membership() != prevMembership) {
- disconnect(u, &User::nameAboutToChange, this, nullptr);
- disconnect(u, &User::nameChanged, this, nullptr);
- d->removeMemberFromMap(u->name(this), u);
- emit userRemoved(u);
- }
- break;
- default:
- if (evt.membership() == MembershipType::Invite
- || evt.membership() == MembershipType::Join) {
- d->membersLeft.removeOne(u);
- Q_ASSERT(!d->membersLeft.contains(u));
- }
- }
-
switch (evt.membership()) {
case MembershipType::Join:
if (prevMembership != MembershipType::Join) {
d->insertMemberIntoMap(u);
- connect(u, &User::nameAboutToChange, this,
- [=](QString newName, QString, const Room* context) {
- if (context == this)
- emit memberAboutToRename(u, newName);
- });
- connect(u, &User::nameChanged, this,
- [=](QString, QString oldName, const Room* context) {
- if (context == this) {
- d->renameMember(u, oldName);
- emit memberRenamed(u);
- }
- });
emit userAdded(u);
+ } else if (oldMemberEvent->displayName() != evt.displayName()) {
+ d->insertMemberIntoMap(u);
+ emit memberRenamed(u);
}
break;
case MembershipType::Invite:
if (!d->usersInvited.contains(u))
d->usersInvited.push_back(u);
+ if (u == localUser() && evt.isDirect())
+ connection()->addToDirectChats(this, user(evt.senderId()));
break;
default:
if (!d->membersLeft.contains(u))
diff --git a/lib/user.cpp b/lib/user.cpp
index 09cf72a2..cc6cb102 100644
--- a/lib/user.cpp
+++ b/lib/user.cpp
@@ -38,150 +38,26 @@
#include <functional>
using namespace Quotient;
-using namespace std::placeholders;
using std::move;
class User::Private {
public:
- static Avatar makeAvatar(QUrl url) { return Avatar(move(url)); }
-
Private(QString userId) : id(move(userId)), hueF(stringToHueF(id)) { }
QString id;
-
- QString mostUsedName;
- QMultiHash<QString, const Room*> otherNames;
qreal hueF;
- Avatar mostUsedAvatar { makeAvatar({}) };
- std::vector<Avatar> otherAvatars;
- auto otherAvatar(const QUrl& url)
- {
- return std::find_if(otherAvatars.begin(), otherAvatars.end(),
- [&url](const auto& av) { return av.url() == url; });
- }
- QMultiHash<QUrl, const Room*> avatarsToRooms;
-
- mutable int totalRooms = 0;
- QString nameForRoom(const Room* r, const QString& hint = {}) const;
- void setNameForRoom(const Room* r, QString newName, const QString& oldName);
- QUrl avatarUrlForRoom(const Room* r, const QUrl& hint = {}) const;
- void setAvatarForRoom(const Room* r, const QUrl& newUrl, const QUrl& oldUrl);
-
- void setAvatarOnServer(QString contentUri, User* q);
+ QString defaultName;
+ 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 vacuumed either. This will probably change in the future.
+ /// Map of mediaId to Avatar objects
+ static UnorderedMap<QString, Avatar> otherAvatars;
};
-QString User::Private::nameForRoom(const Room* r, const QString& hint) const
-{
- // If the hint is accurate, this function is O(1) instead of O(n)
- if (!hint.isNull() && (hint == mostUsedName || otherNames.contains(hint, r)))
- return hint;
- return otherNames.key(r, mostUsedName);
-}
-
-static constexpr int MIN_JOINED_ROOMS_TO_LOG = 20;
-
-void User::Private::setNameForRoom(const Room* r, QString newName,
- const QString& oldName)
-{
- Q_ASSERT(oldName != newName);
- Q_ASSERT(oldName == mostUsedName || otherNames.contains(oldName, r));
- if (totalRooms < 2) {
- Q_ASSERT_X(totalRooms > 0 && otherNames.empty(), __FUNCTION__,
- "Internal structures inconsistency");
- mostUsedName = move(newName);
- return;
- }
- otherNames.remove(oldName, r);
- if (newName != mostUsedName) {
- // Check if the newName is about to become most used.
- if (otherNames.count(newName) >= totalRooms - otherNames.size()) {
- Q_ASSERT(totalRooms > 1);
- QElapsedTimer et;
- if (totalRooms > MIN_JOINED_ROOMS_TO_LOG) {
- qCDebug(MAIN) << "Switching the most used name of user" << id
- << "from" << mostUsedName << "to" << newName;
- qCDebug(MAIN) << "The user is in" << totalRooms << "rooms";
- et.start();
- }
-
- for (auto* r1: r->connection()->allRooms())
- if (nameForRoom(r1) == mostUsedName)
- otherNames.insert(mostUsedName, r1);
-
- mostUsedName = newName;
- otherNames.remove(newName);
- if (totalRooms > MIN_JOINED_ROOMS_TO_LOG)
- qCDebug(PROFILER) << et << "to switch the most used name";
- } else
- otherNames.insert(newName, r);
- }
-}
-
-QUrl User::Private::avatarUrlForRoom(const Room* r, const QUrl& hint) const
-{
- // If the hint is accurate, this function is O(1) instead of O(n)
- if (hint == mostUsedAvatar.url() || avatarsToRooms.contains(hint, r))
- return hint;
- auto it = std::find(avatarsToRooms.begin(), avatarsToRooms.end(), r);
- return it == avatarsToRooms.end() ? mostUsedAvatar.url() : it.key();
-}
-
-void User::Private::setAvatarForRoom(const Room* r, const QUrl& newUrl,
- const QUrl& oldUrl)
-{
- Q_ASSERT(oldUrl != newUrl);
- Q_ASSERT(oldUrl == mostUsedAvatar.url()
- || avatarsToRooms.contains(oldUrl, r));
- if (totalRooms < 2) {
- Q_ASSERT_X(totalRooms > 0 && otherAvatars.empty(), __FUNCTION__,
- "Internal structures inconsistency");
- mostUsedAvatar.updateUrl(newUrl);
- return;
- }
- avatarsToRooms.remove(oldUrl, r);
- if (!avatarsToRooms.contains(oldUrl)) {
- auto it = otherAvatar(oldUrl);
- if (it != otherAvatars.end())
- otherAvatars.erase(it);
- }
- if (newUrl != mostUsedAvatar.url()) {
- // Check if the new avatar is about to become most used.
- const auto newUrlUsage = avatarsToRooms.count(newUrl);
- if (newUrlUsage >= totalRooms - avatarsToRooms.size()) {
- QElapsedTimer et;
- if (totalRooms > MIN_JOINED_ROOMS_TO_LOG) {
- qCInfo(MAIN)
- << "Switching the most used avatar of user" << id
- << "from" << mostUsedAvatar.url().toDisplayString() << "to"
- << newUrl.toDisplayString();
- et.start();
- }
- avatarsToRooms.remove(newUrl);
- auto nextMostUsedIt = otherAvatar(newUrl);
- if (nextMostUsedIt == otherAvatars.end()) {
- qCCritical(MAIN)
- << id << "doesn't have" << newUrl.toDisplayString()
- << "in otherAvatars though it seems to be used in"
- << newUrlUsage << "rooms";
- Q_ASSERT(false);
- otherAvatars.emplace_back(makeAvatar(newUrl));
- nextMostUsedIt = otherAvatars.end() - 1;
- }
- std::swap(mostUsedAvatar, *nextMostUsedIt);
- for (const auto* r1: r->connection()->allRooms())
- if (avatarUrlForRoom(r1) == nextMostUsedIt->url())
- avatarsToRooms.insert(nextMostUsedIt->url(), r1);
-
- if (totalRooms > MIN_JOINED_ROOMS_TO_LOG)
- qCDebug(PROFILER) << et << "to switch the most used avatar";
- } else {
- if (otherAvatar(newUrl) == otherAvatars.end())
- otherAvatars.emplace_back(makeAvatar(newUrl));
- avatarsToRooms.insert(newUrl, r);
- }
- }
-}
+decltype(User::Private::otherAvatars) User::Private::otherAvatars {};
User::User(QString userId, Connection* connection)
: QObject(connection), d(new Private(move(userId)))
@@ -210,43 +86,34 @@ bool User::isGuest() const
int User::hue() const { return int(hueF() * 359); }
-QString User::name(const Room* room) const { return d->nameForRoom(room); }
-
-QString User::rawName(const Room* room) const { return name(room); }
-
-void User::updateName(const QString& newName, const Room* room)
+QString User::name(const Room* room) const
{
- updateName(newName, d->nameForRoom(room), room);
+ return room ? room->getCurrentState<RoomMemberEvent>(id())->displayName()
+ : d->defaultName;
}
-void User::updateName(const QString& newName, const QString& oldName,
- const Room* room)
-{
- Q_ASSERT(oldName == d->mostUsedName
- || d->otherNames.contains(oldName, room));
- if (newName != oldName) {
- emit nameAboutToChange(newName, oldName, room);
- d->setNameForRoom(room, newName, oldName);
- emit nameChanged(newName, oldName, room);
- }
-}
+QString User::rawName(const Room* room) const { return name(room); }
-void User::updateAvatarUrl(const QUrl& newUrl, const QUrl& oldUrl,
- const Room* room)
-{
- Q_ASSERT(oldUrl == d->mostUsedAvatar.url()
- || d->avatarsToRooms.contains(oldUrl, room));
- if (newUrl != oldUrl) {
- d->setAvatarForRoom(room, newUrl, oldUrl);
- emit avatarChanged(this, room);
- }
-}
+void User::updateName(const QString&, const Room*) {}
+void User::updateName(const QString&, const QString&, const Room*) {}
+void User::updateAvatarUrl(const QUrl&, const QUrl&, const Room*) {}
void User::rename(const QString& newName)
{
const auto actualNewName = sanitized(newName);
+ if (actualNewName == d->defaultName)
+ return; // Nothing to do
+
connect(connection()->callApi<SetDisplayNameJob>(id(), actualNewName),
- &BaseJob::success, this, [=] { updateName(actualNewName); });
+ &BaseJob::success, this, [this,actualNewName] {
+ if (actualNewName == d->defaultName)
+ return; // Check again, it could have changed meanwhile
+
+ emit nameAboutToChange(actualNewName, d->defaultName, nullptr);
+ const auto& oldName =
+ std::exchange(d->defaultName, actualNewName);
+ emit nameChanged(d->defaultName, oldName, nullptr);
+ });
}
void User::rename(const QString& newName, const Room* r)
@@ -262,22 +129,39 @@ void User::rename(const QString& newName, const Room* r)
const auto actualNewName = sanitized(newName);
MemberEventContent evtC;
evtC.displayName = actualNewName;
- connect(r->setState<RoomMemberEvent>(id(), move(evtC)), &BaseJob::success,
- this, [=] { updateName(actualNewName, r); });
+ r->setState<RoomMemberEvent>(id(), move(evtC));
+ // The state will be updated locally after it arrives with sync
+}
+
+template <typename SourceT>
+inline bool User::doSetAvatar(SourceT&& source)
+{
+ return d->defaultAvatar.upload(
+ connection(), source, [this](const QString& contentUri) {
+ auto* j = connection()->callApi<SetAvatarUrlJob>(id(), contentUri);
+ connect(j, &BaseJob::success, this,
+ [this, newUrl = QUrl(contentUri)] {
+ if (newUrl == d->defaultAvatar.url()) {
+ qCWarning(MAIN) << "User" << id()
+ << "already has avatar URL set to"
+ << newUrl.toDisplayString();
+ return;
+ }
+
+ d->defaultAvatar.updateUrl(move(newUrl));
+ emit avatarChanged(this, nullptr);
+ });
+ });
}
bool User::setAvatar(const QString& fileName)
{
- return avatarObject().upload(connection(), fileName,
- std::bind(&Private::setAvatarOnServer,
- d.data(), _1, this));
+ return doSetAvatar(fileName);
}
bool User::setAvatar(QIODevice* source)
{
- return avatarObject().upload(connection(), source,
- std::bind(&Private::setAvatarOnServer,
- d.data(), _1, this));
+ return doSetAvatar(source);
}
void User::requestDirectChat() { connection()->requestDirectChat(this); }
@@ -288,34 +172,29 @@ void User::unmarkIgnore() { connection()->removeFromIgnoredUsers(this); }
bool User::isIgnored() const { return connection()->isIgnored(this); }
-void User::Private::setAvatarOnServer(QString contentUri, User* q)
-{
- auto* j = q->connection()->callApi<SetAvatarUrlJob>(id, contentUri);
- connect(j, &BaseJob::success, q,
- [=] { q->updateAvatarUrl(contentUri, avatarUrlForRoom(nullptr)); });
-}
-
QString User::displayname(const Room* room) const
{
- if (room)
- return room->roomMembername(this);
-
- const auto name = d->nameForRoom(nullptr);
- return name.isEmpty() ? d->id : name;
+ return room ? room->roomMembername(this)
+ : d->defaultName.isEmpty() ? d->id
+ : d->defaultName;
}
QString User::fullName(const Room* room) const
{
- const auto name = d->nameForRoom(room);
- return name.isEmpty() ? id() : name % " (" % id() % ')';
+ const auto displayName = name(room);
+ return displayName.isEmpty() ? id() : (displayName % " (" % id() % ')');
}
QString User::bridged() const { return {}; }
const Avatar& User::avatarObject(const Room* room) const
{
- auto it = d->otherAvatar(d->avatarUrlForRoom(room));
- return it != d->otherAvatars.end() ? *it : d->mostUsedAvatar;
+ if (!room)
+ return d->defaultAvatar;
+
+ const auto& url = room->getCurrentState<RoomMemberEvent>(id())->avatarUrl();
+ const auto& mediaId = url.authority() + url.path();
+ return d->otherAvatars.try_emplace(mediaId, url).first->second;
}
QImage User::avatar(int dimension, const Room* room)
@@ -352,25 +231,22 @@ void User::processEvent(const RoomMemberEvent& event, const Room* room,
{
Q_ASSERT(room);
- if (firstMention)
- ++d->totalRooms;
-
- if (event.membership() != MembershipType::Invite
- && event.membership() != MembershipType::Join)
- return;
-
- if (event.prevContent()) {
- // FIXME: the hint doesn't work for bridged users
- auto oldNameHint = d->nameForRoom(room,
- event.prevContent()->displayName);
- updateName(event.displayName(), oldNameHint, room);
- updateAvatarUrl(event.avatarUrl(),
- d->avatarUrlForRoom(room, event.prevContent()->avatarUrl),
- room);
- } else {
- updateName(event.displayName(), room);
- updateAvatarUrl(event.avatarUrl(), d->avatarUrlForRoom(room), room);
+ const auto& oldName = firstMention || !event.prevContent() ? QString()
+ : event.prevContent()->displayName;
+ const auto& newName = event.displayName();
+ // A hacky way to find out if it's about to change or already changed
+ bool isAboutToChange = room->getCurrentState<RoomMemberEvent>(id()) != &event;
+ if (newName != oldName) {
+ if (isAboutToChange)
+ emit nameAboutToChange(newName, oldName, room);
+ else {
+ emit nameChanged(newName, oldName, room);
+ }
}
+ const auto& oldAvatarUrl = firstMention || !event.prevContent() ? QUrl()
+ : event.prevContent()->avatarUrl;
+ if (event.avatarUrl() != oldAvatarUrl && !isAboutToChange)
+ emit avatarChanged(this, room);
}
qreal User::hueF() const { return d->hueF; }
diff --git a/lib/user.h b/lib/user.h
index e4328f1d..ceea7c48 100644
--- a/lib/user.h
+++ b/lib/user.h
@@ -123,20 +123,21 @@ public:
QString avatarMediaId(const Room* room = nullptr) const;
QUrl avatarUrl(const Room* room = nullptr) const;
- /// This method is for internal use and should not be called
- /// from client code
- // FIXME: Move it away to private
+ // TODO: This method is only there to emit obsolete signals:
+ // nameAboutToChange(), nameChanged() and avatarChanged() - all of these
+ // to be removed in 0.7
+ /// \deprecated
void processEvent(const RoomMemberEvent& event, const Room* r,
bool firstMention);
public slots:
- /** Set a new name in the global user profile */
+ /// Set a new name in the global user profile
void rename(const QString& newName);
- /** Set a new name for the user in one room */
+ /// Set a new name for the user in one room
void rename(const QString& newName, const Room* r);
- /** Upload the file and use it as an avatar */
+ /// Upload the file and use it as an avatar
bool setAvatar(const QString& fileName);
- /** Upload contents of the QIODevice and set that as an avatar */
+ /// Upload contents of the QIODevice and set that as an avatar
bool setAvatar(QIODevice* source);
/// Create or find a direct chat with this user
/*! The resulting chat is returned asynchronously via
@@ -151,21 +152,28 @@ public slots:
bool isIgnored() const;
signals:
+ /// \deprecated Use Room::memberListChanged() for member changes
void nameAboutToChange(QString newName, QString oldName,
const Quotient::Room* roomContext);
+ /// \deprecated Use Room::memberListChanged() for member changes
void nameChanged(QString newName, QString oldName,
const Quotient::Room* roomContext);
+ /// \deprecated Use Room::memberListChanged() for member changes
void avatarChanged(Quotient::User* user, const Quotient::Room* roomContext);
-private slots:
- void updateName(const QString& newName, const Room* room = nullptr);
- void updateName(const QString& newName, const QString& oldName,
- const Room* room = nullptr);
- void updateAvatarUrl(const QUrl& newUrl, const QUrl& oldUrl,
- const Room* room = nullptr);
+private slots: // TODO: remove in 0.7
+ /// \deprecated
+ void updateName(const QString&, const Room* = nullptr);
+ /// \deprecated
+ void updateName(const QString&, const QString&, const Room* = nullptr);
+ /// \deprecated
+ void updateAvatarUrl(const QUrl&, const QUrl&, const Room* = nullptr);
private:
class Private;
QScopedPointer<Private> d;
+
+ template <typename SourceT>
+ bool doSetAvatar(SourceT&& source);
};
} // namespace Quotient