From 6a118888523d33349554d75c33214080ef641acc Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 5 Jul 2023 21:02:57 +0400 Subject: [PATCH] Poll views for my story that is viewed. --- Telegram/SourceFiles/data/data_changes.h | 3 +- Telegram/SourceFiles/data/data_stories.cpp | 68 ++++++---- Telegram/SourceFiles/data/data_stories.h | 11 +- Telegram/SourceFiles/data/data_story.cpp | 123 +++++++++++++----- Telegram/SourceFiles/data/data_story.h | 15 ++- .../stories/media_stories_controller.cpp | 13 +- 6 files changed, 161 insertions(+), 72 deletions(-) diff --git a/Telegram/SourceFiles/data/data_changes.h b/Telegram/SourceFiles/data/data_changes.h index ee8ff6075..754f12ef3 100644 --- a/Telegram/SourceFiles/data/data_changes.h +++ b/Telegram/SourceFiles/data/data_changes.h @@ -223,8 +223,9 @@ struct StoryUpdate { Edited = (1U << 0), Destroyed = (1U << 1), NewAdded = (1U << 2), + ViewsAdded = (1U << 3), - LastUsedBit = (1U << 2), + LastUsedBit = (1U << 3), }; using Flags = base::flags; friend inline constexpr auto is_flag_type(Flag) { return true; } diff --git a/Telegram/SourceFiles/data/data_stories.cpp b/Telegram/SourceFiles/data/data_stories.cpp index e0ff8ca26..fa3ff6b58 100644 --- a/Telegram/SourceFiles/data/data_stories.cpp +++ b/Telegram/SourceFiles/data/data_stories.cpp @@ -40,6 +40,8 @@ constexpr auto kStillPreloadFromFirst = 3; constexpr auto kMaxSegmentsCount = 180; constexpr auto kPollingIntervalChat = 5 * TimeId(60); constexpr auto kPollingIntervalViewer = 1 * TimeId(60); +constexpr auto kPollViewsInterval = 10 * crl::time(1000); +constexpr auto kPollingViewsPerPage = Story::kRecentViewersMax; using UpdateFlag = StoryUpdate::Flag; @@ -101,11 +103,13 @@ Stories::Stories(not_null owner) , _expireTimer([=] { processExpired(); }) , _markReadTimer([=] { sendMarkAsReadRequests(); }) , _incrementViewsTimer([=] { sendIncrementViewsRequests(); }) -, _pollingTimer([=] { sendPollingRequests(); }) { +, _pollingTimer([=] { sendPollingRequests(); }) +, _pollingViewsTimer([=] { sendPollingViewsRequests(); }) { } Stories::~Stories() { Expects(_pollingSettings.empty()); + Expects(_pollingViews.empty()); } Session &Stories::owner() const { @@ -350,20 +354,9 @@ Story *Stories::parseAndApply( const auto i = stories.find(id); if (i != end(stories)) { const auto result = i->second.get(); - const auto pinned = result->pinned(); const auto mediaChanged = (result->media() != *media); - if (result->applyChanges(*media, data, now)) { - if (result->pinned() != pinned) { - savedStateUpdated(result); - } - session().changes().storyUpdated( - result, - UpdateFlag::Edited); - if (const auto item = lookupItem(result)) { - item->applyChanges(result); - } - _owner->refreshStoryItemViews(fullId); - } + const auto pinned = result->pinned(); + result->applyChanges(*media, data, now); const auto j = _pollingSettings.find(result); if (j != end(_pollingSettings)) { maybeSchedulePolling(result, j->second, now); @@ -385,12 +378,9 @@ Story *Stories::parseAndApply( id, peer, StoryMedia{ *media }, - data.vdate().v, - data.vexpire_date().v)).first->second.get(); - result->applyChanges(*media, data, now); - if (result->pinned()) { - savedStateUpdated(result); - } + data, + now + )).first->second.get(); if (peer->isSelf()) { const auto added = _archive.list.emplace(id).second; @@ -479,7 +469,7 @@ void Stories::unregisterDependentMessage( } } -void Stories::savedStateUpdated(not_null story) { +void Stories::savedStateChanged(not_null story) { const auto id = story->id(); const auto peer = story->peer()->id; const auto pinned = story->pinned(); @@ -1132,14 +1122,20 @@ void Stories::loadViewsSlice( StoryId id, std::optional offset, Fn)> done) { - _viewsDone = std::move(done); - if (_viewsStoryId == id && _viewsOffset == offset) { + if (_viewsStoryId == id + && _viewsOffset == offset + && (offset || _viewsRequestId)) { + if (_viewsRequestId) { + _viewsDone = std::move(done); + } return; } _viewsStoryId = id; _viewsOffset = offset; + _viewsDone = std::move(done); const auto api = &_owner->session().api(); + const auto perPage = _viewsDone ? kViewsPerPage : kPollingViewsPerPage; api->request(_viewsRequestId).cancel(); _viewsRequestId = api->request(MTPstories_GetStoryViewsList( MTP_int(id), @@ -1509,7 +1505,13 @@ void Stories::registerPolling(not_null story, Polling polling) { auto &settings = _pollingSettings[story]; switch (polling) { case Polling::Chat: ++settings.chat; break; - case Polling::Viewer: ++settings.viewer; break; + case Polling::Viewer: + ++settings.viewer; + if (story->peer()->isSelf() + && _pollingViews.emplace(story).second) { + sendPollingViewsRequests(); + } + break; } maybeSchedulePolling(story, settings, base::unixtime::now()); } @@ -1525,7 +1527,12 @@ void Stories::unregisterPolling(not_null story, Polling polling) { break; case Polling::Viewer: Assert(i->second.viewer > 0); - --i->second.viewer; + if (!--i->second.viewer) { + _pollingViews.remove(story); + if (_pollingViews.empty()) { + _pollingViewsTimer.cancel(); + } + } break; } if (!i->second.chat && !i->second.viewer) { @@ -1583,6 +1590,17 @@ void Stories::sendPollingRequests() { } } +void Stories::sendPollingViewsRequests() { + if (_pollingViews.empty()) { + return; + } else if (!_viewsRequestId) { + Assert(_viewsDone == nullptr); + const auto one = _pollingViews.front(); + loadViewsSlice(_pollingViews.front()->id(), std::nullopt, nullptr); + } + _pollingViewsTimer.callOnce(kPollViewsInterval); +} + void Stories::updateUserStoriesState(not_null peer) { const auto till = _readTill.find(peer->id); const auto readTill = (till != end(_readTill)) ? till->second : 0; diff --git a/Telegram/SourceFiles/data/data_stories.h b/Telegram/SourceFiles/data/data_stories.h index 579ec7b89..2bdd27d1c 100644 --- a/Telegram/SourceFiles/data/data_stories.h +++ b/Telegram/SourceFiles/data/data_stories.h @@ -219,6 +219,10 @@ public: bool registerPolling(FullStoryId id, Polling polling); void unregisterPolling(FullStoryId id, Polling polling); + void savedStateChanged(not_null story); + [[nodiscard]] std::shared_ptr lookupItem( + not_null story); + private: struct Saved { StoriesIds ids; @@ -253,14 +257,10 @@ private: void applyRemovedFromActive(FullStoryId id); void applyDeletedFromSources(PeerId id, StorySourcesList list); void removeDependencyStory(not_null story); - void savedStateUpdated(not_null story); void sort(StorySourcesList list); bool bumpReadTill(PeerId peerId, StoryId maxReadTill); void requestReadTills(); - [[nodiscard]] std::shared_ptr lookupItem( - not_null story); - void sendMarkAsReadRequests(); void sendMarkAsReadRequest(not_null peer, StoryId tillId); void sendIncrementViewsRequests(); @@ -286,6 +286,7 @@ private: const PollingSettings &settings, TimeId now); void sendPollingRequests(); + void sendPollingViewsRequests(); const not_null _owner; std::unordered_map< @@ -359,7 +360,9 @@ private: bool _readTillReceived = false; base::flat_map, PollingSettings> _pollingSettings; + base::flat_set> _pollingViews; base::Timer _pollingTimer; + base::Timer _pollingViewsTimer; }; diff --git a/Telegram/SourceFiles/data/data_story.cpp b/Telegram/SourceFiles/data/data_story.cpp index 3a57400c0..59f672a11 100644 --- a/Telegram/SourceFiles/data/data_story.cpp +++ b/Telegram/SourceFiles/data/data_story.cpp @@ -10,12 +10,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/unixtime.h" #include "api/api_text_entities.h" #include "data/data_document.h" +#include "data/data_changes.h" #include "data/data_file_origin.h" #include "data/data_photo.h" #include "data/data_photo_media.h" #include "data/data_user.h" #include "data/data_session.h" +#include "data/data_stories.h" #include "data/data_thread.h" +#include "history/history_item.h" #include "lang/lang_keys.h" #include "main/main_session.h" #include "media/streaming/media_streaming_reader.h" @@ -23,6 +26,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/text/text_utilities.h" namespace Data { +namespace { + +using UpdateFlag = StoryUpdate::Flag; + +} // namespace class StoryPreload::LoadTask final : private Storage::DownloadMtprotoTask { public: @@ -103,7 +111,7 @@ bool StoryPreload::LoadTask::feedPart( && _requestedOffsets.empty()) { _finished = true; removeFromQueue(); - auto result = Media::Streaming::SerializeComplexPartsMap(_parts); + auto result = ::Media::Streaming::SerializeComplexPartsMap(_parts); if (result.size() == _full) { // Make sure it is parsed as a complex map. result.push_back(char(0)); @@ -130,13 +138,13 @@ Story::Story( StoryId id, not_null peer, StoryMedia media, - TimeId date, - TimeId expires) + const MTPDstoryItem &data, + TimeId now) : _id(id) , _peer(peer) -, _media(std::move(media)) -, _date(date) -, _expires(expires) { +, _date(data.vdate().v) +, _expires(data.vexpire_date().v) { + applyFields(std::move(media), data, now, true); } Session &Story::owner() const { @@ -318,13 +326,6 @@ const TextWithEntities &Story::caption() const { return unsupported() ? empty : _caption; } -void Story::setViewsData( - std::vector> recent, - int total) { - _recentViewers = std::move(recent); - _views = total; -} - const std::vector> &Story::recentViewers() const { return _recentViewers; } @@ -341,6 +342,7 @@ void Story::applyViewsSlice( const std::optional &offset, const std::vector &slice, int total) { + const auto changed = (_views != total); _views = total; if (!offset) { const auto i = _viewsList.empty() @@ -369,12 +371,42 @@ void Story::applyViewsSlice( } } } + const auto known = int(_viewsList.size()); + if (known >= _recentViewers.size()) { + const auto take = std::min(known, kRecentViewersMax); + auto viewers = _viewsList + | ranges::views::take(take) + | ranges::views::transform(&StoryView::peer) + | ranges::to_vector; + if (_recentViewers != viewers) { + _recentViewers = std::move(viewers); + if (!changed) { + // Count not changed, but list of recent viewers changed. + _peer->session().changes().storyUpdated( + this, + UpdateFlag::ViewsAdded); + } + } + } + if (changed) { + _peer->session().changes().storyUpdated( + this, + UpdateFlag::ViewsAdded); + } } -bool Story::applyChanges( +void Story::applyChanges( StoryMedia media, const MTPDstoryItem &data, TimeId now) { + applyFields(std::move(media), data, now, false); +} + +void Story::applyFields( + StoryMedia media, + const MTPDstoryItem &data, + TimeId now, + bool initial) { _lastUpdateTime = now; const auto pinned = data.is_pinned(); @@ -388,45 +420,64 @@ bool Story::applyChanges( &owner().session(), data.ventities().value_or_empty()), }; - auto views = -1; - auto recent = std::vector>(); + auto views = _views; + auto viewers = std::vector>(); if (!data.is_min()) { if (const auto info = data.vviews()) { views = info->data().vviews_count().v; if (const auto list = info->data().vrecent_viewers()) { - recent.reserve(list->v.size()); + viewers.reserve(list->v.size()); auto &owner = _peer->owner(); - for (const auto &id : list->v) { - recent.push_back(owner.peer(peerFromUser(id))); + auto &&cut = list->v + | ranges::views::take(kRecentViewersMax); + for (const auto &id : cut) { + viewers.push_back(owner.peer(peerFromUser(id))); } } } } - const auto changed = (_media != media) - || (_pinned != pinned) - || (_edited != edited) - || (_isPublic != isPublic) - || (_closeFriends != closeFriends) - || (_noForwards != noForwards) - || (_caption != caption) - || (views >= 0 && _views != views) - || (_recentViewers != recent); - if (!changed) { - return false; - } - _media = std::move(media); + const auto pinnedChanged = (_pinned != pinned); + const auto editedChanged = (_edited != edited); + const auto mediaChanged = (_media != media); + const auto captionChanged = (_caption != caption); + const auto viewsChanged = (_views != views) + || (_recentViewers != viewers); + + _isPublic = isPublic; + _closeFriends = closeFriends; + _noForwards = noForwards; _edited = edited; _pinned = pinned; _isPublic = isPublic; _closeFriends = closeFriends; _noForwards = noForwards; - _caption = std::move(caption); - if (views >= 0) { + if (viewsChanged) { _views = views; + _recentViewers = std::move(viewers); + } + if (mediaChanged) { + _media = std::move(media); + } + if (captionChanged) { + _caption = std::move(caption); + } + + const auto changed = (editedChanged || captionChanged || mediaChanged); + if (!initial && (changed || viewsChanged)) { + _peer->session().changes().storyUpdated(this, UpdateFlag() + | (changed ? UpdateFlag::Edited : UpdateFlag()) + | (viewsChanged ? UpdateFlag::ViewsAdded : UpdateFlag())); + } + if (!initial && (captionChanged || mediaChanged)) { + if (const auto item = _peer->owner().stories().lookupItem(this)) { + item->applyChanges(this); + } + _peer->owner().refreshStoryItemViews(fullId()); + } + if (pinnedChanged) { + _peer->owner().stories().savedStateChanged(this); } - _recentViewers = std::move(recent); - return true; } TimeId Story::lastUpdateTime() const { diff --git a/Telegram/SourceFiles/data/data_story.h b/Telegram/SourceFiles/data/data_story.h index ed6254a09..d95c4dd40 100644 --- a/Telegram/SourceFiles/data/data_story.h +++ b/Telegram/SourceFiles/data/data_story.h @@ -61,8 +61,10 @@ public: StoryId id, not_null peer, StoryMedia media, - TimeId date, - TimeId expires); + const MTPDstoryItem &data, + TimeId now); + + static constexpr int kRecentViewersMax = 3; [[nodiscard]] Session &owner() const; [[nodiscard]] Main::Session &session() const; @@ -103,7 +105,6 @@ public: void setCaption(TextWithEntities &&caption); [[nodiscard]] const TextWithEntities &caption() const; - void setViewsData(std::vector> recent, int total); [[nodiscard]] auto recentViewers() const -> const std::vector> &; [[nodiscard]] const std::vector &viewsList() const; @@ -113,13 +114,19 @@ public: const std::vector &slice, int total); - bool applyChanges( + void applyChanges( StoryMedia media, const MTPDstoryItem &data, TimeId now); [[nodiscard]] TimeId lastUpdateTime() const; private: + void applyFields( + StoryMedia media, + const MTPDstoryItem &data, + TimeId now, + bool initial); + const StoryId _id = 0; const not_null _peer; StoryMedia _media; diff --git a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp index d05aa292c..788806bdd 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp @@ -828,11 +828,20 @@ void Controller::subscribeToSession() { }, _sessionLifetime); _session->changes().storyUpdates( Data::StoryUpdate::Flag::Edited + | Data::StoryUpdate::Flag::ViewsAdded ) | rpl::filter([=](const Data::StoryUpdate &update) { return (update.story == this->story()); }) | rpl::start_with_next([=](const Data::StoryUpdate &update) { - show(update.story, _context); - _delegate->storiesRedisplay(update.story); + if (update.flags & Data::StoryUpdate::Flag::Edited) { + show(update.story, _context); + _delegate->storiesRedisplay(update.story); + } else { + _recentViews->show({ + .list = update.story->recentViewers(), + .total = update.story->views(), + .valid = update.story->peer()->isSelf(), + }); + } }, _sessionLifetime); _sessionLifetime.add([=] { _session->data().stories().setPreloadingInViewer({});