2105 lines
58 KiB
C++
2105 lines
58 KiB
C++
/*
|
|
This file is part of Telegram Desktop,
|
|
the official desktop application for the Telegram messaging service.
|
|
|
|
For license and copyright information please follow this link:
|
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|
*/
|
|
#include "mtproto/mtp_instance.h"
|
|
|
|
#include "mtproto/details/mtproto_dcenter.h"
|
|
#include "mtproto/details/mtproto_rsa_public_key.h"
|
|
#include "mtproto/special_config_request.h"
|
|
#include "mtproto/session.h"
|
|
#include "mtproto/mtproto_config.h"
|
|
#include "mtproto/mtproto_dc_options.h"
|
|
#include "mtproto/config_loader.h"
|
|
#include "mtproto/sender.h"
|
|
#include "storage/localstorage.h"
|
|
#include "calls/calls_instance.h"
|
|
#include "main/main_account.h" // Account::configUpdated.
|
|
#include "core/application.h"
|
|
#include "core/core_settings.h"
|
|
#include "lang/lang_instance.h"
|
|
#include "lang/lang_cloud_manager.h"
|
|
#include "base/unixtime.h"
|
|
#include "base/call_delayed.h"
|
|
#include "base/timer.h"
|
|
#include "base/network_reachability.h"
|
|
|
|
namespace MTP {
|
|
namespace {
|
|
|
|
constexpr auto kConfigBecomesOldIn = 2 * 60 * crl::time(1000);
|
|
constexpr auto kConfigBecomesOldForBlockedIn = 8 * crl::time(1000);
|
|
|
|
using namespace details;
|
|
|
|
std::atomic<int> GlobalAtomicRequestId = 0;
|
|
|
|
} // namespace
|
|
|
|
namespace details {
|
|
|
|
int GetNextRequestId() {
|
|
const auto result = ++GlobalAtomicRequestId;
|
|
if (result == std::numeric_limits<int>::max() / 2) {
|
|
GlobalAtomicRequestId = 0;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
} // namespace details
|
|
|
|
class Instance::Private : private Sender {
|
|
public:
|
|
Private(
|
|
not_null<Instance*> instance,
|
|
Instance::Mode mode,
|
|
Fields &&fields);
|
|
|
|
void start();
|
|
|
|
[[nodiscard]] Config &config() const;
|
|
[[nodiscard]] const ConfigFields &configValues() const;
|
|
[[nodiscard]] DcOptions &dcOptions() const;
|
|
[[nodiscard]] Environment environment() const;
|
|
[[nodiscard]] bool isTestMode() const;
|
|
|
|
void resolveProxyDomain(const QString &host);
|
|
void setGoodProxyDomain(const QString &host, const QString &ip);
|
|
void suggestMainDcId(DcId mainDcId);
|
|
void setMainDcId(DcId mainDcId);
|
|
[[nodiscard]] bool hasMainDcId() const;
|
|
[[nodiscard]] DcId mainDcId() const;
|
|
[[nodiscard]] rpl::producer<DcId> mainDcIdValue() const;
|
|
|
|
[[nodiscard]] rpl::producer<> writeKeysRequests() const;
|
|
|
|
void dcPersistentKeyChanged(DcId dcId, const AuthKeyPtr &persistentKey);
|
|
void dcTemporaryKeyChanged(DcId dcId);
|
|
[[nodiscard]] rpl::producer<DcId> dcTemporaryKeyChanged() const;
|
|
[[nodiscard]] AuthKeysList getKeysForWrite() const;
|
|
void addKeysForDestroy(AuthKeysList &&keys);
|
|
[[nodiscard]] rpl::producer<> allKeysDestroyed() const;
|
|
|
|
// Thread safe.
|
|
[[nodiscard]] QString deviceModel() const;
|
|
[[nodiscard]] QString systemVersion() const;
|
|
|
|
// Main thread.
|
|
void requestConfig();
|
|
void requestConfigIfOld();
|
|
void requestCDNConfig();
|
|
void setUserPhone(const QString &phone);
|
|
void badConfigurationError();
|
|
void syncHttpUnixtime();
|
|
|
|
void restartedByTimeout(ShiftedDcId shiftedDcId);
|
|
[[nodiscard]] rpl::producer<ShiftedDcId> restartsByTimeout() const;
|
|
|
|
[[nodiscard]] auto nonPremiumDelayedRequests() const
|
|
-> rpl::producer<mtpRequestId>;
|
|
|
|
void restart();
|
|
void restart(ShiftedDcId shiftedDcId);
|
|
[[nodiscard]] int32 dcstate(ShiftedDcId shiftedDcId = 0);
|
|
[[nodiscard]] QString dctransport(ShiftedDcId shiftedDcId = 0);
|
|
void ping();
|
|
void cancel(mtpRequestId requestId);
|
|
[[nodiscard]] int32 state(mtpRequestId requestId); // < 0 means waiting for such count of ms
|
|
void killSession(ShiftedDcId shiftedDcId);
|
|
void stopSession(ShiftedDcId shiftedDcId);
|
|
void reInitConnection(DcId dcId);
|
|
void logout(Fn<void()> done);
|
|
|
|
not_null<Dcenter*> getDcById(ShiftedDcId shiftedDcId);
|
|
Dcenter *findDc(ShiftedDcId shiftedDcId);
|
|
not_null<Dcenter*> addDc(
|
|
ShiftedDcId shiftedDcId,
|
|
AuthKeyPtr &&key = nullptr);
|
|
void removeDc(ShiftedDcId shiftedDcId);
|
|
|
|
void sendRequest(
|
|
mtpRequestId requestId,
|
|
SerializedRequest &&request,
|
|
ResponseHandler &&callbacks,
|
|
ShiftedDcId shiftedDcId,
|
|
crl::time msCanWait,
|
|
bool needsLayer,
|
|
mtpRequestId afterRequestId);
|
|
void registerRequest(mtpRequestId requestId, ShiftedDcId shiftedDcId);
|
|
void unregisterRequest(mtpRequestId requestId);
|
|
void storeRequest(
|
|
mtpRequestId requestId,
|
|
const SerializedRequest &request,
|
|
ResponseHandler &&callbacks);
|
|
SerializedRequest getRequest(mtpRequestId requestId);
|
|
[[nodiscard]] bool hasCallback(mtpRequestId requestId) const;
|
|
void processCallback(const Response &response);
|
|
void processUpdate(const Response &message);
|
|
|
|
void onStateChange(ShiftedDcId shiftedDcId, int32 state);
|
|
void onSessionReset(ShiftedDcId shiftedDcId);
|
|
|
|
// return true if need to clean request data
|
|
bool rpcErrorOccured(
|
|
const Response &response,
|
|
const FailHandler &onFail,
|
|
const Error &error);
|
|
inline bool rpcErrorOccured(
|
|
const Response &response,
|
|
const ResponseHandler &handler,
|
|
const Error &error) {
|
|
return rpcErrorOccured(response, handler.fail, error);
|
|
}
|
|
|
|
void setUpdatesHandler(Fn<void(const Response&)> handler);
|
|
void setGlobalFailHandler(
|
|
Fn<void(const Error&, const Response&)> handler);
|
|
void setStateChangedHandler(Fn<void(ShiftedDcId shiftedDcId, int32 state)> handler);
|
|
void setSessionResetHandler(Fn<void(ShiftedDcId shiftedDcId)> handler);
|
|
void clearGlobalHandlers();
|
|
|
|
[[nodiscard]] not_null<Session*> getSession(ShiftedDcId shiftedDcId);
|
|
|
|
bool isNormal() const {
|
|
return (_mode == Instance::Mode::Normal);
|
|
}
|
|
bool isKeysDestroyer() const {
|
|
return (_mode == Instance::Mode::KeysDestroyer);
|
|
}
|
|
|
|
void scheduleKeyDestroy(ShiftedDcId shiftedDcId);
|
|
void keyWasPossiblyDestroyed(ShiftedDcId shiftedDcId);
|
|
void performKeyDestroy(ShiftedDcId shiftedDcId);
|
|
void completedKeyDestroy(ShiftedDcId shiftedDcId);
|
|
void keyDestroyedOnServer(ShiftedDcId shiftedDcId, uint64 keyId);
|
|
|
|
void prepareToDestroy();
|
|
|
|
[[nodiscard]] rpl::lifetime &lifetime();
|
|
|
|
private:
|
|
void importDone(
|
|
const MTPauth_Authorization &result,
|
|
const Response &response);
|
|
bool importFail(const Error &error, const Response &response);
|
|
void exportDone(
|
|
const MTPauth_ExportedAuthorization &result,
|
|
const Response &response);
|
|
bool exportFail(const Error &error, const Response &response);
|
|
bool onErrorDefault(const Error &error, const Response &response);
|
|
|
|
void unpaused();
|
|
|
|
Session *findSession(ShiftedDcId shiftedDcId);
|
|
not_null<Session*> startSession(ShiftedDcId shiftedDcId);
|
|
void scheduleSessionDestroy(ShiftedDcId shiftedDcId);
|
|
[[nodiscard]] not_null<QThread*> getThreadForDc(ShiftedDcId shiftedDcId);
|
|
|
|
void applyDomainIps(
|
|
const QString &host,
|
|
const QStringList &ips,
|
|
crl::time expireAt);
|
|
|
|
void logoutGuestDcs();
|
|
bool logoutGuestDone(mtpRequestId requestId);
|
|
|
|
void requestConfigIfExpired();
|
|
void configLoadDone(const MTPConfig &result);
|
|
bool configLoadFail(const Error &error);
|
|
|
|
std::optional<ShiftedDcId> queryRequestByDc(
|
|
mtpRequestId requestId) const;
|
|
std::optional<ShiftedDcId> changeRequestByDc(
|
|
mtpRequestId requestId, DcId newdc);
|
|
|
|
void checkDelayedRequests();
|
|
|
|
const not_null<Instance*> _instance;
|
|
const Instance::Mode _mode = Instance::Mode::Normal;
|
|
const std::unique_ptr<Config> _config;
|
|
const std::shared_ptr<base::NetworkReachability> _networkReachability;
|
|
|
|
std::unique_ptr<QThread> _mainSessionThread;
|
|
std::unique_ptr<QThread> _otherSessionsThread;
|
|
std::vector<std::unique_ptr<QThread>> _fileSessionThreads;
|
|
|
|
QString _deviceModelDefault;
|
|
QString _systemVersion;
|
|
|
|
mutable QMutex _deviceModelMutex;
|
|
QString _customDeviceModel;
|
|
|
|
rpl::variable<DcId> _mainDcId = Fields::kDefaultMainDc;
|
|
bool _mainDcIdForced = false;
|
|
base::flat_map<DcId, std::unique_ptr<Dcenter>> _dcenters;
|
|
std::vector<std::unique_ptr<Dcenter>> _dcentersToDestroy;
|
|
rpl::event_stream<DcId> _dcTemporaryKeyChanged;
|
|
|
|
Session *_mainSession = nullptr;
|
|
base::flat_map<ShiftedDcId, std::unique_ptr<Session>> _sessions;
|
|
std::vector<std::unique_ptr<Session>> _sessionsToDestroy;
|
|
rpl::event_stream<ShiftedDcId> _restartsByTimeout;
|
|
|
|
std::unique_ptr<ConfigLoader> _configLoader;
|
|
std::unique_ptr<DomainResolver> _domainResolver;
|
|
std::unique_ptr<SpecialConfigRequest> _httpUnixtimeLoader;
|
|
QString _userPhone;
|
|
mtpRequestId _cdnConfigLoadRequestId = 0;
|
|
crl::time _lastConfigLoadedTime = 0;
|
|
crl::time _configExpiresAt = 0;
|
|
|
|
base::flat_map<DcId, AuthKeyPtr> _keysForWrite;
|
|
base::flat_map<ShiftedDcId, mtpRequestId> _logoutGuestRequestIds;
|
|
|
|
rpl::event_stream<> _writeKeysRequests;
|
|
rpl::event_stream<> _allKeysDestroyed;
|
|
|
|
// holds dcWithShift for request to this dc or -dc for request to main dc
|
|
std::map<mtpRequestId, ShiftedDcId> _requestsByDc;
|
|
mutable QMutex _requestByDcLock;
|
|
|
|
// holds target dcWithShift for auth export request
|
|
std::map<mtpRequestId, ShiftedDcId> _authExportRequests;
|
|
|
|
std::map<mtpRequestId, ResponseHandler> _parserMap;
|
|
mutable QMutex _parserMapLock;
|
|
|
|
std::map<mtpRequestId, SerializedRequest> _requestMap;
|
|
QReadWriteLock _requestMapLock;
|
|
|
|
std::deque<std::pair<mtpRequestId, crl::time>> _delayedRequests;
|
|
base::flat_map<mtpRequestId, mtpRequestId> _dependentRequests;
|
|
mutable QMutex _dependentRequestsLock;
|
|
|
|
std::map<mtpRequestId, int> _requestsDelays;
|
|
|
|
std::set<mtpRequestId> _badGuestDcRequests;
|
|
|
|
std::map<DcId, std::vector<mtpRequestId>> _authWaiters;
|
|
|
|
Fn<void(const Response&)> _updatesHandler;
|
|
Fn<void(const Error&, const Response&)> _globalFailHandler;
|
|
Fn<void(ShiftedDcId shiftedDcId, int32 state)> _stateChangedHandler;
|
|
Fn<void(ShiftedDcId shiftedDcId)> _sessionResetHandler;
|
|
|
|
rpl::event_stream<mtpRequestId> _nonPremiumDelayedRequests;
|
|
|
|
base::Timer _checkDelayedTimer;
|
|
|
|
Core::SettingsProxy &_proxySettings;
|
|
|
|
rpl::lifetime _lifetime;
|
|
|
|
};
|
|
|
|
Instance::Fields::Fields() = default;
|
|
|
|
Instance::Fields::Fields(Fields &&other) = default;
|
|
|
|
auto Instance::Fields::operator=(Fields &&other) -> Fields & = default;
|
|
|
|
Instance::Fields::~Fields() = default;
|
|
|
|
Instance::Private::Private(
|
|
not_null<Instance*> instance,
|
|
Instance::Mode mode,
|
|
Fields &&fields)
|
|
: Sender(instance)
|
|
, _instance(instance)
|
|
, _mode(mode)
|
|
, _config(std::move(fields.config))
|
|
, _networkReachability(base::NetworkReachability::Instance())
|
|
, _proxySettings(Core::App().settings().proxy()) {
|
|
Expects(_config != nullptr);
|
|
|
|
const auto idealThreadPoolSize = QThread::idealThreadCount();
|
|
_fileSessionThreads.resize(2 * std::max(idealThreadPoolSize / 2, 1));
|
|
|
|
details::unpaused(
|
|
) | rpl::start_with_next([=] {
|
|
unpaused();
|
|
}, _lifetime);
|
|
|
|
_networkReachability->availableChanges(
|
|
) | rpl::start_with_next([=](bool available) {
|
|
restart();
|
|
}, _lifetime);
|
|
|
|
_deviceModelDefault = std::move(fields.deviceModel);
|
|
_systemVersion = std::move(fields.systemVersion);
|
|
|
|
_customDeviceModel = Core::App().settings().customDeviceModel();
|
|
Core::App().settings().customDeviceModelChanges(
|
|
) | rpl::start_with_next([=](const QString &value) {
|
|
QMutexLocker lock(&_deviceModelMutex);
|
|
_customDeviceModel = value;
|
|
lock.unlock();
|
|
|
|
reInitConnection(mainDcId());
|
|
}, _lifetime);
|
|
|
|
for (auto &key : fields.keys) {
|
|
auto dcId = key->dcId();
|
|
auto shiftedDcId = dcId;
|
|
if (isKeysDestroyer()) {
|
|
shiftedDcId = MTP::destroyKeyNextDcId(shiftedDcId);
|
|
|
|
// There could be several keys for one dc if we're destroying them.
|
|
// Place them all in separate shiftedDcId so that they won't conflict.
|
|
while (_keysForWrite.find(shiftedDcId) != _keysForWrite.cend()) {
|
|
shiftedDcId = MTP::destroyKeyNextDcId(shiftedDcId);
|
|
}
|
|
}
|
|
_keysForWrite[shiftedDcId] = key;
|
|
addDc(shiftedDcId, std::move(key));
|
|
}
|
|
|
|
if (fields.mainDcId != Fields::kNotSetMainDc) {
|
|
_mainDcId = fields.mainDcId;
|
|
_mainDcIdForced = true;
|
|
}
|
|
|
|
_proxySettings.connectionTypeChanges(
|
|
) | rpl::start_with_next([=] {
|
|
if (_configLoader) {
|
|
_configLoader->setProxyEnabled(_proxySettings.isEnabled());
|
|
}
|
|
}, _lifetime);
|
|
}
|
|
|
|
void Instance::Private::start() {
|
|
if (isKeysDestroyer()) {
|
|
for (const auto &[shiftedDcId, dc] : _dcenters) {
|
|
startSession(shiftedDcId);
|
|
}
|
|
} else if (hasMainDcId()) {
|
|
_mainSession = startSession(mainDcId());
|
|
}
|
|
|
|
_checkDelayedTimer.setCallback([this] { checkDelayedRequests(); });
|
|
|
|
Assert(!hasMainDcId() == isKeysDestroyer());
|
|
requestConfig();
|
|
}
|
|
|
|
void Instance::Private::resolveProxyDomain(const QString &host) {
|
|
if (!_domainResolver) {
|
|
_domainResolver = std::make_unique<DomainResolver>([=](
|
|
const QString &host,
|
|
const QStringList &ips,
|
|
crl::time expireAt) {
|
|
applyDomainIps(host, ips, expireAt);
|
|
});
|
|
}
|
|
_domainResolver->resolve(host);
|
|
}
|
|
|
|
void Instance::Private::applyDomainIps(
|
|
const QString &host,
|
|
const QStringList &ips,
|
|
crl::time expireAt) {
|
|
const auto applyToProxy = [&](ProxyData &proxy) {
|
|
if (!proxy.tryCustomResolve() || proxy.host != host) {
|
|
return false;
|
|
}
|
|
proxy.resolvedExpireAt = expireAt;
|
|
auto copy = ips;
|
|
auto ¤t = proxy.resolvedIPs;
|
|
const auto i = ranges::remove_if(current, [&](const QString &ip) {
|
|
const auto index = copy.indexOf(ip);
|
|
if (index < 0) {
|
|
return true;
|
|
}
|
|
copy.removeAt(index);
|
|
return false;
|
|
});
|
|
if (i == end(current) && copy.isEmpty()) {
|
|
// Even if the proxy was changed already, we still want
|
|
// to refreshOptions in all sessions across all instances.
|
|
return true;
|
|
}
|
|
current.erase(i, end(current));
|
|
for (const auto &ip : std::as_const(copy)) {
|
|
proxy.resolvedIPs.push_back(ip);
|
|
}
|
|
return true;
|
|
};
|
|
for (auto &proxy : _proxySettings.list()) {
|
|
applyToProxy(proxy);
|
|
}
|
|
auto selected = _proxySettings.selected();
|
|
if (applyToProxy(selected) && _proxySettings.isEnabled()) {
|
|
_proxySettings.setSelected(selected);
|
|
for (const auto &[shiftedDcId, session] : _sessions) {
|
|
session->refreshOptions();
|
|
}
|
|
}
|
|
_instance->proxyDomainResolved(host, ips, expireAt);
|
|
}
|
|
|
|
void Instance::Private::setGoodProxyDomain(
|
|
const QString &host,
|
|
const QString &ip) {
|
|
const auto applyToProxy = [&](ProxyData &proxy) {
|
|
if (!proxy.tryCustomResolve() || proxy.host != host) {
|
|
return false;
|
|
}
|
|
auto ¤t = proxy.resolvedIPs;
|
|
auto i = ranges::find(current, ip);
|
|
if (i == end(current) || i == begin(current)) {
|
|
return false;
|
|
}
|
|
while (i != begin(current)) {
|
|
const auto j = i--;
|
|
std::swap(*i, *j);
|
|
}
|
|
return true;
|
|
};
|
|
for (auto &proxy : _proxySettings.list()) {
|
|
applyToProxy(proxy);
|
|
}
|
|
|
|
auto selected = _proxySettings.selected();
|
|
if (applyToProxy(selected) && _proxySettings.isEnabled()) {
|
|
_proxySettings.setSelected(selected);
|
|
Core::App().refreshGlobalProxy();
|
|
}
|
|
}
|
|
|
|
void Instance::Private::suggestMainDcId(DcId mainDcId) {
|
|
if (!_mainDcIdForced) {
|
|
setMainDcId(mainDcId);
|
|
}
|
|
}
|
|
|
|
void Instance::Private::setMainDcId(DcId mainDcId) {
|
|
if (!_mainSession) {
|
|
LOG(("MTP Error: attempting to change mainDcId in an MTP instance without main session."));
|
|
return;
|
|
}
|
|
|
|
_mainDcIdForced = true;
|
|
const auto oldMainDcId = _mainSession->getDcWithShift();
|
|
if (oldMainDcId != mainDcId) {
|
|
scheduleSessionDestroy(oldMainDcId);
|
|
scheduleSessionDestroy(mainDcId);
|
|
_mainSession = startSession(mainDcId);
|
|
}
|
|
_mainDcId = mainDcId;
|
|
_writeKeysRequests.fire({});
|
|
}
|
|
|
|
bool Instance::Private::hasMainDcId() const {
|
|
return (_mainDcId.current() != Fields::kNoneMainDc);
|
|
}
|
|
|
|
DcId Instance::Private::mainDcId() const {
|
|
Expects(hasMainDcId());
|
|
|
|
return _mainDcId.current();
|
|
}
|
|
|
|
rpl::producer<DcId> Instance::Private::mainDcIdValue() const {
|
|
Expects(_mainDcId.current() != Fields::kNoneMainDc);
|
|
|
|
return _mainDcId.value();
|
|
}
|
|
|
|
void Instance::Private::requestConfig() {
|
|
if (_configLoader || isKeysDestroyer()) {
|
|
return;
|
|
}
|
|
_configLoader = std::make_unique<ConfigLoader>(
|
|
_instance,
|
|
_userPhone,
|
|
[=](const MTPConfig &result) { configLoadDone(result); },
|
|
[=](const Error &error, const Response &) {
|
|
return configLoadFail(error);
|
|
},
|
|
_proxySettings.isEnabled());
|
|
_configLoader->load();
|
|
}
|
|
|
|
void Instance::Private::setUserPhone(const QString &phone) {
|
|
if (_userPhone != phone) {
|
|
_userPhone = phone;
|
|
if (_configLoader) {
|
|
_configLoader->setPhone(_userPhone);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Instance::Private::badConfigurationError() {
|
|
if (_mode == Mode::Normal) {
|
|
Core::App().badMtprotoConfigurationError();
|
|
}
|
|
}
|
|
|
|
void Instance::Private::syncHttpUnixtime() {
|
|
if (base::unixtime::http_valid() || _httpUnixtimeLoader) {
|
|
return;
|
|
}
|
|
_httpUnixtimeLoader = std::make_unique<SpecialConfigRequest>([=] {
|
|
InvokeQueued(_instance, [=] {
|
|
_httpUnixtimeLoader = nullptr;
|
|
});
|
|
}, isTestMode(), configValues().txtDomainString);
|
|
}
|
|
|
|
void Instance::Private::restartedByTimeout(ShiftedDcId shiftedDcId) {
|
|
_restartsByTimeout.fire_copy(shiftedDcId);
|
|
}
|
|
|
|
rpl::producer<ShiftedDcId> Instance::Private::restartsByTimeout() const {
|
|
return _restartsByTimeout.events();
|
|
}
|
|
|
|
auto Instance::Private::nonPremiumDelayedRequests() const
|
|
-> rpl::producer<mtpRequestId> {
|
|
return _nonPremiumDelayedRequests.events();
|
|
}
|
|
|
|
void Instance::Private::requestConfigIfOld() {
|
|
const auto timeout = _config->values().blockedMode
|
|
? kConfigBecomesOldForBlockedIn
|
|
: kConfigBecomesOldIn;
|
|
if (crl::now() - _lastConfigLoadedTime >= timeout) {
|
|
requestConfig();
|
|
}
|
|
}
|
|
|
|
void Instance::Private::requestConfigIfExpired() {
|
|
const auto requestIn = (_configExpiresAt - crl::now());
|
|
if (requestIn > 0) {
|
|
base::call_delayed(
|
|
std::min(requestIn, 3600 * crl::time(1000)),
|
|
_instance,
|
|
[=] { requestConfigIfExpired(); });
|
|
} else {
|
|
requestConfig();
|
|
}
|
|
}
|
|
|
|
void Instance::Private::requestCDNConfig() {
|
|
if (_cdnConfigLoadRequestId || !hasMainDcId()) {
|
|
return;
|
|
}
|
|
_cdnConfigLoadRequestId = request(
|
|
MTPhelp_GetCdnConfig()
|
|
).done([this](const MTPCdnConfig &result) {
|
|
_cdnConfigLoadRequestId = 0;
|
|
result.match([&](const MTPDcdnConfig &data) {
|
|
dcOptions().setCDNConfig(data);
|
|
});
|
|
Local::writeSettings();
|
|
}).send();
|
|
}
|
|
|
|
void Instance::Private::restart() {
|
|
for (const auto &[shiftedDcId, session] : _sessions) {
|
|
session->restart();
|
|
}
|
|
}
|
|
|
|
void Instance::Private::restart(ShiftedDcId shiftedDcId) {
|
|
const auto dcId = BareDcId(shiftedDcId);
|
|
for (const auto &[shiftedDcId, session] : _sessions) {
|
|
if (BareDcId(shiftedDcId) == dcId) {
|
|
session->restart();
|
|
}
|
|
}
|
|
}
|
|
|
|
int32 Instance::Private::dcstate(ShiftedDcId shiftedDcId) {
|
|
if (!shiftedDcId) {
|
|
Assert(_mainSession != nullptr);
|
|
return _mainSession->getState();
|
|
}
|
|
|
|
if (!BareDcId(shiftedDcId)) {
|
|
Assert(_mainSession != nullptr);
|
|
shiftedDcId += BareDcId(_mainSession->getDcWithShift());
|
|
}
|
|
|
|
if (const auto session = findSession(shiftedDcId)) {
|
|
return session->getState();
|
|
}
|
|
return DisconnectedState;
|
|
}
|
|
|
|
QString Instance::Private::dctransport(ShiftedDcId shiftedDcId) {
|
|
if (!shiftedDcId) {
|
|
Assert(_mainSession != nullptr);
|
|
return _mainSession->transport();
|
|
}
|
|
if (!BareDcId(shiftedDcId)) {
|
|
Assert(_mainSession != nullptr);
|
|
shiftedDcId += BareDcId(_mainSession->getDcWithShift());
|
|
}
|
|
|
|
if (const auto session = findSession(shiftedDcId)) {
|
|
return session->transport();
|
|
}
|
|
return QString();
|
|
}
|
|
|
|
void Instance::Private::ping() {
|
|
getSession(0)->ping();
|
|
}
|
|
|
|
void Instance::Private::cancel(mtpRequestId requestId) {
|
|
if (!requestId) return;
|
|
|
|
DEBUG_LOG(("MTP Info: Cancel request %1.").arg(requestId));
|
|
const auto shiftedDcId = queryRequestByDc(requestId);
|
|
auto msgId = mtpMsgId(0);
|
|
{
|
|
QWriteLocker locker(&_requestMapLock);
|
|
auto it = _requestMap.find(requestId);
|
|
if (it != _requestMap.end()) {
|
|
msgId = *(mtpMsgId*)(it->second->constData() + 4);
|
|
_requestMap.erase(it);
|
|
}
|
|
}
|
|
unregisterRequest(requestId);
|
|
if (shiftedDcId) {
|
|
const auto session = getSession(qAbs(*shiftedDcId));
|
|
session->cancel(requestId, msgId);
|
|
}
|
|
|
|
QMutexLocker locker(&_parserMapLock);
|
|
_parserMap.erase(requestId);
|
|
}
|
|
|
|
// result < 0 means waiting for such count of ms.
|
|
int32 Instance::Private::state(mtpRequestId requestId) {
|
|
if (requestId > 0) {
|
|
if (const auto shiftedDcId = queryRequestByDc(requestId)) {
|
|
const auto session = getSession(qAbs(*shiftedDcId));
|
|
return session->requestState(requestId);
|
|
}
|
|
return MTP::RequestSent;
|
|
}
|
|
const auto session = getSession(-requestId);
|
|
return session->requestState(0);
|
|
}
|
|
|
|
void Instance::Private::killSession(ShiftedDcId shiftedDcId) {
|
|
const auto i = _sessions.find(shiftedDcId);
|
|
if (i != _sessions.cend()) {
|
|
Assert(i->second.get() != _mainSession);
|
|
}
|
|
scheduleSessionDestroy(shiftedDcId);
|
|
}
|
|
|
|
void Instance::Private::stopSession(ShiftedDcId shiftedDcId) {
|
|
if (const auto session = findSession(shiftedDcId)) {
|
|
if (session != _mainSession) { // don't stop main session
|
|
session->stop();
|
|
}
|
|
}
|
|
}
|
|
|
|
void Instance::Private::reInitConnection(DcId dcId) {
|
|
for (const auto &[shiftedDcId, session] : _sessions) {
|
|
if (BareDcId(shiftedDcId) == dcId) {
|
|
session->reInitConnection();
|
|
}
|
|
}
|
|
}
|
|
|
|
void Instance::Private::logout(Fn<void()> done) {
|
|
_instance->send(MTPauth_LogOut(), [=](Response) {
|
|
done();
|
|
return true;
|
|
}, [=](const Error&, Response) {
|
|
done();
|
|
return true;
|
|
});
|
|
logoutGuestDcs();
|
|
}
|
|
|
|
void Instance::Private::logoutGuestDcs() {
|
|
Expects(!isKeysDestroyer());
|
|
|
|
auto dcIds = std::vector<DcId>();
|
|
dcIds.reserve(_keysForWrite.size());
|
|
for (const auto &key : _keysForWrite) {
|
|
dcIds.push_back(key.first);
|
|
}
|
|
for (const auto dcId : dcIds) {
|
|
if (dcId == mainDcId() || dcOptions().dcType(dcId) == DcType::Cdn) {
|
|
continue;
|
|
}
|
|
const auto shiftedDcId = MTP::logoutDcId(dcId);
|
|
const auto requestId = _instance->send(MTPauth_LogOut(), [=](
|
|
const Response &response) {
|
|
logoutGuestDone(response.requestId);
|
|
return true;
|
|
}, [=](const Error &, const Response &response) {
|
|
logoutGuestDone(response.requestId);
|
|
return true;
|
|
}, shiftedDcId);
|
|
_logoutGuestRequestIds.emplace(shiftedDcId, requestId);
|
|
}
|
|
}
|
|
|
|
bool Instance::Private::logoutGuestDone(mtpRequestId requestId) {
|
|
for (auto i = _logoutGuestRequestIds.begin(), e = _logoutGuestRequestIds.end(); i != e; ++i) {
|
|
if (i->second == requestId) {
|
|
killSession(i->first);
|
|
_logoutGuestRequestIds.erase(i);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
Dcenter *Instance::Private::findDc(ShiftedDcId shiftedDcId) {
|
|
const auto i = _dcenters.find(shiftedDcId);
|
|
return (i != _dcenters.end()) ? i->second.get() : nullptr;
|
|
}
|
|
|
|
not_null<Dcenter*> Instance::Private::addDc(
|
|
ShiftedDcId shiftedDcId,
|
|
AuthKeyPtr &&key) {
|
|
const auto dcId = BareDcId(shiftedDcId);
|
|
return _dcenters.emplace(
|
|
shiftedDcId,
|
|
std::make_unique<Dcenter>(dcId, std::move(key))
|
|
).first->second.get();
|
|
}
|
|
|
|
void Instance::Private::removeDc(ShiftedDcId shiftedDcId) {
|
|
const auto i = _dcenters.find(shiftedDcId);
|
|
if (i != _dcenters.end()) {
|
|
_dcentersToDestroy.push_back(std::move(i->second));
|
|
_dcenters.erase(i);
|
|
}
|
|
}
|
|
|
|
not_null<Dcenter*> Instance::Private::getDcById(
|
|
ShiftedDcId shiftedDcId) {
|
|
if (const auto result = findDc(shiftedDcId)) {
|
|
return result;
|
|
}
|
|
const auto dcId = [&] {
|
|
const auto result = BareDcId(shiftedDcId);
|
|
if (isTemporaryDcId(result)) {
|
|
if (const auto realDcId = getRealIdFromTemporaryDcId(result)) {
|
|
return realDcId;
|
|
}
|
|
}
|
|
return result;
|
|
}();
|
|
if (dcId != shiftedDcId) {
|
|
if (const auto result = findDc(dcId)) {
|
|
return result;
|
|
}
|
|
}
|
|
return addDc(dcId);
|
|
}
|
|
|
|
void Instance::Private::dcPersistentKeyChanged(
|
|
DcId dcId,
|
|
const AuthKeyPtr &persistentKey) {
|
|
dcTemporaryKeyChanged(dcId);
|
|
|
|
if (isTemporaryDcId(dcId)) {
|
|
return;
|
|
}
|
|
|
|
const auto i = _keysForWrite.find(dcId);
|
|
if (i != _keysForWrite.end() && i->second == persistentKey) {
|
|
return;
|
|
} else if (i == _keysForWrite.end() && !persistentKey) {
|
|
return;
|
|
}
|
|
if (!persistentKey) {
|
|
_keysForWrite.erase(i);
|
|
} else if (i != _keysForWrite.end()) {
|
|
i->second = persistentKey;
|
|
} else {
|
|
_keysForWrite.emplace(dcId, persistentKey);
|
|
}
|
|
DEBUG_LOG(("AuthKey Info: writing auth keys, called by dc %1"
|
|
).arg(dcId));
|
|
_writeKeysRequests.fire({});
|
|
}
|
|
|
|
void Instance::Private::dcTemporaryKeyChanged(DcId dcId) {
|
|
_dcTemporaryKeyChanged.fire_copy(dcId);
|
|
}
|
|
|
|
rpl::producer<DcId> Instance::Private::dcTemporaryKeyChanged() const {
|
|
return _dcTemporaryKeyChanged.events();
|
|
}
|
|
|
|
AuthKeysList Instance::Private::getKeysForWrite() const {
|
|
auto result = AuthKeysList();
|
|
|
|
result.reserve(_keysForWrite.size());
|
|
for (const auto &key : _keysForWrite) {
|
|
result.push_back(key.second);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void Instance::Private::addKeysForDestroy(AuthKeysList &&keys) {
|
|
Expects(isKeysDestroyer());
|
|
|
|
for (auto &key : keys) {
|
|
const auto dcId = key->dcId();
|
|
auto shiftedDcId = MTP::destroyKeyNextDcId(dcId);
|
|
|
|
// There could be several keys for one dc if we're destroying them.
|
|
// Place them all in separate shiftedDcId so that they won't conflict.
|
|
while (_keysForWrite.find(shiftedDcId) != _keysForWrite.cend()) {
|
|
shiftedDcId = MTP::destroyKeyNextDcId(shiftedDcId);
|
|
}
|
|
_keysForWrite[shiftedDcId] = key;
|
|
|
|
addDc(shiftedDcId, std::move(key));
|
|
startSession(shiftedDcId);
|
|
}
|
|
}
|
|
|
|
rpl::producer<> Instance::Private::allKeysDestroyed() const {
|
|
return _allKeysDestroyed.events();
|
|
}
|
|
|
|
rpl::producer<> Instance::Private::writeKeysRequests() const {
|
|
return _writeKeysRequests.events();
|
|
}
|
|
|
|
Config &Instance::Private::config() const {
|
|
return *_config;
|
|
}
|
|
|
|
const ConfigFields &Instance::Private::configValues() const {
|
|
return _config->values();
|
|
}
|
|
|
|
DcOptions &Instance::Private::dcOptions() const {
|
|
return _config->dcOptions();
|
|
}
|
|
|
|
Environment Instance::Private::environment() const {
|
|
return _config->environment();
|
|
}
|
|
|
|
bool Instance::Private::isTestMode() const {
|
|
return _config->isTestMode();
|
|
}
|
|
|
|
QString Instance::Private::deviceModel() const {
|
|
QMutexLocker lock(&_deviceModelMutex);
|
|
return _customDeviceModel.isEmpty()
|
|
? _deviceModelDefault
|
|
: _customDeviceModel;
|
|
}
|
|
|
|
QString Instance::Private::systemVersion() const {
|
|
return _systemVersion;
|
|
}
|
|
|
|
void Instance::Private::unpaused() {
|
|
for (const auto &[shiftedDcId, session] : _sessions) {
|
|
session->unpaused();
|
|
}
|
|
}
|
|
|
|
void Instance::Private::configLoadDone(const MTPConfig &result) {
|
|
Expects(result.type() == mtpc_config);
|
|
|
|
_configLoader.reset();
|
|
_lastConfigLoadedTime = crl::now();
|
|
|
|
const auto &data = result.c_config();
|
|
_config->apply(data);
|
|
|
|
const auto lang = qs(data.vsuggested_lang_code().value_or_empty());
|
|
Lang::CurrentCloudManager().setSuggestedLanguage(lang);
|
|
Lang::CurrentCloudManager().setCurrentVersions(
|
|
data.vlang_pack_version().value_or_empty(),
|
|
data.vbase_lang_pack_version().value_or_empty());
|
|
if (const auto prefix = data.vautoupdate_url_prefix()) {
|
|
Local::writeAutoupdatePrefix(qs(*prefix));
|
|
}
|
|
|
|
_configExpiresAt = crl::now()
|
|
+ (data.vexpires().v - base::unixtime::now()) * crl::time(1000);
|
|
requestConfigIfExpired();
|
|
}
|
|
|
|
bool Instance::Private::configLoadFail(const Error &error) {
|
|
if (IsDefaultHandledError(error)) return false;
|
|
|
|
// loadingConfig = false;
|
|
LOG(("MTP Error: failed to get config!"));
|
|
return false;
|
|
}
|
|
|
|
std::optional<ShiftedDcId> Instance::Private::queryRequestByDc(
|
|
mtpRequestId requestId) const {
|
|
QMutexLocker locker(&_requestByDcLock);
|
|
auto it = _requestsByDc.find(requestId);
|
|
if (it != _requestsByDc.cend()) {
|
|
return it->second;
|
|
}
|
|
return std::nullopt;
|
|
}
|
|
|
|
std::optional<ShiftedDcId> Instance::Private::changeRequestByDc(
|
|
mtpRequestId requestId,
|
|
DcId newdc) {
|
|
QMutexLocker locker(&_requestByDcLock);
|
|
auto it = _requestsByDc.find(requestId);
|
|
if (it != _requestsByDc.cend()) {
|
|
if (it->second < 0) {
|
|
it->second = -newdc;
|
|
} else {
|
|
it->second = ShiftDcId(newdc, GetDcIdShift(it->second));
|
|
}
|
|
return it->second;
|
|
}
|
|
return std::nullopt;
|
|
}
|
|
|
|
void Instance::Private::checkDelayedRequests() {
|
|
auto now = crl::now();
|
|
while (!_delayedRequests.empty() && now >= _delayedRequests.front().second) {
|
|
auto requestId = _delayedRequests.front().first;
|
|
_delayedRequests.pop_front();
|
|
|
|
auto dcWithShift = ShiftedDcId(0);
|
|
if (const auto shiftedDcId = queryRequestByDc(requestId)) {
|
|
dcWithShift = *shiftedDcId;
|
|
} else {
|
|
LOG(("MTP Error: could not find request dc for delayed resend, requestId %1").arg(requestId));
|
|
continue;
|
|
}
|
|
|
|
auto request = SerializedRequest();
|
|
{
|
|
QReadLocker locker(&_requestMapLock);
|
|
auto it = _requestMap.find(requestId);
|
|
if (it == _requestMap.cend()) {
|
|
DEBUG_LOG(("MTP Error: could not find request %1").arg(requestId));
|
|
continue;
|
|
}
|
|
request = it->second;
|
|
}
|
|
const auto session = getSession(qAbs(dcWithShift));
|
|
session->sendPrepared(request);
|
|
}
|
|
|
|
if (!_delayedRequests.empty()) {
|
|
_checkDelayedTimer.callOnce(_delayedRequests.front().second - now);
|
|
}
|
|
}
|
|
|
|
void Instance::Private::sendRequest(
|
|
mtpRequestId requestId,
|
|
SerializedRequest &&request,
|
|
ResponseHandler &&callbacks,
|
|
ShiftedDcId shiftedDcId,
|
|
crl::time msCanWait,
|
|
bool needsLayer,
|
|
mtpRequestId afterRequestId) {
|
|
const auto session = getSession(shiftedDcId);
|
|
|
|
request->requestId = requestId;
|
|
storeRequest(requestId, request, std::move(callbacks));
|
|
|
|
const auto toMainDc = (shiftedDcId == 0);
|
|
const auto realShiftedDcId = session->getDcWithShift();
|
|
const auto signedDcId = toMainDc ? -realShiftedDcId : realShiftedDcId;
|
|
registerRequest(requestId, signedDcId);
|
|
|
|
if (afterRequestId) {
|
|
request->after = getRequest(afterRequestId);
|
|
}
|
|
request->lastSentTime = crl::now();
|
|
request->needsLayer = needsLayer;
|
|
|
|
session->sendPrepared(request, msCanWait);
|
|
}
|
|
|
|
void Instance::Private::registerRequest(
|
|
mtpRequestId requestId,
|
|
ShiftedDcId shiftedDcId) {
|
|
QMutexLocker locker(&_requestByDcLock);
|
|
_requestsByDc[requestId] = shiftedDcId;
|
|
}
|
|
|
|
void Instance::Private::unregisterRequest(mtpRequestId requestId) {
|
|
DEBUG_LOG(("MTP Info: unregistering request %1.").arg(requestId));
|
|
|
|
_requestsDelays.erase(requestId);
|
|
|
|
{
|
|
QWriteLocker locker(&_requestMapLock);
|
|
_requestMap.erase(requestId);
|
|
}
|
|
{
|
|
QMutexLocker locker(&_requestByDcLock);
|
|
_requestsByDc.erase(requestId);
|
|
}
|
|
{
|
|
auto toRemove = base::flat_set<mtpRequestId>();
|
|
auto toResend = base::flat_set<mtpRequestId>();
|
|
|
|
toRemove.emplace(requestId);
|
|
|
|
QMutexLocker locker(&_dependentRequestsLock);
|
|
|
|
auto handling = 0;
|
|
do {
|
|
handling = toResend.size();
|
|
for (const auto &[resendingId, afterId] : _dependentRequests) {
|
|
if (toRemove.contains(afterId)) {
|
|
toRemove.emplace(resendingId);
|
|
toResend.emplace(resendingId);
|
|
}
|
|
}
|
|
} while (handling != toResend.size());
|
|
|
|
for (const auto removingId : toRemove) {
|
|
_dependentRequests.remove(removingId);
|
|
}
|
|
locker.unlock();
|
|
|
|
for (const auto resendingId : toResend) {
|
|
if (const auto shiftedDcId = queryRequestByDc(resendingId)) {
|
|
SerializedRequest request;
|
|
{
|
|
QReadLocker locker(&_requestMapLock);
|
|
auto it = _requestMap.find(resendingId);
|
|
if (it == _requestMap.cend()) {
|
|
LOG(("MTP Error: could not find dependent request %1").arg(resendingId));
|
|
return;
|
|
}
|
|
request = it->second;
|
|
}
|
|
getSession(qAbs(*shiftedDcId))->sendPrepared(request);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Instance::Private::storeRequest(
|
|
mtpRequestId requestId,
|
|
const SerializedRequest &request,
|
|
ResponseHandler &&callbacks) {
|
|
if (callbacks.done || callbacks.fail) {
|
|
QMutexLocker locker(&_parserMapLock);
|
|
_parserMap.emplace(requestId, std::move(callbacks));
|
|
}
|
|
{
|
|
QWriteLocker locker(&_requestMapLock);
|
|
_requestMap.emplace(requestId, request);
|
|
}
|
|
}
|
|
|
|
SerializedRequest Instance::Private::getRequest(mtpRequestId requestId) {
|
|
auto result = SerializedRequest();
|
|
{
|
|
QReadLocker locker(&_requestMapLock);
|
|
auto it = _requestMap.find(requestId);
|
|
if (it != _requestMap.cend()) {
|
|
result = it->second;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool Instance::Private::hasCallback(mtpRequestId requestId) const {
|
|
QMutexLocker locker(&_parserMapLock);
|
|
auto it = _parserMap.find(requestId);
|
|
return (it != _parserMap.cend());
|
|
}
|
|
|
|
void Instance::Private::processCallback(const Response &response) {
|
|
const auto requestId = response.requestId;
|
|
ResponseHandler handler;
|
|
{
|
|
QMutexLocker locker(&_parserMapLock);
|
|
auto it = _parserMap.find(requestId);
|
|
if (it != _parserMap.cend()) {
|
|
handler = std::move(it->second);
|
|
_parserMap.erase(it);
|
|
|
|
DEBUG_LOG(("RPC Info: found parser for request %1, trying to parse response...").arg(requestId));
|
|
}
|
|
}
|
|
if (handler.done || handler.fail) {
|
|
const auto handleError = [&](const Error &error) {
|
|
DEBUG_LOG(("RPC Info: "
|
|
"error received, code %1, type %2, description: %3").arg(
|
|
QString::number(error.code()),
|
|
error.type(),
|
|
error.description()));
|
|
const auto guard = QPointer<Instance>(_instance);
|
|
if (rpcErrorOccured(response, handler, error) && guard) {
|
|
unregisterRequest(requestId);
|
|
} else if (guard) {
|
|
QMutexLocker locker(&_parserMapLock);
|
|
_parserMap.emplace(requestId, std::move(handler));
|
|
}
|
|
};
|
|
|
|
auto from = response.reply.constData();
|
|
if (response.reply.isEmpty()) {
|
|
handleError(Error::Local(
|
|
"RESPONSE_PARSE_FAILED",
|
|
"Empty response."));
|
|
} else if (*from == mtpc_rpc_error) {
|
|
auto error = MTPRpcError();
|
|
handleError(
|
|
Error(error.read(from, from + response.reply.size())
|
|
? error
|
|
: Error::MTPLocal(
|
|
"RESPONSE_PARSE_FAILED",
|
|
"Error parse failed.")));
|
|
} else {
|
|
const auto guard = QPointer<Instance>(_instance);
|
|
if (handler.done && !handler.done(response) && guard) {
|
|
handleError(Error::Local(
|
|
"RESPONSE_PARSE_FAILED",
|
|
"Response parse failed."));
|
|
}
|
|
if (guard) {
|
|
unregisterRequest(requestId);
|
|
}
|
|
}
|
|
} else {
|
|
DEBUG_LOG(("RPC Info: parser not found for %1").arg(requestId));
|
|
unregisterRequest(requestId);
|
|
}
|
|
}
|
|
|
|
void Instance::Private::processUpdate(const Response &message) {
|
|
if (_updatesHandler) {
|
|
_updatesHandler(message);
|
|
}
|
|
}
|
|
|
|
void Instance::Private::onStateChange(ShiftedDcId dcWithShift, int32 state) {
|
|
if (_stateChangedHandler) {
|
|
_stateChangedHandler(dcWithShift, state);
|
|
}
|
|
}
|
|
|
|
void Instance::Private::onSessionReset(ShiftedDcId dcWithShift) {
|
|
if (_sessionResetHandler) {
|
|
_sessionResetHandler(dcWithShift);
|
|
}
|
|
}
|
|
|
|
bool Instance::Private::rpcErrorOccured(
|
|
const Response &response,
|
|
const FailHandler &onFail,
|
|
const Error &error) { // return true if need to clean request data
|
|
if (IsDefaultHandledError(error)) {
|
|
const auto guard = QPointer<Instance>(_instance);
|
|
if (onFail && onFail(error, response)) {
|
|
return true;
|
|
} else if (!guard) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (onErrorDefault(error, response)) {
|
|
return false;
|
|
}
|
|
LOG(("RPC Error: request %1 got fail with code %2, error %3%4").arg(
|
|
QString::number(response.requestId),
|
|
QString::number(error.code()),
|
|
error.type(),
|
|
error.description().isEmpty()
|
|
? QString()
|
|
: QString(": %1").arg(error.description())));
|
|
if (onFail) {
|
|
const auto guard = QPointer<Instance>(_instance);
|
|
onFail(error, response);
|
|
if (!guard) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void Instance::Private::importDone(
|
|
const MTPauth_Authorization &result,
|
|
const Response &response) {
|
|
const auto shiftedDcId = queryRequestByDc(response.requestId);
|
|
if (!shiftedDcId) {
|
|
LOG(("MTP Error: "
|
|
"auth import request not found in requestsByDC, requestId: %1"
|
|
).arg(response.requestId));
|
|
//
|
|
// Don't log out on export/import problems, perhaps this is a server side error.
|
|
//
|
|
//const auto error = Error::Local(
|
|
// "AUTH_IMPORT_FAIL",
|
|
// QString("did not find import request in requestsByDC, "
|
|
// "request %1").arg(requestId));
|
|
//if (_globalFailHandler && hasAuthorization()) {
|
|
// _globalFailHandler(error, response); // auth failed in main dc
|
|
//}
|
|
return;
|
|
}
|
|
auto newdc = BareDcId(*shiftedDcId);
|
|
|
|
DEBUG_LOG(("MTP Info: auth import to dc %1 succeeded").arg(newdc));
|
|
|
|
auto &waiters = _authWaiters[newdc];
|
|
if (waiters.size()) {
|
|
QReadLocker locker(&_requestMapLock);
|
|
for (auto waitedRequestId : waiters) {
|
|
auto it = _requestMap.find(waitedRequestId);
|
|
if (it == _requestMap.cend()) {
|
|
LOG(("MTP Error: could not find request %1 for resending").arg(waitedRequestId));
|
|
continue;
|
|
}
|
|
const auto shiftedDcId = changeRequestByDc(waitedRequestId, newdc);
|
|
if (!shiftedDcId) {
|
|
LOG(("MTP Error: could not find request %1 by dc for resending").arg(waitedRequestId));
|
|
continue;
|
|
} else if (*shiftedDcId < 0) {
|
|
_instance->setMainDcId(newdc);
|
|
}
|
|
DEBUG_LOG(("MTP Info: resending request %1 to dc %2 after import auth").arg(waitedRequestId).arg(*shiftedDcId));
|
|
const auto session = getSession(*shiftedDcId);
|
|
session->sendPrepared(it->second);
|
|
}
|
|
waiters.clear();
|
|
}
|
|
}
|
|
|
|
bool Instance::Private::importFail(
|
|
const Error &error,
|
|
const Response &response) {
|
|
if (IsDefaultHandledError(error)) {
|
|
return false;
|
|
}
|
|
|
|
//
|
|
// Don't log out on export/import problems, perhaps this is a server side error.
|
|
//
|
|
//if (_globalFailHandler && hasAuthorization()) {
|
|
// _globalFailHandler(error, response); // auth import failed
|
|
//}
|
|
return true;
|
|
}
|
|
|
|
void Instance::Private::exportDone(
|
|
const MTPauth_ExportedAuthorization &result,
|
|
const Response &response) {
|
|
auto it = _authExportRequests.find(response.requestId);
|
|
if (it == _authExportRequests.cend()) {
|
|
LOG(("MTP Error: "
|
|
"auth export request target dcWithShift not found, requestId: %1"
|
|
).arg(response.requestId));
|
|
//
|
|
// Don't log out on export/import problems, perhaps this is a server side error.
|
|
//
|
|
//const auto error = Error::Local(
|
|
// "AUTH_IMPORT_FAIL",
|
|
// QString("did not find target dcWithShift, request %1"
|
|
// ).arg(requestId));
|
|
//if (_globalFailHandler && hasAuthorization()) {
|
|
// _globalFailHandler(error, response); // auth failed in main dc
|
|
//}
|
|
return;
|
|
}
|
|
|
|
auto &data = result.c_auth_exportedAuthorization();
|
|
_instance->send(MTPauth_ImportAuthorization(
|
|
data.vid(),
|
|
data.vbytes()
|
|
), [this](const Response &response) {
|
|
auto result = MTPauth_Authorization();
|
|
auto from = response.reply.constData();
|
|
if (!result.read(from, from + response.reply.size())) {
|
|
return false;
|
|
}
|
|
importDone(result, response);
|
|
return true;
|
|
}, [this](const Error &error, const Response &response) {
|
|
return importFail(error, response);
|
|
}, it->second);
|
|
_authExportRequests.erase(response.requestId);
|
|
}
|
|
|
|
bool Instance::Private::exportFail(
|
|
const Error &error,
|
|
const Response &response) {
|
|
if (IsDefaultHandledError(error)) {
|
|
return false;
|
|
}
|
|
|
|
auto it = _authExportRequests.find(response.requestId);
|
|
if (it != _authExportRequests.cend()) {
|
|
_authWaiters[BareDcId(it->second)].clear();
|
|
}
|
|
//
|
|
// Don't log out on export/import problems, perhaps this is a server side error.
|
|
//
|
|
//if (_globalFailHandler && hasAuthorization()) {
|
|
// _globalFailHandler(error, response); // auth failed in main dc
|
|
//}
|
|
return true;
|
|
}
|
|
|
|
bool Instance::Private::onErrorDefault(
|
|
const Error &error,
|
|
const Response &response) {
|
|
const auto requestId = response.requestId;
|
|
const auto &type = error.type();
|
|
const auto code = error.code();
|
|
auto badGuestDc = (code == 400) && (type == u"FILE_ID_INVALID"_q);
|
|
static const auto MigrateRegExp = QRegularExpression("^(FILE|PHONE|NETWORK|USER)_MIGRATE_(\\d+)$");
|
|
static const auto FloodWaitRegExp = QRegularExpression("^FLOOD_WAIT_(\\d+)$");
|
|
static const auto FloodPremiumWaitRegExp = QRegularExpression("^FLOOD_PREMIUM_WAIT_(\\d+)$");
|
|
static const auto SlowmodeWaitRegExp = QRegularExpression("^SLOWMODE_WAIT_(\\d+)$");
|
|
QRegularExpressionMatch m1, m2, m3;
|
|
if ((m1 = MigrateRegExp.match(type)).hasMatch()) {
|
|
if (!requestId) return false;
|
|
|
|
auto dcWithShift = ShiftedDcId(0);
|
|
auto newdcWithShift = ShiftedDcId(m1.captured(2).toInt());
|
|
if (const auto shiftedDcId = queryRequestByDc(requestId)) {
|
|
dcWithShift = *shiftedDcId;
|
|
} else {
|
|
LOG(("MTP Error: could not find request %1 for migrating to %2").arg(requestId).arg(newdcWithShift));
|
|
}
|
|
if (!dcWithShift || !newdcWithShift) return false;
|
|
|
|
DEBUG_LOG(("MTP Info: changing request %1 from dcWithShift%2 to dc%3").arg(requestId).arg(dcWithShift).arg(newdcWithShift));
|
|
if (dcWithShift < 0) { // newdc shift = 0
|
|
if (false/* && hasAuthorization() && _authExportRequests.find(requestId) == _authExportRequests.cend()*/) {
|
|
//
|
|
// migrate not supported at this moment
|
|
// this was not tested even once
|
|
//
|
|
//DEBUG_LOG(("MTP Info: importing auth to dc %1").arg(newdcWithShift));
|
|
//auto &waiters(_authWaiters[newdcWithShift]);
|
|
//if (waiters.empty()) {
|
|
// auto exportRequestId = _instance->send(MTPauth_ExportAuthorization(
|
|
// MTP_int(newdcWithShift)
|
|
// ), [this](const Response &response) {
|
|
// auto result = MTPauth_ExportedAuthorization();
|
|
// auto from = response.reply.constData();
|
|
// if (!result.read(from, from + response.reply.size())) {
|
|
// return false;
|
|
// }
|
|
// exportDone(result, response);
|
|
// return true;
|
|
// }, [this](const Error &error, const Response &response) {
|
|
// return exportFail(error, response);
|
|
// });
|
|
// _authExportRequests.emplace(exportRequestId, newdcWithShift);
|
|
//}
|
|
//waiters.push_back(requestId);
|
|
//return true;
|
|
} else {
|
|
_instance->setMainDcId(newdcWithShift);
|
|
}
|
|
} else {
|
|
newdcWithShift = ShiftDcId(newdcWithShift, GetDcIdShift(dcWithShift));
|
|
}
|
|
|
|
auto request = SerializedRequest();
|
|
{
|
|
QReadLocker locker(&_requestMapLock);
|
|
auto it = _requestMap.find(requestId);
|
|
if (it == _requestMap.cend()) {
|
|
LOG(("MTP Error: could not find request %1").arg(requestId));
|
|
return false;
|
|
}
|
|
request = it->second;
|
|
}
|
|
const auto session = getSession(newdcWithShift);
|
|
registerRequest(
|
|
requestId,
|
|
(dcWithShift < 0) ? -newdcWithShift : newdcWithShift);
|
|
session->sendPrepared(request);
|
|
return true;
|
|
} else if (type == u"MSG_WAIT_TIMEOUT"_q || type == u"MSG_WAIT_FAILED"_q) {
|
|
SerializedRequest request;
|
|
{
|
|
QReadLocker locker(&_requestMapLock);
|
|
auto it = _requestMap.find(requestId);
|
|
if (it == _requestMap.cend()) {
|
|
LOG(("MTP Error: could not find MSG_WAIT_* request %1").arg(requestId));
|
|
return false;
|
|
}
|
|
request = it->second;
|
|
}
|
|
if (!request->after) {
|
|
LOG(("MTP Error: MSG_WAIT_* for not dependent request %1").arg(requestId));
|
|
return false;
|
|
}
|
|
auto dcWithShift = ShiftedDcId(0);
|
|
if (const auto shiftedDcId = queryRequestByDc(requestId)) {
|
|
dcWithShift = *shiftedDcId;
|
|
if (const auto afterDcId = queryRequestByDc(request->after->requestId)) {
|
|
if (*shiftedDcId != *afterDcId) {
|
|
request->after = SerializedRequest();
|
|
}
|
|
} else {
|
|
request->after = SerializedRequest();
|
|
}
|
|
} else {
|
|
LOG(("MTP Error: could not find MSG_WAIT_* request %1 by dc").arg(requestId));
|
|
}
|
|
if (!dcWithShift) {
|
|
return false;
|
|
}
|
|
|
|
if (!request->after) {
|
|
getSession(qAbs(dcWithShift))->sendPrepared(request);
|
|
} else {
|
|
QMutexLocker locker(&_dependentRequestsLock);
|
|
_dependentRequests.emplace(requestId, request->after->requestId);
|
|
}
|
|
return true;
|
|
} else if (code < 0
|
|
|| code >= 500
|
|
|| (m1 = FloodWaitRegExp.match(type)).hasMatch()
|
|
|| (m2 = FloodPremiumWaitRegExp.match(type)).hasMatch()
|
|
|| ((m3 = SlowmodeWaitRegExp.match(type)).hasMatch()
|
|
&& m3.captured(1).toInt() < 3)) {
|
|
if (!requestId) {
|
|
return false;
|
|
}
|
|
|
|
auto secs = 1;
|
|
auto nonPremiumDelay = false;
|
|
if (code < 0 || code >= 500) {
|
|
const auto it = _requestsDelays.find(requestId);
|
|
if (it != _requestsDelays.cend()) {
|
|
secs = (it->second > 60) ? it->second : (it->second *= 2);
|
|
} else {
|
|
_requestsDelays.emplace(requestId, secs);
|
|
}
|
|
} else if (m1.hasMatch()) {
|
|
secs = m1.captured(1).toInt();
|
|
// if (secs >= 60) return false;
|
|
} else if (m2.hasMatch()) {
|
|
secs = m2.captured(1).toInt();
|
|
nonPremiumDelay = true;
|
|
} else if (m3.hasMatch()) {
|
|
secs = m3.captured(1).toInt();
|
|
}
|
|
auto sendAt = crl::now() + secs * 1000 + 10;
|
|
auto it = _delayedRequests.begin(), e = _delayedRequests.end();
|
|
for (; it != e; ++it) {
|
|
if (it->first == requestId) {
|
|
return true;
|
|
} else if (it->second > sendAt) {
|
|
break;
|
|
}
|
|
}
|
|
_delayedRequests.insert(it, std::make_pair(requestId, sendAt));
|
|
|
|
checkDelayedRequests();
|
|
|
|
if (nonPremiumDelay) {
|
|
_nonPremiumDelayedRequests.fire_copy(requestId);
|
|
}
|
|
|
|
return true;
|
|
} else if ((code == 401 && type != u"AUTH_KEY_PERM_EMPTY"_q)
|
|
|| (badGuestDc && _badGuestDcRequests.find(requestId) == _badGuestDcRequests.cend())) {
|
|
auto dcWithShift = ShiftedDcId(0);
|
|
if (const auto shiftedDcId = queryRequestByDc(requestId)) {
|
|
dcWithShift = *shiftedDcId;
|
|
} else {
|
|
LOG(("MTP Error: unauthorized request without dc info, requestId %1").arg(requestId));
|
|
}
|
|
auto newdc = BareDcId(qAbs(dcWithShift));
|
|
if (!newdc || !hasMainDcId() || newdc == mainDcId()) {
|
|
if (!badGuestDc && _globalFailHandler) {
|
|
_globalFailHandler(error, response); // auth failed in main dc
|
|
}
|
|
return false;
|
|
}
|
|
|
|
DEBUG_LOG(("MTP Info: importing auth to dcWithShift %1"
|
|
).arg(dcWithShift));
|
|
auto &waiters(_authWaiters[newdc]);
|
|
if (!waiters.size()) {
|
|
auto exportRequestId = _instance->send(MTPauth_ExportAuthorization(
|
|
MTP_int(newdc)
|
|
), [this](const Response &response) {
|
|
auto result = MTPauth_ExportedAuthorization();
|
|
auto from = response.reply.constData();
|
|
if (!result.read(from, from + response.reply.size())) {
|
|
return false;
|
|
}
|
|
exportDone(result, response);
|
|
return true;
|
|
}, [this](const Error &error, const Response &response) {
|
|
return exportFail(error, response);
|
|
});
|
|
_authExportRequests.emplace(exportRequestId, abs(dcWithShift));
|
|
}
|
|
waiters.push_back(requestId);
|
|
if (badGuestDc) _badGuestDcRequests.insert(requestId);
|
|
return true;
|
|
} else if (type == u"CONNECTION_NOT_INITED"_q
|
|
|| type == u"CONNECTION_LAYER_INVALID"_q) {
|
|
SerializedRequest request;
|
|
{
|
|
QReadLocker locker(&_requestMapLock);
|
|
auto it = _requestMap.find(requestId);
|
|
if (it == _requestMap.cend()) {
|
|
LOG(("MTP Error: could not find request %1").arg(requestId));
|
|
return false;
|
|
}
|
|
request = it->second;
|
|
}
|
|
auto dcWithShift = ShiftedDcId(0);
|
|
if (const auto shiftedDcId = queryRequestByDc(requestId)) {
|
|
dcWithShift = *shiftedDcId;
|
|
} else {
|
|
LOG(("MTP Error: could not find request %1 for resending with init connection").arg(requestId));
|
|
}
|
|
if (!dcWithShift) return false;
|
|
|
|
const auto session = getSession(qAbs(dcWithShift));
|
|
request->needsLayer = true;
|
|
session->setConnectionNotInited();
|
|
session->sendPrepared(request);
|
|
return true;
|
|
} else if (type == u"CONNECTION_LANG_CODE_INVALID"_q) {
|
|
Lang::CurrentCloudManager().resetToDefault();
|
|
}
|
|
if (badGuestDc) _badGuestDcRequests.erase(requestId);
|
|
return false;
|
|
}
|
|
|
|
not_null<Session*> Instance::Private::getSession(
|
|
ShiftedDcId shiftedDcId) {
|
|
if (!shiftedDcId) {
|
|
Assert(_mainSession != nullptr);
|
|
return _mainSession;
|
|
} else if (!BareDcId(shiftedDcId)) {
|
|
Assert(_mainSession != nullptr);
|
|
shiftedDcId += BareDcId(_mainSession->getDcWithShift());
|
|
}
|
|
|
|
if (const auto session = findSession(shiftedDcId)) {
|
|
return session;
|
|
}
|
|
return startSession(shiftedDcId);
|
|
}
|
|
|
|
rpl::lifetime &Instance::Private::lifetime() {
|
|
return _lifetime;
|
|
}
|
|
|
|
Session *Instance::Private::findSession(ShiftedDcId shiftedDcId) {
|
|
const auto i = _sessions.find(shiftedDcId);
|
|
return (i != _sessions.end()) ? i->second.get() : nullptr;
|
|
}
|
|
|
|
not_null<Session*> Instance::Private::startSession(ShiftedDcId shiftedDcId) {
|
|
Expects(BareDcId(shiftedDcId) != 0);
|
|
|
|
const auto dc = getDcById(shiftedDcId);
|
|
const auto thread = getThreadForDc(shiftedDcId);
|
|
const auto result = _sessions.emplace(
|
|
shiftedDcId,
|
|
std::make_unique<Session>(_instance, thread, shiftedDcId, dc)
|
|
).first->second.get();
|
|
if (isKeysDestroyer()) {
|
|
scheduleKeyDestroy(shiftedDcId);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void Instance::Private::scheduleSessionDestroy(ShiftedDcId shiftedDcId) {
|
|
const auto i = _sessions.find(shiftedDcId);
|
|
if (i == _sessions.cend()) {
|
|
return;
|
|
}
|
|
i->second->kill();
|
|
_sessionsToDestroy.push_back(std::move(i->second));
|
|
_sessions.erase(i);
|
|
InvokeQueued(_instance, [=] {
|
|
_sessionsToDestroy.clear();
|
|
});
|
|
}
|
|
|
|
|
|
not_null<QThread*> Instance::Private::getThreadForDc(
|
|
ShiftedDcId shiftedDcId) {
|
|
static const auto EnsureStarted = [](
|
|
std::unique_ptr<QThread> &thread,
|
|
auto name) {
|
|
if (!thread) {
|
|
thread = std::make_unique<QThread>();
|
|
thread->setObjectName(name());
|
|
thread->start();
|
|
}
|
|
return thread.get();
|
|
};
|
|
static const auto FindOne = [](
|
|
std::vector<std::unique_ptr<QThread>> &threads,
|
|
const char *prefix,
|
|
int index,
|
|
bool shift) {
|
|
Expects(!threads.empty());
|
|
Expects(!(threads.size() % 2));
|
|
|
|
const auto count = int(threads.size());
|
|
index %= count;
|
|
if (index >= count / 2) {
|
|
index = (count - 1) - (index - count / 2);
|
|
}
|
|
if (shift) {
|
|
index = (index + count / 2) % count;
|
|
}
|
|
return EnsureStarted(threads[index], [=] {
|
|
return QString("MTP %1 Session (%2)").arg(prefix).arg(index);
|
|
});
|
|
};
|
|
if (shiftedDcId == BareDcId(shiftedDcId)) {
|
|
return EnsureStarted(_mainSessionThread, [] {
|
|
return QString("MTP Main Session");
|
|
});
|
|
} else if (isDownloadDcId(shiftedDcId)) {
|
|
const auto index = GetDcIdShift(shiftedDcId) - kBaseDownloadDcShift;
|
|
const auto composed = index + BareDcId(shiftedDcId);
|
|
return FindOne(_fileSessionThreads, "Download", composed, false);
|
|
} else if (isUploadDcId(shiftedDcId)) {
|
|
const auto index = GetDcIdShift(shiftedDcId) - kBaseUploadDcShift;
|
|
const auto composed = index + BareDcId(shiftedDcId);
|
|
return FindOne(_fileSessionThreads, "Upload", composed, true);
|
|
}
|
|
return EnsureStarted(_otherSessionsThread, [] {
|
|
return QString("MTP Other Session");
|
|
});
|
|
}
|
|
|
|
void Instance::Private::scheduleKeyDestroy(ShiftedDcId shiftedDcId) {
|
|
Expects(isKeysDestroyer());
|
|
|
|
if (dcOptions().dcType(shiftedDcId) == DcType::Cdn) {
|
|
performKeyDestroy(shiftedDcId);
|
|
} else {
|
|
_instance->send(MTPauth_LogOut(), [=](const Response &) {
|
|
performKeyDestroy(shiftedDcId);
|
|
return true;
|
|
}, [=](const Error &error, const Response &) {
|
|
if (IsDefaultHandledError(error)) {
|
|
return false;
|
|
}
|
|
performKeyDestroy(shiftedDcId);
|
|
return true;
|
|
}, shiftedDcId);
|
|
}
|
|
}
|
|
|
|
void Instance::Private::keyWasPossiblyDestroyed(ShiftedDcId shiftedDcId) {
|
|
Expects(isKeysDestroyer());
|
|
|
|
InvokeQueued(_instance, [=] {
|
|
LOG(("MTP Info: checkIfKeyWasDestroyed on destroying key %1, "
|
|
"assuming it is destroyed.").arg(shiftedDcId));
|
|
completedKeyDestroy(shiftedDcId);
|
|
});
|
|
}
|
|
|
|
void Instance::Private::performKeyDestroy(ShiftedDcId shiftedDcId) {
|
|
Expects(isKeysDestroyer());
|
|
|
|
_instance->send(MTPDestroy_auth_key(), [=](const Response &response) {
|
|
auto result = MTPDestroyAuthKeyRes();
|
|
auto from = response.reply.constData();
|
|
if (!result.read(from, from + response.reply.size())) {
|
|
return false;
|
|
}
|
|
result.match([&](const MTPDdestroy_auth_key_ok &) {
|
|
LOG(("MTP Info: key %1 destroyed.").arg(shiftedDcId));
|
|
}, [&](const MTPDdestroy_auth_key_fail &) {
|
|
LOG(("MTP Error: key %1 destruction fail, leave it for now."
|
|
).arg(shiftedDcId));
|
|
killSession(shiftedDcId);
|
|
}, [&](const MTPDdestroy_auth_key_none &) {
|
|
LOG(("MTP Info: key %1 already destroyed.").arg(shiftedDcId));
|
|
});
|
|
_instance->keyWasPossiblyDestroyed(shiftedDcId);
|
|
return true;
|
|
}, [=](const Error &error, const Response &response) {
|
|
LOG(("MTP Error: key %1 destruction resulted in error: %2"
|
|
).arg(shiftedDcId).arg(error.type()));
|
|
_instance->keyWasPossiblyDestroyed(shiftedDcId);
|
|
return true;
|
|
}, shiftedDcId);
|
|
}
|
|
|
|
void Instance::Private::completedKeyDestroy(ShiftedDcId shiftedDcId) {
|
|
Expects(isKeysDestroyer());
|
|
|
|
removeDc(shiftedDcId);
|
|
_keysForWrite.erase(shiftedDcId);
|
|
killSession(shiftedDcId);
|
|
if (_dcenters.empty()) {
|
|
_allKeysDestroyed.fire({});
|
|
}
|
|
}
|
|
|
|
void Instance::Private::keyDestroyedOnServer(
|
|
ShiftedDcId shiftedDcId,
|
|
uint64 keyId) {
|
|
LOG(("Destroying key for dc: %1").arg(shiftedDcId));
|
|
if (const auto dc = findDc(BareDcId(shiftedDcId))) {
|
|
if (dc->destroyConfirmedForgottenKey(keyId)) {
|
|
LOG(("Key destroyed!"));
|
|
dcPersistentKeyChanged(BareDcId(shiftedDcId), nullptr);
|
|
} else {
|
|
LOG(("Key already is different."));
|
|
}
|
|
}
|
|
restart(shiftedDcId);
|
|
}
|
|
|
|
void Instance::Private::setUpdatesHandler(
|
|
Fn<void(const Response&)> handler) {
|
|
_updatesHandler = std::move(handler);
|
|
}
|
|
|
|
void Instance::Private::setGlobalFailHandler(
|
|
Fn<void(const Error&, const Response&)> handler) {
|
|
_globalFailHandler = std::move(handler);
|
|
}
|
|
|
|
void Instance::Private::setStateChangedHandler(
|
|
Fn<void(ShiftedDcId shiftedDcId, int32 state)> handler) {
|
|
_stateChangedHandler = std::move(handler);
|
|
}
|
|
|
|
void Instance::Private::setSessionResetHandler(
|
|
Fn<void(ShiftedDcId shiftedDcId)> handler) {
|
|
_sessionResetHandler = std::move(handler);
|
|
}
|
|
|
|
void Instance::Private::clearGlobalHandlers() {
|
|
setUpdatesHandler(nullptr);
|
|
setGlobalFailHandler(nullptr);
|
|
setStateChangedHandler(nullptr);
|
|
setSessionResetHandler(nullptr);
|
|
}
|
|
|
|
void Instance::Private::prepareToDestroy() {
|
|
// It accesses Instance in destructor, so it should be destroyed first.
|
|
_configLoader.reset();
|
|
|
|
requestCancellingDiscard();
|
|
|
|
for (const auto &[shiftedDcId, session] : base::take(_sessions)) {
|
|
session->kill();
|
|
}
|
|
_mainSession = nullptr;
|
|
|
|
auto threads = std::vector<std::unique_ptr<QThread>>();
|
|
threads.push_back(base::take(_mainSessionThread));
|
|
threads.push_back(base::take(_otherSessionsThread));
|
|
for (auto &thread : base::take(_fileSessionThreads)) {
|
|
threads.push_back(std::move(thread));
|
|
}
|
|
for (const auto &thread : threads) {
|
|
if (thread) {
|
|
thread->quit();
|
|
}
|
|
}
|
|
for (const auto &thread : threads) {
|
|
if (thread) {
|
|
thread->wait();
|
|
}
|
|
}
|
|
}
|
|
|
|
Instance::Instance(Mode mode, Fields &&fields)
|
|
: QObject()
|
|
, _private(std::make_unique<Private>(this, mode, std::move(fields))) {
|
|
_private->start();
|
|
}
|
|
|
|
void Instance::resolveProxyDomain(const QString &host) {
|
|
_private->resolveProxyDomain(host);
|
|
}
|
|
|
|
void Instance::setGoodProxyDomain(const QString &host, const QString &ip) {
|
|
_private->setGoodProxyDomain(host, ip);
|
|
}
|
|
|
|
void Instance::suggestMainDcId(DcId mainDcId) {
|
|
_private->suggestMainDcId(mainDcId);
|
|
}
|
|
|
|
void Instance::setMainDcId(DcId mainDcId) {
|
|
_private->setMainDcId(mainDcId);
|
|
}
|
|
|
|
DcId Instance::mainDcId() const {
|
|
return _private->mainDcId();
|
|
}
|
|
|
|
rpl::producer<DcId> Instance::mainDcIdValue() const {
|
|
return _private->mainDcIdValue();
|
|
}
|
|
|
|
QString Instance::systemLangCode() const {
|
|
return Lang::GetInstance().systemLangCode();
|
|
}
|
|
|
|
QString Instance::cloudLangCode() const {
|
|
return Lang::GetInstance().cloudLangCode(Lang::Pack::Current);
|
|
}
|
|
|
|
QString Instance::langPackName() const {
|
|
return Lang::GetInstance().langPackName();
|
|
}
|
|
|
|
rpl::producer<> Instance::writeKeysRequests() const {
|
|
return _private->writeKeysRequests();
|
|
}
|
|
|
|
rpl::producer<> Instance::allKeysDestroyed() const {
|
|
return _private->allKeysDestroyed();
|
|
}
|
|
|
|
void Instance::requestConfig() {
|
|
_private->requestConfig();
|
|
}
|
|
|
|
void Instance::setUserPhone(const QString &phone) {
|
|
_private->setUserPhone(phone);
|
|
}
|
|
|
|
void Instance::badConfigurationError() {
|
|
_private->badConfigurationError();
|
|
}
|
|
|
|
void Instance::syncHttpUnixtime() {
|
|
_private->syncHttpUnixtime();
|
|
}
|
|
|
|
void Instance::restartedByTimeout(ShiftedDcId shiftedDcId) {
|
|
_private->restartedByTimeout(shiftedDcId);
|
|
}
|
|
|
|
rpl::producer<ShiftedDcId> Instance::restartsByTimeout() const {
|
|
return _private->restartsByTimeout();
|
|
}
|
|
|
|
rpl::producer<mtpRequestId> Instance::nonPremiumDelayedRequests() const {
|
|
return _private->nonPremiumDelayedRequests();
|
|
}
|
|
|
|
void Instance::requestConfigIfOld() {
|
|
_private->requestConfigIfOld();
|
|
}
|
|
|
|
void Instance::requestCDNConfig() {
|
|
_private->requestCDNConfig();
|
|
}
|
|
|
|
void Instance::restart() {
|
|
_private->restart();
|
|
}
|
|
|
|
void Instance::restart(ShiftedDcId shiftedDcId) {
|
|
_private->restart(shiftedDcId);
|
|
}
|
|
|
|
int32 Instance::dcstate(ShiftedDcId shiftedDcId) {
|
|
return _private->dcstate(shiftedDcId);
|
|
}
|
|
|
|
QString Instance::dctransport(ShiftedDcId shiftedDcId) {
|
|
return _private->dctransport(shiftedDcId);
|
|
}
|
|
|
|
void Instance::ping() {
|
|
_private->ping();
|
|
}
|
|
|
|
void Instance::cancel(mtpRequestId requestId) {
|
|
_private->cancel(requestId);
|
|
}
|
|
|
|
int32 Instance::state(mtpRequestId requestId) { // < 0 means waiting for such count of ms
|
|
return _private->state(requestId);
|
|
}
|
|
|
|
void Instance::killSession(ShiftedDcId shiftedDcId) {
|
|
_private->killSession(shiftedDcId);
|
|
}
|
|
|
|
void Instance::stopSession(ShiftedDcId shiftedDcId) {
|
|
_private->stopSession(shiftedDcId);
|
|
}
|
|
|
|
void Instance::reInitConnection(DcId dcId) {
|
|
_private->reInitConnection(dcId);
|
|
}
|
|
|
|
void Instance::logout(Fn<void()> done) {
|
|
_private->logout(std::move(done));
|
|
}
|
|
|
|
void Instance::dcPersistentKeyChanged(
|
|
DcId dcId,
|
|
const AuthKeyPtr &persistentKey) {
|
|
_private->dcPersistentKeyChanged(dcId, persistentKey);
|
|
}
|
|
|
|
void Instance::dcTemporaryKeyChanged(DcId dcId) {
|
|
_private->dcTemporaryKeyChanged(dcId);
|
|
}
|
|
|
|
rpl::producer<DcId> Instance::dcTemporaryKeyChanged() const {
|
|
return _private->dcTemporaryKeyChanged();
|
|
}
|
|
|
|
AuthKeysList Instance::getKeysForWrite() const {
|
|
return _private->getKeysForWrite();
|
|
}
|
|
|
|
void Instance::addKeysForDestroy(AuthKeysList &&keys) {
|
|
_private->addKeysForDestroy(std::move(keys));
|
|
}
|
|
|
|
Config &Instance::config() const {
|
|
return _private->config();
|
|
}
|
|
|
|
const ConfigFields &Instance::configValues() const {
|
|
return _private->configValues();
|
|
}
|
|
|
|
DcOptions &Instance::dcOptions() const {
|
|
return _private->dcOptions();
|
|
}
|
|
|
|
Environment Instance::environment() const {
|
|
return _private->environment();
|
|
}
|
|
|
|
bool Instance::isTestMode() const {
|
|
return _private->isTestMode();
|
|
}
|
|
|
|
QString Instance::deviceModel() const {
|
|
return _private->deviceModel();
|
|
}
|
|
|
|
QString Instance::systemVersion() const {
|
|
return _private->systemVersion();
|
|
}
|
|
|
|
void Instance::setUpdatesHandler(Fn<void(const Response&)> handler) {
|
|
_private->setUpdatesHandler(std::move(handler));
|
|
}
|
|
|
|
void Instance::setGlobalFailHandler(
|
|
Fn<void(const Error&, const Response&)> handler) {
|
|
_private->setGlobalFailHandler(std::move(handler));
|
|
}
|
|
|
|
void Instance::setStateChangedHandler(
|
|
Fn<void(ShiftedDcId shiftedDcId, int32 state)> handler) {
|
|
_private->setStateChangedHandler(std::move(handler));
|
|
}
|
|
|
|
void Instance::setSessionResetHandler(
|
|
Fn<void(ShiftedDcId shiftedDcId)> handler) {
|
|
_private->setSessionResetHandler(std::move(handler));
|
|
}
|
|
|
|
void Instance::clearGlobalHandlers() {
|
|
_private->clearGlobalHandlers();
|
|
}
|
|
|
|
void Instance::onStateChange(ShiftedDcId shiftedDcId, int32 state) {
|
|
_private->onStateChange(shiftedDcId, state);
|
|
}
|
|
|
|
void Instance::onSessionReset(ShiftedDcId shiftedDcId) {
|
|
_private->onSessionReset(shiftedDcId);
|
|
}
|
|
|
|
bool Instance::hasCallback(mtpRequestId requestId) const {
|
|
return _private->hasCallback(requestId);
|
|
}
|
|
|
|
void Instance::processCallback(const Response &response) {
|
|
_private->processCallback(response);
|
|
}
|
|
|
|
void Instance::processUpdate(const Response &message) {
|
|
_private->processUpdate(message);
|
|
}
|
|
|
|
bool Instance::rpcErrorOccured(
|
|
const Response &response,
|
|
const FailHandler &onFail,
|
|
const Error &error) {
|
|
return _private->rpcErrorOccured(response, onFail, error);
|
|
}
|
|
|
|
bool Instance::isKeysDestroyer() const {
|
|
return _private->isKeysDestroyer();
|
|
}
|
|
|
|
void Instance::keyWasPossiblyDestroyed(ShiftedDcId shiftedDcId) {
|
|
_private->keyWasPossiblyDestroyed(shiftedDcId);
|
|
}
|
|
|
|
void Instance::keyDestroyedOnServer(ShiftedDcId shiftedDcId, uint64 keyId) {
|
|
_private->keyDestroyedOnServer(shiftedDcId, keyId);
|
|
}
|
|
|
|
void Instance::sendRequest(
|
|
mtpRequestId requestId,
|
|
SerializedRequest &&request,
|
|
ResponseHandler &&callbacks,
|
|
ShiftedDcId shiftedDcId,
|
|
crl::time msCanWait,
|
|
bool needsLayer,
|
|
mtpRequestId afterRequestId) {
|
|
return _private->sendRequest(
|
|
requestId,
|
|
std::move(request),
|
|
std::move(callbacks),
|
|
shiftedDcId,
|
|
msCanWait,
|
|
needsLayer,
|
|
afterRequestId);
|
|
}
|
|
|
|
void Instance::sendAnything(ShiftedDcId shiftedDcId, crl::time msCanWait) {
|
|
_private->getSession(shiftedDcId)->sendAnything(msCanWait);
|
|
}
|
|
|
|
rpl::lifetime &Instance::lifetime() {
|
|
return _private->lifetime();
|
|
}
|
|
|
|
Instance::~Instance() {
|
|
_private->prepareToDestroy();
|
|
}
|
|
|
|
} // namespace MTP
|