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();
}
|