inline_bot_downloads.cpp 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421
  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 "inline_bots/inline_bot_downloads.h"
  8. #include "core/file_utilities.h"
  9. #include "data/data_document.h"
  10. #include "data/data_peer_id.h"
  11. #include "data/data_user.h"
  12. #include "lang/lang_keys.h"
  13. #include "main/main_session.h"
  14. #include "storage/file_download_web.h"
  15. #include "storage/serialize_common.h"
  16. #include "storage/storage_account.h"
  17. #include "ui/chat/attach/attach_bot_downloads.h"
  18. #include "ui/layers/generic_box.h"
  19. #include "ui/text/text_utilities.h"
  20. #include "ui/widgets/labels.h"
  21. #include "styles/style_chat.h"
  22. #include <QtCore/QBuffer>
  23. #include <QtCore/QDataStream>
  24. #include "base/call_delayed.h"
  25. namespace InlineBots {
  26. namespace {
  27. constexpr auto kDownloadsVersion = 1;
  28. constexpr auto kMaxDownloadsBots = 4096;
  29. constexpr auto kMaxDownloadsPerBot = 16384;
  30. } // namespace
  31. Downloads::Downloads(not_null<Main::Session*> session)
  32. : _session(session) {
  33. }
  34. Downloads::~Downloads() {
  35. base::take(_loaders);
  36. base::take(_lists);
  37. }
  38. DownloadId Downloads::start(StartArgs &&args) {
  39. read();
  40. const auto botId = args.bot->id;
  41. const auto id = ++_autoIncrementId;
  42. auto &list = _lists[botId].list;
  43. list.push_back({
  44. .id = id,
  45. .url = std::move(args.url),
  46. .path = std::move(args.path),
  47. });
  48. load(botId, id, list.back());
  49. return id;
  50. }
  51. void Downloads::load(
  52. PeerId botId,
  53. DownloadId id,
  54. DownloadsEntry &entry) {
  55. entry.loading = 1;
  56. entry.failed = 0;
  57. auto &loader = _loaders[id];
  58. Assert(!loader.loader);
  59. loader.botId = botId;
  60. loader.loader = std::make_unique<webFileLoader>(
  61. _session,
  62. entry.url,
  63. entry.path,
  64. WebRequestType::FullLoad);
  65. applyProgress(botId, id, 0, 0);
  66. loader.loader->updates(
  67. ) | rpl::start_with_next_error_done([=] {
  68. progress(botId, id);
  69. }, [=](FileLoader::Error) {
  70. fail(botId, id);
  71. }, [=] {
  72. done(botId, id);
  73. }, loader.loader->lifetime());
  74. loader.loader->start();
  75. }
  76. void Downloads::progress(PeerId botId, DownloadId id) {
  77. const auto i = _loaders.find(id);
  78. if (i == end(_loaders)) {
  79. return;
  80. }
  81. const auto &loader = i->second.loader;
  82. const auto total = loader->fullSize();
  83. const auto ready = loader->currentOffset();
  84. auto &list = _lists[botId].list;
  85. const auto j = ranges::find(
  86. list,
  87. id,
  88. &DownloadsEntry::id);
  89. Assert(j != end(list));
  90. if (total < 0 || ready > total) {
  91. fail(botId, id);
  92. return;
  93. } else if (ready == total) {
  94. // Wait for 'done' signal.
  95. return;
  96. }
  97. applyProgress(botId, id, total, ready);
  98. }
  99. void Downloads::fail(PeerId botId, DownloadId id, bool cancel) {
  100. const auto i = _loaders.find(id);
  101. if (i == end(_loaders)) {
  102. return;
  103. }
  104. auto loader = std::move(i->second.loader);
  105. _loaders.erase(i);
  106. loader = nullptr;
  107. auto &list = _lists[botId].list;
  108. const auto k = ranges::find(
  109. list,
  110. id,
  111. &DownloadsEntry::id);
  112. Assert(k != end(list));
  113. k->loading = 0;
  114. k->failed = 1;
  115. if (cancel) {
  116. auto copy = *k;
  117. list.erase(k);
  118. applyProgress(botId, copy, 0, 0);
  119. } else {
  120. applyProgress(botId, *k, 0, 0);
  121. }
  122. }
  123. void Downloads::done(PeerId botId, DownloadId id) {
  124. const auto i = _loaders.find(id);
  125. if (i == end(_loaders)) {
  126. return;
  127. }
  128. const auto total = i->second.loader->fullSize();
  129. if (total <= 0) {
  130. fail(botId, id);
  131. return;
  132. }
  133. _loaders.erase(i);
  134. auto &list = _lists[botId].list;
  135. const auto j = ranges::find(
  136. list,
  137. id,
  138. &DownloadsEntry::id);
  139. Assert(j != end(list));
  140. j->loading = 0;
  141. applyProgress(botId, id, total, total);
  142. }
  143. void Downloads::applyProgress(
  144. PeerId botId,
  145. DownloadId id,
  146. int64 total,
  147. int64 ready) {
  148. Expects(total >= 0);
  149. Expects(ready >= 0 && ready <= total);
  150. auto &list = _lists[botId].list;
  151. const auto j = ranges::find(
  152. list,
  153. id,
  154. &DownloadsEntry::id);
  155. Assert(j != end(list));
  156. applyProgress(botId, *j, total, ready);
  157. }
  158. void Downloads::applyProgress(
  159. PeerId botId,
  160. DownloadsEntry &entry,
  161. int64 total,
  162. int64 ready) {
  163. auto &progress = _progressView[botId];
  164. auto current = progress.current();
  165. auto subtract = int64(0);
  166. if (current.ready == current.total) {
  167. subtract = current.ready;
  168. }
  169. if (entry.total != total) {
  170. const auto delta = total - entry.total;
  171. entry.total = total;
  172. current.total += delta;
  173. }
  174. if (entry.ready != ready) {
  175. const auto delta = ready - entry.ready;
  176. entry.ready = ready;
  177. current.ready += delta;
  178. }
  179. if (subtract > 0
  180. && current.ready >= subtract
  181. && current.total >= subtract) {
  182. current.ready -= subtract;
  183. current.total -= subtract;
  184. }
  185. if (entry.loading || current.ready < current.total) {
  186. current.loading = 1;
  187. } else {
  188. current.loading = 0;
  189. }
  190. if (total > 0 && total == ready) {
  191. write();
  192. }
  193. progress = current;
  194. }
  195. void Downloads::action(
  196. not_null<UserData*> bot,
  197. DownloadId id,
  198. DownloadsAction type) {
  199. switch (type) {
  200. case DownloadsAction::Open: {
  201. const auto i = ranges::find(
  202. _lists[bot->id].list,
  203. id,
  204. &DownloadsEntry::id);
  205. if (i == end(_lists[bot->id].list)) {
  206. return;
  207. }
  208. File::ShowInFolder(i->path);
  209. } break;
  210. case DownloadsAction::Cancel: {
  211. const auto i = _loaders.find(id);
  212. if (i == end(_loaders)) {
  213. return;
  214. }
  215. const auto botId = i->second.botId;
  216. fail(botId, id, true);
  217. } break;
  218. case DownloadsAction::Retry: {
  219. const auto i = ranges::find(
  220. _lists[bot->id].list,
  221. id,
  222. &DownloadsEntry::id);
  223. if (i == end(_lists[bot->id].list)) {
  224. return;
  225. }
  226. load(bot->id, id, *i);
  227. } break;
  228. }
  229. }
  230. [[nodiscard]] auto Downloads::progress(not_null<UserData*> bot)
  231. ->rpl::producer<DownloadsProgress> {
  232. read();
  233. return _progressView[bot->id].value();
  234. }
  235. const std::vector<DownloadsEntry> &Downloads::list(
  236. not_null<UserData*> bot,
  237. bool forceCheck) {
  238. read();
  239. auto &entry = _lists[bot->id];
  240. if (forceCheck) {
  241. const auto was = int(entry.list.size());
  242. for (auto i = begin(entry.list); i != end(entry.list);) {
  243. if (i->loading || i->failed) {
  244. ++i;
  245. } else if (auto info = QFileInfo(i->path)
  246. ; !info.exists() || info.size() != i->total) {
  247. i = entry.list.erase(i);
  248. } else {
  249. ++i;
  250. }
  251. }
  252. if (int(entry.list.size()) != was) {
  253. write();
  254. }
  255. }
  256. return entry.list;
  257. }
  258. void Downloads::read() {
  259. auto bytes = _session->local().readInlineBotsDownloads();
  260. if (bytes.isEmpty()) {
  261. return;
  262. }
  263. Assert(_lists.empty());
  264. auto stream = QDataStream(&bytes, QIODevice::ReadOnly);
  265. stream.setVersion(QDataStream::Qt_5_1);
  266. quint32 version = 0, count = 0;
  267. stream >> version;
  268. if (version != kDownloadsVersion) {
  269. return;
  270. }
  271. stream >> count;
  272. if (!count || count > kMaxDownloadsBots) {
  273. return;
  274. }
  275. auto lists = base::flat_map<PeerId, List>();
  276. for (auto i = 0; i != count; ++i) {
  277. quint64 rawBotId = 0;
  278. quint32 count = 0;
  279. stream >> rawBotId >> count;
  280. const auto botId = DeserializePeerId(rawBotId);
  281. if (!botId
  282. || !peerIsUser(botId)
  283. || count > kMaxDownloadsPerBot
  284. || lists.contains(botId)) {
  285. return;
  286. }
  287. auto &list = lists[botId];
  288. list.list.reserve(count);
  289. for (auto j = 0; j != count; ++j) {
  290. auto entry = DownloadsEntry();
  291. auto size = int64();
  292. stream >> entry.url >> entry.path >> size;
  293. entry.total = entry.ready = size;
  294. entry.id = ++_autoIncrementId;
  295. list.list.push_back(std::move(entry));
  296. }
  297. }
  298. _lists = std::move(lists);
  299. }
  300. void Downloads::write() {
  301. auto size = sizeof(quint32) // version
  302. + sizeof(quint32); // lists count
  303. for (const auto &[botId, list] : _lists) {
  304. size += sizeof(quint64) // botId
  305. + sizeof(quint32); // list count
  306. for (const auto &entry : list.list) {
  307. if (entry.total > 0 && entry.ready == entry.total) {
  308. size += Serialize::stringSize(entry.url)
  309. + Serialize::stringSize(entry.path)
  310. + sizeof(quint64); // size
  311. }
  312. }
  313. }
  314. auto bytes = QByteArray();
  315. bytes.reserve(size);
  316. auto buffer = QBuffer(&bytes);
  317. buffer.open(QIODevice::WriteOnly);
  318. auto stream = QDataStream(&buffer);
  319. stream.setVersion(QDataStream::Qt_5_1);
  320. stream << quint32(kDownloadsVersion) << quint32(_lists.size());
  321. for (const auto &[botId, list] : _lists) {
  322. stream << SerializePeerId(botId) << quint32(list.list.size());
  323. for (const auto &entry : list.list) {
  324. if (entry.total > 0 && entry.ready == entry.total) {
  325. stream << entry.url << entry.path << entry.total;
  326. }
  327. }
  328. }
  329. buffer.close();
  330. _session->local().writeInlineBotsDownloads(bytes);
  331. }
  332. void DownloadFileBox(not_null<Ui::GenericBox*> box, DownloadBoxArgs args) {
  333. Expects(!args.name.isEmpty());
  334. box->setTitle(tr::lng_bot_download_file());
  335. box->addRow(object_ptr<Ui::FlatLabel>(
  336. box,
  337. tr::lng_bot_download_file_sure(
  338. lt_bot,
  339. rpl::single(Ui::Text::Bold(args.bot)),
  340. Ui::Text::RichLangValue),
  341. st::botDownloadLabel));
  342. //box->addRow(MakeFilePreview(box, args));
  343. const auto done = std::move(args.done);
  344. const auto name = args.name;
  345. const auto session = args.session;
  346. const auto chosen = std::make_shared<bool>();
  347. box->addButton(tr::lng_bot_download_file_button(), [=] {
  348. const auto path = FileNameForSave(
  349. session,
  350. tr::lng_save_file(tr::now),
  351. QString(),
  352. u"file"_q,
  353. name,
  354. false,
  355. QDir());
  356. if (!path.isEmpty()) {
  357. *chosen = true;
  358. box->closeBox();
  359. done(path);
  360. }
  361. });
  362. box->addButton(tr::lng_cancel(), [=] {
  363. box->closeBox();
  364. });
  365. box->boxClosing() | rpl::start_with_next([=] {
  366. if (!*chosen) {
  367. done(QString());
  368. }
  369. }, box->lifetime());
  370. }
  371. } // namespace InlineBots