Extract Data::MediaPreload.

This commit is contained in:
John Preston 2024-10-02 11:20:27 +04:00
parent 20f53c89ad
commit 677314dacb
5 changed files with 321 additions and 192 deletions

View file

@ -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

View file

@ -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<DocumentData*> video) {
const auto result = video->videoPreloadPrefix();
return result
? result
: std::min(int64(kDefaultPreloadPrefix), video->size);
}
} // namespace
MediaPreload::MediaPreload(Fn<void()> done)
: _done(std::move(done)) {
}
void MediaPreload::callDone() {
if (const auto onstack = _done) {
onstack();
}
}
PhotoPreload::PhotoPreload(
not_null<PhotoData*> photo,
FileOrigin origin,
Fn<void()> done)
: MediaPreload(std::move(done))
, _photo(photo->createMediaView()) {
start(origin);
}
PhotoPreload::~PhotoPreload() {
if (_photo) {
base::take(_photo)->owner()->cancel();
}
}
bool PhotoPreload::Should(
not_null<PhotoData*> photo,
not_null<PeerData*> 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<DocumentData*> video,
FileOrigin origin,
Fn<void()> 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<has_weak_ptr*>(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<VideoPreload*>(strong)->callDone();
}
});
} else {
crl::on_main([weak] {
if (const auto strong = weak.get()) {
static_cast<VideoPreload*>(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<DocumentData*> 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

View file

@ -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<void()> done);
virtual ~MediaPreload() = default;
protected:
void callDone();
private:
Fn<void()> _done;
};
class PhotoPreload final : public MediaPreload {
public:
[[nodiscard]] static bool Should(
not_null<PhotoData*> photo,
not_null<PeerData*> context);
PhotoPreload(
not_null<PhotoData*> data,
FileOrigin origin,
Fn<void()> done);
~PhotoPreload();
private:
void start(FileOrigin origin);
std::shared_ptr<PhotoMedia> _photo;
rpl::lifetime _lifetime;
};
class VideoPreload final
: public MediaPreload
, private Storage::DownloadMtprotoTask {
public:
[[nodiscard]] static bool Can(not_null<DocumentData*> video);
VideoPreload(
not_null<DocumentData*> video,
FileOrigin origin,
Fn<void()> 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<DocumentData*> _video;
base::flat_map<uint32, QByteArray> _parts;
base::flat_set<int> _requestedOffsets;
int64 _full = 0;
int _nextRequestOffset = 0;
bool _finished = false;
bool _failed = false;
};
} // namespace Data

View file

@ -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<DocumentData*> document,
Fn<void(QByteArray)> 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<uint32, QByteArray> _parts;
Fn<void(QByteArray)> _done;
base::flat_set<int> _requestedOffsets;
int64 _full = 0;
int _nextRequestOffset = 0;
bool _finished = false;
bool _failed = false;
};
StoryPreload::LoadTask::LoadTask(
FullStoryId id,
not_null<DocumentData*> document,
Fn<void(QByteArray)> 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<PeerData*> peer,
@ -999,17 +899,32 @@ PeerData *Story::fromPeer() const {
}
StoryPreload::StoryPreload(not_null<Story*> story, Fn<void()> 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<PhotoPreload>(
photo,
story->fullId(),
std::move(done));
} else {
done();
}
} else if (const auto video = _story->document()) {
if (VideoPreload::Can(video)) {
_task = std::make_unique<VideoPreload>(
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<Story*> 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<LoadTask>(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

View file

@ -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*> story() const;
private:
class LoadTask;
void start();
void load();
void callDone();
const not_null<Story*> _story;
Fn<void()> _done;
std::shared_ptr<Data::PhotoMedia> _photo;
std::unique_ptr<LoadTask> _task;
rpl::lifetime _lifetime;
std::unique_ptr<MediaPreload> _task;
};