iv_instance.cpp 33 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259
  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 "iv/iv_instance.h"
  8. #include "apiwrap.h"
  9. #include "base/platform/base_platform_info.h"
  10. #include "base/qt_signal_producer.h"
  11. #include "boxes/share_box.h"
  12. #include "core/application.h"
  13. #include "core/file_utilities.h"
  14. #include "core/shortcuts.h"
  15. #include "core/click_handler_types.h"
  16. #include "data/data_changes.h"
  17. #include "data/data_channel.h"
  18. #include "data/data_cloud_file.h"
  19. #include "data/data_document.h"
  20. #include "data/data_document_media.h"
  21. #include "data/data_file_origin.h"
  22. #include "data/data_photo_media.h"
  23. #include "data/data_session.h"
  24. #include "data/data_thread.h"
  25. #include "data/data_web_page.h"
  26. #include "data/data_user.h"
  27. #include "history/history_item_helpers.h"
  28. #include "info/profile/info_profile_values.h"
  29. #include "iv/iv_controller.h"
  30. #include "iv/iv_data.h"
  31. #include "lang/lang_keys.h"
  32. #include "lottie/lottie_common.h" // Lottie::ReadContent.
  33. #include "main/main_account.h"
  34. #include "main/main_session.h"
  35. #include "main/session/session_show.h"
  36. #include "media/streaming/media_streaming_loader.h"
  37. #include "media/view/media_view_open_common.h"
  38. #include "storage/file_download.h"
  39. #include "storage/storage_account.h"
  40. #include "ui/boxes/confirm_box.h"
  41. #include "ui/layers/layer_widget.h"
  42. #include "ui/text/text_utilities.h"
  43. #include "ui/toast/toast.h"
  44. #include "ui/basic_click_handlers.h"
  45. #include "webview/webview_data_stream_memory.h"
  46. #include "webview/webview_interface.h"
  47. #include "window/window_controller.h"
  48. #include "window/window_session_controller.h"
  49. #include "window/window_session_controller_link_info.h"
  50. #include <QtGui/QGuiApplication>
  51. #include <QtGui/QWindow>
  52. namespace Iv {
  53. namespace {
  54. constexpr auto kGeoPointScale = 1;
  55. constexpr auto kGeoPointZoomMin = 13;
  56. constexpr auto kMaxLoadParts = 5;
  57. constexpr auto kKeepLoadingParts = 8;
  58. constexpr auto kAllowPageReloadAfter = 3 * crl::time(1000);
  59. } // namespace
  60. class Shown final : public base::has_weak_ptr {
  61. public:
  62. Shown(
  63. not_null<Delegate*> delegate,
  64. not_null<Main::Session*> session,
  65. not_null<Data*> data,
  66. QString hash);
  67. [[nodiscard]] bool showing(
  68. not_null<Main::Session*> session,
  69. not_null<Data*> data) const;
  70. [[nodiscard]] bool showingFrom(not_null<Main::Session*> session) const;
  71. [[nodiscard]] bool activeFor(not_null<Main::Session*> session) const;
  72. [[nodiscard]] bool active() const;
  73. void moveTo(not_null<Data*> data, QString hash);
  74. void update(not_null<Data*> data);
  75. void showJoinedTooltip();
  76. void minimize();
  77. [[nodiscard]] rpl::producer<Controller::Event> events() const {
  78. return _events.events();
  79. }
  80. [[nodiscard]] rpl::lifetime &lifetime() {
  81. return _lifetime;
  82. }
  83. private:
  84. struct MapPreview {
  85. std::unique_ptr<::Data::CloudFile> file;
  86. QByteArray bytes;
  87. };
  88. struct PartRequest {
  89. Webview::DataRequest request;
  90. QByteArray data;
  91. std::vector<bool> loaded;
  92. int64 offset = 0;
  93. };
  94. struct FileStream {
  95. not_null<DocumentData*> document;
  96. std::unique_ptr<Media::Streaming::Loader> loader;
  97. std::vector<PartRequest> requests;
  98. std::string mime;
  99. rpl::lifetime lifetime;
  100. };
  101. struct FileLoad {
  102. std::shared_ptr<::Data::DocumentMedia> media;
  103. std::vector<Webview::DataRequest> requests;
  104. };
  105. void prepare(not_null<Data*> data, const QString &hash);
  106. void createController();
  107. void showWindowed(Prepared result);
  108. [[nodiscard]] ShareBoxResult shareBox(ShareBoxDescriptor &&descriptor);
  109. [[nodiscard]] ::Data::FileOrigin fileOrigin(
  110. not_null<WebPageData*> page) const;
  111. void streamPhoto(QStringView idWithPageId, Webview::DataRequest request);
  112. void streamFile(QStringView idWithPageId, Webview::DataRequest request);
  113. void streamFile(FileStream &file, Webview::DataRequest request);
  114. void processPartInFile(
  115. FileStream &file,
  116. Media::Streaming::LoadedPart &&part);
  117. bool finishRequestWithPart(
  118. PartRequest &request,
  119. const Media::Streaming::LoadedPart &part);
  120. void streamMap(QString params, Webview::DataRequest request);
  121. void sendEmbed(QByteArray hash, Webview::DataRequest request);
  122. void fillChannelJoinedValues(const Prepared &result);
  123. void fillEmbeds(base::flat_map<QByteArray, QByteArray> added);
  124. void subscribeToDocuments();
  125. [[nodiscard]] QByteArray readFile(
  126. const std::shared_ptr<::Data::DocumentMedia> &media);
  127. void requestDone(
  128. Webview::DataRequest request,
  129. QByteArray bytes,
  130. std::string mime,
  131. int64 offset = 0,
  132. int64 total = 0);
  133. void requestFail(Webview::DataRequest request);
  134. const not_null<Delegate*> _delegate;
  135. const not_null<Main::Session*> _session;
  136. std::shared_ptr<Main::SessionShow> _show;
  137. QString _id;
  138. std::unique_ptr<Controller> _controller;
  139. base::flat_map<DocumentId, FileStream> _streams;
  140. base::flat_map<DocumentId, FileLoad> _files;
  141. base::flat_map<QByteArray, rpl::producer<bool>> _inChannelValues;
  142. bool _preparing = false;
  143. base::flat_map<QByteArray, QByteArray> _embeds;
  144. base::flat_map<QString, MapPreview> _maps;
  145. std::vector<QByteArray> _resources;
  146. int _resource = -1;
  147. rpl::event_stream<Controller::Event> _events;
  148. rpl::lifetime _documentLifetime;
  149. rpl::lifetime _lifetime;
  150. };
  151. class TonSite final : public base::has_weak_ptr {
  152. public:
  153. TonSite(not_null<Delegate*> delegate, QString uri);
  154. [[nodiscard]] bool active() const;
  155. void moveTo(QString uri);
  156. void minimize();
  157. [[nodiscard]] rpl::producer<Controller::Event> events() const {
  158. return _events.events();
  159. }
  160. [[nodiscard]] rpl::lifetime &lifetime() {
  161. return _lifetime;
  162. }
  163. private:
  164. void createController();
  165. void showWindowed();
  166. const not_null<Delegate*> _delegate;
  167. QString _uri;
  168. std::unique_ptr<Controller> _controller;
  169. rpl::event_stream<Controller::Event> _events;
  170. rpl::lifetime _lifetime;
  171. };
  172. Shown::Shown(
  173. not_null<Delegate*> delegate,
  174. not_null<Main::Session*> session,
  175. not_null<Data*> data,
  176. QString hash)
  177. : _delegate(delegate)
  178. , _session(session) {
  179. prepare(data, hash);
  180. }
  181. void Shown::prepare(not_null<Data*> data, const QString &hash) {
  182. const auto weak = base::make_weak(this);
  183. _preparing = true;
  184. const auto id = _id = data->id();
  185. data->prepare({}, [=](Prepared result) {
  186. result.hash = hash;
  187. crl::on_main(weak, [=, result = std::move(result)]() mutable {
  188. result.url = id;
  189. if (_id != id || !_preparing) {
  190. return;
  191. }
  192. _preparing = false;
  193. fillChannelJoinedValues(result);
  194. fillEmbeds(std::move(result.embeds));
  195. showWindowed(std::move(result));
  196. });
  197. });
  198. }
  199. void Shown::fillChannelJoinedValues(const Prepared &result) {
  200. for (const auto &id : result.channelIds) {
  201. const auto channelId = ChannelId(id.toLongLong());
  202. const auto channel = _session->data().channel(channelId);
  203. if (!channel->isLoaded() && !channel->username().isEmpty()) {
  204. channel->session().api().request(MTPcontacts_ResolveUsername(
  205. MTP_flags(0),
  206. MTP_string(channel->username()),
  207. MTP_string()
  208. )).done([=](const MTPcontacts_ResolvedPeer &result) {
  209. channel->owner().processUsers(result.data().vusers());
  210. channel->owner().processChats(result.data().vchats());
  211. }).send();
  212. }
  213. _inChannelValues[id] = Info::Profile::AmInChannelValue(channel);
  214. }
  215. }
  216. void Shown::fillEmbeds(base::flat_map<QByteArray, QByteArray> added) {
  217. if (_embeds.empty()) {
  218. _embeds = std::move(added);
  219. } else {
  220. for (auto &[k, v] : added) {
  221. _embeds[k] = std::move(v);
  222. }
  223. }
  224. }
  225. ShareBoxResult Shown::shareBox(ShareBoxDescriptor &&descriptor) {
  226. class Show final : public Ui::Show {
  227. public:
  228. Show(QPointer<QWidget> parent, Fn<Ui::LayerStackWidget*()> lookup)
  229. : _parent(parent)
  230. , _lookup(lookup) {
  231. }
  232. void showOrHideBoxOrLayer(
  233. std::variant<
  234. v::null_t,
  235. object_ptr<Ui::BoxContent>,
  236. std::unique_ptr<Ui::LayerWidget>> &&layer,
  237. Ui::LayerOptions options,
  238. anim::type animated) const override {
  239. using UniqueLayer = std::unique_ptr<Ui::LayerWidget>;
  240. using ObjectBox = object_ptr<Ui::BoxContent>;
  241. const auto stack = _lookup();
  242. if (!stack) {
  243. return;
  244. } else if (auto layerWidget = std::get_if<UniqueLayer>(&layer)) {
  245. stack->showLayer(std::move(*layerWidget), options, animated);
  246. } else if (auto box = std::get_if<ObjectBox>(&layer)) {
  247. stack->showBox(std::move(*box), options, animated);
  248. } else {
  249. stack->hideAll(animated);
  250. }
  251. }
  252. not_null<QWidget*> toastParent() const override {
  253. return _parent.data();
  254. }
  255. bool valid() const override {
  256. return _lookup() != nullptr;
  257. }
  258. operator bool() const override {
  259. return valid();
  260. }
  261. private:
  262. const QPointer<QWidget> _parent;
  263. const Fn<Ui::LayerStackWidget*()> _lookup;
  264. };
  265. const auto url = descriptor.url;
  266. const auto wrap = descriptor.parent;
  267. struct State {
  268. Ui::LayerStackWidget *stack = nullptr;
  269. rpl::event_stream<> destroyRequests;
  270. };
  271. const auto state = wrap->lifetime().make_state<State>();
  272. const auto weak = QPointer<Ui::RpWidget>(wrap);
  273. const auto lookup = crl::guard(weak, [state] { return state->stack; });
  274. const auto layer = Ui::CreateChild<Ui::LayerStackWidget>(
  275. wrap.get(),
  276. [=] { return std::make_shared<Show>(weak.data(), lookup); });
  277. state->stack = layer;
  278. const auto show = layer->showFactory()();
  279. layer->setHideByBackgroundClick(false);
  280. layer->move(0, 0);
  281. wrap->sizeValue(
  282. ) | rpl::start_with_next([=](QSize size) {
  283. layer->resize(size);
  284. }, layer->lifetime());
  285. layer->hideFinishEvents(
  286. ) | rpl::filter([=] {
  287. return !!lookup(); // Last hide finish is sent from destructor.
  288. }) | rpl::start_with_next([=] {
  289. state->destroyRequests.fire({});
  290. }, wrap->lifetime());
  291. const auto waiting = layer->lifetime().make_state<rpl::lifetime>();
  292. const auto focus = crl::guard(layer, [=] {
  293. const auto set = [=] {
  294. layer->window()->setFocus();
  295. layer->setInnerFocus();
  296. };
  297. const auto handle = layer->window()->windowHandle();
  298. if (!handle) {
  299. waiting->destroy();
  300. return;
  301. } else if (QGuiApplication::focusWindow() == handle) {
  302. waiting->destroy();
  303. set();
  304. } else {
  305. *waiting = base::qt_signal_producer(
  306. qApp,
  307. &QGuiApplication::focusWindowChanged
  308. ) | rpl::filter([=](QWindow *focused) {
  309. const auto handle = layer->window()->windowHandle();
  310. return handle && (focused == handle);
  311. }) | rpl::start_with_next([=] {
  312. waiting->destroy();
  313. set();
  314. });
  315. layer->window()->activateWindow();
  316. }
  317. });
  318. auto result = ShareBoxResult{
  319. .focus = focus,
  320. .hide = [=] { show->hideLayer(); },
  321. .destroyRequests = state->destroyRequests.events(),
  322. };
  323. FastShareLink(Main::MakeSessionShow(show, _session), url);
  324. return result;
  325. }
  326. void Shown::createController() {
  327. Expects(!_controller);
  328. const auto showShareBox = [=](ShareBoxDescriptor &&descriptor) {
  329. return shareBox(std::move(descriptor));
  330. };
  331. _controller = std::make_unique<Controller>(
  332. _delegate,
  333. std::move(showShareBox));
  334. _controller->events(
  335. ) | rpl::start_to_stream(_events, _controller->lifetime());
  336. _controller->dataRequests(
  337. ) | rpl::start_with_next([=](Webview::DataRequest request) {
  338. const auto requested = QString::fromStdString(request.id);
  339. const auto id = QStringView(requested);
  340. if (id.startsWith(u"photo/")) {
  341. streamPhoto(id.mid(6), std::move(request));
  342. } else if (id.startsWith(u"document/"_q)) {
  343. streamFile(id.mid(9), std::move(request));
  344. } else if (id.startsWith(u"map/"_q)) {
  345. streamMap(id.mid(4).toUtf8(), std::move(request));
  346. } else if (id.startsWith(u"html/"_q)) {
  347. sendEmbed(id.mid(5).toUtf8(), std::move(request));
  348. }
  349. }, _controller->lifetime());
  350. }
  351. void Shown::showWindowed(Prepared result) {
  352. if (!_controller) {
  353. createController();
  354. }
  355. _controller->show(
  356. _session->local().resolveStorageIdOther(),
  357. std::move(result),
  358. base::duplicate(_inChannelValues));
  359. }
  360. ::Data::FileOrigin Shown::fileOrigin(not_null<WebPageData*> page) const {
  361. return ::Data::FileOriginWebPage{ page->url };
  362. }
  363. void Shown::streamPhoto(
  364. QStringView idWithPageId,
  365. Webview::DataRequest request) {
  366. using namespace Data;
  367. const auto parts = idWithPageId.split('/');
  368. if (parts.size() != 2) {
  369. requestFail(std::move(request));
  370. return;
  371. }
  372. const auto photo = _session->data().photo(parts[0].toULongLong());
  373. const auto page = _session->data().webpage(parts[1].toULongLong());
  374. if (photo->isNull() || page->url.isEmpty()) {
  375. requestFail(std::move(request));
  376. return;
  377. }
  378. const auto media = photo->createMediaView();
  379. media->wanted(PhotoSize::Large, fileOrigin(page));
  380. const auto check = [=] {
  381. if (!media->loaded() && !media->owner()->failed(PhotoSize::Large)) {
  382. return false;
  383. }
  384. requestDone(
  385. request,
  386. media->imageBytes(PhotoSize::Large),
  387. "image/jpeg");
  388. return true;
  389. };
  390. if (!check()) {
  391. photo->session().downloaderTaskFinished(
  392. ) | rpl::filter(
  393. check
  394. ) | rpl::take(1) | rpl::start(_controller->lifetime());
  395. }
  396. }
  397. void Shown::streamFile(
  398. QStringView idWithPageId,
  399. Webview::DataRequest request) {
  400. using namespace Data;
  401. const auto parts = idWithPageId.split('/');
  402. if (parts.size() != 2) {
  403. requestFail(std::move(request));
  404. return;
  405. }
  406. const auto documentId = DocumentId(parts[0].toULongLong());
  407. const auto i = _streams.find(documentId);
  408. if (i != end(_streams)) {
  409. streamFile(i->second, std::move(request));
  410. return;
  411. }
  412. const auto document = _session->data().document(documentId);
  413. const auto page = _session->data().webpage(parts[1].toULongLong());
  414. if (page->url.isEmpty()) {
  415. requestFail(std::move(request));
  416. return;
  417. }
  418. auto loader = document->createStreamingLoader(fileOrigin(page), false);
  419. if (!loader) {
  420. if (document->size >= Storage::kMaxFileInMemory) {
  421. requestFail(std::move(request));
  422. } else {
  423. auto media = document->createMediaView();
  424. if (const auto content = readFile(media); !content.isEmpty()) {
  425. requestDone(
  426. std::move(request),
  427. content,
  428. document->mimeString().toStdString());
  429. } else {
  430. subscribeToDocuments();
  431. auto &file = _files[documentId];
  432. file.media = std::move(media);
  433. file.requests.push_back(std::move(request));
  434. document->forceToCache(true);
  435. document->save(fileOrigin(page), QString());
  436. }
  437. }
  438. return;
  439. }
  440. auto &file = _streams.emplace(
  441. documentId,
  442. FileStream{
  443. .document = document,
  444. .loader = std::move(loader),
  445. .mime = document->mimeString().toStdString(),
  446. }).first->second;
  447. file.loader->parts(
  448. ) | rpl::start_with_next([=](Media::Streaming::LoadedPart &&part) {
  449. const auto i = _streams.find(documentId);
  450. Assert(i != end(_streams));
  451. processPartInFile(i->second, std::move(part));
  452. }, file.lifetime);
  453. streamFile(file, std::move(request));
  454. }
  455. void Shown::streamFile(FileStream &file, Webview::DataRequest request) {
  456. constexpr auto kPart = Media::Streaming::Loader::kPartSize;
  457. const auto size = file.document->size;
  458. const auto last = int((size + kPart - 1) / kPart);
  459. const auto from = int(std::min(int64(request.offset), size) / kPart);
  460. const auto till = (request.limit > 0)
  461. ? std::min(int64(request.offset + request.limit), size)
  462. : size;
  463. const auto parts = std::min(
  464. int((till + kPart - 1) / kPart) - from,
  465. kMaxLoadParts);
  466. //auto base = IvBaseCacheKey(document);
  467. const auto length = std::min((from + parts) * kPart, size)
  468. - from * kPart;
  469. file.requests.push_back(PartRequest{
  470. .request = std::move(request),
  471. .data = QByteArray(length, 0),
  472. .loaded = std::vector<bool>(parts, false),
  473. .offset = from * kPart,
  474. });
  475. file.loader->resetPriorities();
  476. const auto load = std::min(from + kKeepLoadingParts, last) - from;
  477. for (auto i = 0; i != load; ++i) {
  478. file.loader->load((from + i) * kPart);
  479. }
  480. }
  481. void Shown::subscribeToDocuments() {
  482. if (_documentLifetime) {
  483. return;
  484. }
  485. _documentLifetime = _session->data().documentLoadProgress(
  486. ) | rpl::filter([=](not_null<DocumentData*> document) {
  487. return !document->loading();
  488. }) | rpl::start_with_next([=](not_null<DocumentData*> document) {
  489. const auto i = _files.find(document->id);
  490. if (i == end(_files)) {
  491. return;
  492. }
  493. auto requests = base::take(i->second.requests);
  494. const auto content = readFile(i->second.media);
  495. _files.erase(i);
  496. if (!content.isEmpty()) {
  497. for (auto &request : requests) {
  498. requestDone(
  499. std::move(request),
  500. content,
  501. document->mimeString().toStdString());
  502. }
  503. } else {
  504. for (auto &request : requests) {
  505. requestFail(std::move(request));
  506. }
  507. }
  508. });
  509. }
  510. QByteArray Shown::readFile(
  511. const std::shared_ptr<::Data::DocumentMedia> &media) {
  512. return Lottie::ReadContent(media->bytes(), media->owner()->filepath());
  513. }
  514. void Shown::processPartInFile(
  515. FileStream &file,
  516. Media::Streaming::LoadedPart &&part) {
  517. for (auto i = begin(file.requests); i != end(file.requests);) {
  518. if (finishRequestWithPart(*i, part)) {
  519. auto done = base::take(*i);
  520. i = file.requests.erase(i);
  521. requestDone(
  522. std::move(done.request),
  523. done.data,
  524. file.mime,
  525. done.offset,
  526. file.document->size);
  527. } else {
  528. ++i;
  529. }
  530. }
  531. }
  532. bool Shown::finishRequestWithPart(
  533. PartRequest &request,
  534. const Media::Streaming::LoadedPart &part) {
  535. const auto offset = part.offset;
  536. if (offset == Media::Streaming::LoadedPart::kFailedOffset) {
  537. request.data = QByteArray();
  538. return true;
  539. } else if (offset < request.offset
  540. || offset >= request.offset + request.data.size()) {
  541. return false;
  542. }
  543. constexpr auto kPart = Media::Streaming::Loader::kPartSize;
  544. const auto copy = std::min(
  545. int(part.bytes.size()),
  546. int(request.data.size() - (offset - request.offset)));
  547. const auto index = (offset - request.offset) / kPart;
  548. Assert(index < request.loaded.size());
  549. if (request.loaded[index]) {
  550. return false;
  551. }
  552. request.loaded[index] = true;
  553. memcpy(
  554. request.data.data() + index * kPart,
  555. part.bytes.constData(),
  556. copy);
  557. return !ranges::contains(request.loaded, false);
  558. }
  559. void Shown::streamMap(QString params, Webview::DataRequest request) {
  560. using namespace ::Data;
  561. const auto parts = params.split(u'&');
  562. if (parts.size() != 3) {
  563. requestFail(std::move(request));
  564. return;
  565. }
  566. const auto point = GeoPointFromId(parts[0].toUtf8());
  567. const auto size = parts[1].split(',');
  568. const auto zoom = parts[2].toInt();
  569. if (size.size() != 2) {
  570. requestFail(std::move(request));
  571. return;
  572. }
  573. const auto location = GeoPointLocation{
  574. .lat = point.lat,
  575. .lon = point.lon,
  576. .access = point.access,
  577. .width = size[0].toInt(),
  578. .height = size[1].toInt(),
  579. .zoom = std::max(zoom, kGeoPointZoomMin),
  580. .scale = kGeoPointScale,
  581. };
  582. const auto prepared = ImageWithLocation{
  583. .location = ImageLocation(
  584. { location },
  585. location.width,
  586. location.height)
  587. };
  588. auto &preview = _maps.emplace(params, MapPreview()).first->second;
  589. preview.file = std::make_unique<CloudFile>();
  590. UpdateCloudFile(
  591. *preview.file,
  592. prepared,
  593. _session->data().cache(),
  594. kImageCacheTag,
  595. [=](FileOrigin origin) { /* restartLoader not used here */ });
  596. const auto autoLoading = false;
  597. const auto finalCheck = [=] { return true; };
  598. const auto done = [=](QByteArray bytes) {
  599. const auto i = _maps.find(params);
  600. Assert(i != end(_maps));
  601. i->second.bytes = std::move(bytes);
  602. requestDone(request, i->second.bytes, "image/png");
  603. };
  604. LoadCloudFile(
  605. _session,
  606. *preview.file,
  607. FileOrigin(),
  608. LoadFromCloudOrLocal,
  609. autoLoading,
  610. kImageCacheTag,
  611. finalCheck,
  612. done,
  613. [=](bool) { done("failed..."); });
  614. }
  615. void Shown::sendEmbed(QByteArray hash, Webview::DataRequest request) {
  616. const auto i = _embeds.find(hash);
  617. if (i != end(_embeds)) {
  618. requestDone(std::move(request), i->second, "text/html; charset=utf-8");
  619. } else {
  620. requestFail(std::move(request));
  621. }
  622. }
  623. void Shown::requestDone(
  624. Webview::DataRequest request,
  625. QByteArray bytes,
  626. std::string mime,
  627. int64 offset,
  628. int64 total) {
  629. if (bytes.isEmpty() && mime.empty()) {
  630. requestFail(std::move(request));
  631. return;
  632. }
  633. crl::on_main([
  634. done = std::move(request.done),
  635. data = std::move(bytes),
  636. mime = std::move(mime),
  637. offset,
  638. total
  639. ] {
  640. using namespace Webview;
  641. done({
  642. .stream = std::make_unique<DataStreamFromMemory>(data, mime),
  643. .streamOffset = offset,
  644. .totalSize = total,
  645. });
  646. });
  647. }
  648. void Shown::requestFail(Webview::DataRequest request) {
  649. crl::on_main([done = std::move(request.done)] {
  650. done({});
  651. });
  652. }
  653. bool Shown::showing(
  654. not_null<Main::Session*> session,
  655. not_null<Data*> data) const {
  656. return showingFrom(session) && (_id == data->id());
  657. }
  658. bool Shown::showingFrom(not_null<Main::Session*> session) const {
  659. return (_session == session);
  660. }
  661. bool Shown::activeFor(not_null<Main::Session*> session) const {
  662. return showingFrom(session) && _controller;
  663. }
  664. bool Shown::active() const {
  665. return _controller && _controller->active();
  666. }
  667. void Shown::moveTo(not_null<Data*> data, QString hash) {
  668. prepare(data, hash);
  669. }
  670. void Shown::update(not_null<Data*> data) {
  671. const auto weak = base::make_weak(this);
  672. const auto id = data->id();
  673. data->prepare({}, [=](Prepared result) {
  674. crl::on_main(weak, [=, result = std::move(result)]() mutable {
  675. result.url = id;
  676. fillChannelJoinedValues(result);
  677. fillEmbeds(std::move(result.embeds));
  678. if (_controller) {
  679. _controller->update(std::move(result));
  680. }
  681. });
  682. });
  683. }
  684. void Shown::showJoinedTooltip() {
  685. if (_controller) {
  686. _controller->showJoinedTooltip();
  687. }
  688. }
  689. void Shown::minimize() {
  690. if (_controller) {
  691. _controller->minimize();
  692. }
  693. }
  694. TonSite::TonSite(not_null<Delegate*> delegate, QString uri)
  695. : _delegate(delegate)
  696. , _uri(uri) {
  697. showWindowed();
  698. }
  699. void TonSite::createController() {
  700. Expects(!_controller);
  701. const auto showShareBox = [=](ShareBoxDescriptor &&descriptor) {
  702. return ShareBoxResult();
  703. };
  704. _controller = std::make_unique<Controller>(
  705. _delegate,
  706. std::move(showShareBox));
  707. _controller->events(
  708. ) | rpl::start_to_stream(_events, _controller->lifetime());
  709. }
  710. void TonSite::showWindowed() {
  711. if (!_controller) {
  712. createController();
  713. }
  714. _controller->showTonSite(Storage::TonSiteStorageId(), _uri);
  715. }
  716. bool TonSite::active() const {
  717. return _controller && _controller->active();
  718. }
  719. void TonSite::moveTo(QString uri) {
  720. _controller->showTonSite({}, uri);
  721. }
  722. void TonSite::minimize() {
  723. if (_controller) {
  724. _controller->minimize();
  725. }
  726. }
  727. Instance::Instance(not_null<Delegate*> delegate) : _delegate(delegate) {
  728. }
  729. Instance::~Instance() = default;
  730. void Instance::show(
  731. not_null<Window::SessionController*> controller,
  732. not_null<Data*> data,
  733. QString hash) {
  734. _delegate->ivSetLastSourceWindow(controller->widget());
  735. show(controller->uiShow(), data, hash);
  736. }
  737. void Instance::show(
  738. std::shared_ptr<Main::SessionShow> show,
  739. not_null<Data*> data,
  740. QString hash) {
  741. this->show(&show->session(), data, hash);
  742. }
  743. void Instance::show(
  744. not_null<Main::Session*> session,
  745. not_null<Data*> data,
  746. QString hash) {
  747. if (Platform::IsMac()) {
  748. // Otherwise IV is not visible under the media viewer.
  749. Core::App().hideMediaView();
  750. }
  751. const auto guard = gsl::finally([&] {
  752. requestFull(session, data->id());
  753. });
  754. if (_shown && _shownSession == session) {
  755. _shown->moveTo(data, hash);
  756. return;
  757. }
  758. _shown = std::make_unique<Shown>(_delegate, session, data, hash);
  759. _shownSession = session;
  760. _shown->events() | rpl::start_with_next([=](Controller::Event event) {
  761. using Type = Controller::Event::Type;
  762. const auto lower = event.url.toLower();
  763. const auto urlChecked = lower.startsWith("http://")
  764. || lower.startsWith("https://");
  765. const auto tonsite = lower.startsWith("tonsite://");
  766. switch (event.type) {
  767. case Type::Close:
  768. _shown = nullptr;
  769. break;
  770. case Type::Quit:
  771. Shortcuts::Launch(Shortcuts::Command::Quit);
  772. break;
  773. case Type::OpenChannel:
  774. processOpenChannel(event.context);
  775. break;
  776. case Type::JoinChannel:
  777. processJoinChannel(event.context);
  778. break;
  779. case Type::OpenLinkExternal:
  780. if (urlChecked) {
  781. File::OpenUrl(event.url);
  782. closeAll();
  783. } else if (tonsite) {
  784. showTonSite(event.url);
  785. }
  786. break;
  787. case Type::OpenMedia:
  788. if (const auto window = Core::App().activeWindow()) {
  789. const auto current = window->sessionController();
  790. const auto controller = (current
  791. && &current->session() == _shownSession)
  792. ? current
  793. : nullptr;
  794. const auto item = (HistoryItem*)nullptr;
  795. const auto topicRootId = MsgId(0);
  796. if (event.context.startsWith("-photo")) {
  797. const auto id = event.context.mid(6).toULongLong();
  798. const auto photo = _shownSession->data().photo(id);
  799. if (!photo->isNull()) {
  800. window->openInMediaView({
  801. controller,
  802. photo,
  803. item,
  804. topicRootId
  805. });
  806. }
  807. } else if (event.context.startsWith("-video")) {
  808. const auto id = event.context.mid(6).toULongLong();
  809. const auto video = _shownSession->data().document(id);
  810. if (!video->isNull()) {
  811. window->openInMediaView({
  812. controller,
  813. video,
  814. item,
  815. topicRootId
  816. });
  817. }
  818. }
  819. }
  820. break;
  821. case Type::OpenPage:
  822. case Type::OpenLink: {
  823. if (tonsite) {
  824. showTonSite(event.url);
  825. break;
  826. } else if (!urlChecked) {
  827. break;
  828. }
  829. const auto session = _shownSession;
  830. const auto url = event.url;
  831. auto &requested = _fullRequested[session][url];
  832. requested.lastRequestedAt = crl::now();
  833. session->api().request(MTPmessages_GetWebPage(
  834. MTP_string(url),
  835. MTP_int(requested.hash)
  836. )).done([=](const MTPmessages_WebPage &result) {
  837. const auto page = processReceivedPage(session, url, result);
  838. if (page && page->iv) {
  839. const auto parts = event.url.split('#');
  840. const auto hash = (parts.size() > 1) ? parts[1] : u""_q;
  841. this->show(_shownSession, page->iv.get(), hash);
  842. } else {
  843. UrlClickHandler::Open(event.url);
  844. }
  845. }).fail([=] {
  846. UrlClickHandler::Open(event.url);
  847. }).send();
  848. } break;
  849. case Type::Report:
  850. if (const auto controller = _shownSession->tryResolveWindow()) {
  851. controller->window().activate();
  852. controller->showPeerByLink(Window::PeerByLinkInfo{
  853. .usernameOrId = "previews",
  854. .resolveType = Window::ResolveType::BotStart,
  855. .startToken = ("webpage"
  856. + QString::number(event.context.toULongLong())),
  857. });
  858. }
  859. break;
  860. }
  861. }, _shown->lifetime());
  862. session->changes().peerUpdates(
  863. ::Data::PeerUpdate::Flag::ChannelAmIn
  864. ) | rpl::start_with_next([=](const ::Data::PeerUpdate &update) {
  865. if (const auto channel = update.peer->asChannel()) {
  866. if (channel->amIn()) {
  867. const auto i = _joining.find(session);
  868. const auto value = not_null{ channel };
  869. if (i != end(_joining) && i->second.remove(value)) {
  870. _shown->showJoinedTooltip();
  871. }
  872. }
  873. }
  874. }, _shown->lifetime());
  875. trackSession(session);
  876. }
  877. void Instance::trackSession(not_null<Main::Session*> session) {
  878. if (!_tracking.emplace(session).second) {
  879. return;
  880. }
  881. session->lifetime().add([=] {
  882. _tracking.remove(session);
  883. _joining.remove(session);
  884. _fullRequested.remove(session);
  885. _ivCache.remove(session);
  886. if (_ivRequestSession == session) {
  887. session->api().request(_ivRequestId).cancel();
  888. _ivRequestSession = nullptr;
  889. _ivRequestUri = QString();
  890. _ivRequestId = 0;
  891. }
  892. if (_shownSession == session) {
  893. _shownSession = nullptr;
  894. }
  895. if (_shown && _shown->showingFrom(session)) {
  896. _shown = nullptr;
  897. }
  898. });
  899. }
  900. void Instance::openWithIvPreferred(
  901. not_null<Window::SessionController*> controller,
  902. QString uri,
  903. QVariant context) {
  904. auto my = context.value<ClickHandlerContext>();
  905. my.sessionWindow = controller;
  906. openWithIvPreferred(
  907. &controller->session(),
  908. uri,
  909. QVariant::fromValue(my));
  910. }
  911. void Instance::openWithIvPreferred(
  912. not_null<Main::Session*> session,
  913. QString uri,
  914. QVariant context) {
  915. const auto openExternal = [=] {
  916. auto my = context.value<ClickHandlerContext>();
  917. my.ignoreIv = true;
  918. UrlClickHandler::Open(uri, QVariant::fromValue(my));
  919. };
  920. const auto parts = uri.split('#');
  921. if (parts.isEmpty() || parts[0].isEmpty()) {
  922. return;
  923. } else if (!ShowButton()) {
  924. return openExternal();
  925. }
  926. trackSession(session);
  927. const auto hash = (parts.size() > 1) ? parts[1] : u""_q;
  928. const auto url = parts[0];
  929. auto &cache = _ivCache[session];
  930. if (const auto i = cache.find(url); i != end(cache)) {
  931. const auto page = i->second;
  932. if (page && page->iv) {
  933. auto my = context.value<ClickHandlerContext>();
  934. if (const auto window = my.sessionWindow.get()) {
  935. show(window, page->iv.get(), hash);
  936. } else {
  937. show(session, page->iv.get(), hash);
  938. }
  939. } else {
  940. openExternal();
  941. }
  942. return;
  943. } else if (_ivRequestSession == session.get() && _ivRequestUri == uri) {
  944. return;
  945. } else if (_ivRequestId) {
  946. _ivRequestSession->api().request(_ivRequestId).cancel();
  947. }
  948. const auto finish = [=](WebPageData *page) {
  949. Expects(_ivRequestSession == session);
  950. _ivRequestId = 0;
  951. _ivRequestUri = QString();
  952. _ivRequestSession = nullptr;
  953. _ivCache[session][url] = page;
  954. openWithIvPreferred(session, uri, context);
  955. };
  956. _ivRequestSession = session;
  957. _ivRequestUri = uri;
  958. auto &requested = _fullRequested[session][url];
  959. requested.lastRequestedAt = crl::now();
  960. _ivRequestId = session->api().request(MTPmessages_GetWebPage(
  961. MTP_string(url),
  962. MTP_int(requested.hash)
  963. )).done([=](const MTPmessages_WebPage &result) {
  964. finish(processReceivedPage(session, url, result));
  965. }).fail([=] {
  966. finish(nullptr);
  967. }).send();
  968. }
  969. void Instance::showTonSite(
  970. const QString &uri,
  971. QVariant context) {
  972. if (!Controller::IsGoodTonSiteUrl(uri)) {
  973. Ui::Toast::Show(tr::lng_iv_not_supported(tr::now));
  974. return;
  975. } else if (Platform::IsMac()) {
  976. // Otherwise IV is not visible under the media viewer.
  977. Core::App().hideMediaView();
  978. }
  979. if (_tonSite) {
  980. _tonSite->moveTo(uri);
  981. return;
  982. }
  983. _tonSite = std::make_unique<TonSite>(_delegate, uri);
  984. _tonSite->events() | rpl::start_with_next([=](Controller::Event event) {
  985. using Type = Controller::Event::Type;
  986. const auto lower = event.url.toLower();
  987. const auto urlChecked = lower.startsWith("http://")
  988. || lower.startsWith("https://");
  989. const auto tonsite = lower.startsWith("tonsite://");
  990. switch (event.type) {
  991. case Type::Close:
  992. _tonSite = nullptr;
  993. break;
  994. case Type::Quit:
  995. Shortcuts::Launch(Shortcuts::Command::Quit);
  996. break;
  997. case Type::OpenLinkExternal:
  998. if (urlChecked) {
  999. File::OpenUrl(event.url);
  1000. closeAll();
  1001. } else if (tonsite) {
  1002. showTonSite(event.url);
  1003. }
  1004. break;
  1005. case Type::OpenPage:
  1006. case Type::OpenLink:
  1007. if (urlChecked) {
  1008. UrlClickHandler::Open(event.url);
  1009. } else if (tonsite) {
  1010. showTonSite(event.url);
  1011. }
  1012. break;
  1013. }
  1014. }, _tonSite->lifetime());
  1015. }
  1016. void Instance::requestFull(
  1017. not_null<Main::Session*> session,
  1018. const QString &id) {
  1019. if (!_tracking.contains(session)) {
  1020. return;
  1021. }
  1022. auto &requested = _fullRequested[session][id];
  1023. const auto last = requested.lastRequestedAt;
  1024. const auto now = crl::now();
  1025. if (last && (now - last) < kAllowPageReloadAfter) {
  1026. return;
  1027. }
  1028. requested.lastRequestedAt = now;
  1029. session->api().request(MTPmessages_GetWebPage(
  1030. MTP_string(id),
  1031. MTP_int(requested.hash)
  1032. )).done([=](const MTPmessages_WebPage &result) {
  1033. const auto page = processReceivedPage(session, id, result);
  1034. if (page && page->iv && _shown && _shownSession == session) {
  1035. _shown->update(page->iv.get());
  1036. }
  1037. }).send();
  1038. }
  1039. WebPageData *Instance::processReceivedPage(
  1040. not_null<Main::Session*> session,
  1041. const QString &url,
  1042. const MTPmessages_WebPage &result) {
  1043. const auto &data = result.data();
  1044. const auto owner = &session->data();
  1045. owner->processUsers(data.vusers());
  1046. owner->processChats(data.vchats());
  1047. auto &requested = _fullRequested[session][url];
  1048. const auto &mtp = data.vwebpage();
  1049. mtp.match([&](const MTPDwebPageNotModified &data) {
  1050. const auto page = requested.page;
  1051. if (const auto views = data.vcached_page_views()) {
  1052. if (page && page->iv) {
  1053. page->iv->updateCachedViews(views->v);
  1054. }
  1055. }
  1056. }, [&](const MTPDwebPage &data) {
  1057. requested.hash = data.vhash().v;
  1058. requested.page = owner->processWebpage(data).get();
  1059. }, [&](const auto &) {
  1060. requested.page = owner->processWebpage(mtp).get();
  1061. });
  1062. return requested.page;
  1063. }
  1064. void Instance::processOpenChannel(const QString &context) {
  1065. if (!_shownSession) {
  1066. return;
  1067. } else if (const auto channelId = ChannelId(context.toLongLong())) {
  1068. const auto channel = _shownSession->data().channel(channelId);
  1069. if (channel->isLoaded()) {
  1070. if (const auto controller = _shownSession->tryResolveWindow(channel)) {
  1071. controller->showPeerHistory(channel);
  1072. _shown = nullptr;
  1073. }
  1074. } else if (!channel->username().isEmpty()) {
  1075. if (const auto controller = _shownSession->tryResolveWindow(channel)) {
  1076. controller->showPeerByLink({
  1077. .usernameOrId = channel->username(),
  1078. });
  1079. _shown = nullptr;
  1080. }
  1081. }
  1082. }
  1083. }
  1084. void Instance::processJoinChannel(const QString &context) {
  1085. if (!_shownSession) {
  1086. return;
  1087. } else if (const auto channelId = ChannelId(context.toLongLong())) {
  1088. const auto channel = _shownSession->data().channel(channelId);
  1089. _joining[_shownSession].emplace(channel);
  1090. if (channel->isLoaded()) {
  1091. _shownSession->api().joinChannel(channel);
  1092. } else if (!channel->username().isEmpty()) {
  1093. if (const auto controller = _shownSession->tryResolveWindow(channel)) {
  1094. controller->showPeerByLink({
  1095. .usernameOrId = channel->username(),
  1096. .joinChannel = true,
  1097. });
  1098. }
  1099. }
  1100. }
  1101. }
  1102. bool Instance::hasActiveWindow(not_null<Main::Session*> session) const {
  1103. return _shown && _shown->activeFor(session);
  1104. }
  1105. bool Instance::closeActive() {
  1106. if (_shown && _shown->active()) {
  1107. _shown = nullptr;
  1108. return true;
  1109. } else if (_tonSite && _tonSite->active()) {
  1110. _tonSite = nullptr;
  1111. return true;
  1112. }
  1113. return false;
  1114. }
  1115. bool Instance::minimizeActive() {
  1116. if (_shown && _shown->active()) {
  1117. _shown->minimize();
  1118. return true;
  1119. } else if (_tonSite && _tonSite->active()) {
  1120. _tonSite->minimize();
  1121. return true;
  1122. }
  1123. return false;
  1124. }
  1125. void Instance::closeAll() {
  1126. _shown = nullptr;
  1127. _tonSite = nullptr;
  1128. }
  1129. bool PreferForUri(const QString &uri) {
  1130. const auto url = QUrl(uri);
  1131. const auto host = url.host().toLower();
  1132. const auto path = url.path().toLower();
  1133. return (host == u"telegra.ph"_q)
  1134. || (host == u"te.legra.ph"_q)
  1135. || (host == u"graph.org"_q)
  1136. || (host == u"telegram.org"_q
  1137. && (path.startsWith(u"/faq"_q)
  1138. || path.startsWith(u"/privacy"_q)
  1139. || path.startsWith(u"/blog"_q)));
  1140. }
  1141. } // namespace Iv