| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588 |
- /*
- 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);
- }
- void PhotoData::setFields(TimeId date, bool hasAttachedStickers) {
- _dateOrExtendedVideoDuration = date;
- _hasStickers = hasAttachedStickers;
- _extendedMediaPreview = false;
- }
- void PhotoData::setExtendedMediaPreview(
- QSize dimensions,
- const QByteArray &inlineThumbnailBytes,
- std::optional<TimeId> videoDuration) {
- _extendedMediaPreview = true;
- updateImages(
- inlineThumbnailBytes,
- {},
- {},
- { .location = { {}, dimensions.width(), dimensions.height() } },
- {},
- {},
- {});
- _dateOrExtendedVideoDuration = videoDuration ? (*videoDuration + 1) : 0;
- }
- bool PhotoData::extendedMediaPreview() const {
- return _extendedMediaPreview;
- }
- std::optional<TimeId> PhotoData::extendedMediaVideoDuration() const {
- return (_extendedMediaPreview && _dateOrExtendedVideoDuration)
- ? TimeId(_dateOrExtendedVideoDuration - 1)
- : std::optional<TimeId>();
- }
- 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);
- }
- TimeId PhotoData::date() const {
- return _extendedMediaPreview ? 0 : _dateOrExtendedVideoDuration;
- }
- 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;
- }
|