info_downloads_provider.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570
  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 "info/downloads/info_downloads_provider.h"
  8. #include "info/media/info_media_widget.h"
  9. #include "info/media/info_media_list_section.h"
  10. #include "info/info_controller.h"
  11. #include "ui/text/format_song_document_name.h"
  12. #include "ui/ui_utility.h"
  13. #include "data/data_download_manager.h"
  14. #include "data/data_document.h"
  15. #include "data/data_media_types.h"
  16. #include "data/data_session.h"
  17. #include "main/main_session.h"
  18. #include "main/main_account.h"
  19. #include "history/history_item.h"
  20. #include "history/history_item_helpers.h"
  21. #include "history/history.h"
  22. #include "core/application.h"
  23. #include "storage/storage_shared_media.h"
  24. #include "layout/layout_selection.h"
  25. #include "styles/style_overview.h"
  26. namespace Info::Downloads {
  27. namespace {
  28. using namespace Media;
  29. } // namespace
  30. Provider::Provider(not_null<AbstractController*> controller)
  31. : _controller(controller) {
  32. style::PaletteChanged(
  33. ) | rpl::start_with_next([=] {
  34. for (auto &layout : _layouts) {
  35. layout.second.item->invalidateCache();
  36. }
  37. }, _lifetime);
  38. }
  39. Type Provider::type() {
  40. return Type::File;
  41. }
  42. bool Provider::hasSelectRestriction() {
  43. return false;
  44. }
  45. rpl::producer<bool> Provider::hasSelectRestrictionChanges() {
  46. return rpl::never<bool>();
  47. }
  48. bool Provider::sectionHasFloatingHeader() {
  49. return false;
  50. }
  51. QString Provider::sectionTitle(not_null<const BaseLayout*> item) {
  52. return QString();
  53. }
  54. bool Provider::sectionItemBelongsHere(
  55. not_null<const BaseLayout*> item,
  56. not_null<const BaseLayout*> previous) {
  57. return true;
  58. }
  59. bool Provider::isPossiblyMyItem(not_null<const HistoryItem*> item) {
  60. return true;
  61. }
  62. std::optional<int> Provider::fullCount() {
  63. return _queryWords.empty()
  64. ? _fullCount
  65. : (_foundCount || _fullCount.has_value())
  66. ? _foundCount
  67. : std::optional<int>();
  68. }
  69. void Provider::restart() {
  70. }
  71. void Provider::checkPreload(
  72. QSize viewport,
  73. not_null<BaseLayout*> topLayout,
  74. not_null<BaseLayout*> bottomLayout,
  75. bool preloadTop,
  76. bool preloadBottom) {
  77. }
  78. void Provider::setSearchQuery(QString query) {
  79. if (_query == query) {
  80. return;
  81. }
  82. _query = query;
  83. auto words = TextUtilities::PrepareSearchWords(_query);
  84. if (!_started || _queryWords == words) {
  85. return;
  86. }
  87. _queryWords = std::move(words);
  88. if (searchMode()) {
  89. _foundCount = 0;
  90. for (auto &element : _elements) {
  91. if ((element.found = computeIsFound(element))) {
  92. ++_foundCount;
  93. }
  94. }
  95. }
  96. _refreshed.fire({});
  97. }
  98. void Provider::refreshViewer() {
  99. if (_started) {
  100. return;
  101. }
  102. _started = true;
  103. auto &manager = Core::App().downloadManager();
  104. rpl::single(rpl::empty) | rpl::then(
  105. manager.loadingListChanges() | rpl::to_empty
  106. ) | rpl::start_with_next([=, &manager] {
  107. auto copy = _downloading;
  108. for (const auto id : manager.loadingList()) {
  109. if (!id->done) {
  110. const auto item = id->object.item;
  111. if (!copy.remove(item) && !_downloaded.contains(item)) {
  112. _downloading.emplace(item);
  113. addElementNow({
  114. .item = item,
  115. .started = id->started,
  116. .path = id->path,
  117. });
  118. trackItemSession(item);
  119. refreshPostponed(true);
  120. }
  121. }
  122. }
  123. for (const auto &item : copy) {
  124. Assert(!_downloaded.contains(item));
  125. remove(item);
  126. }
  127. if (!_fullCount.has_value()) {
  128. refreshPostponed(false);
  129. }
  130. }, _lifetime);
  131. for (const auto id : manager.loadedList()) {
  132. addPostponed(id);
  133. }
  134. manager.loadedAdded(
  135. ) | rpl::start_with_next([=](not_null<const Data::DownloadedId*> entry) {
  136. addPostponed(entry);
  137. }, _lifetime);
  138. manager.loadedRemoved(
  139. ) | rpl::start_with_next([=](not_null<const HistoryItem*> item) {
  140. if (!_downloading.contains(item)) {
  141. remove(item);
  142. } else {
  143. _downloaded.remove(item);
  144. _addPostponed.erase(
  145. ranges::remove(_addPostponed, item, &Element::item),
  146. end(_addPostponed));
  147. }
  148. }, _lifetime);
  149. manager.loadedResolveDone(
  150. ) | rpl::start_with_next([=] {
  151. if (!_fullCount.has_value()) {
  152. _fullCount = 0;
  153. }
  154. }, _lifetime);
  155. performAdd();
  156. performRefresh();
  157. }
  158. void Provider::addPostponed(not_null<const Data::DownloadedId*> entry) {
  159. Expects(entry->object != nullptr);
  160. const auto item = entry->object->item;
  161. trackItemSession(item);
  162. const auto i = ranges::find(_addPostponed, item, &Element::item);
  163. if (i != end(_addPostponed)) {
  164. i->path = entry->path;
  165. i->started = entry->started;
  166. } else {
  167. _addPostponed.push_back({
  168. .item = item,
  169. .started = entry->started,
  170. .path = entry->path,
  171. });
  172. if (_addPostponed.size() == 1) {
  173. Ui::PostponeCall(this, [=] {
  174. performAdd();
  175. });
  176. }
  177. }
  178. }
  179. void Provider::performAdd() {
  180. if (_addPostponed.empty()) {
  181. return;
  182. }
  183. for (auto &element : base::take(_addPostponed)) {
  184. _downloaded.emplace(element.item);
  185. if (!_downloading.remove(element.item)) {
  186. addElementNow(std::move(element));
  187. }
  188. }
  189. refreshPostponed(true);
  190. }
  191. void Provider::addElementNow(Element &&element) {
  192. _elements.push_back(std::move(element));
  193. auto &added = _elements.back();
  194. fillSearchIndex(added);
  195. added.found = searchMode() && computeIsFound(added);
  196. if (added.found) {
  197. ++_foundCount;
  198. }
  199. }
  200. void Provider::remove(not_null<const HistoryItem*> item) {
  201. _addPostponed.erase(
  202. ranges::remove(_addPostponed, item, &Element::item),
  203. end(_addPostponed));
  204. _downloading.remove(item);
  205. _downloaded.remove(item);
  206. const auto proj = [&](const Element &element) {
  207. if (element.item != item) {
  208. return false;
  209. } else if (element.found && searchMode()) {
  210. --_foundCount;
  211. }
  212. return true;
  213. };
  214. _elements.erase(ranges::remove_if(_elements, proj), end(_elements));
  215. if (const auto i = _layouts.find(item); i != end(_layouts)) {
  216. _layoutRemoved.fire(i->second.item.get());
  217. _layouts.erase(i);
  218. }
  219. refreshPostponed(false);
  220. }
  221. void Provider::refreshPostponed(bool added) {
  222. if (added) {
  223. _postponedRefreshSort = true;
  224. }
  225. if (!_postponedRefresh) {
  226. _postponedRefresh = true;
  227. Ui::PostponeCall(this, [=] {
  228. performRefresh();
  229. });
  230. }
  231. }
  232. void Provider::performRefresh() {
  233. if (!_postponedRefresh) {
  234. return;
  235. }
  236. _postponedRefresh = false;
  237. if (!_elements.empty() || _fullCount.has_value()) {
  238. _fullCount = _elements.size();
  239. }
  240. if (base::take(_postponedRefreshSort)) {
  241. ranges::sort(_elements, ranges::less(), &Element::started);
  242. }
  243. _refreshed.fire({});
  244. }
  245. void Provider::trackItemSession(not_null<const HistoryItem*> item) {
  246. const auto session = &item->history()->session();
  247. if (_trackedSessions.contains(session)) {
  248. return;
  249. }
  250. auto &lifetime = _trackedSessions.emplace(session).first->second;
  251. session->data().itemRemoved(
  252. ) | rpl::start_with_next([this](auto item) {
  253. itemRemoved(item);
  254. }, lifetime);
  255. session->account().sessionChanges(
  256. ) | rpl::take(1) | rpl::start_with_next([=] {
  257. _trackedSessions.remove(session);
  258. }, lifetime);
  259. }
  260. rpl::producer<> Provider::refreshed() {
  261. return _refreshed.events();
  262. }
  263. std::vector<ListSection> Provider::fillSections(
  264. not_null<Overview::Layout::Delegate*> delegate) {
  265. const auto search = searchMode();
  266. if (!search) {
  267. markLayoutsStale();
  268. }
  269. const auto guard = gsl::finally([&] { clearStaleLayouts(); });
  270. if (_elements.empty() || (search && !_foundCount)) {
  271. return {};
  272. }
  273. auto result = std::vector<ListSection>();
  274. result.emplace_back(Type::File, sectionDelegate());
  275. auto &section = result.back();
  276. for (const auto &element : ranges::views::reverse(_elements)) {
  277. if (search && !element.found) {
  278. continue;
  279. } else if (auto layout = getLayout(element, delegate)) {
  280. section.addItem(layout);
  281. }
  282. }
  283. section.finishSection();
  284. return result;
  285. }
  286. void Provider::markLayoutsStale() {
  287. for (auto &layout : _layouts) {
  288. layout.second.stale = true;
  289. }
  290. }
  291. void Provider::clearStaleLayouts() {
  292. for (auto i = _layouts.begin(); i != _layouts.end();) {
  293. if (i->second.stale) {
  294. _layoutRemoved.fire(i->second.item.get());
  295. i = _layouts.erase(i);
  296. } else {
  297. ++i;
  298. }
  299. }
  300. }
  301. rpl::producer<not_null<BaseLayout*>> Provider::layoutRemoved() {
  302. return _layoutRemoved.events();
  303. }
  304. BaseLayout *Provider::lookupLayout(const HistoryItem *item) {
  305. return nullptr;
  306. }
  307. bool Provider::isMyItem(not_null<const HistoryItem*> item) {
  308. return _downloading.contains(item) || _downloaded.contains(item);
  309. }
  310. bool Provider::isAfter(
  311. not_null<const HistoryItem*> a,
  312. not_null<const HistoryItem*> b) {
  313. if (a != b) {
  314. for (const auto &element : _elements) {
  315. if (element.item == a) {
  316. return false;
  317. } else if (element.item == b) {
  318. return true;
  319. }
  320. }
  321. }
  322. return false;
  323. }
  324. bool Provider::searchMode() const {
  325. return !_queryWords.empty();
  326. }
  327. void Provider::fillSearchIndex(Element &element) {
  328. auto strings = QStringList(QFileInfo(element.path).fileName());
  329. if (const auto media = element.item->media()) {
  330. if (const auto document = media->document()) {
  331. strings.append(document->filename());
  332. strings.append(Ui::Text::FormatDownloadsName(document).text);
  333. }
  334. }
  335. element.words = TextUtilities::PrepareSearchWords(strings.join(' '));
  336. element.letters.clear();
  337. for (const auto &word : element.words) {
  338. element.letters.emplace(word.front());
  339. }
  340. }
  341. bool Provider::computeIsFound(const Element &element) const {
  342. Expects(!_queryWords.empty());
  343. const auto has = [&](const QString &queryWord) {
  344. if (!element.letters.contains(queryWord.front())) {
  345. return false;
  346. }
  347. for (const auto &word : element.words) {
  348. if (word.startsWith(queryWord)) {
  349. return true;
  350. }
  351. }
  352. return false;
  353. };
  354. for (const auto &queryWord : _queryWords) {
  355. if (!has(queryWord)) {
  356. return false;
  357. }
  358. }
  359. return true;
  360. }
  361. void Provider::itemRemoved(not_null<const HistoryItem*> item) {
  362. remove(item);
  363. }
  364. BaseLayout *Provider::getLayout(
  365. Element element,
  366. not_null<Overview::Layout::Delegate*> delegate) {
  367. auto it = _layouts.find(element.item);
  368. if (it == _layouts.end()) {
  369. if (auto layout = createLayout(element, delegate)) {
  370. layout->initDimensions();
  371. it = _layouts.emplace(element.item, std::move(layout)).first;
  372. } else {
  373. return nullptr;
  374. }
  375. }
  376. it->second.stale = false;
  377. return it->second.item.get();
  378. }
  379. std::unique_ptr<BaseLayout> Provider::createLayout(
  380. Element element,
  381. not_null<Overview::Layout::Delegate*> delegate) {
  382. const auto getFile = [&]() -> DocumentData* {
  383. if (auto media = element.item->media()) {
  384. return media->document();
  385. }
  386. return nullptr;
  387. };
  388. using namespace Overview::Layout;
  389. auto &songSt = st::overviewFileLayout;
  390. if (const auto file = getFile()) {
  391. return std::make_unique<Document>(
  392. delegate,
  393. element.item,
  394. DocumentFields{
  395. .document = file,
  396. .dateOverride = Data::DateFromDownloadDate(element.started),
  397. .forceFileLayout = true,
  398. },
  399. songSt);
  400. }
  401. return nullptr;
  402. }
  403. ListItemSelectionData Provider::computeSelectionData(
  404. not_null<const HistoryItem*> item,
  405. TextSelection selection) {
  406. auto result = ListItemSelectionData(selection);
  407. result.canDelete = true;
  408. result.canForward = item->allowsForward()
  409. && (&item->history()->session() == &_controller->session());
  410. return result;
  411. }
  412. void Provider::applyDragSelection(
  413. ListSelectedMap &selected,
  414. not_null<const HistoryItem*> fromItem,
  415. bool skipFrom,
  416. not_null<const HistoryItem*> tillItem,
  417. bool skipTill) {
  418. auto from = ranges::find(_elements, fromItem, &Element::item);
  419. auto till = ranges::find(_elements, tillItem, &Element::item);
  420. if (from == end(_elements) || till == end(_elements)) {
  421. return;
  422. }
  423. if (skipFrom) {
  424. ++from;
  425. }
  426. if (!skipTill) {
  427. ++till;
  428. }
  429. if (from >= till) {
  430. selected.clear();
  431. return;
  432. }
  433. const auto search = !_queryWords.isEmpty();
  434. auto chosen = base::flat_set<not_null<const HistoryItem*>>();
  435. chosen.reserve(till - from);
  436. for (auto i = from; i != till; ++i) {
  437. if (search && !i->found) {
  438. continue;
  439. }
  440. const auto item = i->item;
  441. chosen.emplace(item);
  442. ChangeItemSelection(
  443. selected,
  444. item,
  445. computeSelectionData(item, FullSelection));
  446. }
  447. if (selected.size() != chosen.size()) {
  448. for (auto i = begin(selected); i != end(selected);) {
  449. if (selected.contains(i->first)) {
  450. ++i;
  451. } else {
  452. i = selected.erase(i);
  453. }
  454. }
  455. }
  456. }
  457. bool Provider::allowSaveFileAs(
  458. not_null<const HistoryItem*> item,
  459. not_null<DocumentData*> document) {
  460. return false;
  461. }
  462. QString Provider::showInFolderPath(
  463. not_null<const HistoryItem*> item,
  464. not_null<DocumentData*> document) {
  465. const auto i = ranges::find(_elements, item, &Element::item);
  466. return (i != end(_elements)) ? i->path : QString();
  467. }
  468. int64 Provider::scrollTopStatePosition(not_null<HistoryItem*> item) {
  469. const auto i = ranges::find(_elements, item, &Element::item);
  470. return (i != end(_elements)) ? i->started : 0;
  471. }
  472. HistoryItem *Provider::scrollTopStateItem(ListScrollTopState state) {
  473. if (!state.position) {
  474. return _elements.empty() ? nullptr : _elements.back().item.get();
  475. }
  476. const auto i = ranges::lower_bound(
  477. _elements,
  478. state.position,
  479. ranges::less(),
  480. &Element::started);
  481. return (i != end(_elements))
  482. ? i->item.get()
  483. : _elements.empty()
  484. ? nullptr
  485. : _elements.back().item.get();
  486. }
  487. void Provider::saveState(
  488. not_null<Media::Memento*> memento,
  489. ListScrollTopState scrollState) {
  490. if (!_elements.empty() && scrollState.item) {
  491. memento->setAroundId({ PeerId(), 1 });
  492. memento->setScrollTopItem(scrollState.item->globalId());
  493. memento->setScrollTopItemPosition(scrollState.position);
  494. memento->setScrollTopShift(scrollState.shift);
  495. }
  496. }
  497. void Provider::restoreState(
  498. not_null<Media::Memento*> memento,
  499. Fn<void(ListScrollTopState)> restoreScrollState) {
  500. if (memento->aroundId() == FullMsgId(PeerId(), 1)) {
  501. restoreScrollState({
  502. .position = memento->scrollTopItemPosition(),
  503. .item = MessageByGlobalId(memento->scrollTopItem()),
  504. .shift = memento->scrollTopShift(),
  505. });
  506. refreshViewer();
  507. }
  508. }
  509. } // namespace Info::Downloads