Cache topPeers locally.

This commit is contained in:
John Preston 2024-04-11 17:46:19 +04:00
parent c11f4efc5c
commit 4cdd939028
9 changed files with 280 additions and 14 deletions

View file

@ -95,9 +95,6 @@ SettingsProxy::SettingsProxy()
}
QByteArray SettingsProxy::serialize() const {
auto result = QByteArray();
auto stream = QDataStream(&result, QIODevice::WriteOnly);
const auto serializedSelected = SerializeProxyData(_selected);
const auto serializedList = ranges::views::all(
_list
@ -111,9 +108,7 @@ QByteArray SettingsProxy::serialize() const {
0,
ranges::plus(),
&Serialize::bytearraySize);
result.reserve(size);
stream.setVersion(QDataStream::Qt_5_1);
auto stream = Serialize::ByteArrayWriter(size);
stream
<< qint32(_tryIPv6 ? 1 : 0)
<< qint32(_useProxyForCalls ? 1 : 0)
@ -123,9 +118,7 @@ QByteArray SettingsProxy::serialize() const {
for (const auto &i : serializedList) {
stream << i;
}
stream.device()->close();
return result;
return std::move(stream).result();
}
bool SettingsProxy::setFromSerialized(const QByteArray &serialized) {
@ -133,7 +126,7 @@ bool SettingsProxy::setFromSerialized(const QByteArray &serialized) {
return true;
}
auto stream = QDataStream(serialized);
auto stream = Serialize::ByteArrayReader(serialized);
auto tryIPv6 = qint32(_tryIPv6 ? 1 : 0);
auto useProxyForCalls = qint32(_useProxyForCalls ? 1 : 0);
@ -148,7 +141,7 @@ bool SettingsProxy::setFromSerialized(const QByteArray &serialized) {
>> settings
>> selectedProxy
>> listCount;
if (stream.status() == QDataStream::Ok) {
if (stream.ok()) {
for (auto i = 0; i != listCount; ++i) {
QByteArray data;
stream >> data;
@ -157,7 +150,7 @@ bool SettingsProxy::setFromSerialized(const QByteArray &serialized) {
}
}
if (stream.status() != QDataStream::Ok) {
if (!stream.ok()) {
LOG(("App Error: "
"Bad data for Core::SettingsProxy::setFromSerialized()"));
return false;

View file

@ -14,6 +14,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_user.h"
#include "main/main_session.h"
#include "mtproto/mtproto_config.h"
#include "storage/serialize_common.h"
#include "storage/serialize_peer.h"
#include "storage/storage_account.h"
namespace Data {
namespace {
@ -25,6 +28,19 @@ constexpr auto kRequestTimeLimit = 10 * crl::time(1000);
return std::exp((now - was) * 1. / decay);
}
[[nodiscard]] quint64 SerializeRating(float64 rating) {
return quint64(
base::SafeRound(std::clamp(rating, 0., 1'000'000.) * 1'000'000.));
}
[[nodiscard]] float64 DeserializeRating(quint64 rating) {
return std::clamp(
rating,
quint64(),
quint64(1'000'000'000'000ULL)
) / 1'000'000.;
}
} // namespace
TopPeers::TopPeers(not_null<Main::Session*> session)
@ -43,12 +59,16 @@ TopPeers::TopPeers(not_null<Main::Session*> session)
TopPeers::~TopPeers() = default;
std::vector<not_null<PeerData*>> TopPeers::list() const {
_session->local().readSearchSuggestions();
return _list
| ranges::view::transform(&TopPeer::peer)
| ranges::to_vector;
}
bool TopPeers::disabled() const {
_session->local().readSearchSuggestions();
return _disabled;
}
@ -67,9 +87,13 @@ void TopPeers::remove(not_null<PeerData*> peer) {
MTP_topPeerCategoryCorrespondents(),
peer->input
)).send();
_session->local().writeSearchSuggestionsDelayed();
}
void TopPeers::increment(not_null<PeerData*> peer, TimeId date) {
_session->local().readSearchSuggestions();
if (_disabled || date <= _lastReceivedDate) {
return;
}
@ -95,6 +119,8 @@ void TopPeers::increment(not_null<PeerData*> peer, TimeId date) {
if (changed) {
_updates.fire({});
}
_session->local().writeSearchSuggestionsDelayed();
}
}
@ -108,6 +134,8 @@ void TopPeers::reload() {
}
void TopPeers::toggleDisabled(bool disabled) {
_session->local().readSearchSuggestions();
if (disabled) {
if (!_disabled || !_list.empty()) {
_disabled = true;
@ -126,6 +154,8 @@ void TopPeers::toggleDisabled(bool disabled) {
request();
}
}).send();
_session->local().writeSearchSuggestionsDelayed();
}
void TopPeers::request() {
@ -182,10 +212,67 @@ void TopPeers::request() {
uint64 TopPeers::countHash() const {
using namespace Api;
auto hash = HashInit();
for (const auto &top : _list) {
for (const auto &top : _list | ranges::views::take(kLimit)) {
HashUpdate(hash, peerToUser(top.peer->id).bare);
}
return HashFinalize(hash);
}
QByteArray TopPeers::serialize() const {
_session->local().readSearchSuggestions();
if (!_disabled && _list.empty()) {
return {};
}
auto size = 3 * sizeof(quint32); // AppVersion, disabled, count
const auto count = std::min(int(_list.size()), kLimit);
auto &&list = _list | ranges::views::take(count);
for (const auto &top : list) {
size += Serialize::peerSize(top.peer) + sizeof(quint64);
}
auto stream = Serialize::ByteArrayWriter(size);
stream
<< quint32(AppVersion)
<< quint32(_disabled ? 1 : 0)
<< quint32(_list.size());
for (const auto &top : list) {
Serialize::writePeer(stream, top.peer);
stream << SerializeRating(top.rating);
}
return std::move(stream).result();
}
void TopPeers::applyLocal(QByteArray serialized) {
if (_lastReceived || serialized.isEmpty()) {
return;
}
auto stream = Serialize::ByteArrayReader(serialized);
auto streamAppVersion = quint32();
auto disabled = quint32();
auto count = quint32();
stream >> streamAppVersion >> disabled >> count;
if (!stream.ok()) {
return;
}
_list.reserve(count);
for (auto i = 0; i != int(count); ++i) {
auto rating = quint64();
const auto peer = Serialize::readPeer(
_session,
streamAppVersion,
stream);
stream >> rating;
if (stream.ok() && peer) {
_list.push_back({
.peer = peer,
.rating = DeserializeRating(rating),
});
} else {
_list.clear();
return;
}
}
_disabled = (disabled == 1);
}
} // namespace Data

View file

@ -27,6 +27,9 @@ public:
void reload();
void toggleDisabled(bool disabled);
[[nodiscard]] QByteArray serialize() const;
void applyLocal(QByteArray serialized);
private:
struct TopPeer {
not_null<PeerData*> peer;

View file

@ -53,6 +53,7 @@ Account::Account(not_null<Domain*> domain, const QString &dataName, int index)
Account::~Account() {
if (const auto session = maybeSession()) {
session->saveSettingsNowIfNeeded();
_local->writeSearchSuggestionsIfNeeded();
}
destroySession(DestroyReason::Quitting);
}

View file

@ -9,6 +9,25 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Serialize {
ByteArrayWriter::ByteArrayWriter(int expectedSize)
: _stream(&_result, QIODevice::WriteOnly) {
if (expectedSize) {
_result.reserve(expectedSize);
}
_stream.setVersion(QDataStream::Qt_5_1);
}
QByteArray ByteArrayWriter::result() && {
_stream.device()->close();
return std::move(_result);
}
ByteArrayReader::ByteArrayReader(QByteArray data)
: _data(std::move(data))
, _stream(&_data, QIODevice::ReadOnly) {
_stream.setVersion(QDataStream::Qt_5_1);
}
void writeColor(QDataStream &stream, const QColor &color) {
stream << (quint32(uchar(color.red()))
| (quint32(uchar(color.green())) << 8)

View file

@ -14,6 +14,70 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Serialize {
class ByteArrayWriter final {
public:
explicit ByteArrayWriter(int expectedSize = 0);
[[nodiscard]] QDataStream &underlying() {
return _stream;
}
[[nodiscard]] operator QDataStream &() {
return _stream;
}
[[nodiscard]] QByteArray result() &&;
private:
QByteArray _result;
QDataStream _stream;
};
template <typename T>
inline ByteArrayWriter &operator<<(ByteArrayWriter &stream, const T &data) {
stream.underlying() << data;
return stream;
}
class ByteArrayReader final {
public:
explicit ByteArrayReader(QByteArray data);
[[nodiscard]] QDataStream &underlying() {
return _stream;
}
[[nodiscard]] operator QDataStream &() {
return _stream;
}
[[nodiscard]] bool atEnd() const {
return _stream.atEnd();
}
[[nodiscard]] bool status() const {
return _stream.status();
}
[[nodiscard]] bool ok() const {
return _stream.status() == QDataStream::Ok;
}
private:
QByteArray _data;
QDataStream _stream;
};
template <typename T>
inline ByteArrayReader &operator>>(ByteArrayReader &stream, T &data) {
if (!stream.ok()) {
data = T();
} else {
stream.underlying() >> data;
if (!stream.ok()) {
data = T();
}
}
return stream;
}
inline int stringSize(const QString &str) {
return sizeof(quint32) + str.size() * sizeof(ushort);
}

View file

@ -118,12 +118,16 @@ uint32 peerSize(not_null<PeerData*> peer) {
+ imageLocationSize(peer->userpicLocation())
+ sizeof(qint32); // userpic has video
if (const auto user = peer->asUser()) {
const auto botInlinePlaceholder = user->isBot()
? user->botInfo->inlinePlaceholder
: QString();
result += stringSize(user->firstName)
+ stringSize(user->lastName)
+ stringSize(user->phone())
+ stringSize(user->username())
+ sizeof(quint64) // access
+ sizeof(qint32) // flags
+ stringSize(botInlinePlaceholder)
+ sizeof(quint32) // lastseen
+ sizeof(qint32) // contact
+ sizeof(qint32); // botInfoVersion

View file

@ -27,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "core/application.h"
#include "core/core_settings.h"
#include "core/file_location.h"
#include "data/components/top_peers.h"
#include "data/stickers/data_stickers.h"
#include "data/data_session.h"
#include "data/data_document.h"
@ -42,6 +43,7 @@ using namespace details;
using Database = Cache::Database;
constexpr auto kDelayedWriteTimeout = crl::time(1000);
constexpr auto kWriteSearchSuggestionsDelay = 5 * crl::time(1000);
constexpr auto kStickersVersionTag = quint32(-1);
constexpr auto kStickersSerializeVersion = 4;
@ -87,6 +89,7 @@ enum { // Local Storage Keys
lskSelfSerialized = 0x15, // serialized self
lskMasksKeys = 0x16, // no data
lskCustomEmojiKeys = 0x17, // no data
lskSearchSuggestions = 0x18, // no data
};
auto EmptyMessageDraftSources()
@ -137,10 +140,13 @@ Account::Account(not_null<Main::Account*> owner, const QString &dataName)
, _cacheTotalTimeLimit(Database::Settings().totalTimeLimit)
, _cacheBigFileTotalTimeLimit(Database::Settings().totalTimeLimit)
, _writeMapTimer([=] { writeMap(); })
, _writeLocationsTimer([=] { writeLocations(); }) {
, _writeLocationsTimer([=] { writeLocations(); })
, _writeSearchSuggestionsTimer([=] { writeSearchSuggestions(); }) {
}
Account::~Account() {
Expects(!_writeSearchSuggestionsTimer.isActive());
if (_localKey && _mapChanged) {
writeMap();
}
@ -209,6 +215,7 @@ base::flat_set<QString> Account::collectGoodNames() const {
_installedCustomEmojiKey,
_featuredCustomEmojiKey,
_archivedCustomEmojiKey,
_searchSuggestionsKey,
};
auto result = base::flat_set<QString>{
"map0",
@ -294,6 +301,7 @@ Account::ReadMapResult Account::readMapWith(
quint64 savedGifsKey = 0;
quint64 legacyBackgroundKeyDay = 0, legacyBackgroundKeyNight = 0;
quint64 userSettingsKey = 0, recentHashtagsAndBotsKey = 0, exportSettingsKey = 0;
quint64 searchSuggestionsKey = 0;
while (!map.stream.atEnd()) {
quint32 keyType;
map.stream >> keyType;
@ -399,6 +407,9 @@ Account::ReadMapResult Account::readMapWith(
>> featuredCustomEmojiKey
>> archivedCustomEmojiKey;
} break;
case lskSearchSuggestions: {
map.stream >> searchSuggestionsKey;
} break;
default:
LOG(("App Error: unknown key type in encrypted map: %1").arg(keyType));
return ReadMapResult::Failed;
@ -434,6 +445,7 @@ Account::ReadMapResult Account::readMapWith(
_settingsKey = userSettingsKey;
_recentHashtagsAndBotsKey = recentHashtagsAndBotsKey;
_exportSettingsKey = exportSettingsKey;
_searchSuggestionsKey = searchSuggestionsKey;
_oldMapVersion = mapData.version;
if (_oldMapVersion < AppVersion) {
@ -539,6 +551,7 @@ void Account::writeMap() {
if (_installedCustomEmojiKey || _featuredCustomEmojiKey || _archivedCustomEmojiKey) {
mapSize += sizeof(quint32) + 3 * sizeof(quint64);
}
if (_searchSuggestionsKey) mapSize += sizeof(quint32) + sizeof(quint64);
EncryptedDescriptor mapData(mapSize);
if (!self.isEmpty()) {
@ -598,12 +611,18 @@ void Account::writeMap() {
<< quint64(_featuredCustomEmojiKey)
<< quint64(_archivedCustomEmojiKey);
}
if (_searchSuggestionsKey) {
mapData.stream << quint32(lskSearchSuggestions);
mapData.stream << quint64(_searchSuggestionsKey);
}
map.writeEncrypted(mapData, _localKey);
_mapChanged = false;
}
void Account::reset() {
_writeSearchSuggestionsTimer.cancel();
auto names = collectGoodNames();
_draftsMap.clear();
_draftCursorsMap.clear();
@ -624,6 +643,7 @@ void Account::reset() {
_archivedCustomEmojiKey = 0;
_legacyBackgroundKeyDay = _legacyBackgroundKeyNight = 0;
_settingsKey = _recentHashtagsAndBotsKey = _exportSettingsKey = 0;
_searchSuggestionsKey = 0;
_oldMapVersion = 0;
_fileLocations.clear();
_fileLocationPairs.clear();
@ -2842,6 +2862,73 @@ Export::Settings Account::readExportSettings() {
: Export::Settings();
}
void Account::writeSearchSuggestionsDelayed() {
Expects(_owner->sessionExists());
if (!_writeSearchSuggestionsTimer.isActive()) {
_writeSearchSuggestionsTimer.callOnce(kWriteSearchSuggestionsDelay);
}
}
void Account::writeSearchSuggestionsIfNeeded() {
if (_writeSearchSuggestionsTimer.isActive()) {
_writeSearchSuggestionsTimer.cancel();
writeSearchSuggestions();
}
}
void Account::writeSearchSuggestions() {
Expects(_owner->sessionExists());
const auto top = _owner->session().topPeers().serialize();
const auto recent = QByteArray();// _owner->session().recentPeers().serialize();
if (top.isEmpty() && recent.isEmpty()) {
if (_searchSuggestionsKey) {
ClearKey(_searchSuggestionsKey, _basePath);
_searchSuggestionsKey = 0;
writeMapDelayed();
}
return;
}
if (!_searchSuggestionsKey) {
_searchSuggestionsKey = GenerateKey(_basePath);
writeMapQueued();
}
quint32 size = Serialize::bytearraySize(top)
+ Serialize::bytearraySize(recent);
EncryptedDescriptor data(size);
data.stream << top << recent;
FileWriteDescriptor file(_searchSuggestionsKey, _basePath);
file.writeEncrypted(data, _localKey);
}
void Account::readSearchSuggestions() {
if (_searchSuggestionsRead) {
return;
}
_searchSuggestionsRead = true;
if (!_searchSuggestionsKey) {
return;
}
FileReadDescriptor suggestions;
if (!ReadEncryptedFile(suggestions, _searchSuggestionsKey, _basePath, _localKey)) {
ClearKey(_searchSuggestionsKey, _basePath);
_searchSuggestionsKey = 0;
writeMapDelayed();
return;
}
auto top = QByteArray();
auto recent = QByteArray();
suggestions.stream >> top >> recent;
if (CheckStreamStatus(suggestions.stream)) {
_owner->session().topPeers().applyLocal(top);
//_owner->session().recentPeers().applyLocal(recent);
}
}
void Account::writeSelf() {
writeMapDelayed();
}

View file

@ -148,6 +148,11 @@ public:
void writeExportSettings(const Export::Settings &settings);
[[nodiscard]] Export::Settings readExportSettings();
void writeSearchSuggestionsDelayed();
void writeSearchSuggestionsIfNeeded();
void writeSearchSuggestions();
void readSearchSuggestions();
void writeSelf();
// Read self is special, it can't get session from account, because
@ -291,6 +296,7 @@ private:
FileKey _installedCustomEmojiKey = 0;
FileKey _featuredCustomEmojiKey = 0;
FileKey _archivedCustomEmojiKey = 0;
FileKey _searchSuggestionsKey = 0;
qint64 _cacheTotalSizeLimit = 0;
qint64 _cacheBigFileTotalSizeLimit = 0;
@ -301,11 +307,13 @@ private:
bool _trustedBotsRead = false;
bool _readingUserSettings = false;
bool _recentHashtagsAndBotsWereRead = false;
bool _searchSuggestionsRead = false;
int _oldMapVersion = 0;
base::Timer _writeMapTimer;
base::Timer _writeLocationsTimer;
base::Timer _writeSearchSuggestionsTimer;
bool _mapChanged = false;
bool _locationsChanged = false;