From 6a6857b9d4dbf22402f2871494bdd06cdccdf366 Mon Sep 17 00:00:00 2001
From: Kitsune Ral <Kitsune-Ral@users.sf.net>
Date: Wed, 3 Jul 2019 23:28:09 +0900
Subject: Room/Connection: make room aliases work properly

Closes #301.
---
 lib/connection.cpp | 49 ++++++++++++++++++++++++++++++++-----------------
 lib/connection.h   | 10 ++++++----
 lib/room.cpp       | 39 +++++++++++++++++++++++++++++++++++----
 lib/room.h         | 14 ++++++++++----
 4 files changed, 83 insertions(+), 29 deletions(-)

diff --git a/lib/connection.cpp b/lib/connection.cpp
index 4c068b8f..783e12c0 100644
--- a/lib/connection.cpp
+++ b/lib/connection.cpp
@@ -82,8 +82,9 @@ class Connection::Private
         // separately; specifically, we should keep objects for Invite and
         // Leave state of the same room if the two happen to co-exist.
         QHash<QPair<QString, bool>, Room*> roomMap;
-        // Mapping from aliases to room ids, as per the last sync
-        QHash<QString, QString> roomAliasMap;
+        /// Mapping from serverparts to alias/room id mappings,
+        /// as of the last sync
+        QHash<QString, QHash<QString, QString>> roomAliasMap;
         QVector<QString> roomIdsToForget;
         QVector<Room*> firstTimeRooms;
         QVector<QString> pendingStateRoomIds;
@@ -158,20 +159,31 @@ Connection::~Connection()
     stopSync();
 }
 
-void Connection::resolveServer(const QString& mxidOrDomain)
+static const auto ServerPartRegEx = QStringLiteral(
+    "(\\[[^]]+\\]|[^:@]+)" // Either IPv6 address or hostname/IPv4 address
+    "(?::(\\d{1,5}))?" // Optional port
+);
+
+QString serverPart(const QString& mxId)
 {
-    // At this point we may have something as complex as
-    // @username:[IPv6:address]:port, or as simple as a plain domain name.
+    static auto re = "^[@!#$+].+?:(" // Localpart and colon
+                     % ServerPartRegEx % ")$";
+    static QRegularExpression parser(re,
+        QRegularExpression::UseUnicodePropertiesOption); // Because Asian digits
+    return parser.match(mxId).captured(1);
+}
 
-    // Try to parse as an FQID; if there's no @ part, assume it's a domain name.
-    QRegularExpression parser(
+void Connection::resolveServer(const QString& mxidOrDomain)
+{
+    // mxIdOrDomain may be something as complex as
+    // @username:[IPv6:address]:port, or as simple as a plain serverpart.
+    static QRegularExpression parser(
         "^(@.+?:)?" // Optional username (allow everything for compatibility)
-        "(\\[[^]]+\\]|[^:@]+)" // Either IPv6 address or hostname/IPv4 address
-        "(:\\d{1,5})?$", // Optional port
-        QRegularExpression::UseUnicodePropertiesOption); // Because asian digits
+        % ServerPartRegEx % '$',
+        QRegularExpression::UseUnicodePropertiesOption); // Because Asian digits
     auto match = parser.match(mxidOrDomain);
 
-    QUrl maybeBaseUrl = QUrl::fromUserInput(match.captured(2));
+    auto maybeBaseUrl = QUrl::fromUserInput(match.captured(2));
     maybeBaseUrl.setScheme("https"); // Instead of the Qt-default "http"
     if (!match.hasMatch() || !maybeBaseUrl.isValid())
     {
@@ -883,33 +895,36 @@ Room* Connection::room(const QString& roomId, JoinStates states) const
 
 Room* Connection::roomByAlias(const QString& roomAlias, JoinStates states) const
 {
-    const auto id = d->roomAliasMap.value(roomAlias);
+    const auto id =
+        d->roomAliasMap.value(serverPart(roomAlias)).value(roomAlias);
     if (!id.isEmpty())
         return room(id, states);
+
     qCWarning(MAIN) << "Room for alias" << roomAlias
                     << "is not found under account" << userId();
     return nullptr;
 }
 
 void Connection::updateRoomAliases(const QString& roomId,
+                                   const QString& aliasServer,
                                    const QStringList& previousRoomAliases,
                                    const QStringList& roomAliases)
 {
+    auto& aliasMap = d->roomAliasMap[aliasServer]; // Allocate if necessary
     for (const auto& a: previousRoomAliases)
-        if (d->roomAliasMap.remove(a) == 0)
+        if (aliasMap.remove(a) == 0)
             qCWarning(MAIN) << "Alias" << a << "is not found (already deleted?)";
 
     for (const auto& a: roomAliases)
     {
-        auto& mappedId = d->roomAliasMap[a];
+        auto& mappedId = aliasMap[a];
         if (!mappedId.isEmpty())
         {
             if (mappedId == roomId)
-                qCDebug(MAIN) << "Alias" << a << "is already mapped to room"
+                qCDebug(MAIN) << "Alias" << a << "is already mapped to"
                               << roomId;
             else
-                qCWarning(MAIN) << "Alias" << a
-                                << "will be force-remapped from room"
+                qCWarning(MAIN) << "Alias" << a << "will be force-remapped from"
                                 << mappedId << "to" << roomId;
         }
         mappedId = roomId;
diff --git a/lib/connection.h b/lib/connection.h
index cc2feed8..f688c10b 100644
--- a/lib/connection.h
+++ b/lib/connection.h
@@ -250,11 +250,13 @@ namespace QMatrixClient
             Q_INVOKABLE Room* roomByAlias(const QString& roomAlias,
                 JoinStates states = JoinState::Invite|JoinState::Join) const;
             /** Update the internal map of room aliases to IDs */
-            /// This is used for internal bookkeeping of rooms. Do NOT use
-            /// it to try change aliases, use Room::setAliases instead
+            /// This is used to maintain the internal index of room aliases.
+            /// It does NOT change aliases on the server,
+            /// \sa Room::setLocalAliases
             void updateRoomAliases(const QString& roomId,
-                const QStringList& previousRoomAliases,
-                const QStringList& roomAliases);
+                                   const QString& aliasServer,
+                                   const QStringList& previousRoomAliases,
+                                   const QStringList& roomAliases);
             Q_INVOKABLE Room* invitation(const QString& roomId) const;
             Q_INVOKABLE User* user(const QString& userId);
             const User* user() const;
diff --git a/lib/room.cpp b/lib/room.cpp
index 9042130a..06f3490c 100644
--- a/lib/room.cpp
+++ b/lib/room.cpp
@@ -96,9 +96,14 @@ class Room::Private
         /// The state of the room at timeline position after-maxTimelineIndex()
         /// \sa Room::syncEdge
         QHash<StateEventKey, const StateEventBase*> currentState;
+        /// Servers with aliases for this room except the one of the local user
+        /// \sa Room::remoteAliases
+        QSet<QString> aliasServers;
+
         Timeline timeline;
         PendingEvents unsyncedEvents;
         QHash<QString, TimelineItem::index_t> eventsIndex;
+
         QString displayname;
         Avatar avatar;
         int highlightCount = 0;
@@ -381,9 +386,18 @@ QString Room::name() const
     return d->getCurrentState<RoomNameEvent>()->name();
 }
 
-QStringList Room::aliases() const
+QStringList Room::localAliases() const
 {
-    return d->getCurrentState<RoomAliasesEvent>()->aliases();
+    return d->getCurrentState<RoomAliasesEvent>(
+                connection()->homeserver().authority())->aliases();
+}
+
+QStringList Room::remoteAliases() const
+{
+    QStringList result;
+    for (const auto& s: d->aliasServers)
+        result += d->getCurrentState<RoomAliasesEvent>(s)->aliases();
+    return result;
 }
 
 QString Room::canonicalAlias() const
@@ -1624,7 +1638,7 @@ void Room::setCanonicalAlias(const QString& newAlias)
     d->requestSetState(RoomCanonicalAliasEvent(newAlias));
 }
 
-void Room::setAliases(const QStringList& aliases)
+void Room::setLocalAliases(const QStringList& aliases)
 {
     d->requestSetState(RoomAliasesEvent(aliases));
 }
@@ -2192,16 +2206,30 @@ Room::Changes Room::processStateEvent(const RoomEvent& e)
     if (!is<RoomMemberEvent>(e)) // Room member events are too numerous
         qCDebug(EVENTS) << "Room state event:" << e;
 
+    // clang-format off
     return visit(e
         , [] (const RoomNameEvent&) {
             return NameChange;
         }
         , [this,oldStateEvent] (const RoomAliasesEvent& ae) {
+            // clang-format on
+            if (ae.aliases().isEmpty()) {
+                qDebug(MAIN).noquote() << ae.stateKey()
+                    << "no more has aliases for room" << objectName();
+                d->aliasServers.remove(ae.stateKey());
+            } else {
+                d->aliasServers.insert(ae.stateKey());
+                qDebug(MAIN).nospace().noquote()
+                    << "New server with aliases for room " << objectName()
+                    << ": " << ae.stateKey();
+            }
             const auto previousAliases = oldStateEvent
                 ? static_cast<const RoomAliasesEvent*>(oldStateEvent)->aliases()
                 : QStringList();
-            connection()->updateRoomAliases(id(), previousAliases, ae.aliases());
+            connection()->updateRoomAliases(id(), ae.stateKey(),
+                                            previousAliases, ae.aliases());
             return OtherChange;
+            // clang-format off
         }
         , [this] (const RoomCanonicalAliasEvent& evt) {
             setObjectName(evt.alias().isEmpty() ? d->id : evt.alias());
@@ -2216,6 +2244,7 @@ Room::Changes Room::processStateEvent(const RoomEvent& e)
             return AvatarChange;
         }
         , [this,oldStateEvent] (const RoomMemberEvent& evt) {
+            // clang-format on
             auto* u = user(evt.userId());
             const auto* oldMemberEvent =
                     static_cast<const RoomMemberEvent*>(oldStateEvent);
@@ -2288,6 +2317,7 @@ Room::Changes Room::processStateEvent(const RoomEvent& e)
                     d->membersLeft.append(u);
             }
             return MembersChange;
+            // clang-format off
         }
         , [this] (const EncryptionEvent&) {
             emit encryption(); // It can only be done once, so emit it here.
@@ -2310,6 +2340,7 @@ Room::Changes Room::processStateEvent(const RoomEvent& e)
             return OtherChange;
         }
     );
