tdesktop/Telegram/SourceFiles/data/data_photo.cpp

553 lines
14 KiB
C++

/*
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_photo.h"
#include "data/data_session.h"
#include "data/data_reply_preview.h"
#include "data/data_photo_media.h"
#include "main/main_session.h"
#include "history/history.h"
#include "history/history_item.h"
#include "media/streaming/media_streaming_loader_local.h"
#include "media/streaming/media_streaming_loader_mtproto.h"
#include "storage/file_download.h"
#include "core/application.h"
namespace {
constexpr auto kPhotoSideLimit = 2560;
using Data::PhotoMedia;
using Data::PhotoSize;
using Data::PhotoSizeIndex;
using Data::kPhotoSizeCount;
[[nodiscard]] QImage ValidatePhotoImage(
QImage image,
const Data::CloudFile &file) {
return (v::is<WebFileLocation>(file.location.file().data)
&& image.format() == QImage::Format_ARGB32)
? Images::Opaque(std::move(image))
: image;
}
} // namespace
PhotoData::PhotoData(not_null<Data::Session*> owner, PhotoId id)
: id(id)
, _owner(owner) {
}
PhotoData::~PhotoData() {
for (auto &image : _images) {
base::take(image.loader).reset();
}
base::take(_videoSizes);
}
Data::Session &PhotoData::owner() const {
return *_owner;
}
Main::Session &PhotoData::session() const {
return _owner->session();
}
void PhotoData::automaticLoadSettingsChanged() {
const auto index = PhotoSizeIndex(PhotoSize::Large);
if (!(_images[index].flags & Data::CloudFile::Flag::Cancelled)) {
return;
}
_images[index].loader = nullptr;
_images[index].flags &= ~Data::CloudFile::Flag::Cancelled;
}
void PhotoData::load(
Data::FileOrigin origin,
LoadFromCloudSetting fromCloud,
bool autoLoading) {
load(PhotoSize::Large, origin, fromCloud, autoLoading);
}
bool PhotoData::loading() const {
return loading(PhotoSize::Large);
}
int PhotoData::validSizeIndex(PhotoSize size) const {
const auto index = PhotoSizeIndex(size);
for (auto i = index; i != kPhotoSizeCount; ++i) {
if (_images[i].location.valid()) {
return i;
}
}
return PhotoSizeIndex(PhotoSize::Large);
}
int PhotoData::existingSizeIndex(PhotoSize size) const {
const auto index = PhotoSizeIndex(size);
for (auto i = index; i != kPhotoSizeCount; ++i) {
if (_images[i].location.valid() || _images[i].progressivePartSize) {
return i;
}
}
return PhotoSizeIndex(PhotoSize::Large);
}
bool PhotoData::hasExact(PhotoSize size) const {
return _images[PhotoSizeIndex(size)].location.valid();
}
bool PhotoData::loading(PhotoSize size) const {
const auto valid = validSizeIndex(size);
const auto existing = existingSizeIndex(size);
if (!_images[valid].loader) {
return false;
} else if (valid == existing) {
return true;
}
return (_images[valid].loader->loadSize()
>= _images[existing].progressivePartSize);
}
bool PhotoData::failed(PhotoSize size) const {
const auto flags = _images[validSizeIndex(size)].flags;
return (flags & Data::CloudFile::Flag::Failed);
}
void PhotoData::clearFailed(PhotoSize size) {
_images[validSizeIndex(size)].flags &= ~Data::CloudFile::Flag::Failed;
}
const ImageLocation &PhotoData::location(PhotoSize size) const {
return _images[validSizeIndex(size)].location;
}
int PhotoData::SideLimit() {
return kPhotoSideLimit;
}
std::optional<QSize> PhotoData::size(PhotoSize size) const {
const auto &provided = location(size);
const auto result = QSize{ provided.width(), provided.height() };
const auto limit = SideLimit();
if (result.isEmpty()) {
return std::nullopt;
} else if (result.width() <= limit && result.height() <= limit) {
return result;
}
const auto scaled = result.scaled(limit, limit, Qt::KeepAspectRatio);
return QSize(std::max(scaled.width(), 1), std::max(scaled.height(), 1));
}
int PhotoData::imageByteSize(PhotoSize size) const {
const auto existing = existingSizeIndex(size);
if (const auto result = _images[existing].progressivePartSize) {
return result;
}
return _images[validSizeIndex(size)].byteSize;
}
bool PhotoData::displayLoading() const {
const auto index = PhotoSizeIndex(PhotoSize::Large);
if (const auto loader = _images[index].loader.get()) {
return !loader->finished()
&& (!loader->loadingLocal() || !loader->autoLoading());
}
return (uploading() && !waitingForAlbum());
}
void PhotoData::cancel() {
if (loading()) {
_images[PhotoSizeIndex(PhotoSize::Large)].loader->cancel();
}
}
float64 PhotoData::progress() const {
if (uploading()) {
if (uploadingData->size > 0) {
const auto result = float64(uploadingData->offset)
/ uploadingData->size;
return std::clamp(result, 0., 1.);
}
return 0.;
}
const auto index = PhotoSizeIndex(PhotoSize::Large);
return loading() ? _images[index].loader->currentProgress() : 0.;
}
bool PhotoData::cancelled() const {
const auto index = PhotoSizeIndex(PhotoSize::Large);
return (_images[index].flags & Data::CloudFile::Flag::Cancelled);
}
void PhotoData::setWaitingForAlbum() {
if (uploading()) {
uploadingData->waitingForAlbum = true;
}
}
bool PhotoData::waitingForAlbum() const {
return uploading() && uploadingData->waitingForAlbum;
}
int32 PhotoData::loadOffset() const {
const auto index = PhotoSizeIndex(PhotoSize::Large);
return loading() ? _images[index].loader->currentOffset() : 0;
}
bool PhotoData::uploading() const {
return (uploadingData != nullptr);
}
Image *PhotoData::getReplyPreview(
Data::FileOrigin origin,
not_null<PeerData*> context,
bool spoiler) {
if (!_replyPreview) {
_replyPreview = std::make_unique<Data::ReplyPreview>(this);
}
return _replyPreview->image(origin, context, spoiler);
}
Image *PhotoData::getReplyPreview(not_null<HistoryItem*> item) {
const auto media = item->media();
const auto spoiler = media && media->hasSpoiler();
return getReplyPreview(item->fullId(), item->history()->peer, spoiler);
}
bool PhotoData::replyPreviewLoaded(bool spoiler) const {
if (!_replyPreview) {
return false;
}
return _replyPreview->loaded(spoiler);
}
void PhotoData::setRemoteLocation(
int32 dc,
uint64 access,
const QByteArray &fileReference) {
_fileReference = fileReference;
if (_dc != dc || _access != access) {
_dc = dc;
_access = access;
}
}
MTPInputPhoto PhotoData::mtpInput() const {
return MTP_inputPhoto(
MTP_long(id),
MTP_long(_access),
MTP_bytes(_fileReference));
}
QByteArray PhotoData::fileReference() const {
return _fileReference;
}
void PhotoData::refreshFileReference(const QByteArray &value) {
_fileReference = value;
for (auto &image : _images) {
image.location.refreshFileReference(value);
}
}
void PhotoData::collectLocalData(not_null<PhotoData*> local) {
if (local == this) {
return;
}
for (auto i = 0; i != kPhotoSizeCount; ++i) {
if (const auto from = local->_images[i].location.file().cacheKey()) {
if (const auto to = _images[i].location.file().cacheKey()) {
_owner->cache().copyIfEmpty(from, to);
}
}
}
if (const auto localMedia = local->activeMediaView()) {
auto media = createMediaView();
media->collectLocalData(localMedia.get());
_owner->keepAlive(std::move(media));
}
}
bool PhotoData::isNull() const {
return !_images[PhotoSizeIndex(PhotoSize::Large)].location.valid();
}
void PhotoData::load(
PhotoSize size,
Data::FileOrigin origin,
LoadFromCloudSetting fromCloud,
bool autoLoading) {
const auto valid = validSizeIndex(size);
const auto existing = existingSizeIndex(size);
// Could've changed, if the requested size didn't have a location.
const auto validSize = static_cast<PhotoSize>(valid);
const auto finalCheck = [=] {
if (const auto active = activeMediaView()) {
return !active->image(size);
}
return true;
};
const auto done = [=](QImage result, QByteArray bytes) {
Expects(_images[valid].loader != nullptr);
// Find out what progressive photo size have we loaded exactly.
auto goodFor = validSize;
const auto loadSize = _images[valid].loader->loadSize();
if (valid > 0 && _images[valid].byteSize > loadSize) {
for (auto i = valid; i != 0;) {
--i;
const auto required = _images[i].progressivePartSize;
if (required > 0 && required <= loadSize) {
goodFor = static_cast<PhotoSize>(i);
break;
}
}
}
if (const auto active = activeMediaView()) {
active->set(
validSize,
goodFor,
ValidatePhotoImage(std::move(result), _images[valid]),
std::move(bytes));
}
if (validSize == PhotoSize::Large && goodFor == validSize) {
_owner->photoLoadDone(this);
}
};
const auto fail = [=](bool started) {
if (validSize == PhotoSize::Large) {
_owner->photoLoadFail(this, started);
}
};
const auto progress = [=] {
if (validSize == PhotoSize::Large) {
_owner->photoLoadProgress(this);
}
};
Data::LoadCloudFile(
&session(),
_images[valid],
origin,
fromCloud,
autoLoading,
Data::kImageCacheTag,
finalCheck,
done,
fail,
progress,
_images[existing].progressivePartSize);
if (size == PhotoSize::Large) {
_owner->notifyPhotoLayoutChanged(this);
}
}
std::shared_ptr<PhotoMedia> PhotoData::createMediaView() {
if (auto result = activeMediaView()) {
return result;
}
auto result = std::make_shared<PhotoMedia>(this);
_media = result;
return result;
}
std::shared_ptr<PhotoMedia> PhotoData::activeMediaView() const {
return _media.lock();
}
void PhotoData::updateImages(
const QByteArray &inlineThumbnailBytes,
const ImageWithLocation &small,
const ImageWithLocation &thumbnail,
const ImageWithLocation &large,
const ImageWithLocation &videoSmall,
const ImageWithLocation &videoLarge,
crl::time videoStartTime) {
if (!inlineThumbnailBytes.isEmpty()
&& _inlineThumbnailBytes.isEmpty()) {
_inlineThumbnailBytes = inlineThumbnailBytes;
}
const auto update = [&](PhotoSize size, const ImageWithLocation &data) {
const auto index = PhotoSizeIndex(size);
Data::UpdateCloudFile(
_images[index],
data,
owner().cache(),
Data::kImageCacheTag,
[=](Data::FileOrigin origin) { load(size, origin); },
[=](QImage preloaded, QByteArray bytes) {
if (const auto media = activeMediaView()) {
media->set(
size,
size,
ValidatePhotoImage(
std::move(preloaded),
_images[index]),
std::move(bytes));
}
});
};
update(PhotoSize::Small, small);
update(PhotoSize::Thumbnail, thumbnail);
update(PhotoSize::Large, large);
if (!videoLarge.location.valid()) {
_videoSizes = nullptr;
} else {
if (!_videoSizes) {
_videoSizes = std::make_unique<VideoSizes>();
}
_videoSizes->startTime = videoStartTime;
constexpr auto large = PhotoSize::Large;
constexpr auto small = PhotoSize::Small;
Data::UpdateCloudFile(
_videoSizes->large,
videoLarge,
owner().cache(),
Data::kAnimationCacheTag,
[&](Data::FileOrigin origin) { loadVideo(large, origin); });
Data::UpdateCloudFile(
_videoSizes->small,
videoSmall,
owner().cache(),
Data::kAnimationCacheTag,
[&](Data::FileOrigin origin) { loadVideo(small, origin); });
}
}
[[nodiscard]] bool PhotoData::hasAttachedStickers() const {
return _hasStickers;
}
void PhotoData::setHasAttachedStickers(bool value) {
_hasStickers = value;
}
int PhotoData::width() const {
return _images[PhotoSizeIndex(PhotoSize::Large)].location.width();
}
int PhotoData::height() const {
return _images[PhotoSizeIndex(PhotoSize::Large)].location.height();
}
Data::CloudFile &PhotoData::videoFile(PhotoSize size) {
Expects(_videoSizes != nullptr);
return (size == PhotoSize::Small && hasVideoSmall())
? _videoSizes->small
: _videoSizes->large;
}
const Data::CloudFile &PhotoData::videoFile(PhotoSize size) const {
Expects(_videoSizes != nullptr);
return (size == PhotoSize::Small && hasVideoSmall())
? _videoSizes->small
: _videoSizes->large;
}
bool PhotoData::hasVideo() const {
return _videoSizes != nullptr;
}
bool PhotoData::hasVideoSmall() const {
return hasVideo() && _videoSizes->small.location.valid();
}
bool PhotoData::videoLoading(Data::PhotoSize size) const {
return _videoSizes && videoFile(size).loader != nullptr;
}
bool PhotoData::videoFailed(Data::PhotoSize size) const {
return _videoSizes
&& (videoFile(size).flags & Data::CloudFile::Flag::Failed);
}
void PhotoData::loadVideo(Data::PhotoSize size, Data::FileOrigin origin) {
if (!_videoSizes) {
return;
}
const auto autoLoading = false;
const auto finalCheck = [=] {
if (const auto active = activeMediaView()) {
return active->videoContent(size).isEmpty();
}
return true;
};
const auto done = [=](QByteArray result) {
if (const auto active = activeMediaView()) {
active->setVideo(size, std::move(result));
}
};
Data::LoadCloudFile(
&session(),
videoFile(size),
origin,
LoadFromCloudOrLocal,
autoLoading,
Data::kAnimationCacheTag,
finalCheck,
done);
}
const ImageLocation &PhotoData::videoLocation(Data::PhotoSize size) const {
static const auto empty = ImageLocation();
return _videoSizes ? videoFile(size).location : empty;
}
int PhotoData::videoByteSize(Data::PhotoSize size) const {
return _videoSizes ? videoFile(size).byteSize : 0;
}
crl::time PhotoData::videoStartPosition() const {
return _videoSizes ? _videoSizes->startTime : crl::time(0);
}
void PhotoData::setVideoPlaybackFailed() {
if (_videoSizes) {
_videoSizes->playbackFailed = true;
}
}
bool PhotoData::videoPlaybackFailed() const {
return _videoSizes && _videoSizes->playbackFailed;
}
bool PhotoData::videoCanBePlayed() const {
return hasVideo() && !videoPlaybackFailed();
}
auto PhotoData::createStreamingLoader(
Data::FileOrigin origin,
bool forceRemoteLoader) const
-> std::unique_ptr<Media::Streaming::Loader> {
if (!hasVideo()) {
return nullptr;
}
constexpr auto large = PhotoSize::Large;
if (!forceRemoteLoader) {
const auto media = activeMediaView();
const auto bytes = media ? media->videoContent(large) : QByteArray();
if (media && !bytes.isEmpty()) {
return Media::Streaming::MakeBytesLoader(bytes);
}
}
return v::is<StorageFileLocation>(videoLocation(large).file().data)
? std::make_unique<Media::Streaming::LoaderMtproto>(
&session().downloader(),
v::get<StorageFileLocation>(videoLocation(large).file().data),
videoByteSize(large),
origin)
: nullptr;
}