Cache media search results until empty query.

This commit is contained in:
John Preston 2017-10-31 11:03:18 +04:00
parent eb2719fad1
commit c9152b0b3a
5 changed files with 281 additions and 67 deletions

View File

@ -0,0 +1,155 @@
/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#pragma once
namespace base {
namespace details {
template <
typename Type,
typename Operator,
typename = decltype(Operator{}(
std::declval<Type>(),
std::declval<Type>()))>
char test_operator(const Type &, const Operator &);
int test_operator(...);
template <typename Type, template <typename> typename Operator>
struct has_operator
: std::bool_constant<
sizeof(test_operator(
std::declval<Type>(),
std::declval<Operator<Type>>()
)) == sizeof(char)> {
};
template <
typename Type,
template <typename> typename Operator>
constexpr bool has_operator_v
= has_operator<Type, Operator>::value;
template <typename Type>
constexpr bool has_less_v = has_operator_v<Type, std::less>;
template <typename Type>
constexpr bool has_greater_v = has_operator_v<Type, std::greater>;
template <typename Type>
constexpr bool has_less_equal_v = has_operator_v<Type, std::less_equal>;
template <typename Type>
constexpr bool has_greater_equal_v = has_operator_v<Type, std::greater_equal>;
template <typename Type>
constexpr bool has_equal_to_v = has_operator_v<Type, std::equal_to>;
template <typename Type>
constexpr bool has_not_equal_to_v = has_operator_v<Type, std::not_equal_to>;
} // namespace details
} // namespace base
template <
typename ValueType,
typename Helper = decltype(
value_ordering_helper(std::declval<ValueType>()))>
inline auto operator<(const ValueType &a, const ValueType &b)
-> std::enable_if_t<base::details::has_less_v<Helper>, bool> {
return value_ordering_helper(a) < value_ordering_helper(b);
}
template <
typename ValueType,
typename Helper = decltype(
value_ordering_helper(std::declval<ValueType>()))>
inline auto operator>(const ValueType &a, const ValueType &b)
-> std::enable_if_t<
base::details::has_greater_v<Helper>
|| base::details::has_less_v<Helper>,
bool
> {
if constexpr (base::details::has_greater_v<Helper>) {
return value_ordering_helper(a) > value_ordering_helper(b);
} else {
return value_ordering_helper(b) < value_ordering_helper(a);
}
}
template <
typename ValueType,
typename Helper = decltype(
value_ordering_helper(std::declval<ValueType>()))>
inline auto operator<=(const ValueType &a, const ValueType &b)
-> std::enable_if_t<
base::details::has_less_equal_v<Helper>
|| base::details::has_less_v<Helper>,
bool
> {
if constexpr (base::details::has_less_equal_v<Helper>) {
return value_ordering_helper(a) <= value_ordering_helper(b);
} else {
return !(value_ordering_helper(b) < value_ordering_helper(a));
}
}
template <
typename ValueType,
typename Helper = decltype(
value_ordering_helper(std::declval<ValueType>()))>
inline auto operator>=(const ValueType &a, const ValueType &b)
-> std::enable_if_t<
base::details::has_greater_equal_v<Helper>
|| base::details::has_less_v<Helper>,
bool
> {
if constexpr (base::details::has_greater_equal_v<Helper>) {
return value_ordering_helper(a) >= value_ordering_helper(b);
} else {
return !(value_ordering_helper(a) < value_ordering_helper(b));
}
}
template <
typename ValueType,
typename Helper = decltype(
value_ordering_helper(std::declval<ValueType>()))>
inline auto operator==(const ValueType &a, const ValueType &b)
-> std::enable_if_t<base::details::has_equal_to_v<Helper>, bool> {
return value_ordering_helper(a) == value_ordering_helper(b);
}
template <
typename ValueType,
typename Helper = decltype(
value_ordering_helper(std::declval<ValueType>()))>
inline auto operator!=(const ValueType &a, const ValueType &b)
-> std::enable_if_t<
base::details::has_not_equal_to_v<Helper>
|| base::details::has_equal_to_v<Helper>,
bool
> {
if constexpr (base::details::has_not_equal_to_v<Helper>) {
return value_ordering_helper(a) != value_ordering_helper(b);
} else {
return !(value_ordering_helper(a) == value_ordering_helper(b));
}
}

View File

