2017-10-30 19:24:20 +00:00
|
|
|
/*
|
|
|
|
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
|
|
|
|
*/
|
2017-11-06 07:31:18 +00:00
|
|
|
#include "data/data_search_controller.h"
|
2017-10-30 19:24:20 +00:00
|
|
|
|
|
|
|
#include "auth_session.h"
|
|
|
|
|
|
|
|
namespace Api {
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
constexpr auto kSharedMediaLimit = 100;
|
|
|
|
constexpr auto kDefaultSearchTimeoutMs = TimeMs(200);
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
MTPmessages_Search PrepareSearchRequest(
|
|
|
|
not_null<PeerData*> peer,
|
|
|
|
Storage::SharedMediaType type,
|
|
|
|
const QString &query,
|
|
|
|
MsgId messageId,
|
|
|
|
SparseIdsLoadDirection direction) {
|
|
|
|
auto filter = [&] {
|
|
|
|
using Type = Storage::SharedMediaType;
|
|
|
|
switch (type) {
|
|
|
|
case Type::Photo:
|
|
|
|
return MTP_inputMessagesFilterPhotos();
|
|
|
|
case Type::Video:
|
|
|
|
return MTP_inputMessagesFilterVideo();
|
|
|
|
case Type::MusicFile:
|
|
|
|
return MTP_inputMessagesFilterMusic();
|
|
|
|
case Type::File:
|
|
|
|
return MTP_inputMessagesFilterDocument();
|
|
|
|
case Type::VoiceFile:
|
|
|
|
return MTP_inputMessagesFilterVoice();
|
|
|
|
case Type::RoundVoiceFile:
|
|
|
|
return MTP_inputMessagesFilterRoundVoice();
|
|
|
|
case Type::RoundFile:
|
|
|
|
return MTP_inputMessagesFilterRoundVideo();
|
|
|
|
case Type::GIF:
|
|
|
|
return MTP_inputMessagesFilterGif();
|
|
|
|
case Type::Link:
|
|
|
|
return MTP_inputMessagesFilterUrl();
|
|
|
|
case Type::ChatPhoto:
|
|
|
|
return MTP_inputMessagesFilterChatPhotos();
|
|
|
|
}
|
|
|
|
return MTP_inputMessagesFilterEmpty();
|
|
|
|
}();
|
|
|
|
|
|
|
|
auto minId = 0;
|
|
|
|
auto maxId = 0;
|
|
|
|
auto limit = messageId ? kSharedMediaLimit : 0;
|
|
|
|
auto offsetId = [&] {
|
|
|
|
switch (direction) {
|
|
|
|
case SparseIdsLoadDirection::Before:
|
|
|
|
case SparseIdsLoadDirection::Around: return messageId;
|
|
|
|
case SparseIdsLoadDirection::After: return messageId + 1;
|
|
|
|
}
|
|
|
|
Unexpected("Direction in PrepareSearchRequest");
|
|
|
|
}();
|
|
|
|
auto addOffset = [&] {
|
|
|
|
switch (direction) {
|
|
|
|
case SparseIdsLoadDirection::Before: return 0;
|
|
|
|
case SparseIdsLoadDirection::Around: return -limit / 2;
|
|
|
|
case SparseIdsLoadDirection::After: return -limit;
|
|
|
|
}
|
|
|
|
Unexpected("Direction in PrepareSearchRequest");
|
|
|
|
}();
|
|
|
|
|
|
|
|
return MTPmessages_Search(
|
|
|
|
MTP_flags(0),
|
|
|
|
peer->input,
|
|
|
|
MTP_string(query),
|
|
|
|
MTP_inputUserEmpty(),
|
|
|
|
filter,
|
|
|
|
MTP_int(0),
|
|
|
|
MTP_int(0),
|
|
|
|
MTP_int(offsetId),
|
|
|
|
MTP_int(addOffset),
|
|
|
|
MTP_int(limit),
|
|
|
|
MTP_int(maxId),
|
|
|
|
MTP_int(minId));
|
|
|
|
}
|
|
|
|
|
|
|
|
SearchResult ParseSearchResult(
|
|
|
|
not_null<PeerData*> peer,
|
|
|
|
Storage::SharedMediaType type,
|
|
|
|
MsgId messageId,
|
|
|
|
SparseIdsLoadDirection direction,
|
|
|
|
const MTPmessages_Messages &data) {
|
|
|
|
auto result = SearchResult();
|
2017-11-20 19:54:05 +00:00
|
|
|
result.noSkipRange = MsgRange{ messageId, messageId };
|
|
|
|
|
|
|
|
auto messages = [&] {
|
2017-10-30 19:24:20 +00:00
|
|
|
switch (data.type()) {
|
|
|
|
case mtpc_messages_messages: {
|
|
|
|
auto &d = data.c_messages_messages();
|
|
|
|
App::feedUsers(d.vusers);
|
|
|
|
App::feedChats(d.vchats);
|
|
|
|
result.fullCount = d.vmessages.v.size();
|
|
|
|
return &d.vmessages.v;
|
|
|
|
} break;
|
|
|
|
|
|
|
|
case mtpc_messages_messagesSlice: {
|
|
|
|
auto &d = data.c_messages_messagesSlice();
|
|
|
|
App::feedUsers(d.vusers);
|
|
|
|
App::feedChats(d.vchats);
|
|
|
|
result.fullCount = d.vcount.v;
|
|
|
|
return &d.vmessages.v;
|
|
|
|
} break;
|
|
|
|
|
|
|
|
case mtpc_messages_channelMessages: {
|
|
|
|
auto &d = data.c_messages_channelMessages();
|
|
|
|
if (auto channel = peer->asChannel()) {
|
|
|
|
channel->ptsReceived(d.vpts.v);
|
|
|
|
} else {
|
|
|
|
LOG(("API Error: received messages.channelMessages when no channel was passed! (ParseSearchResult)"));
|
|
|
|
}
|
|
|
|
App::feedUsers(d.vusers);
|
|
|
|
App::feedChats(d.vchats);
|
|
|
|
result.fullCount = d.vcount.v;
|
|
|
|
return &d.vmessages.v;
|
|
|
|
} break;
|
2017-11-20 19:54:05 +00:00
|
|
|
|
|
|
|
case mtpc_messages_messagesNotModified: {
|
|
|
|
LOG(("API Error: received messages.messagesNotModified! (ParseSearchResult)"));
|
|
|
|
return (const QVector<MTPMessage>*)nullptr;
|
|
|
|
} break;
|
2017-10-30 19:24:20 +00:00
|
|
|
}
|
|
|
|
Unexpected("messages.Messages type in ParseSearchResult()");
|
|
|
|
}();
|
|
|
|
|
2017-11-20 19:54:05 +00:00
|
|
|
if (!messages) {
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2017-10-30 19:24:20 +00:00
|
|
|
auto addType = NewMessageExisting;
|
2017-11-20 19:54:05 +00:00
|
|
|
result.messageIds.reserve(messages->size());
|
|
|
|
for (auto &message : *messages) {
|
2017-10-30 19:24:20 +00:00
|
|
|
if (auto item = App::histories().addNewMessage(message, addType)) {
|
2017-11-23 09:58:12 +00:00
|
|
|
auto itemId = item->id;
|
2017-10-30 19:24:20 +00:00
|
|
|
if ((type == Storage::SharedMediaType::kCount)
|
|
|
|
|| item->sharedMediaTypes().test(type)) {
|
|
|
|
result.messageIds.push_back(itemId);
|
|
|
|
}
|
2017-11-23 09:58:12 +00:00
|
|
|
accumulate_min(result.noSkipRange.from, itemId);
|
|
|
|
accumulate_max(result.noSkipRange.till, itemId);
|
2017-10-30 19:24:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (messageId && result.messageIds.empty()) {
|
|
|
|
result.noSkipRange = [&]() -> MsgRange {
|
|
|
|
switch (direction) {
|
|
|
|
case SparseIdsLoadDirection::Before: // All old loaded.
|
|
|
|
return { 0, result.noSkipRange.till };
|
|
|
|
case SparseIdsLoadDirection::Around: // All loaded.
|
|
|
|
return { 0, ServerMaxMsgId };
|
|
|
|
case SparseIdsLoadDirection::After: // All new loaded.
|
|
|
|
return { result.noSkipRange.from, ServerMaxMsgId };
|
|
|
|
}
|
|
|
|
Unexpected("Direction in ParseSearchResult");
|
|
|
|
}();
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2017-10-31 07:03:18 +00:00
|
|
|
SearchController::CacheEntry::CacheEntry(const Query &query)
|
|
|
|
: peerData(App::peer(query.peerId))
|
|
|
|
, migratedData(query.migratedPeerId
|
2017-10-30 19:24:20 +00:00
|
|
|
? base::make_optional(Data(App::peer(query.migratedPeerId)))
|
|
|
|
: base::none) {
|
|
|
|
}
|
|
|
|
|
2017-10-31 07:03:18 +00:00
|
|
|
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(
|
2017-10-30 19:24:20 +00:00
|
|
|
SparseIdsMergedSlice::UniversalMsgId aroundId,
|
|
|
|
int limitBefore,
|
|
|
|
int limitAfter) {
|
2017-10-31 07:03:18 +00:00
|
|
|
Expects(_current != _cache.cend());
|
|
|
|
|
|
|
|
auto query = (const Query&)_current->first;
|
|
|
|
auto createSimpleViewer = [=](
|
2017-10-30 19:24:20 +00:00
|
|
|
PeerId peerId,
|
|
|
|
SparseIdsSlice::Key simpleKey,
|
|
|
|
int limitBefore,
|
|
|
|
int limitAfter) {
|
|
|
|
return simpleIdsSlice(
|
|
|
|
peerId,
|
|
|
|
simpleKey,
|
2017-10-31 07:03:18 +00:00
|
|
|
query,
|
2017-10-30 19:24:20 +00:00
|
|
|
limitBefore,
|
|
|
|
limitAfter);
|
|
|
|
};
|
|
|
|
return SparseIdsMergedSlice::CreateViewer(
|
|
|
|
SparseIdsMergedSlice::Key(
|
2017-10-31 07:03:18 +00:00
|
|
|
query.peerId,
|
|
|
|
query.migratedPeerId,
|
2017-10-30 19:24:20 +00:00
|
|
|
aroundId),
|
|
|
|
limitBefore,
|
|
|
|
limitAfter,
|
|
|
|
std::move(createSimpleViewer));
|
|
|
|
}
|
|
|
|
|
2017-10-31 07:03:18 +00:00
|
|
|
rpl::producer<SparseIdsSlice> SearchController::simpleIdsSlice(
|
2017-10-30 19:24:20 +00:00
|
|
|
PeerId peerId,
|
|
|
|
MsgId aroundId,
|
2017-10-31 07:03:18 +00:00
|
|
|
const Query &query,
|
2017-10-30 19:24:20 +00:00
|
|
|
int limitBefore,
|
|
|
|
int limitAfter) {
|
|
|
|
Expects(peerId != 0);
|
|
|
|
Expects(IsServerMsgId(aroundId) || (aroundId == 0));
|
|
|
|
Expects((aroundId != 0)
|
|
|
|
|| (limitBefore == 0 && limitAfter == 0));
|
2017-10-31 07:03:18 +00:00
|
|
|
Expects((query.peerId == peerId)
|
|
|
|
|| (query.migratedPeerId == peerId));
|
|
|
|
|
|
|
|
auto it = _cache.find(query);
|
|
|
|
if (it == _cache.end()) {
|
|
|
|
return [=](auto) { return rpl::lifetime(); };
|
|
|
|
}
|
2017-10-30 19:24:20 +00:00
|
|
|
|
2017-10-31 07:03:18 +00:00
|
|
|
auto listData = (peerId == query.peerId)
|
|
|
|
? &it->second->peerData
|
|
|
|
: &*it->second->migratedData;
|
2017-10-30 19:24:20 +00:00
|
|
|
return [=](auto consumer) {
|
|
|
|
auto lifetime = rpl::lifetime();
|
|
|
|
auto builder = lifetime.make_state<SparseIdsSliceBuilder>(
|
|
|
|
aroundId,
|
|
|
|
limitBefore,
|
|
|
|
limitAfter);
|
|
|
|
builder->insufficientAround()
|
|
|
|
| rpl::start_with_next([=](
|
|
|
|
const SparseIdsSliceBuilder::AroundData &data) {
|
2017-10-31 07:03:18 +00:00
|
|
|
requestMore(data, query, listData);
|
2017-10-30 19:24:20 +00:00
|
|
|
}, lifetime);
|
|
|
|
|
|
|
|
auto pushNextSnapshot = [=] {
|
|
|
|
consumer.put_next(builder->snapshot());
|
|
|
|
};
|
|
|
|
|
|
|
|
listData->list.sliceUpdated()
|
|
|
|
| rpl::filter([=](const SliceUpdate &update) {
|
|
|
|
return builder->applyUpdate(update);
|
|
|
|
})
|
|
|
|
| rpl::start_with_next(pushNextSnapshot, lifetime);
|
|
|
|
|
|
|
|
Auth().data().itemRemoved()
|
|
|
|
| rpl::filter([=](not_null<const HistoryItem*> item) {
|
|
|
|
return (item->history()->peer->id == peerId);
|
|
|
|
})
|
|
|
|
| rpl::filter([=](not_null<const HistoryItem*> item) {
|
|
|
|
return builder->removeOne(item->id);
|
|
|
|
})
|
|
|
|
| rpl::start_with_next(pushNextSnapshot, lifetime);
|
|
|
|
|
|
|
|
Auth().data().historyCleared()
|
|
|
|
| rpl::filter([=](not_null<const History*> history) {
|
|
|
|
return (history->peer->id == peerId);
|
|
|
|
})
|
|
|
|
| rpl::filter([=] { return builder->removeAll(); })
|
|
|
|
| rpl::start_with_next(pushNextSnapshot, lifetime);
|
|
|
|
|
2017-10-31 07:03:18 +00:00
|
|
|
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);
|
2017-10-30 19:24:20 +00:00
|
|
|
|
|
|
|
return lifetime;
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2017-11-03 15:47:08 +00:00
|
|
|
auto SearchController::saveState() -> SavedState {
|
|
|
|
auto result = SavedState();
|
|
|
|
if (_current != _cache.end()) {
|
|
|
|
result.query = _current->first;
|
|
|
|
result.peerList = std::move(_current->second->peerData.list);
|
|
|
|
if (auto &migrated = _current->second->migratedData) {
|
|
|
|
result.migratedList = std::move(migrated->list);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
void SearchController::restoreState(SavedState &&state) {
|
|
|
|
if (!state.query.peerId) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto it = _cache.find(state.query);
|
|
|
|
if (it == _cache.end()) {
|
|
|
|
it = _cache.emplace(
|
|
|
|
state.query,
|
|
|
|
std::make_unique<CacheEntry>(state.query)).first;
|
|
|
|
}
|
|
|
|
auto replace = Data(it->second->peerData.peer);
|
|
|
|
replace.list = std::move(state.peerList);
|
|
|
|
it->second->peerData = std::move(replace);
|
|
|
|
if (auto &migrated = state.migratedList) {
|
|
|
|
Assert(it->second->migratedData.has_value());
|
|
|
|
auto replace = Data(it->second->migratedData->peer);
|
|
|
|
replace.list = std::move(*migrated);
|
|
|
|
it->second->migratedData = std::move(replace);
|
|
|
|
}
|
|
|
|
_current = it;
|
|
|
|
}
|
|
|
|
|
2017-10-31 07:03:18 +00:00
|
|
|
void SearchController::requestMore(
|
2017-10-30 19:24:20 +00:00
|
|
|
const SparseIdsSliceBuilder::AroundData &key,
|
2017-10-31 07:03:18 +00:00
|
|
|
const Query &query,
|
2017-10-30 19:24:20 +00:00
|
|
|
Data *listData) {
|
|
|
|
if (listData->requests.contains(key)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
auto requestId = request(PrepareSearchRequest(
|
|
|
|
listData->peer,
|
2017-10-31 07:03:18 +00:00
|
|
|
query.type,
|
|
|
|
query.query,
|
2017-10-30 19:24:20 +00:00
|
|
|
key.aroundId,
|
|
|
|
key.direction)
|
|
|
|
).done([=](const MTPmessages_Messages &result) {
|
2017-10-31 07:03:18 +00:00
|
|
|
listData->requests.remove(key);
|
2017-10-30 19:24:20 +00:00
|
|
|
auto parsed = ParseSearchResult(
|
|
|
|
listData->peer,
|
2017-10-31 07:03:18 +00:00
|
|
|
query.type,
|
2017-10-30 19:24:20 +00:00
|
|
|
key.aroundId,
|
|
|
|
key.direction,
|
|
|
|
result);
|
|
|
|
listData->list.addSlice(
|
|
|
|
std::move(parsed.messageIds),
|
|
|
|
parsed.noSkipRange,
|
|
|
|
parsed.fullCount);
|
|
|
|
}).send();
|
2017-10-31 07:03:18 +00:00
|
|
|
listData->requests.emplace(key, [=] {
|
|
|
|
request(requestId).cancel();
|
|
|
|
});
|
2017-10-30 19:24:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
DelayedSearchController::DelayedSearchController() {
|
|
|
|
_timer.setCallback([this] { setQueryFast(_nextQuery); });
|
|
|
|
}
|
|
|
|
|
|
|
|
void DelayedSearchController::setQuery(const Query &query) {
|
2017-10-31 07:03:18 +00:00
|
|
|
setQuery(query, kDefaultSearchTimeoutMs);
|
|
|
|
}
|
|
|
|
|
|
|
|
void DelayedSearchController::setQuery(
|
|
|
|
const Query &query,
|
|
|
|
TimeMs delay) {
|
2017-11-03 15:47:08 +00:00
|
|
|
if (currentQuery() == query) {
|
|
|
|
_timer.cancel();
|
|
|
|
return;
|
|
|
|
}
|
2017-10-31 07:03:18 +00:00
|
|
|
if (_controller.hasInCache(query)) {
|
|
|
|
setQueryFast(query);
|
|
|
|
} else {
|
|
|
|
_nextQuery = query;
|
|
|
|
_timer.callOnce(delay);
|
|
|
|
}
|
2017-10-30 19:24:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void DelayedSearchController::setQueryFast(const Query &query) {
|
|
|
|
_controller.setQuery(query);
|
2017-11-14 17:22:44 +00:00
|
|
|
_currentQueryChanges.fire_copy(query.query);
|
2017-10-30 19:24:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace Api
|