| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526 |
- /*
- This file is part of Telegram Desktop,
- the official desktop application for the Telegram messaging service.
- For license and copyright information please follow this link:
- https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
- */
- #include "data/data_shared_media.h"
- #include <rpl/combine.h>
- #include "main/main_session.h"
- #include "apiwrap.h"
- #include "storage/storage_facade.h"
- #include "history/history.h"
- #include "history/history_item.h"
- #include "data/components/scheduled_messages.h"
- #include "data/data_document.h"
- #include "data/data_media_types.h"
- #include "data/data_photo.h"
- #include "data/data_session.h"
- #include "core/crash_reports.h"
- namespace {
- using Type = Storage::SharedMediaType;
- bool IsItemGoodForType(const not_null<HistoryItem*> item, Type type) {
- const auto media = item->media();
- if (!media || media->webpage()) {
- return false;
- }
- const auto photo = media->photo();
- const auto photoType = (type == Type::Photo);
- const auto photoVideoType = (type == Type::PhotoVideo);
- if ((photoType || photoVideoType) && photo) {
- return true;
- }
- const auto document = media->document();
- if (!document) {
- return false;
- }
- const auto voiceType = (type == Type::VoiceFile);
- const auto voiceDoc = document->isVoiceMessage();
- const auto roundType = (type == Type::RoundFile);
- const auto roundDoc = document->isVideoMessage();
- const auto audioType = (type == Type::MusicFile);
- const auto audioDoc = document->isAudioFile();
- const auto gifType = (type == Type::GIF);
- const auto gifDoc = document->isGifv();
- const auto videoType = (type == Type::Video);
- const auto videoDoc = document->isVideoFile();
- const auto voiceRoundType = (type == Type::RoundVoiceFile);
- const auto fileType = (type == Type::File);
- return (audioType && audioDoc)
- || (voiceType && voiceDoc)
- || (roundType && roundDoc)
- || (voiceRoundType && (roundDoc || voiceDoc))
- || (gifType && gifDoc)
- || ((videoType || photoVideoType) && videoDoc)
- || (fileType && (document->isTheme()
- || document->isImage()
- || !document->canBeStreamed(item)));
- }
- } // namespace
- std::optional<Storage::SharedMediaType> SharedMediaOverviewType(
- Storage::SharedMediaType type) {
- switch (type) {
- case Type::Photo:
- case Type::Video:
- case Type::MusicFile:
- case Type::File:
- case Type::RoundVoiceFile:
- case Type::Link: return type;
- }
- return std::nullopt;
- }
- bool SharedMediaAllowSearch(Storage::SharedMediaType type) {
- switch (type) {
- case Type::MusicFile:
- case Type::File:
- case Type::Link: return true;
- default: return false;
- }
- }
- rpl::producer<SparseIdsSlice> SharedMediaViewer(
- not_null<Main::Session*> session,
- Storage::SharedMediaKey key,
- int limitBefore,
- int limitAfter) {
- Expects(IsServerMsgId(key.messageId) || (key.messageId == 0));
- Expects((key.messageId != 0) || (limitBefore == 0 && limitAfter == 0));
- return [=](auto consumer) {
- auto lifetime = rpl::lifetime();
- auto builder = lifetime.make_state<SparseIdsSliceBuilder>(
- key.messageId,
- limitBefore,
- limitAfter);
- auto requestMediaAround = [
- peer = session->data().peer(key.peerId),
- topicRootId = key.topicRootId,
- type = key.type
- ](const SparseIdsSliceBuilder::AroundData &data) {
- peer->session().api().requestSharedMedia(
- peer,
- topicRootId,
- type,
- data.aroundId,
- data.direction);
- };
- builder->insufficientAround(
- ) | rpl::start_with_next(requestMediaAround, lifetime);
- auto pushNextSnapshot = [=] {
- consumer.put_next(builder->snapshot());
- };
- using SliceUpdate = Storage::SharedMediaSliceUpdate;
- session->storage().sharedMediaSliceUpdated(
- ) | rpl::filter([=](const SliceUpdate &update) {
- return (update.peerId == key.peerId)
- && (update.topicRootId == key.topicRootId)
- && (update.type == key.type);
- }) | rpl::filter([=](const SliceUpdate &update) {
- return builder->applyUpdate(update.data);
- }) | rpl::start_with_next(pushNextSnapshot, lifetime);
- using OneRemoved = Storage::SharedMediaRemoveOne;
- session->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;
- session->storage().sharedMediaAllRemoved(
- ) | rpl::filter([=](const AllRemoved &update) {
- return (update.peerId == key.peerId)
- && (!update.topicRootId
- || update.topicRootId == key.topicRootId)
- && update.types.test(key.type);
- }) | rpl::filter([=] {
- return builder->removeAll();
- }) | rpl::start_with_next(pushNextSnapshot, lifetime);
- using InvalidateBottom = Storage::SharedMediaInvalidateBottom;
- session->storage().sharedMediaBottomInvalidated(
- ) | rpl::filter([=](const InvalidateBottom &update) {
- return (update.peerId == key.peerId);
- }) | rpl::filter([=] {
- return builder->invalidateBottom();
- }) | rpl::start_with_next(pushNextSnapshot, lifetime);
- using Result = Storage::SharedMediaResult;
- session->storage().query(Storage::SharedMediaQuery(
- key,
- limitBefore,
- limitAfter
- )) | rpl::filter([=](const Result &result) {
- return builder->applyInitial(result);
- }) | rpl::start_with_next_done(
- pushNextSnapshot,
- [=] { builder->checkInsufficient(); },
- lifetime);
- return lifetime;
- };
- }
- rpl::producer<SparseIdsMergedSlice> SharedScheduledMediaViewer(
- not_null<Main::Session*> session,
- SharedMediaMergedKey key,
- int limitBefore,
- int limitAfter) {
- Expects(!key.mergedKey.universalId
- || Data::IsScheduledMsgId(key.mergedKey.universalId));
- Expects((key.mergedKey.universalId != 0)
- || (limitBefore == 0 && limitAfter == 0));
- const auto history = session->data().history(key.mergedKey.peerId);
- return rpl::single(rpl::empty) | rpl::then(
- session->scheduledMessages().updates(history)
- ) | rpl::map([=] {
- const auto list = session->scheduledMessages().list(history);
- auto items = ranges::views::all(
- list.ids
- ) | ranges::views::transform([=](const FullMsgId &fullId) {
- return session->data().message(fullId);
- }) | ranges::views::filter([=](HistoryItem *item) {
- return item
- ? IsItemGoodForType(item, key.type)
- : false;
- }) | ranges::to_vector;
- ranges::sort(items, ranges::less(), &HistoryItem::position);
- auto finishMsgIds = ranges::views::all(
- items
- ) | ranges::views::transform([=](not_null<HistoryItem*> item) {
- return item->fullId().msg;
- }) | ranges::to_vector;
- const auto fullCount = finishMsgIds.size();
- auto unsorted = SparseUnsortedIdsSlice(
- std::move(finishMsgIds),
- fullCount,
- list.skippedBefore,
- list.skippedAfter);
- return SparseIdsMergedSlice(
- key.mergedKey,
- std::move(unsorted));
- });
- }
- rpl::producer<SparseIdsMergedSlice> SharedMediaMergedViewer(
- not_null<Main::Session*> session,
- SharedMediaMergedKey key,
- int limitBefore,
- int limitAfter) {
- auto createSimpleViewer = [=](
- PeerId peerId,
- MsgId topicRootId,
- SparseIdsSlice::Key simpleKey,
- int limitBefore,
- int limitAfter) {
- return SharedMediaViewer(
- session,
- Storage::SharedMediaKey(
- peerId,
- topicRootId,
- key.type,
- simpleKey),
- limitBefore,
- limitAfter
- );
- };
- return SparseIdsMergedSlice::CreateViewer(
- key.mergedKey,
- limitBefore,
- limitAfter,
- std::move(createSimpleViewer));
- }
- SharedMediaWithLastSlice::SharedMediaWithLastSlice(
- not_null<Main::Session*> session,
- Key key)
- : SharedMediaWithLastSlice(
- session,
- key,
- SparseIdsMergedSlice(ViewerKey(key)),
- EndingSlice(key)) {
- }
- SharedMediaWithLastSlice::SharedMediaWithLastSlice(
- not_null<Main::Session*> session,
- Key key,
- SparseIdsMergedSlice slice,
- std::optional<SparseIdsMergedSlice> ending)
- : _session(session)
- , _key(key)
- , _slice(std::move(slice))
- , _ending(std::move(ending))
- , _lastPhotoId(LastPeerPhotoId(session, key.peerId))
- , _isolatedLastPhoto(_key.type == Type::ChatPhoto
- ? IsLastIsolated(session, _slice, _ending, _lastPhotoId)
- : false) {
- }
- std::optional<int> SharedMediaWithLastSlice::fullCount() const {
- return Add(
- _slice.fullCount(),
- _isolatedLastPhoto | [](bool isolated) { return isolated ? 1 : 0; });
- }
- std::optional<int> SharedMediaWithLastSlice::skippedBeforeImpl() const {
- return _slice.skippedBefore();
- }
- std::optional<int> SharedMediaWithLastSlice::skippedBefore() const {
- return _reversed ? skippedAfterImpl() : skippedBeforeImpl();
- }
- std::optional<int> SharedMediaWithLastSlice::skippedAfterImpl() const {
- return isolatedInSlice()
- ? Add(
- _slice.skippedAfter(),
- lastPhotoSkip())
- : (lastPhotoSkip() | [](int) { return 0; });
- }
- std::optional<int> SharedMediaWithLastSlice::skippedAfter() const {
- return _reversed ? skippedBeforeImpl() : skippedAfterImpl();
- }
- std::optional<int> SharedMediaWithLastSlice::indexOfImpl(Value value) const {
- return std::get_if<FullMsgId>(&value)
- ? _slice.indexOf(*std::get_if<FullMsgId>(&value))
- : (isolatedInSlice()
- || !_lastPhotoId
- || (*std::get_if<not_null<PhotoData*>>(&value))->id != *_lastPhotoId)
- ? std::nullopt
- : Add(_slice.size() - 1, lastPhotoSkip());
- }
- std::optional<int> SharedMediaWithLastSlice::indexOf(Value value) const {
- const auto result = indexOfImpl(value);
- if (result && (*result < 0 || *result >= size())) {
- // Should not happen.
- auto info = QStringList();
- info.push_back("slice:" + QString::number(_slice.size()));
- info.push_back(_slice.fullCount()
- ? QString::number(*_slice.fullCount())
- : QString("-"));
- info.push_back(_slice.skippedBefore()
- ? QString::number(*_slice.skippedBefore())
- : QString("-"));
- info.push_back(_slice.skippedAfter()
- ? QString::number(*_slice.skippedAfter())
- : QString("-"));
- info.push_back("ending:" + (_ending
- ? QString::number(_ending->size())
- : QString("-")));
- info.push_back((_ending && _ending->fullCount())
- ? QString::number(*_ending->fullCount())
- : QString("-"));
- info.push_back((_ending && _ending->skippedBefore())
- ? QString::number(*_ending->skippedBefore())
- : QString("-"));
- info.push_back((_ending && _ending->skippedAfter())
- ? QString::number(*_ending->skippedAfter())
- : QString("-"));
- if (const auto msgId = std::get_if<FullMsgId>(&value)) {
- info.push_back("value:" + QString::number(msgId->peer.value));
- info.push_back(QString::number(msgId->msg.bare));
- const auto index = _slice.indexOf(*std::get_if<FullMsgId>(&value));
- info.push_back("index:" + (index
- ? QString::number(*index)
- : QString("-")));
- } else if (const auto photo = std::get_if<not_null<PhotoData*>>(&value)) {
- info.push_back("value:" + QString::number((*photo)->id));
- } else {
- info.push_back("value:bad");
- }
- info.push_back("isolated:" + QString(Logs::b(isolatedInSlice())));
- info.push_back("last:" + (_lastPhotoId
- ? QString::number(*_lastPhotoId)
- : QString("-")));
- info.push_back("isolated_last:" + (_isolatedLastPhoto
- ? QString(Logs::b(*_isolatedLastPhoto))
- : QString("-")));
- info.push_back("skip:" + (lastPhotoSkip()
- ? QString::number(*lastPhotoSkip())
- : QString("-")));
- CrashReports::SetAnnotation("DebugInfo", info.join(','));
- Unexpected("Result in SharedMediaWithLastSlice::indexOf");
- }
- return _reversed
- ? (result | func::negate | func::add(size() - 1))
- : result;
- }
- int SharedMediaWithLastSlice::size() const {
- return _slice.size()
- + ((!isolatedInSlice() && lastPhotoSkip() == 1) ? 1 : 0);
- }
- SharedMediaWithLastSlice::Value SharedMediaWithLastSlice::operator[](int index) const {
- Expects(index >= 0 && index < size());
- if (_reversed) {
- index = size() - index - 1;
- }
- return (index < _slice.size())
- ? Value(_slice[index])
- : Value(_session->data().photo(*_lastPhotoId));
- }
- std::optional<int> SharedMediaWithLastSlice::distance(
- const Key &a,
- const Key &b) const {
- if (auto i = indexOf(ComputeId(a))) {
- if (auto j = indexOf(ComputeId(b))) {
- return *j - *i;
- }
- }
- return std::nullopt;
- }
- void SharedMediaWithLastSlice::reverse() {
- _reversed = !_reversed;
- }
- std::optional<PhotoId> SharedMediaWithLastSlice::LastPeerPhotoId(
- not_null<Main::Session*> session,
- PeerId peerId) {
- if (const auto peer = session->data().peerLoaded(peerId)) {
- return peer->userpicPhotoUnknown()
- ? std::nullopt
- : base::make_optional(peer->userpicPhotoId());
- }
- return std::nullopt;
- }
- std::optional<bool> SharedMediaWithLastSlice::IsLastIsolated(
- not_null<Main::Session*> session,
- const SparseIdsMergedSlice &slice,
- const std::optional<SparseIdsMergedSlice> &ending,
- std::optional<PhotoId> lastPeerPhotoId) {
- if (!lastPeerPhotoId) {
- return std::nullopt;
- } else if (!*lastPeerPhotoId) {
- return false;
- }
- return LastFullMsgId(ending ? *ending : slice)
- | [&](FullMsgId msgId) { return session->data().message(msgId); }
- | [](HistoryItem *item) { return item ? item->media() : nullptr; }
- | [](Data::Media *media) { return media ? media->photo() : nullptr; }
- | [](PhotoData *photo) { return photo ? photo->id : 0; }
- | [&](PhotoId photoId) { return *lastPeerPhotoId != photoId; };
- }
- std::optional<FullMsgId> SharedMediaWithLastSlice::LastFullMsgId(
- const SparseIdsMergedSlice &slice) {
- if (slice.fullCount() == 0) {
- return FullMsgId();
- } else if (slice.size() == 0 || slice.skippedAfter() != 0) {
- return std::nullopt;
- }
- return slice[slice.size() - 1];
- }
- rpl::producer<SharedMediaWithLastSlice> SharedMediaWithLastViewer(
- not_null<Main::Session*> session,
- SharedMediaWithLastSlice::Key key,
- int limitBefore,
- int limitAfter) {
- return [=](auto consumer) {
- auto viewerKey = SharedMediaMergedKey(
- SharedMediaWithLastSlice::ViewerKey(key),
- key.type);
- if (std::get_if<not_null<PhotoData*>>(&key.universalId)) {
- return SharedMediaMergedViewer(
- session,
- std::move(viewerKey),
- limitBefore,
- limitAfter
- ) | rpl::start_with_next([=](SparseIdsMergedSlice &&update) {
- consumer.put_next(SharedMediaWithLastSlice(
- session,
- key,
- std::move(update),
- std::nullopt));
- });
- }
- if (key.topicRootId == SharedMediaWithLastSlice::kScheduledTopicId) {
- return SharedScheduledMediaViewer(
- session,
- std::move(viewerKey),
- limitBefore,
- limitAfter
- ) | rpl::start_with_next([=](SparseIdsMergedSlice &&update) {
- consumer.put_next(SharedMediaWithLastSlice(
- session,
- key,
- std::move(update),
- std::nullopt));
- });
- }
- return rpl::combine(
- SharedMediaMergedViewer(
- session,
- std::move(viewerKey),
- limitBefore,
- limitAfter),
- SharedMediaMergedViewer(
- session,
- SharedMediaMergedKey(
- SharedMediaWithLastSlice::EndingKey(key),
- key.type),
- 1,
- 1)
- ) | rpl::start_with_next([=](
- SparseIdsMergedSlice &&viewer,
- SparseIdsMergedSlice &&ending) {
- consumer.put_next(SharedMediaWithLastSlice(
- session,
- key,
- std::move(viewer),
- std::move(ending)));
- });
- };
- }
- rpl::producer<SharedMediaWithLastSlice> SharedMediaWithLastReversedViewer(
- not_null<Main::Session*> session,
- SharedMediaWithLastSlice::Key key,
- int limitBefore,
- int limitAfter) {
- return SharedMediaWithLastViewer(
- session,
- key,
- limitBefore,
- limitAfter
- ) | rpl::map([](SharedMediaWithLastSlice &&slice) {
- slice.reverse();
- return std::move(slice);
- });
- }
|