@ -169,19 +169,39 @@ SearchResult ParseSearchResult(
return result;
}
SingleSearchController::SingleSearchController(const Query &query)
: _query(query)
, _peerData(App::peer(query.peerId))
, _migratedData(query.migratedPeerId
SearchController::CacheEntry::CacheEntry(const Query &query)
: peerData(App::peer(query.peerId))
, migratedData(query.migratedPeerId
? base::make_optional(Data(App::peer(query.migratedPeerId)))
: base::none) {
}
rpl::producer<SparseIdsMergedSlice> SingleSearchController::idsSlice(
bool SearchController::hasInCache(const Query &query) const {
return query.query.isEmpty() || _cache.contains(query);
}
void SearchController::setQuery(const Query &query) {
if (query.query.isEmpty()) {
_cache.clear();
_current = _cache.end();
} else {
_current = _cache.find(query);
}
if (_current == _cache.end()) {
_current = _cache.emplace(
query,
std::make_unique<CacheEntry>(query)).first;
}
}
rpl::producer<SparseIdsMergedSlice> SearchController::idsSlice(
SparseIdsMergedSlice::UniversalMsgId aroundId,
int limitBefore,
int limitAfter) {
auto createSimpleViewer = [this](
Expects(_current != _cache.cend());
auto query = (const Query&)_current->first;
auto createSimpleViewer = [=](
PeerId peerId,
SparseIdsSlice::Key simpleKey,
int limitBefore,
@ -189,34 +209,41 @@ rpl::producer<SparseIdsMergedSlice> SingleSearchController::idsSlice(
return simpleIdsSlice(
peerId,
simpleKey,
query,
limitBefore,
limitAfter);
};
return SparseIdsMergedSlice::CreateViewer(
SparseIdsMergedSlice::Key(
_query.peerId,
_query.migratedPeerId,
query.peerId,
query.migratedPeerId,
aroundId),
limitBefore,
limitAfter,
std::move(createSimpleViewer));
}
rpl::producer<SparseIdsSlice> SingleSearchController::simpleIdsSlice(
rpl::producer<SparseIdsSlice> SearchController::simpleIdsSlice(
PeerId peerId,
MsgId aroundId,
const Query &query,
int limitBefore,
int limitAfter) {
Expects(peerId != 0);
Expects(IsServerMsgId(aroundId) || (aroundId == 0));
Expects((aroundId != 0)
|| (limitBefore == 0 && limitAfter == 0));
Expects((_query.peerId == peerId)
|| (_query.migratedPeerId == peerId));
Expects((query.peerId == peerId)
|| (query.migratedPeerId == peerId));
auto listData = (peerId == _query.peerId)
? &_peerData
: &*_migratedData;
auto it = _cache.find(query);
if (it == _cache.end()) {
return [=](auto) { return rpl::lifetime(); };
}
auto listData = (peerId == query.peerId)
? &it->second->peerData
: &*it->second->migratedData;
return [=](auto consumer) {
auto lifetime = rpl::lifetime();
auto builder = lifetime.make_state<SparseIdsSliceBuilder>(
@ -226,7 +253,7 @@ rpl::producer<SparseIdsSlice> SingleSearchController::simpleIdsSlice(
builder->insufficientAround()
| rpl::start_with_next([=](
const SparseIdsSliceBuilder::AroundData &data) {
requestMore(data, listData);
requestMore(data, query, listData);
}, lifetime);
auto pushNextSnapshot = [=] {
@ -255,28 +282,41 @@ rpl::producer<SparseIdsSlice> SingleSearchController::simpleIdsSlice(
| rpl::filter([=] { return builder->removeAll(); })
| rpl::start_with_next(pushNextSnapshot, lifetime);
builder->checkInsufficient();
using Result = Storage::SparseIdsListResult;
listData->list.query(Storage::SparseIdsListQuery(
aroundId,
limitBefore,
limitAfter))
| rpl::filter([=](const Result &result) {
return builder->applyInitial(result);
})
| rpl::start_with_next_done(
pushNextSnapshot,
[=] { builder->checkInsufficient(); },
lifetime);
return lifetime;
};
}
void SingleSearchController::requestMore(
void SearchController::requestMore(
const SparseIdsSliceBuilder::AroundData &key,
const Query &query,
Data *listData) {
if (listData->requests.contains(key)) {
return;
}
auto requestId = request(PrepareSearchRequest(
listData->peer,
_query.type,
_query.query,
query.type,
query.query,
key.aroundId,
key.direction)
).done([=](const MTPmessages_Messages &result) {
listData->requests.remove(key);
auto parsed = ParseSearchResult(
listData->peer,
_query.type,
query.type,
key.aroundId,
key.direction,
result);
@ -285,8 +325,9 @@ void SingleSearchController::requestMore(
parsed.noSkipRange,
parsed.fullCount);
}).send();
listData->requests.emplace(key, requestId);
listData->requests.emplace(key, [=] {
request(requestId).cancel();
});
}
DelayedSearchController::DelayedSearchController() {
@ -294,9 +335,18 @@ DelayedSearchController::DelayedSearchController() {
}
void DelayedSearchController::setQuery(const Query &query) {
setQuery(
query,
query.query.isEmpty() ? 0 : kDefaultSearchTimeoutMs);
setQuery(query, kDefaultSearchTimeoutMs);
}
void DelayedSearchController::setQuery(
const Query &query,
TimeMs delay) {
if (_controller.hasInCache(query)) {
setQueryFast(query);
} else {
_nextQuery = query;
_timer.callOnce(delay);
}
}
void DelayedSearchController::setQueryFast(const Query &query) {

View File

@ -24,6 +24,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "history/history_sparse_ids.h"
#include "storage/storage_sparse_ids_list.h"
#include "storage/storage_shared_media.h"
#include "base/value_ordering.h"
namespace Api {
@ -47,7 +48,7 @@ SearchResult ParseSearchResult(
SparseIdsLoadDirection direction,
const MTPmessages_Messages &data);
class SingleSearchController : private MTP::Sender {
class SearchController : private MTP::Sender {
public:
struct Query {
using MediaType = Storage::SharedMediaType;
@ -57,19 +58,30 @@ public:
MediaType type = MediaType::kCount;
QString query;
// from_id, min_date, max_date
friend inline auto value_ordering_helper(const Query &value) {
return std::tie(
value.peerId,
value.migratedPeerId,
value.type,
value.query);
}
};
SingleSearchController(const Query &query);
void setQuery(const Query &query);
bool hasInCache(const Query &query) const;
Query query() const {
Expects(_current != _cache.cend());
return _current->first;
}
rpl::producer<SparseIdsMergedSlice> idsSlice(
SparseIdsMergedSlice::UniversalMsgId aroundId,
int limitBefore,
int limitAfter);
Query query() const {
return _query;
}
private:
struct Data {
explicit Data(not_null<PeerData*> peer) : peer(peer) {
@ -79,49 +91,40 @@ private:
Storage::SparseIdsList list;
base::flat_map<
SparseIdsSliceBuilder::AroundData,
mtpRequestId> requests;
rpl::lifetime> requests;
};
using SliceUpdate = Storage::SparseIdsSliceUpdate;
struct CacheEntry {
CacheEntry(const Query &query);
Data peerData;
base::optional<Data> migratedData;
};
struct CacheLess {
inline bool operator()(const Query &a, const Query &b) const {
return (a < b);
}
};
using Cache = base::flat_map<
Query,
std::unique_ptr<CacheEntry>,
CacheLess>;
rpl::producer<SparseIdsSlice> simpleIdsSlice(
PeerId peerId,
MsgId aroundId,
const Query &query,
int limitBefore,
int limitAfter);
void requestMore(
const SparseIdsSliceBuilder::AroundData &key,
const Query &query,
Data *listData);
Query _query;
Data _peerData;
base::optional<Data> _migratedData;
};
class SearchController {
public:
using Query = SingleSearchController::Query;
void setQuery(const Query &query) {
_controller = SingleSearchController(query);
}
Query query() const {
return _controller ? _controller->query() : Query();
}
rpl::producer<SparseIdsMergedSlice> idsSlice(
SparseIdsMergedSlice::UniversalMsgId aroundId,
int limitBefore,
int limitAfter) {
Expects(_controller.has_value());
return _controller->idsSlice(
aroundId,
limitBefore,
limitAfter);
}
private:
base::optional<SingleSearchController> _controller;
Cache _cache;
Cache::iterator _current = _cache.end();
};
@ -129,12 +132,9 @@ class DelayedSearchController {
public:
DelayedSearchController();
using Query = SingleSearchController::Query;
using Query = SearchController::Query;
void setQuery(const Query &query);
void setQuery(const Query &query, TimeMs delay) {
_nextQuery = query;
_timer.callOnce(delay);
}
void setQuery(const Query &query, TimeMs delay);
void setQueryFast(const Query &query);
Query currentQuery() const {

View File

@ -38,6 +38,7 @@ class event_stream {
public:
event_stream();
event_stream(event_stream &&other);
event_stream &operator=(event_stream &&other);
template <typename OtherValue>
void fire_forward(OtherValue &&value) const;
@ -90,6 +91,13 @@ inline event_stream<Value>::event_stream(event_stream &&other)
: _consumers(base::take(other._consumers)) {
}
template <typename Value>
inline event_stream<Value> &event_stream<Value>::operator=(
event_stream &&other) {
_consumers = base::take(other._consumers);
return *this;
}
template <typename Value>
template <typename OtherValue>
inline void event_stream<Value>::fire_forward(

View File

@ -29,6 +29,7 @@
<(src_loc)/base/unique_any.h
<(src_loc)/base/unique_function.h
<(src_loc)/base/unique_qptr.h
<(src_loc)/base/value_ordering.h
<(src_loc)/base/variant.h
<(src_loc)/base/virtual_method.h
<(src_loc)/base/weak_unique_ptr.h