/* 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 "ui/image/image_source.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()) { return; } const auto index = PhotoSizeIndex(PhotoSize::Large); _images[index].flags |= Data::CloudFile::Flag::Cancelled; destroyLoader(PhotoSize::Large); _owner->photoLoadDone(this); } 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); } void PhotoData::unload() { _replyPreview = 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); auto &image = _images[index]; if (image.loader) { if (fromCloud == LoadFromCloudOrLocal) { image.loader->permitLoadFromCloud(); } return; } else if ((image.flags & Data::CloudFile::Flag::Failed) || !image.location.valid()) { return; } else if (const auto active = activeMediaView()) { if (active->image(size)) { return; } } // Could've changed, if the requested size didn't have a location. size = static_cast(index); image.flags &= ~Data::CloudFile::Flag::Cancelled; image.loader = CreateFileLoader( image.location.file(), origin, QString(), image.byteSize, UnknownFileLocation, LoadToCacheAsWell, fromCloud, autoLoading, Data::kImageCacheTag); image.loader->updates( ) | rpl::start_with_next_error_done([=] { if (size == PhotoSize::Large) { _owner->photoLoadProgress(this); } }, [=, &image](bool started) { finishLoad(size); image.flags |= Data::CloudFile::Flag::Failed; if (size == PhotoSize::Large) { _owner->photoLoadFail(this, started); } }, [=] { finishLoad(size); if (size == PhotoSize::Large) { _owner->photoLoadDone(this); } }, image.loader->lifetime()); image.loader->start(); if (size == PhotoSize::Large) { _owner->notifyPhotoLayoutChanged(this); } } void PhotoData::finishLoad(PhotoSize size) { const auto index = PhotoSizeIndex(size); auto &image = _images[index]; // NB! image.loader may be in ~FileLoader() already. const auto guard = gsl::finally([&] { destroyLoader(size); }); if (!image.loader || image.loader->cancelled()) { image.flags |= Data::CloudFile::Flag::Cancelled; return; } else if (auto read = image.loader->imageData(); read.isNull()) { image.flags |= Data::CloudFile::Flag::Failed; } else if (const auto active = activeMediaView()) { active->set(size, std::move(read)); } } void PhotoData::destroyLoader(PhotoSize size) { const auto index = PhotoSizeIndex(size); auto &image = _images[index]; // NB! image.loader may be in ~FileLoader() already. if (!image.loader) { return; } const auto loader = base::take(image.loader); if (image.flags & Data::CloudFile::Flag::Cancelled) { loader->cancel(); } } 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::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(context) , _session(&photo->session()) , _photo(photo) , _peer(peer) { } void PhotoOpenClickHandler::onClickImpl() const { if (valid()) { Core::App().showPhoto(this); } } void PhotoSaveClickHandler::onClickImpl() const { if (!valid()) { return; } const auto data = photo(); if (!data->date) { return; } else { data->load(context()); } } void PhotoCancelClickHandler::onClickImpl() const { if (!valid()) { return; } const auto data = photo(); if (!data->date) { return; } else if (data->uploading()) { if (const auto item = data->owner().message(context())) { App::main()->cancelUploadLayer(item); } } else { data->cancel(); } }