/****************************************************************************** * Copyright (C) 2015 Felix Rohrbach * * 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 */ #include "user.h" #include "connection.h" #include "room.h" #include "avatar.h" #include "events/event.h" #include "events/roommemberevent.h" #include "csapi/room_state.h" #include "csapi/profile.h" #include "csapi/content-repo.h" #include #include #include #include #include #include using namespace QMatrixClient; using namespace std::placeholders; using std::move; class User::Private { public: static Avatar makeAvatar(QUrl url) { return Avatar(move(url)); } Private(QString userId, Connection* connection) : userId(move(userId)), connection(connection) { } QString userId; Connection* connection; QString bridged; QString mostUsedName; QMultiHash otherNames; Avatar mostUsedAvatar { makeAvatar({}) }; std::vector otherAvatars; auto otherAvatar(QUrl url) { return std::find_if(otherAvatars.begin(), otherAvatars.end(), [&url] (const auto& av) { return av.url() == url; }); } QMultiHash avatarsToRooms; mutable int totalRooms = 0; QString nameForRoom(const Room* r, const QString& hint = {}) const; void setNameForRoom(const Room* r, QString newName, 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 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)) 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, 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" << userId << "from" << mostUsedName << "to" << newName; qCDebug(MAIN) << "The user is in" << totalRooms << "rooms"; et.start(); } for (auto* r1: connection->roomMap()) 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 t
{{>preamble}}
#pragma once

{{#operations}}#include "jobs/basejob.h"
{{/operations}}{{#models}}#include "converters.h"
{{/models}}
{{#imports}}#include {{_}}
{{/imports}}
namespace QMatrixClient
{
{{#models}}    // Data structures
{{#    model}}{{#description}}
    /// {{_}}{{/description}}
    struct {{name}}{{#parents?}} : {{#parents}}{{name}}{{>cjoin}}{{/parents}}{{/parents?}}
    {
{{#vars}}{{#description}}        /// {{_}}
{{/description}}        {{>maybeOmittableType}} {{nameCamelCase}};
{{/vars}}{{#propertyMap}}{{#description}}        /// {{_}}
{{/description}}        {{>maybeOmittableType}} {{nameCamelCase}};
{{/propertyMap}}    };
    template <> struct JsonObjectConverter<{{name}}>
    {
        {{#in?}}static void dumpTo(QJsonObject& jo, const {{name}}& pod);
        {{/in?}}{{#out?}}static void fillFrom({{^propertyMap}}const QJsonObject&{{/propertyMap
                           }}{{#propertyMap}}QJsonObject{{/propertyMap}} jo, {{name}}& pod);
{{/out?}}    };
{{/model}}
{{/models}}{{#operations}}    // Operations
{{#    operation}}{{#summary}}
    /// {{summary}}{{#description?}}{{!add a linebreak between summary and description if both exist}}
    ///{{/description?}}{{/summary}}{{#description}}
    /// {{_}}{{/description}}
    class {{camelCaseOperationId}}Job : public BaseJob
    {
        public:{{#models}}
            // Inner data structures
{{#            model}}{{#description}}
            /// {{_}}{{/description}}
            struct {{name}}{{#parents?}} : {{#parents}}{{name}}{{>cjoin}}{{/parents}}{{/parents?}}
            {
{{#vars}}{{#description}}                /// {{_}}
{{/description}}                {{>maybeOmittableType}} {{nameCamelCase}};
{{/vars}}{{#propertyMap}}{{#description}}                /// {{_}}
{{/description}}                {{>maybeOmittableType}} {{nameCamelCase}};
{{/propertyMap}}            };
{{/            model}}
            // Construction/destruction
{{/        models}}{{#allParams?}}
            /*! {{summary}}{{#allParams}}
             * \param {{nameCamelCase}}{{#description}}
             *   {{_}}{{/description}}{{/allParams}}
             */{{/allParams?}}
            explicit {{camelCaseOperationId}}Job({{#allParams}}{{>joinedParamDecl}}{{/allParams}});{{^bodyParams}}

            /*! Construct a URL without creating a full-fledged job object
             *
             * This function can be used when a URL for
             * {{camelCaseOperationId}}Job is necessary but the job
             * itself isn't.
             */
            static QUrl makeRequestUrl(QUrl baseUrl{{#allParams?}}, {{#allParams}}{{>joinedParamDecl}}{{/allParams}}{{/allParams?}});
{{/bodyParams}}{{#        responses}}{{#normalResponse?}}{{#allProperties?}}
            ~{{camelCaseOperationId}}Job() override;

            // Result properties
{{#allProperties}}{{#description}}
            /// {{_}}{{/description}}
            {{>maybeCrefType}} {{paramName}}(){{^moveOnly}} const{{/moveOnly}};{{/allProperties}}

        protected:
            Status {{#producesNonJson?}}parseReply(QNetworkReply* reply){{/producesNonJson?}}{{^producesNonJson?}}parseJson(const QJsonDocument& data){{/producesNonJson?}} override;

        private:
            class Private;
            QScopedPointer<Private> d;{{/allProperties?}}{{/normalResponse?}}{{/responses}}
    };
{{/operation}}{{/operations}}{{!skip EOL
}}} // namespace QMatrixClient