From a27edcad1cbbb48a44a21f6932390722f1ce3809 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sun, 29 Oct 2017 19:32:01 +0400 Subject: [PATCH] Extract SparseIdsList module from SharedMedia. This way it can be reused in search results management. --- Telegram/SourceFiles/apiwrap.cpp | 7 + Telegram/SourceFiles/apiwrap.h | 12 +- Telegram/SourceFiles/base/flat_set.h | 50 ++ .../history/history_shared_media.cpp | 655 +++--------------- .../history/history_shared_media.h | 220 +----- .../history/history_sparse_ids.cpp | 391 +++++++++++ .../SourceFiles/history/history_sparse_ids.h | 213 ++++++ .../info/media/info_media_list_widget.cpp | 14 +- .../info/media/info_media_list_widget.h | 4 +- .../info/profile/info_profile_values.cpp | 13 +- Telegram/SourceFiles/mediaview.cpp | 25 +- Telegram/SourceFiles/mediaview.h | 1 + Telegram/SourceFiles/mtproto/sender.h | 35 +- Telegram/SourceFiles/storage/storage_facade.h | 4 +- .../storage/storage_shared_media.cpp | 221 +----- .../storage/storage_shared_media.h | 111 +-- .../storage/storage_sparse_ids_list.cpp | 228 ++++++ .../storage/storage_sparse_ids_list.h | 116 ++++ Telegram/gyp/telegram_sources.txt | 4 + 19 files changed, 1254 insertions(+), 1070 deletions(-) create mode 100644 Telegram/SourceFiles/history/history_sparse_ids.cpp create mode 100644 Telegram/SourceFiles/history/history_sparse_ids.h create mode 100644 Telegram/SourceFiles/storage/storage_sparse_ids_list.cpp create mode 100644 Telegram/SourceFiles/storage/storage_sparse_ids_list.h diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 4ba880f02..0058e2a46 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -40,6 +40,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "storage/storage_facade.h" #include "storage/storage_shared_media.h" #include "storage/storage_user_photos.h" +#include "history/history_sparse_ids.h" namespace { @@ -1922,6 +1923,12 @@ void ApiWrap::sendSaveChatAdminsRequests(not_null chat) { requestSendDelayed(); } +void ApiWrap::requestSharedMediaCount( + not_null peer, + Storage::SharedMediaType type) { + requestSharedMedia(peer, type, 0, SliceType::Before); +} + void ApiWrap::requestSharedMedia( not_null peer, SharedMediaType type, diff --git a/Telegram/SourceFiles/apiwrap.h b/Telegram/SourceFiles/apiwrap.h index 2cc80bf9e..1ab158c61 100644 --- a/Telegram/SourceFiles/apiwrap.h +++ b/Telegram/SourceFiles/apiwrap.h @@ -33,6 +33,8 @@ namespace Storage { enum class SharedMediaType : char; } // namespace Storage +enum class SparseIdsLoadDirection; + namespace Api { inline const MTPVector *getChatsFromMessagesChats(const MTPmessages_Chats &chats) { @@ -113,11 +115,7 @@ public: bool adminsEnabled, base::flat_set> &&admins); - enum class SliceType { - Around, - Before, - After, - }; + using SliceType = SparseIdsLoadDirection; void requestSharedMedia( not_null peer, Storage::SharedMediaType type, @@ -125,9 +123,7 @@ public: SliceType slice); void requestSharedMediaCount( not_null peer, - Storage::SharedMediaType type) { - requestSharedMedia(peer, type, 0, SliceType::Before); - } + Storage::SharedMediaType type); void requestUserPhotos( not_null user, diff --git a/Telegram/SourceFiles/base/flat_set.h b/Telegram/SourceFiles/base/flat_set.h index 1a5061b14..92b8c2b13 100644 --- a/Telegram/SourceFiles/base/flat_set.h +++ b/Telegram/SourceFiles/base/flat_set.h @@ -386,6 +386,32 @@ public: return compare()(value, *where) ? _impl.end() : where; } + template < + typename OtherType, + typename = typename Compare::is_transparent> + iterator findFirst(const OtherType &value) { + if (empty() + || compare()(value, front()) + || compare()(back(), value)) { + return end(); + } + auto where = getLowerBound(value); + return compare()(value, *where) ? _impl.end() : where; + } + + template < + typename OtherType, + typename = typename Compare::is_transparent> + const_iterator findFirst(const OtherType &value) const { + if (empty() + || compare()(value, front()) + || compare()(back(), value)) { + return end(); + } + auto where = getLowerBound(value); + return compare()(value, *where) ? _impl.end() : where; + } + bool contains(const Type &value) const { return findFirst(value) != end(); } @@ -446,6 +472,18 @@ private: typename impl::const_iterator getLowerBound(const Type &value) const { return base::lower_bound(_impl, value, compare()); } + template < + typename OtherType, + typename = typename Compare::is_transparent> + typename impl::iterator getLowerBound(const OtherType &value) { + return base::lower_bound(_impl, value, compare()); + } + template < + typename OtherType, + typename = typename Compare::is_transparent> + typename impl::const_iterator getLowerBound(const OtherType &value) const { + return base::lower_bound(_impl, value, compare()); + } typename impl::iterator getUpperBound(const Type &value) { return base::upper_bound(_impl, value, compare()); } @@ -557,6 +595,18 @@ public: const_iterator find(const Type &value) const { return this->findFirst(value); } + template < + typename OtherType, + typename = typename Compare::is_transparent> + iterator find(const OtherType &value) { + return this->findFirst(value); + } + template < + typename OtherType, + typename = typename Compare::is_transparent> + const_iterator find(const OtherType &value) const { + return this->findFirst(value); + } template void modify(iterator which, Action action) { diff --git a/Telegram/SourceFiles/history/history_shared_media.cpp b/Telegram/SourceFiles/history/history_shared_media.cpp index 9f5e63e44..42368140f 100644 --- a/Telegram/SourceFiles/history/history_shared_media.cpp +++ b/Telegram/SourceFiles/history/history_shared_media.cpp @@ -26,10 +26,11 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "storage/storage_facade.h" #include "storage/storage_shared_media.h" #include "history/history_media_types.h" +#include "history/history_sparse_ids.h" namespace { -using Type = SharedMediaSlice::Type; +using Type = Storage::SharedMediaType; inline MediaOverviewType SharedMediaTypeToOverview(Type type) { switch (type) { @@ -62,368 +63,8 @@ void SharedMediaShowOverview( } } -class SharedMediaSliceBuilder { -public: - using Type = Storage::SharedMediaType; - using Key = Storage::SharedMediaKey; - - SharedMediaSliceBuilder(Key key, int limitBefore, int limitAfter); - - using Result = Storage::SharedMediaResult; - using SliceUpdate = Storage::SharedMediaSliceUpdate; - using RemoveOne = Storage::SharedMediaRemoveOne; - using RemoveAll = Storage::SharedMediaRemoveAll; - bool applyUpdate(const Result &result); - bool applyUpdate(const SliceUpdate &update); - bool applyUpdate(const RemoveOne &update); - bool applyUpdate(const RemoveAll &update); - - void checkInsufficientMedia(); - using AroundData = std::pair; - auto insufficientMediaAround() const { - return _insufficientMediaAround.events(); - } - - SharedMediaSlice snapshot() const; - -private: - enum class RequestDirection { - Before, - After, - }; - void requestMessages(RequestDirection direction); - void requestMessagesCount(); - void fillSkippedAndSliceToLimits(); - void sliceToLimits(); - - void mergeSliceData( - base::optional count, - const base::flat_set &messageIds, - base::optional skippedBefore = base::none, - base::optional skippedAfter = base::none); - - Key _key; - base::flat_set _ids; - MsgRange _range; - base::optional _fullCount; - base::optional _skippedBefore; - base::optional _skippedAfter; - int _limitBefore = 0; - int _limitAfter = 0; - - rpl::event_stream _insufficientMediaAround; - -}; - -SharedMediaSlice::SharedMediaSlice(Key key) : SharedMediaSlice( - key, - {}, - {}, - base::none, - base::none, - base::none) { -} - -SharedMediaSlice::SharedMediaSlice( - Key key, - const base::flat_set &ids, - MsgRange range, - base::optional fullCount, - base::optional skippedBefore, - base::optional skippedAfter) - : _key(key) - , _ids(ids) - , _range(range) - , _fullCount(fullCount) - , _skippedBefore(skippedBefore) - , _skippedAfter(skippedAfter) { -} - -base::optional SharedMediaSlice::indexOf(MsgId msgId) const { - auto it = _ids.find(msgId); - if (it != _ids.end()) { - return (it - _ids.begin()); - } - return base::none; -} - -MsgId SharedMediaSlice::operator[](int index) const { - Expects(index >= 0 && index < size()); - - return *(_ids.begin() + index); -} - -base::optional SharedMediaSlice::distance(const Key &a, const Key &b) const { - if (a.type != _key.type - || b.type != _key.type - || a.peerId != _key.peerId - || b.peerId != _key.peerId) { - return base::none; - } - if (auto i = indexOf(a.messageId)) { - if (auto j = indexOf(b.messageId)) { - return *j - *i; - } - } - return base::none; -} - -base::optional SharedMediaSlice::nearest(MsgId msgId) const { - if (auto it = base::lower_bound(_ids, msgId); it != _ids.end()) { - return *it; - } else if (_ids.empty()) { - return base::none; - } - return _ids.back(); -} - -QString SharedMediaSlice::debug() const { - auto before = _skippedBefore - ? (*_skippedBefore - ? ('(' + QString::number(*_skippedBefore) + ").. ") - : QString()) - : QString(".. "); - auto after = _skippedAfter - ? (*_skippedAfter - ? (" ..(" + QString::number(*_skippedAfter) + ')') - : QString()) - : QString(" .."); - auto middle = (size() > 2) - ? QString::number((*this)[0]) + " .. " + QString::number((*this)[size() - 1]) - : (size() > 1) - ? QString::number((*this)[0]) + ' ' + QString::number((*this)[1]) - : ((size() > 0) ? QString::number((*this)[0]) : QString()); - return before + middle + after; -} - -SharedMediaSliceBuilder::SharedMediaSliceBuilder( - Key key, - int limitBefore, - int limitAfter) - : _key(key) - , _limitBefore(limitBefore) - , _limitAfter(limitAfter) { -} - -bool SharedMediaSliceBuilder::applyUpdate(const Result &result) { - mergeSliceData( - result.count, - result.messageIds, - result.skippedBefore, - result.skippedAfter); - return true; -} - -bool SharedMediaSliceBuilder::applyUpdate(const SliceUpdate &update) { - if (update.peerId != _key.peerId || update.type != _key.type) { - return false; - } - auto intersects = [](MsgRange range1, MsgRange range2) { - return (range1.from <= range2.till) - && (range2.from <= range1.till); - }; - auto needMergeMessages = (update.messages != nullptr) - && intersects(update.range, { - _ids.empty() ? _key.messageId : _ids.front(), - _ids.empty() ? _key.messageId : _ids.back() - }); - if (!needMergeMessages && !update.count) { - return false; - } - auto skippedBefore = (update.range.from == 0) - ? 0 - : base::optional {}; - auto skippedAfter = (update.range.till == ServerMaxMsgId) - ? 0 - : base::optional {}; - mergeSliceData( - update.count, - needMergeMessages - ? *update.messages - : base::flat_set {}, - skippedBefore, - skippedAfter); - return true; -} - -bool SharedMediaSliceBuilder::applyUpdate(const RemoveOne &update) { - if (update.peerId != _key.peerId || !update.types.test(_key.type)) { - return false; - } - auto changed = false; - if (_fullCount && *_fullCount > 0) { - --*_fullCount; - changed = true; - } - if (_ids.contains(update.messageId)) { - _ids.remove(update.messageId); - changed = true; - } else if (!_ids.empty()) { - if (_ids.front() > update.messageId - && _skippedBefore - && *_skippedBefore > 0) { - --*_skippedBefore; - changed = true; - } else if (_ids.back() < update.messageId - && _skippedAfter - && *_skippedAfter > 0) { - --*_skippedAfter; - changed = true; - } - } - return changed; -} - -bool SharedMediaSliceBuilder::applyUpdate(const RemoveAll &update) { - if (update.peerId != _key.peerId) { - return false; - } - _ids = {}; - _range = { 0, ServerMaxMsgId }; - _fullCount = 0; - _skippedBefore = 0; - _skippedAfter = 0; - return true; -} - -void SharedMediaSliceBuilder::checkInsufficientMedia() { - sliceToLimits(); -} - -void SharedMediaSliceBuilder::mergeSliceData( - base::optional count, - const base::flat_set &messageIds, - base::optional skippedBefore, - base::optional skippedAfter) { - if (messageIds.empty()) { - if (count && _fullCount != count) { - _fullCount = count; - if (*_fullCount <= _ids.size()) { - _fullCount = _ids.size(); - _skippedBefore = _skippedAfter = 0; - } - } - fillSkippedAndSliceToLimits(); - return; - } - if (count) { - _fullCount = count; - } - auto wasMinId = _ids.empty() ? -1 : _ids.front(); - auto wasMaxId = _ids.empty() ? -1 : _ids.back(); - _ids.merge(messageIds.begin(), messageIds.end()); - - auto adjustSkippedBefore = [&](MsgId oldId, int oldSkippedBefore) { - auto it = _ids.find(oldId); - Assert(it != _ids.end()); - _skippedBefore = oldSkippedBefore - (it - _ids.begin()); - accumulate_max(*_skippedBefore, 0); - }; - if (skippedBefore) { - adjustSkippedBefore(messageIds.front(), *skippedBefore); - } else if (wasMinId >= 0 && _skippedBefore) { - adjustSkippedBefore(wasMinId, *_skippedBefore); - } else { - _skippedBefore = base::none; - } - - auto adjustSkippedAfter = [&](MsgId oldId, int oldSkippedAfter) { - auto it = _ids.find(oldId); - Assert(it != _ids.end()); - _skippedAfter = oldSkippedAfter - (_ids.end() - it - 1); - accumulate_max(*_skippedAfter, 0); - }; - if (skippedAfter) { - adjustSkippedAfter(messageIds.back(), *skippedAfter); - } else if (wasMaxId >= 0 && _skippedAfter) { - adjustSkippedAfter(wasMaxId, *_skippedAfter); - } else { - _skippedAfter = base::none; - } - fillSkippedAndSliceToLimits(); -} - -void SharedMediaSliceBuilder::fillSkippedAndSliceToLimits() { - if (_fullCount) { - if (_skippedBefore && !_skippedAfter) { - _skippedAfter = *_fullCount - - *_skippedBefore - - int(_ids.size()); - } else if (_skippedAfter && !_skippedBefore) { - _skippedBefore = *_fullCount - - *_skippedAfter - - int(_ids.size()); - } - } - sliceToLimits(); -} - -void SharedMediaSliceBuilder::sliceToLimits() { - if (!_key.messageId) { - if (!_fullCount) { - requestMessagesCount(); - } - return; - } - auto requestedSomething = false; - auto aroundIt = base::lower_bound(_ids, _key.messageId); - auto removeFromBegin = (aroundIt - _ids.begin() - _limitBefore); - auto removeFromEnd = (_ids.end() - aroundIt - _limitAfter - 1); - if (removeFromBegin > 0) { - _ids.erase(_ids.begin(), _ids.begin() + removeFromBegin); - if (_skippedBefore) { - *_skippedBefore += removeFromBegin; - } - } else if (removeFromBegin < 0 - && (!_skippedBefore || *_skippedBefore > 0)) { - requestedSomething = true; - requestMessages(RequestDirection::Before); - } - if (removeFromEnd > 0) { - _ids.erase(_ids.end() - removeFromEnd, _ids.end()); - if (_skippedAfter) { - *_skippedAfter += removeFromEnd; - } - } else if (removeFromEnd < 0 - && (!_skippedAfter || *_skippedAfter > 0)) { - requestedSomething = true; - requestMessages(RequestDirection::After); - } - if (!_fullCount && !requestedSomething) { - requestMessagesCount(); - } -} - -void SharedMediaSliceBuilder::requestMessages( - RequestDirection direction) { - using SliceType = ApiWrap::SliceType; - auto requestAroundData = [&]() -> AroundData { - if (_ids.empty()) { - return { _key.messageId, SliceType::Around }; - } else if (direction == RequestDirection::Before) { - return { _ids.front(), SliceType::Before }; - } - return { _ids.back(), SliceType::After }; - }; - _insufficientMediaAround.fire(requestAroundData()); -} - -void SharedMediaSliceBuilder::requestMessagesCount() { - _insufficientMediaAround.fire({ 0, ApiWrap::SliceType::Around }); -} - -SharedMediaSlice SharedMediaSliceBuilder::snapshot() const { - return SharedMediaSlice( - _key, - _ids, - _range, - _fullCount, - _skippedBefore, - _skippedAfter); -} - -rpl::producer SharedMediaViewer( - SharedMediaSlice::Key key, +rpl::producer SharedMediaViewer( + Storage::SharedMediaKey key, int limitBefore, int limitAfter) { Expects(IsServerMsgId(key.messageId) || (key.messageId == 0)); @@ -431,199 +72,123 @@ rpl::producer SharedMediaViewer( return [=](auto consumer) { auto lifetime = rpl::lifetime(); - auto builder = lifetime.make_state( - key, + auto builder = lifetime.make_state( + key.messageId, limitBefore, limitAfter); - auto applyUpdate = [=](auto &&update) { - if (builder->applyUpdate(std::forward(update))) { - consumer.put_next(builder->snapshot()); - } - }; auto requestMediaAround = [ peer = App::peer(key.peerId), type = key.type - ](const SharedMediaSliceBuilder::AroundData &data) { + ](const SparseIdsSliceBuilder::AroundData &data) { Auth().api().requestSharedMedia( peer, type, data.first, data.second); }; - builder->insufficientMediaAround() + builder->insufficientAround() | rpl::start_with_next(requestMediaAround, lifetime); - Auth().storage().sharedMediaSliceUpdated() - | rpl::start_with_next(applyUpdate, lifetime); - Auth().storage().sharedMediaOneRemoved() - | rpl::start_with_next(applyUpdate, lifetime); - Auth().storage().sharedMediaAllRemoved() - | rpl::start_with_next(applyUpdate, lifetime); + auto pushNextSnapshot = [=] { + consumer.put_next(builder->snapshot()); + }; + using SliceUpdate = Storage::SharedMediaSliceUpdate; + Auth().storage().sharedMediaSliceUpdated() + | rpl::filter([=](const SliceUpdate &update) { + return (update.peerId == key.peerId) + && (update.type == key.type); + }) + | rpl::filter([=](const SliceUpdate &update) { + return builder->applyUpdate(update.data); + }) + | rpl::start_with_next(pushNextSnapshot, lifetime); + + using OneRemoved = Storage::SharedMediaRemoveOne; + Auth().storage().sharedMediaOneRemoved() + | rpl::filter([=](const OneRemoved &update) { + return (update.peerId == key.peerId) + && update.types.test(key.type); + }) + | rpl::filter([=](const OneRemoved &update) { + return builder->removeOne(update.messageId); + }) + | rpl::start_with_next(pushNextSnapshot, lifetime); + + using AllRemoved = Storage::SharedMediaRemoveAll; + Auth().storage().sharedMediaAllRemoved() + | rpl::filter([=](const AllRemoved &update) { + return (update.peerId == key.peerId); + }) + | rpl::filter([=](const AllRemoved &update) { + return builder->removeAll(); + }) + | rpl::start_with_next(pushNextSnapshot, lifetime); + + using Result = Storage::SharedMediaResult; Auth().storage().query( Storage::SharedMediaQuery( key, limitBefore, - limitAfter) - ) | rpl::start_with_next_done( - applyUpdate, - [=] { builder->checkInsufficientMedia(); }, - lifetime); + limitAfter)) + | rpl::filter([=](const Result &result) { + return builder->applyInitial(result); + }) + | rpl::start_with_next_done( + pushNextSnapshot, + [=] { builder->checkInsufficient(); }, + lifetime); return lifetime; }; } -SharedMediaMergedSlice::SharedMediaMergedSlice(Key key) : SharedMediaMergedSlice( - key, - SharedMediaSlice(PartKey(key)), - MigratedSlice(key)) { -} - -SharedMediaMergedSlice::SharedMediaMergedSlice( - Key key, - SharedMediaSlice part, - base::optional migrated) -: _key(key) -, _part(std::move(part)) -, _migrated(std::move(migrated)) { -} - -base::optional SharedMediaMergedSlice::fullCount() const { - return Add( - _part.fullCount(), - _migrated ? _migrated->fullCount() : 0); -} - -base::optional SharedMediaMergedSlice::skippedBefore() const { - return Add( - isolatedInMigrated() ? 0 : _part.skippedBefore(), - _migrated - ? (isolatedInPart() - ? _migrated->fullCount() - : _migrated->skippedBefore()) - : 0 - ); -} - -base::optional SharedMediaMergedSlice::skippedAfter() const { - return Add( - isolatedInMigrated() ? _part.fullCount() : _part.skippedAfter(), - isolatedInPart() ? 0 : _migrated->skippedAfter() - ); -} - -base::optional SharedMediaMergedSlice::indexOf(FullMsgId fullId) const { - return isFromPart(fullId) - ? (_part.indexOf(fullId.msg) | func::add(migratedSize())) - : isolatedInPart() - ? base::none - : isFromMigrated(fullId) - ? _migrated->indexOf(fullId.msg) - : base::none; -} - -int SharedMediaMergedSlice::size() const { - return (isolatedInPart() ? 0 : migratedSize()) - + (isolatedInMigrated() ? 0 : _part.size()); -} - -FullMsgId SharedMediaMergedSlice::operator[](int index) const { - Expects(index >= 0 && index < size()); - - if (auto size = migratedSize()) { - if (index < size) { - return ComputeId(*_migrated, index); - } - index -= size; - } - return ComputeId(_part, index); -} - -base::optional SharedMediaMergedSlice::distance( - const Key &a, - const Key &b) const { - if (a.type != _key.type - || b.type != _key.type - || a.peerId != _key.peerId - || b.peerId != _key.peerId - || a.migratedPeerId != _key.migratedPeerId - || b.migratedPeerId != _key.migratedPeerId) { - return base::none; - } - if (auto i = indexOf(ComputeId(a))) { - if (auto j = indexOf(ComputeId(b))) { - return *j - *i; - } - } - return base::none; -} - -auto SharedMediaMergedSlice::nearest( - UniversalMsgId id) const -> base::optional { - auto convertFromMigratedNearest = [](MsgId result) { - return result - ServerMaxMsgId; - }; - if (IsServerMsgId(id)) { - if (auto partNearestId = _part.nearest(id)) { - return partNearestId; - } else if (isolatedInPart()) { - return base::none; - } - return _migrated->nearest(ServerMaxMsgId - 1) - | convertFromMigratedNearest; - } - if (auto migratedNearestId = _migrated - ? _migrated->nearest(id + ServerMaxMsgId) - : base::none) { - return migratedNearestId - | convertFromMigratedNearest; - } else if (isolatedInMigrated()) { - return base::none; - } - return _part.nearest(0); -} - -QString SharedMediaMergedSlice::debug() const { - return (_migrated ? (_migrated->debug() + '|') : QString()) + _part.debug(); -} - -rpl::producer SharedMediaMergedViewer( - SharedMediaMergedSlice::Key key, +rpl::producer SharedMediaMergedViewer( + SharedMediaMergedKey key, int limitBefore, int limitAfter) { - Expects(IsServerMsgId(key.universalId) - || (key.universalId == 0) - || (IsServerMsgId(ServerMaxMsgId + key.universalId) && key.migratedPeerId != 0)); - Expects((key.universalId != 0) || (limitBefore == 0 && limitAfter == 0)); + Expects(IsServerMsgId(key.mergedKey.universalId) + || (key.mergedKey.universalId == 0) + || (IsServerMsgId(ServerMaxMsgId + key.mergedKey.universalId) && key.mergedKey.migratedPeerId != 0)); + Expects((key.mergedKey.universalId != 0) + || (limitBefore == 0 && limitAfter == 0)); return [=](auto consumer) { - if (!key.migratedPeerId) { + if (!key.mergedKey.migratedPeerId) { return SharedMediaViewer( - SharedMediaMergedSlice::PartKey(key), + Storage::SharedMediaKey( + key.mergedKey.peerId, + key.type, + SparseIdsMergedSlice::PartKey(key.mergedKey)), limitBefore, limitAfter - ) | rpl::start_with_next([=](SharedMediaSlice &&part) { - consumer.put_next(SharedMediaMergedSlice( - key, + ) | rpl::start_with_next([=](SparseIdsSlice &&part) { + consumer.put_next(SparseIdsMergedSlice( + key.mergedKey, std::move(part), base::none)); }); } return rpl::combine( SharedMediaViewer( - SharedMediaMergedSlice::PartKey(key), + Storage::SharedMediaKey( + key.mergedKey.peerId, + key.type, + SparseIdsMergedSlice::PartKey(key.mergedKey)), limitBefore, limitAfter), SharedMediaViewer( - SharedMediaMergedSlice::MigratedKey(key), + Storage::SharedMediaKey( + key.mergedKey.migratedPeerId, + key.type, + SparseIdsMergedSlice::MigratedKey(key.mergedKey)), limitBefore, limitAfter) ) | rpl::start_with_next([=]( - SharedMediaSlice &&part, - SharedMediaSlice &&migrated) { - consumer.put_next(SharedMediaMergedSlice( - key, + SparseIdsSlice &&part, + SparseIdsSlice &&migrated) { + consumer.put_next(SparseIdsMergedSlice( + key.mergedKey, std::move(part), std::move(migrated))); }); @@ -633,21 +198,21 @@ rpl::producer SharedMediaMergedViewer( SharedMediaWithLastSlice::SharedMediaWithLastSlice(Key key) : SharedMediaWithLastSlice( key, - SharedMediaMergedSlice(ViewerKey(key)), + SparseIdsMergedSlice(ViewerKey(key)), EndingSlice(key)) { } SharedMediaWithLastSlice::SharedMediaWithLastSlice( Key key, - SharedMediaMergedSlice slice, - base::optional ending) - : _key(key) - , _slice(std::move(slice)) - , _ending(std::move(ending)) - , _lastPhotoId(LastPeerPhotoId(key.peerId)) - , _isolatedLastPhoto(_key.type == Type::ChatPhoto - ? IsLastIsolated(_slice, _ending, _lastPhotoId) - : false) { + SparseIdsMergedSlice slice, + base::optional ending) +: _key(key) +, _slice(std::move(slice)) +, _ending(std::move(ending)) +, _lastPhotoId(LastPeerPhotoId(key.peerId)) +, _isolatedLastPhoto(_key.type == Type::ChatPhoto + ? IsLastIsolated(_slice, _ending, _lastPhotoId) + : false) { } base::optional SharedMediaWithLastSlice::fullCount() const { @@ -690,15 +255,9 @@ SharedMediaWithLastSlice::Value SharedMediaWithLastSlice::operator[](int index) : Value(App::photo(_lastPhotoId)); } -base::optional SharedMediaWithLastSlice::distance(const Key &a, const Key &b) const { - if (a.type != _key.type - || b.type != _key.type - || a.peerId != _key.peerId - || b.peerId != _key.peerId - || a.migratedPeerId != _key.migratedPeerId - || b.migratedPeerId != _key.migratedPeerId) { - return base::none; - } +base::optional SharedMediaWithLastSlice::distance( + const Key &a, + const Key &b) const { if (auto i = indexOf(ComputeId(a))) { if (auto j = indexOf(ComputeId(b))) { return *j - *i; @@ -707,12 +266,6 @@ base::optional SharedMediaWithLastSlice::distance(const Key &a, const Key & return base::none; } -QString SharedMediaWithLastSlice::debug() const { - return _slice.debug() + (_isolatedLastPhoto - ? (*_isolatedLastPhoto ? "@" : "") - : "?"); -} - PhotoId SharedMediaWithLastSlice::LastPeerPhotoId(PeerId peerId) { if (auto peer = App::peerLoaded(peerId)) { return peer->photoId; @@ -721,8 +274,8 @@ PhotoId SharedMediaWithLastSlice::LastPeerPhotoId(PeerId peerId) { } base::optional SharedMediaWithLastSlice::IsLastIsolated( - const SharedMediaMergedSlice &slice, - const base::optional &ending, + const SparseIdsMergedSlice &slice, + const base::optional &ending, PhotoId lastPeerPhotoId) { if (lastPeerPhotoId == UnknownPeerPhotoId) { return base::none; @@ -742,7 +295,7 @@ base::optional SharedMediaWithLastSlice::IsLastIsolated( } base::optional SharedMediaWithLastSlice::LastFullMsgId( - const SharedMediaMergedSlice &slice) { + const SparseIdsMergedSlice &slice) { if (slice.fullCount() == 0) { return FullMsgId(); } else if (slice.size() == 0 || slice.skippedAfter() != 0) { @@ -758,10 +311,12 @@ rpl::producer SharedMediaWithLastViewer( return [=](auto consumer) { if (base::get_if>(&key.universalId)) { return SharedMediaMergedViewer( - SharedMediaWithLastSlice::ViewerKey(key), + SharedMediaMergedKey( + SharedMediaWithLastSlice::ViewerKey(key), + key.type), limitBefore, limitAfter - ) | rpl::start_with_next([=](SharedMediaMergedSlice &&update) { + ) | rpl::start_with_next([=](SparseIdsMergedSlice &&update) { consumer.put_next(SharedMediaWithLastSlice( key, std::move(update), @@ -770,16 +325,20 @@ rpl::producer SharedMediaWithLastViewer( } return rpl::combine( SharedMediaMergedViewer( - SharedMediaWithLastSlice::ViewerKey(key), + SharedMediaMergedKey( + SharedMediaWithLastSlice::ViewerKey(key), + key.type), limitBefore, limitAfter), SharedMediaMergedViewer( - SharedMediaWithLastSlice::EndingKey(key), + SharedMediaMergedKey( + SharedMediaWithLastSlice::EndingKey(key), + key.type), 1, 1) ) | rpl::start_with_next([=]( - SharedMediaMergedSlice &&viewer, - SharedMediaMergedSlice &&ending) { + SparseIdsMergedSlice &&viewer, + SparseIdsMergedSlice &&ending) { consumer.put_next(SharedMediaWithLastSlice( key, std::move(viewer), diff --git a/Telegram/SourceFiles/history/history_shared_media.h b/Telegram/SourceFiles/history/history_shared_media.h index 5f2934e2f..1e0b80db9 100644 --- a/Telegram/SourceFiles/history/history_shared_media.h +++ b/Telegram/SourceFiles/history/history_shared_media.h @@ -22,6 +22,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "storage/storage_shared_media.h" #include "base/weak_unique_ptr.h" +#include "history/history_sparse_ids.h" base::optional SharedMediaOverviewType( Storage::SharedMediaType type); @@ -29,176 +30,33 @@ void SharedMediaShowOverview( Storage::SharedMediaType type, not_null history); -class SharedMediaSlice { -public: - using Type = Storage::SharedMediaType; - using Key = Storage::SharedMediaKey; - - SharedMediaSlice(Key key); - SharedMediaSlice( - Key key, - const base::flat_set &ids, - MsgRange range, - base::optional fullCount, - base::optional skippedBefore, - base::optional skippedAfter); - - const Key &key() const { return _key; } - - base::optional fullCount() const { return _fullCount; } - base::optional skippedBefore() const { return _skippedBefore; } - base::optional skippedAfter() const { return _skippedAfter; } - base::optional indexOf(MsgId msgId) const; - int size() const { return _ids.size(); } - MsgId operator[](int index) const; - base::optional distance(const Key &a, const Key &b) const; - base::optional nearest(MsgId msgId) const; - - QString debug() const; - -private: - Key _key; - base::flat_set _ids; - MsgRange _range; - base::optional _fullCount; - base::optional _skippedBefore; - base::optional _skippedAfter; - - class SharedMediaSliceBuilder; - -}; - -rpl::producer SharedMediaViewer( - SharedMediaSlice::Key key, +rpl::producer SharedMediaViewer( + Storage::SharedMediaKey key, int limitBefore, int limitAfter); -class SharedMediaMergedSlice { -public: +struct SharedMediaMergedKey { using Type = Storage::SharedMediaType; - using UniversalMsgId = MsgId; - struct Key { - Key( - PeerId peerId, - PeerId migratedPeerId, - Type type, - UniversalMsgId universalId) - : peerId(peerId) - , migratedPeerId(migratedPeerId) - , type(type) - , universalId(universalId) { - } - bool operator==(const Key &other) const { - return (peerId == other.peerId) - && (migratedPeerId == other.migratedPeerId) - && (type == other.type) - && (universalId == other.universalId); - } - - PeerId peerId = 0; - PeerId migratedPeerId = 0; - Type type = Type::kCount; - UniversalMsgId universalId = 0; - - }; - - SharedMediaMergedSlice(Key key); - SharedMediaMergedSlice( - Key key, - SharedMediaSlice part, - base::optional migrated); - - const Key &key() const { return _key; } - - base::optional fullCount() const; - base::optional skippedBefore() const; - base::optional skippedAfter() const; - base::optional indexOf(FullMsgId fullId) const; - int size() const; - FullMsgId operator[](int index) const; - base::optional distance(const Key &a, const Key &b) const; - base::optional nearest(UniversalMsgId id) const; - - QString debug() const; - - static SharedMediaSlice::Key PartKey(const Key &key) { - return { - key.peerId, - key.type, - (key.universalId < 0) ? 1 : key.universalId - }; - } - static SharedMediaSlice::Key MigratedKey(const Key &key) { - return { - key.migratedPeerId, - key.type, - (key.universalId < 0) - ? (ServerMaxMsgId + key.universalId) - : (key.universalId > 0) ? (ServerMaxMsgId - 1) : 0 - }; + SharedMediaMergedKey( + SparseIdsMergedSlice::Key mergedKey, + Type type) + : mergedKey(mergedKey) + , type(type) { } -private: - static base::optional MigratedSlice(const Key &key) { - return key.migratedPeerId - ? base::make_optional(SharedMediaSlice(MigratedKey(key))) - : base::none; + bool operator==(const SharedMediaMergedKey &other) const { + return (mergedKey == other.mergedKey) + && (type == other.type); } - static bool IsFromSlice(const SharedMediaSlice &slice, FullMsgId fullId) { - auto peer = slice.key().peerId; - return peerIsChannel(peer) - ? (peer == peerFromChannel(fullId.channel)) - : !fullId.channel; - } - static FullMsgId ComputeId(PeerId peerId, MsgId msgId) { - return FullMsgId( - peerIsChannel(peerId) ? peerToBareInt(peerId) : 0, - msgId); - } - static FullMsgId ComputeId(const SharedMediaSlice &slice, int index) { - return ComputeId(slice.key().peerId, slice[index]); - }; - static FullMsgId ComputeId(const Key &key) { - return (key.universalId >= 0) - ? ComputeId(key.peerId, key.universalId) - : ComputeId(key.migratedPeerId, ServerMaxMsgId + key.universalId); - } - static base::optional Add( - const base::optional &a, - const base::optional &b) { - return (a && b) ? base::make_optional(*a + *b) : base::none; - } - - bool isFromPart(FullMsgId fullId) const { - return IsFromSlice(_part, fullId); - } - bool isFromMigrated(FullMsgId fullId) const { - return _migrated ? IsFromSlice(*_migrated, fullId) : false; - } - int migratedSize() const { - return isolatedInPart() ? 0 : _migrated->size(); - } - bool isolatedInPart() const { - return IsServerMsgId(_key.universalId) - && (!_migrated || _part.skippedBefore() != 0); - } - bool isolatedInMigrated() const { - return IsServerMsgId(ServerMaxMsgId + _key.universalId) - && (_migrated->skippedAfter() != 0); - } - - Key _key; - SharedMediaSlice _part; - base::optional _migrated; - - friend class SharedMediaMergedSliceBuilder; + SparseIdsMergedSlice::Key mergedKey; + Type type = Type::kCount; }; -rpl::producer SharedMediaMergedViewer( - SharedMediaMergedSlice::Key key, +rpl::producer SharedMediaMergedViewer( + SharedMediaMergedKey key, int limitBefore, int limitAfter); @@ -206,9 +64,8 @@ class SharedMediaWithLastSlice { public: using Type = Storage::SharedMediaType; - // base::none in those mean CurrentPeerPhoto. using Value = base::variant>; - using MessageId = SharedMediaMergedSlice::UniversalMsgId; + using MessageId = SparseIdsMergedSlice::UniversalMsgId; using UniversalMsgId = base::variant< MessageId, not_null>; @@ -219,10 +76,10 @@ public: PeerId migratedPeerId, Type type, UniversalMsgId universalId) - : peerId(peerId) - , migratedPeerId(migratedPeerId) - , type(type) - , universalId(universalId) { + : peerId(peerId) + , migratedPeerId(migratedPeerId) + , type(type) + , universalId(universalId) { Expects(base::get_if(&universalId) != nullptr || type == Type::ChatPhoto); } @@ -233,6 +90,9 @@ public: && (type == other.type) && (universalId == other.universalId); } + bool operator!=(const Key &other) const { + return !(*this == other); + } PeerId peerId = 0; PeerId migratedPeerId = 0; @@ -244,8 +104,8 @@ public: SharedMediaWithLastSlice(Key key); SharedMediaWithLastSlice( Key key, - SharedMediaMergedSlice slice, - base::optional ending); + SparseIdsMergedSlice slice, + base::optional ending); base::optional fullCount() const; base::optional skippedBefore() const; @@ -255,41 +115,37 @@ public: Value operator[](int index) const; base::optional distance(const Key &a, const Key &b) const; - QString debug() const; - - static SharedMediaMergedSlice::Key ViewerKey(const Key &key) { + static SparseIdsMergedSlice::Key ViewerKey(const Key &key) { return { key.peerId, key.migratedPeerId, - key.type, base::get_if(&key.universalId) - ? (*base::get_if(&key.universalId)) - : ServerMaxMsgId - 1 + ? (*base::get_if(&key.universalId)) + : ServerMaxMsgId - 1 }; } - static SharedMediaMergedSlice::Key EndingKey(const Key &key) { + static SparseIdsMergedSlice::Key EndingKey(const Key &key) { return { key.peerId, key.migratedPeerId, - key.type, ServerMaxMsgId - 1 }; } private: - static base::optional EndingSlice(const Key &key) { + static base::optional EndingSlice(const Key &key) { return base::get_if(&key.universalId) - ? base::make_optional(SharedMediaMergedSlice(EndingKey(key))) + ? base::make_optional(SparseIdsMergedSlice(EndingKey(key))) : base::none; } static PhotoId LastPeerPhotoId(PeerId peerId); static base::optional IsLastIsolated( - const SharedMediaMergedSlice &slice, - const base::optional &ending, + const SparseIdsMergedSlice &slice, + const base::optional &ending, PhotoId lastPeerPhotoId); static base::optional LastFullMsgId( - const SharedMediaMergedSlice &slice); + const SparseIdsMergedSlice &slice); static base::optional Add( const base::optional &a, const base::optional &b) { @@ -318,13 +174,11 @@ private: } Key _key; - SharedMediaMergedSlice _slice; - base::optional _ending; + SparseIdsMergedSlice _slice; + base::optional _ending; PhotoId _lastPhotoId = 0; base::optional _isolatedLastPhoto; - friend class SharedMediaWithLastSliceBuilder; - }; rpl::producer SharedMediaWithLastViewer( diff --git a/Telegram/SourceFiles/history/history_sparse_ids.cpp b/Telegram/SourceFiles/history/history_sparse_ids.cpp new file mode 100644 index 000000000..8baa1d3c2 --- /dev/null +++ b/Telegram/SourceFiles/history/history_sparse_ids.cpp @@ -0,0 +1,391 @@ +/* +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 +*/ +#include "history/history_sparse_ids.h" + +#include "storage/storage_sparse_ids_list.h" + +SparseIdsSlice::SparseIdsSlice( + const base::flat_set &ids, + MsgRange range, + base::optional fullCount, + base::optional skippedBefore, + base::optional skippedAfter) +: _ids(ids) +, _range(range) +, _fullCount(fullCount) +, _skippedBefore(skippedBefore) +, _skippedAfter(skippedAfter) { +} + +base::optional SparseIdsSlice::indexOf(MsgId msgId) const { + auto it = _ids.find(msgId); + if (it != _ids.end()) { + return (it - _ids.begin()); + } + return base::none; +} + +MsgId SparseIdsSlice::operator[](int index) const { + Expects(index >= 0 && index < size()); + + return *(_ids.begin() + index); +} + +base::optional SparseIdsSlice::distance( + MsgId a, + MsgId b) const { + if (auto i = indexOf(a)) { + if (auto j = indexOf(b)) { + return *j - *i; + } + } + return base::none; +} + +base::optional SparseIdsSlice::nearest(MsgId msgId) const { + if (auto it = base::lower_bound(_ids, msgId); it != _ids.end()) { + return *it; + } else if (_ids.empty()) { + return base::none; + } + return _ids.back(); +} + +SparseIdsMergedSlice::SparseIdsMergedSlice(Key key) +: SparseIdsMergedSlice( + key, + SparseIdsSlice(), + MigratedSlice(key)) { +} + +SparseIdsMergedSlice::SparseIdsMergedSlice( + Key key, + SparseIdsSlice part, + base::optional migrated) +: _key(key) +, _part(std::move(part)) +, _migrated(std::move(migrated)) { +} + +base::optional SparseIdsMergedSlice::fullCount() const { + return Add( + _part.fullCount(), + _migrated ? _migrated->fullCount() : 0); +} + +base::optional SparseIdsMergedSlice::skippedBefore() const { + return Add( + isolatedInMigrated() ? 0 : _part.skippedBefore(), + _migrated + ? (isolatedInPart() + ? _migrated->fullCount() + : _migrated->skippedBefore()) + : 0 + ); +} + +base::optional SparseIdsMergedSlice::skippedAfter() const { + return Add( + isolatedInMigrated() ? _part.fullCount() : _part.skippedAfter(), + isolatedInPart() ? 0 : _migrated->skippedAfter() + ); +} + +base::optional SparseIdsMergedSlice::indexOf( + FullMsgId fullId) const { + return isFromPart(fullId) + ? (_part.indexOf(fullId.msg) | func::add(migratedSize())) + : isolatedInPart() + ? base::none + : isFromMigrated(fullId) + ? _migrated->indexOf(fullId.msg) + : base::none; +} + +int SparseIdsMergedSlice::size() const { + return (isolatedInPart() ? 0 : migratedSize()) + + (isolatedInMigrated() ? 0 : _part.size()); +} + +FullMsgId SparseIdsMergedSlice::operator[](int index) const { + Expects(index >= 0 && index < size()); + + if (auto size = migratedSize()) { + if (index < size) { + return ComputeId(_key.migratedPeerId, (*_migrated)[index]); + } + index -= size; + } + return ComputeId(_key.peerId, _part[index]); +} + +base::optional SparseIdsMergedSlice::distance( + const Key &a, + const Key &b) const { + if (auto i = indexOf(ComputeId(a))) { + if (auto j = indexOf(ComputeId(b))) { + return *j - *i; + } + } + return base::none; +} + +auto SparseIdsMergedSlice::nearest( + UniversalMsgId id) const -> base::optional { + auto convertFromMigratedNearest = [](MsgId result) { + return result - ServerMaxMsgId; + }; + if (IsServerMsgId(id)) { + if (auto partNearestId = _part.nearest(id)) { + return partNearestId; + } else if (isolatedInPart()) { + return base::none; + } + return _migrated->nearest(ServerMaxMsgId - 1) + | convertFromMigratedNearest; + } + if (auto migratedNearestId = _migrated + ? _migrated->nearest(id + ServerMaxMsgId) + : base::none) { + return migratedNearestId + | convertFromMigratedNearest; + } else if (isolatedInMigrated()) { + return base::none; + } + return _part.nearest(0); +} + +SparseIdsSliceBuilder::SparseIdsSliceBuilder( + Key key, + int limitBefore, + int limitAfter) +: _key(key) +, _limitBefore(limitBefore) +, _limitAfter(limitAfter) { +} + +bool SparseIdsSliceBuilder::applyInitial( + const Storage::SparseIdsListResult &result) { + mergeSliceData( + result.count, + result.messageIds, + result.skippedBefore, + result.skippedAfter); + return true; +} + +bool SparseIdsSliceBuilder::applyUpdate( + const Storage::SparseIdsSliceUpdate &update) { + auto intersects = [](MsgRange range1, MsgRange range2) { + return (range1.from <= range2.till) + && (range2.from <= range1.till); + }; + auto needMergeMessages = (update.messages != nullptr) + && intersects(update.range, { + _ids.empty() ? _key : _ids.front(), + _ids.empty() ? _key : _ids.back() + }); + if (!needMergeMessages && !update.count) { + return false; + } + auto skippedBefore = (update.range.from == 0) + ? 0 + : base::optional {}; + auto skippedAfter = (update.range.till == ServerMaxMsgId) + ? 0 + : base::optional {}; + mergeSliceData( + update.count, + needMergeMessages + ? *update.messages + : base::flat_set {}, + skippedBefore, + skippedAfter); + return true; +} + +bool SparseIdsSliceBuilder::removeOne(MsgId messageId) { + auto changed = false; + if (_fullCount && *_fullCount > 0) { + --*_fullCount; + changed = true; + } + if (_ids.contains(messageId)) { + _ids.remove(messageId); + changed = true; + } else if (!_ids.empty()) { + if (_ids.front() > messageId + && _skippedBefore + && *_skippedBefore > 0) { + --*_skippedBefore; + changed = true; + } else if (_ids.back() < messageId + && _skippedAfter + && *_skippedAfter > 0) { + --*_skippedAfter; + changed = true; + } + } + return changed; +} + +bool SparseIdsSliceBuilder::removeAll() { + _ids = {}; + _range = { 0, ServerMaxMsgId }; + _fullCount = 0; + _skippedBefore = 0; + _skippedAfter = 0; + return true; +} + +void SparseIdsSliceBuilder::checkInsufficient() { + sliceToLimits(); +} + +void SparseIdsSliceBuilder::mergeSliceData( + base::optional count, + const base::flat_set &messageIds, + base::optional skippedBefore, + base::optional skippedAfter) { + if (messageIds.empty()) { + if (count && _fullCount != count) { + _fullCount = count; + if (*_fullCount <= _ids.size()) { + _fullCount = _ids.size(); + _skippedBefore = _skippedAfter = 0; + } + } + fillSkippedAndSliceToLimits(); + return; + } + if (count) { + _fullCount = count; + } + auto wasMinId = _ids.empty() ? -1 : _ids.front(); + auto wasMaxId = _ids.empty() ? -1 : _ids.back(); + _ids.merge(messageIds.begin(), messageIds.end()); + + auto adjustSkippedBefore = [&](MsgId oldId, int oldSkippedBefore) { + auto it = _ids.find(oldId); + Assert(it != _ids.end()); + _skippedBefore = oldSkippedBefore - (it - _ids.begin()); + accumulate_max(*_skippedBefore, 0); + }; + if (skippedBefore) { + adjustSkippedBefore(messageIds.front(), *skippedBefore); + } else if (wasMinId >= 0 && _skippedBefore) { + adjustSkippedBefore(wasMinId, *_skippedBefore); + } else { + _skippedBefore = base::none; + } + + auto adjustSkippedAfter = [&](MsgId oldId, int oldSkippedAfter) { + auto it = _ids.find(oldId); + Assert(it != _ids.end()); + _skippedAfter = oldSkippedAfter - (_ids.end() - it - 1); + accumulate_max(*_skippedAfter, 0); + }; + if (skippedAfter) { + adjustSkippedAfter(messageIds.back(), *skippedAfter); + } else if (wasMaxId >= 0 && _skippedAfter) { + adjustSkippedAfter(wasMaxId, *_skippedAfter); + } else { + _skippedAfter = base::none; + } + fillSkippedAndSliceToLimits(); +} + +void SparseIdsSliceBuilder::fillSkippedAndSliceToLimits() { + if (_fullCount) { + if (_skippedBefore && !_skippedAfter) { + _skippedAfter = *_fullCount + - *_skippedBefore + - int(_ids.size()); + } else if (_skippedAfter && !_skippedBefore) { + _skippedBefore = *_fullCount + - *_skippedAfter + - int(_ids.size()); + } + } + sliceToLimits(); +} + +void SparseIdsSliceBuilder::sliceToLimits() { + if (!_key) { + if (!_fullCount) { + requestMessagesCount(); + } + return; + } + auto requestedSomething = false; + auto aroundIt = base::lower_bound(_ids, _key); + auto removeFromBegin = (aroundIt - _ids.begin() - _limitBefore); + auto removeFromEnd = (_ids.end() - aroundIt - _limitAfter - 1); + if (removeFromBegin > 0) { + _ids.erase(_ids.begin(), _ids.begin() + removeFromBegin); + if (_skippedBefore) { + *_skippedBefore += removeFromBegin; + } + } else if (removeFromBegin < 0 + && (!_skippedBefore || *_skippedBefore > 0)) { + requestedSomething = true; + requestMessages(RequestDirection::Before); + } + if (removeFromEnd > 0) { + _ids.erase(_ids.end() - removeFromEnd, _ids.end()); + if (_skippedAfter) { + *_skippedAfter += removeFromEnd; + } + } else if (removeFromEnd < 0 + && (!_skippedAfter || *_skippedAfter > 0)) { + requestedSomething = true; + requestMessages(RequestDirection::After); + } + if (!_fullCount && !requestedSomething) { + requestMessagesCount(); + } +} + +void SparseIdsSliceBuilder::requestMessages( + RequestDirection direction) { + auto requestAroundData = [&]() -> AroundData { + if (_ids.empty()) { + return { _key, SparseIdsLoadDirection::Around }; + } else if (direction == RequestDirection::Before) { + return { _ids.front(), SparseIdsLoadDirection::Before }; + } + return { _ids.back(), SparseIdsLoadDirection::After }; + }; + _insufficientAround.fire(requestAroundData()); +} + +void SparseIdsSliceBuilder::requestMessagesCount() { + _insufficientAround.fire({ 0, SparseIdsLoadDirection::Around }); +} + +SparseIdsSlice SparseIdsSliceBuilder::snapshot() const { + return SparseIdsSlice( + _ids, + _range, + _fullCount, + _skippedBefore, + _skippedAfter); +} diff --git a/Telegram/SourceFiles/history/history_sparse_ids.h b/Telegram/SourceFiles/history/history_sparse_ids.h new file mode 100644 index 000000000..9b9e965de --- /dev/null +++ b/Telegram/SourceFiles/history/history_sparse_ids.h @@ -0,0 +1,213 @@ +/* +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 Storage { +struct SparseIdsListResult; +struct SparseIdsSliceUpdate; +} // namespace Storage + +enum class SparseIdsLoadDirection { + Around, + Before, + After, +}; + +class SparseIdsSlice { +public: + using Key = MsgId; + + SparseIdsSlice() = default; + SparseIdsSlice( + const base::flat_set &ids, + MsgRange range, + base::optional fullCount, + base::optional skippedBefore, + base::optional skippedAfter); + + base::optional fullCount() const { return _fullCount; } + base::optional skippedBefore() const { return _skippedBefore; } + base::optional skippedAfter() const { return _skippedAfter; } + base::optional indexOf(MsgId msgId) const; + int size() const { return _ids.size(); } + MsgId operator[](int index) const; + base::optional distance(MsgId a, MsgId b) const; + base::optional nearest(MsgId msgId) const; + +private: + base::flat_set _ids; + MsgRange _range; + base::optional _fullCount; + base::optional _skippedBefore; + base::optional _skippedAfter; + +}; + +class SparseIdsMergedSlice { +public: + using UniversalMsgId = MsgId; + struct Key { + Key( + PeerId peerId, + PeerId migratedPeerId, + UniversalMsgId universalId) + : peerId(peerId) + , migratedPeerId(migratedPeerId) + , universalId(universalId) { + } + + bool operator==(const Key &other) const { + return (peerId == other.peerId) + && (migratedPeerId == other.migratedPeerId) + && (universalId == other.universalId); + } + + PeerId peerId = 0; + PeerId migratedPeerId = 0; + UniversalMsgId universalId = 0; + + }; + + SparseIdsMergedSlice(Key key); + SparseIdsMergedSlice( + Key key, + SparseIdsSlice part, + base::optional migrated); + + base::optional fullCount() const; + base::optional skippedBefore() const; + base::optional skippedAfter() const; + base::optional indexOf(FullMsgId fullId) const; + int size() const; + FullMsgId operator[](int index) const; + base::optional distance(const Key &a, const Key &b) const; + base::optional nearest(UniversalMsgId id) const; + + static SparseIdsSlice::Key PartKey(const Key &key) { + return (key.universalId < 0) ? 1 : key.universalId; + } + static SparseIdsSlice::Key MigratedKey(const Key &key) { + return (key.universalId < 0) + ? (ServerMaxMsgId + key.universalId) + : (key.universalId > 0) ? (ServerMaxMsgId - 1) : 0; + } + +private: + static base::optional MigratedSlice(const Key &key) { + return key.migratedPeerId + ? base::make_optional(SparseIdsSlice()) + : base::none; + } + + static bool IsFromSlice(PeerId peerId, FullMsgId fullId) { + return peerIsChannel(peerId) + ? (peerId == peerFromChannel(fullId.channel)) + : !fullId.channel; + } + static FullMsgId ComputeId(PeerId peerId, MsgId msgId) { + return FullMsgId( + peerIsChannel(peerId) ? peerToBareInt(peerId) : 0, + msgId); + } + static FullMsgId ComputeId(const Key &key) { + return (key.universalId >= 0) + ? ComputeId(key.peerId, key.universalId) + : ComputeId(key.migratedPeerId, ServerMaxMsgId + key.universalId); + } + static base::optional Add( + const base::optional &a, + const base::optional &b) { + return (a && b) ? base::make_optional(*a + *b) : base::none; + } + + bool isFromPart(FullMsgId fullId) const { + return IsFromSlice(_key.peerId, fullId); + } + bool isFromMigrated(FullMsgId fullId) const { + return _migrated + ? IsFromSlice(_key.migratedPeerId, fullId) + : false; + } + int migratedSize() const { + return isolatedInPart() ? 0 : _migrated->size(); + } + bool isolatedInPart() const { + return IsServerMsgId(_key.universalId) + && (!_migrated || _part.skippedBefore() != 0); + } + bool isolatedInMigrated() const { + return IsServerMsgId(ServerMaxMsgId + _key.universalId) + && (_migrated->skippedAfter() != 0); + } + + Key _key; + SparseIdsSlice _part; + base::optional _migrated; + +}; + +class SparseIdsSliceBuilder { +public: + using Key = SparseIdsSlice::Key; + + SparseIdsSliceBuilder(Key key, int limitBefore, int limitAfter); + + bool applyInitial(const Storage::SparseIdsListResult &result); + bool applyUpdate(const Storage::SparseIdsSliceUpdate &update); + bool removeOne(MsgId messageId); + bool removeAll(); + + void checkInsufficient(); + using AroundData = std::pair; + auto insufficientAround() const { + return _insufficientAround.events(); + } + + SparseIdsSlice snapshot() const; + +private: + enum class RequestDirection { + Before, + After, + }; + void requestMessages(RequestDirection direction); + void requestMessagesCount(); + void fillSkippedAndSliceToLimits(); + void sliceToLimits(); + + void mergeSliceData( + base::optional count, + const base::flat_set &messageIds, + base::optional skippedBefore = base::none, + base::optional skippedAfter = base::none); + + Key _key; + base::flat_set _ids; + MsgRange _range; + base::optional _fullCount; + base::optional _skippedBefore; + base::optional _skippedAfter; + int _limitBefore = 0; + int _limitAfter = 0; + + rpl::event_stream _insufficientAround; + +}; diff --git a/Telegram/SourceFiles/info/media/info_media_list_widget.cpp b/Telegram/SourceFiles/info/media/info_media_list_widget.cpp index 89974fd1c..ffb2f64b4 100644 --- a/Telegram/SourceFiles/info/media/info_media_list_widget.cpp +++ b/Telegram/SourceFiles/info/media/info_media_list_widget.cpp @@ -738,27 +738,29 @@ void ListWidget::invalidatePaletteCache() { } } -SharedMediaMergedSlice::Key ListWidget::sliceKey( +SparseIdsMergedSlice::Key ListWidget::sliceKey( UniversalMsgId universalId) const { - using Key = SharedMediaMergedSlice::Key; + using Key = SparseIdsMergedSlice::Key; if (auto migrateFrom = _peer->migrateFrom()) { - return Key(_peer->id, migrateFrom->id, _type, universalId); + return Key(_peer->id, migrateFrom->id, universalId); } if (universalId < 0) { // Convert back to plain id for non-migrated histories. universalId += ServerMaxMsgId; } - return Key(_peer->id, 0, _type, universalId); + return Key(_peer->id, 0, universalId); } void ListWidget::refreshViewer() { _viewerLifetime.destroy(); SharedMediaMergedViewer( - sliceKey(_universalAroundId), + SharedMediaMergedKey( + sliceKey(_universalAroundId), + _type), _idsLimit, _idsLimit) | rpl::start_with_next([this]( - SharedMediaMergedSlice &&slice) { + SparseIdsMergedSlice &&slice) { _slice = std::move(slice); if (auto nearest = _slice.nearest(_universalAroundId)) { _universalAroundId = *nearest; diff --git a/Telegram/SourceFiles/info/media/info_media_list_widget.h b/Telegram/SourceFiles/info/media/info_media_list_widget.h index 1a54f0e1b..eb3df5682 100644 --- a/Telegram/SourceFiles/info/media/info_media_list_widget.h +++ b/Telegram/SourceFiles/info/media/info_media_list_widget.h @@ -178,7 +178,7 @@ private: void refreshViewer(); void invalidatePaletteCache(); void refreshRows(); - SharedMediaMergedSlice::Key sliceKey( + SparseIdsMergedSlice::Key sliceKey( UniversalMsgId universalId) const; BaseLayout *getLayout(UniversalMsgId universalId); BaseLayout *getExistingLayout(UniversalMsgId universalId) const; @@ -280,7 +280,7 @@ private: static constexpr auto kDefaultAroundId = (ServerMaxMsgId - 1); UniversalMsgId _universalAroundId = kDefaultAroundId; int _idsLimit = kMinimalIdsLimit; - SharedMediaMergedSlice _slice; + SparseIdsMergedSlice _slice; std::map _layouts; std::vector
_sections; diff --git a/Telegram/SourceFiles/info/profile/info_profile_values.cpp b/Telegram/SourceFiles/info/profile/info_profile_values.cpp index ec8adf381..a78cd2e0b 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_values.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_values.cpp @@ -172,14 +172,15 @@ rpl::producer SharedMediaCountValue( auto aroundId = 0; auto limit = 0; auto updated = SharedMediaMergedViewer( - SharedMediaMergedSlice::Key( - real->id, - migrated ? migrated->id : 0, - type, - aroundId), + SharedMediaMergedKey( + SparseIdsMergedSlice::Key( + real->id, + migrated ? migrated->id : 0, + aroundId), + type), limit, limit) - | rpl::map([](const SharedMediaMergedSlice &slice) { + | rpl::map([](const SparseIdsMergedSlice &slice) { return slice.fullCount(); }) | rpl::filter_optional(); diff --git a/Telegram/SourceFiles/mediaview.cpp b/Telegram/SourceFiles/mediaview.cpp index afa77415b..85cfaf3ba 100644 --- a/Telegram/SourceFiles/mediaview.cpp +++ b/Telegram/SourceFiles/mediaview.cpp @@ -1035,14 +1035,27 @@ bool MediaView::validSharedMedia() const { if (!_sharedMedia) { return false; } - auto countDistanceInData = [](const auto &a, const auto &b) { + using Key = SharedMediaWithLastSlice::Key; + auto inSameDomain = [](const Key &a, const Key &b) { + return (a.type == b.type) + && (a.peerId == b.peerId) + && (a.migratedPeerId == b.migratedPeerId); + }; + auto countDistanceInData = [&](const Key &a, const Key &b) { return [&](const SharedMediaWithLastSlice &data) { - return data.distance(a, b); + return inSameDomain(a, b) + ? data.distance(a, b) + : base::optional(); }; }; - auto distance = (key == _sharedMedia->key) ? 0 : - _sharedMediaData + if (key == _sharedMedia->key) { + return true; + } else if (!_sharedMediaDataKey + || _sharedMedia->key != *_sharedMediaDataKey) { + return false; + } + auto distance = _sharedMediaData | countDistanceInData(*key, _sharedMedia->key) | func::abs; if (distance) { @@ -1066,14 +1079,17 @@ void MediaView::validateSharedMedia() { } else { _sharedMedia = nullptr; _sharedMediaData = base::none; + _sharedMediaDataKey = base::none; } } void MediaView::handleSharedMediaUpdate(SharedMediaWithLastSlice &&update) { if ((!_photo && !_doc) || !_sharedMedia) { _sharedMediaData = base::none; + _sharedMediaDataKey = base::none; } else { _sharedMediaData = std::move(update); + _sharedMediaDataKey = _sharedMedia->key; } findCurrent(); updateControls(); @@ -2733,6 +2749,7 @@ void MediaView::setVisible(bool visible) { if (!visible) { _sharedMedia = nullptr; _sharedMediaData = base::none; + _sharedMediaDataKey = base::none; _userPhotos = nullptr; _userPhotosData = base::none; if (_menu) _menu->hideMenu(true); diff --git a/Telegram/SourceFiles/mediaview.h b/Telegram/SourceFiles/mediaview.h index 1fe4aa088..532475867 100644 --- a/Telegram/SourceFiles/mediaview.h +++ b/Telegram/SourceFiles/mediaview.h @@ -252,6 +252,7 @@ private: DocumentData *_doc = nullptr; std::unique_ptr _sharedMedia; base::optional _sharedMediaData; + base::optional _sharedMediaDataKey; std::unique_ptr _userPhotos; base::optional _userPhotosData; diff --git a/Telegram/SourceFiles/mtproto/sender.h b/Telegram/SourceFiles/mtproto/sender.h index d061dee17..7bd6f6a70 100644 --- a/Telegram/SourceFiles/mtproto/sender.h +++ b/Telegram/SourceFiles/mtproto/sender.h @@ -279,12 +279,15 @@ public: }; - template ::value>, typename = typename Request::Unboxed> + template < + typename Request, + typename = std::enable_if_t>, + typename = typename Request::Unboxed> [[nodiscard]] SpecificRequestBuilder request(Request &&request) noexcept; [[nodiscard]] SentRequestWrap request(mtpRequestId requestId) noexcept; - [[nodiscard]] decltype(auto) requestCanceller() noexcept { + [[nodiscard]] auto requestCanceller() noexcept { return [this](mtpRequestId requestId) { request(requestId).cancel(); }; @@ -305,7 +308,20 @@ public: private: class RequestWrap { public: - RequestWrap(Instance *instance, mtpRequestId requestId) noexcept : _id(requestId) { + RequestWrap( + Instance *instance, + mtpRequestId requestId) noexcept + : _id(requestId) { + } + + RequestWrap(const RequestWrap &other) = delete; + RequestWrap &operator=(const RequestWrap &other) = delete; + RequestWrap(RequestWrap &&other) : _id(base::take(other._id)) { + } + RequestWrap &operator=(RequestWrap &&other) { + cancelRequest(); + _id = base::take(other._id); + return *this; } mtpRequestId id() const noexcept { @@ -315,12 +331,17 @@ private: } ~RequestWrap() { - if (auto instance = MainInstance()) { - instance->cancel(_id); - } + cancelRequest(); } private: + void cancelRequest() { + if (_id) { + if (auto instance = MainInstance()) { + instance->cancel(_id); + } + } + } mtpRequestId _id = 0; }; @@ -370,7 +391,7 @@ private: } } - std::set _requests; // Better to use flatmap. + base::flat_set _requests; }; diff --git a/Telegram/SourceFiles/storage/storage_facade.h b/Telegram/SourceFiles/storage/storage_facade.h index d131dfa61..f1d28593e 100644 --- a/Telegram/SourceFiles/storage/storage_facade.h +++ b/Telegram/SourceFiles/storage/storage_facade.h @@ -25,13 +25,15 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org namespace Storage { +struct SparseIdsListResult; + struct SharedMediaAddNew; struct SharedMediaAddExisting; struct SharedMediaAddSlice; struct SharedMediaRemoveOne; struct SharedMediaRemoveAll; struct SharedMediaQuery; -struct SharedMediaResult; +using SharedMediaResult = SparseIdsListResult; struct SharedMediaSliceUpdate; struct UserPhotosAddNew; diff --git a/Telegram/SourceFiles/storage/storage_shared_media.cpp b/Telegram/SourceFiles/storage/storage_shared_media.cpp index cf0479128..809925285 100644 --- a/Telegram/SourceFiles/storage/storage_shared_media.cpp +++ b/Telegram/SourceFiles/storage/storage_shared_media.cpp @@ -20,213 +20,11 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org */ #include "storage/storage_shared_media.h" +#include #include "base/task_queue.h" namespace Storage { -SharedMedia::List::Slice::Slice( - base::flat_set &&messages, - MsgRange range) - : messages(std::move(messages)) - , range(range) { -} - -template -void SharedMedia::List::Slice::merge( - const Range &moreMessages, - MsgRange moreNoSkipRange) { - Expects(moreNoSkipRange.from <= range.till); - Expects(range.from <= moreNoSkipRange.till); - - messages.merge(std::begin(moreMessages), std::end(moreMessages)); - range = { - qMin(range.from, moreNoSkipRange.from), - qMax(range.till, moreNoSkipRange.till) - }; -} - -template -int SharedMedia::List::uniteAndAdd( - SliceUpdate &update, - base::flat_set::iterator uniteFrom, - base::flat_set::iterator uniteTill, - const Range &messages, - MsgRange noSkipRange) { - auto uniteFromIndex = uniteFrom - _slices.begin(); - auto was = uniteFrom->messages.size(); - _slices.modify(uniteFrom, [&](Slice &slice) { - slice.merge(messages, noSkipRange); - }); - auto firstToErase = uniteFrom + 1; - if (firstToErase != uniteTill) { - for (auto it = firstToErase; it != uniteTill; ++it) { - _slices.modify(uniteFrom, [&](Slice &slice) { - slice.merge(it->messages, it->range); - }); - } - _slices.erase(firstToErase, uniteTill); - uniteFrom = _slices.begin() + uniteFromIndex; - } - update.messages = &uniteFrom->messages; - update.range = uniteFrom->range; - return uniteFrom->messages.size() - was; -} - -template -int SharedMedia::List::addRangeItemsAndCountNew( - SliceUpdate &update, - const Range &messages, - MsgRange noSkipRange) { - Expects((noSkipRange.from < noSkipRange.till) - || (noSkipRange.from == noSkipRange.till && messages.begin() == messages.end())); - if (noSkipRange.from == noSkipRange.till) { - return 0; - } - - auto uniteFrom = base::lower_bound( - _slices, - noSkipRange.from, - [](const Slice &slice, MsgId from) { return slice.range.till < from; }); - auto uniteTill = base::upper_bound( - _slices, - noSkipRange.till, - [](MsgId till, const Slice &slice) { return till < slice.range.from; }); - if (uniteFrom < uniteTill) { - return uniteAndAdd(update, uniteFrom, uniteTill, messages, noSkipRange); - } - - auto sliceMessages = base::flat_set { - std::begin(messages), - std::end(messages) }; - auto slice = _slices.emplace( - std::move(sliceMessages), - noSkipRange); - update.messages = &slice->messages; - update.range = slice->range; - return slice->messages.size(); -} - -template -void SharedMedia::List::addRange( - const Range &messages, - MsgRange noSkipRange, - base::optional count, - bool incrementCount) { - Expects(!count || !incrementCount); - - auto wasCount = _count; - auto update = SliceUpdate(); - auto result = addRangeItemsAndCountNew(update, messages, noSkipRange); - if (count) { - _count = count; - } else if (incrementCount && _count && result > 0) { - *_count += result; - } - if (_slices.size() == 1) { - if (_slices.front().range == MsgRange { 0, ServerMaxMsgId }) { - _count = _slices.front().messages.size(); - } - } - update.count = _count; - _sliceUpdated.fire(std::move(update)); -} - -void SharedMedia::List::addNew(MsgId messageId) { - auto range = { messageId }; - addRange(range, { messageId, ServerMaxMsgId }, base::none, true); -} - -void SharedMedia::List::addExisting( - MsgId messageId, - MsgRange noSkipRange) { - auto range = { messageId }; - addRange(range, noSkipRange, base::none); -} - -void SharedMedia::List::addSlice( - std::vector &&messageIds, - MsgRange noSkipRange, - base::optional count) { - addRange(messageIds, noSkipRange, count); -} - -void SharedMedia::List::removeOne(MsgId messageId) { - auto slice = base::lower_bound( - _slices, - messageId, - [](const Slice &slice, MsgId from) { return slice.range.till < from; }); - if (slice != _slices.end() && slice->range.from <= messageId) { - _slices.modify(slice, [messageId](Slice &slice) { - return slice.messages.remove(messageId); - }); - } - if (_count) { - --*_count; - } -} - -void SharedMedia::List::removeAll() { - _slices.clear(); - _slices.emplace(base::flat_set{}, MsgRange { 0, ServerMaxMsgId }); - _count = 0; -} - -rpl::producer SharedMedia::List::query( - SharedMediaQuery &&query) const { - return [this, query = std::move(query)](auto consumer) { - auto slice = query.key.messageId - ? base::lower_bound( - _slices, - query.key.messageId, - [](const Slice &slice, MsgId id) { - return slice.range.till < id; - }) - : _slices.end(); - if (slice != _slices.end() - && slice->range.from <= query.key.messageId) { - consumer.put_next(queryFromSlice(query, *slice)); - } else if (_count) { - auto result = SharedMediaResult {}; - result.count = _count; - consumer.put_next(std::move(result)); - } - consumer.put_done(); - return rpl::lifetime(); - }; -} - -SharedMediaResult SharedMedia::List::queryFromSlice( - const SharedMediaQuery &query, - const Slice &slice) const { - auto result = SharedMediaResult {}; - auto position = base::lower_bound(slice.messages, query.key.messageId); - auto haveBefore = int(position - slice.messages.begin()); - auto haveEqualOrAfter = int(slice.messages.end() - position); - auto before = qMin(haveBefore, query.limitBefore); - auto equalOrAfter = qMin(haveEqualOrAfter, query.limitAfter + 1); - auto ids = std::vector(position - before, position + equalOrAfter); - result.messageIds.merge(ids.begin(), ids.end()); - if (slice.range.from == 0) { - result.skippedBefore = haveBefore - before; - } - if (slice.range.till == ServerMaxMsgId) { - result.skippedAfter = haveEqualOrAfter - equalOrAfter; - } - if (_count) { - result.count = _count; - if (!result.skippedBefore && result.skippedAfter) { - result.skippedBefore = *result.count - - *result.skippedAfter - - int(result.messageIds.size()); - } else if (!result.skippedAfter && result.skippedBefore) { - result.skippedAfter = *result.count - - *result.skippedBefore - - int(result.messageIds.size()); - } - } - return result; -} - std::map::iterator SharedMedia::enforceLists(PeerId peer) { auto result = _lists.find(peer); @@ -239,15 +37,13 @@ std::map::iterator auto type = static_cast(index); list.sliceUpdated() - | rpl::start_with_next([this, peer, type]( - const SliceUpdate &update) { - _sliceUpdated.fire(SharedMediaSliceUpdate( + | rpl::map([=](const SparseIdsSliceUpdate &update) { + return SharedMediaSliceUpdate( peer, type, - update.messages, - update.range, - update.count)); - }, _lifetime); + update); + }) + | rpl::start_to_stream(_sliceUpdated, _lifetime); } return result; } @@ -308,7 +104,10 @@ rpl::producer SharedMedia::query(SharedMediaQuery &&query) co auto peerIt = _lists.find(query.key.peerId); if (peerIt != _lists.end()) { auto index = static_cast(query.key.type); - return peerIt->second[index].query(std::move(query)); + return peerIt->second[index].query(SparseIdsListQuery( + query.key.messageId, + query.limitBefore, + query.limitAfter)); } return [](auto consumer) { consumer.put_done(); diff --git a/Telegram/SourceFiles/storage/storage_shared_media.h b/Telegram/SourceFiles/storage/storage_shared_media.h index 336729cf8..62a8140f7 100644 --- a/Telegram/SourceFiles/storage/storage_shared_media.h +++ b/Telegram/SourceFiles/storage/storage_shared_media.h @@ -22,6 +22,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include #include "storage/storage_facade.h" +#include "storage/storage_sparse_ids_list.h" namespace Storage { @@ -105,9 +106,9 @@ struct SharedMediaRemoveOne { PeerId peerId, SharedMediaTypesMask types, MsgId messageId) - : peerId(peerId) - , messageId(messageId) - , types(types) { + : peerId(peerId) + , messageId(messageId) + , types(types) { } PeerId peerId = 0; @@ -129,9 +130,9 @@ struct SharedMediaKey { PeerId peerId, SharedMediaType type, MsgId messageId) - : peerId(peerId) - , type(type) - , messageId(messageId) { + : peerId(peerId) + , type(type) + , messageId(messageId) { } bool operator==(const SharedMediaKey &other) const { @@ -154,9 +155,9 @@ struct SharedMediaQuery { SharedMediaKey key, int limitBefore, int limitAfter) - : key(key) - , limitBefore(limitBefore) - , limitAfter(limitAfter) { + : key(key) + , limitBefore(limitBefore) + , limitAfter(limitAfter) { } SharedMediaKey key; @@ -165,32 +166,21 @@ struct SharedMediaQuery { }; -struct SharedMediaResult { - base::optional count; - base::optional skippedBefore; - base::optional skippedAfter; - base::flat_set messageIds; -}; +using SharedMediaResult = SparseIdsListResult; struct SharedMediaSliceUpdate { SharedMediaSliceUpdate( PeerId peerId, SharedMediaType type, - const base::flat_set *messages, - MsgRange range, - base::optional count) - : peerId(peerId) - , type(type) - , messages(messages) - , range(range) - , count(count) { + const SparseIdsSliceUpdate &data) + : peerId(peerId) + , type(type) + , data(data) { } PeerId peerId = 0; SharedMediaType type = SharedMediaType::kCount; - const base::flat_set *messages = nullptr; - MsgRange range; - base::optional count; + SparseIdsSliceUpdate data; }; class SharedMedia { @@ -216,74 +206,7 @@ public: } private: - class List { - public: - void addNew(MsgId messageId); - void addExisting(MsgId messageId, MsgRange noSkipRange); - void addSlice( - std::vector &&messageIds, - MsgRange noSkipRange, - base::optional count); - void removeOne(MsgId messageId); - void removeAll(); - rpl::producer query(SharedMediaQuery &&query) const; - - struct SliceUpdate { - const base::flat_set *messages = nullptr; - MsgRange range; - base::optional count; - }; - rpl::producer sliceUpdated() const { - return _sliceUpdated.events(); - } - - private: - struct Slice { - Slice(base::flat_set &&messages, MsgRange range); - - template - void merge(const Range &moreMessages, MsgRange moreNoSkipRange); - - base::flat_set messages; - MsgRange range; - - inline bool operator<(const Slice &other) const { - return range.from < other.range.from; - } - - }; - - template - int uniteAndAdd( - SliceUpdate &update, - base::flat_set::iterator uniteFrom, - base::flat_set::iterator uniteTill, - const Range &messages, - MsgRange noSkipRange); - template - int addRangeItemsAndCountNew( - SliceUpdate &update, - const Range &messages, - MsgRange noSkipRange); - template - void addRange( - const Range &messages, - MsgRange noSkipRange, - base::optional count, - bool incrementCount = false); - - SharedMediaResult queryFromSlice( - const SharedMediaQuery &query, - const Slice &slice) const; - - base::optional _count; - base::flat_set _slices; - - rpl::event_stream _sliceUpdated; - - }; - using SliceUpdate = List::SliceUpdate; - using Lists = std::array; + using Lists = std::array; std::map::iterator enforceLists(PeerId peer); diff --git a/Telegram/SourceFiles/storage/storage_sparse_ids_list.cpp b/Telegram/SourceFiles/storage/storage_sparse_ids_list.cpp new file mode 100644 index 000000000..df11949f1 --- /dev/null +++ b/Telegram/SourceFiles/storage/storage_sparse_ids_list.cpp @@ -0,0 +1,228 @@ +/* +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 +*/ +#include "storage/storage_sparse_ids_list.h" + +namespace Storage { + +SparseIdsList::Slice::Slice( + base::flat_set &&messages, + MsgRange range) + : messages(std::move(messages)) + , range(range) { +} + +template +void SparseIdsList::Slice::merge( + const Range &moreMessages, + MsgRange moreNoSkipRange) { + Expects(moreNoSkipRange.from <= range.till); + Expects(range.from <= moreNoSkipRange.till); + + messages.merge(std::begin(moreMessages), std::end(moreMessages)); + range = { + qMin(range.from, moreNoSkipRange.from), + qMax(range.till, moreNoSkipRange.till) + }; +} + +template +int SparseIdsList::uniteAndAdd( + SparseIdsSliceUpdate &update, + base::flat_set::iterator uniteFrom, + base::flat_set::iterator uniteTill, + const Range &messages, + MsgRange noSkipRange) { + auto uniteFromIndex = uniteFrom - _slices.begin(); + auto was = uniteFrom->messages.size(); + _slices.modify(uniteFrom, [&](Slice &slice) { + slice.merge(messages, noSkipRange); + }); + auto firstToErase = uniteFrom + 1; + if (firstToErase != uniteTill) { + for (auto it = firstToErase; it != uniteTill; ++it) { + _slices.modify(uniteFrom, [&](Slice &slice) { + slice.merge(it->messages, it->range); + }); + } + _slices.erase(firstToErase, uniteTill); + uniteFrom = _slices.begin() + uniteFromIndex; + } + update.messages = &uniteFrom->messages; + update.range = uniteFrom->range; + return uniteFrom->messages.size() - was; +} + +template +int SparseIdsList::addRangeItemsAndCountNew( + SparseIdsSliceUpdate &update, + const Range &messages, + MsgRange noSkipRange) { + Expects((noSkipRange.from < noSkipRange.till) + || (noSkipRange.from == noSkipRange.till && messages.begin() == messages.end())); + if (noSkipRange.from == noSkipRange.till) { + return 0; + } + + auto uniteFrom = base::lower_bound( + _slices, + noSkipRange.from, + [](const Slice &slice, MsgId from) { return slice.range.till < from; }); + auto uniteTill = base::upper_bound( + _slices, + noSkipRange.till, + [](MsgId till, const Slice &slice) { return till < slice.range.from; }); + if (uniteFrom < uniteTill) { + return uniteAndAdd(update, uniteFrom, uniteTill, messages, noSkipRange); + } + + auto sliceMessages = base::flat_set { + std::begin(messages), + std::end(messages) }; + auto slice = _slices.emplace( + std::move(sliceMessages), + noSkipRange); + update.messages = &slice->messages; + update.range = slice->range; + return slice->messages.size(); +} + +template +void SparseIdsList::addRange( + const Range &messages, + MsgRange noSkipRange, + base::optional count, + bool incrementCount) { + Expects(!count || !incrementCount); + + auto wasCount = _count; + auto update = SparseIdsSliceUpdate(); + auto result = addRangeItemsAndCountNew(update, messages, noSkipRange); + if (count) { + _count = count; + } else if (incrementCount && _count && result > 0) { + *_count += result; + } + if (_slices.size() == 1) { + if (_slices.front().range == MsgRange { 0, ServerMaxMsgId }) { + _count = _slices.front().messages.size(); + } + } + update.count = _count; + _sliceUpdated.fire(std::move(update)); +} + +void SparseIdsList::addNew(MsgId messageId) { + auto range = { messageId }; + addRange(range, { messageId, ServerMaxMsgId }, base::none, true); +} + +void SparseIdsList::addExisting( + MsgId messageId, + MsgRange noSkipRange) { + auto range = { messageId }; + addRange(range, noSkipRange, base::none); +} + +void SparseIdsList::addSlice( + std::vector &&messageIds, + MsgRange noSkipRange, + base::optional count) { + addRange(messageIds, noSkipRange, count); +} + +void SparseIdsList::removeOne(MsgId messageId) { + auto slice = base::lower_bound( + _slices, + messageId, + [](const Slice &slice, MsgId from) { return slice.range.till < from; }); + if (slice != _slices.end() && slice->range.from <= messageId) { + _slices.modify(slice, [messageId](Slice &slice) { + return slice.messages.remove(messageId); + }); + } + if (_count) { + --*_count; + } +} + +void SparseIdsList::removeAll() { + _slices.clear(); + _slices.emplace(base::flat_set{}, MsgRange { 0, ServerMaxMsgId }); + _count = 0; +} + +rpl::producer SparseIdsList::query( + SparseIdsListQuery &&query) const { + return [this, query = std::move(query)](auto consumer) { + auto slice = query.aroundId + ? base::lower_bound( + _slices, + query.aroundId, + [](const Slice &slice, MsgId id) { + return slice.range.till < id; + }) + : _slices.end(); + if (slice != _slices.end() + && slice->range.from <= query.aroundId) { + consumer.put_next(queryFromSlice(query, *slice)); + } else if (_count) { + auto result = SparseIdsListResult {}; + result.count = _count; + consumer.put_next(std::move(result)); + } + consumer.put_done(); + return rpl::lifetime(); + }; +} + +SparseIdsListResult SparseIdsList::queryFromSlice( + const SparseIdsListQuery &query, + const Slice &slice) const { + auto result = SparseIdsListResult {}; + auto position = base::lower_bound(slice.messages, query.aroundId); + auto haveBefore = int(position - slice.messages.begin()); + auto haveEqualOrAfter = int(slice.messages.end() - position); + auto before = qMin(haveBefore, query.limitBefore); + auto equalOrAfter = qMin(haveEqualOrAfter, query.limitAfter + 1); + auto ids = std::vector(position - before, position + equalOrAfter); + result.messageIds.merge(ids.begin(), ids.end()); + if (slice.range.from == 0) { + result.skippedBefore = haveBefore - before; + } + if (slice.range.till == ServerMaxMsgId) { + result.skippedAfter = haveEqualOrAfter - equalOrAfter; + } + if (_count) { + result.count = _count; + if (!result.skippedBefore && result.skippedAfter) { + result.skippedBefore = *result.count + - *result.skippedAfter + - int(result.messageIds.size()); + } else if (!result.skippedAfter && result.skippedBefore) { + result.skippedAfter = *result.count + - *result.skippedBefore + - int(result.messageIds.size()); + } + } + return result; +} + +} // namespace Storage diff --git a/Telegram/SourceFiles/storage/storage_sparse_ids_list.h b/Telegram/SourceFiles/storage/storage_sparse_ids_list.h new file mode 100644 index 000000000..5714803e7 --- /dev/null +++ b/Telegram/SourceFiles/storage/storage_sparse_ids_list.h @@ -0,0 +1,116 @@ +/* +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 Storage { + +struct SparseIdsListQuery { + SparseIdsListQuery( + MsgId aroundId, + int limitBefore, + int limitAfter) + : aroundId(aroundId) + , limitBefore(limitBefore) + , limitAfter(limitAfter) { + } + + MsgId aroundId = 0; + int limitBefore = 0; + int limitAfter = 0; + +}; + +struct SparseIdsListResult { + base::optional count; + base::optional skippedBefore; + base::optional skippedAfter; + base::flat_set messageIds; +}; + +struct SparseIdsSliceUpdate { + const base::flat_set *messages = nullptr; + MsgRange range; + base::optional count; +}; + +class SparseIdsList { +public: + void addNew(MsgId messageId); + void addExisting(MsgId messageId, MsgRange noSkipRange); + void addSlice( + std::vector &&messageIds, + MsgRange noSkipRange, + base::optional count); + void removeOne(MsgId messageId); + void removeAll(); + rpl::producer query(SparseIdsListQuery &&query) const; + + rpl::producer sliceUpdated() const { + return _sliceUpdated.events(); + } + +private: + struct Slice { + Slice(base::flat_set &&messages, MsgRange range); + + template + void merge(const Range &moreMessages, MsgRange moreNoSkipRange); + + base::flat_set messages; + MsgRange range; + + inline bool operator<(const Slice &other) const { + return range.from < other.range.from; + } + + }; + + template + int uniteAndAdd( + SparseIdsSliceUpdate &update, + base::flat_set::iterator uniteFrom, + base::flat_set::iterator uniteTill, + const Range &messages, + MsgRange noSkipRange); + template + int addRangeItemsAndCountNew( + SparseIdsSliceUpdate &update, + const Range &messages, + MsgRange noSkipRange); + template + void addRange( + const Range &messages, + MsgRange noSkipRange, + base::optional count, + bool incrementCount = false); + + SparseIdsListResult queryFromSlice( + const SparseIdsListQuery &query, + const Slice &slice) const; + + base::optional _count; + base::flat_set _slices; + + rpl::event_stream _sliceUpdated; + +}; + +} // namespace Storage diff --git a/Telegram/gyp/telegram_sources.txt b/Telegram/gyp/telegram_sources.txt index 8e0eb7de1..68b9618a6 100644 --- a/Telegram/gyp/telegram_sources.txt +++ b/Telegram/gyp/telegram_sources.txt @@ -203,6 +203,8 @@ <(src_loc)/history/history_service_layout.h <(src_loc)/history/history_shared_media.cpp <(src_loc)/history/history_shared_media.h +<(src_loc)/history/history_sparse_ids.cpp +<(src_loc)/history/history_sparse_ids.h <(src_loc)/history/history_user_photos.cpp <(src_loc)/history/history_user_photos.h <(src_loc)/history/history_widget.cpp @@ -505,6 +507,8 @@ <(src_loc)/storage/storage_facade.h <(src_loc)/storage/storage_shared_media.cpp <(src_loc)/storage/storage_shared_media.h +<(src_loc)/storage/storage_sparse_ids_list.cpp +<(src_loc)/storage/storage_sparse_ids_list.h <(src_loc)/storage/storage_user_photos.cpp <(src_loc)/storage/storage_user_photos.h <(src_loc)/ui/effects/cross_animation.cpp