diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 3a3d7e5a76..a4ac996e8d 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -553,6 +553,8 @@ PRIVATE data/data_stories.h data/data_stories_ids.cpp data/data_stories_ids.h + data/data_story.cpp + data/data_story.h data/data_streaming.cpp data/data_streaming.h data/data_thread.cpp diff --git a/Telegram/SourceFiles/boxes/peer_list_controllers.cpp b/Telegram/SourceFiles/boxes/peer_list_controllers.cpp index 1b62876ed9..1e104b5a36 100644 --- a/Telegram/SourceFiles/boxes/peer_list_controllers.cpp +++ b/Telegram/SourceFiles/boxes/peer_list_controllers.cpp @@ -65,6 +65,7 @@ object_ptr PrepareContactsBox( Mode mode = ContactsBoxController::SortMode::Online; }; + const auto stories = &sessionController->session().data().stories(); const auto state = box->lifetime().make_state(); box->addButton(tr::lng_close(), [=] { box->closeBox(); }); box->addLeftButton( @@ -80,17 +81,17 @@ object_ptr PrepareContactsBox( }); raw->setSortMode(Mode::Online); - auto stories = object_ptr( + auto list = object_ptr( box, st::dialogsStoriesList, Stories::ContentForSession( &sessionController->session(), Data::StorySourcesList::Hidden), [=] { return state->stories->height() - box->scrollTop(); }); - const auto raw = state->stories = stories.data(); + const auto raw = state->stories = list.data(); box->peerListSetAboveWidget(object_ptr<::Ui::PaddingWrap<>>( box, - std::move(stories), + std::move(list), style::margins(0, st::membersMarginTop, 0, 0))); raw->clicks( @@ -107,7 +108,7 @@ object_ptr PrepareContactsBox( raw->toggleShown( ) | rpl::start_with_next([=](Stories::ToggleShownRequest request) { - sessionController->session().data().stories().toggleHidden( + stories->toggleHidden( PeerId(int64(request.id)), !request.shown, sessionController->uiShow()); @@ -115,9 +116,13 @@ object_ptr PrepareContactsBox( raw->loadMoreRequests( ) | rpl::start_with_next([=] { - sessionController->session().data().stories().loadMore( - Data::StorySourcesList::Hidden); + stories->loadMore(Data::StorySourcesList::Hidden); }, raw->lifetime()); + + stories->incrementPreloadingHiddenSources(); + raw->lifetime().add([=] { + stories->decrementPreloadingHiddenSources(); + }); }; return Box(std::move(controller), std::move(init)); } diff --git a/Telegram/SourceFiles/data/data_document.cpp b/Telegram/SourceFiles/data/data_document.cpp index 473b7027d4..173cfbb911 100644 --- a/Telegram/SourceFiles/data/data_document.cpp +++ b/Telegram/SourceFiles/data/data_document.cpp @@ -335,6 +335,7 @@ void DocumentData::setattributes( validateLottieSticker(); + _videoPreloadPrefix = 0; for (const auto &attribute : attributes) { attribute.match([&](const MTPDdocumentAttributeImageSize &data) { dimensions = QSize(data.vw().v, data.vh().v); @@ -390,6 +391,10 @@ void DocumentData::setattributes( : VideoDocument; if (data.is_round_message()) { _additional = std::make_unique(); + } else if (const auto size = data.vpreload_prefix_size()) { + if (size->v > 0) { + _videoPreloadPrefix = size->v; + } } } else if (const auto info = sticker()) { info->type = StickerType::Webm; @@ -1334,6 +1339,23 @@ bool DocumentData::inappPlaybackFailed() const { return (_flags & Flag::StreamingPlaybackFailed); } +int DocumentData::videoPreloadPrefix() const { + return _videoPreloadPrefix; +} + +StorageFileLocation DocumentData::videoPreloadLocation() const { + return hasRemoteLocation() + ? StorageFileLocation( + _dc, + session().userId(), + MTP_inputDocumentFileLocation( + MTP_long(id), + MTP_long(_access), + MTP_bytes(_fileReference), + MTP_string())) + : StorageFileLocation(); +} + auto DocumentData::createStreamingLoader( Data::FileOrigin origin, bool forceRemoteLoader) const diff --git a/Telegram/SourceFiles/data/data_document.h b/Telegram/SourceFiles/data/data_document.h index 617eb331c7..ded87abfde 100644 --- a/Telegram/SourceFiles/data/data_document.h +++ b/Telegram/SourceFiles/data/data_document.h @@ -274,6 +274,8 @@ public: void setInappPlaybackFailed(); [[nodiscard]] bool inappPlaybackFailed() const; + [[nodiscard]] int videoPreloadPrefix() const; + [[nodiscard]] StorageFileLocation videoPreloadLocation() const; DocumentId id = 0; int64 size = 0; @@ -344,6 +346,7 @@ private: const not_null _owner; + int _videoPreloadPrefix = 0; // Two types of location: from MTProto by dc+access or from web by url int32 _dc = 0; uint64 _access = 0; diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp index 1a80583c8e..79f99591d2 100644 --- a/Telegram/SourceFiles/data/data_media_types.cpp +++ b/Telegram/SourceFiles/data/data_media_types.cpp @@ -57,6 +57,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_channel.h" #include "data/data_file_origin.h" #include "data/data_stories.h" +#include "data/data_story.h" #include "main/main_session.h" #include "main/main_session_settings.h" #include "core/application.h" diff --git a/Telegram/SourceFiles/data/data_photo_media.cpp b/Telegram/SourceFiles/data/data_photo_media.cpp index 7bbdcdd1ee..be713b4a93 100644 --- a/Telegram/SourceFiles/data/data_photo_media.cpp +++ b/Telegram/SourceFiles/data/data_photo_media.cpp @@ -166,14 +166,22 @@ bool PhotoMedia::autoLoadThumbnailAllowed(not_null peer) const { } void PhotoMedia::automaticLoad( - Data::FileOrigin origin, + FileOrigin origin, const HistoryItem *item) { - if (!item || loaded() || _owner->cancelled()) { + if (item) { + automaticLoad(origin, item->history()->peer); + } +} + +void PhotoMedia::automaticLoad( + FileOrigin origin, + not_null peer) { + if (loaded() || _owner->cancelled()) { return; } const auto loadFromCloud = Data::AutoDownload::Should( _owner->session().settings().autoDownload(), - item->history()->peer, + peer, _owner); _owner->load( origin, diff --git a/Telegram/SourceFiles/data/data_photo_media.h b/Telegram/SourceFiles/data/data_photo_media.h index 7c5c34bd4e..f9e3c77087 100644 --- a/Telegram/SourceFiles/data/data_photo_media.h +++ b/Telegram/SourceFiles/data/data_photo_media.h @@ -43,7 +43,8 @@ public: [[nodiscard]] bool autoLoadThumbnailAllowed( not_null peer) const; - void automaticLoad(Data::FileOrigin origin, const HistoryItem *item); + void automaticLoad(FileOrigin origin, const HistoryItem *item); + void automaticLoad(FileOrigin origin, not_null peer); void collectLocalData(not_null local); diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index 2023c9b671..6c3100d4d0 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -67,6 +67,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_forum_icons.h" #include "data/data_cloud_themes.h" #include "data/data_stories.h" +#include "data/data_story.h" #include "data/data_streaming.h" #include "data/data_media_rotation.h" #include "data/data_histories.h" diff --git a/Telegram/SourceFiles/data/data_stories.cpp b/Telegram/SourceFiles/data/data_stories.cpp index 30d7fbc8f7..0c7586151b 100644 --- a/Telegram/SourceFiles/data/data_stories.cpp +++ b/Telegram/SourceFiles/data/data_stories.cpp @@ -9,17 +9,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_report.h" #include "base/unixtime.h" -#include "api/api_text_entities.h" #include "apiwrap.h" #include "core/application.h" #include "data/data_changes.h" -#include "data/data_chat_participant_status.h" #include "data/data_document.h" -#include "data/data_file_origin.h" #include "data/data_photo.h" #include "data/data_user.h" #include "data/data_session.h" -#include "data/data_thread.h" #include "history/history.h" #include "history/history_item.h" #include "lang/lang_keys.h" @@ -39,6 +35,8 @@ constexpr auto kArchiveFirstPerPage = 30; constexpr auto kArchivePerPage = 100; constexpr auto kSavedFirstPerPage = 30; constexpr auto kSavedPerPage = 100; +constexpr auto kMaxPreloadSources = 10; +constexpr auto kStillPreloadFromFirst = 3; using UpdateFlag = StoryUpdate::Flag; @@ -86,296 +84,12 @@ bool StoriesSource::unread() const { return !ids.empty() && readTill < ids.back().id; } -Story::Story( - StoryId id, - not_null peer, - StoryMedia media, - TimeId date, - TimeId expires) -: _id(id) -, _peer(peer) -, _media(std::move(media)) -, _date(date) -, _expires(expires) { -} - -Session &Story::owner() const { - return _peer->owner(); -} - -Main::Session &Story::session() const { - return _peer->session(); -} - -not_null Story::peer() const { - return _peer; -} - -StoryId Story::id() const { - return _id; -} - -bool Story::mine() const { - return _peer->isSelf(); -} - -StoryIdDates Story::idDates() const { - return { _id, _date, _expires }; -} - -FullStoryId Story::fullId() const { - return { _peer->id, _id }; -} - -TimeId Story::date() const { - return _date; -} - -TimeId Story::expires() const { - return _expires; -} - -bool Story::expired(TimeId now) const { - return _expires <= (now ? now : base::unixtime::now()); -} - -bool Story::unsupported() const { - return v::is_null(_media.data); -} - -const StoryMedia &Story::media() const { - return _media; -} - -PhotoData *Story::photo() const { - const auto result = std::get_if>(&_media.data); - return result ? result->get() : nullptr; -} - -DocumentData *Story::document() const { - const auto result = std::get_if>(&_media.data); - return result ? result->get() : nullptr; -} - -bool Story::hasReplyPreview() const { - return v::match(_media.data, [](not_null photo) { - return !photo->isNull(); - }, [](not_null document) { - return document->hasThumbnail(); - }, [](v::null_t) { - return false; - }); -} - -Image *Story::replyPreview() const { - return v::match(_media.data, [&](not_null photo) { - return photo->getReplyPreview( - Data::FileOriginStory(_peer->id, _id), - _peer, - false); - }, [&](not_null document) { - return document->getReplyPreview( - Data::FileOriginStory(_peer->id, _id), - _peer, - false); - }, [](v::null_t) { - return (Image*)nullptr; - }); -} - -TextWithEntities Story::inReplyText() const { - const auto type = tr::lng_in_dlg_story(tr::now); - return _caption.text.isEmpty() - ? Ui::Text::PlainLink(type) - : tr::lng_dialogs_text_media( - tr::now, - lt_media_part, - tr::lng_dialogs_text_media_wrapped( - tr::now, - lt_media, - Ui::Text::PlainLink(type), - Ui::Text::WithEntities), - lt_caption, - _caption, - Ui::Text::WithEntities); -} - -void Story::setPinned(bool pinned) { - _pinned = pinned; -} - -bool Story::pinned() const { - return _pinned; -} - -void Story::setIsPublic(bool isPublic) { - _isPublic = isPublic; -} - -bool Story::isPublic() const { - return _isPublic; -} - -void Story::setCloseFriends(bool closeFriends) { - _closeFriends = closeFriends; -} - -bool Story::closeFriends() const { - return _closeFriends; -} - -bool Story::canDownload() const { - return _peer->isSelf(); -} - -bool Story::canShare() const { - return isPublic() && (pinned() || !expired()); -} - -bool Story::canDelete() const { - return _peer->isSelf(); -} - -bool Story::canReport() const { - return !_peer->isSelf(); -} - -bool Story::hasDirectLink() const { - if (!_isPublic || (!_pinned && expired())) { - return false; +StoryIdDates StoriesSource::toOpen() const { + if (ids.empty()) { + return {}; } - const auto user = _peer->asUser(); - return user && !user->username().isEmpty(); -} - -std::optional Story::errorTextForForward( - not_null to) const { - const auto peer = to->peer(); - const auto holdsPhoto = v::is>(_media.data); - const auto first = holdsPhoto - ? ChatRestriction::SendPhotos - : ChatRestriction::SendVideos; - const auto second = holdsPhoto - ? ChatRestriction::SendVideos - : ChatRestriction::SendPhotos; - if (const auto error = Data::RestrictionError(peer, first)) { - return *error; - } else if (const auto error = Data::RestrictionError(peer, second)) { - return *error; - } else if (!Data::CanSend(to, first, false) - || !Data::CanSend(to, second, false)) { - return tr::lng_forward_cant(tr::now); - } - return {}; -} - -void Story::setCaption(TextWithEntities &&caption) { - _caption = std::move(caption); -} - -const TextWithEntities &Story::caption() const { - static const auto empty = TextWithEntities(); - 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; -} - -const std::vector &Story::viewsList() const { - return _viewsList; -} - -int Story::views() const { - return _views; -} - -void Story::applyViewsSlice( - const std::optional &offset, - const std::vector &slice, - int total) { - _views = total; - if (!offset) { - const auto i = _viewsList.empty() - ? end(slice) - : ranges::find(slice, _viewsList.front()); - const auto merge = (i != end(slice)) - && !ranges::contains(slice, _viewsList.back()); - if (merge) { - _viewsList.insert(begin(_viewsList), begin(slice), i); - } else { - _viewsList = slice; - } - } else if (!slice.empty()) { - const auto i = ranges::find(_viewsList, *offset); - const auto merge = (i != end(_viewsList)) - && !ranges::contains(_viewsList, slice.back()); - if (merge) { - const auto after = i + 1; - if (after == end(_viewsList)) { - _viewsList.insert(after, begin(slice), end(slice)); - } else { - const auto j = ranges::find(slice, _viewsList.back()); - if (j != end(slice)) { - _viewsList.insert(end(_viewsList), j + 1, end(slice)); - } - } - } - } -} - -bool Story::applyChanges(StoryMedia media, const MTPDstoryItem &data) { - const auto pinned = data.is_pinned(); - const auto isPublic = data.is_public(); - const auto closeFriends = data.is_close_friends(); - auto caption = TextWithEntities{ - data.vcaption().value_or_empty(), - Api::EntitiesFromMTP( - &owner().session(), - data.ventities().value_or_empty()), - }; - auto views = -1; - auto recent = 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()); - auto &owner = _peer->owner(); - for (const auto &id : list->v) { - recent.push_back(owner.peer(peerFromUser(id))); - } - } - } - } - - const auto changed = (_media != media) - || (_pinned != pinned) - || (_isPublic != isPublic) - || (_closeFriends != closeFriends) - || (_caption != caption) - || (views >= 0 && _views != views) - || (_recentViewers != recent); - if (!changed) { - return false; - } - _media = std::move(media); - _pinned = pinned; - _isPublic = isPublic; - _closeFriends = closeFriends; - _caption = std::move(caption); - if (views >= 0) { - _views = views; - } - _recentViewers = std::move(recent); - return true; + const auto i = ids.lower_bound(StoryIdDates{ readTill + 1 }); + return (i != end(ids)) ? *i : ids.front(); } Stories::Stories(not_null owner) @@ -622,6 +336,7 @@ Story *Stories::parseAndApply( 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)) { if (result->pinned() != pinned) { savedStateUpdated(result); @@ -633,6 +348,16 @@ Story *Stories::parseAndApply( item->applyChanges(result); } } + if (mediaChanged) { + const auto fullId = result->fullId(); + _preloaded.remove(fullId); + if (_preloading && _preloading->id() == fullId) { + _preloading = nullptr; + rebuildPreloadSources(StorySourcesList::NotHidden); + rebuildPreloadSources(StorySourcesList::Hidden); + continuePreloading(); + } + } return result; } const auto result = stories.emplace(id, std::make_unique( @@ -911,6 +636,9 @@ void Stories::applyDeleted(FullStoryId id) { } } } + if (_preloading && _preloading->id() == id) { + preloadFinished(); + } if (i->second.empty()) { _stories.erase(i); } @@ -996,6 +724,7 @@ void Stories::sort(StorySourcesList list) { }; ranges::sort(sources, ranges::greater(), proj); _sourcesChanged[index].fire({}); + preloadSourcesChanged(list); } std::shared_ptr Stories::lookupItem(not_null story) { @@ -1225,6 +954,7 @@ void Stories::toggleHidden( if (i != end(_sources[main])) { _sources[main].erase(i); _sourcesChanged[main].fire({}); + preloadSourcesChanged(StorySourcesList::NotHidden); } const auto j = ranges::find(_sources[other], peerId, proj); if (j == end(_sources[other])) { @@ -1238,6 +968,7 @@ void Stories::toggleHidden( if (i != end(_sources[other])) { _sources[other].erase(i); _sourcesChanged[other].fire({}); + preloadSourcesChanged(StorySourcesList::Hidden); } const auto j = ranges::find(_sources[main], peerId, proj); if (j == end(_sources[main])) { @@ -1563,4 +1294,134 @@ bool Stories::isQuitPrevent() { return true; } +void Stories::incrementPreloadingHiddenSources() { + Expects(_preloadingHiddenSourcesCounter >= 0); + + if (++_preloadingHiddenSourcesCounter == 1 + && rebuildPreloadSources(StorySourcesList::Hidden)) { + continuePreloading(); + } +} + +void Stories::decrementPreloadingHiddenSources() { + Expects(_preloadingHiddenSourcesCounter > 0); + + if (!--_preloadingHiddenSourcesCounter + && rebuildPreloadSources(StorySourcesList::Hidden)) { + continuePreloading(); + } +} + +void Stories::setPreloadingInViewer(std::vector ids) { + ids.erase(ranges::remove_if(ids, [&](FullStoryId id) { + return _preloaded.contains(id); + }), end(ids)); + if (_toPreloadViewer != ids) { + _toPreloadViewer = std::move(ids); + continuePreloading(); + } +} + +void Stories::preloadSourcesChanged(StorySourcesList list) { + if (rebuildPreloadSources(list)) { + continuePreloading(); + } +} + +bool Stories::rebuildPreloadSources(StorySourcesList list) { + const auto index = static_cast(list); + if (!_preloadingHiddenSourcesCounter + && list == StorySourcesList::Hidden) { + return !base::take(_toPreloadSources[index]).empty(); + } + auto now = std::vector(); + auto processed = 0; + for (const auto &source : _sources[index]) { + const auto i = _all.find(source.id); + if (i != end(_all)) { + if (const auto id = i->second.toOpen().id) { + const auto fullId = FullStoryId{ source.id, id }; + if (!_preloaded.contains(fullId)) { + now.push_back(fullId); + } + } + } + if (++processed >= kMaxPreloadSources) { + break; + } + } + if (now != _toPreloadSources[index]) { + _toPreloadSources[index] = std::move(now); + return true; + } + return false; +} + +void Stories::continuePreloading() { + const auto now = _preloading ? _preloading->id() : FullStoryId(); + if (now) { + if (shouldContinuePreload(now)) { + return; + } + _preloading = nullptr; + } + const auto id = nextPreloadId(); + if (!id) { + return; + } else if (const auto maybeStory = lookup(id)) { + startPreloading(*maybeStory); + } +} + +bool Stories::shouldContinuePreload(FullStoryId id) const { + const auto first = ranges::views::concat( + _toPreloadViewer, + _toPreloadSources[static_cast(StorySourcesList::Hidden)], + _toPreloadSources[static_cast(StorySourcesList::NotHidden)] + ) | ranges::views::take(kStillPreloadFromFirst); + return ranges::contains(first, id); +} + +FullStoryId Stories::nextPreloadId() const { + const auto hidden = static_cast(StorySourcesList::Hidden); + const auto main = static_cast(StorySourcesList::NotHidden); + const auto result = !_toPreloadViewer.empty() + ? _toPreloadViewer.front() + : !_toPreloadSources[hidden].empty() + ? _toPreloadSources[hidden].front() + : !_toPreloadSources[main].empty() + ? _toPreloadSources[main].front() + : FullStoryId(); + + Ensures(!_preloaded.contains(result)); + return result; +} + +void Stories::startPreloading(not_null story) { + Expects(!_preloaded.contains(story->fullId())); + + const auto id = story->fullId(); + _preloading = std::make_unique(story, [=] { + preloadFinished(true); + }); +} + +void Stories::preloadFinished(bool markAsPreloaded) { + Expects(_preloading != nullptr); + + const auto id = base::take(_preloading)->id(); + for (auto &sources : _toPreloadSources) { + sources.erase(ranges::remove(sources, id), end(sources)); + } + _toPreloadViewer.erase( + ranges::remove(_toPreloadViewer, id), + end(_toPreloadViewer)); + if (markAsPreloaded) { + _preloaded.emplace(id); + } + crl::on_main(this, [=] { + continuePreloading(); + }); +} + } // namespace Data diff --git a/Telegram/SourceFiles/data/data_stories.h b/Telegram/SourceFiles/data/data_stories.h index 2cd0dcccff..80d4a97327 100644 --- a/Telegram/SourceFiles/data/data_stories.h +++ b/Telegram/SourceFiles/data/data_stories.h @@ -11,11 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/expected.h" #include "base/timer.h" #include "base/weak_ptr.h" - -class Image; -class PhotoData; -class DocumentData; -enum class ChatRestriction; +#include "data/data_story.h" namespace Main { class Session; @@ -29,23 +25,10 @@ enum class ReportReason; namespace Data { class Session; -class Thread; - -struct StoryIdDates { - StoryId id = 0; - TimeId date = 0; - TimeId expires = 0; - - [[nodiscard]] bool valid() const { - return id != 0; - } - explicit operator bool() const { - return valid(); - } - - friend inline auto operator<=>(StoryIdDates, StoryIdDates) = default; - friend inline bool operator==(StoryIdDates, StoryIdDates) = default; -}; +struct StoryView; +struct StoryIdDates; +class Story; +class StoryPreload; struct StoriesIds { base::flat_set> list; @@ -55,98 +38,6 @@ struct StoriesIds { const StoriesIds&) = default; }; -struct StoryMedia { - std::variant< - v::null_t, - not_null, - not_null> data; - - friend inline bool operator==(StoryMedia, StoryMedia) = default; -}; - -struct StoryView { - not_null peer; - TimeId date = 0; - - friend inline bool operator==(StoryView, StoryView) = default; -}; - -class Story final { -public: - Story( - StoryId id, - not_null peer, - StoryMedia media, - TimeId date, - TimeId expires); - - [[nodiscard]] Session &owner() const; - [[nodiscard]] Main::Session &session() const; - [[nodiscard]] not_null peer() const; - - [[nodiscard]] StoryId id() const; - [[nodiscard]] bool mine() const; - [[nodiscard]] StoryIdDates idDates() const; - [[nodiscard]] FullStoryId fullId() const; - [[nodiscard]] TimeId date() const; - [[nodiscard]] TimeId expires() const; - [[nodiscard]] bool unsupported() const; - [[nodiscard]] bool expired(TimeId now = 0) const; - [[nodiscard]] const StoryMedia &media() const; - [[nodiscard]] PhotoData *photo() const; - [[nodiscard]] DocumentData *document() const; - - [[nodiscard]] bool hasReplyPreview() const; - [[nodiscard]] Image *replyPreview() const; - [[nodiscard]] TextWithEntities inReplyText() const; - - void setPinned(bool pinned); - [[nodiscard]] bool pinned() const; - void setIsPublic(bool isPublic); - [[nodiscard]] bool isPublic() const; - void setCloseFriends(bool closeFriends); - [[nodiscard]] bool closeFriends() const; - - [[nodiscard]] bool canDownload() const; - [[nodiscard]] bool canShare() const; - [[nodiscard]] bool canDelete() const; - [[nodiscard]] bool canReport() const; - - [[nodiscard]] bool hasDirectLink() const; - [[nodiscard]] std::optional errorTextForForward( - not_null to) const; - - 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; - [[nodiscard]] int views() const; - void applyViewsSlice( - const std::optional &offset, - const std::vector &slice, - int total); - - bool applyChanges(StoryMedia media, const MTPDstoryItem &data); - -private: - const StoryId _id = 0; - const not_null _peer; - StoryMedia _media; - TextWithEntities _caption; - std::vector> _recentViewers; - std::vector _viewsList; - int _views = 0; - const TimeId _date = 0; - const TimeId _expires = 0; - bool _pinned : 1 = false; - bool _isPublic : 1 = false; - bool _closeFriends : 1 = false; - -}; - struct StoriesSourceInfo { PeerId id = 0; TimeId last = 0; @@ -167,6 +58,7 @@ struct StoriesSource { [[nodiscard]] StoriesSourceInfo info() const; [[nodiscard]] bool unread() const; + [[nodiscard]] StoryIdDates toOpen() const; friend inline bool operator==(StoriesSource, StoriesSource) = default; }; @@ -298,6 +190,10 @@ public: Ui::ReportReason reason, QString text); + void incrementPreloadingHiddenSources(); + void decrementPreloadingHiddenSources(); + void setPreloadingInViewer(std::vector ids); + private: struct Saved { StoriesIds ids; @@ -339,11 +235,18 @@ private: void checkQuitPreventFinished(); void requestUserStories(not_null user); - void addToArchive(not_null story); void registerExpiring(TimeId expires, FullStoryId id); void scheduleExpireTimer(); void processExpired(); + void preloadSourcesChanged(StorySourcesList list); + bool rebuildPreloadSources(StorySourcesList list); + void continuePreloading(); + [[nodiscard]] bool shouldContinuePreload(FullStoryId id) const; + [[nodiscard]] FullStoryId nextPreloadId() const; + void startPreloading(not_null story); + void preloadFinished(bool markAsPreloaded = false); + const not_null _owner; std::unordered_map< PeerId, @@ -402,6 +305,12 @@ private: Fn)> _viewsDone; mtpRequestId _viewsRequestId = 0; + base::flat_set _preloaded; + std::vector _toPreloadSources[kStorySourcesListCount]; + std::vector _toPreloadViewer; + std::unique_ptr _preloading; + int _preloadingHiddenSourcesCounter = 0; + }; } // namespace Data diff --git a/Telegram/SourceFiles/media/streaming/media_streaming_reader.cpp b/Telegram/SourceFiles/media/streaming/media_streaming_reader.cpp index d87ad902f9..aa5841c53b 100644 --- a/Telegram/SourceFiles/media/streaming/media_streaming_reader.cpp +++ b/Telegram/SourceFiles/media/streaming/media_streaming_reader.cpp @@ -807,23 +807,7 @@ void Reader::Slices::unloadSlice(Slice &slice) const { } QByteArray Reader::Slices::serializeComplexSlice(const Slice &slice) const { - auto result = QByteArray(); - const auto count = slice.parts.size(); - const auto intSize = sizeof(int32); - result.reserve(count * kPartSize + 2 * intSize * (count + 1)); - const auto appendInt = [&](int value) { - auto serialized = int32(value); - result.append( - reinterpret_cast(&serialized), - intSize); - }; - appendInt(count); - for (const auto &[offset, part] : slice.parts) { - appendInt(offset); - appendInt(part.size()); - result.append(part); - } - return result; + return SerializeComplexPartsMap(slice.parts); } QByteArray Reader::Slices::serializeAndUnloadFirstSliceNoHeader() { @@ -1411,5 +1395,26 @@ Reader::~Reader() { finalizeCache(); } +QByteArray SerializeComplexPartsMap( + const base::flat_map &parts) { + auto result = QByteArray(); + const auto count = parts.size(); + const auto intSize = sizeof(int32); + result.reserve(count * kPartSize + 2 * intSize * (count + 1)); + const auto appendInt = [&](int value) { + auto serialized = int32(value); + result.append( + reinterpret_cast(&serialized), + intSize); + }; + appendInt(count); + for (const auto &[offset, part] : parts) { + appendInt(offset); + appendInt(part.size()); + result.append(part); + } + return result; +} + } // namespace Streaming } // namespace Media diff --git a/Telegram/SourceFiles/media/streaming/media_streaming_reader.h b/Telegram/SourceFiles/media/streaming/media_streaming_reader.h index c588823ac4..1798298461 100644 --- a/Telegram/SourceFiles/media/streaming/media_streaming_reader.h +++ b/Telegram/SourceFiles/media/streaming/media_streaming_reader.h @@ -277,5 +277,8 @@ private: }; +[[nodiscard]] QByteArray SerializeComplexPartsMap( + const base::flat_map &parts); + } // namespace Streaming } // namespace Media diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index 9758d85452..e35e83d038 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -2542,14 +2542,14 @@ void SessionController::openPeerStories( auto &stories = session().data().stories(); if (const auto source = stories.source(peerId)) { - const auto j = source->ids.lower_bound( - StoryIdDates{ source->readTill + 1 }); - openPeerStory( - source->user, - j != source->ids.end() ? j->id : source->ids.front().id, - (list - ? StoriesContext{ *list } - : StoriesContext{ StoriesContextPeer() })); + if (const auto idDates = source->toOpen()) { + openPeerStory( + source->user, + idDates.id, + (list + ? StoriesContext{ *list } + : StoriesContext{ StoriesContextPeer() })); + } } }