+    // clang-format on
 }
 
 Room::Changes Room::processEphemeralEvent(EventPtr&& event)
diff --git a/lib/room.h b/lib/room.h
index d4a1b959..7c85e4ed 100644
--- a/lib/room.h
+++ b/lib/room.h
@@ -86,7 +86,8 @@ namespace QMatrixClient
             Q_PROPERTY(QString predecessorId READ predecessorId NOTIFY baseStateLoaded)
             Q_PROPERTY(QString successorId READ successorId NOTIFY upgraded)
             Q_PROPERTY(QString name READ name NOTIFY namesChanged)
-            Q_PROPERTY(QStringList aliases READ aliases NOTIFY namesChanged)
+            Q_PROPERTY(QStringList localAliases READ localAliases NOTIFY namesChanged)
+            Q_PROPERTY(QStringList remoteAliases READ remoteAliases NOTIFY namesChanged)
             Q_PROPERTY(QString canonicalAlias READ canonicalAlias NOTIFY namesChanged)
             Q_PROPERTY(QString displayName READ displayName NOTIFY displaynameChanged)
             Q_PROPERTY(QString topic READ topic NOTIFY topicChanged)
@@ -156,7 +157,12 @@ namespace QMatrixClient
             QString predecessorId() const;
             QString successorId() const;
             QString name() const;
-            QStringList aliases() const;
+            /// Room aliases defined on the current user's server
+            /// \sa remoteAliases, setLocalAliases
+            QStringList localAliases() const;
+            /// Room aliases defined on other servers
+            /// \sa localAliases
+            QStringList remoteAliases() const;
             QString canonicalAlias() const;
             QString displayName() const;
             QString topic() const;
@@ -436,7 +442,8 @@ namespace QMatrixClient
             void discardMessage(const QString& txnId);
             void setName(const QString& newName);
             void setCanonicalAlias(const QString& newAlias);
-            void setAliases(const QStringList& aliases);
+            /// Set room aliases on the user's current server
+            void setLocalAliases(const QStringList& aliases);
             void setTopic(const QString& newTopic);
 
             /// You shouldn't normally call this method; it's here for debugging
@@ -590,7 +597,6 @@ namespace QMatrixClient
             void beforeDestruction(Room*);
 
         protected:
-            /// Returns true if any of room names/aliases has changed
             virtual Changes processStateEvent(const RoomEvent& e);
             virtual Changes processEphemeralEvent(EventPtr&& event);
             virtual Changes processAccountDataEvent(EventPtr&& event);
-- 
cgit v1.2.3