init
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled
This commit is contained in:
347
Telegram/SourceFiles/dialogs/dialogs_search_posts.cpp
Normal file
347
Telegram/SourceFiles/dialogs/dialogs_search_posts.cpp
Normal file
@@ -0,0 +1,347 @@
|
||||
/*
|
||||
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_search_posts.h"
|
||||
|
||||
#include "apiwrap.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_peer_values.h"
|
||||
#include "history/history.h"
|
||||
#include "main/main_session.h"
|
||||
|
||||
namespace Dialogs {
|
||||
namespace {
|
||||
|
||||
constexpr auto kQueryDelay = crl::time(500);
|
||||
constexpr auto kPerPage = 50;
|
||||
|
||||
[[nodiscard]] const QRegularExpression &SearchSplitter() {
|
||||
static const auto result = QRegularExpression(QString::fromLatin1(""
|
||||
"[\\s\\-\\+\\(\\)\\[\\]\\{\\}\\<\\>\\,\\.\\!\\_\\;\\\"\\'\\x0]"));
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
PostsSearch::PostsSearch(not_null<Main::Session*> session)
|
||||
: _session(session)
|
||||
, _api(&_session->api().instance())
|
||||
, _timer([=] { applyQuery(); })
|
||||
, _recheckTimer([=] { recheck(); }) {
|
||||
Data::AmPremiumValue(_session) | rpl::on_next([=] {
|
||||
maybePushPremiumUpdate();
|
||||
}, _lifetime);
|
||||
}
|
||||
|
||||
rpl::producer<PostsSearchState> PostsSearch::stateUpdates() const {
|
||||
return _stateUpdates.events();
|
||||
}
|
||||
|
||||
rpl::producer<PostsSearchState> PostsSearch::pagesUpdates() const {
|
||||
return _pagesUpdates.events();
|
||||
}
|
||||
|
||||
void PostsSearch::requestMore() {
|
||||
if (!_query) {
|
||||
return;
|
||||
}
|
||||
auto &entry = _entries[*_query];
|
||||
if (_queryPushed != *_query || !entry.pagesPushed) {
|
||||
return;
|
||||
} else if (entry.pagesPushed < entry.pages.size()) {
|
||||
_pagesUpdates.fire(PostsSearchState{
|
||||
.page = entry.pages[entry.pagesPushed++],
|
||||
.totalCount = entry.totalCount,
|
||||
});
|
||||
} else {
|
||||
requestSearch(*_query);
|
||||
}
|
||||
}
|
||||
|
||||
void PostsSearch::setQuery(const QString &query) {
|
||||
const auto words = TextUtilities::PrepareSearchWords(
|
||||
query,
|
||||
&SearchSplitter());
|
||||
const auto prepared = words.isEmpty() ? QString() : words.join(' ');
|
||||
if (_queryExact == query) {
|
||||
return;
|
||||
}
|
||||
_queryExact = query;
|
||||
_query = prepared;
|
||||
const auto i = _entries.find(prepared);
|
||||
if (i != end(_entries)) {
|
||||
pushStateUpdate(i->second);
|
||||
} else if (prepared.isEmpty()) {
|
||||
applyQuery();
|
||||
} else {
|
||||
_timer.callOnce(kQueryDelay);
|
||||
}
|
||||
}
|
||||
|
||||
int PostsSearch::setAllowedStars(int stars) {
|
||||
if (!_query) {
|
||||
return 0;
|
||||
} else if (_floodState) {
|
||||
if (_floodState->freeSearchesLeft > 0) {
|
||||
stars = 0;
|
||||
} else if (_floodState->nextFreeSearchTime > 0
|
||||
&& _floodState->nextFreeSearchTime <= base::unixtime::now()) {
|
||||
stars = 0;
|
||||
} else {
|
||||
stars = std::min(int(_floodState->starsPerPaidSearch), stars);
|
||||
}
|
||||
}
|
||||
_entries[*_query].allowedStars = stars;
|
||||
requestSearch(*_query);
|
||||
return stars;
|
||||
}
|
||||
|
||||
void PostsSearch::pushStateUpdate(const Entry &entry) {
|
||||
Expects(_query.has_value());
|
||||
|
||||
const auto initial = (_queryPushed != *_query);
|
||||
if (initial) {
|
||||
_queryPushed = *_query;
|
||||
entry.pagesPushed = 0;
|
||||
} else if (entry.pagesPushed > 0) {
|
||||
if (entry.pagesPushed < entry.pages.size()) {
|
||||
_pagesUpdates.fire(PostsSearchState{
|
||||
.page = entry.pages[entry.pagesPushed++],
|
||||
.totalCount = entry.totalCount,
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
const auto empty = entry.pages.empty()
|
||||
|| (entry.pages.size() == 1 && entry.pages.front().empty());
|
||||
if (!empty || (entry.loaded && !_query->isEmpty())) {
|
||||
if (!entry.pages.empty()) {
|
||||
++entry.pagesPushed;
|
||||
}
|
||||
_stateUpdates.fire(PostsSearchState{
|
||||
.page = (entry.pages.empty()
|
||||
? std::vector<not_null<HistoryItem*>>()
|
||||
: entry.pages.front()),
|
||||
.totalCount = entry.totalCount,
|
||||
});
|
||||
} else if (entry.checkId || entry.searchId) {
|
||||
_stateUpdates.fire(PostsSearchState{
|
||||
.loading = true,
|
||||
});
|
||||
} else {
|
||||
Assert(_floodState.has_value());
|
||||
auto copy = _floodState;
|
||||
copy->query = *_queryExact;
|
||||
copy->needsPremium = !_session->premium();
|
||||
_stateUpdates.fire(PostsSearchState{
|
||||
.intro = std::move(copy),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void PostsSearch::maybePushPremiumUpdate() {
|
||||
if (!_floodState || !_query) {
|
||||
return;
|
||||
}
|
||||
auto &entry = _entries[*_query];
|
||||
if (!entry.pages.empty()
|
||||
|| entry.loaded
|
||||
|| entry.checkId
|
||||
|| entry.searchId) {
|
||||
return;
|
||||
}
|
||||
pushStateUpdate(entry);
|
||||
}
|
||||
|
||||
void PostsSearch::applyQuery() {
|
||||
Expects(_query.has_value());
|
||||
|
||||
_timer.cancel();
|
||||
if (_query->isEmpty()) {
|
||||
requestSearch(QString());
|
||||
} else {
|
||||
requestState(*_query);
|
||||
}
|
||||
}
|
||||
|
||||
void PostsSearch::requestSearch(const QString &query) {
|
||||
auto &entry = _entries[query];
|
||||
if (entry.searchId || entry.loaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto useStars = entry.allowedStars;
|
||||
entry.allowedStars = 0;
|
||||
|
||||
using Flag = MTPchannels_SearchPosts::Flag;
|
||||
entry.searchId = _api.request(MTPchannels_SearchPosts(
|
||||
MTP_flags(Flag::f_query
|
||||
| (useStars ? Flag::f_allow_paid_stars : Flag())),
|
||||
MTP_string(), // hashtag
|
||||
MTP_string(query),
|
||||
MTP_int(entry.offsetRate),
|
||||
(entry.offsetPeer ? entry.offsetPeer->input() : MTP_inputPeerEmpty()),
|
||||
MTP_int(entry.offsetId),
|
||||
MTP_int(kPerPage),
|
||||
MTP_long(useStars)
|
||||
)).done([=](const MTPmessages_Messages &result) {
|
||||
auto &entry = _entries[query];
|
||||
entry.searchId = 0;
|
||||
|
||||
const auto initial = !entry.offsetId;
|
||||
const auto owner = &_session->data();
|
||||
const auto processList = [&](const MTPVector<MTPMessage> &messages) {
|
||||
auto result = std::vector<not_null<HistoryItem*>>();
|
||||
for (const auto &message : messages.v) {
|
||||
const auto msgId = IdFromMessage(message);
|
||||
const auto peerId = PeerFromMessage(message);
|
||||
const auto lastDate = DateFromMessage(message);
|
||||
if (const auto peer = owner->peerLoaded(peerId)) {
|
||||
if (lastDate) {
|
||||
const auto item = owner->addNewMessage(
|
||||
message,
|
||||
MessageFlags(),
|
||||
NewMessageType::Existing);
|
||||
result.push_back(item);
|
||||
}
|
||||
entry.offsetPeer = peer;
|
||||
} else {
|
||||
LOG(("API Error: a search results with not loaded peer %1"
|
||||
).arg(peerId.value));
|
||||
}
|
||||
entry.offsetId = msgId;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
auto totalCount = 0;
|
||||
auto messages = result.match([&](const MTPDmessages_messages &data) {
|
||||
owner->processUsers(data.vusers());
|
||||
owner->processChats(data.vchats());
|
||||
entry.loaded = true;
|
||||
auto list = processList(data.vmessages());
|
||||
totalCount = list.size();
|
||||
return list;
|
||||
}, [&](const MTPDmessages_messagesSlice &data) {
|
||||
owner->processUsers(data.vusers());
|
||||
owner->processChats(data.vchats());
|
||||
auto list = processList(data.vmessages());
|
||||
const auto nextRate = data.vnext_rate();
|
||||
const auto rateUpdated = nextRate
|
||||
&& (nextRate->v != entry.offsetRate);
|
||||
const auto finished = list.empty();
|
||||
if (rateUpdated) {
|
||||
entry.offsetRate = nextRate->v;
|
||||
}
|
||||
if (finished) {
|
||||
entry.loaded = true;
|
||||
}
|
||||
totalCount = data.vcount().v;
|
||||
if (const auto flood = data.vsearch_flood()) {
|
||||
setFloodStateFrom(flood->data());
|
||||
}
|
||||
return list;
|
||||
}, [&](const MTPDmessages_channelMessages &data) {
|
||||
LOG(("API Error: "
|
||||
"received messages.channelMessages when no channel "
|
||||
"was passed! (PostsSearch::performSearch)"));
|
||||
owner->processUsers(data.vusers());
|
||||
owner->processChats(data.vchats());
|
||||
auto list = processList(data.vmessages());
|
||||
if (list.empty()) {
|
||||
entry.loaded = true;
|
||||
}
|
||||
totalCount = data.vcount().v;
|
||||
return list;
|
||||
}, [&](const MTPDmessages_messagesNotModified &) {
|
||||
LOG(("API Error: received messages.messagesNotModified! "
|
||||
"(PostsSearch::performSearch)"));
|
||||
entry.loaded = true;
|
||||
return std::vector<not_null<HistoryItem*>>();
|
||||
});
|
||||
if (initial) {
|
||||
entry.pages.clear();
|
||||
}
|
||||
entry.pages.push_back(std::move(messages));
|
||||
const auto count = int(ranges::accumulate(
|
||||
entry.pages,
|
||||
size_type(),
|
||||
ranges::plus(),
|
||||
&std::vector<not_null<HistoryItem*>>::size));
|
||||
const auto full = entry.loaded ? count : std::max(count, totalCount);
|
||||
entry.totalCount = full;
|
||||
if (_query == query) {
|
||||
pushStateUpdate(entry);
|
||||
}
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
auto &entry = _entries[query];
|
||||
entry.searchId = 0;
|
||||
|
||||
const auto initial = !entry.offsetId;
|
||||
const auto &type = error.type();
|
||||
if (initial && type.startsWith(u"FLOOD_WAIT_"_q)) {
|
||||
requestState(query);
|
||||
} else {
|
||||
entry.loaded = true;
|
||||
}
|
||||
}).handleFloodErrors().send();
|
||||
}
|
||||
|
||||
void PostsSearch::setFloodStateFrom(const MTPDsearchPostsFlood &data) {
|
||||
_recheckTimer.cancel();
|
||||
const auto left = std::max(data.vremains().v, 0);
|
||||
const auto next = data.vwait_till().value_or_empty();
|
||||
if (!left && next > 0) {
|
||||
const auto now = base::unixtime::now();
|
||||
const auto delay = std::clamp(next - now, 1, 86401);
|
||||
_recheckTimer.callOnce(delay * crl::time(1000));
|
||||
}
|
||||
_floodState = PostsSearchIntroState{
|
||||
.freeSearchesPerDay = data.vtotal_daily().v,
|
||||
.freeSearchesLeft = left,
|
||||
.nextFreeSearchTime = next,
|
||||
.starsPerPaidSearch = uint32(data.vstars_amount().v),
|
||||
};
|
||||
}
|
||||
|
||||
void PostsSearch::recheck() {
|
||||
requestState(*_query, true);
|
||||
}
|
||||
|
||||
void PostsSearch::requestState(const QString &query, bool force) {
|
||||
auto &entry = _entries[query];
|
||||
if (force) {
|
||||
_api.request(base::take(entry.checkId)).cancel();
|
||||
} else if (entry.checkId || entry.loaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
using Flag = MTPchannels_CheckSearchPostsFlood::Flag;
|
||||
entry.checkId = _api.request(MTPchannels_CheckSearchPostsFlood(
|
||||
MTP_flags(Flag::f_query),
|
||||
MTP_string(query)
|
||||
)).done([=](const MTPSearchPostsFlood &result) {
|
||||
auto &entry = _entries[query];
|
||||
entry.checkId = 0;
|
||||
|
||||
const auto &data = result.data();
|
||||
setFloodStateFrom(data);
|
||||
if (data.is_query_is_free()) {
|
||||
if (!entry.loaded) {
|
||||
requestSearch(query);
|
||||
}
|
||||
} else if (_query == query) {
|
||||
pushStateUpdate(entry);
|
||||
}
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
auto &entry = _entries[query];
|
||||
entry.checkId = 0;
|
||||
entry.loaded = true;
|
||||
}).handleFloodErrors().send();
|
||||
}
|
||||
|
||||
} // namespace Dialogs
|
||||
Reference in New Issue
Block a user