summary refs log tree commit diff
diff options
context:
space:
mode:
authorNicolas Werner <nicolas.werner@hotmail.de>2023-03-14 03:02:54 +0100
committerNicolas Werner <nicolas.werner@hotmail.de>2023-03-14 18:16:08 +0100
commit234ac79acc5d4ea1566b1b587c866e30f8cdfe83 (patch)
treecbfaac61845a32d352c298796bafac3152651b85
parentSpeed up writes of events with statekeys to state db (diff)
downloadnheko-234ac79acc5d4ea1566b1b587c866e30f8cdfe83.tar.xz
Update spaces events automatically in the background
-rw-r--r--src/ChatPage.cpp13
-rw-r--r--src/ChatPage.h4
-rw-r--r--src/PowerlevelsEditModels.cpp9
-rw-r--r--src/UserSettingsPage.cpp35
-rw-r--r--src/UserSettingsPage.h7
-rw-r--r--src/Utils.cpp180
-rw-r--r--src/Utils.h3
7 files changed, 246 insertions, 5 deletions
diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp
index 37449980..b5c8d3b4 100644
--- a/src/ChatPage.cpp
+++ b/src/ChatPage.cpp
@@ -95,6 +95,12 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QObject *parent)
                   return;
               }
 
+              // only update spaces every 20 minutes
+              if (lastSpacesUpdate < QDateTime::currentDateTime().addSecs(-20 * 60)) {
+                  lastSpacesUpdate = QDateTime::currentDateTime();
+                  utils::updateSpaceVias();
+              }
+
               if (!isConnected_)
                   emit connectionRestored();
           });
@@ -380,6 +386,13 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QObject *parent)
       },
       Qt::QueuedConnection);
 
+    connect(
+      this,
+      &ChatPage::callFunctionOnGuiThread,
+      this,
+      [](std::function<void()> f) { f(); },
+      Qt::QueuedConnection);
+
     connectCallMessage<mtx::events::voip::CallInvite>();
     connectCallMessage<mtx::events::voip::CallCandidates>();
     connectCallMessage<mtx::events::voip::CallAnswer>();
diff --git a/src/ChatPage.h b/src/ChatPage.h
index 2c707be1..fbf4fbce 100644
--- a/src/ChatPage.h
+++ b/src/ChatPage.h
@@ -175,6 +175,8 @@ signals:
                        bool promptForConfirmation = true);
     void newOnlineKeyBackupAvailable();
 
+    void callFunctionOnGuiThread(std::function<void()>);
+
 private slots:
     void logout();
     void removeRoom(const QString &room_id);
@@ -221,6 +223,8 @@ private:
     CallManager *callManager_;
 
     std::unique_ptr<mtx::pushrules::PushRuleEvaluator> pushrules;
+
+    QDateTime lastSpacesUpdate = QDateTime::currentDateTime();
 };
 
 template<class Collection>
