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