mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2026-08-06 20:03:12 +00:00
402cb0e668
ChatFilters::applyRemove() emptied the filter in the list and only then detached its chats. Unpinning a chat re-caches its siblings' pinned indices, which runs Session::refreshChatListEntry(); seeing the filter still listed but emptied, it re-entered removeFromChatList() -> setChatPinned() -> PinnedList::setPinned() on the same pinned list while its re-cache loop iterated with a cached size, reading past the end of the vector. This aborted on closing the Folders editor after removing two or more folders that had pinned chats. Detach the filter from the list before applyChange() tears down its chats, so the re-entrant refresh no longer touches it. Also harden the setPinned() re-cache loop to re-check the container size each iteration as defense in depth. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
168 lines
4.4 KiB
C++
168 lines
4.4 KiB
C++
/*
|
|
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 "dialogs/dialogs_pinned_list.h"
|
|
|
|
#include "data/data_saved_messages.h"
|
|
#include "dialogs/dialogs_key.h"
|
|
#include "dialogs/dialogs_entry.h"
|
|
#include "history/history.h"
|
|
#include "data/data_session.h"
|
|
#include "data/data_forum.h"
|
|
|
|
namespace Dialogs {
|
|
|
|
PinnedList::PinnedList(FilterId filterId, int limit)
|
|
: _filterId(filterId)
|
|
, _limit(limit) {
|
|
Expects(limit > 0);
|
|
}
|
|
|
|
void PinnedList::setLimit(int limit) {
|
|
Expects(limit > 0);
|
|
|
|
if (_limit == limit) {
|
|
return;
|
|
}
|
|
_limit = limit;
|
|
applyLimit(_limit);
|
|
}
|
|
|
|
void PinnedList::addPinned(Key key) {
|
|
Expects(key.entry()->folderKnown());
|
|
|
|
addPinnedGetPosition(key);
|
|
}
|
|
|
|
int PinnedList::addPinnedGetPosition(Key key) {
|
|
const auto already = ranges::find(_data, key);
|
|
if (already != end(_data)) {
|
|
return already - begin(_data);
|
|
}
|
|
applyLimit(_limit - 1);
|
|
const auto position = int(_data.size());
|
|
_data.push_back(key);
|
|
key.entry()->cachePinnedIndex(_filterId, position + 1);
|
|
return position;
|
|
}
|
|
|
|
void PinnedList::setPinned(Key key, bool pinned) {
|
|
Expects(key.entry()->folderKnown() || _filterId != 0);
|
|
|
|
if (pinned) {
|
|
const int position = addPinnedGetPosition(key);
|
|
if (position) {
|
|
const auto begin = _data.begin();
|
|
std::rotate(begin, begin + position, begin + position + 1);
|
|
for (auto i = 0; i != position + 1; ++i) {
|
|
_data[i].entry()->cachePinnedIndex(_filterId, i + 1);
|
|
}
|
|
}
|
|
} else if (const auto it = ranges::find(_data, key); it != end(_data)) {
|
|
const auto index = int(it - begin(_data));
|
|
_data.erase(it);
|
|
key.entry()->cachePinnedIndex(_filterId, 0);
|
|
// cachePinnedIndex() may re-enter and erase from _data (for example
|
|
// while a filter is being removed), so re-check the size each
|
|
// iteration instead of caching it to avoid an out-of-bounds access.
|
|
for (auto i = index; i < int(size(_data)); ++i) {
|
|
_data[i].entry()->cachePinnedIndex(_filterId, i + 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
void PinnedList::applyLimit(int limit) {
|
|
Expects(limit >= 0);
|
|
|
|
while (_data.size() > limit) {
|
|
setPinned(_data.back(), false);
|
|
}
|
|
}
|
|
|
|
void PinnedList::clear() {
|
|
applyLimit(0);
|
|
}
|
|
|
|
void PinnedList::applyList(
|
|
not_null<Data::Session*> owner,
|
|
const QVector<MTPDialogPeer> &list) {
|
|
Expects(this != owner->savedMessages().chatsList()->pinned());
|
|
|
|
clear();
|
|
for (const auto &peer : list) {
|
|
peer.match([&](const MTPDdialogPeer &data) {
|
|
if (const auto peerId = peerFromMTP(data.vpeer())) {
|
|
addPinned(owner->history(peerId));
|
|
}
|
|
}, [&](const MTPDdialogPeerFolder &data) {
|
|
addPinned(owner->folder(data.vfolder_id().v));
|
|
});
|
|
}
|
|
}
|
|
|
|
void PinnedList::applyList(
|
|
not_null<Data::SavedMessages*> sublistsOwner,
|
|
const QVector<MTPDialogPeer> &list) {
|
|
Expects(this == sublistsOwner->chatsList()->pinned());
|
|
|
|
clear();
|
|
for (const auto &peer : list) {
|
|
peer.match([&](const MTPDdialogPeer &data) {
|
|
if (const auto peerId = peerFromMTP(data.vpeer())) {
|
|
const auto peer = sublistsOwner->owner().peer(peerId);
|
|
addPinned(sublistsOwner->sublist(peer));
|
|
}
|
|
}, [](const MTPDdialogPeerFolder &data) {
|
|
});
|
|
}
|
|
}
|
|
|
|
void PinnedList::applyList(
|
|
not_null<Data::Forum*> forum,
|
|
const QVector<MTPint> &list) {
|
|
Expects(this == forum->topicsList()->pinned());
|
|
|
|
clear();
|
|
for (const auto &topicId : list) {
|
|
addPinned(forum->topicFor(topicId.v));
|
|
}
|
|
}
|
|
|
|
void PinnedList::applyList(const std::vector<not_null<History*>> &list) {
|
|
Expects(_filterId != 0);
|
|
|
|
const auto old = base::take(_data);
|
|
|
|
const auto count = int(list.size());
|
|
_data.reserve(count);
|
|
for (auto i = 0; i != count; ++i) {
|
|
const auto history = list[i];
|
|
_data.emplace_back(history);
|
|
history->cachePinnedIndex(_filterId, i + 1);
|
|
}
|
|
|
|
for (const auto &key : old) {
|
|
const auto history = key.history();
|
|
if (!history || !ranges::contains(_data, history, &Key::history)) {
|
|
key.entry()->cachePinnedIndex(_filterId, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
void PinnedList::reorder(Key key1, Key key2) {
|
|
const auto index1 = ranges::find(_data, key1) - begin(_data);
|
|
const auto index2 = ranges::find(_data, key2) - begin(_data);
|
|
Assert(index1 >= 0 && index1 < _data.size());
|
|
Assert(index2 >= 0 && index2 < _data.size());
|
|
Assert(index1 != index2);
|
|
std::swap(_data[index1], _data[index2]);
|
|
key1.entry()->cachePinnedIndex(_filterId, index2 + 1);
|
|
key2.entry()->cachePinnedIndex(_filterId, index1 + 1);
|
|
}
|
|
|
|
} // namespace Dialogs
|