diff --git a/src/PowerlevelsEditModels.cpp b/src/PowerlevelsEditModels.cpp
index 720ed338..f0fd9194 100644
--- a/src/PowerlevelsEditModels.cpp
+++ b/src/PowerlevelsEditModels.cpp
@@ -727,9 +727,12 @@ struct PowerLevelApplier
           [self = *this](const mtx::responses::EventId &, mtx::http::RequestErr e) mutable {
               if (e) {
                   if (e->status_code == 429 && e->matrix_error.retry_after.count() != 0) {
-                      QTimer::singleShot(e->matrix_error.retry_after,
-                                         ChatPage::instance(),
-                                         [self = std::move(self)]() mutable { self.next(); });
+                      ChatPage::instance()->callFunctionOnGuiThread(
+                        [self = std::move(self), interval = e->matrix_error.retry_after]() {
+                            QTimer::singleShot(interval,
+                                               ChatPage::instance(),
+                                               [self = std::move(self)]() mutable { self.next(); });
+                        });
                       return;
                   }
 
diff --git a/src/UserSettingsPage.cpp b/src/UserSettingsPage.cpp
index 7527c17f..dac9aef2 100644
--- a/src/UserSettingsPage.cpp
+++ b/src/UserSettingsPage.cpp
@@ -98,6 +98,8 @@ UserSettings::load(std::optional<QString> profile)
     privacyScreenTimeout_ =
       settings.value(QStringLiteral("user/privacy_screen_timeout"), 0).toInt();
     exposeDBusApi_ = settings.value(QStringLiteral("user/expose_dbus_api"), false).toBool();
+    updateSpaceVias_ =
+      settings.value(QStringLiteral("user/space_background_maintenance"), true).toBool();
 
     mobileMode_ = settings.value(QStringLiteral("user/mobile_mode"), false).toBool();
     emojiFont_  = settings.value(QStringLiteral("user/emoji_font_family"), "emoji").toString();
@@ -294,6 +296,17 @@ UserSettings::setExposeDBusApi(bool state)
 }
 
 void
+UserSettings::setUpdateSpaceVias(bool state)
+{
+    if (updateSpaceVias_ == state)
+        return;
+
+    updateSpaceVias_ = state;
+    emit updateSpaceViasChanged(state);
+    save();
+}
+
+void
 UserSettings::setMarkdown(bool state)
 {
     if (state == markdown_)
@@ -901,6 +914,7 @@ UserSettings::save()
     settings.setValue(QStringLiteral("open_image_external"), openImageExternal_);
     settings.setValue(QStringLiteral("open_video_external"), openVideoExternal_);
     settings.setValue(QStringLiteral("expose_dbus_api"), exposeDBusApi_);
+    settings.setValue(QStringLiteral("space_background_maintenance"), updateSpaceVias_);
 
     settings.endGroup(); // user
 
@@ -1102,6 +1116,8 @@ UserSettingsModel::data(const QModelIndex &index, int role) const
             return tr("Master signing key");
         case ExposeDBusApi:
             return tr("Expose room information via D-Bus");
+        case UpdateSpaceVias:
+            return tr("Periodically update community routing information");
         }
     } else if (role == Value) {
         switch (index.row()) {
@@ -1235,6 +1251,8 @@ UserSettingsModel::data(const QModelIndex &index, int role) const
             return cache::secret(mtx::secret_storage::secrets::cross_signing_master).has_value();
         case ExposeDBusApi:
             return i->exposeDBusApi();
+        case UpdateSpaceVias:
+            return i->updateSpaceVias();
         }
     } else if (role == Description) {
         switch (index.row()) {
@@ -1405,6 +1423,12 @@ UserSettingsModel::data(const QModelIndex &index, int role) const
                       "This can have useful applications, but it also could be used for nefarious "
                       "purposes. Enable at your own risk.\n\n"
                       "This setting will take effect upon restart.");
+        case UpdateSpaceVias:
+            return tr(
+              "To allow new users to join a community, the community needs to expose some "
+              "information about what servers participate in a room to community members. Since "
+              "the room participants can change over time, this needs to be updated from time to "
+              "time. This setting enables a background job to do that automatically.");
         }
     } else if (role == Type) {
         switch (index.row()) {
@@ -1453,6 +1477,7 @@ UserSettingsModel::data(const QModelIndex &index, int role) const
         case ShareKeysWithTrustedUsers:
         case UseOnlineKeyBackup:
         case ExposeDBusApi:
+        case UpdateSpaceVias:
         case SpaceNotifications:
         case FancyEffects:
         case ReducedMotion:
@@ -1938,6 +1963,13 @@ UserSettingsModel::setData(const QModelIndex &index, const QVariant &value, int
             } else
                 return false;
         }
+        case UpdateSpaceVias: {
+            if (value.userType() == QMetaType::Bool) {
+                i->setUpdateSpaceVias(value.toBool());
+                return true;
+            } else
+                return false;
+        }
         }
     }
     return false;
@@ -2187,4 +2219,7 @@ UserSettingsModel::UserSettingsModel(QObject *p)
     connect(s.get(), &UserSettings::exposeDBusApiChanged, this, [this] {
         emit dataChanged(index(ExposeDBusApi), index(ExposeDBusApi), {Value});
     });
+    connect(s.get(), &UserSettings::updateSpaceViasChanged, this, [this] {
+        emit dataChanged(index(UpdateSpaceVias), index(UpdateSpaceVias), {Value});
+    });
 }
diff --git a/src/UserSettingsPage.h b/src/UserSettingsPage.h
index d150c282..8477818b 100644
--- a/src/UserSettingsPage.h
+++ b/src/UserSettingsPage.h
@@ -121,6 +121,8 @@ class UserSettings final : public QObject
                  hiddenWidgetsChanged)
     Q_PROPERTY(
       bool exposeDBusApi READ exposeDBusApi WRITE setExposeDBusApi NOTIFY exposeDBusApiChanged)
+    Q_PROPERTY(bool updateSpaceVias READ updateSpaceVias WRITE setUpdateSpaceVias NOTIFY
+                 updateSpaceViasChanged)
 
     UserSettings();
 
@@ -205,6 +207,7 @@ public:
     void setOpenVideoExternal(bool state);
     void setCollapsedSpaces(QList<QStringList> spaces);
     void setExposeDBusApi(bool state);
+    void setUpdateSpaceVias(bool state);
 
     QString theme() const { return !theme_.isEmpty() ? theme_ : defaultTheme_; }
     bool messageHoverHighlight() const { return messageHoverHighlight_; }
