diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 83aadaeaa..abd2ef8f8 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -569,6 +569,8 @@ PRIVATE data/data_lastseen_status.h data/data_location.cpp data/data_location.h + data/data_media_preload.cpp + data/data_media_preload.h data/data_media_rotation.cpp data/data_media_rotation.h data/data_media_types.cpp diff --git a/Telegram/SourceFiles/data/data_media_preload.cpp b/Telegram/SourceFiles/data/data_media_preload.cpp new file mode 100644 index 000000000..24b8ec401 --- /dev/null +++ b/Telegram/SourceFiles/data/data_media_preload.cpp @@ -0,0 +1,210 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "data/data_media_preload.h" + +#include "data/data_document.h" +#include "data/data_document_media.h" +#include "data/data_file_origin.h" +#include "data/data_photo.h" +#include "data/data_photo_media.h" +#include "data/data_session.h" +#include "main/main_session.h" +#include "main/main_session_settings.h" +#include "media/streaming/media_streaming_reader.h" +#include "storage/file_download.h" // kMaxFileInMemory. + +namespace Data { +namespace { + +constexpr auto kDefaultPreloadPrefix = 4 * 1024 * 1024; + +[[nodiscard]] int64 ChoosePreloadPrefix(not_null video) { + const auto result = video->videoPreloadPrefix(); + return result + ? result + : std::min(int64(kDefaultPreloadPrefix), video->size); +} + +} // namespace + +MediaPreload::MediaPreload(Fn done) +: _done(std::move(done)) { +} + +void MediaPreload::callDone() { + if (const auto onstack = _done) { + onstack(); + } +} + +PhotoPreload::PhotoPreload( + not_null photo, + FileOrigin origin, + Fn done) +: MediaPreload(std::move(done)) +, _photo(photo->createMediaView()) { + start(origin); +} + +PhotoPreload::~PhotoPreload() { + if (_photo) { + base::take(_photo)->owner()->cancel(); + } +} + +bool PhotoPreload::Should( + not_null photo, + not_null context) { + return !photo->cancelled() + && AutoDownload::Should( + photo->session().settings().autoDownload(), + context, + photo); +} + +void PhotoPreload::start(FileOrigin origin) { + if (_photo->loaded()) { + callDone(); + } else { + _photo->owner()->load(origin, LoadFromCloudOrLocal, true); + _photo->owner()->session().downloaderTaskFinished( + ) | rpl::filter([=] { + return _photo->loaded(); + }) | rpl::start_with_next([=] { callDone(); }, _lifetime); + } +} + +VideoPreload::VideoPreload( + not_null video, + FileOrigin origin, + Fn done) +: MediaPreload(std::move(done)) +, DownloadMtprotoTask( + &video->session().downloader(), + video->videoPreloadLocation(), + origin) +, _video(video) +, _full(video->size) { + if (Can(video)) { + check(); + } else { + callDone(); + } +} + +void VideoPreload::check() { + const auto key = _video->bigFileBaseCacheKey(); + const auto weak = base::make_weak(static_cast(this)); + _video->owner().cacheBigFile().get(key, [weak]( + const QByteArray &result) { + if (!result.isEmpty()) { + crl::on_main([weak] { + if (const auto strong = weak.get()) { + static_cast(strong)->callDone(); + } + }); + } else { + crl::on_main([weak] { + if (const auto strong = weak.get()) { + static_cast(strong)->load(); + } + }); + } + }); +} + +void VideoPreload::load() { + if (!Can(_video)) { + callDone(); + return; + } + const auto key = _video->bigFileBaseCacheKey(); + const auto prefix = ChoosePreloadPrefix(_video); + Assert(prefix > 0 && prefix <= _video->size); + const auto part = Storage::kDownloadPartSize; + const auto parts = (prefix + part - 1) / part; + for (auto i = 0; i != parts; ++i) { + _parts.emplace(i * part, QByteArray()); + } + addToQueue(); +} + +void VideoPreload::done(QByteArray result) { + const auto key = _video->bigFileBaseCacheKey(); + if (!result.isEmpty() && key) { + Assert(result.size() < Storage::kMaxFileInMemory); + _video->owner().cacheBigFile().putIfEmpty( + key, + Storage::Cache::Database::TaggedValue(std::move(result), 0)); + } + callDone(); +} + +VideoPreload::~VideoPreload() { + if (!_finished && !_failed) { + cancelAllRequests(); + } +} + +bool VideoPreload::Can(not_null video) { + return video->canBeStreamed(nullptr) + && video->videoPreloadLocation().valid() + && video->bigFileBaseCacheKey(); +} + +bool VideoPreload::readyToRequest() const { + const auto part = Storage::kDownloadPartSize; + return !_failed && (_nextRequestOffset < _parts.size() * part); +} + +int64 VideoPreload::takeNextRequestOffset() { + Expects(readyToRequest()); + + _requestedOffsets.emplace(_nextRequestOffset); + _nextRequestOffset += Storage::kDownloadPartSize; + return _requestedOffsets.back(); +} + +bool VideoPreload::feedPart( + int64 offset, + const QByteArray &bytes) { + Expects(offset < _parts.size() * Storage::kDownloadPartSize); + Expects(_requestedOffsets.contains(int(offset))); + Expects(bytes.size() <= Storage::kDownloadPartSize); + + const auto part = Storage::kDownloadPartSize; + _requestedOffsets.remove(int(offset)); + _parts[offset] = bytes; + if ((_nextRequestOffset + part >= _parts.size() * part) + && _requestedOffsets.empty()) { + _finished = true; + removeFromQueue(); + auto result = ::Media::Streaming::SerializeComplexPartsMap(_parts); + if (result.size() == _full) { + // Make sure it is parsed as a complex map. + result.push_back(char(0)); + } + done(std::move(result)); + } + return true; +} + +void VideoPreload::cancelOnFail() { + _failed = true; + cancelAllRequests(); + done({}); +} + +bool VideoPreload::setWebFileSizeHook(int64 size) { + _failed = true; + cancelAllRequests(); + done({}); + return false; +} + +} // namespace Data diff --git a/Telegram/SourceFiles/data/data_media_preload.h b/Telegram/SourceFiles/data/data_media_preload.h new file mode 100644 index 000000000..30a47f4fa --- /dev/null +++ b/Telegram/SourceFiles/data/data_media_preload.h @@ -0,0 +1,83 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "storage/download_manager_mtproto.h" + +namespace Data { + +class PhotoMedia; +struct FileOrigin; + +class MediaPreload { +public: + explicit MediaPreload(Fn done); + virtual ~MediaPreload() = default; + +protected: + void callDone(); + +private: + Fn _done; + +}; + +class PhotoPreload final : public MediaPreload { +public: + [[nodiscard]] static bool Should( + not_null photo, + not_null context); + + PhotoPreload( + not_null data, + FileOrigin origin, + Fn done); + ~PhotoPreload(); + +private: + void start(FileOrigin origin); + + std::shared_ptr _photo; + rpl::lifetime _lifetime; + +}; + +class VideoPreload final + : public MediaPreload + , private Storage::DownloadMtprotoTask { +public: + [[nodiscard]] static bool Can(not_null video); + + VideoPreload( + not_null video, + FileOrigin origin, + Fn done); + ~VideoPreload(); + +private: + void check(); + void load(); + void done(QByteArray result); + + bool readyToRequest() const override; + int64 takeNextRequestOffset() override; + bool feedPart(int64 offset, const QByteArray &bytes) override; + void cancelOnFail() override; + bool setWebFileSizeHook(int64 size) override; + + const not_null _video; + base::flat_map _parts; + base::flat_set _requestedOffsets; + int64 _full = 0; + int _nextRequestOffset = 0; + bool _finished = false; + bool _failed = false; + +}; + +} // namespace Data diff --git a/Telegram/SourceFiles/data/data_story.cpp b/Telegram/SourceFiles/data/data_story.cpp index d9c37de4e..9adcad015 100644 --- a/Telegram/SourceFiles/data/data_story.cpp +++ b/Telegram/SourceFiles/data/data_story.cpp @@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_changes.h" #include "data/data_channel.h" #include "data/data_file_origin.h" +#include "data/data_media_preload.h" #include "data/data_photo.h" #include "data/data_photo_media.h" #include "data/data_user.h" @@ -230,107 +231,6 @@ using UpdateFlag = StoryUpdate::Flag; } // namespace -class StoryPreload::LoadTask final : private Storage::DownloadMtprotoTask { -public: - LoadTask( - FullStoryId id, - not_null document, - Fn done); - ~LoadTask(); - -private: - bool readyToRequest() const override; - int64 takeNextRequestOffset() override; - bool feedPart(int64 offset, const QByteArray &bytes) override; - void cancelOnFail() override; - bool setWebFileSizeHook(int64 size) override; - - base::flat_map _parts; - Fn _done; - base::flat_set _requestedOffsets; - int64 _full = 0; - int _nextRequestOffset = 0; - bool _finished = false; - bool _failed = false; - -}; - -StoryPreload::LoadTask::LoadTask( - FullStoryId id, - not_null document, - Fn done) -: DownloadMtprotoTask( - &document->session().downloader(), - document->videoPreloadLocation(), - id) -, _done(std::move(done)) -, _full(document->size) { - const auto prefix = document->videoPreloadPrefix(); - Assert(prefix > 0 && prefix <= document->size); - const auto part = Storage::kDownloadPartSize; - const auto parts = (prefix + part - 1) / part; - for (auto i = 0; i != parts; ++i) { - _parts.emplace(i * part, QByteArray()); - } - addToQueue(); -} - -StoryPreload::LoadTask::~LoadTask() { - if (!_finished && !_failed) { - cancelAllRequests(); - } -} - -bool StoryPreload::LoadTask::readyToRequest() const { - const auto part = Storage::kDownloadPartSize; - return !_failed && (_nextRequestOffset < _parts.size() * part); -} - -int64 StoryPreload::LoadTask::takeNextRequestOffset() { - Expects(readyToRequest()); - - _requestedOffsets.emplace(_nextRequestOffset); - _nextRequestOffset += Storage::kDownloadPartSize; - return _requestedOffsets.back(); -} - -bool StoryPreload::LoadTask::feedPart( - int64 offset, - const QByteArray &bytes) { - Expects(offset < _parts.size() * Storage::kDownloadPartSize); - Expects(_requestedOffsets.contains(int(offset))); - Expects(bytes.size() <= Storage::kDownloadPartSize); - - const auto part = Storage::kDownloadPartSize; - _requestedOffsets.remove(int(offset)); - _parts[offset] = bytes; - if ((_nextRequestOffset + part >= _parts.size() * part) - && _requestedOffsets.empty()) { - _finished = true; - removeFromQueue(); - auto result = ::Media::Streaming::SerializeComplexPartsMap(_parts); - if (result.size() == _full) { - // Make sure it is parsed as a complex map. - result.push_back(char(0)); - } - _done(result); - } - return true; -} - -void StoryPreload::LoadTask::cancelOnFail() { - _failed = true; - cancelAllRequests(); - _done({}); -} - -bool StoryPreload::LoadTask::setWebFileSizeHook(int64 size) { - _failed = true; - cancelAllRequests(); - _done({}); - return false; -} - Story::Story( StoryId id, not_null peer, @@ -999,17 +899,32 @@ PeerData *Story::fromPeer() const { } StoryPreload::StoryPreload(not_null story, Fn done) -: _story(story) -, _done(std::move(done)) { - start(); -} - -StoryPreload::~StoryPreload() { - if (_photo) { - base::take(_photo)->owner()->cancel(); +: _story(story) { + if (const auto photo = _story->photo()) { + if (PhotoPreload::Should(photo, story->peer())) { + _task = std::make_unique( + photo, + story->fullId(), + std::move(done)); + } else { + done(); + } + } else if (const auto video = _story->document()) { + if (VideoPreload::Can(video)) { + _task = std::make_unique( + video, + story->fullId(), + std::move(done)); + } else { + done(); + } + } else { + done(); } } +StoryPreload::~StoryPreload() = default; + FullStoryId StoryPreload::id() const { return _story->fullId(); } @@ -1018,76 +933,4 @@ not_null StoryPreload::story() const { return _story; } -void StoryPreload::start() { - if (const auto photo = _story->photo()) { - _photo = photo->createMediaView(); - if (_photo->loaded()) { - callDone(); - } else { - _photo->automaticLoad(_story->fullId(), _story->peer()); - photo->session().downloaderTaskFinished( - ) | rpl::filter([=] { - return _photo->loaded(); - }) | rpl::start_with_next([=] { callDone(); }, _lifetime); - } - } else if (const auto video = _story->document()) { - if (video->canBeStreamed(nullptr) && video->videoPreloadPrefix()) { - const auto key = video->bigFileBaseCacheKey(); - if (key) { - const auto weak = base::make_weak(this); - video->owner().cacheBigFile().get(key, [weak]( - const QByteArray &result) { - if (!result.isEmpty()) { - crl::on_main([weak] { - if (const auto strong = weak.get()) { - strong->callDone(); - } - }); - } else { - crl::on_main([weak] { - if (const auto strong = weak.get()) { - strong->load(); - } - }); - } - }); - } else { - callDone(); - } - } else { - callDone(); - } - } else { - callDone(); - } -} - -void StoryPreload::load() { - Expects(_story->document() != nullptr); - - const auto video = _story->document(); - const auto valid = video->videoPreloadLocation().valid(); - const auto prefix = video->videoPreloadPrefix(); - const auto key = video->bigFileBaseCacheKey(); - if (!valid || prefix <= 0 || prefix > video->size || !key) { - callDone(); - return; - } - _task = std::make_unique(id(), video, [=](QByteArray data) { - if (!data.isEmpty()) { - Assert(data.size() < Storage::kMaxFileInMemory); - _story->owner().cacheBigFile().putIfEmpty( - key, - Storage::Cache::Database::TaggedValue(std::move(data), 0)); - } - callDone(); - }); -} - -void StoryPreload::callDone() { - if (const auto onstack = _done) { - onstack(); - } -} - } // namespace Data diff --git a/Telegram/SourceFiles/data/data_story.h b/Telegram/SourceFiles/data/data_story.h index bd508591c..490f979ce 100644 --- a/Telegram/SourceFiles/data/data_story.h +++ b/Telegram/SourceFiles/data/data_story.h @@ -23,7 +23,7 @@ namespace Data { class Session; class Thread; -class PhotoMedia; +class MediaPreload; enum class StoryPrivacy : uchar { Public, @@ -301,18 +301,9 @@ public: [[nodiscard]] not_null story() const; private: - class LoadTask; - - void start(); - void load(); - void callDone(); - const not_null _story; - Fn _done; - std::shared_ptr _photo; - std::unique_ptr _task; - rpl::lifetime _lifetime; + std::unique_ptr _task; };