2
0
mirror of https://github.com/kotatogram/kotatogram-desktop synced 2025-08-31 06:35:14 +00:00

Add short-polling of stories.

This commit is contained in:
John Preston
2023-07-05 19:52:22 +04:00
parent 12fe0a836a
commit 5ccb97668c
14 changed files with 308 additions and 52 deletions

View File

@@ -38,6 +38,8 @@ constexpr auto kSavedPerPage = 100;
constexpr auto kMaxPreloadSources = 10;
constexpr auto kStillPreloadFromFirst = 3;
constexpr auto kMaxSegmentsCount = 180;
constexpr auto kPollingIntervalChat = 5 * TimeId(60);
constexpr auto kPollingIntervalViewer = 1 * TimeId(60);
using UpdateFlag = StoryUpdate::Flag;
@@ -98,10 +100,12 @@ Stories::Stories(not_null<Session*> owner)
: _owner(owner)
, _expireTimer([=] { processExpired(); })
, _markReadTimer([=] { sendMarkAsReadRequests(); })
, _incrementViewsTimer([=] { sendIncrementViewsRequests(); }) {
, _incrementViewsTimer([=] { sendIncrementViewsRequests(); })
, _pollingTimer([=] { sendPollingRequests(); }) {
}
Stories::~Stories() {
Expects(_pollingSettings.empty());
}
Session &Stories::owner() const {
@@ -348,7 +352,7 @@ Story *Stories::parseAndApply(
const auto result = i->second.get();
const auto pinned = result->pinned();
const auto mediaChanged = (result->media() != *media);
if (result->applyChanges(*media, data)) {
if (result->applyChanges(*media, data, now)) {
if (result->pinned() != pinned) {
savedStateUpdated(result);
}
@@ -358,6 +362,11 @@ Story *Stories::parseAndApply(
if (const auto item = lookupItem(result)) {
item->applyChanges(result);
}
_owner->refreshStoryItemViews(fullId);
}
const auto j = _pollingSettings.find(result);
if (j != end(_pollingSettings)) {
maybeSchedulePolling(result, j->second, now);
}
if (mediaChanged) {
_preloaded.remove(fullId);
@@ -378,7 +387,7 @@ Story *Stories::parseAndApply(
StoryMedia{ *media },
data.vdate().v,
data.vexpire_date().v)).first->second.get();
result->applyChanges(*media, data);
result->applyChanges(*media, data, now);
if (result->pinned()) {
savedStateUpdated(result);
}
@@ -656,6 +665,7 @@ void Stories::applyDeleted(FullStoryId id) {
preloadFinished(id);
}
_owner->refreshStoryItemViews(id);
Assert(!_pollingSettings.contains(story.get()));
if (i->second.empty()) {
_stories.erase(i);
}
@@ -818,13 +828,15 @@ base::expected<not_null<Story*>, NoStory> Stories::lookup(
_deleted.contains(id) ? NoStory::Deleted : NoStory::Unknown);
}
void Stories::resolve(FullStoryId id, Fn<void()> done) {
const auto already = lookup(id);
if (already.has_value() || already.error() != NoStory::Unknown) {
if (done) {
done();
void Stories::resolve(FullStoryId id, Fn<void()> done, bool force) {
if (!force) {
const auto already = lookup(id);
if (already.has_value() || already.error() != NoStory::Unknown) {
if (done) {
done();
}
return;
}
return;
}
if (const auto i = _resolveSent.find(id.peer); i != end(_resolveSent)) {
if (const auto j = i->second.find(id.story); j != end(i->second)) {
@@ -1493,6 +1505,84 @@ bool Stories::isUnread(not_null<Story*> story) {
return (story->id() > readTill);
}
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;
}
maybeSchedulePolling(story, settings, base::unixtime::now());
}
void Stories::unregisterPolling(not_null<Story*> story, Polling polling) {
const auto i = _pollingSettings.find(story);
Assert(i != end(_pollingSettings));
switch (polling) {
case Polling::Chat:
Assert(i->second.chat > 0);
--i->second.chat;
break;
case Polling::Viewer:
Assert(i->second.viewer > 0);
--i->second.viewer;
break;
}
if (!i->second.chat && !i->second.viewer) {
_pollingSettings.erase(i);
}
}
bool Stories::registerPolling(FullStoryId id, Polling polling) {
if (const auto maybeStory = lookup(id)) {
registerPolling(*maybeStory, polling);
return true;
}
return false;
}
void Stories::unregisterPolling(FullStoryId id, Polling polling) {
const auto maybeStory = lookup(id);
Assert(maybeStory.has_value());
unregisterPolling(*maybeStory, polling);
}
int Stories::pollingInterval(const PollingSettings &settings) const {
return settings.viewer ? kPollingIntervalViewer : kPollingIntervalChat;
}
void Stories::maybeSchedulePolling(
not_null<Story*> story,
const PollingSettings &settings,
TimeId now) {
const auto last = story->lastUpdateTime();
const auto next = last + pollingInterval(settings);
const auto left = std::max(next - now, 0) * crl::time(1000) + 1;
if (!_pollingTimer.isActive() || _pollingTimer.remainingTime() > left) {
_pollingTimer.callOnce(left);
}
}
void Stories::sendPollingRequests() {
auto min = 0;
const auto now = base::unixtime::now();
for (const auto &[story, settings] : _pollingSettings) {
const auto last = story->lastUpdateTime();
const auto next = last + pollingInterval(settings);
if (now >= next) {
resolve(story->fullId(), nullptr, true);
} else {
const auto left = (next - now) * crl::time(1000) + 1;
if (!min || left < min) {
min = left;
}
}
}
if (min > 0) {
_pollingTimer.callOnce(min);
}
}
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

@@ -153,7 +153,7 @@ public:
[[nodiscard]] base::expected<not_null<Story*>, NoStory> lookup(
FullStoryId id) const;
void resolve(FullStoryId id, Fn<void()> done);
void resolve(FullStoryId id, Fn<void()> done, bool force = false);
[[nodiscard]] std::shared_ptr<HistoryItem> resolveItem(FullStoryId id);
[[nodiscard]] std::shared_ptr<HistoryItem> resolveItem(
not_null<Story*> story);
@@ -209,6 +209,16 @@ public:
StoryId storyMaxId);
[[nodiscard]] bool isUnread(not_null<Story*> story);
enum class Polling {
Chat,
Viewer,
};
void registerPolling(not_null<Story*> story, Polling polling);
void unregisterPolling(not_null<Story*> story, Polling polling);
bool registerPolling(FullStoryId id, Polling polling);
void unregisterPolling(FullStoryId id, Polling polling);
private:
struct Saved {
StoriesIds ids;
@@ -217,6 +227,10 @@ private:
bool loaded = false;
mtpRequestId requestId = 0;
};
struct PollingSettings {
int chat = 0;
int viewer = 0;
};
void parseAndApply(const MTPUserStories &stories);
[[nodiscard]] Story *parseAndApply(
@@ -265,6 +279,14 @@ private:
void startPreloading(not_null<Story*> story);
void preloadFinished(FullStoryId id, bool markAsPreloaded = false);
[[nodiscard]] int pollingInterval(
const PollingSettings &settings) const;
void maybeSchedulePolling(
not_null<Story*> story,
const PollingSettings &settings,
TimeId now);
void sendPollingRequests();
const not_null<Session*> _owner;
std::unordered_map<
PeerId,
@@ -336,6 +358,9 @@ private:
mtpRequestId _readTillsRequestId = 0;
bool _readTillReceived = false;
base::flat_map<not_null<Story*>, PollingSettings> _pollingSettings;
base::Timer _pollingTimer;
};
} // namespace Data

View File

@@ -371,7 +371,12 @@ void Story::applyViewsSlice(
}
}
bool Story::applyChanges(StoryMedia media, const MTPDstoryItem &data) {
bool Story::applyChanges(
StoryMedia media,
const MTPDstoryItem &data,
TimeId now) {
_lastUpdateTime = now;
const auto pinned = data.is_pinned();
const auto edited = data.is_edited();
const auto isPublic = data.is_public();
@@ -424,6 +429,10 @@ bool Story::applyChanges(StoryMedia media, const MTPDstoryItem &data) {
return true;
}
TimeId Story::lastUpdateTime() const {
return _lastUpdateTime;
}
StoryPreload::StoryPreload(not_null<Story*> story, Fn<void()> done)
: _story(story)
, _done(std::move(done)) {

View File

@@ -113,7 +113,11 @@ public:
const std::vector<StoryView> &slice,
int total);
bool applyChanges(StoryMedia media, const MTPDstoryItem &data);
bool applyChanges(
StoryMedia media,
const MTPDstoryItem &data,
TimeId now);
[[nodiscard]] TimeId lastUpdateTime() const;
private:
const StoryId _id = 0;
@@ -125,6 +129,7 @@ private:
int _views = 0;
const TimeId _date = 0;
const TimeId _expires = 0;
TimeId _lastUpdateTime = 0;
bool _pinned : 1 = false;
bool _isPublic : 1 = false;
bool _closeFriends : 1 = false;