@@ -277,6 +280,7 @@ public:
     bool openVideoExternal() const { return openVideoExternal_; }
     QList<QStringList> collapsedSpaces() const { return collapsedSpaces_; }
     bool exposeDBusApi() const { return exposeDBusApi_; }
+    bool updateSpaceVias() const { return updateSpaceVias_; }
 
 signals:
     void groupViewStateChanged(bool state);
@@ -339,6 +343,7 @@ signals:
     void hiddenWidgetsChanged();
     void recentReactionsChanged();
     void exposeDBusApiChanged(bool state);
+    void updateSpaceViasChanged(bool state);
 
 private:
     // Default to system theme if QT_QPA_PLATFORMTHEME var is set.
@@ -410,6 +415,7 @@ private:
     bool openImageExternal_;
     bool openVideoExternal_;
     bool exposeDBusApi_;
+    bool updateSpaceVias_;
 
     QSettings settings;
 
@@ -439,6 +445,7 @@ class UserSettingsModel final : public QAbstractListModel
 #ifdef NHEKO_DBUS_SYS
         ExposeDBusApi,
 #endif
+        UpdateSpaceVias,
 
         AccessibilitySection,
         ReducedMotion,
diff --git a/src/Utils.cpp b/src/Utils.cpp
index 54bd51ab..c5b2abd1 100644
--- a/src/Utils.cpp
+++ b/src/Utils.cpp
@@ -16,6 +16,7 @@
 #include <QStringBuilder>
 #include <QTextBoundaryFinder>
 #include <QTextDocument>
+#include <QTimer>
 #include <QWindow>
 #include <QXmlStreamReader>
 
@@ -28,11 +29,13 @@
 
 #include "Cache.h"
 #include "Cache_p.h"
+#include "ChatPage.h"
 #include "Config.h"
 #include "EventAccessors.h"
 #include "Logging.h"
 #include "MatrixClient.h"
 #include "UserSettingsPage.h"
+#include "timeline/Permissions.h"
 
 using TimelineEvent = mtx::events::collections::TimelineEvents;
 
@@ -1294,8 +1297,6 @@ utils::roomVias(const std::string &roomid)
                 deniedServers.reserve(acls->content.deny.size());
                 for (const auto &s : acls->content.deny)
                     allowedServers.push_back(globToRegexp(s));
-
-                nhlog::ui()->critical("ACL: {}", nlohmann::json(acls->content).dump(2));
             }
 
             auto isHostAllowed = [&acls, &allowedServers, &deniedServers](const std::string &host) {
@@ -1423,3 +1424,178 @@ utils::roomVias(const std::string &roomid)
 
     return vias;
 }
