Poll views for my story that is viewed.

This commit is contained in:
John Preston 2023-07-05 21:02:57 +04:00
parent 5ccb97668c
commit 6a11888852
6 changed files with 161 additions and 72 deletions

View File

@ -223,8 +223,9 @@ struct StoryUpdate {
Edited = (1U << 0), Edited = (1U << 0),
Destroyed = (1U << 1), Destroyed = (1U << 1),
NewAdded = (1U << 2), NewAdded = (1U << 2),
ViewsAdded = (1U << 3),
LastUsedBit = (1U << 2), LastUsedBit = (1U << 3),
}; };
using Flags = base::flags<Flag>; using Flags = base::flags<Flag>;
friend inline constexpr auto is_flag_type(Flag) { return true; } friend inline constexpr auto is_flag_type(Flag) { return true; }

View File

@ -40,6 +40,8 @@ constexpr auto kStillPreloadFromFirst = 3;
constexpr auto kMaxSegmentsCount = 180; constexpr auto kMaxSegmentsCount = 180;
constexpr auto kPollingIntervalChat = 5 * TimeId(60); constexpr auto kPollingIntervalChat = 5 * TimeId(60);
constexpr auto kPollingIntervalViewer = 1 * TimeId(60); constexpr auto kPollingIntervalViewer = 1 * TimeId(60);
constexpr auto kPollViewsInterval = 10 * crl::time(1000);
constexpr auto kPollingViewsPerPage = Story::kRecentViewersMax;
using UpdateFlag = StoryUpdate::Flag; using UpdateFlag = StoryUpdate::Flag;
@ -101,11 +103,13 @@ Stories::Stories(not_null<Session*> owner)
, _expireTimer([=] { processExpired(); }) , _expireTimer([=] { processExpired(); })
, _markReadTimer([=] { sendMarkAsReadRequests(); }) , _markReadTimer([=] { sendMarkAsReadRequests(); })
, _incrementViewsTimer([=] { sendIncrementViewsRequests(); }) , _incrementViewsTimer([=] { sendIncrementViewsRequests(); })
, _pollingTimer([=] { sendPollingRequests(); }) { , _pollingTimer([=] { sendPollingRequests(); })
, _pollingViewsTimer([=] { sendPollingViewsRequests(); }) {
} }
Stories::~Stories() { Stories::~Stories() {
Expects(_pollingSettings.empty()); Expects(_pollingSettings.empty());
Expects(_pollingViews.empty());
} }
Session &Stories::owner() const { Session &Stories::owner() const {
@ -350,20 +354,9 @@ Story *Stories::parseAndApply(
const auto i = stories.find(id); const auto i = stories.find(id);
if (i != end(stories)) { if (i != end(stories)) {
const auto result = i->second.get(); const auto result = i->second.get();
const auto pinned = result->pinned();
const auto mediaChanged = (result->media() != *media); const auto mediaChanged = (result->media() != *media);
if (result->applyChanges(*media, data, now)) { const auto pinned = result->pinned();
if (result->pinned() != pinned) { result->applyChanges(*media, data, now);
savedStateUpdated(result);
}
session().changes().storyUpdated(
result,
UpdateFlag::Edited);
if (const auto item = lookupItem(result)) {
item->applyChanges(result);
}
_owner->refreshStoryItemViews(fullId);
}
const auto j = _pollingSettings.find(result); const auto j = _pollingSettings.find(result);
if (j != end(_pollingSettings)) { if (j != end(_pollingSettings)) {
maybeSchedulePolling(result, j->second, now); maybeSchedulePolling(result, j->second, now);
@ -385,12 +378,9 @@ Story *Stories::parseAndApply(
id, id,
peer, peer,
StoryMedia{ *media }, StoryMedia{ *media },
data.vdate().v, data,
data.vexpire_date().v)).first->second.get(); now
result->applyChanges(*media, data, now); )).first->second.get();
if (result->pinned()) {
savedStateUpdated(result);
}
if (peer->isSelf()) { if (peer->isSelf()) {
const auto added = _archive.list.emplace(id).second; const auto added = _archive.list.emplace(id).second;
@ -479,7 +469,7 @@ void Stories::unregisterDependentMessage(
} }
} }
void Stories::savedStateUpdated(not_null<Story*> story) { void Stories::savedStateChanged(not_null<Story*> story) {
const auto id = story->id(); const auto id = story->id();
const auto peer = story->peer()->id; const auto peer = story->peer()->id;
const auto pinned = story->pinned(); const auto pinned = story->pinned();
@ -1132,14 +1122,20 @@ void Stories::loadViewsSlice(
StoryId id, StoryId id,
std::optional<StoryView> offset, std::optional<StoryView> offset,
Fn<void(std::vector<StoryView>)> done) { Fn<void(std::vector<StoryView>)> done) {
_viewsDone = std::move(done); if (_viewsStoryId == id
if (_viewsStoryId == id && _viewsOffset == offset) { && _viewsOffset == offset
&& (offset || _viewsRequestId)) {
if (_viewsRequestId) {
_viewsDone = std::move(done);
}
return; return;
} }
_viewsStoryId = id; _viewsStoryId = id;
_viewsOffset = offset; _viewsOffset = offset;
_viewsDone = std::move(done);
const auto api = &_owner->session().api(); const auto api = &_owner->session().api();
const auto perPage = _viewsDone ? kViewsPerPage : kPollingViewsPerPage;
api->request(_viewsRequestId).cancel(); api->request(_viewsRequestId).cancel();
_viewsRequestId = api->request(MTPstories_GetStoryViewsList( _viewsRequestId = api->request(MTPstories_GetStoryViewsList(
MTP_int(id), MTP_int(id),
@ -1509,7 +1505,13 @@ void Stories::registerPolling(not_null<Story*> story, Polling polling) {
auto &settings = _pollingSettings[story]; auto &settings = _pollingSettings[story];
switch (polling) { switch (polling) {
case Polling::Chat: ++settings.chat; break; 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()); maybeSchedulePolling(story, settings, base::unixtime::now());
} }
@ -1525,7 +1527,12 @@ void Stories::unregisterPolling(not_null<Story*> story, Polling polling) {
break; break;
case Polling::Viewer: case Polling::Viewer:
Assert(i->second.viewer > 0); Assert(i->second.viewer > 0);
--i->second.viewer; if (!--i->second.viewer) {
_pollingViews.remove(story);
if (_pollingViews.empty()) {
_pollingViewsTimer.cancel();
}
}
break; break;
} }
if (!i->second.chat && !i->second.viewer) { 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<PeerData*> peer) { void Stories::updateUserStoriesState(not_null<PeerData*> peer) {
const auto till = _readTill.find(peer->id); const auto till = _readTill.find(peer->id);
const auto readTill = (till != end(_readTill)) ? till->second : 0; const auto readTill = (till != end(_readTill)) ? till->second : 0;

View File

@ -219,6 +219,10 @@ public:
bool registerPolling(FullStoryId id, Polling polling); bool registerPolling(FullStoryId id, Polling polling);
void unregisterPolling(FullStoryId id, Polling polling); void unregisterPolling(FullStoryId id, Polling polling);
void savedStateChanged(not_null<Story*> story);
[[nodiscard]] std::shared_ptr<HistoryItem> lookupItem(
not_null<Story*> story);
private: private:
struct Saved { struct Saved {
StoriesIds ids; StoriesIds ids;
@ -253,14 +257,10 @@ private:
void applyRemovedFromActive(FullStoryId id); void applyRemovedFromActive(FullStoryId id);
void applyDeletedFromSources(PeerId id, StorySourcesList list); void applyDeletedFromSources(PeerId id, StorySourcesList list);
void removeDependencyStory(not_null<Story*> story); void removeDependencyStory(not_null<Story*> story);
void savedStateUpdated(not_null<Story*> story);
void sort(StorySourcesList list); void sort(StorySourcesList list);
bool bumpReadTill(PeerId peerId, StoryId maxReadTill); bool bumpReadTill(PeerId peerId, StoryId maxReadTill);
void requestReadTills(); void requestReadTills();
[[nodiscard]] std::shared_ptr<HistoryItem> lookupItem(
not_null<Story*> story);
void sendMarkAsReadRequests(); void sendMarkAsReadRequests();
void sendMarkAsReadRequest(not_null<PeerData*> peer, StoryId tillId); void sendMarkAsReadRequest(not_null<PeerData*> peer, StoryId tillId);
void sendIncrementViewsRequests(); void sendIncrementViewsRequests();
@ -286,6 +286,7 @@ private:
const PollingSettings &settings, const PollingSettings &settings,
TimeId now); TimeId now);
void sendPollingRequests(); void sendPollingRequests();
void sendPollingViewsRequests();
const not_null<Session*> _owner; const not_null<Session*> _owner;
std::unordered_map< std::unordered_map<
@ -359,7 +360,9 @@ private:
bool _readTillReceived = false; bool _readTillReceived = false;
base::flat_map<not_null<Story*>, PollingSettings> _pollingSettings; base::flat_map<not_null<Story*>, PollingSettings> _pollingSettings;
base::flat_set<not_null<Story*>> _pollingViews;
base::Timer _pollingTimer; base::Timer _pollingTimer;
base::Timer _pollingViewsTimer;
}; };

View File

@ -10,12 +10,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/unixtime.h" #include "base/unixtime.h"
#include "api/api_text_entities.h" #include "api/api_text_entities.h"
#include "data/data_document.h" #include "data/data_document.h"
#include "data/data_changes.h"
#include "data/data_file_origin.h" #include "data/data_file_origin.h"
#include "data/data_photo.h" #include "data/data_photo.h"
#include "data/data_photo_media.h" #include "data/data_photo_media.h"
#include "data/data_user.h" #include "data/data_user.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "data/data_stories.h"
#include "data/data_thread.h" #include "data/data_thread.h"
#include "history/history_item.h"
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "media/streaming/media_streaming_reader.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" #include "ui/text/text_utilities.h"
namespace Data { namespace Data {
namespace {
using UpdateFlag = StoryUpdate::Flag;
} // namespace
class StoryPreload::LoadTask final : private Storage::DownloadMtprotoTask { class StoryPreload::LoadTask final : private Storage::DownloadMtprotoTask {
public: public:
@ -103,7 +111,7 @@ bool StoryPreload::LoadTask::feedPart(
&& _requestedOffsets.empty()) { && _requestedOffsets.empty()) {
_finished = true; _finished = true;
removeFromQueue(); removeFromQueue();
auto result = Media::Streaming::SerializeComplexPartsMap(_parts); auto result = ::Media::Streaming::SerializeComplexPartsMap(_parts);
if (result.size() == _full) { if (result.size() == _full) {
// Make sure it is parsed as a complex map. // Make sure it is parsed as a complex map.
result.push_back(char(0)); result.push_back(char(0));
@ -130,13 +138,13 @@ Story::Story(
StoryId id, StoryId id,
not_null<PeerData*> peer, not_null<PeerData*> peer,
StoryMedia media, StoryMedia media,
TimeId date, const MTPDstoryItem &data,
TimeId expires) TimeId now)
: _id(id) : _id(id)
, _peer(peer) , _peer(peer)
, _media(std::move(media)) , _date(data.vdate().v)
, _date(date) , _expires(data.vexpire_date().v) {
, _expires(expires) { applyFields(std::move(media), data, now, true);
} }
Session &Story::owner() const { Session &Story::owner() const {
@ -318,13 +326,6 @@ const TextWithEntities &Story::caption() const {
return unsupported() ? empty : _caption; return unsupported() ? empty : _caption;
} }
void Story::setViewsData(
std::vector<not_null<PeerData*>> recent,
int total) {
_recentViewers = std::move(recent);
_views = total;
}
const std::vector<not_null<PeerData*>> &Story::recentViewers() const { const std::vector<not_null<PeerData*>> &Story::recentViewers() const {
return _recentViewers; return _recentViewers;
} }
@ -341,6 +342,7 @@ void Story::applyViewsSlice(
const std::optional<StoryView> &offset, const std::optional<StoryView> &offset,
const std::vector<StoryView> &slice, const std::vector<StoryView> &slice,
int total) { int total) {
const auto changed = (_views != total);
_views = total; _views = total;
if (!offset) { if (!offset) {
const auto i = _viewsList.empty() 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, StoryMedia media,
const MTPDstoryItem &data, const MTPDstoryItem &data,
TimeId now) { TimeId now) {
applyFields(std::move(media), data, now, false);
}
void Story::applyFields(
StoryMedia media,
const MTPDstoryItem &data,
TimeId now,
bool initial) {
_lastUpdateTime = now; _lastUpdateTime = now;
const auto pinned = data.is_pinned(); const auto pinned = data.is_pinned();
@ -388,45 +420,64 @@ bool Story::applyChanges(
&owner().session(), &owner().session(),
data.ventities().value_or_empty()), data.ventities().value_or_empty()),
}; };
auto views = -1; auto views = _views;
auto recent = std::vector<not_null<PeerData*>>(); auto viewers = std::vector<not_null<PeerData*>>();
if (!data.is_min()) { if (!data.is_min()) {
if (const auto info = data.vviews()) { if (const auto info = data.vviews()) {
views = info->data().vviews_count().v; views = info->data().vviews_count().v;
if (const auto list = info->data().vrecent_viewers()) { if (const auto list = info->data().vrecent_viewers()) {
recent.reserve(list->v.size()); viewers.reserve(list->v.size());
auto &owner = _peer->owner(); auto &owner = _peer->owner();
for (const auto &id : list->v) { auto &&cut = list->v
recent.push_back(owner.peer(peerFromUser(id))); | ranges::views::take(kRecentViewersMax);
for (const auto &id : cut) {
viewers.push_back(owner.peer(peerFromUser(id)));
} }
} }
} }
} }
const auto changed = (_media != media) const auto pinnedChanged = (_pinned != pinned);
|| (_pinned != pinned) const auto editedChanged = (_edited != edited);
|| (_edited != edited) const auto mediaChanged = (_media != media);
|| (_isPublic != isPublic) const auto captionChanged = (_caption != caption);
|| (_closeFriends != closeFriends) const auto viewsChanged = (_views != views)
|| (_noForwards != noForwards) || (_recentViewers != viewers);
|| (_caption != caption)
|| (views >= 0 && _views != views) _isPublic = isPublic;
|| (_recentViewers != recent); _closeFriends = closeFriends;
if (!changed) { _noForwards = noForwards;
return false;
}
_media = std::move(media);
_edited = edited; _edited = edited;
_pinned = pinned; _pinned = pinned;
_isPublic = isPublic; _isPublic = isPublic;
_closeFriends = closeFriends; _closeFriends = closeFriends;
_noForwards = noForwards; _noForwards = noForwards;
_caption = std::move(caption); if (viewsChanged) {
if (views >= 0) {
_views = views; _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 { TimeId Story::lastUpdateTime() const {

View File

@ -61,8 +61,10 @@ public:
StoryId id, StoryId id,
not_null<PeerData*> peer, not_null<PeerData*> peer,
StoryMedia media, StoryMedia media,
TimeId date, const MTPDstoryItem &data,
TimeId expires); TimeId now);
static constexpr int kRecentViewersMax = 3;
[[nodiscard]] Session &owner() const; [[nodiscard]] Session &owner() const;
[[nodiscard]] Main::Session &session() const; [[nodiscard]] Main::Session &session() const;
@ -103,7 +105,6 @@ public:
void setCaption(TextWithEntities &&caption); void setCaption(TextWithEntities &&caption);
[[nodiscard]] const TextWithEntities &caption() const; [[nodiscard]] const TextWithEntities &caption() const;
void setViewsData(std::vector<not_null<PeerData*>> recent, int total);
[[nodiscard]] auto recentViewers() const [[nodiscard]] auto recentViewers() const
-> const std::vector<not_null<PeerData*>> &; -> const std::vector<not_null<PeerData*>> &;
[[nodiscard]] const std::vector<StoryView> &viewsList() const; [[nodiscard]] const std::vector<StoryView> &viewsList() const;
@ -113,13 +114,19 @@ public:
const std::vector<StoryView> &slice, const std::vector<StoryView> &slice,
int total); int total);
bool applyChanges( void applyChanges(
StoryMedia media, StoryMedia media,
const MTPDstoryItem &data, const MTPDstoryItem &data,
TimeId now); TimeId now);
[[nodiscard]] TimeId lastUpdateTime() const; [[nodiscard]] TimeId lastUpdateTime() const;
private: private:
void applyFields(
StoryMedia media,
const MTPDstoryItem &data,
TimeId now,
bool initial);
const StoryId _id = 0; const StoryId _id = 0;
const not_null<PeerData*> _peer; const not_null<PeerData*> _peer;
StoryMedia _media; StoryMedia _media;

View File

@ -828,11 +828,20 @@ void Controller::subscribeToSession() {
}, _sessionLifetime); }, _sessionLifetime);
_session->changes().storyUpdates( _session->changes().storyUpdates(
Data::StoryUpdate::Flag::Edited Data::StoryUpdate::Flag::Edited
| Data::StoryUpdate::Flag::ViewsAdded
) | rpl::filter([=](const Data::StoryUpdate &update) { ) | rpl::filter([=](const Data::StoryUpdate &update) {
return (update.story == this->story()); return (update.story == this->story());
}) | rpl::start_with_next([=](const Data::StoryUpdate &update) { }) | rpl::start_with_next([=](const Data::StoryUpdate &update) {
show(update.story, _context); if (update.flags & Data::StoryUpdate::Flag::Edited) {
_delegate->storiesRedisplay(update.story); 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);
_sessionLifetime.add([=] { _sessionLifetime.add([=] {
_session->data().stories().setPreloadingInViewer({}); _session->data().stories().setPreloadingInViewer({});