data_shared_media.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526
  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_shared_media.h"
  8. #include <rpl/combine.h>
  9. #include "main/main_session.h"
  10. #include "apiwrap.h"
  11. #include "storage/storage_facade.h"
  12. #include "history/history.h"
  13. #include "history/history_item.h"
  14. #include "data/components/scheduled_messages.h"
  15. #include "data/data_document.h"
  16. #include "data/data_media_types.h"
  17. #include "data/data_photo.h"
  18. #include "data/data_session.h"
  19. #include "core/crash_reports.h"
  20. namespace {
  21. using Type = Storage::SharedMediaType;
  22. bool IsItemGoodForType(const not_null<HistoryItem*> item, Type type) {
  23. const auto media = item->media();
  24. if (!media || media->webpage()) {
  25. return false;
  26. }
  27. const auto photo = media->photo();
  28. const auto photoType = (type == Type::Photo);
  29. const auto photoVideoType = (type == Type::PhotoVideo);
  30. if ((photoType || photoVideoType) && photo) {
  31. return true;
  32. }
  33. const auto document = media->document();
  34. if (!document) {
  35. return false;
  36. }
  37. const auto voiceType = (type == Type::VoiceFile);
  38. const auto voiceDoc = document->isVoiceMessage();
  39. const auto roundType = (type == Type::RoundFile);
  40. const auto roundDoc = document->isVideoMessage();
  41. const auto audioType = (type == Type::MusicFile);
  42. const auto audioDoc = document->isAudioFile();
  43. const auto gifType = (type == Type::GIF);
  44. const auto gifDoc = document->isGifv();
  45. const auto videoType = (type == Type::Video);
  46. const auto videoDoc = document->isVideoFile();
  47. const auto voiceRoundType = (type == Type::RoundVoiceFile);
  48. const auto fileType = (type == Type::File);
  49. return (audioType && audioDoc)
  50. || (voiceType && voiceDoc)
  51. || (roundType && roundDoc)
  52. || (voiceRoundType && (roundDoc || voiceDoc))
  53. || (gifType && gifDoc)
  54. || ((videoType || photoVideoType) && videoDoc)
  55. || (fileType && (document->isTheme()
  56. || document->isImage()
  57. || !document->canBeStreamed(item)));
  58. }
  59. } // namespace
  60. std::optional<Storage::SharedMediaType> SharedMediaOverviewType(
  61. Storage::SharedMediaType type) {
  62. switch (type) {
  63. case Type::Photo:
  64. case Type::Video:
  65. case Type::MusicFile:
  66. case Type::File:
  67. case Type::RoundVoiceFile:
  68. case Type::Link: return type;
  69. }
  70. return std::nullopt;
  71. }
  72. bool SharedMediaAllowSearch(Storage::SharedMediaType type) {
  73. switch (type) {
  74. case Type::MusicFile:
  75. case Type::File:
  76. case Type::Link: return true;
  77. default: return false;
  78. }
  79. }
  80. rpl::producer<SparseIdsSlice> SharedMediaViewer(
  81. not_null<Main::Session*> session,
  82. Storage::SharedMediaKey key,
  83. int limitBefore,
  84. int limitAfter) {
  85. Expects(IsServerMsgId(key.messageId) || (key.messageId == 0));
  86. Expects((key.messageId != 0) || (limitBefore == 0 && limitAfter == 0));
  87. return [=](auto consumer) {
  88. auto lifetime = rpl::lifetime();
  89. auto builder = lifetime.make_state<SparseIdsSliceBuilder>(
  90. key.messageId,
  91. limitBefore,
  92. limitAfter);
  93. auto requestMediaAround = [
  94. peer = session->data().peer(key.peerId),
  95. topicRootId = key.topicRootId,
  96. type = key.type
  97. ](const SparseIdsSliceBuilder::AroundData &data) {
  98. peer->session().api().requestSharedMedia(
  99. peer,
  100. topicRootId,
  101. type,
  102. data.aroundId,
  103. data.direction);
  104. };
  105. builder->insufficientAround(
  106. ) | rpl::start_with_next(requestMediaAround, lifetime);
  107. auto pushNextSnapshot = [=] {
  108. consumer.put_next(builder->snapshot());
  109. };
  110. using SliceUpdate = Storage::SharedMediaSliceUpdate;
  111. session->storage().sharedMediaSliceUpdated(
  112. ) | rpl::filter([=](const SliceUpdate &update) {
  113. return (update.peerId == key.peerId)
  114. && (update.topicRootId == key.topicRootId)
  115. && (update.type == key.type);
  116. }) | rpl::filter([=](const SliceUpdate &update) {
  117. return builder->applyUpdate(update.data);
  118. }) | rpl::start_with_next(pushNextSnapshot, lifetime);
  119. using OneRemoved = Storage::SharedMediaRemoveOne;
  120. session->storage().sharedMediaOneRemoved(
  121. ) | rpl::filter([=](const OneRemoved &update) {
  122. return (update.peerId == key.peerId)
  123. && update.types.test(key.type);
  124. }) | rpl::filter([=](const OneRemoved &update) {
  125. return builder->removeOne(update.messageId);
  126. }) | rpl::start_with_next(pushNextSnapshot, lifetime);
  127. using AllRemoved = Storage::SharedMediaRemoveAll;
  128. session->storage().sharedMediaAllRemoved(
  129. ) | rpl::filter([=](const AllRemoved &update) {
  130. return (update.peerId == key.peerId)
  131. && (!update.topicRootId
  132. || update.topicRootId == key.topicRootId)
  133. && update.types.test(key.type);
  134. }) | rpl::filter([=] {
  135. return builder->removeAll();
  136. }) | rpl::start_with_next(pushNextSnapshot, lifetime);
  137. using InvalidateBottom = Storage::SharedMediaInvalidateBottom;
  138. session->storage().sharedMediaBottomInvalidated(
  139. ) | rpl::filter([=](const InvalidateBottom &update) {
  140. return (update.peerId == key.peerId);
  141. }) | rpl::filter([=] {
  142. return builder->invalidateBottom();
  143. }) | rpl::start_with_next(pushNextSnapshot, lifetime);
  144. using Result = Storage::SharedMediaResult;
  145. session->storage().query(Storage::SharedMediaQuery(
  146. key,
  147. limitBefore,
  148. limitAfter
  149. )) | rpl::filter([=](const Result &result) {
  150. return builder->applyInitial(result);
  151. }) | rpl::start_with_next_done(
  152. pushNextSnapshot,
  153. [=] { builder->checkInsufficient(); },
  154. lifetime);
  155. return lifetime;
  156. };
  157. }
  158. rpl::producer<SparseIdsMergedSlice> SharedScheduledMediaViewer(
  159. not_null<Main::Session*> session,
  160. SharedMediaMergedKey key,
  161. int limitBefore,
  162. int limitAfter) {
  163. Expects(!key.mergedKey.universalId
  164. || Data::IsScheduledMsgId(key.mergedKey.universalId));
  165. Expects((key.mergedKey.universalId != 0)
  166. || (limitBefore == 0 && limitAfter == 0));
  167. const auto history = session->data().history(key.mergedKey.peerId);
  168. return rpl::single(rpl::empty) | rpl::then(
  169. session->scheduledMessages().updates(history)
  170. ) | rpl::map([=] {
  171. const auto list = session->scheduledMessages().list(history);
  172. auto items = ranges::views::all(
  173. list.ids
  174. ) | ranges::views::transform([=](const FullMsgId &fullId) {
  175. return session->data().message(fullId);
  176. }) | ranges::views::filter([=](HistoryItem *item) {
  177. return item
  178. ? IsItemGoodForType(item, key.type)
  179. : false;
  180. }) | ranges::to_vector;
  181. ranges::sort(items, ranges::less(), &HistoryItem::position);
  182. auto finishMsgIds = ranges::views::all(
  183. items
  184. ) | ranges::views::transform([=](not_null<HistoryItem*> item) {
  185. return item->fullId().msg;
  186. }) | ranges::to_vector;
  187. const auto fullCount = finishMsgIds.size();
  188. auto unsorted = SparseUnsortedIdsSlice(
  189. std::move(finishMsgIds),
  190. fullCount,
  191. list.skippedBefore,
  192. list.skippedAfter);
  193. return SparseIdsMergedSlice(
  194. key.mergedKey,
  195. std::move(unsorted));
  196. });
  197. }
  198. rpl::producer<SparseIdsMergedSlice> SharedMediaMergedViewer(
  199. not_null<Main::Session*> session,
  200. SharedMediaMergedKey key,
  201. int limitBefore,
  202. int limitAfter) {
  203. auto createSimpleViewer = [=](
  204. PeerId peerId,
  205. MsgId topicRootId,
  206. SparseIdsSlice::Key simpleKey,
  207. int limitBefore,
  208. int limitAfter) {
  209. return SharedMediaViewer(
  210. session,
  211. Storage::SharedMediaKey(
  212. peerId,
  213. topicRootId,
  214. key.type,
  215. simpleKey),
  216. limitBefore,
  217. limitAfter
  218. );
  219. };
  220. return SparseIdsMergedSlice::CreateViewer(
  221. key.mergedKey,
  222. limitBefore,
  223. limitAfter,
  224. std::move(createSimpleViewer));
  225. }
  226. SharedMediaWithLastSlice::SharedMediaWithLastSlice(
  227. not_null<Main::Session*> session,
  228. Key key)
  229. : SharedMediaWithLastSlice(
  230. session,
  231. key,
  232. SparseIdsMergedSlice(ViewerKey(key)),
  233. EndingSlice(key)) {
  234. }
  235. SharedMediaWithLastSlice::SharedMediaWithLastSlice(
  236. not_null<Main::Session*> session,
  237. Key key,
  238. SparseIdsMergedSlice slice,
  239. std::optional<SparseIdsMergedSlice> ending)
  240. : _session(session)
  241. , _key(key)
  242. , _slice(std::move(slice))
  243. , _ending(std::move(ending))
  244. , _lastPhotoId(LastPeerPhotoId(session, key.peerId))
  245. , _isolatedLastPhoto(_key.type == Type::ChatPhoto
  246. ? IsLastIsolated(session, _slice, _ending, _lastPhotoId)
  247. : false) {
  248. }
  249. std::optional<int> SharedMediaWithLastSlice::fullCount() const {
  250. return Add(
  251. _slice.fullCount(),
  252. _isolatedLastPhoto | [](bool isolated) { return isolated ? 1 : 0; });
  253. }
  254. std::optional<int> SharedMediaWithLastSlice::skippedBeforeImpl() const {
  255. return _slice.skippedBefore();
  256. }
  257. std::optional<int> SharedMediaWithLastSlice::skippedBefore() const {
  258. return _reversed ? skippedAfterImpl() : skippedBeforeImpl();
  259. }
  260. std::optional<int> SharedMediaWithLastSlice::skippedAfterImpl() const {
  261. return isolatedInSlice()
  262. ? Add(
  263. _slice.skippedAfter(),
  264. lastPhotoSkip())
  265. : (lastPhotoSkip() | [](int) { return 0; });
  266. }
  267. std::optional<int> SharedMediaWithLastSlice::skippedAfter() const {
  268. return _reversed ? skippedBeforeImpl() : skippedAfterImpl();
  269. }
  270. std::optional<int> SharedMediaWithLastSlice::indexOfImpl(Value value) const {
  271. return std::get_if<FullMsgId>(&value)
  272. ? _slice.indexOf(*std::get_if<FullMsgId>(&value))
  273. : (isolatedInSlice()
  274. || !_lastPhotoId
  275. || (*std::get_if<not_null<PhotoData*>>(&value))->id != *_lastPhotoId)
  276. ? std::nullopt
  277. : Add(_slice.size() - 1, lastPhotoSkip());
  278. }
  279. std::optional<int> SharedMediaWithLastSlice::indexOf(Value value) const {
  280. const auto result = indexOfImpl(value);
  281. if (result && (*result < 0 || *result >= size())) {
  282. // Should not happen.
  283. auto info = QStringList();
  284. info.push_back("slice:" + QString::number(_slice.size()));
  285. info.push_back(_slice.fullCount()
  286. ? QString::number(*_slice.fullCount())
  287. : QString("-"));
  288. info.push_back(_slice.skippedBefore()
  289. ? QString::number(*_slice.skippedBefore())
  290. : QString("-"));
  291. info.push_back(_slice.skippedAfter()
  292. ? QString::number(*_slice.skippedAfter())
  293. : QString("-"));
  294. info.push_back("ending:" + (_ending
  295. ? QString::number(_ending->size())
  296. : QString("-")));
  297. info.push_back((_ending && _ending->fullCount())
  298. ? QString::number(*_ending->fullCount())
  299. : QString("-"));
  300. info.push_back((_ending && _ending->skippedBefore())
  301. ? QString::number(*_ending->skippedBefore())
  302. : QString("-"));
  303. info.push_back((_ending && _ending->skippedAfter())
  304. ? QString::number(*_ending->skippedAfter())
  305. : QString("-"));
  306. if (const auto msgId = std::get_if<FullMsgId>(&value)) {
  307. info.push_back("value:" + QString::number(msgId->peer.value));
  308. info.push_back(QString::number(msgId->msg.bare));
  309. const auto index = _slice.indexOf(*std::get_if<FullMsgId>(&value));
  310. info.push_back("index:" + (index
  311. ? QString::number(*index)
  312. : QString("-")));
  313. } else if (const auto photo = std::get_if<not_null<PhotoData*>>(&value)) {
  314. info.push_back("value:" + QString::number((*photo)->id));
  315. } else {
  316. info.push_back("value:bad");
  317. }
  318. info.push_back("isolated:" + QString(Logs::b(isolatedInSlice())));
  319. info.push_back("last:" + (_lastPhotoId
  320. ? QString::number(*_lastPhotoId)
  321. : QString("-")));
  322. info.push_back("isolated_last:" + (_isolatedLastPhoto
  323. ? QString(Logs::b(*_isolatedLastPhoto))
  324. : QString("-")));
  325. info.push_back("skip:" + (lastPhotoSkip()
  326. ? QString::number(*lastPhotoSkip())
  327. : QString("-")));
  328. CrashReports::SetAnnotation("DebugInfo", info.join(','));
  329. Unexpected("Result in SharedMediaWithLastSlice::indexOf");
  330. }
  331. return _reversed
  332. ? (result | func::negate | func::add(size() - 1))
  333. : result;
  334. }
  335. int SharedMediaWithLastSlice::size() const {
  336. return _slice.size()
  337. + ((!isolatedInSlice() && lastPhotoSkip() == 1) ? 1 : 0);
  338. }
  339. SharedMediaWithLastSlice::Value SharedMediaWithLastSlice::operator[](int index) const {
  340. Expects(index >= 0 && index < size());
  341. if (_reversed) {
  342. index = size() - index - 1;
  343. }
  344. return (index < _slice.size())
  345. ? Value(_slice[index])
  346. : Value(_session->data().photo(*_lastPhotoId));
  347. }
  348. std::optional<int> SharedMediaWithLastSlice::distance(
  349. const Key &a,
  350. const Key &b) const {
  351. if (auto i = indexOf(ComputeId(a))) {
  352. if (auto j = indexOf(ComputeId(b))) {
  353. return *j - *i;
  354. }
  355. }
  356. return std::nullopt;
  357. }
  358. void SharedMediaWithLastSlice::reverse() {
  359. _reversed = !_reversed;
  360. }
  361. std::optional<PhotoId> SharedMediaWithLastSlice::LastPeerPhotoId(
  362. not_null<Main::Session*> session,
  363. PeerId peerId) {
  364. if (const auto peer = session->data().peerLoaded(peerId)) {
  365. return peer->userpicPhotoUnknown()
  366. ? std::nullopt
  367. : base::make_optional(peer->userpicPhotoId());
  368. }
  369. return std::nullopt;
  370. }
  371. std::optional<bool> SharedMediaWithLastSlice::IsLastIsolated(
  372. not_null<Main::Session*> session,
  373. const SparseIdsMergedSlice &slice,
  374. const std::optional<SparseIdsMergedSlice> &ending,
  375. std::optional<PhotoId> lastPeerPhotoId) {
  376. if (!lastPeerPhotoId) {
  377. return std::nullopt;
  378. } else if (!*lastPeerPhotoId) {
  379. return false;
  380. }
  381. return LastFullMsgId(ending ? *ending : slice)
  382. | [&](FullMsgId msgId) { return session->data().message(msgId); }
  383. | [](HistoryItem *item) { return item ? item->media() : nullptr; }
  384. | [](Data::Media *media) { return media ? media->photo() : nullptr; }
  385. | [](PhotoData *photo) { return photo ? photo->id : 0; }
  386. | [&](PhotoId photoId) { return *lastPeerPhotoId != photoId; };
  387. }
  388. std::optional<FullMsgId> SharedMediaWithLastSlice::LastFullMsgId(
  389. const SparseIdsMergedSlice &slice) {
  390. if (slice.fullCount() == 0) {
  391. return FullMsgId();
  392. } else if (slice.size() == 0 || slice.skippedAfter() != 0) {
  393. return std::nullopt;
  394. }
  395. return slice[slice.size() - 1];
  396. }
  397. rpl::producer<SharedMediaWithLastSlice> SharedMediaWithLastViewer(
  398. not_null<Main::Session*> session,
  399. SharedMediaWithLastSlice::Key key,
  400. int limitBefore,
  401. int limitAfter) {
  402. return [=](auto consumer) {
  403. auto viewerKey = SharedMediaMergedKey(
  404. SharedMediaWithLastSlice::ViewerKey(key),
  405. key.type);
  406. if (std::get_if<not_null<PhotoData*>>(&key.universalId)) {
  407. return SharedMediaMergedViewer(
  408. session,
  409. std::move(viewerKey),
  410. limitBefore,
  411. limitAfter
  412. ) | rpl::start_with_next([=](SparseIdsMergedSlice &&update) {
  413. consumer.put_next(SharedMediaWithLastSlice(
  414. session,
  415. key,
  416. std::move(update),
  417. std::nullopt));
  418. });
  419. }
  420. if (key.topicRootId == SharedMediaWithLastSlice::kScheduledTopicId) {
  421. return SharedScheduledMediaViewer(
  422. session,
  423. std::move(viewerKey),
  424. limitBefore,
  425. limitAfter
  426. ) | rpl::start_with_next([=](SparseIdsMergedSlice &&update) {
  427. consumer.put_next(SharedMediaWithLastSlice(
  428. session,
  429. key,
  430. std::move(update),
  431. std::nullopt));
  432. });
  433. }
  434. return rpl::combine(
  435. SharedMediaMergedViewer(
  436. session,
  437. std::move(viewerKey),
  438. limitBefore,
  439. limitAfter),
  440. SharedMediaMergedViewer(
  441. session,
  442. SharedMediaMergedKey(
  443. SharedMediaWithLastSlice::EndingKey(key),
  444. key.type),
  445. 1,
  446. 1)
  447. ) | rpl::start_with_next([=](
  448. SparseIdsMergedSlice &&viewer,
  449. SparseIdsMergedSlice &&ending) {
  450. consumer.put_next(SharedMediaWithLastSlice(
  451. session,
  452. key,
  453. std::move(viewer),
  454. std::move(ending)));
  455. });
  456. };
  457. }
  458. rpl::producer<SharedMediaWithLastSlice> SharedMediaWithLastReversedViewer(
  459. not_null<Main::Session*> session,
  460. SharedMediaWithLastSlice::Key key,
  461. int limitBefore,
  462. int limitAfter) {
  463. return SharedMediaWithLastViewer(
  464. session,
  465. key,
  466. limitBefore,
  467. limitAfter
  468. ) | rpl::map([](SharedMediaWithLastSlice &&slice) {
  469. slice.reverse();
  470. return std::move(slice);
  471. });
  472. }