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),
Destroyed = (1U << 1),
NewAdded = (1U << 2),
ViewsAdded = (1U << 3),
LastUsedBit = (1U << 2),
LastUsedBit = (1U << 3),
};
using Flags = base::flags<Flag>;
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 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<Session*> 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*> story) {
void Stories::savedStateChanged(not_null<Story*> 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<StoryView> offset,
Fn<void(std::vector<StoryView>)> 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*> 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*> 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<PeerData*> peer) {
const auto till = _readTill.find(peer->id);
const auto readTill = (till != end(_readTill)) ? till->second : 0;

View File

@ -219,6 +219,10 @@ public:
bool registerPolling(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:
struct Saved {
StoriesIds ids;
@ -253,14 +257,10 @@ private:
void applyRemovedFromActive(FullStoryId id);
void applyDeletedFromSources(PeerId id, StorySourcesList list);
void removeDependencyStory(not_null<Story*> story);
void savedStateUpdated(not_null<Story*> story);
void sort(StorySourcesList list);
bool bumpReadTill(PeerId peerId, StoryId maxReadTill);
void requestReadTills();
[[nodiscard]] std::shared_ptr<HistoryItem> lookupItem(
not_null<Story*> story);
void sendMarkAsReadRequests();
void sendMarkAsReadRequest(not_null<PeerData*> peer, StoryId tillId);
void sendIncrementViewsRequests();
@ -286,6 +286,7 @@ private:
const PollingSettings &settings,
TimeId now);
void sendPollingRequests();
void sendPollingViewsRequests();
const not_null<Session*> _owner;
std::unordered_map<
@ -359,7 +360,9 @@ private:
bool _readTillReceived = false;
base::flat_map<not_null<Story*>, PollingSettings> _pollingSettings;
base::flat_set<not_null<Story*>> _pollingViews;
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 "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<PeerData*> 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<not_null<PeerData*>> recent,
int total) {
_recentViewers = std::move(recent);
_views = total;
}
const std::vector<not_null<PeerData*>> &Story::recentViewers() const {
return _recentViewers;
}
@ -341,6 +342,7 @@ void Story::applyViewsSlice(
const std::optional<StoryView> &offset,
const std::vector<StoryView> &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<not_null<PeerData*>>();
auto views = _views;
auto viewers = std::vector<not_null<PeerData*>>();
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 {

View File

@ -61,8 +61,10 @@ public:
StoryId id,
not_null<PeerData*> 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<not_null<PeerData*>> recent, int total);
[[nodiscard]] auto recentViewers() const
-> const std::vector<not_null<PeerData*>> &;
[[nodiscard]] const std::vector<StoryView> &viewsList() const;
@ -113,13 +114,19 @@ public:
const std::vector<StoryView> &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<PeerData*> _peer;
StoryMedia _media;

View File

@ -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({});