1 /*
2 This file is part of Telegram Desktop,
3 the official desktop application for the Telegram messaging service.
4
5 For license and copyright information please follow this link:
6 https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
7 */
8 #include "data/data_photo.h"
9
10 #include "data/data_session.h"
11 #include "data/data_file_origin.h"
12 #include "data/data_reply_preview.h"
13 #include "data/data_photo_media.h"
14 #include "ui/image/image.h"
15 #include "main/main_session.h"
16 #include "media/streaming/media_streaming_loader_local.h"
17 #include "media/streaming/media_streaming_loader_mtproto.h"
18 #include "mainwidget.h"
19 #include "storage/file_download.h"
20 #include "core/application.h"
21 #include "facades.h"
22
23 namespace {
24
25 constexpr auto kPhotoSideLimit = 1280;
26
27 using Data::PhotoMedia;
28 using Data::PhotoSize;
29 using Data::PhotoSizeIndex;
30 using Data::kPhotoSizeCount;
31
ValidatePhotoImage(QImage image,const Data::CloudFile & file)32 [[nodiscard]] QImage ValidatePhotoImage(
33 QImage image,
34 const Data::CloudFile &file) {
35 return (v::is<WebFileLocation>(file.location.file().data)
36 && image.format() == QImage::Format_ARGB32)
37 ? Images::prepareOpaque(std::move(image))
38 : image;
39 }
40
41 } // namespace
42
PhotoData(not_null<Data::Session * > owner,PhotoId id)43 PhotoData::PhotoData(not_null<Data::Session*> owner, PhotoId id)
44 : id(id)
45 , _owner(owner) {
46 }
47
~PhotoData()48 PhotoData::~PhotoData() {
49 for (auto &image : _images) {
50 base::take(image.loader).reset();
51 }
52 base::take(_video.loader).reset();
53 }
54
owner() const55 Data::Session &PhotoData::owner() const {
56 return *_owner;
57 }
58
session() const59 Main::Session &PhotoData::session() const {
60 return _owner->session();
61 }
62
automaticLoadSettingsChanged()63 void PhotoData::automaticLoadSettingsChanged() {
64 const auto index = PhotoSizeIndex(PhotoSize::Large);
65 if (!(_images[index].flags & Data::CloudFile::Flag::Cancelled)) {
66 return;
67 }
68 _images[index].loader = nullptr;
69 _images[index].flags &= ~Data::CloudFile::Flag::Cancelled;
70 }
71
load(Data::FileOrigin origin,LoadFromCloudSetting fromCloud,bool autoLoading)72 void PhotoData::load(
73 Data::FileOrigin origin,
74 LoadFromCloudSetting fromCloud,
75 bool autoLoading) {
76 load(PhotoSize::Large, origin, fromCloud, autoLoading);
77 }
78
loading() const79 bool PhotoData::loading() const {
80 return loading(PhotoSize::Large);
81 }
82
validSizeIndex(PhotoSize size) const83 int PhotoData::validSizeIndex(PhotoSize size) const {
84 const auto index = PhotoSizeIndex(size);
85 for (auto i = index; i != kPhotoSizeCount; ++i) {
86 if (_images[i].location.valid()) {
87 return i;
88 }
89 }
90 return PhotoSizeIndex(PhotoSize::Large);
91 }
92
existingSizeIndex(PhotoSize size) const93 int PhotoData::existingSizeIndex(PhotoSize size) const {
94 const auto index = PhotoSizeIndex(size);
95 for (auto i = index; i != kPhotoSizeCount; ++i) {
96 if (_images[i].location.valid() || _images[i].progressivePartSize) {
97 return i;
98 }
99 }
100 return PhotoSizeIndex(PhotoSize::Large);
101 }
102
hasExact(PhotoSize size) const103 bool PhotoData::hasExact(PhotoSize size) const {
104 return _images[PhotoSizeIndex(size)].location.valid();
105 }
106
loading(PhotoSize size) const107 bool PhotoData::loading(PhotoSize size) const {
108 const auto valid = validSizeIndex(size);
109 const auto existing = existingSizeIndex(size);
110 if (!_images[valid].loader) {
111 return false;
112 } else if (valid == existing) {
113 return true;
114 }
115 return (_images[valid].loader->loadSize()
116 >= _images[existing].progressivePartSize);
117 }
118
failed(PhotoSize size) const119 bool PhotoData::failed(PhotoSize size) const {
120 const auto flags = _images[validSizeIndex(size)].flags;
121 return (flags & Data::CloudFile::Flag::Failed);
122 }
123
clearFailed(PhotoSize size)124 void PhotoData::clearFailed(PhotoSize size) {
125 _images[validSizeIndex(size)].flags &= ~Data::CloudFile::Flag::Failed;
126 }
127
location(PhotoSize size) const128 const ImageLocation &PhotoData::location(PhotoSize size) const {
129 return _images[validSizeIndex(size)].location;
130 }
131
SideLimit()132 int PhotoData::SideLimit() {
133 return kPhotoSideLimit;
134 }
135
size(PhotoSize size) const136 std::optional<QSize> PhotoData::size(PhotoSize size) const {
137 const auto &provided = location(size);
138 const auto result = QSize{ provided.width(), provided.height() };
139 const auto limit = SideLimit();
140 if (result.isEmpty()) {
141 return std::nullopt;
142 } else if (result.width() <= limit && result.height() <= limit) {
143 return result;
144 }
145 const auto scaled = result.scaled(limit, limit, Qt::KeepAspectRatio);
146 return QSize(std::max(scaled.width(), 1), std::max(scaled.height(), 1));
147 }
148
imageByteSize(PhotoSize size) const149 int PhotoData::imageByteSize(PhotoSize size) const {
150 const auto existing = existingSizeIndex(size);
151 if (const auto result = _images[existing].progressivePartSize) {
152 return result;
153 }
154 return _images[validSizeIndex(size)].byteSize;
155 }
156
displayLoading() const157 bool PhotoData::displayLoading() const {
158 const auto index = PhotoSizeIndex(PhotoSize::Large);
159 if (const auto loader = _images[index].loader.get()) {
160 return !loader->finished()
161 && (!loader->loadingLocal() || !loader->autoLoading());
162 }
163 return (uploading() && !waitingForAlbum());
164 }
165
cancel()166 void PhotoData::cancel() {
167 if (loading()) {
168 _images[PhotoSizeIndex(PhotoSize::Large)].loader->cancel();
169 }
170 }
171
progress() const172 float64 PhotoData::progress() const {
173 if (uploading()) {
174 if (uploadingData->size > 0) {
175 const auto result = float64(uploadingData->offset)
176 / uploadingData->size;
177 return std::clamp(result, 0., 1.);
178 }
179 return 0.;
180 }
181 const auto index = PhotoSizeIndex(PhotoSize::Large);
182 return loading() ? _images[index].loader->currentProgress() : 0.;
183 }
184
cancelled() const185 bool PhotoData::cancelled() const {
186 const auto index = PhotoSizeIndex(PhotoSize::Large);
187 return (_images[index].flags & Data::CloudFile::Flag::Cancelled);
188 }
189
setWaitingForAlbum()190 void PhotoData::setWaitingForAlbum() {
191 if (uploading()) {
192 uploadingData->waitingForAlbum = true;
193 }
194 }
195
waitingForAlbum() const196 bool PhotoData::waitingForAlbum() const {
197 return uploading() && uploadingData->waitingForAlbum;
198 }
199
loadOffset() const200 int32 PhotoData::loadOffset() const {
201 const auto index = PhotoSizeIndex(PhotoSize::Large);
202 return loading() ? _images[index].loader->currentOffset() : 0;
203 }
204
uploading() const205 bool PhotoData::uploading() const {
206 return (uploadingData != nullptr);
207 }
208
getReplyPreview(Data::FileOrigin origin)209 Image *PhotoData::getReplyPreview(Data::FileOrigin origin) {
210 if (!_replyPreview) {
211 _replyPreview = std::make_unique<Data::ReplyPreview>(this);
212 }
213 return _replyPreview->image(origin);
214 }
215
replyPreviewLoaded() const216 bool PhotoData::replyPreviewLoaded() const {
217 if (!_replyPreview) {
218 return false;
219 }
220 return _replyPreview->loaded();
221 }
222
setRemoteLocation(int32 dc,uint64 access,const QByteArray & fileReference)223 void PhotoData::setRemoteLocation(
224 int32 dc,
225 uint64 access,
226 const QByteArray &fileReference) {
227 _fileReference = fileReference;
228 if (_dc != dc || _access != access) {
229 _dc = dc;
230 _access = access;
231 }
232 }
233
mtpInput() const234 MTPInputPhoto PhotoData::mtpInput() const {
235 return MTP_inputPhoto(
236 MTP_long(id),
237 MTP_long(_access),
238 MTP_bytes(_fileReference));
239 }
240
fileReference() const241 QByteArray PhotoData::fileReference() const {
242 return _fileReference;
243 }
244
refreshFileReference(const QByteArray & value)245 void PhotoData::refreshFileReference(const QByteArray &value) {
246 _fileReference = value;
247 for (auto &image : _images) {
248 image.location.refreshFileReference(value);
249 }
250 }
251
collectLocalData(not_null<PhotoData * > local)252 void PhotoData::collectLocalData(not_null<PhotoData*> local) {
253 if (local == this) {
254 return;
255 }
256
257 for (auto i = 0; i != kPhotoSizeCount; ++i) {
258 if (const auto from = local->_images[i].location.file().cacheKey()) {
259 if (const auto to = _images[i].location.file().cacheKey()) {
260 _owner->cache().copyIfEmpty(from, to);
261 }
262 }
263 }
264 if (const auto localMedia = local->activeMediaView()) {
265 auto media = createMediaView();
266 media->collectLocalData(localMedia.get());
267 _owner->keepAlive(std::move(media));
268 }
269 }
270
isNull() const271 bool PhotoData::isNull() const {
272 return !_images[PhotoSizeIndex(PhotoSize::Large)].location.valid();
273 }
274
load(PhotoSize size,Data::FileOrigin origin,LoadFromCloudSetting fromCloud,bool autoLoading)275 void PhotoData::load(
276 PhotoSize size,
277 Data::FileOrigin origin,
278 LoadFromCloudSetting fromCloud,
279 bool autoLoading) {
280 const auto valid = validSizeIndex(size);
281 const auto existing = existingSizeIndex(size);
282
283 // Could've changed, if the requested size didn't have a location.
284 const auto validSize = static_cast<PhotoSize>(valid);
285 const auto finalCheck = [=] {
286 if (const auto active = activeMediaView()) {
287 return !active->image(size);
288 }
289 return true;
290 };
291 const auto done = [=](QImage result) {
292 Expects(_images[valid].loader != nullptr);
293
294 // Find out what progressive photo size have we loaded exactly.
295 auto goodFor = validSize;
296 const auto loadSize = _images[valid].loader->loadSize();
297 if (valid > 0 && _images[valid].byteSize > loadSize) {
298 for (auto i = valid; i != 0;) {
299 --i;
300 const auto required = _images[i].progressivePartSize;
301 if (required > 0 && required <= loadSize) {
302 goodFor = static_cast<PhotoSize>(i);
303 break;
304 }
305 }
306 }
307 if (const auto active = activeMediaView()) {
308 active->set(
309 validSize,
310 goodFor,
311 ValidatePhotoImage(std::move(result), _images[valid]));
312 }
313 if (validSize == PhotoSize::Large && goodFor == validSize) {
314 _owner->photoLoadDone(this);
315 }
316 };
317 const auto fail = [=](bool started) {
318 if (validSize == PhotoSize::Large) {
319 _owner->photoLoadFail(this, started);
320 }
321 };
322 const auto progress = [=] {
323 if (validSize == PhotoSize::Large) {
324 _owner->photoLoadProgress(this);
325 }
326 };
327 Data::LoadCloudFile(
328 &session(),
329 _images[valid],
330 origin,
331 fromCloud,
332 autoLoading,
333 Data::kImageCacheTag,
334 finalCheck,
335 done,
336 fail,
337 progress,
338 _images[existing].progressivePartSize);
339
340 if (size == PhotoSize::Large) {
341 _owner->notifyPhotoLayoutChanged(this);
342 }
343 }
344
createMediaView()345 std::shared_ptr<PhotoMedia> PhotoData::createMediaView() {
346 if (auto result = activeMediaView()) {
347 return result;
348 }
349 auto result = std::make_shared<PhotoMedia>(this);
350 _media = result;
351 return result;
352 }
353
activeMediaView() const354 std::shared_ptr<PhotoMedia> PhotoData::activeMediaView() const {
355 return _media.lock();
356 }
357
updateImages(const QByteArray & inlineThumbnailBytes,const ImageWithLocation & small,const ImageWithLocation & thumbnail,const ImageWithLocation & large,const ImageWithLocation & video,crl::time videoStartTime)358 void PhotoData::updateImages(
359 const QByteArray &inlineThumbnailBytes,
360 const ImageWithLocation &small,
361 const ImageWithLocation &thumbnail,
362 const ImageWithLocation &large,
363 const ImageWithLocation &video,
364 crl::time videoStartTime) {
365 if (!inlineThumbnailBytes.isEmpty()
366 && _inlineThumbnailBytes.isEmpty()) {
367 _inlineThumbnailBytes = inlineThumbnailBytes;
368 }
369 const auto update = [&](PhotoSize size, const ImageWithLocation &data) {
370 const auto index = PhotoSizeIndex(size);
371 Data::UpdateCloudFile(
372 _images[index],
373 data,
374 owner().cache(),
375 Data::kImageCacheTag,
376 [=](Data::FileOrigin origin) { load(size, origin); },
377 [=](QImage preloaded) {
378 if (const auto media = activeMediaView()) {
379 media->set(
380 size,
381 size,
382 ValidatePhotoImage(
383 std::move(preloaded),
384 _images[index]));
385 }
386 });
387 };
388 update(PhotoSize::Small, small);
389 update(PhotoSize::Thumbnail, thumbnail);
390 update(PhotoSize::Large, large);
391
392 if (video.location.valid()) {
393 _videoStartTime = videoStartTime;
394 }
395 Data::UpdateCloudFile(
396 _video,
397 video,
398 owner().cache(),
399 Data::kAnimationCacheTag,
400 [&](Data::FileOrigin origin) { loadVideo(origin); });
401 }
402
hasAttachedStickers() const403 [[nodiscard]] bool PhotoData::hasAttachedStickers() const {
404 return _hasStickers;
405 }
406
setHasAttachedStickers(bool value)407 void PhotoData::setHasAttachedStickers(bool value) {
408 _hasStickers = value;
409 }
410
width() const411 int PhotoData::width() const {
412 return _images[PhotoSizeIndex(PhotoSize::Large)].location.width();
413 }
414
height() const415 int PhotoData::height() const {
416 return _images[PhotoSizeIndex(PhotoSize::Large)].location.height();
417 }
418
hasVideo() const419 bool PhotoData::hasVideo() const {
420 return _video.location.valid();
421 }
422
videoLoading() const423 bool PhotoData::videoLoading() const {
424 return _video.loader != nullptr;
425 }
426
videoFailed() const427 bool PhotoData::videoFailed() const {
428 return (_video.flags & Data::CloudFile::Flag::Failed);
429 }
430
loadVideo(Data::FileOrigin origin)431 void PhotoData::loadVideo(Data::FileOrigin origin) {
432 const auto autoLoading = false;
433 const auto finalCheck = [=] {
434 if (const auto active = activeMediaView()) {
435 return active->videoContent().isEmpty();
436 }
437 return true;
438 };
439 const auto done = [=](QByteArray result) {
440 if (const auto active = activeMediaView()) {
441 active->setVideo(std::move(result));
442 }
443 };
444 Data::LoadCloudFile(
445 &session(),
446 _video,
447 origin,
448 LoadFromCloudOrLocal,
449 autoLoading,
450 Data::kAnimationCacheTag,
451 finalCheck,
452 done);
453 }
454
videoLocation() const455 const ImageLocation &PhotoData::videoLocation() const {
456 return _video.location;
457 }
458
videoByteSize() const459 int PhotoData::videoByteSize() const {
460 return _video.byteSize;
461 }
462
videoCanBePlayed() const463 bool PhotoData::videoCanBePlayed() const {
464 return hasVideo() && !videoPlaybackFailed();
465 }
466
createStreamingLoader(Data::FileOrigin origin,bool forceRemoteLoader) const467 auto PhotoData::createStreamingLoader(
468 Data::FileOrigin origin,
469 bool forceRemoteLoader) const
470 -> std::unique_ptr<Media::Streaming::Loader> {
471 if (!hasVideo()) {
472 return nullptr;
473 }
474 if (!forceRemoteLoader) {
475 const auto media = activeMediaView();
476 if (media && !media->videoContent().isEmpty()) {
477 return Media::Streaming::MakeBytesLoader(media->videoContent());
478 }
479 }
480 return v::is<StorageFileLocation>(videoLocation().file().data)
481 ? std::make_unique<Media::Streaming::LoaderMtproto>(
482 &session().downloader(),
483 v::get<StorageFileLocation>(videoLocation().file().data),
484 videoByteSize(),
485 origin)
486 : nullptr;
487 }
488