/* 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); } bool operator!=(const Key &other) const { return !(*this == other); } 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; using SimpleViewerFunction = rpl::producer( PeerId peerId, SparseIdsSlice::Key simpleKey, int limitBefore, int limitAfter); static rpl::producer CreateViewer( SparseIdsMergedSlice::Key key, int limitBefore, int limitAfter, base::lambda simpleViewer); private: 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; } 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(); struct AroundData { MsgId aroundId = 0; SparseIdsLoadDirection direction = SparseIdsLoadDirection::Around; inline bool operator<(const AroundData &other) const { return (aroundId < other.aroundId) || ((aroundId == other.aroundId) && (direction < other.direction)); } }; 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; };