menu_item_download_files.cpp 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  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 "menu/menu_item_download_files.h"
  8. #include "core/application.h"
  9. #include "core/core_settings.h"
  10. #include "core/file_utilities.h"
  11. #include "data/data_document.h"
  12. #include "data/data_document_media.h"
  13. #include "data/data_file_click_handler.h"
  14. #include "data/data_photo.h"
  15. #include "data/data_photo_media.h"
  16. #include "data/data_session.h"
  17. #include "history/history_inner_widget.h"
  18. #include "history/history_item.h"
  19. #include "history/view/history_view_list_widget.h" // HistoryView::SelectedItem.
  20. #include "lang/lang_keys.h"
  21. #include "main/main_session.h"
  22. #include "mainwindow.h"
  23. #include "storage/storage_account.h"
  24. #include "ui/text/text_utilities.h"
  25. #include "ui/toast/toast.h"
  26. #include "ui/widgets/popup_menu.h"
  27. #include "window/window_session_controller.h"
  28. #include "window/window_controller.h"
  29. #include "styles/style_menu_icons.h"
  30. #include "styles/style_widgets.h"
  31. namespace Menu {
  32. namespace {
  33. using Documents = std::vector<std::pair<not_null<DocumentData*>, FullMsgId>>;
  34. using Photos = std::vector<std::pair<not_null<PhotoData*>, FullMsgId>>;
  35. [[nodiscard]] bool Added(
  36. HistoryItem *item,
  37. Documents &documents,
  38. Photos &photos) {
  39. if (item && !item->forbidsForward()) {
  40. if (const auto media = item->media()) {
  41. if (const auto photo = media->photo()) {
  42. photos.emplace_back(photo, item->fullId());
  43. return true;
  44. } else if (const auto document = media->document()) {
  45. documents.emplace_back(document, item->fullId());
  46. return true;
  47. }
  48. }
  49. }
  50. return false;
  51. }
  52. void AddAction(
  53. not_null<Ui::PopupMenu*> menu,
  54. not_null<Window::SessionController*> controller,
  55. Documents &&documents,
  56. Photos &&photos,
  57. Fn<void()> callback) {
  58. const auto text = documents.empty()
  59. ? tr::lng_context_save_images_selected(tr::now)
  60. : tr::lng_context_save_documents_selected(tr::now);
  61. const auto icon = documents.empty()
  62. ? &st::menuIconSaveImage
  63. : &st::menuIconDownload;
  64. const auto shouldShowToast = documents.empty();
  65. const auto weak = base::make_weak(controller);
  66. const auto saveImages = [=](const QString &folderPath) {
  67. const auto controller = weak.get();
  68. if (!controller) {
  69. return;
  70. }
  71. const auto session = &controller->session();
  72. const auto downloadPath = folderPath.isEmpty()
  73. ? Core::App().settings().downloadPath()
  74. : folderPath;
  75. const auto path = downloadPath.isEmpty()
  76. ? File::DefaultDownloadPath(session)
  77. : (downloadPath == FileDialog::Tmp())
  78. ? session->local().tempDirectory()
  79. : downloadPath;
  80. if (path.isEmpty()) {
  81. return;
  82. }
  83. QDir().mkpath(path);
  84. const auto showToast = !shouldShowToast
  85. ? Fn<void(const QString &)>(nullptr)
  86. : [=](const QString &lastPath) {
  87. const auto filter = [lastPath](const auto ...) {
  88. File::ShowInFolder(lastPath);
  89. return false;
  90. };
  91. controller->showToast({
  92. .text = (photos.size() > 1
  93. ? tr::lng_mediaview_saved_images_to
  94. : tr::lng_mediaview_saved_to)(
  95. tr::now,
  96. lt_downloads,
  97. Ui::Text::Link(
  98. tr::lng_mediaview_downloads(tr::now),
  99. "internal:show_saved_message"),
  100. Ui::Text::WithEntities),
  101. .filter = filter,
  102. .st = &st::defaultToast,
  103. });
  104. };
  105. auto views = std::vector<std::shared_ptr<Data::PhotoMedia>>();
  106. for (const auto &[photo, fullId] : photos) {
  107. if (const auto view = photo->createMediaView()) {
  108. view->wanted(Data::PhotoSize::Large, fullId);
  109. views.push_back(view);
  110. }
  111. }
  112. const auto finalCheck = [=] {
  113. for (const auto &[photo, _] : photos) {
  114. if (photo->loading()) {
  115. return false;
  116. }
  117. }
  118. return true;
  119. };
  120. const auto saveToFiles = [=] {
  121. const auto fullPath = [&](int i) {
  122. return filedialogDefaultName(
  123. u"photo_"_q + QString::number(i),
  124. u".jpg"_q,
  125. path);
  126. };
  127. auto lastPath = QString();
  128. for (auto i = 0; i < views.size(); i++) {
  129. lastPath = fullPath(i + 1);
  130. views[i]->saveToFile(lastPath);
  131. }
  132. if (showToast) {
  133. showToast(lastPath);
  134. }
  135. };
  136. if (finalCheck()) {
  137. saveToFiles();
  138. } else {
  139. auto lifetime = std::make_shared<rpl::lifetime>();
  140. session->downloaderTaskFinished(
  141. ) | rpl::start_with_next([=]() mutable {
  142. if (finalCheck()) {
  143. saveToFiles();
  144. base::take(lifetime)->destroy();
  145. }
  146. }, *lifetime);
  147. }
  148. };
  149. const auto saveDocuments = [=](const QString &folderPath) {
  150. for (const auto &[document, origin] : documents) {
  151. if (!folderPath.isEmpty()) {
  152. document->save(origin, folderPath + document->filename());
  153. } else {
  154. DocumentSaveClickHandler::SaveAndTrack(origin, document);
  155. }
  156. }
  157. };
  158. menu->addAction(text, [=] {
  159. const auto save = [=](const QString &folderPath) {
  160. saveImages(folderPath);
  161. saveDocuments(folderPath);
  162. callback();
  163. };
  164. const auto controller = weak.get();
  165. if (!controller) {
  166. return;
  167. }
  168. if (Core::App().settings().askDownloadPath()) {
  169. const auto initialPath = [] {
  170. const auto path = Core::App().settings().downloadPath();
  171. if (!path.isEmpty() && path != FileDialog::Tmp()) {
  172. return path.left(path.size()
  173. - (path.endsWith('/') ? 1 : 0));
  174. }
  175. return QString();
  176. }();
  177. const auto handleFolder = [=](const QString &result) {
  178. if (!result.isEmpty()) {
  179. const auto folderPath = result.endsWith('/')
  180. ? result
  181. : (result + '/');
  182. save(folderPath);
  183. }
  184. };
  185. FileDialog::GetFolder(
  186. controller->window().widget().get(),
  187. tr::lng_download_path_choose(tr::now),
  188. initialPath,
  189. handleFolder);
  190. } else {
  191. save(QString());
  192. }
  193. }, icon);
  194. }
  195. } // namespace
  196. void AddDownloadFilesAction(
  197. not_null<Ui::PopupMenu*> menu,
  198. not_null<Window::SessionController*> window,
  199. const std::vector<HistoryView::SelectedItem> &selectedItems,
  200. not_null<HistoryView::ListWidget*> list) {
  201. if (selectedItems.empty()) {
  202. return;
  203. }
  204. auto docs = Documents();
  205. auto photos = Photos();
  206. for (const auto &selectedItem : selectedItems) {
  207. const auto &id = selectedItem.msgId;
  208. const auto item = window->session().data().message(id);
  209. if (!Added(item, docs, photos)) {
  210. return;
  211. }
  212. }
  213. const auto done = [weak = Ui::MakeWeak(list)] {
  214. if (const auto strong = weak.data()) {
  215. strong->cancelSelection();
  216. }
  217. };
  218. AddAction(menu, window, std::move(docs), std::move(photos), done);
  219. }
  220. void AddDownloadFilesAction(
  221. not_null<Ui::PopupMenu*> menu,
  222. not_null<Window::SessionController*> window,
  223. const std::map<HistoryItem*, TextSelection, std::less<>> &items,
  224. not_null<HistoryInner*> list) {
  225. if (items.empty()) {
  226. return;
  227. }
  228. auto docs = Documents();
  229. auto photos = Photos();
  230. for (const auto &pair : items) {
  231. if (!Added(pair.first, docs, photos)) {
  232. return;
  233. }
  234. }
  235. const auto done = [weak = Ui::MakeWeak(list)] {
  236. if (const auto strong = weak.data()) {
  237. strong->clearSelected();
  238. }
  239. };
  240. AddAction(menu, window, std::move(docs), std::move(photos), done);
  241. }
  242. } // namespace Menu