/* 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_file_origin.h" #include "data/data_reply_preview.h" #include "data/data_photo_media.h" #include "ui/image/image.h" #include "main/main_session.h" #include "mainwidget.h" #include "storage/file_download.h" #include "core/application.h" #include "facades.h" #include "app.h" namespace { constexpr auto kPhotoSideLimit = 1280; using Data::PhotoMedia; using Data::PhotoSize; using Data::PhotoSizeIndex; using Data::kPhotoSizeCount; } // namespace PhotoData::PhotoData(not_null owner, PhotoId id) : id(id) , _owner(owner) { } PhotoData::~PhotoData() { for (auto &image : _images) { base::take(image.loader).reset(); } } 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); } bool PhotoData::hasExact(PhotoSize size) const { return _images[PhotoSizeIndex(size)].location.valid(); } bool PhotoData::loading(PhotoSize size) const { return (_images[validSizeIndex(size)].loader != nullptr); } bool PhotoData::failed(PhotoSize size) const { const auto flags = _images[validSizeIndex(size)].flags; return (flags & Data::CloudFile::Flag::Failed); } const ImageLocation &PhotoData::location(PhotoSize size) const { return _images[validSizeIndex(size)].location; } int PhotoData::SideLimit() { return kPhotoSideLimit; } std::optional 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 { return _images[validSizeIndex(size)].byteSize; } bool PhotoData::displayLoading() const { const auto index = PhotoSizeIndex(PhotoSize::Large); return _images[index].loader ? (!_images[index].loader->loadingLocal() || !_images[index].loader->autoLoading()) : (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 snap(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) { if (!_replyPreview) { _replyPreview = std::make_unique(this); } return _replyPreview->image(origin); } 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 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 index = validSizeIndex(size); // Could've changed, if the requested size didn't have a location. const auto loadingSize = static_cast(index); const auto finalCheck = [=] { if (const auto active = activeMediaView()) { return !active->image(size); } return true; }; const auto done = [=](QImage result) { if (const auto active = activeMediaView()) { active->set(loadingSize, std::move(result)); } if (loadingSize == PhotoSize::Large) { _owner->photoLoadDone(this); } }; const auto fail = [=](bool started) { if (loadingSize == PhotoSize::Large) { _owner->photoLoadFail(this, started); } }; const auto progress = [=] { if (loadingSize == PhotoSize::Large) { _owner->photoLoadProgress(this); } }; Data::LoadCloudFile( &session(), _images[index], origin, fromCloud, autoLoading, Data::kImageCacheTag, finalCheck, done, fail, progress); if (size == PhotoSize::Large) { _owner->notifyPhotoLayoutChanged(this); } } std::shared_ptr PhotoData::createMediaView() { if (auto result = activeMediaView()) { return result; } auto result = std::make_shared(this); _media = result; return result; } std::shared_ptr PhotoData::activeMediaView() const { return _media.lock(); } void PhotoData::updateImages( const QByteArray &inlineThumbnailBytes, const ImageWithLocation &small, const ImageWithLocation &thumbnail, const ImageWithLocation &large) { if (!inlineThumbnailBytes.isEmpty() && _inlineThumbnailBytes.isEmpty()) { _inlineThumbnailBytes = inlineThumbnailBytes; } const auto update = [&](PhotoSize size, const ImageWithLocation &data) { Data::UpdateCloudFile( _images[PhotoSizeIndex(size)], data, owner().cache(), Data::kImageCacheTag, [=](Data::FileOrigin origin) { load(size, origin); }, [=](QImage preloaded) { if (const auto media = activeMediaView()) { media->set(size, data.preloaded); } }); }; update(PhotoSize::Small, small); update(PhotoSize::Thumbnail, thumbnail); update(PhotoSize::Large, large); } int PhotoData::width() const { return _images[PhotoSizeIndex(PhotoSize::Large)].location.width(); } int PhotoData::height() const { return _images[PhotoSizeIndex(PhotoSize::Large)].location.height(); } PhotoClickHandler::PhotoClickHandler( not_null photo, FullMsgId context, PeerData *peer) : FileClickHandler(&photo->session(), context) , _photo(photo) , _peer(peer) { } void PhotoOpenClickHandler::onClickImpl() const { Core::App().showPhoto(this); } void PhotoSaveClickHandler::onClickImpl() const { const auto data = photo(); if (!data->date) { return; } else { data->load(context()); } } void PhotoCancelClickHandler::onClickImpl() const { const auto data = photo(); if (!data->date) { return; } else if (data->uploading()) { if (const auto item = data->owner().message(context())) { if (const auto m = App::main()) { // multi good if (&m->session() == &data->session()) { m->cancelUploadLayer(item); } } } } else { data->cancel(); } }