aboutsummaryrefslogtreecommitdiff
path: root/lib/user.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'lib/user.cpp')
-rw-r--r--lib/user.cpp121
1 files changed, 73 insertions, 48 deletions
diff --git a/lib/user.cpp b/lib/user.cpp
index 43792e74..8adb1dae 100644
--- a/lib/user.cpp
+++ b/lib/user.cpp
@@ -20,18 +20,22 @@
#include "avatar.h"
#include "connection.h"
+#include "room.h"
+
#include "csapi/content-repo.h"
#include "csapi/profile.h"
#include "csapi/room_state.h"
+
#include "events/event.h"
#include "events/roommemberevent.h"
-#include "room.h"
+#include <QtCore/QCryptographicHash>
#include <QtCore/QElapsedTimer>
#include <QtCore/QPointer>
#include <QtCore/QRegularExpression>
#include <QtCore/QStringBuilder>
#include <QtCore/QTimer>
+#include <QtCore/QtEndian>
#include <functional>
@@ -41,23 +45,39 @@ using std::move;
class User::Private
{
- public:
+public:
static Avatar makeAvatar(QUrl url) { return Avatar(move(url)); }
- Private(QString userId, Connection* connection)
- : userId(move(userId)), connection(connection)
+ qreal makeHueF()
{
+ Q_ASSERT(!userId.isEmpty());
+ QByteArray hash = QCryptographicHash::hash(userId.toUtf8(),
+ QCryptographicHash::Sha1);
+ QDataStream dataStream(qToLittleEndian(hash).left(2));
+ dataStream.setByteOrder(QDataStream::LittleEndian);
+ quint16 hashValue;
+ dataStream >> hashValue;
+ const auto hueF = qreal(hashValue) / std::numeric_limits<quint16>::max();
+ Q_ASSERT((0 <= hueF) && (hueF <= 1));
+ return hueF;
}
+ Private(QString userId, Connection* connection)
+ : userId(move(userId))
+ , connection(connection)
+ , hueF(makeHueF())
+ {}
+
QString userId;
Connection* connection;
QString bridged;
QString mostUsedName;
QMultiHash<QString, const Room*> otherNames;
+ qreal hueF;
Avatar mostUsedAvatar { makeAvatar({}) };
std::vector<Avatar> otherAvatars;
- auto otherAvatar(QUrl url)
+ auto otherAvatar(const QUrl& url)
{
return std::find_if(otherAvatars.begin(), otherAvatars.end(),
[&url](const auto& av) { return av.url() == url; });
@@ -67,10 +87,9 @@ class User::Private
mutable int totalRooms = 0;
QString nameForRoom(const Room* r, const QString& hint = {}) const;
- void setNameForRoom(const Room* r, QString newName, QString oldName);
+ 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 setAvatarForRoom(const Room* r, const QUrl& newUrl, const QUrl& oldUrl);
void setAvatarOnServer(QString contentUri, User* q);
};
@@ -78,7 +97,7 @@ class User::Private
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 == mostUsedName || otherNames.contains(hint, r))
+ if (!hint.isNull() && (hint == mostUsedName || otherNames.contains(hint, r)))
return hint;
return otherNames.key(r, mostUsedName);
}
@@ -86,7 +105,7 @@ QString User::Private::nameForRoom(const Room* r, const QString& hint) const
static constexpr int MIN_JOINED_ROOMS_TO_LOG = 20;
void User::Private::setNameForRoom(const Room* r, QString newName,
- QString oldName)
+ const QString& oldName)
{
Q_ASSERT(oldName != newName);
Q_ASSERT(oldName == mostUsedName || otherNames.contains(oldName, r));
@@ -103,14 +122,14 @@ void User::Private::setNameForRoom(const Room* r, QString newName,
Q_ASSERT(totalRooms > 1);
QElapsedTimer et;
if (totalRooms > MIN_JOINED_ROOMS_TO_LOG) {
- qCDebug(MAIN)
- << "Switching the most used name of user" << userId
- << "from" << mostUsedName << "to" << newName;
+ qCDebug(MAIN) << "Switching the most used name of user" << userId
+ << "from" << mostUsedName << "to" << newName;
qCDebug(MAIN) << "The user is in" << totalRooms << "rooms";
et.start();
}
- for (auto* r1 : connection->roomMap())
+ const auto& roomMap = connection->roomMap();
+ for (auto* r1 : roomMap)
if (nameForRoom(r1) == mostUsedName)
otherNames.insert(mostUsedName, r1);
@@ -152,21 +171,21 @@ void User::Private::setAvatarForRoom(const Room* r, const QUrl& newUrl,
}
if (newUrl != mostUsedAvatar.url()) {
// Check if the new avatar is about to become most used.
- if (avatarsToRooms.count(newUrl)
- >= totalRooms - avatarsToRooms.size()) {
+ if (avatarsToRooms.count(newUrl) >= totalRooms - avatarsToRooms.size()) {
QElapsedTimer et;
if (totalRooms > MIN_JOINED_ROOMS_TO_LOG) {
qCDebug(MAIN)
- << "Switching the most used avatar of user" << userId
- << "from" << mostUsedAvatar.url().toDisplayString()
- << "to" << newUrl.toDisplayString();
+ << "Switching the most used avatar of user" << userId
+ << "from" << mostUsedAvatar.url().toDisplayString() << "to"
+ << newUrl.toDisplayString();
et.start();
}
avatarsToRooms.remove(newUrl);
auto nextMostUsedIt = otherAvatar(newUrl);
Q_ASSERT(nextMostUsedIt != otherAvatars.end());
std::swap(mostUsedAvatar, *nextMostUsedIt);
- for (const auto* r1 : connection->roomMap())
+ const auto& roomMap = connection->roomMap();
+ for (const auto* r1 : roomMap)
if (avatarUrlForRoom(r1) == nextMostUsedIt->url())
avatarsToRooms.insert(nextMostUsedIt->url(), r1);
@@ -181,7 +200,8 @@ void User::Private::setAvatarForRoom(const Room* r, const QUrl& newUrl,
}
User::User(QString userId, Connection* connection)
- : QObject(connection), d(new Private(move(userId), connection))
+ : QObject(connection)
+ , d(new Private(move(userId), connection))
{
setObjectName(userId);
}
@@ -205,6 +225,8 @@ bool User::isGuest() const
return *it == ':';
}
+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
@@ -245,8 +267,9 @@ void User::updateAvatarUrl(const QUrl& newUrl, const QUrl& oldUrl,
void User::rename(const QString& newName)
{
- auto job = connection()->callApi<SetDisplayNameJob>(id(), newName);
- connect(job, &BaseJob::success, this, [=] { updateName(newName); });
+ const auto actualNewName = sanitized(newName);
+ connect(connection()->callApi<SetDisplayNameJob>(id(), actualNewName),
+ &BaseJob::success, this, [=] { updateName(actualNewName); });
}
void User::rename(const QString& newName, const Room* r)
@@ -259,24 +282,25 @@ void User::rename(const QString& newName, const Room* r)
}
Q_ASSERT_X(r->memberJoinState(this) == JoinState::Join, __FUNCTION__,
"Attempt to rename a user that's not a room member");
+ const auto actualNewName = sanitized(newName);
MemberEventContent evtC;
- evtC.displayName = newName;
- auto job = r->setMemberState(id(), RoomMemberEvent(move(evtC)));
- connect(job, &BaseJob::success, this, [=] { updateName(newName, r); });
+ evtC.displayName = actualNewName;
+ connect(r->setMemberState(id(), RoomMemberEvent(move(evtC))),
+ &BaseJob::success, this, [=] { updateName(actualNewName, r); });
}
bool User::setAvatar(const QString& fileName)
{
- return avatarObject().upload(
- connection(), fileName,
- std::bind(&Private::setAvatarOnServer, d.data(), _1, this));
+ return avatarObject().upload(connection(), fileName,
+ std::bind(&Private::setAvatarOnServer,
+ d.data(), _1, this));
}
bool User::setAvatar(QIODevice* source)
{
- return avatarObject().upload(
- connection(), source,
- std::bind(&Private::setAvatarOnServer, d.data(), _1, this));
+ return avatarObject().upload(connection(), source,
+ std::bind(&Private::setAvatarOnServer,
+ d.data(), _1, this));
}
void User::requestDirectChat() { connection()->requestDirectChat(this); }
@@ -346,19 +370,18 @@ QUrl User::avatarUrl(const Room* room) const
return avatarObject(room).url();
}
-void User::processEvent(const RoomMemberEvent& event, const Room* room)
+void User::processEvent(const RoomMemberEvent& event, const Room* room,
+ bool firstMention)
{
Q_ASSERT(room);
+
+ if (firstMention)
+ ++d->totalRooms;
+
if (event.membership() != MembershipType::Invite
&& event.membership() != MembershipType::Join)
return;
- auto aboutToEnter = room->memberJoinState(this) == JoinState::Leave
- && (event.membership() == MembershipType::Join
- || event.membership() == MembershipType::Invite);
- if (aboutToEnter)
- ++d->totalRooms;
-
auto newName = event.displayName();
// `bridged` value uses the same notification signal as the name;
// it is assumed that first setting of the bridge occurs together with
@@ -366,29 +389,31 @@ void User::processEvent(const RoomMemberEvent& event, const Room* room)
// exceptionally rare (the only reasonable case being that the bridge
// changes the naming convention). For the same reason room-specific
// bridge tags are not supported at all.
- QRegularExpression reSuffix(" \\((IRC|Gitter|Telegram)\\)$");
+ QRegularExpression reSuffix(
+ QStringLiteral(" \\((IRC|Gitter|Telegram)\\)$"));
auto match = reSuffix.match(newName);
if (match.hasMatch()) {
if (d->bridged != match.captured(1)) {
if (!d->bridged.isEmpty())
qCWarning(MAIN)
- << "Bridge for user" << id() << "changed:" << d->bridged
- << "->" << match.captured(1);
+ << "Bridge for user" << id() << "changed:" << d->bridged
+ << "->" << match.captured(1);
d->bridged = match.captured(1);
}
newName.truncate(match.capturedStart(0));
}
if (event.prevContent()) {
// FIXME: the hint doesn't work for bridged users
- auto oldNameHint =
- d->nameForRoom(room, event.prevContent()->displayName);
+ auto oldNameHint = d->nameForRoom(room,
+ event.prevContent()->displayName);
updateName(newName, oldNameHint, room);
- updateAvatarUrl(
- event.avatarUrl(),
- d->avatarUrlForRoom(room, event.prevContent()->avatarUrl),
- room);
+ updateAvatarUrl(event.avatarUrl(),
+ d->avatarUrlForRoom(room, event.prevContent()->avatarUrl),
+ room);
} else {
updateName(newName, room);
updateAvatarUrl(event.avatarUrl(), d->avatarUrlForRoom(room), room);
}
}
+
+qreal User::hueF() const { return d->hueF; }