data_photo.cpp 15 KB

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