1 /*
2 This file is part of Telegram Desktop,
3 the official desktop application for the Telegram messaging service.
4
5 For license and copyright information please follow this link:
6 https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
7 */
8 #include "export/export_api_wrap.h"
9
10 #include "export/export_settings.h"
11 #include "export/data/export_data_types.h"
12 #include "export/output/export_output_result.h"
13 #include "export/output/export_output_file.h"
14 #include "mtproto/mtproto_response.h"
15 #include "base/value_ordering.h"
16 #include "base/bytes.h"
17 #include "base/random.h"
18 #include <set>
19 #include <deque>
20
21 namespace Export {
22 namespace {
23
24 constexpr auto kUserpicsSliceLimit = 100;
25 constexpr auto kFileChunkSize = 128 * 1024;
26 constexpr auto kFileRequestsCount = 2;
27 //constexpr auto kFileNextRequestDelay = crl::time(20);
28 constexpr auto kChatsSliceLimit = 100;
29 constexpr auto kMessagesSliceLimit = 100;
30 constexpr auto kTopPeerSliceLimit = 100;
31 constexpr auto kFileMaxSize = 2000 * 1024 * 1024;
32 constexpr auto kLocationCacheSize = 100'000;
33
34 struct LocationKey {
35 uint64 type;
36 uint64 id;
37
operator <Export::__anon7c2d49d30111::LocationKey38 inline bool operator<(const LocationKey &other) const {
39 return std::tie(type, id) < std::tie(other.type, other.id);
40 }
41 };
42
ComputeLocationKey(const Data::FileLocation & value)43 LocationKey ComputeLocationKey(const Data::FileLocation &value) {
44 auto result = LocationKey();
45 result.type = value.dcId;
46 value.data.match([&](const MTPDinputDocumentFileLocation &data) {
47 const auto letter = data.vthumb_size().v.isEmpty()
48 ? char(0)
49 : data.vthumb_size().v[0];
50 result.type |= (2ULL << 24);
51 result.type |= (uint64(uint32(letter)) << 16);
52 result.id = data.vid().v;
53 }, [&](const MTPDinputPhotoFileLocation &data) {
54 const auto letter = data.vthumb_size().v.isEmpty()
55 ? char(0)
56 : data.vthumb_size().v[0];
57 result.type |= (6ULL << 24);
58 result.type |= (uint64(uint32(letter)) << 16);
59 result.id = data.vid().v;
60 }, [&](const MTPDinputTakeoutFileLocation &data) {
61 result.type |= (5ULL << 24);
62 }, [](const auto &data) {
63 Unexpected("File location type in Export::ComputeLocationKey.");
64 });
65 return result;
66 }
67
SettingsFromDialogsType(Data::DialogInfo::Type type)68 Settings::Type SettingsFromDialogsType(Data::DialogInfo::Type type) {
69 using DialogType = Data::DialogInfo::Type;
70 switch (type) {
71 case DialogType::Self:
72 case DialogType::Personal:
73 return Settings::Type::PersonalChats;
74 case DialogType::Bot:
75 return Settings::Type::BotChats;
76 case DialogType::PrivateGroup:
77 case DialogType::PrivateSupergroup:
78 return Settings::Type::PrivateGroups;
79 case DialogType::PublicSupergroup:
80 return Settings::Type::PublicGroups;
81 case DialogType::PrivateChannel:
82 return Settings::Type::PrivateChannels;
83 case DialogType::PublicChannel:
84 return Settings::Type::PublicChannels;
85 }
86 return Settings::Type(0);
87 }
88
89 } // namespace
90
91 class ApiWrap::LoadedFileCache {
92 public:
93 using Location = Data::FileLocation;
94
95 LoadedFileCache(int limit);
96
97 void save(const Location &location, const QString &relativePath);
98 std::optional<QString> find(const Location &location) const;
99
100 private:
101 int _limit = 0;
102 std::map<LocationKey, QString> _map;
103 std::deque<LocationKey> _list;
104
105 };
106
107 struct ApiWrap::StartProcess {
108 FnMut<void(StartInfo)> done;
109
110 enum class Step {
111 UserpicsCount,
112 SplitRanges,
113 DialogsCount,
114 LeftChannelsCount,
115 };
116 std::deque<Step> steps;
117 int splitIndex = 0;
118 StartInfo info;
119 };
120
121 struct ApiWrap::ContactsProcess {
122 FnMut<void(Data::ContactsList&&)> done;
123
124 Data::ContactsList result;
125
126 int topPeersOffset = 0;
127 };
128
129 struct ApiWrap::UserpicsProcess {
130 FnMut<bool(Data::UserpicsInfo&&)> start;
131 Fn<bool(DownloadProgress)> fileProgress;
132 Fn<bool(Data::UserpicsSlice&&)> handleSlice;
133 FnMut<void()> finish;
134
135 int processed = 0;
136 std::optional<Data::UserpicsSlice> slice;
137 uint64 maxId = 0;
138 bool lastSlice = false;
139 int fileIndex = 0;
140 };
141
142 struct ApiWrap::OtherDataProcess {
143 Data::File file;
144 FnMut<void(Data::File&&)> done;
145 };
146
147 struct ApiWrap::FileProcess {
148 FileProcess(const QString &path, Output::Stats *stats);
149
150 Output::File file;
151 QString relativePath;
152
153 Fn<bool(FileProgress)> progress;
154 FnMut<void(const QString &relativePath)> done;
155
156 uint64 randomId = 0;
157 Data::FileLocation location;
158 Data::FileOrigin origin;
159 int offset = 0;
160 int size = 0;
161
162 struct Request {
163 int offset = 0;
164 QByteArray bytes;
165 };
166 std::deque<Request> requests;
167 mtpRequestId requestId = 0;
168 };
169
170 struct ApiWrap::FileProgress {
171 int ready = 0;
172 int total = 0;
173 };
174
175 struct ApiWrap::ChatsProcess {
176 Fn<bool(int count)> progress;
177 FnMut<void(Data::DialogsInfo&&)> done;
178
179 Data::DialogsInfo info;
180 int processedCount = 0;
181 std::map<PeerId, int> indexByPeer;
182 };
183
184 struct ApiWrap::LeftChannelsProcess : ChatsProcess {
185 int fullCount = 0;
186 int offset = 0;
187 bool finished = false;
188 };
189
190 struct ApiWrap::DialogsProcess : ChatsProcess {
191 int splitIndexPlusOne = 0;
192 TimeId offsetDate = 0;
193 int32 offsetId = 0;
194 MTPInputPeer offsetPeer = MTP_inputPeerEmpty();
195 };
196
197 struct ApiWrap::ChatProcess {
198 Data::DialogInfo info;
199
200 FnMut<bool(const Data::DialogInfo &)> start;
201 Fn<bool(DownloadProgress)> fileProgress;
202 Fn<bool(Data::MessagesSlice&&)> handleSlice;
203 FnMut<void()> done;
204
205 FnMut<void(MTPmessages_Messages&&)> requestDone;
206
207 int localSplitIndex = 0;
208 int32 largestIdPlusOne = 1;
209
210 Data::ParseMediaContext context;
211 std::optional<Data::MessagesSlice> slice;
212 bool lastSlice = false;
213 int fileIndex = 0;
214 };
215
216
217 template <typename Request>
218 class ApiWrap::RequestBuilder {
219 public:
220 using Original = MTP::ConcurrentSender::SpecificRequestBuilder<Request>;
221 using Response = typename Request::ResponseType;
222
223 RequestBuilder(
224 Original &&builder,
225 Fn<void(const MTP::Error&)> commonFailHandler);
226
227 [[nodiscard]] RequestBuilder &done(FnMut<void()> &&handler);
228 [[nodiscard]] RequestBuilder &done(
229 FnMut<void(Response &&)> &&handler);
230 [[nodiscard]] RequestBuilder &fail(
231 Fn<bool(const MTP::Error&)> &&handler);
232
233 mtpRequestId send();
234
235 private:
236 Original _builder;
237 Fn<void(const MTP::Error&)> _commonFailHandler;
238
239 };
240
241 template <typename Request>
RequestBuilder(Original && builder,Fn<void (const MTP::Error &)> commonFailHandler)242 ApiWrap::RequestBuilder<Request>::RequestBuilder(
243 Original &&builder,
244 Fn<void(const MTP::Error&)> commonFailHandler)
245 : _builder(std::move(builder))
246 , _commonFailHandler(std::move(commonFailHandler)) {
247 }
248
249 template <typename Request>
done(FnMut<void ()> && handler)250 auto ApiWrap::RequestBuilder<Request>::done(
251 FnMut<void()> &&handler
252 ) -> RequestBuilder& {
253 if (handler) {
254 [[maybe_unused]] auto &silence_warning = _builder.done(std::move(handler));
255 }
256 return *this;
257 }
258
259 template <typename Request>
done(FnMut<void (Response &&)> && handler)260 auto ApiWrap::RequestBuilder<Request>::done(
261 FnMut<void(Response &&)> &&handler
262 ) -> RequestBuilder& {
263 if (handler) {
264 [[maybe_unused]] auto &silence_warning = _builder.done(std::move(handler));
265 }
266 return *this;
267 }
268
269 template <typename Request>
fail(Fn<bool (const MTP::Error &)> && handler)270 auto ApiWrap::RequestBuilder<Request>::fail(
271 Fn<bool(const MTP::Error &)> &&handler
272 ) -> RequestBuilder& {
273 if (handler) {
274 [[maybe_unused]] auto &silence_warning = _builder.fail([
275 common = base::take(_commonFailHandler),
276 specific = std::move(handler)
277 ](const MTP::Error &error) {
278 if (!specific(error)) {
279 common(error);
280 }
281 });
282 }
283 return *this;
284 }
285
286 template <typename Request>
send()287 mtpRequestId ApiWrap::RequestBuilder<Request>::send() {
288 return _commonFailHandler
289 ? _builder.fail(base::take(_commonFailHandler)).send()
290 : _builder.send();
291 }
292
LoadedFileCache(int limit)293 ApiWrap::LoadedFileCache::LoadedFileCache(int limit) : _limit(limit) {
294 Expects(limit >= 0);
295 }
296
save(const Location & location,const QString & relativePath)297 void ApiWrap::LoadedFileCache::save(
298 const Location &location,
299 const QString &relativePath) {
300 if (!location) {
301 return;
302 }
303 const auto key = ComputeLocationKey(location);
304 _map[key] = relativePath;
305 _list.push_back(key);
306 if (_list.size() > _limit) {
307 const auto key = _list.front();
308 _list.pop_front();
309 _map.erase(key);
310 }
311 }
312
find(const Location & location) const313 std::optional<QString> ApiWrap::LoadedFileCache::find(
314 const Location &location) const {
315 if (!location) {
316 return std::nullopt;
317 }
318 const auto key = ComputeLocationKey(location);
319 if (const auto i = _map.find(key); i != end(_map)) {
320 return i->second;
321 }
322 return std::nullopt;
323 }
324
FileProcess(const QString & path,Output::Stats * stats)325 ApiWrap::FileProcess::FileProcess(const QString &path, Output::Stats *stats)
326 : file(path, stats) {
327 }
328
329 template <typename Request>
mainRequest(Request && request)330 auto ApiWrap::mainRequest(Request &&request) {
331 Expects(_takeoutId.has_value());
332
333 auto original = std::move(_mtp.request(MTPInvokeWithTakeout<Request>(
334 MTP_long(*_takeoutId),
335 std::forward<Request>(request)
336 )).toDC(MTP::ShiftDcId(0, MTP::kExportDcShift)));
337
338 return RequestBuilder<MTPInvokeWithTakeout<Request>>(
339 std::move(original),
340 [=](const MTP::Error &result) { error(result); });
341 }
342
343 template <typename Request>
splitRequest(int index,Request && request)344 auto ApiWrap::splitRequest(int index, Request &&request) {
345 Expects(index < _splits.size());
346
347 //if (index == _splits.size() - 1) {
348 // return mainRequest(std::forward<Request>(request));
349 //}
350 return mainRequest(MTPInvokeWithMessagesRange<Request>(
351 _splits[index],
352 std::forward<Request>(request)));
353 }
354
fileRequest(const Data::FileLocation & location,int offset)355 auto ApiWrap::fileRequest(const Data::FileLocation &location, int offset) {
356 Expects(location.dcId != 0
357 || location.data.type() == mtpc_inputTakeoutFileLocation);
358 Expects(_takeoutId.has_value());
359 Expects(_fileProcess->requestId == 0);
360
361 return std::move(_mtp.request(MTPInvokeWithTakeout<MTPupload_GetFile>(
362 MTP_long(*_takeoutId),
363 MTPupload_GetFile(
364 MTP_flags(0),
365 location.data,
366 MTP_int(offset),
367 MTP_int(kFileChunkSize))
368 )).fail([=](const MTP::Error &result) {
369 _fileProcess->requestId = 0;
370 if (result.type() == qstr("TAKEOUT_FILE_EMPTY")
371 && _otherDataProcess != nullptr) {
372 filePartDone(
373 0,
374 MTP_upload_file(
375 MTP_storage_filePartial(),
376 MTP_int(0),
377 MTP_bytes()));
378 } else if (result.type() == qstr("LOCATION_INVALID")
379 || result.type() == qstr("VERSION_INVALID")
380 || result.type() == qstr("LOCATION_NOT_AVAILABLE")) {
381 filePartUnavailable();
382 } else if (result.code() == 400
383 && result.type().startsWith(qstr("FILE_REFERENCE_"))) {
384 filePartRefreshReference(offset);
385 } else {
386 error(std::move(result));
387 }
388 }).toDC(MTP::ShiftDcId(location.dcId, MTP::kExportMediaDcShift)));
389 }
390
ApiWrap(QPointer<MTP::Instance> weak,Fn<void (FnMut<void ()>)> runner)391 ApiWrap::ApiWrap(QPointer<MTP::Instance> weak, Fn<void(FnMut<void()>)> runner)
392 : _mtp(weak, std::move(runner))
393 , _fileCache(std::make_unique<LoadedFileCache>(kLocationCacheSize)) {
394 }
395
errors() const396 rpl::producer<MTP::Error> ApiWrap::errors() const {
397 return _errors.events();
398 }
399
ioErrors() const400 rpl::producer<Output::Result> ApiWrap::ioErrors() const {
401 return _ioErrors.events();
402 }
403
startExport(const Settings & settings,Output::Stats * stats,FnMut<void (StartInfo)> done)404 void ApiWrap::startExport(
405 const Settings &settings,
406 Output::Stats *stats,
407 FnMut<void(StartInfo)> done) {
408 Expects(_settings == nullptr);
409 Expects(_startProcess == nullptr);
410
411 _settings = std::make_unique<Settings>(settings);
412 _stats = stats;
413 _startProcess = std::make_unique<StartProcess>();
414 _startProcess->done = std::move(done);
415
416 using Step = StartProcess::Step;
417 if (_settings->types & Settings::Type::Userpics) {
418 _startProcess->steps.push_back(Step::UserpicsCount);
419 }
420 if (_settings->types & Settings::Type::AnyChatsMask) {
421 _startProcess->steps.push_back(Step::SplitRanges);
422 }
423 if (_settings->types & Settings::Type::AnyChatsMask) {
424 _startProcess->steps.push_back(Step::DialogsCount);
425 }
426 if (_settings->types & Settings::Type::GroupsChannelsMask) {
427 if (!_settings->onlySinglePeer()) {
428 _startProcess->steps.push_back(Step::LeftChannelsCount);
429 }
430 }
431 startMainSession([=] {
432 sendNextStartRequest();
433 });
434 }
435
sendNextStartRequest()436 void ApiWrap::sendNextStartRequest() {
437 Expects(_startProcess != nullptr);
438
439 auto &steps = _startProcess->steps;
440 if (steps.empty()) {
441 finishStartProcess();
442 return;
443 }
444 using Step = StartProcess::Step;
445 const auto step = steps.front();
446 steps.pop_front();
447 switch (step) {
448 case Step::UserpicsCount:
449 return requestUserpicsCount();
450 case Step::SplitRanges:
451 return requestSplitRanges();
452 case Step::DialogsCount:
453 return requestDialogsCount();
454 case Step::LeftChannelsCount:
455 return requestLeftChannelsCount();
456 }
457 Unexpected("Step in ApiWrap::sendNextStartRequest.");
458 }
459
requestUserpicsCount()460 void ApiWrap::requestUserpicsCount() {
461 Expects(_startProcess != nullptr);
462
463 mainRequest(MTPphotos_GetUserPhotos(
464 _user,
465 MTP_int(0), // offset
466 MTP_long(0), // max_id
467 MTP_int(0) // limit
468 )).done([=](const MTPphotos_Photos &result) {
469 Expects(_settings != nullptr);
470 Expects(_startProcess != nullptr);
471
472 _startProcess->info.userpicsCount = result.match(
473 [](const MTPDphotos_photos &data) {
474 return int(data.vphotos().v.size());
475 }, [](const MTPDphotos_photosSlice &data) {
476 return data.vcount().v;
477 });
478
479 sendNextStartRequest();
480 }).send();
481 }
482
requestSplitRanges()483 void ApiWrap::requestSplitRanges() {
484 Expects(_startProcess != nullptr);
485
486 mainRequest(MTPmessages_GetSplitRanges(
487 )).done([=](const MTPVector<MTPMessageRange> &result) {
488 _splits = result.v;
489 if (_splits.empty()) {
490 _splits.push_back(MTP_messageRange(
491 MTP_int(1),
492 MTP_int(std::numeric_limits<int>::max())));
493 }
494 _startProcess->splitIndex = useOnlyLastSplit()
495 ? (_splits.size() - 1)
496 : 0;
497
498 sendNextStartRequest();
499 }).send();
500 }
501
requestDialogsCount()502 void ApiWrap::requestDialogsCount() {
503 Expects(_startProcess != nullptr);
504
505 if (_settings->onlySinglePeer()) {
506 _startProcess->info.dialogsCount =
507 (_settings->singlePeer.type() == mtpc_inputPeerChannel
508 ? 1
509 : _splits.size());
510 sendNextStartRequest();
511 return;
512 }
513
514 const auto offsetDate = 0;
515 const auto offsetId = 0;
516 const auto offsetPeer = MTP_inputPeerEmpty();
517 const auto limit = 1;
518 const auto hash = uint64(0);
519 splitRequest(_startProcess->splitIndex, MTPmessages_GetDialogs(
520 MTP_flags(0),
521 MTPint(), // folder_id
522 MTP_int(offsetDate),
523 MTP_int(offsetId),
524 offsetPeer,
525 MTP_int(limit),
526 MTP_long(hash)
527 )).done([=](const MTPmessages_Dialogs &result) {
528 Expects(_settings != nullptr);
529 Expects(_startProcess != nullptr);
530
531 const auto count = result.match(
532 [](const MTPDmessages_dialogs &data) {
533 return int(data.vdialogs().v.size());
534 }, [](const MTPDmessages_dialogsSlice &data) {
535 return data.vcount().v;
536 }, [](const MTPDmessages_dialogsNotModified &data) {
537 return -1;
538 });
539 if (count < 0) {
540 error("Unexpected dialogsNotModified received.");
541 return;
542 }
543 _startProcess->info.dialogsCount += count;
544
545 if (++_startProcess->splitIndex >= _splits.size()) {
546 sendNextStartRequest();
547 } else {
548 requestDialogsCount();
549 }
550 }).send();
551 }
552
requestLeftChannelsCount()553 void ApiWrap::requestLeftChannelsCount() {
554 Expects(_startProcess != nullptr);
555 Expects(_leftChannelsProcess == nullptr);
556
557 _leftChannelsProcess = std::make_unique<LeftChannelsProcess>();
558 requestLeftChannelsSliceGeneric([=] {
559 Expects(_startProcess != nullptr);
560 Expects(_leftChannelsProcess != nullptr);
561
562 _startProcess->info.dialogsCount
563 += _leftChannelsProcess->fullCount;
564 sendNextStartRequest();
565 });
566 }
567
finishStartProcess()568 void ApiWrap::finishStartProcess() {
569 Expects(_startProcess != nullptr);
570
571 const auto process = base::take(_startProcess);
572 process->done(process->info);
573 }
574
useOnlyLastSplit() const575 bool ApiWrap::useOnlyLastSplit() const {
576 return !(_settings->types & Settings::Type::NonChannelChatsMask);
577 }
578
requestLeftChannelsList(Fn<bool (int count)> progress,FnMut<void (Data::DialogsInfo &&)> done)579 void ApiWrap::requestLeftChannelsList(
580 Fn<bool(int count)> progress,
581 FnMut<void(Data::DialogsInfo&&)> done) {
582 Expects(_leftChannelsProcess != nullptr);
583
584 _leftChannelsProcess->progress = std::move(progress);
585 _leftChannelsProcess->done = std::move(done);
586 requestLeftChannelsSlice();
587 }
588
requestLeftChannelsSlice()589 void ApiWrap::requestLeftChannelsSlice() {
590 requestLeftChannelsSliceGeneric([=] {
591 Expects(_leftChannelsProcess != nullptr);
592
593 if (_leftChannelsProcess->finished) {
594 const auto process = base::take(_leftChannelsProcess);
595 process->done(std::move(process->info));
596 } else {
597 requestLeftChannelsSlice();
598 }
599 });
600 }
601
requestDialogsList(Fn<bool (int count)> progress,FnMut<void (Data::DialogsInfo &&)> done)602 void ApiWrap::requestDialogsList(
603 Fn<bool(int count)> progress,
604 FnMut<void(Data::DialogsInfo&&)> done) {
605 Expects(_dialogsProcess == nullptr);
606
607 _dialogsProcess = std::make_unique<DialogsProcess>();
608 _dialogsProcess->splitIndexPlusOne = _splits.size();
609 _dialogsProcess->progress = std::move(progress);
610 _dialogsProcess->done = std::move(done);
611
612 requestDialogsSlice();
613 }
614
startMainSession(FnMut<void ()> done)615 void ApiWrap::startMainSession(FnMut<void()> done) {
616 using Type = Settings::Type;
617 const auto sizeLimit = _settings->media.sizeLimit;
618 const auto hasFiles = ((_settings->media.types != 0) && (sizeLimit > 0))
619 || (_settings->types & Type::Userpics);
620
621 using Flag = MTPaccount_InitTakeoutSession::Flag;
622 const auto flags = Flag(0)
623 | (_settings->types & Type::Contacts ? Flag::f_contacts : Flag(0))
624 | (hasFiles ? Flag::f_files : Flag(0))
625 | ((hasFiles && sizeLimit < kFileMaxSize)
626 ? Flag::f_file_max_size
627 : Flag(0))
628 | (_settings->types & (Type::PersonalChats | Type::BotChats)
629 ? Flag::f_message_users
630 : Flag(0))
631 | (_settings->types & Type::PrivateGroups
632 ? (Flag::f_message_chats | Flag::f_message_megagroups)
633 : Flag(0))
634 | (_settings->types & Type::PublicGroups
635 ? Flag::f_message_megagroups
636 : Flag(0))
637 | (_settings->types & (Type::PrivateChannels | Type::PublicChannels)
638 ? Flag::f_message_channels
639 : Flag(0));
640
641 _mtp.request(MTPusers_GetUsers(
642 MTP_vector<MTPInputUser>(1, MTP_inputUserSelf())
643 )).done([=, done = std::move(done)](
644 const MTPVector<MTPUser> &result) mutable {
645 for (const auto &user : result.v) {
646 user.match([&](const MTPDuser &data) {
647 if (data.is_self()) {
648 _selfId.emplace(data.vid());
649 }
650 }, [&](const MTPDuserEmpty&) {
651 });
652 }
653 if (!_selfId) {
654 error("Could not retrieve selfId.");
655 return;
656 }
657 _mtp.request(MTPaccount_InitTakeoutSession(
658 MTP_flags(flags),
659 MTP_int(sizeLimit)
660 )).done([=, done = std::move(done)](
661 const MTPaccount_Takeout &result) mutable {
662 _takeoutId = result.match([](const MTPDaccount_takeout &data) {
663 return data.vid().v;
664 });
665 done();
666 }).fail([=](const MTP::Error &result) {
667 error(result);
668 }).toDC(MTP::ShiftDcId(0, MTP::kExportDcShift)).send();
669 }).fail([=](const MTP::Error &result) {
670 error(result);
671 }).send();
672 }
673
requestPersonalInfo(FnMut<void (Data::PersonalInfo &&)> done)674 void ApiWrap::requestPersonalInfo(FnMut<void(Data::PersonalInfo&&)> done) {
675 mainRequest(MTPusers_GetFullUser(
676 _user
677 )).done([=, done = std::move(done)](const MTPUserFull &result) mutable {
678 Expects(result.type() == mtpc_userFull);
679
680 const auto &full = result.c_userFull();
681 if (full.vuser().type() == mtpc_user) {
682 done(Data::ParsePersonalInfo(result));
683 } else {
684 error("Bad user type.");
685 }
686 }).send();
687 }
688
requestOtherData(const QString & suggestedPath,FnMut<void (Data::File &&)> done)689 void ApiWrap::requestOtherData(
690 const QString &suggestedPath,
691 FnMut<void(Data::File&&)> done) {
692 Expects(_otherDataProcess == nullptr);
693
694 _otherDataProcess = std::make_unique<OtherDataProcess>();
695 _otherDataProcess->done = std::move(done);
696 _otherDataProcess->file.location.data = MTP_inputTakeoutFileLocation();
697 _otherDataProcess->file.suggestedPath = suggestedPath;
698 loadFile(
699 _otherDataProcess->file,
700 Data::FileOrigin(),
701 [](FileProgress progress) { return true; },
702 [=](const QString &result) { otherDataDone(result); });
703 }
704
otherDataDone(const QString & relativePath)705 void ApiWrap::otherDataDone(const QString &relativePath) {
706 Expects(_otherDataProcess != nullptr);
707
708 const auto process = base::take(_otherDataProcess);
709 process->file.relativePath = relativePath;
710 if (relativePath.isEmpty()) {
711 process->file.skipReason = Data::File::SkipReason::Unavailable;
712 }
713 process->done(std::move(process->file));
714 }
715
requestUserpics(FnMut<bool (Data::UserpicsInfo &&)> start,Fn<bool (DownloadProgress)> progress,Fn<bool (Data::UserpicsSlice &&)> slice,FnMut<void ()> finish)716 void ApiWrap::requestUserpics(
717 FnMut<bool(Data::UserpicsInfo&&)> start,
718 Fn<bool(DownloadProgress)> progress,
719 Fn<bool(Data::UserpicsSlice&&)> slice,
720 FnMut<void()> finish) {
721 Expects(_userpicsProcess == nullptr);
722
723 _userpicsProcess = std::make_unique<UserpicsProcess>();
724 _userpicsProcess->start = std::move(start);
725 _userpicsProcess->fileProgress = std::move(progress);
726 _userpicsProcess->handleSlice = std::move(slice);
727 _userpicsProcess->finish = std::move(finish);
728
729 mainRequest(MTPphotos_GetUserPhotos(
730 _user,
731 MTP_int(0), // offset
732 MTP_long(_userpicsProcess->maxId),
733 MTP_int(kUserpicsSliceLimit)
734 )).done([=](const MTPphotos_Photos &result) mutable {
735 Expects(_userpicsProcess != nullptr);
736
737 auto startInfo = result.match(
738 [](const MTPDphotos_photos &data) {
739 return Data::UserpicsInfo{ int(data.vphotos().v.size()) };
740 }, [](const MTPDphotos_photosSlice &data) {
741 return Data::UserpicsInfo{ data.vcount().v };
742 });
743 if (!_userpicsProcess->start(std::move(startInfo))) {
744 return;
745 }
746
747 handleUserpicsSlice(result);
748 }).send();
749 }
750
handleUserpicsSlice(const MTPphotos_Photos & result)751 void ApiWrap::handleUserpicsSlice(const MTPphotos_Photos &result) {
752 Expects(_userpicsProcess != nullptr);
753
754 result.match([&](const auto &data) {
755 if constexpr (MTPDphotos_photos::Is<decltype(data)>()) {
756 _userpicsProcess->lastSlice = true;
757 }
758 loadUserpicsFiles(Data::ParseUserpicsSlice(
759 data.vphotos(),
760 _userpicsProcess->processed));
761 });
762 }
763
loadUserpicsFiles(Data::UserpicsSlice && slice)764 void ApiWrap::loadUserpicsFiles(Data::UserpicsSlice &&slice) {
765 Expects(_userpicsProcess != nullptr);
766 Expects(!_userpicsProcess->slice.has_value());
767
768 if (slice.list.empty()) {
769 _userpicsProcess->lastSlice = true;
770 }
771 _userpicsProcess->slice = std::move(slice);
772 _userpicsProcess->fileIndex = 0;
773 loadNextUserpic();
774 }
775
loadNextUserpic()776 void ApiWrap::loadNextUserpic() {
777 Expects(_userpicsProcess != nullptr);
778 Expects(_userpicsProcess->slice.has_value());
779
780 for (auto &list = _userpicsProcess->slice->list
781 ; _userpicsProcess->fileIndex < list.size()
782 ; ++_userpicsProcess->fileIndex) {
783 const auto ready = processFileLoad(
784 list[_userpicsProcess->fileIndex].image.file,
785 Data::FileOrigin(),
786 [=](FileProgress value) { return loadUserpicProgress(value); },
787 [=](const QString &path) { loadUserpicDone(path); });
788 if (!ready) {
789 return;
790 }
791 }
792 finishUserpicsSlice();
793 }
794
finishUserpicsSlice()795 void ApiWrap::finishUserpicsSlice() {
796 Expects(_userpicsProcess != nullptr);
797 Expects(_userpicsProcess->slice.has_value());
798
799 auto slice = *base::take(_userpicsProcess->slice);
800 if (!slice.list.empty()) {
801 _userpicsProcess->processed += slice.list.size();
802 _userpicsProcess->maxId = slice.list.back().id;
803 if (!_userpicsProcess->handleSlice(std::move(slice))) {
804 return;
805 }
806 }
807 if (_userpicsProcess->lastSlice) {
808 finishUserpics();
809 return;
810 }
811
812 mainRequest(MTPphotos_GetUserPhotos(
813 _user,
814 MTP_int(0), // offset
815 MTP_long(_userpicsProcess->maxId),
816 MTP_int(kUserpicsSliceLimit)
817 )).done([=](const MTPphotos_Photos &result) {
818 handleUserpicsSlice(result);
819 }).send();
820 }
821
loadUserpicProgress(FileProgress progress)822 bool ApiWrap::loadUserpicProgress(FileProgress progress) {
823 Expects(_fileProcess != nullptr);
824 Expects(_userpicsProcess != nullptr);
825 Expects(_userpicsProcess->slice.has_value());
826 Expects((_userpicsProcess->fileIndex >= 0)
827 && (_userpicsProcess->fileIndex
828 < _userpicsProcess->slice->list.size()));
829
830 return _userpicsProcess->fileProgress(DownloadProgress{
831 _fileProcess->randomId,
832 _fileProcess->relativePath,
833 _userpicsProcess->fileIndex,
834 progress.ready,
835 progress.total });
836 }
837
loadUserpicDone(const QString & relativePath)838 void ApiWrap::loadUserpicDone(const QString &relativePath) {
839 Expects(_userpicsProcess != nullptr);
840 Expects(_userpicsProcess->slice.has_value());
841 Expects((_userpicsProcess->fileIndex >= 0)
842 && (_userpicsProcess->fileIndex
843 < _userpicsProcess->slice->list.size()));
844
845 const auto index = _userpicsProcess->fileIndex;
846 auto &file = _userpicsProcess->slice->list[index].image.file;
847 file.relativePath = relativePath;
848 if (relativePath.isEmpty()) {
849 file.skipReason = Data::File::SkipReason::Unavailable;
850 }
851 loadNextUserpic();
852 }
853
finishUserpics()854 void ApiWrap::finishUserpics() {
855 Expects(_userpicsProcess != nullptr);
856
857 base::take(_userpicsProcess)->finish();
858 }
859
requestContacts(FnMut<void (Data::ContactsList &&)> done)860 void ApiWrap::requestContacts(FnMut<void(Data::ContactsList&&)> done) {
861 Expects(_contactsProcess == nullptr);
862
863 _contactsProcess = std::make_unique<ContactsProcess>();
864 _contactsProcess->done = std::move(done);
865 mainRequest(MTPcontacts_GetSaved(
866 )).done([=](const MTPVector<MTPSavedContact> &result) {
867 _contactsProcess->result = Data::ParseContactsList(result);
868 requestTopPeersSlice();
869 }).send();
870 }
871
requestTopPeersSlice()872 void ApiWrap::requestTopPeersSlice() {
873 Expects(_contactsProcess != nullptr);
874
875 using Flag = MTPcontacts_GetTopPeers::Flag;
876 mainRequest(MTPcontacts_GetTopPeers(
877 MTP_flags(Flag::f_correspondents
878 | Flag::f_bots_inline
879 | Flag::f_phone_calls),
880 MTP_int(_contactsProcess->topPeersOffset),
881 MTP_int(kTopPeerSliceLimit),
882 MTP_long(0) // hash
883 )).done([=](const MTPcontacts_TopPeers &result) {
884 Expects(_contactsProcess != nullptr);
885
886 if (!Data::AppendTopPeers(_contactsProcess->result, result)) {
887 error("Unexpected data in ApiWrap::requestTopPeersSlice.");
888 return;
889 }
890
891 const auto offset = _contactsProcess->topPeersOffset;
892 const auto loaded = result.match(
893 [](const MTPDcontacts_topPeersNotModified &data) {
894 return true;
895 }, [](const MTPDcontacts_topPeersDisabled &data) {
896 return true;
897 }, [&](const MTPDcontacts_topPeers &data) {
898 for (const auto &category : data.vcategories().v) {
899 const auto loaded = category.match(
900 [&](const MTPDtopPeerCategoryPeers &data) {
901 return offset + data.vpeers().v.size() >= data.vcount().v;
902 });
903 if (!loaded) {
904 return false;
905 }
906 }
907 return true;
908 });
909
910 if (loaded) {
911 auto process = base::take(_contactsProcess);
912 process->done(std::move(process->result));
913 } else {
914 _contactsProcess->topPeersOffset = std::max(std::max(
915 _contactsProcess->result.correspondents.size(),
916 _contactsProcess->result.inlineBots.size()),
917 _contactsProcess->result.phoneCalls.size());
918 requestTopPeersSlice();
919 }
920 }).send();
921 }
922
requestSessions(FnMut<void (Data::SessionsList &&)> done)923 void ApiWrap::requestSessions(FnMut<void(Data::SessionsList&&)> done) {
924 mainRequest(MTPaccount_GetAuthorizations(
925 )).done([=, done = std::move(done)](
926 const MTPaccount_Authorizations &result) mutable {
927 auto list = Data::ParseSessionsList(result);
928 mainRequest(MTPaccount_GetWebAuthorizations(
929 )).done([=, done = std::move(done), list = std::move(list)](
930 const MTPaccount_WebAuthorizations &result) mutable {
931 list.webList = Data::ParseWebSessionsList(result).webList;
932 done(std::move(list));
933 }).send();
934 }).send();
935 }
936
requestMessages(const Data::DialogInfo & info,FnMut<bool (const Data::DialogInfo &)> start,Fn<bool (DownloadProgress)> progress,Fn<bool (Data::MessagesSlice &&)> slice,FnMut<void ()> done)937 void ApiWrap::requestMessages(
938 const Data::DialogInfo &info,
939 FnMut<bool(const Data::DialogInfo &)> start,
940 Fn<bool(DownloadProgress)> progress,
941 Fn<bool(Data::MessagesSlice&&)> slice,
942 FnMut<void()> done) {
943 Expects(_chatProcess == nullptr);
944 Expects(_selfId.has_value());
945
946 _chatProcess = std::make_unique<ChatProcess>();
947 _chatProcess->context.selfPeerId = peerFromUser(*_selfId);
948 _chatProcess->info = info;
949 _chatProcess->start = std::move(start);
950 _chatProcess->fileProgress = std::move(progress);
951 _chatProcess->handleSlice = std::move(slice);
952 _chatProcess->done = std::move(done);
953
954 requestMessagesCount(0);
955 }
956
requestMessagesCount(int localSplitIndex)957 void ApiWrap::requestMessagesCount(int localSplitIndex) {
958 Expects(_chatProcess != nullptr);
959 Expects(localSplitIndex < _chatProcess->info.splits.size());
960
961 requestChatMessages(
962 _chatProcess->info.splits[localSplitIndex],
963 0, // offset_id
964 0, // add_offset
965 1, // limit
966 [=](const MTPmessages_Messages &result) {
967 Expects(_chatProcess != nullptr);
968
969 const auto count = result.match(
970 [](const MTPDmessages_messages &data) {
971 return int(data.vmessages().v.size());
972 }, [](const MTPDmessages_messagesSlice &data) {
973 return data.vcount().v;
974 }, [](const MTPDmessages_channelMessages &data) {
975 return data.vcount().v;
976 }, [](const MTPDmessages_messagesNotModified &data) {
977 return -1;
978 });
979 if (count < 0) {
980 error("Unexpected messagesNotModified received.");
981 return;
982 }
983 const auto skipSplit = !Data::SingleMessageAfter(
984 result,
985 _settings->singlePeerFrom);
986 if (skipSplit) {
987 // No messages from the requested range, skip this split.
988 messagesCountLoaded(localSplitIndex, 0);
989 return;
990 }
991 checkFirstMessageDate(localSplitIndex, count);
992 });
993 }
994
checkFirstMessageDate(int localSplitIndex,int count)995 void ApiWrap::checkFirstMessageDate(int localSplitIndex, int count) {
996 Expects(_chatProcess != nullptr);
997 Expects(localSplitIndex < _chatProcess->info.splits.size());
998
999 if (_settings->singlePeerTill <= 0) {
1000 messagesCountLoaded(localSplitIndex, count);
1001 return;
1002 }
1003
1004 // Request first message in this split to check if its' date < till.
1005 requestChatMessages(
1006 _chatProcess->info.splits[localSplitIndex],
1007 1, // offset_id
1008 -1, // add_offset
1009 1, // limit
1010 [=](const MTPmessages_Messages &result) {
1011 Expects(_chatProcess != nullptr);
1012
1013 const auto skipSplit = !Data::SingleMessageBefore(
1014 result,
1015 _settings->singlePeerTill);
1016 messagesCountLoaded(localSplitIndex, skipSplit ? 0 : count);
1017 });
1018 }
1019
messagesCountLoaded(int localSplitIndex,int count)1020 void ApiWrap::messagesCountLoaded(int localSplitIndex, int count) {
1021 Expects(_chatProcess != nullptr);
1022 Expects(localSplitIndex < _chatProcess->info.splits.size());
1023
1024 _chatProcess->info.messagesCountPerSplit[localSplitIndex] = count;
1025 if (localSplitIndex + 1 < _chatProcess->info.splits.size()) {
1026 requestMessagesCount(localSplitIndex + 1);
1027 } else if (_chatProcess->start(_chatProcess->info)) {
1028 requestMessagesSlice();
1029 }
1030 }
1031
finishExport(FnMut<void ()> done)1032 void ApiWrap::finishExport(FnMut<void()> done) {
1033 const auto guard = gsl::finally([&] { _takeoutId = std::nullopt; });
1034
1035 mainRequest(MTPaccount_FinishTakeoutSession(
1036 MTP_flags(MTPaccount_FinishTakeoutSession::Flag::f_success)
1037 )).done(std::move(done)).send();
1038 }
1039
skipFile(uint64 randomId)1040 void ApiWrap::skipFile(uint64 randomId) {
1041 if (!_fileProcess || _fileProcess->randomId != randomId) {
1042 return;
1043 }
1044 LOG(("Export Info: File skipped."));
1045 Assert(!_fileProcess->requests.empty());
1046 Assert(_fileProcess->requestId != 0);
1047 _mtp.request(base::take(_fileProcess->requestId)).cancel();
1048 base::take(_fileProcess)->done(QString());
1049 }
1050
cancelExportFast()1051 void ApiWrap::cancelExportFast() {
1052 if (_takeoutId.has_value()) {
1053 const auto requestId = mainRequest(MTPaccount_FinishTakeoutSession(
1054 MTP_flags(0)
1055 )).send();
1056 _mtp.request(requestId).detach();
1057 }
1058 }
1059
requestSinglePeerDialog()1060 void ApiWrap::requestSinglePeerDialog() {
1061 auto doneSinglePeer = [=](const auto &result) {
1062 appendSinglePeerDialogs(
1063 Data::ParseDialogsInfo(_settings->singlePeer, result));
1064 };
1065 const auto requestUser = [&](const MTPInputUser &data) {
1066 mainRequest(MTPusers_GetUsers(
1067 MTP_vector<MTPInputUser>(1, data)
1068 )).done(std::move(doneSinglePeer)).send();
1069 };
1070 _settings->singlePeer.match([&](const MTPDinputPeerUser &data) {
1071 requestUser(MTP_inputUser(data.vuser_id(), data.vaccess_hash()));
1072 }, [&](const MTPDinputPeerChat &data) {
1073 mainRequest(MTPmessages_GetChats(
1074 MTP_vector<MTPlong>(1, data.vchat_id())
1075 )).done(std::move(doneSinglePeer)).send();
1076 }, [&](const MTPDinputPeerChannel &data) {
1077 mainRequest(MTPchannels_GetChannels(
1078 MTP_vector<MTPInputChannel>(
1079 1,
1080 MTP_inputChannel(data.vchannel_id(), data.vaccess_hash()))
1081 )).done(std::move(doneSinglePeer)).send();
1082 }, [&](const MTPDinputPeerSelf &data) {
1083 requestUser(MTP_inputUserSelf());
1084 }, [&](const MTPDinputPeerUserFromMessage &data) {
1085 Unexpected("From message peer in ApiWrap::requestSinglePeerDialog.");
1086 }, [&](const MTPDinputPeerChannelFromMessage &data) {
1087 Unexpected("From message peer in ApiWrap::requestSinglePeerDialog.");
1088 }, [](const MTPDinputPeerEmpty &data) {
1089 Unexpected("Empty peer in ApiWrap::requestSinglePeerDialog.");
1090 });
1091 }
1092
requestSinglePeerMigrated(const Data::DialogInfo & info)1093 mtpRequestId ApiWrap::requestSinglePeerMigrated(
1094 const Data::DialogInfo &info) {
1095 const auto input = info.input.match([&](
1096 const MTPDinputPeerChannel & data) {
1097 return MTP_inputChannel(
1098 data.vchannel_id(),
1099 data.vaccess_hash());
1100 }, [](auto&&) -> MTPinputChannel {
1101 Unexpected("Peer type in a supergroup.");
1102 });
1103 return mainRequest(MTPchannels_GetFullChannel(
1104 input
1105 )).done([=](const MTPmessages_ChatFull &result) {
1106 auto info = result.match([&](
1107 const MTPDmessages_chatFull &data) {
1108 const auto migratedChatId = data.vfull_chat().match([&](
1109 const MTPDchannelFull &data) {
1110 return data.vmigrated_from_chat_id().value_or_empty();
1111 }, [](auto &&other) -> BareId {
1112 return 0;
1113 });
1114 return migratedChatId
1115 ? Data::ParseDialogsInfo(
1116 MTP_inputPeerChat(MTP_long(migratedChatId)),
1117 MTP_messages_chats(data.vchats()))
1118 : Data::DialogsInfo();
1119 });
1120 appendSinglePeerDialogs(std::move(info));
1121 }).send();
1122 }
1123
appendSinglePeerDialogs(Data::DialogsInfo && info)1124 void ApiWrap::appendSinglePeerDialogs(Data::DialogsInfo &&info) {
1125 const auto isSupergroupType = [](Data::DialogInfo::Type type) {
1126 using Type = Data::DialogInfo::Type;
1127 return (type == Type::PrivateSupergroup)
1128 || (type == Type::PublicSupergroup);
1129 };
1130 const auto isChannelType = [](Data::DialogInfo::Type type) {
1131 using Type = Data::DialogInfo::Type;
1132 return (type == Type::PrivateChannel)
1133 || (type == Type::PublicChannel);
1134 };
1135
1136 auto migratedRequestId = mtpRequestId(0);
1137 const auto last = _dialogsProcess->splitIndexPlusOne - 1;
1138 for (auto &info : info.chats) {
1139 if (isSupergroupType(info.type) && !migratedRequestId) {
1140 migratedRequestId = requestSinglePeerMigrated(info);
1141 continue;
1142 } else if (isChannelType(info.type)) {
1143 continue;
1144 }
1145 for (auto i = last; i != 0; --i) {
1146 info.splits.push_back(i - 1);
1147 info.messagesCountPerSplit.push_back(0);
1148 }
1149 }
1150
1151 if (!migratedRequestId) {
1152 _dialogsProcess->processedCount += info.chats.size();
1153 }
1154 appendDialogsSlice(std::move(info));
1155
1156 if (migratedRequestId
1157 || !_dialogsProcess->progress(_dialogsProcess->processedCount)) {
1158 return;
1159 }
1160 finishDialogsList();
1161 }
1162
requestDialogsSlice()1163 void ApiWrap::requestDialogsSlice() {
1164 Expects(_dialogsProcess != nullptr);
1165
1166 if (_settings->onlySinglePeer()) {
1167 requestSinglePeerDialog();
1168 return;
1169 }
1170
1171 const auto splitIndex = _dialogsProcess->splitIndexPlusOne - 1;
1172 const auto hash = uint64(0);
1173 splitRequest(splitIndex, MTPmessages_GetDialogs(
1174 MTP_flags(0),
1175 MTPint(), // folder_id
1176 MTP_int(_dialogsProcess->offsetDate),
1177 MTP_int(_dialogsProcess->offsetId),
1178 _dialogsProcess->offsetPeer,
1179 MTP_int(kChatsSliceLimit),
1180 MTP_long(hash)
1181 )).done([=](const MTPmessages_Dialogs &result) {
1182 if (result.type() == mtpc_messages_dialogsNotModified) {
1183 error("Unexpected dialogsNotModified received.");
1184 return;
1185 }
1186 auto finished = result.match(
1187 [](const MTPDmessages_dialogs &data) {
1188 return true;
1189 }, [](const MTPDmessages_dialogsSlice &data) {
1190 return data.vdialogs().v.isEmpty();
1191 }, [](const MTPDmessages_dialogsNotModified &data) {
1192 return true;
1193 });
1194
1195 auto info = Data::ParseDialogsInfo(result);
1196 _dialogsProcess->processedCount += info.chats.size();
1197 const auto last = info.chats.empty()
1198 ? Data::DialogInfo()
1199 : info.chats.back();
1200 appendDialogsSlice(std::move(info));
1201
1202 if (!_dialogsProcess->progress(_dialogsProcess->processedCount)) {
1203 return;
1204 }
1205
1206 if (!finished && last.topMessageDate > 0) {
1207 _dialogsProcess->offsetId = last.topMessageId;
1208 _dialogsProcess->offsetDate = last.topMessageDate;
1209 _dialogsProcess->offsetPeer = last.input;
1210 } else if (!useOnlyLastSplit()
1211 && --_dialogsProcess->splitIndexPlusOne > 0) {
1212 _dialogsProcess->offsetId = 0;
1213 _dialogsProcess->offsetDate = 0;
1214 _dialogsProcess->offsetPeer = MTP_inputPeerEmpty();
1215 } else {
1216 requestLeftChannelsIfNeeded();
1217 return;
1218 }
1219 requestDialogsSlice();
1220 }).send();
1221 }
1222
appendDialogsSlice(Data::DialogsInfo && info)1223 void ApiWrap::appendDialogsSlice(Data::DialogsInfo &&info) {
1224 Expects(_dialogsProcess != nullptr);
1225 Expects(_dialogsProcess->splitIndexPlusOne <= _splits.size());
1226
1227 appendChatsSlice(
1228 *_dialogsProcess,
1229 _dialogsProcess->info.chats,
1230 std::move(info.chats),
1231 _dialogsProcess->splitIndexPlusOne - 1);
1232 }
1233
requestLeftChannelsIfNeeded()1234 void ApiWrap::requestLeftChannelsIfNeeded() {
1235 if (_settings->types & Settings::Type::GroupsChannelsMask) {
1236 requestLeftChannelsList([=](int count) {
1237 Expects(_dialogsProcess != nullptr);
1238
1239 return _dialogsProcess->progress(
1240 _dialogsProcess->processedCount + count);
1241 }, [=](Data::DialogsInfo &&result) {
1242 Expects(_dialogsProcess != nullptr);
1243
1244 _dialogsProcess->info.left = std::move(result.left);
1245 finishDialogsList();
1246 });
1247 } else {
1248 finishDialogsList();
1249 }
1250 }
1251
finishDialogsList()1252 void ApiWrap::finishDialogsList() {
1253 Expects(_dialogsProcess != nullptr);
1254
1255 const auto process = base::take(_dialogsProcess);
1256 Data::FinalizeDialogsInfo(process->info, *_settings);
1257 process->done(std::move(process->info));
1258 }
1259
requestLeftChannelsSliceGeneric(FnMut<void ()> done)1260 void ApiWrap::requestLeftChannelsSliceGeneric(FnMut<void()> done) {
1261 Expects(_leftChannelsProcess != nullptr);
1262
1263 mainRequest(MTPchannels_GetLeftChannels(
1264 MTP_int(_leftChannelsProcess->offset)
1265 )).done([=, done = std::move(done)](
1266 const MTPmessages_Chats &result) mutable {
1267 Expects(_leftChannelsProcess != nullptr);
1268
1269 appendLeftChannelsSlice(Data::ParseLeftChannelsInfo(result));
1270
1271 const auto process = _leftChannelsProcess.get();
1272 process->offset += result.match(
1273 [](const auto &data) {
1274 return int(data.vchats().v.size());
1275 });
1276
1277 process->fullCount = result.match(
1278 [](const MTPDmessages_chats &data) {
1279 return int(data.vchats().v.size());
1280 }, [](const MTPDmessages_chatsSlice &data) {
1281 return data.vcount().v;
1282 });
1283
1284 process->finished = result.match(
1285 [](const MTPDmessages_chats &data) {
1286 return true;
1287 }, [](const MTPDmessages_chatsSlice &data) {
1288 return data.vchats().v.isEmpty();
1289 });
1290
1291 if (process->progress) {
1292 if (!process->progress(process->info.left.size())) {
1293 return;
1294 }
1295 }
1296
1297 done();
1298 }).send();
1299 }
1300
appendLeftChannelsSlice(Data::DialogsInfo && info)1301 void ApiWrap::appendLeftChannelsSlice(Data::DialogsInfo &&info) {
1302 Expects(_leftChannelsProcess != nullptr);
1303 Expects(!_splits.empty());
1304
1305 appendChatsSlice(
1306 *_leftChannelsProcess,
1307 _leftChannelsProcess->info.left,
1308 std::move(info.left),
1309 _splits.size() - 1);
1310 }
1311
appendChatsSlice(ChatsProcess & process,std::vector<Data::DialogInfo> & to,std::vector<Data::DialogInfo> && from,int splitIndex)1312 void ApiWrap::appendChatsSlice(
1313 ChatsProcess &process,
1314 std::vector<Data::DialogInfo> &to,
1315 std::vector<Data::DialogInfo> &&from,
1316 int splitIndex) {
1317 Expects(_settings != nullptr);
1318
1319 const auto types = _settings->types;
1320 const auto goodByTypes = [&](const Data::DialogInfo &info) {
1321 return ((types & SettingsFromDialogsType(info.type)) != 0);
1322 };
1323 auto filtered = ranges::views::all(
1324 from
1325 ) | ranges::views::filter([&](const Data::DialogInfo &info) {
1326 if (goodByTypes(info)) {
1327 return true;
1328 } else if (info.migratedToChannelId
1329 && (((types & Settings::Type::PublicGroups) != 0)
1330 || ((types & Settings::Type::PrivateGroups) != 0))) {
1331 return true;
1332 }
1333 return false;
1334 });
1335 to.reserve(to.size() + from.size());
1336 for (auto &info : filtered) {
1337 const auto nextIndex = to.size();
1338 if (info.migratedToChannelId) {
1339 const auto toPeerId = PeerId(info.migratedToChannelId);
1340 const auto i = process.indexByPeer.find(toPeerId);
1341 if (i != process.indexByPeer.end()
1342 && Data::AddMigrateFromSlice(
1343 to[i->second],
1344 info,
1345 splitIndex,
1346 int(_splits.size()))) {
1347 continue;
1348 } else if (!goodByTypes(info)) {
1349 continue;
1350 }
1351 }
1352 const auto [i, ok] = process.indexByPeer.emplace(
1353 info.peerId,
1354 nextIndex);
1355 if (ok) {
1356 to.push_back(std::move(info));
1357 }
1358 to[i->second].splits.push_back(splitIndex);
1359 to[i->second].messagesCountPerSplit.push_back(0);
1360 }
1361 }
1362
requestMessagesSlice()1363 void ApiWrap::requestMessagesSlice() {
1364 Expects(_chatProcess != nullptr);
1365
1366 const auto count = _chatProcess->info.messagesCountPerSplit[
1367 _chatProcess->localSplitIndex];
1368 if (!count) {
1369 loadMessagesFiles({});
1370 return;
1371 }
1372 requestChatMessages(
1373 _chatProcess->info.splits[_chatProcess->localSplitIndex],
1374 _chatProcess->largestIdPlusOne,
1375 -kMessagesSliceLimit,
1376 kMessagesSliceLimit,
1377 [=](const MTPmessages_Messages &result) {
1378 Expects(_chatProcess != nullptr);
1379
1380 result.match([&](const MTPDmessages_messagesNotModified &data) {
1381 error("Unexpected messagesNotModified received.");
1382 }, [&](const auto &data) {
1383 if constexpr (MTPDmessages_messages::Is<decltype(data)>()) {
1384 _chatProcess->lastSlice = true;
1385 }
1386 loadMessagesFiles(Data::ParseMessagesSlice(
1387 _chatProcess->context,
1388 data.vmessages(),
1389 data.vusers(),
1390 data.vchats(),
1391 _chatProcess->info.relativePath));
1392 });
1393 });
1394 }
1395
requestChatMessages(int splitIndex,int offsetId,int addOffset,int limit,FnMut<void (MTPmessages_Messages &&)> done)1396 void ApiWrap::requestChatMessages(
1397 int splitIndex,
1398 int offsetId,
1399 int addOffset,
1400 int limit,
1401 FnMut<void(MTPmessages_Messages&&)> done) {
1402 Expects(_chatProcess != nullptr);
1403
1404 _chatProcess->requestDone = std::move(done);
1405 const auto doneHandler = [=](MTPmessages_Messages &&result) {
1406 Expects(_chatProcess != nullptr);
1407
1408 base::take(_chatProcess->requestDone)(std::move(result));
1409 };
1410 const auto splitsCount = int(_splits.size());
1411 const auto realPeerInput = (splitIndex >= 0)
1412 ? _chatProcess->info.input
1413 : _chatProcess->info.migratedFromInput;
1414 const auto realSplitIndex = (splitIndex >= 0)
1415 ? splitIndex
1416 : (splitsCount + splitIndex);
1417 if (_chatProcess->info.onlyMyMessages) {
1418 splitRequest(realSplitIndex, MTPmessages_Search(
1419 MTP_flags(MTPmessages_Search::Flag::f_from_id),
1420 realPeerInput,
1421 MTP_string(), // query
1422 MTP_inputPeerSelf(),
1423 MTPint(), // top_msg_id
1424 MTP_inputMessagesFilterEmpty(),
1425 MTP_int(0), // min_date
1426 MTP_int(0), // max_date
1427 MTP_int(offsetId),
1428 MTP_int(addOffset),
1429 MTP_int(limit),
1430 MTP_int(0), // max_id
1431 MTP_int(0), // min_id
1432 MTP_long(0) // hash
1433 )).done(doneHandler).send();
1434 } else {
1435 splitRequest(realSplitIndex, MTPmessages_GetHistory(
1436 realPeerInput,
1437 MTP_int(offsetId),
1438 MTP_int(0), // offset_date
1439 MTP_int(addOffset),
1440 MTP_int(limit),
1441 MTP_int(0), // max_id
1442 MTP_int(0), // min_id
1443 MTP_long(0) // hash
1444 )).fail([=](const MTP::Error &error) {
1445 Expects(_chatProcess != nullptr);
1446
1447 if (error.type() == qstr("CHANNEL_PRIVATE")) {
1448 if (realPeerInput.type() == mtpc_inputPeerChannel
1449 && !_chatProcess->info.onlyMyMessages) {
1450
1451 // Perhaps we just left / were kicked from channel.
1452 // Just switch to only my messages.
1453 _chatProcess->info.onlyMyMessages = true;
1454 requestChatMessages(
1455 splitIndex,
1456 offsetId,
1457 addOffset,
1458 limit,
1459 base::take(_chatProcess->requestDone));
1460 return true;
1461 }
1462 }
1463 return false;
1464 }).done(doneHandler).send();
1465 }
1466 }
1467
loadMessagesFiles(Data::MessagesSlice && slice)1468 void ApiWrap::loadMessagesFiles(Data::MessagesSlice &&slice) {
1469 Expects(_chatProcess != nullptr);
1470 Expects(!_chatProcess->slice.has_value());
1471
1472 if (slice.list.empty()) {
1473 _chatProcess->lastSlice = true;
1474 }
1475 _chatProcess->slice = std::move(slice);
1476 _chatProcess->fileIndex = 0;
1477
1478 loadNextMessageFile();
1479 }
1480
currentFileMessage() const1481 Data::Message *ApiWrap::currentFileMessage() const {
1482 Expects(_chatProcess != nullptr);
1483 Expects(_chatProcess->slice.has_value());
1484
1485 return &_chatProcess->slice->list[_chatProcess->fileIndex];
1486 }
1487
currentFileMessageOrigin() const1488 Data::FileOrigin ApiWrap::currentFileMessageOrigin() const {
1489 Expects(_chatProcess != nullptr);
1490 Expects(_chatProcess->slice.has_value());
1491
1492 const auto splitIndex = _chatProcess->info.splits[
1493 _chatProcess->localSplitIndex];
1494 auto result = Data::FileOrigin();
1495 result.messageId = currentFileMessage()->id;
1496 result.split = (splitIndex >= 0)
1497 ? splitIndex
1498 : (int(_splits.size()) + splitIndex);
1499 result.peer = (splitIndex >= 0)
1500 ? _chatProcess->info.input
1501 : _chatProcess->info.migratedFromInput;
1502 return result;
1503 }
1504
loadNextMessageFile()1505 void ApiWrap::loadNextMessageFile() {
1506 Expects(_chatProcess != nullptr);
1507 Expects(_chatProcess->slice.has_value());
1508
1509 for (auto &list = _chatProcess->slice->list
1510 ; _chatProcess->fileIndex < list.size()
1511 ; ++_chatProcess->fileIndex) {
1512 const auto &message = list[_chatProcess->fileIndex];
1513 if (Data::SkipMessageByDate(message, *_settings)) {
1514 continue;
1515 }
1516 const auto fileProgress = [=](FileProgress value) {
1517 return loadMessageFileProgress(value);
1518 };
1519 const auto ready = processFileLoad(
1520 list[_chatProcess->fileIndex].file(),
1521 currentFileMessageOrigin(),
1522 fileProgress,
1523 [=](const QString &path) { loadMessageFileDone(path); },
1524 currentFileMessage());
1525 if (!ready) {
1526 return;
1527 }
1528 const auto thumbProgress = [=](FileProgress value) {
1529 return loadMessageThumbProgress(value);
1530 };
1531 const auto thumbReady = processFileLoad(
1532 list[_chatProcess->fileIndex].thumb().file,
1533 currentFileMessageOrigin(),
1534 thumbProgress,
1535 [=](const QString &path) { loadMessageThumbDone(path); },
1536 currentFileMessage());
1537 if (!thumbReady) {
1538 return;
1539 }
1540 }
1541 finishMessagesSlice();
1542 }
1543
finishMessagesSlice()1544 void ApiWrap::finishMessagesSlice() {
1545 Expects(_chatProcess != nullptr);
1546 Expects(_chatProcess->slice.has_value());
1547
1548 auto slice = *base::take(_chatProcess->slice);
1549 if (!slice.list.empty()) {
1550 _chatProcess->largestIdPlusOne = slice.list.back().id + 1;
1551 const auto splitIndex = _chatProcess->info.splits[
1552 _chatProcess->localSplitIndex];
1553 if (splitIndex < 0) {
1554 slice = AdjustMigrateMessageIds(std::move(slice));
1555 }
1556 if (!_chatProcess->handleSlice(std::move(slice))) {
1557 return;
1558 }
1559 }
1560 if (_chatProcess->lastSlice
1561 && (++_chatProcess->localSplitIndex
1562 < _chatProcess->info.splits.size())) {
1563 _chatProcess->lastSlice = false;
1564 _chatProcess->largestIdPlusOne = 1;
1565 }
1566 if (!_chatProcess->lastSlice) {
1567 requestMessagesSlice();
1568 } else {
1569 finishMessages();
1570 }
1571 }
1572
loadMessageFileProgress(FileProgress progress)1573 bool ApiWrap::loadMessageFileProgress(FileProgress progress) {
1574 Expects(_fileProcess != nullptr);
1575 Expects(_chatProcess != nullptr);
1576 Expects(_chatProcess->slice.has_value());
1577 Expects((_chatProcess->fileIndex >= 0)
1578 && (_chatProcess->fileIndex < _chatProcess->slice->list.size()));
1579
1580 return _chatProcess->fileProgress(DownloadProgress{
1581 .randomId = _fileProcess->randomId,
1582 .path = _fileProcess->relativePath,
1583 .itemIndex = _chatProcess->fileIndex,
1584 .ready = progress.ready,
1585 .total = progress.total });
1586 }
1587
loadMessageFileDone(const QString & relativePath)1588 void ApiWrap::loadMessageFileDone(const QString &relativePath) {
1589 Expects(_chatProcess != nullptr);
1590 Expects(_chatProcess->slice.has_value());
1591 Expects((_chatProcess->fileIndex >= 0)
1592 && (_chatProcess->fileIndex < _chatProcess->slice->list.size()));
1593
1594 const auto index = _chatProcess->fileIndex;
1595 auto &file = _chatProcess->slice->list[index].file();
1596 file.relativePath = relativePath;
1597 if (relativePath.isEmpty()) {
1598 file.skipReason = Data::File::SkipReason::Unavailable;
1599 }
1600 loadNextMessageFile();
1601 }
1602
loadMessageThumbProgress(FileProgress progress)1603 bool ApiWrap::loadMessageThumbProgress(FileProgress progress) {
1604 return loadMessageFileProgress(progress);
1605 }
1606
loadMessageThumbDone(const QString & relativePath)1607 void ApiWrap::loadMessageThumbDone(const QString &relativePath) {
1608 Expects(_chatProcess != nullptr);
1609 Expects(_chatProcess->slice.has_value());
1610 Expects((_chatProcess->fileIndex >= 0)
1611 && (_chatProcess->fileIndex < _chatProcess->slice->list.size()));
1612
1613 const auto index = _chatProcess->fileIndex;
1614 auto &file = _chatProcess->slice->list[index].thumb().file;
1615 file.relativePath = relativePath;
1616 if (relativePath.isEmpty()) {
1617 file.skipReason = Data::File::SkipReason::Unavailable;
1618 }
1619 loadNextMessageFile();
1620 }
1621
finishMessages()1622 void ApiWrap::finishMessages() {
1623 Expects(_chatProcess != nullptr);
1624 Expects(!_chatProcess->slice.has_value());
1625
1626 const auto process = base::take(_chatProcess);
1627 process->done();
1628 }
1629
processFileLoad(Data::File & file,const Data::FileOrigin & origin,Fn<bool (FileProgress)> progress,FnMut<void (QString)> done,Data::Message * message)1630 bool ApiWrap::processFileLoad(
1631 Data::File &file,
1632 const Data::FileOrigin &origin,
1633 Fn<bool(FileProgress)> progress,
1634 FnMut<void(QString)> done,
1635 Data::Message *message) {
1636 using SkipReason = Data::File::SkipReason;
1637
1638 if (!file.relativePath.isEmpty()
1639 || file.skipReason != SkipReason::None) {
1640 return true;
1641 } else if (!file.location && file.content.isEmpty()) {
1642 file.skipReason = SkipReason::Unavailable;
1643 return true;
1644 } else if (writePreloadedFile(file, origin)) {
1645 return !file.relativePath.isEmpty();
1646 }
1647
1648 using Type = MediaSettings::Type;
1649 const auto type = message ? v::match(message->media.content, [&](
1650 const Data::Document &data) {
1651 if (data.isSticker) {
1652 return Type::Sticker;
1653 } else if (data.isVideoMessage) {
1654 return Type::VideoMessage;
1655 } else if (data.isVoiceMessage) {
1656 return Type::VoiceMessage;
1657 } else if (data.isAnimated) {
1658 return Type::GIF;
1659 } else if (data.isVideoFile) {
1660 return Type::Video;
1661 } else {
1662 return Type::File;
1663 }
1664 }, [](const auto &data) {
1665 return Type::Photo;
1666 }) : Type(0);
1667
1668 const auto limit = _settings->media.sizeLimit;
1669 if (message && Data::SkipMessageByDate(*message, *_settings)) {
1670 file.skipReason = SkipReason::DateLimits;
1671 return true;
1672 } else if ((_settings->media.types & type) != type) {
1673 file.skipReason = SkipReason::FileType;
1674 return true;
1675 } else if ((message ? message->file().size : file.size) >= limit) {
1676 // Don't load thumbs for large files that we skip.
1677 file.skipReason = SkipReason::FileSize;
1678 return true;
1679 }
1680 loadFile(file, origin, std::move(progress), std::move(done));
1681 return false;
1682 }
1683
writePreloadedFile(Data::File & file,const Data::FileOrigin & origin)1684 bool ApiWrap::writePreloadedFile(
1685 Data::File &file,
1686 const Data::FileOrigin &origin) {
1687 Expects(_settings != nullptr);
1688
1689 using namespace Output;
1690
1691 if (const auto path = _fileCache->find(file.location)) {
1692 file.relativePath = *path;
1693 return true;
1694 } else if (!file.content.isEmpty()) {
1695 const auto process = prepareFileProcess(file, origin);
1696 if (const auto result = process->file.writeBlock(file.content)) {
1697 file.relativePath = process->relativePath;
1698 _fileCache->save(file.location, file.relativePath);
1699 } else {
1700 ioError(result);
1701 }
1702 return true;
1703 }
1704 return false;
1705 }
1706
loadFile(const Data::File & file,const Data::FileOrigin & origin,Fn<bool (FileProgress)> progress,FnMut<void (QString)> done)1707 void ApiWrap::loadFile(
1708 const Data::File &file,
1709 const Data::FileOrigin &origin,
1710 Fn<bool(FileProgress)> progress,
1711 FnMut<void(QString)> done) {
1712 Expects(_fileProcess == nullptr);
1713 Expects(file.location.dcId != 0
1714 || file.location.data.type() == mtpc_inputTakeoutFileLocation);
1715
1716 _fileProcess = prepareFileProcess(file, origin);
1717 _fileProcess->progress = std::move(progress);
1718 _fileProcess->done = std::move(done);
1719
1720 if (_fileProcess->progress) {
1721 const auto progress = FileProgress{
1722 _fileProcess->file.size(),
1723 _fileProcess->size
1724 };
1725 if (!_fileProcess->progress(progress)) {
1726 return;
1727 }
1728 }
1729
1730 loadFilePart();
1731
1732 Ensures(_fileProcess->requestId != 0);
1733 }
1734
prepareFileProcess(const Data::File & file,const Data::FileOrigin & origin) const1735 auto ApiWrap::prepareFileProcess(
1736 const Data::File &file,
1737 const Data::FileOrigin &origin) const
1738 -> std::unique_ptr<FileProcess> {
1739 Expects(_settings != nullptr);
1740
1741 const auto relativePath = Output::File::PrepareRelativePath(
1742 _settings->path,
1743 file.suggestedPath);
1744 auto result = std::make_unique<FileProcess>(
1745 _settings->path + relativePath,
1746 _stats);
1747 result->relativePath = relativePath;
1748 result->location = file.location;
1749 result->size = file.size;
1750 result->origin = origin;
1751 result->randomId = base::RandomValue<uint64>();
1752 return result;
1753 }
1754
loadFilePart()1755 void ApiWrap::loadFilePart() {
1756 if (!_fileProcess
1757 || _fileProcess->requestId
1758 || _fileProcess->requests.size() >= kFileRequestsCount
1759 || (_fileProcess->size > 0
1760 && _fileProcess->offset >= _fileProcess->size)) {
1761 return;
1762 }
1763
1764 const auto offset = _fileProcess->offset;
1765 _fileProcess->requests.push_back({ offset });
1766 _fileProcess->requestId = fileRequest(
1767 _fileProcess->location,
1768 _fileProcess->offset
1769 ).done([=](const MTPupload_File &result) {
1770 _fileProcess->requestId = 0;
1771 filePartDone(offset, result);
1772 }).send();
1773 _fileProcess->offset += kFileChunkSize;
1774
1775 if (_fileProcess->size > 0
1776 && _fileProcess->requests.size() < kFileRequestsCount) {
1777 // Only one request at a time supported right now.
1778 //const auto runner = _runner;
1779 //crl::on_main([=] {
1780 // QTimer::singleShot(kFileNextRequestDelay, [=] {
1781 // runner([=] {
1782 // loadFilePart();
1783 // });
1784 // });
1785 //});
1786 }
1787 }
1788
filePartDone(int offset,const MTPupload_File & result)1789 void ApiWrap::filePartDone(int offset, const MTPupload_File &result) {
1790 Expects(_fileProcess != nullptr);
1791 Expects(!_fileProcess->requests.empty());
1792
1793 if (result.type() == mtpc_upload_fileCdnRedirect) {
1794 error("Cdn redirect is not supported.");
1795 return;
1796 }
1797 const auto &data = result.c_upload_file();
1798 if (data.vbytes().v.isEmpty()) {
1799 if (_fileProcess->size > 0) {
1800 error("Empty bytes received in file part.");
1801 return;
1802 }
1803 const auto result = _fileProcess->file.writeBlock({});
1804 if (!result) {
1805 ioError(result);
1806 return;
1807 }
1808 } else {
1809 using Request = FileProcess::Request;
1810 auto &requests = _fileProcess->requests;
1811 const auto i = ranges::find(
1812 requests,
1813 offset,
1814 [](const Request &request) { return request.offset; });
1815 Assert(i != end(requests));
1816
1817 i->bytes = data.vbytes().v;
1818
1819 auto &file = _fileProcess->file;
1820 while (!requests.empty() && !requests.front().bytes.isEmpty()) {
1821 const auto &bytes = requests.front().bytes;
1822 if (const auto result = file.writeBlock(bytes); !result) {
1823 ioError(result);
1824 return;
1825 }
1826 requests.pop_front();
1827 }
1828
1829 if (_fileProcess->progress) {
1830 _fileProcess->progress(FileProgress{
1831 file.size(),
1832 _fileProcess->size });
1833 }
1834
1835 if (!requests.empty()
1836 || !_fileProcess->size
1837 || _fileProcess->size > _fileProcess->offset) {
1838 loadFilePart();
1839 return;
1840 }
1841 }
1842
1843 auto process = base::take(_fileProcess);
1844 const auto relativePath = process->relativePath;
1845 _fileCache->save(process->location, relativePath);
1846 process->done(process->relativePath);
1847 }
1848
filePartRefreshReference(int offset)1849 void ApiWrap::filePartRefreshReference(int offset) {
1850 Expects(_fileProcess != nullptr);
1851 Expects(_fileProcess->requestId == 0);
1852
1853 const auto &origin = _fileProcess->origin;
1854 if (!origin.messageId) {
1855 error("FILE_REFERENCE error for non-message file.");
1856 return;
1857 }
1858 if (origin.peer.type() == mtpc_inputPeerChannel
1859 || origin.peer.type() == mtpc_inputPeerChannelFromMessage) {
1860 const auto channel = (origin.peer.type() == mtpc_inputPeerChannel)
1861 ? MTP_inputChannel(
1862 origin.peer.c_inputPeerChannel().vchannel_id(),
1863 origin.peer.c_inputPeerChannel().vaccess_hash())
1864 : MTP_inputChannelFromMessage(
1865 origin.peer.c_inputPeerChannelFromMessage().vpeer(),
1866 origin.peer.c_inputPeerChannelFromMessage().vmsg_id(),
1867 origin.peer.c_inputPeerChannelFromMessage().vchannel_id());
1868 _fileProcess->requestId = mainRequest(MTPchannels_GetMessages(
1869 channel,
1870 MTP_vector<MTPInputMessage>(
1871 1,
1872 MTP_inputMessageID(MTP_int(origin.messageId)))
1873 )).fail([=](const MTP::Error &error) {
1874 _fileProcess->requestId = 0;
1875 filePartUnavailable();
1876 return true;
1877 }).done([=](const MTPmessages_Messages &result) {
1878 _fileProcess->requestId = 0;
1879 filePartExtractReference(offset, result);
1880 }).send();
1881 } else {
1882 _fileProcess->requestId = splitRequest(
1883 origin.split,
1884 MTPmessages_GetMessages(
1885 MTP_vector<MTPInputMessage>(
1886 1,
1887 MTP_inputMessageID(MTP_int(origin.messageId)))
1888 )
1889 ).fail([=](const MTP::Error &error) {
1890 _fileProcess->requestId = 0;
1891 filePartUnavailable();
1892 return true;
1893 }).done([=](const MTPmessages_Messages &result) {
1894 _fileProcess->requestId = 0;
1895 filePartExtractReference(offset, result);
1896 }).send();
1897 }
1898 }
1899
filePartExtractReference(int offset,const MTPmessages_Messages & result)1900 void ApiWrap::filePartExtractReference(
1901 int offset,
1902 const MTPmessages_Messages &result) {
1903 Expects(_fileProcess != nullptr);
1904 Expects(_fileProcess->requestId == 0);
1905
1906 result.match([&](const MTPDmessages_messagesNotModified &data) {
1907 error("Unexpected messagesNotModified received.");
1908 }, [&](const auto &data) {
1909 Expects(_selfId.has_value());
1910
1911 auto context = Data::ParseMediaContext();
1912 context.selfPeerId = peerFromUser(*_selfId);
1913 const auto messages = Data::ParseMessagesSlice(
1914 context,
1915 data.vmessages(),
1916 data.vusers(),
1917 data.vchats(),
1918 _chatProcess->info.relativePath);
1919 for (const auto &message : messages.list) {
1920 if (message.id == _fileProcess->origin.messageId) {
1921 const auto refresh1 = Data::RefreshFileReference(
1922 _fileProcess->location,
1923 message.file().location);
1924 const auto refresh2 = Data::RefreshFileReference(
1925 _fileProcess->location,
1926 message.thumb().file.location);
1927 if (refresh1 || refresh2) {
1928 _fileProcess->requestId = fileRequest(
1929 _fileProcess->location,
1930 offset
1931 ).done([=](const MTPupload_File &result) {
1932 _fileProcess->requestId = 0;
1933 filePartDone(offset, result);
1934 }).send();
1935 return;
1936 }
1937 }
1938 }
1939 filePartUnavailable();
1940 });
1941 }
1942
filePartUnavailable()1943 void ApiWrap::filePartUnavailable() {
1944 Expects(_fileProcess != nullptr);
1945 Expects(!_fileProcess->requests.empty());
1946
1947 LOG(("Export Error: File unavailable."));
1948
1949 base::take(_fileProcess)->done(QString());
1950 }
1951
error(const MTP::Error & error)1952 void ApiWrap::error(const MTP::Error &error) {
1953 _errors.fire_copy(error);
1954 }
1955
error(const QString & text)1956 void ApiWrap::error(const QString &text) {
1957 error(MTP::Error(
1958 MTP_rpc_error(MTP_int(0), MTP_string("API_ERROR: " + text))));
1959 }
1960
ioError(const Output::Result & result)1961 void ApiWrap::ioError(const Output::Result &result) {
1962 _ioErrors.fire_copy(result);
1963 }
1964
1965 ApiWrap::~ApiWrap() = default;
1966
1967 } // namespace Export
1968