tdesktop/Telegram/SourceFiles/data/data_forum.cpp

447 lines
11 KiB
C++
Raw Normal View History

/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "data/data_forum.h"
2022-09-20 18:12:30 +00:00
#include "data/data_channel.h"
#include "data/data_histories.h"
2022-09-20 18:12:30 +00:00
#include "data/data_session.h"
2022-10-21 17:13:13 +00:00
#include "data/data_forum_icons.h"
2022-09-20 18:12:30 +00:00
#include "data/data_forum_topic.h"
#include "data/notify/data_notify_settings.h"
2022-09-20 18:12:30 +00:00
#include "history/history.h"
#include "history/history_item.h"
#include "history/history_unread_things.h"
2022-09-20 18:12:30 +00:00
#include "main/main_session.h"
#include "base/random.h"
#include "apiwrap.h"
#include "lang/lang_keys.h"
#include "core/application.h"
2022-09-20 18:12:30 +00:00
#include "ui/layers/generic_box.h"
#include "ui/widgets/input_fields.h"
2022-10-28 05:19:27 +00:00
#include "storage/storage_facade.h"
#include "storage/storage_shared_media.h"
2022-09-20 18:12:30 +00:00
#include "window/window_session_controller.h"
#include "window/notifications_manager.h"
2022-09-20 18:12:30 +00:00
#include "styles/style_boxes.h"
namespace Data {
2022-09-20 18:12:30 +00:00
namespace {
constexpr auto kTopicsFirstLoad = 20;
constexpr auto kLoadedTopicsMinCount = 20;
2022-09-20 18:12:30 +00:00
constexpr auto kTopicsPerPage = 500;
constexpr auto kStalePerRequest = 100;
// constexpr auto kGeneralColorId = 0xA9A9A9;
2022-09-20 18:12:30 +00:00
} // namespace
Forum::Forum(not_null<History*> history)
: _history(history)
, _topicsList(&session(), FilterId(0), rpl::single(1)) {
Expects(_history->peer->isChannel());
if (_history->inChatList()) {
preloadTopics();
}
2022-10-21 17:13:13 +00:00
if (channel()->canCreateTopics()) {
owner().forumIcons().requestDefaultIfUnknown();
}
2022-09-20 18:12:30 +00:00
}
Forum::~Forum() {
for (const auto &request : _topicRequests) {
if (request.second.id != _staleRequestId) {
owner().histories().cancelRequest(request.second.id);
}
}
if (_staleRequestId) {
session().api().request(_staleRequestId).cancel();
}
if (_requestId) {
session().api().request(_requestId).cancel();
}
2022-10-28 05:19:27 +00:00
const auto peerId = _history->peer->id;
for (const auto &[rootId, topic] : _topics) {
session().storage().unload(Storage::SharedMediaUnloadThread(
peerId,
rootId));
_history->setForwardDraft(rootId, {});
2022-10-28 05:19:27 +00:00
}
}
Session &Forum::owner() const {
return _history->owner();
}
Main::Session &Forum::session() const {
return _history->session();
}
not_null<History*> Forum::history() const {
return _history;
}
not_null<ChannelData*> Forum::channel() const {
return _history->peer->asChannel();
}
2022-09-20 18:12:30 +00:00
not_null<Dialogs::MainList*> Forum::topicsList() {
return &_topicsList;
}
2022-10-19 08:19:11 +00:00
void Forum::unpinTopic() {
const auto list = _topicsList.pinned();
while (!list->order().empty()) {
list->setPinned(list->order().front(), false);
}
}
rpl::producer<> Forum::destroyed() const {
return channel()->flagsValue(
) | rpl::filter([=](const ChannelData::Flags::Change &update) {
using Flag = ChannelData::Flag;
return (update.diff & Flag::Forum) && !(update.value & Flag::Forum);
}) | rpl::take(1) | rpl::to_empty;
}
rpl::producer<not_null<ForumTopic*>> Forum::topicDestroyed() const {
return _topicDestroyed.events();
}
void Forum::preloadTopics() {
if (topicsList()->indexed()->size() < kLoadedTopicsMinCount) {
requestTopics();
}
}
void Forum::reloadTopics() {
_topicsList.setLoaded(false);
session().api().request(base::take(_requestId)).cancel();
_offset = {};
for (const auto &[rootId, topic] : _topics) {
if (!topic->creating()) {
_staleRootIds.emplace(topic->rootId());
}
}
requestTopics();
}
2022-09-20 18:12:30 +00:00
void Forum::requestTopics() {
if (_topicsList.loaded() || _requestId) {
2022-09-20 18:12:30 +00:00
return;
}
const auto firstLoad = !_offset.date;
2022-09-20 18:12:30 +00:00
const auto loadCount = firstLoad ? kTopicsFirstLoad : kTopicsPerPage;
_requestId = session().api().request(MTPchannels_GetForumTopics(
2022-09-20 18:12:30 +00:00
MTP_flags(0),
channel()->inputChannel,
2022-09-20 18:12:30 +00:00
MTPstring(), // q
MTP_int(_offset.date),
MTP_int(_offset.id),
MTP_int(_offset.topicId),
2022-09-20 18:12:30 +00:00
MTP_int(loadCount)
)).done([=](const MTPmessages_ForumTopics &result) {
applyReceivedTopics(result, _offset);
const auto &list = result.data().vtopics().v;
if (list.isEmpty() || list.size() == result.data().vcount().v) {
_topicsList.setLoaded();
}
2022-09-20 18:12:30 +00:00
_requestId = 0;
_chatsListChanges.fire({});
if (_topicsList.loaded()) {
2022-09-20 18:12:30 +00:00
_chatsListLoadedEvents.fire({});
}
requestSomeStale();
2022-09-20 18:12:30 +00:00
}).fail([=](const MTP::Error &error) {
_requestId = 0;
_topicsList.setLoaded();
2022-10-12 13:42:35 +00:00
if (error.type() == u"CHANNEL_FORUM_MISSING"_q) {
const auto flags = channel()->flags() & ~ChannelDataFlag::Forum;
channel()->setFlags(flags);
}
2022-09-20 18:12:30 +00:00
}).send();
}
void Forum::applyTopicDeleted(MsgId rootId) {
_topicsDeleted.emplace(rootId);
const auto i = _topics.find(rootId);
if (i != end(_topics)) {
2022-10-19 10:59:37 +00:00
const auto raw = i->second.get();
Core::App().notifications().clearFromTopic(raw);
owner().removeChatListEntry(raw);
_topicDestroyed.fire(raw);
_topics.erase(i);
2022-10-19 10:59:37 +00:00
_history->destroyMessagesByTopic(rootId);
2022-10-28 05:19:27 +00:00
session().storage().unload(Storage::SharedMediaUnloadThread(
_history->peer->id,
rootId));
_history->setForwardDraft(rootId, {});
}
}
2022-09-27 06:25:26 +00:00
void Forum::applyReceivedTopics(
const MTPmessages_ForumTopics &topics,
ForumOffsets &updateOffsets) {
applyReceivedTopics(topics, [&](not_null<ForumTopic*> topic) {
if (const auto last = topic->lastServerMessage()) {
updateOffsets.date = last->date();
updateOffsets.id = last->id;
}
updateOffsets.topicId = topic->rootId();
});
}
void Forum::applyReceivedTopics(
const MTPmessages_ForumTopics &topics,
Fn<void(not_null<ForumTopic*>)> callback) {
2022-09-27 06:25:26 +00:00
const auto &data = topics.data();
owner().processUsers(data.vusers());
owner().processChats(data.vchats());
owner().processMessages(data.vmessages(), NewMessageType::Existing);
2022-09-27 06:25:26 +00:00
channel()->ptsReceived(data.vpts().v);
const auto &list = data.vtopics().v;
for (const auto &topic : list) {
const auto rootId = topic.match([&](const auto &data) {
return data.vid().v;
});
_staleRootIds.remove(rootId);
topic.match([&](const MTPDforumTopicDeleted &data) {
applyTopicDeleted(rootId);
}, [&](const MTPDforumTopic &data) {
_topicsDeleted.remove(rootId);
const auto i = _topics.find(rootId);
const auto creating = (i == end(_topics));
const auto raw = creating
? _topics.emplace(
rootId,
std::make_unique<ForumTopic>(this, rootId)
).first->second.get()
: i->second.get();
raw->applyTopic(data);
if (callback) {
callback(raw);
}
});
2022-09-27 06:25:26 +00:00
}
if (!_staleRootIds.empty()) {
requestSomeStale();
}
}
void Forum::requestSomeStale() {
2022-10-27 07:14:49 +00:00
if (_staleRequestId
|| (!_offset.id && _requestId)
|| _staleRootIds.empty()) {
return;
}
const auto type = Histories::RequestType::History;
auto rootIds = QVector<MTPint>();
rootIds.reserve(std::min(int(_staleRootIds.size()), kStalePerRequest));
for (auto i = begin(_staleRootIds); i != end(_staleRootIds);) {
const auto rootId = *i;
i = _staleRootIds.erase(i);
if (_topicRequests.contains(rootId)) {
continue;
}
rootIds.push_back(MTP_int(rootId));
if (rootIds.size() == kStalePerRequest) {
break;
}
}
2022-10-27 07:14:49 +00:00
if (rootIds.empty()) {
return;
}
const auto call = [=] {
for (const auto &id : rootIds) {
finishTopicRequest(id.v);
}
};
auto &histories = owner().histories();
_staleRequestId = histories.sendRequest(_history, type, [=](
Fn<void()> finish) {
return session().api().request(
MTPchannels_GetForumTopicsByID(
channel()->inputChannel,
MTP_vector<MTPint>(rootIds))
).done([=](const MTPmessages_ForumTopics &result) {
applyReceivedTopics(result);
call();
finish();
}).fail([=] {
call();
finish();
}).send();
});
for (const auto &id : rootIds) {
_topicRequests[id.v].id = _staleRequestId;
}
}
void Forum::finishTopicRequest(MsgId rootId) {
if (const auto request = _topicRequests.take(rootId)) {
for (const auto &callback : request->callbacks) {
callback();
}
}
2022-09-27 06:25:26 +00:00
}
void Forum::requestTopic(MsgId rootId, Fn<void()> done) {
_staleRootIds.remove(rootId);
auto &request = _topicRequests[rootId];
if (done) {
request.callbacks.push_back(std::move(done));
}
if (request.id) {
return;
}
const auto call = [=] {
finishTopicRequest(rootId);
};
const auto type = Histories::RequestType::History;
auto &histories = owner().histories();
request.id = histories.sendRequest(_history, type, [=](
Fn<void()> finish) {
return session().api().request(
MTPchannels_GetForumTopicsByID(
channel()->inputChannel,
MTP_vector<MTPint>(1, MTP_int(rootId.bare)))
).done([=](const MTPmessages_ForumTopics &result) {
applyReceivedTopics(result);
call();
finish();
}).fail([=] {
call();
finish();
}).send();
});
}
ForumTopic *Forum::applyTopicAdded(
MsgId rootId,
const QString &title,
int32 colorId,
DocumentId iconId) {
Expects(rootId != 0);
2022-10-04 15:34:45 +00:00
const auto i = _topics.find(rootId);
const auto raw = (i != end(_topics))
? i->second.get()
: _topics.emplace(
2022-09-20 18:12:30 +00:00
rootId,
std::make_unique<ForumTopic>(this, rootId)
2022-09-20 18:12:30 +00:00
).first->second.get();
2022-10-04 15:34:45 +00:00
raw->applyTitle(title);
raw->applyColorId(colorId);
2022-10-04 15:34:45 +00:00
raw->applyIconId(iconId);
if (!creating(rootId)) {
raw->addToChatList(FilterId(), topicsList());
_chatsListChanges.fire({});
2022-09-20 18:12:30 +00:00
}
return raw;
2022-09-20 18:12:30 +00:00
}
MsgId Forum::reserveCreatingId(
const QString &title,
int32 colorId,
DocumentId iconId) {
const auto result = owner().nextLocalMessageId();
_creatingRootIds.emplace(result);
applyTopicAdded(result, title, colorId, iconId);
return result;
}
void Forum::discardCreatingId(MsgId rootId) {
Expects(creating(rootId));
const auto i = _topics.find(rootId);
if (i != end(_topics)) {
Assert(!i->second->inChatList());
_topics.erase(i);
}
_creatingRootIds.remove(rootId);
}
bool Forum::creating(MsgId rootId) const {
return _creatingRootIds.contains(rootId);
}
2022-10-04 15:34:45 +00:00
void Forum::created(MsgId rootId, MsgId realId) {
if (rootId == realId) {
return;
}
_creatingRootIds.remove(rootId);
const auto i = _topics.find(rootId);
Assert(i != end(_topics));
auto topic = std::move(i->second);
_topics.erase(i);
const auto id = FullMsgId(_history->peer->id, realId);
if (!_topics.contains(realId)) {
_topics.emplace(
realId,
std::move(topic)
).first->second->setRealRootId(realId);
}
owner().notifyItemIdChange({ id, rootId });
2022-10-04 15:34:45 +00:00
}
void Forum::clearAllUnreadMentions() {
for (const auto &[rootId, topic] : _topics) {
topic->unreadMentions().clear();
}
}
void Forum::clearAllUnreadReactions() {
for (const auto &[rootId, topic] : _topics) {
topic->unreadReactions().clear();
}
}
void Forum::enumerateTopics(Fn<void(not_null<ForumTopic*>)> action) const {
for (const auto &[rootId, topic] : _topics) {
action(topic.get());
}
}
ForumTopic *Forum::topicFor(MsgId rootId) {
if (!rootId) {
return nullptr;
}
2022-10-13 08:46:30 +00:00
const auto i = _topics.find(rootId);
return (i != end(_topics)) ? i->second.get() : nullptr;
}
ForumTopic *Forum::enforceTopicFor(MsgId rootId) {
Expects(rootId != 0);
const auto i = _topics.find(rootId);
if (i != end(_topics)) {
return i->second.get();
}
const auto result = applyTopicAdded(rootId, {}, {}, {});
requestTopic(rootId);
return result;
}
bool Forum::topicDeleted(MsgId rootId) const {
return _topicsDeleted.contains(rootId);
}
2022-09-20 18:12:30 +00:00
rpl::producer<> Forum::chatsListChanges() const {
return _chatsListChanges.events();
}
rpl::producer<> Forum::chatsListLoadedEvents() const {
return _chatsListLoadedEvents.events();
}
} // namespace Data