+
+void
+utils::updateSpaceVias()
+{
+    if (!UserSettings::instance()->updateSpaceVias())
+        return;
+
+    nhlog::net()->info("update space vias called");
+
+    auto rooms = cache::roomInfo(false);
+
+    auto us = http::client()->user_id().to_string();
+
+    auto weekAgo = (uint64_t)QDateTime::currentDateTime().addDays(-7).toMSecsSinceEpoch();
+
+    struct ApplySpaceUpdatesState
+    {
+        std::vector<mtx::events::StateEvent<mtx::events::state::space::Child>> childrenToUpdate;
+        std::vector<mtx::events::StateEvent<mtx::events::state::space::Parent>> parentsToUpdate;
+
+        static void next(std::shared_ptr<ApplySpaceUpdatesState> state)
+        {
+            if (!state->childrenToUpdate.empty()) {
+                const auto &child = state->childrenToUpdate.back();
+
+                http::client()->send_state_event(
+                  child.room_id,
+                  child.state_key,
+                  child.content,
+                  [state = std::move(state)](const mtx::responses::EventId &,
+                                             mtx::http::RequestErr e) mutable {
+                      const auto &child_ = state->childrenToUpdate.back();
+                      if (e) {
+                          if (e->status_code == 429 && e->matrix_error.retry_after.count() != 0) {
+                              ChatPage::instance()->callFunctionOnGuiThread(
+                                [state    = std::move(state),
+                                 interval = e->matrix_error.retry_after]() {
+                                    QTimer::singleShot(interval,
+                                                       ChatPage::instance(),
+                                                       [self = std::move(state)]() mutable {
+                                                           next(std::move(self));
+                                                       });
+                                });
+                              return;
+                          }
+
+                          nhlog::net()->error("Failed to update space child {} -> {}: {}",
+                                              child_.room_id,
+                                              child_.state_key,
+                                              *e);
+                      }
+                      nhlog::net()->info(
+                        "Updated space child {} -> {}", child_.room_id, child_.state_key);
+                      state->childrenToUpdate.pop_back();
+                      next(std::move(state));
+                  });
+                return;
+            } else if (!state->parentsToUpdate.empty()) {
+                const auto &parent = state->parentsToUpdate.back();
+
+                http::client()->send_state_event(
+                  parent.room_id,
+                  parent.state_key,
+                  parent.content,
+                  [state = std::move(state)](const mtx::responses::EventId &,
+                                             mtx::http::RequestErr e) mutable {
+                      const auto &parent_ = state->parentsToUpdate.back();
+                      if (e) {
+                          if (e->status_code == 429 && e->matrix_error.retry_after.count() != 0) {
+                              ChatPage::instance()->callFunctionOnGuiThread(
+                                [state    = std::move(state),
+                                 interval = e->matrix_error.retry_after]() {
+                                    QTimer::singleShot(interval,
+                                                       ChatPage::instance(),
+                                                       [self = std::move(state)]() mutable {
+                                                           next(std::move(self));
+                                                       });
+                                });
+                              return;
+                          }
+
+                          nhlog::net()->error("Failed to update space parent {} -> {}: {}",
+                                              parent_.room_id,
+                                              parent_.state_key,
+                                              *e);
+                      }
+                      nhlog::net()->info(
+                        "Updated space parent {} -> {}", parent_.room_id, parent_.state_key);
+                      state->parentsToUpdate.pop_back();
+                      next(std::move(state));
+                  });
+                return;
+            }
+        }
+    };
+
+    auto asus = std::make_shared<ApplySpaceUpdatesState>();
+
+    for (const auto &[roomid, info] : rooms.toStdMap()) {
+        if (!info.is_space)
+            continue;
+
+        auto spaceid = roomid.toStdString();
+
+        if (auto pl = cache::client()
+                        ->getStateEvent<mtx::events::state::PowerLevels>(spaceid)
+                        .value_or(mtx::events::StateEvent<mtx::events::state::PowerLevels>{})
+                        .content;
+            pl.user_level(us) < pl.state_level(to_string(mtx::events::EventType::SpaceChild)))
+            continue;
+
+        auto children = cache::client()->getChildRoomIds(spaceid);
+
+        for (const auto &childid : children) {
+            // only update children we are joined to
+            if (!rooms.contains(QString::fromStdString(childid)))
+                continue;
+
+            auto child =
+              cache::client()->getStateEvent<mtx::events::state::space::Child>(spaceid, childid);
+            if (child &&
+                // don't update too often
+                child->origin_server_ts < weekAgo &&
+                // ignore unset spaces
+                (child->content.via && !child->content.via->empty())) {
+                auto newVias = utils::roomVias(childid);
+
+                if (!newVias.empty() && newVias != child->content.via) {
+                    nhlog::net()->info("Will update {} -> {} child relation from {} to {}",
+                                       spaceid,
+                                       childid,
+                                       fmt::join(*child->content.via, ","),
+                                       fmt::join(newVias, ","));
+
+                    child->content.via = std::move(newVias);
+                    child->room_id     = spaceid;
+                    asus->childrenToUpdate.push_back(*std::move(child));
+                }
+            }
+
+            auto parent =
+              cache::client()->getStateEvent<mtx::events::state::space::Parent>(childid, spaceid);
+            if (parent &&
+                // don't update too often
+                parent->origin_server_ts < weekAgo &&
+                // ignore unset spaces
+                (parent->content.via && !parent->content.via->empty())) {
+                if (auto pl =
+                      cache::client()
+                        ->getStateEvent<mtx::events::state::PowerLevels>(childid)
+                        .value_or(mtx::events::StateEvent<mtx::events::state::PowerLevels>{})
+                        .content;
+                    pl.user_level(us) <
+                    pl.state_level(to_string(mtx::events::EventType::SpaceParent)))
+                    continue;
+
+                auto newVias = utils::roomVias(spaceid);
+
+                if (!newVias.empty() && newVias != parent->content.via) {
+                    nhlog::net()->info("Will update {} -> {} parent relation from {} to {}",
+                                       childid,
+                                       spaceid,
+                                       fmt::join(*parent->content.via, ","),
+                                       fmt::join(newVias, ","));
+
+                    parent->content.via = std::move(newVias);
+                    parent->room_id     = childid;
+                    asus->parentsToUpdate.push_back(*std::move(parent));
+                }
+            }
+        }
+    }
+
+    ApplySpaceUpdatesState::next(std::move(asus));
+}
diff --git a/src/Utils.h b/src/Utils.h
index bf411178..2bf01f84 100644
--- a/src/Utils.h
+++ b/src/Utils.h
@@ -335,4 +335,7 @@ markRoomAsDirect(QString roomid, std::vector<RoomMember> members);
 
 std::vector<std::string>
 roomVias(const std::string &roomid);
+
+void
+updateSpaceVias();
 }