mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-15 21:57:10 +02:00
Preload stories in the sources lists.
This commit is contained in:
parent
21543338d7
commit
d567282430
13 changed files with 264 additions and 443 deletions
|
@ -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
|
||||
|
|
|
@ -65,6 +65,7 @@ object_ptr<Ui::BoxContent> PrepareContactsBox(
|
|||
Mode mode = ContactsBoxController::SortMode::Online;
|
||||
};
|
||||
|
||||
const auto stories = &sessionController->session().data().stories();
|
||||
const auto state = box->lifetime().make_state<State>();
|
||||
box->addButton(tr::lng_close(), [=] { box->closeBox(); });
|
||||
box->addLeftButton(
|
||||
|
@ -80,17 +81,17 @@ object_ptr<Ui::BoxContent> PrepareContactsBox(
|
|||
});
|
||||
raw->setSortMode(Mode::Online);
|
||||
|
||||
auto stories = object_ptr<Stories::List>(
|
||||
auto list = object_ptr<Stories::List>(
|
||||
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<Ui::BoxContent> 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<Ui::BoxContent> 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<PeerListBox>(std::move(controller), std::move(init));
|
||||
}
|
||||
|
|
|
@ -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<RoundData>();
|
||||
} 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
|
||||
|
|
|
@ -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<Data::Session*> _owner;
|
||||
|
||||
int _videoPreloadPrefix = 0;
|
||||
// Two types of location: from MTProto by dc+access or from web by url
|
||||
int32 _dc = 0;
|
||||
uint64 _access = 0;
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -166,14 +166,22 @@ bool PhotoMedia::autoLoadThumbnailAllowed(not_null<PeerData*> 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<PeerData*> peer) {
|
||||
if (loaded() || _owner->cancelled()) {
|
||||
return;
|
||||
}
|
||||
const auto loadFromCloud = Data::AutoDownload::Should(
|
||||
_owner->session().settings().autoDownload(),
|
||||
item->history()->peer,
|
||||
peer,
|
||||
_owner);
|
||||
_owner->load(
|
||||
origin,
|
||||
|
|
|
@ -43,7 +43,8 @@ public:
|
|||
|
||||
[[nodiscard]] bool autoLoadThumbnailAllowed(
|
||||
not_null<PeerData*> peer) const;
|
||||
void automaticLoad(Data::FileOrigin origin, const HistoryItem *item);
|
||||
void automaticLoad(FileOrigin origin, const HistoryItem *item);
|
||||
void automaticLoad(FileOrigin origin, not_null<PeerData*> peer);
|
||||
|
||||
void collectLocalData(not_null<PhotoMedia*> local);
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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<PeerData*> 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<PeerData*> 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<not_null<PhotoData*>>(&_media.data);
|
||||
return result ? result->get() : nullptr;
|
||||
}
|
||||
|
||||
DocumentData *Story::document() const {
|
||||
const auto result = std::get_if<not_null<DocumentData*>>(&_media.data);
|
||||
return result ? result->get() : nullptr;
|
||||
}
|
||||
|
||||
bool Story::hasReplyPreview() const {
|
||||
return v::match(_media.data, [](not_null<PhotoData*> photo) {
|
||||
return !photo->isNull();
|
||||
}, [](not_null<DocumentData*> document) {
|
||||
return document->hasThumbnail();
|
||||
}, [](v::null_t) {
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
Image *Story::replyPreview() const {
|
||||
return v::match(_media.data, [&](not_null<PhotoData*> photo) {
|
||||
return photo->getReplyPreview(
|
||||
Data::FileOriginStory(_peer->id, _id),
|
||||
_peer,
|
||||
false);
|
||||
}, [&](not_null<DocumentData*> 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<QString> Story::errorTextForForward(
|
||||
not_null<Thread*> to) const {
|
||||
const auto peer = to->peer();
|
||||
const auto holdsPhoto = v::is<not_null<PhotoData*>>(_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<not_null<PeerData*>> recent,
|
||||
int total) {
|
||||
_recentViewers = std::move(recent);
|
||||
_views = total;
|
||||
}
|
||||
|
||||
const std::vector<not_null<PeerData*>> &Story::recentViewers() const {
|
||||
return _recentViewers;
|
||||
}
|
||||
|
||||
const std::vector<StoryView> &Story::viewsList() const {
|
||||
return _viewsList;
|
||||
}
|
||||
|
||||
int Story::views() const {
|
||||
return _views;
|
||||
}
|
||||
|
||||
void Story::applyViewsSlice(
|
||||
const std::optional<StoryView> &offset,
|
||||
const std::vector<StoryView> &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<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());
|
||||
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<Session*> 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<Story>(
|
||||
|
@ -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<HistoryItem> Stories::lookupItem(not_null<Story*> 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<FullStoryId> 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<int>(list);
|
||||
if (!_preloadingHiddenSourcesCounter
|
||||
&& list == StorySourcesList::Hidden) {
|
||||
return !base::take(_toPreloadSources[index]).empty();
|
||||
}
|
||||
auto now = std::vector<FullStoryId>();
|
||||
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<int>(StorySourcesList::Hidden)],
|
||||
_toPreloadSources[static_cast<int>(StorySourcesList::NotHidden)]
|
||||
) | ranges::views::take(kStillPreloadFromFirst);
|
||||
return ranges::contains(first, id);
|
||||
}
|
||||
|
||||
FullStoryId Stories::nextPreloadId() const {
|
||||
const auto hidden = static_cast<int>(StorySourcesList::Hidden);
|
||||
const auto main = static_cast<int>(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*> story) {
|
||||
Expects(!_preloaded.contains(story->fullId()));
|
||||
|
||||
const auto id = story->fullId();
|
||||
_preloading = std::make_unique<StoryPreload>(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
|
||||
|
|
|
@ -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<StoryId, std::greater<>> list;
|
||||
|
@ -55,98 +38,6 @@ struct StoriesIds {
|
|||
const StoriesIds&) = default;
|
||||
};
|
||||
|
||||
struct StoryMedia {
|
||||
std::variant<
|
||||
v::null_t,
|
||||
not_null<PhotoData*>,
|
||||
not_null<DocumentData*>> data;
|
||||
|
||||
friend inline bool operator==(StoryMedia, StoryMedia) = default;
|
||||
};
|
||||
|
||||
struct StoryView {
|
||||
not_null<PeerData*> peer;
|
||||
TimeId date = 0;
|
||||
|
||||
friend inline bool operator==(StoryView, StoryView) = default;
|
||||
};
|
||||
|
||||
class Story final {
|
||||
public:
|
||||
Story(
|
||||
StoryId id,
|
||||
not_null<PeerData*> peer,
|
||||
StoryMedia media,
|
||||
TimeId date,
|
||||
TimeId expires);
|
||||
|
||||
[[nodiscard]] Session &owner() const;
|
||||
[[nodiscard]] Main::Session &session() const;
|
||||
[[nodiscard]] not_null<PeerData*> 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<QString> errorTextForForward(
|
||||
not_null<Thread*> to) const;
|
||||
|
||||
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;
|
||||
[[nodiscard]] int views() const;
|
||||
void applyViewsSlice(
|
||||
const std::optional<StoryView> &offset,
|
||||
const std::vector<StoryView> &slice,
|
||||
int total);
|
||||
|
||||
bool applyChanges(StoryMedia media, const MTPDstoryItem &data);
|
||||
|
||||
private:
|
||||
const StoryId _id = 0;
|
||||
const not_null<PeerData*> _peer;
|
||||
StoryMedia _media;
|
||||
TextWithEntities _caption;
|
||||
std::vector<not_null<PeerData*>> _recentViewers;
|
||||
std::vector<StoryView> _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<FullStoryId> ids);
|
||||
|
||||
private:
|
||||
struct Saved {
|
||||
StoriesIds ids;
|
||||
|
@ -339,11 +235,18 @@ private:
|
|||
void checkQuitPreventFinished();
|
||||
|
||||
void requestUserStories(not_null<UserData*> user);
|
||||
void addToArchive(not_null<Story*> 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*> story);
|
||||
void preloadFinished(bool markAsPreloaded = false);
|
||||
|
||||
const not_null<Session*> _owner;
|
||||
std::unordered_map<
|
||||
PeerId,
|
||||
|
@ -402,6 +305,12 @@ private:
|
|||
Fn<void(std::vector<StoryView>)> _viewsDone;
|
||||
mtpRequestId _viewsRequestId = 0;
|
||||
|
||||
base::flat_set<FullStoryId> _preloaded;
|
||||
std::vector<FullStoryId> _toPreloadSources[kStorySourcesListCount];
|
||||
std::vector<FullStoryId> _toPreloadViewer;
|
||||
std::unique_ptr<StoryPreload> _preloading;
|
||||
int _preloadingHiddenSourcesCounter = 0;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Data
|
||||
|
|
|
@ -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<const char*>(&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<uint32, QByteArray> &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<const char*>(&serialized),
|
||||
intSize);
|
||||
};
|
||||
appendInt(count);
|
||||
for (const auto &[offset, part] : parts) {
|
||||
appendInt(offset);
|
||||
appendInt(part.size());
|
||||
result.append(part);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace Streaming
|
||||
} // namespace Media
|
||||
|
|
|
@ -277,5 +277,8 @@ private:
|
|||
|
||||
};
|
||||
|
||||
[[nodiscard]] QByteArray SerializeComplexPartsMap(
|
||||
const base::flat_map<uint32, QByteArray> &parts);
|
||||
|
||||
} // namespace Streaming
|
||||
} // namespace Media
|
||||
|
|
|
@ -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() }));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue