Add cache for global media search requests.

This commit is contained in:
John Preston 2024-12-17 21:09:46 +04:00
parent d59eb8e731
commit 3f0d687656
9 changed files with 119 additions and 110 deletions

View file

@ -1427,6 +1427,9 @@ void Widget::updateSuggestions(anim::type animated) {
controller(), controller(),
TopPeersContent(&session()), TopPeersContent(&session()),
RecentPeersContent(&session())); RecentPeersContent(&session()));
_suggestions->clearSearchQueryRequests() | rpl::start_with_next([=] {
setSearchQuery(QString());
}, _suggestions->lifetime());
_searchSuggestionsLocked = false; _searchSuggestionsLocked = false;
rpl::merge( rpl::merge(
@ -2934,7 +2937,11 @@ void Widget::updateCancelSearch() {
QString Widget::validateSearchQuery() { QString Widget::validateSearchQuery() {
const auto query = currentSearchQuery(); const auto query = currentSearchQuery();
if (_searchState.tab == ChatSearchTab::PublicPosts) { if (!_subsectionTopBar
&& _suggestions
&& _suggestions->consumeSearchQuery(query)) {
return QString();
} else if (_searchState.tab == ChatSearchTab::PublicPosts) {
if (_searchHashOrCashtag == HashOrCashtag::None) { if (_searchHashOrCashtag == HashOrCashtag::None) {
_searchHashOrCashtag = HashOrCashtag::Hashtag; _searchHashOrCashtag = HashOrCashtag::Hashtag;
} }
@ -3932,9 +3939,18 @@ void Widget::setSearchQuery(const QString &query, int cursorPosition) {
} }
bool Widget::cancelSearch(CancelSearchOptions options) { bool Widget::cancelSearch(CancelSearchOptions options) {
const auto clearingSuggestionsQuery = _suggestions
&& _suggestions->consumeSearchQuery(QString());
if (clearingSuggestionsQuery) {
setSearchQuery(QString());
if (!options.forceFullCancel) {
return true;
}
}
cancelSearchRequest(); cancelSearchRequest();
auto updatedState = _searchState; auto updatedState = _searchState;
const auto clearingQuery = !updatedState.query.isEmpty(); const auto clearingQuery = clearingSuggestionsQuery
|| !updatedState.query.isEmpty();
const auto forceFullCancel = options.forceFullCancel; const auto forceFullCancel = options.forceFullCancel;
auto clearingInChat = (forceFullCancel || !clearingQuery) auto clearingInChat = (forceFullCancel || !clearingQuery)
&& (updatedState.inChat && (updatedState.inChat

View file

@ -48,6 +48,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/delayed_activation.h" #include "ui/delayed_activation.h"
#include "ui/dynamic_thumbnails.h" #include "ui/dynamic_thumbnails.h"
#include "ui/painter.h" #include "ui/painter.h"
#include "ui/search_field_controller.h"
#include "ui/unread_badge_paint.h" #include "ui/unread_badge_paint.h"
#include "ui/ui_utility.h" #include "ui/ui_utility.h"
#include "window/window_separate_id.h" #include "window/window_separate_id.h"
@ -66,6 +67,7 @@ constexpr auto kCollapsedChannelsCount = 5;
constexpr auto kProbablyMaxChannels = 1000; constexpr auto kProbablyMaxChannels = 1000;
constexpr auto kCollapsedAppsCount = 5; constexpr auto kCollapsedAppsCount = 5;
constexpr auto kProbablyMaxApps = 100; constexpr auto kProbablyMaxApps = 100;
constexpr auto kSearchQueryDelay = crl::time(900);
class RecentRow final : public PeerListRow { class RecentRow final : public PeerListRow {
public: public:
@ -1335,7 +1337,8 @@ Suggestions::Suggestions(
, _appsContent( , _appsContent(
_appsScroll->setOwnedWidget(object_ptr<Ui::VerticalLayout>(this))) _appsScroll->setOwnedWidget(object_ptr<Ui::VerticalLayout>(this)))
, _recentApps(setupRecentApps()) , _recentApps(setupRecentApps())
, _popularApps(setupPopularApps()) { , _popularApps(setupPopularApps())
, _searchQueryTimer([=] { applySearchQuery(); }) {
setupTabs(); setupTabs();
setupChats(); setupChats();
setupChannels(); setupChannels();
@ -1754,6 +1757,43 @@ void Suggestions::chooseRow() {
} }
} }
bool Suggestions::consumeSearchQuery(const QString &query) {
using Type = MediaType;
const auto key = _key.current();
const auto tab = key.tab;
const auto type = (key.tab == Tab::Media) ? key.mediaType : Type::kCount;
if (tab != Tab::Downloads
&& type != Type::File
&& type != Type::Link
&& type != Type::MusicFile) {
return false;
} else if (_searchQuery == query) {
return false;
}
_searchQuery = query;
_persist = !_searchQuery.isEmpty();
if (query.isEmpty() || tab == Tab::Downloads) {
_searchQueryTimer.cancel();
applySearchQuery();
} else {
_searchQueryTimer.callOnce(kSearchQueryDelay);
}
return true;
}
void Suggestions::applySearchQuery() {
const auto key = _key.current();
const auto controller = _mediaLists[key].wrap->controller();
const auto search = controller->searchFieldController();
if (search->query() != _searchQuery) {
search->setQuery(_searchQuery);
}
}
rpl::producer<> Suggestions::clearSearchQueryRequests() const {
return _clearSearchQueryRequests.events();
}
Data::Thread *Suggestions::updateFromParentDrag(QPoint globalPosition) { Data::Thread *Suggestions::updateFromParentDrag(QPoint globalPosition) {
switch (_key.current().tab) { switch (_key.current().tab) {
case Tab::Chats: return updateFromChatsDrag(globalPosition); case Tab::Chats: return updateFromChatsDrag(globalPosition);
@ -1825,8 +1865,10 @@ void Suggestions::switchTab(Key key) {
if (was == key) { if (was == key) {
return; return;
} }
consumeSearchQuery(QString());
_key = key; _key = key;
_persist = false; _persist = false;
_clearSearchQueryRequests.fire({});
if (_tabs->isHidden()) { if (_tabs->isHidden()) {
return; return;
} }

View file

@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#pragma once #pragma once
#include "base/object_ptr.h" #include "base/object_ptr.h"
#include "base/timer.h"
#include "dialogs/ui/top_peers_strip.h" #include "dialogs/ui/top_peers_strip.h"
#include "ui/effects/animations.h" #include "ui/effects/animations.h"
#include "ui/rp_widget.h" #include "ui/rp_widget.h"
@ -64,6 +65,9 @@ public:
void selectJump(Qt::Key direction, int pageSize = 0); void selectJump(Qt::Key direction, int pageSize = 0);
void chooseRow(); void chooseRow();
bool consumeSearchQuery(const QString &query);
[[nodiscard]] rpl::producer<> clearSearchQueryRequests() const;
[[nodiscard]] Data::Thread *updateFromParentDrag(QPoint globalPosition); [[nodiscard]] Data::Thread *updateFromParentDrag(QPoint globalPosition);
void dragLeft(); void dragLeft();
@ -193,6 +197,7 @@ private:
void handlePressForChatPreview(PeerId id, Fn<void(bool)> callback); void handlePressForChatPreview(PeerId id, Fn<void(bool)> callback);
void updateControlsGeometry(); void updateControlsGeometry();
void applySearchQuery();
const not_null<Window::SessionController*> _controller; const not_null<Window::SessionController*> _controller;
@ -231,6 +236,9 @@ private:
const std::unique_ptr<ObjectList> _popularApps; const std::unique_ptr<ObjectList> _popularApps;
base::flat_map<Key, MediaList> _mediaLists; base::flat_map<Key, MediaList> _mediaLists;
rpl::event_stream<> _clearSearchQueryRequests;
QString _searchQuery;
base::Timer _searchQueryTimer;
Ui::Animations::Simple _shownAnimation; Ui::Animations::Simple _shownAnimation;
Fn<void()> _showFinished; Fn<void()> _showFinished;

View file

@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "info/global_media/info_global_media_provider.h" #include "info/global_media/info_global_media_provider.h"
#include "info/global_media/info_global_media_widget.h" #include "info/global_media/info_global_media_widget.h"
#include "info/media/info_media_empty_widget.h"
#include "info/media/info_media_list_widget.h" #include "info/media/info_media_list_widget.h"
#include "info/info_controller.h" #include "info/info_controller.h"
#include "ui/widgets/labels.h" #include "ui/widgets/labels.h"
@ -18,75 +19,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Info::GlobalMedia { namespace Info::GlobalMedia {
class EmptyWidget : public Ui::RpWidget {
public:
EmptyWidget(QWidget *parent);
void setFullHeight(rpl::producer<int> fullHeightValue);
void setSearchQuery(const QString &query);
protected:
int resizeGetHeight(int newWidth) override;
void paintEvent(QPaintEvent *e) override;
private:
object_ptr<Ui::FlatLabel> _text;
int _height = 0;
};
EmptyWidget::EmptyWidget(QWidget *parent)
: RpWidget(parent)
, _text(this, st::infoEmptyLabel) {
}
void EmptyWidget::setFullHeight(rpl::producer<int> fullHeightValue) {
std::move(
fullHeightValue
) | rpl::start_with_next([this](int fullHeight) {
// Make icon center be on 1/3 height.
auto iconCenter = fullHeight / 3;
auto iconHeight = st::infoEmptyFile.height();
auto iconTop = iconCenter - iconHeight / 2;
_height = iconTop + st::infoEmptyIconTop;
resizeToWidth(width());
}, lifetime());
}
void EmptyWidget::setSearchQuery(const QString &query) {
_text->setText(query.isEmpty()
? tr::lng_media_file_empty(tr::now)
: tr::lng_media_file_empty_search(tr::now));
resizeToWidth(width());
}
int EmptyWidget::resizeGetHeight(int newWidth) {
auto labelTop = _height - st::infoEmptyLabelTop;
auto labelWidth = newWidth - 2 * st::infoEmptyLabelSkip;
_text->resizeToNaturalWidth(labelWidth);
auto labelLeft = (newWidth - _text->width()) / 2;
_text->moveToLeft(labelLeft, labelTop, newWidth);
update();
return _height;
}
void EmptyWidget::paintEvent(QPaintEvent *e) {
auto p = QPainter(this);
const auto iconLeft = (width() - st::infoEmptyFile.width()) / 2;
const auto iconTop = height() - st::infoEmptyIconTop;
st::infoEmptyFile.paint(p, iconLeft, iconTop, width());
}
InnerWidget::InnerWidget( InnerWidget::InnerWidget(
QWidget *parent, QWidget *parent,
not_null<Controller*> controller) not_null<Controller*> controller)
: RpWidget(parent) : RpWidget(parent)
, _controller(controller) , _controller(controller)
, _empty(this) { , _empty(this) {
_empty->setType(type());
_empty->heightValue( _empty->heightValue(
) | rpl::start_with_next( ) | rpl::start_with_next(
[this] { refreshHeight(); }, [this] { refreshHeight(); },

View file

@ -21,16 +21,17 @@ enum class SharedMediaType : signed char;
} // namespace Storage } // namespace Storage
namespace Info { namespace Info {
class Controller; class Controller;
struct SelectedItems; struct SelectedItems;
enum class SelectionAction; enum class SelectionAction;
} // namespace Info
namespace Media { namespace Info::Media {
class ListWidget; class ListWidget;
} // namespace Media class EmptyWidget;
} // namespace Info::Media
namespace GlobalMedia { namespace Info::GlobalMedia {
class Memento; class Memento;
class EmptyWidget; class EmptyWidget;
@ -71,7 +72,7 @@ private:
const not_null<Controller*> _controller; const not_null<Controller*> _controller;
object_ptr<Media::ListWidget> _list = { nullptr }; object_ptr<Media::ListWidget> _list = { nullptr };
object_ptr<EmptyWidget> _empty; object_ptr<Media::EmptyWidget> _empty;
bool _inResize = false; bool _inResize = false;
@ -81,5 +82,4 @@ private:
}; };
} // namespace GlobalMedia } // namespace Info::GlobalMedia
} // namespace Info

View file

@ -227,18 +227,6 @@ void Provider::checkPreload(
} }
} }
void Provider::applyListQuery(const QString &query) {
if (_totalListQuery == query) {
return;
}
_totalListQuery = query;
_totalList.clear();
_totalOffsetPosition = Data::MessagePosition();
_totalOffsetRate = 0;
_totalFullCount = 0;
_totalLoaded = false;
}
rpl::producer<GlobalMediaSlice> Provider::source( rpl::producer<GlobalMediaSlice> Provider::source(
Type type, Type type,
Data::MessagePosition aroundId, Data::MessagePosition aroundId,
@ -247,7 +235,7 @@ rpl::producer<GlobalMediaSlice> Provider::source(
int limitAfter) { int limitAfter) {
Expects(_type == type); Expects(_type == type);
applyListQuery(query); _totalListQuery = query;
return [=](auto consumer) { return [=](auto consumer) {
auto lifetime = rpl::lifetime(); auto lifetime = rpl::lifetime();
const auto session = &_controller->session(); const auto session = &_controller->session();
@ -268,7 +256,7 @@ rpl::producer<GlobalMediaSlice> Provider::source(
state->pushAndLoadMore = [=] { state->pushAndLoadMore = [=] {
auto result = fillRequest(aroundId, limitBefore, limitAfter); auto result = fillRequest(aroundId, limitBefore, limitAfter);
consumer.put_next(std::move(result.slice)); consumer.put_next(std::move(result.slice));
if (!_totalLoaded && result.notEnough) { if (!currentList()->loaded && result.notEnough) {
state->requestId = requestMore(state->pushAndLoadMore); state->requestId = requestMore(state->pushAndLoadMore);
} }
}; };
@ -280,30 +268,32 @@ rpl::producer<GlobalMediaSlice> Provider::source(
mtpRequestId Provider::requestMore(Fn<void()> loaded) { mtpRequestId Provider::requestMore(Fn<void()> loaded) {
const auto done = [=](const Api::GlobalMediaResult &result) { const auto done = [=](const Api::GlobalMediaResult &result) {
const auto list = currentList();
if (result.messageIds.empty()) { if (result.messageIds.empty()) {
_totalLoaded = true; list->loaded = true;
_totalFullCount = _totalList.size(); list->fullCount = list->list.size();
} else { } else {
_totalList.reserve(_totalList.size() + result.messageIds.size()); list->list.reserve(list->list.size() + result.messageIds.size());
_totalFullCount = result.fullCount; list->fullCount = result.fullCount;
for (const auto &position : result.messageIds) { for (const auto &position : result.messageIds) {
_seenIds.emplace(position.fullId); _seenIds.emplace(position.fullId);
_totalOffsetPosition = position; list->offsetPosition = position;
_totalList.push_back(position); list->list.push_back(position);
} }
} }
if (!result.offsetRate) { if (!result.offsetRate) {
_totalLoaded = true; list->loaded = true;
} else { } else {
_totalOffsetRate = result.offsetRate; list->offsetRate = result.offsetRate;
} }
loaded(); loaded();
}; };
const auto list = currentList();
return _controller->session().api().requestGlobalMedia( return _controller->session().api().requestGlobalMedia(
_type, _type,
_totalListQuery, _totalListQuery,
_totalOffsetRate, list->offsetRate,
_totalOffsetPosition, list->offsetPosition,
done); done);
} }
@ -311,24 +301,25 @@ Provider::FillResult Provider::fillRequest(
Data::MessagePosition aroundId, Data::MessagePosition aroundId,
int limitBefore, int limitBefore,
int limitAfter) { int limitAfter) {
const auto list = currentList();
const auto i = ranges::lower_bound( const auto i = ranges::lower_bound(
_totalList, list->list,
aroundId, aroundId,
std::greater<>()); std::greater<>());
const auto hasAfter = int(i - begin(_totalList)); const auto hasAfter = int(i - begin(list->list));
const auto hasBefore = int(end(_totalList) - i); const auto hasBefore = int(end(list->list) - i);
const auto takeAfter = std::min(limitAfter, hasAfter); const auto takeAfter = std::min(limitAfter, hasAfter);
const auto takeBefore = std::min(limitBefore, hasBefore); const auto takeBefore = std::min(limitBefore, hasBefore);
auto list = std::vector<Data::MessagePosition>{ auto messages = std::vector<Data::MessagePosition>{
i - takeAfter, i - takeAfter,
i + takeBefore, i + takeBefore,
}; };
return FillResult{ return FillResult{
.slice = GlobalMediaSlice( .slice = GlobalMediaSlice(
GlobalMediaKey{ aroundId }, GlobalMediaKey{ aroundId },
std::move(list), std::move(messages),
((!_totalList.empty() || _totalLoaded) ((!list->list.empty() || list->loaded)
? _totalFullCount ? list->fullCount
: std::optional<int>()), : std::optional<int>()),
hasAfter - takeAfter), hasAfter - takeAfter),
.notEnough = (takeBefore < limitBefore), .notEnough = (takeBefore < limitBefore),
@ -400,6 +391,10 @@ void Provider::clearStaleLayouts() {
} }
} }
Provider::List *Provider::currentList() {
return &_totalLists[_totalListQuery];
}
rpl::producer<not_null<Media::BaseLayout*>> Provider::layoutRemoved() { rpl::producer<not_null<Media::BaseLayout*>> Provider::layoutRemoved() {
return _layoutRemoved.events(); return _layoutRemoved.events();
} }

View file

@ -126,6 +126,13 @@ private:
GlobalMediaSlice slice; GlobalMediaSlice slice;
bool notEnough = false; bool notEnough = false;
}; };
struct List {
std::vector<Data::MessagePosition> list;
Data::MessagePosition offsetPosition;
int32 offsetRate = 0;
int fullCount = 0;
bool loaded = false;
};
bool sectionHasFloatingHeader() override; bool sectionHasFloatingHeader() override;
QString sectionTitle(not_null<const BaseLayout*> item) override; QString sectionTitle(not_null<const BaseLayout*> item) override;
@ -154,7 +161,7 @@ private:
void itemRemoved(not_null<const HistoryItem*> item); void itemRemoved(not_null<const HistoryItem*> item);
void markLayoutsStale(); void markLayoutsStale();
void clearStaleLayouts(); void clearStaleLayouts();
void applyListQuery(const QString &query); [[nodiscard]] List *currentList();
[[nodiscard]] FillResult fillRequest( [[nodiscard]] FillResult fillRequest(
Data::MessagePosition aroundId, Data::MessagePosition aroundId,
int limitBefore, int limitBefore,
@ -174,11 +181,7 @@ private:
rpl::event_stream<> _refreshed; rpl::event_stream<> _refreshed;
QString _totalListQuery; QString _totalListQuery;
std::vector<Data::MessagePosition> _totalList; base::flat_map<QString, List> _totalLists;
Data::MessagePosition _totalOffsetPosition;
int32 _totalOffsetRate = 0;
int _totalFullCount = 0;
bool _totalLoaded = false;
rpl::lifetime _lifetime; rpl::lifetime _lifetime;
rpl::lifetime _viewerLifetime; rpl::lifetime _viewerLifetime;

View file

@ -92,6 +92,10 @@ rpl::producer<QString> SearchFieldController::queryChanges() const {
return _query.changes(); return _query.changes();
} }
void SearchFieldController::setQuery(const QString &query) {
_query = query;
}
base::unique_qptr<Ui::InputField> SearchFieldController::createField( base::unique_qptr<Ui::InputField> SearchFieldController::createField(
QWidget *parent, QWidget *parent,
const style::InputField &st) { const style::InputField &st) {

View file

@ -40,6 +40,8 @@ public:
rpl::producer<QString> queryValue() const; rpl::producer<QString> queryValue() const;
rpl::producer<QString> queryChanges() const; rpl::producer<QString> queryChanges() const;
void setQuery(const QString &query);
rpl::lifetime &lifetime() { rpl::lifetime &lifetime() {
return _lifetime; return _lifetime;
} }