mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2026-07-25 06:54:43 +00:00
Add proxy auto-rotation option.
This commit is contained in:
@@ -503,6 +503,8 @@ PRIVATE
|
||||
chat_helpers/ttl_media_layer_widget.h
|
||||
core/application.cpp
|
||||
core/application.h
|
||||
core/proxy_rotation_manager.cpp
|
||||
core/proxy_rotation_manager.h
|
||||
core/cached_webview_availability.h
|
||||
core/bank_card_click_handler.cpp
|
||||
core/bank_card_click_handler.h
|
||||
@@ -1436,6 +1438,8 @@ PRIVATE
|
||||
mtproto/facade.h
|
||||
mtproto/mtp_instance.cpp
|
||||
mtproto/mtp_instance.h
|
||||
mtproto/proxy_check.cpp
|
||||
mtproto/proxy_check.h
|
||||
mtproto/sender.h
|
||||
mtproto/session.cpp
|
||||
mtproto/session.h
|
||||
|
||||
@@ -1239,6 +1239,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_proxy_use_system_settings" = "Use system proxy settings";
|
||||
"lng_proxy_use_custom" = "Use custom proxy";
|
||||
"lng_proxy_use_for_calls" = "Use proxy for calls";
|
||||
"lng_proxy_auto_switch" = "Auto-switch proxies";
|
||||
"lng_proxy_auto_switch_about" = "You can choose how quickly the app should auto-connect to the nearest active proxy if the current one stops working.";
|
||||
"lng_proxy_auto_switch_timeout#one" = "{count} s";
|
||||
"lng_proxy_auto_switch_timeout#other" = "{count} s";
|
||||
"lng_proxy_about" = "Proxy servers may be helpful in accessing Telegram if there is no connection in a specific region.";
|
||||
"lng_proxy_add" = "Add proxy";
|
||||
"lng_proxy_share" = "Share";
|
||||
|
||||
@@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "main/main_account.h"
|
||||
#include "main/main_session.h"
|
||||
#include "mtproto/facade.h"
|
||||
#include "mtproto/proxy_check.h"
|
||||
#include "settings/settings_common.h"
|
||||
#include "storage/localstorage.h"
|
||||
#include "ui/basic_click_handlers.h"
|
||||
@@ -32,6 +33,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/checkbox.h"
|
||||
#include "ui/widgets/dropdown_menu.h"
|
||||
#include "ui/widgets/discrete_sliders.h"
|
||||
#include "ui/widgets/fields/input_field.h"
|
||||
#include "ui/widgets/fields/number_input.h"
|
||||
#include "ui/widgets/fields/password_input.h"
|
||||
@@ -62,6 +64,20 @@ constexpr auto kSaveSettingsDelayedTimeout = crl::time(1000);
|
||||
|
||||
using ProxyData = MTP::ProxyData;
|
||||
|
||||
[[nodiscard]] int ClosestProxyRotationTimeoutSection(int value) {
|
||||
auto result = 0;
|
||||
auto bestDistance = 0;
|
||||
for (auto i = 0; i != int(Core::SettingsProxy::kProxyRotationTimeouts.size()); ++i) {
|
||||
const auto current = Core::SettingsProxy::kProxyRotationTimeouts[i];
|
||||
const auto distance = (current > value) ? (current - value) : (value - current);
|
||||
if ((i == 0) || (distance < bestDistance)) {
|
||||
result = i;
|
||||
bestDistance = distance;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::vector<QString> ExtractUrlsSimple(const QString &input) {
|
||||
auto urls = std::vector<QString>();
|
||||
static auto urlRegex = QRegularExpression(R"((https?:\/\/[^\s]+))");
|
||||
@@ -376,12 +392,16 @@ private:
|
||||
void setupButtons(int id, not_null<ProxyRow*> button);
|
||||
int rowHeight() const;
|
||||
void refreshProxyForCalls();
|
||||
void refreshProxyRotation();
|
||||
|
||||
not_null<ProxiesBoxController*> _controller;
|
||||
Core::SettingsProxy &_settings;
|
||||
QPointer<Ui::Checkbox> _tryIPv6;
|
||||
std::shared_ptr<Ui::RadioenumGroup<ProxyData::Settings>> _proxySettings;
|
||||
QPointer<Ui::SlideWrap<Ui::Checkbox>> _proxyForCalls;
|
||||
QPointer<Ui::SlideWrap<Ui::Checkbox>> _proxyRotation;
|
||||
QPointer<Ui::SlideWrap<Ui::VerticalLayout>> _proxyRotationOptions;
|
||||
QPointer<Ui::SettingsSlider> _proxyRotationTimeout;
|
||||
QPointer<Ui::DividerLabel> _about;
|
||||
base::unique_qptr<Ui::RpWidget> _noRows;
|
||||
object_ptr<Ui::VerticalLayout> _initialWrap;
|
||||
@@ -900,6 +920,47 @@ void ProxiesBox::setupContent() {
|
||||
0,
|
||||
st::proxyTryIPv6Padding.right(),
|
||||
st::proxyTryIPv6Padding.top()));
|
||||
_proxyRotation = inner->add(
|
||||
object_ptr<Ui::SlideWrap<Ui::Checkbox>>(
|
||||
inner,
|
||||
object_ptr<Ui::Checkbox>(
|
||||
inner,
|
||||
tr::lng_proxy_auto_switch(tr::now),
|
||||
_settings.proxyRotationEnabled()),
|
||||
style::margins(
|
||||
0,
|
||||
st::proxyUsePadding.top(),
|
||||
0,
|
||||
st::proxyUsePadding.bottom())),
|
||||
style::margins(
|
||||
st::proxyTryIPv6Padding.left(),
|
||||
0,
|
||||
st::proxyTryIPv6Padding.right(),
|
||||
st::proxyTryIPv6Padding.top()));
|
||||
_proxyRotationOptions = inner->add(
|
||||
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
||||
inner,
|
||||
object_ptr<Ui::VerticalLayout>(inner)));
|
||||
_proxyRotationTimeout = _proxyRotationOptions->entity()->add(
|
||||
object_ptr<Ui::SettingsSlider>(
|
||||
_proxyRotationOptions->entity(),
|
||||
st::settingsSlider),
|
||||
st::settingsBigScalePadding);
|
||||
for (const auto seconds : Core::SettingsProxy::kProxyRotationTimeouts) {
|
||||
_proxyRotationTimeout->addSection(
|
||||
tr::lng_proxy_auto_switch_timeout(
|
||||
tr::now,
|
||||
lt_count,
|
||||
seconds));
|
||||
}
|
||||
_proxyRotationTimeout->setActiveSectionFast(
|
||||
ClosestProxyRotationTimeoutSection(_settings.proxyRotationTimeout()));
|
||||
_proxyRotationOptions->entity()->add(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
_proxyRotationOptions->entity(),
|
||||
tr::lng_proxy_auto_switch_about(tr::now),
|
||||
st::boxDividerLabel),
|
||||
st::proxyAboutPadding);
|
||||
|
||||
_about = inner->add(
|
||||
object_ptr<Ui::DividerLabel>(
|
||||
@@ -922,6 +983,7 @@ void ProxiesBox::setupContent() {
|
||||
addNewProxy();
|
||||
}
|
||||
refreshProxyForCalls();
|
||||
refreshProxyRotation();
|
||||
});
|
||||
_tryIPv6->checkedChanges(
|
||||
) | rpl::on_next([=](bool checked) {
|
||||
@@ -931,18 +993,33 @@ void ProxiesBox::setupContent() {
|
||||
_controller->proxySettingsValue(
|
||||
) | rpl::on_next([=](ProxyData::Settings value) {
|
||||
_proxySettings->setValue(value);
|
||||
refreshProxyForCalls();
|
||||
refreshProxyRotation();
|
||||
}, inner->lifetime());
|
||||
|
||||
_proxyForCalls->entity()->checkedChanges(
|
||||
) | rpl::on_next([=](bool checked) {
|
||||
_controller->setProxyForCalls(checked);
|
||||
}, _proxyForCalls->lifetime());
|
||||
_proxyRotation->entity()->checkedChanges(
|
||||
) | rpl::on_next([=](bool checked) {
|
||||
_controller->setProxyRotationEnabled(checked);
|
||||
refreshProxyRotation();
|
||||
}, _proxyRotation->lifetime());
|
||||
_proxyRotationTimeout->sectionActivated(
|
||||
) | rpl::on_next([=](int section) {
|
||||
_controller->setProxyRotationTimeout(
|
||||
Core::SettingsProxy::kProxyRotationTimeouts[section]);
|
||||
}, _proxyRotationTimeout->lifetime());
|
||||
|
||||
if (_rows.empty()) {
|
||||
createNoRowsLabel();
|
||||
}
|
||||
refreshProxyForCalls();
|
||||
refreshProxyRotation();
|
||||
_proxyForCalls->finishAnimating();
|
||||
_proxyRotation->finishAnimating();
|
||||
_proxyRotationOptions->finishAnimating();
|
||||
|
||||
{
|
||||
const auto wrap = inner->add(
|
||||
@@ -987,6 +1064,20 @@ void ProxiesBox::refreshProxyForCalls() {
|
||||
anim::type::normal);
|
||||
}
|
||||
|
||||
void ProxiesBox::refreshProxyRotation() {
|
||||
if (!_proxyRotation || !_proxyRotationOptions) {
|
||||
return;
|
||||
}
|
||||
const auto visible = (_proxySettings->current()
|
||||
== ProxyData::Settings::Enabled)
|
||||
&& _settings.selected()
|
||||
&& (_settings.list().size() > 1);
|
||||
_proxyRotation->toggle(visible, anim::type::normal);
|
||||
_proxyRotationOptions->toggle(
|
||||
visible && _proxyRotation->entity()->checked(),
|
||||
anim::type::normal);
|
||||
}
|
||||
|
||||
int ProxiesBox::rowHeight() const {
|
||||
return st::proxyRowPadding.top()
|
||||
+ st::semiboldFont->height
|
||||
@@ -1029,6 +1120,7 @@ void ProxiesBox::applyView(View &&view) {
|
||||
} else {
|
||||
i->second->updateFields(std::move(view));
|
||||
}
|
||||
refreshProxyRotation();
|
||||
}
|
||||
|
||||
void ProxiesBox::createNoRowsLabel() {
|
||||
@@ -1359,95 +1451,7 @@ void ProxyBox::addLabel(
|
||||
}
|
||||
|
||||
using Connection = MTP::details::AbstractConnection;
|
||||
using Checker = MTP::details::ConnectionPointer;
|
||||
|
||||
void ResetProxyCheckers(Checker &v4, Checker &v6) {
|
||||
v4 = nullptr;
|
||||
v6 = nullptr;
|
||||
}
|
||||
|
||||
void DropProxyChecker(Checker &v4, Checker &v6, not_null<Connection*> raw) {
|
||||
if (v4.get() == raw) {
|
||||
v4 = nullptr;
|
||||
} else if (v6.get() == raw) {
|
||||
v6 = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] bool HasProxyCheckers(const Checker &v4, const Checker &v6) {
|
||||
return v4 || v6;
|
||||
}
|
||||
|
||||
void StartProxyCheck(
|
||||
not_null<MTP::Instance*> mtproto,
|
||||
const ProxyData &proxy,
|
||||
Checker &v4,
|
||||
Checker &v6,
|
||||
Fn<void(Connection *raw, int ping)> done,
|
||||
Fn<void(Connection *raw)> fail) {
|
||||
using Variants = MTP::DcOptions::Variants;
|
||||
|
||||
ResetProxyCheckers(v4, v6);
|
||||
const auto connType = (proxy.type == ProxyData::Type::Http)
|
||||
? Variants::Http
|
||||
: Variants::Tcp;
|
||||
const auto dcId = mtproto->mainDcId();
|
||||
const auto setup = [&](Checker &checker, const bytes::vector &secret) {
|
||||
checker = Connection::Create(
|
||||
mtproto,
|
||||
connType,
|
||||
QThread::currentThread(),
|
||||
secret,
|
||||
proxy);
|
||||
const auto raw = checker.get();
|
||||
raw->connect(raw, &Connection::connected, [=] {
|
||||
if (done) {
|
||||
done(raw, raw->pingTime());
|
||||
}
|
||||
});
|
||||
const auto failed = [=] {
|
||||
if (fail) {
|
||||
fail(raw);
|
||||
}
|
||||
};
|
||||
raw->connect(raw, &Connection::disconnected, failed);
|
||||
raw->connect(raw, &Connection::error, failed);
|
||||
};
|
||||
if (proxy.type == ProxyData::Type::Mtproto) {
|
||||
const auto secret = proxy.secretFromMtprotoPassword();
|
||||
setup(v4, secret);
|
||||
v4->connectToServer(
|
||||
proxy.host,
|
||||
proxy.port,
|
||||
secret,
|
||||
dcId,
|
||||
false);
|
||||
return;
|
||||
}
|
||||
const auto options = mtproto->dcOptions().lookup(
|
||||
dcId,
|
||||
MTP::DcType::Regular,
|
||||
true);
|
||||
const auto tryConnect = [&](Checker &checker, Variants::Address address) {
|
||||
const auto &list = options.data[address][connType];
|
||||
if (list.empty()
|
||||
|| ((address == Variants::IPv6)
|
||||
&& !Core::App().settings().proxy().tryIPv6())) {
|
||||
checker = nullptr;
|
||||
return;
|
||||
}
|
||||
const auto &endpoint = list.front();
|
||||
setup(checker, endpoint.secret);
|
||||
checker->connectToServer(
|
||||
QString::fromStdString(endpoint.ip),
|
||||
endpoint.port,
|
||||
endpoint.secret,
|
||||
dcId,
|
||||
false);
|
||||
};
|
||||
tryConnect(v4, Variants::IPv4);
|
||||
tryConnect(v6, Variants::IPv6);
|
||||
}
|
||||
using Checker = MTP::ProxyCheckConnection;
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -1609,18 +1613,19 @@ void ProxiesBoxController::ShowApplyConfirmation(
|
||||
};
|
||||
statusLabel->setTextColorOverride(st::proxyRowStatusFg->c);
|
||||
relayout();
|
||||
StartProxyCheck(
|
||||
MTP::StartProxyCheck(
|
||||
&account->mtp(),
|
||||
proxy,
|
||||
Core::App().settings().proxy().tryIPv6(),
|
||||
state->v4,
|
||||
state->v6,
|
||||
[=](Connection *raw, int ping) {
|
||||
if (!weak || state->finished) {
|
||||
return;
|
||||
}
|
||||
DropProxyChecker(state->v4, state->v6, raw);
|
||||
MTP::DropProxyChecker(state->v4, state->v6, raw);
|
||||
state->finished = true;
|
||||
ResetProxyCheckers(state->v4, state->v6);
|
||||
MTP::ResetProxyCheckers(state->v4, state->v6);
|
||||
state->statusValue = TextWithEntities{
|
||||
tr::lng_proxy_box_table_available(
|
||||
tr::now,
|
||||
@@ -1635,13 +1640,13 @@ void ProxiesBoxController::ShowApplyConfirmation(
|
||||
if (!weak || state->finished) {
|
||||
return;
|
||||
}
|
||||
DropProxyChecker(state->v4, state->v6, raw);
|
||||
if (!HasProxyCheckers(state->v4, state->v6)) {
|
||||
MTP::DropProxyChecker(state->v4, state->v6, raw);
|
||||
if (!MTP::HasProxyCheckers(state->v4, state->v6)) {
|
||||
state->finished = true;
|
||||
setUnavailable();
|
||||
}
|
||||
});
|
||||
if (!HasProxyCheckers(state->v4, state->v6)) {
|
||||
if (!MTP::HasProxyCheckers(state->v4, state->v6)) {
|
||||
state->finished = true;
|
||||
setUnavailable();
|
||||
}
|
||||
@@ -1681,9 +1686,9 @@ void ProxiesBoxController::ShowApplyConfirmation(
|
||||
const auto enableButton = box->addButton(
|
||||
tr::lng_proxy_box_table_button(),
|
||||
[=] {
|
||||
auto &proxies = Core::App().settings().proxy().list();
|
||||
if (!ranges::contains(proxies, proxy)) {
|
||||
proxies.push_back(proxy);
|
||||
auto &settings = Core::App().settings().proxy();
|
||||
if (settings.indexInList(proxy) < 0) {
|
||||
settings.addToList(proxy);
|
||||
}
|
||||
Core::App().setCurrentProxy(
|
||||
proxy,
|
||||
@@ -1719,9 +1724,10 @@ auto ProxiesBoxController::proxySettingsValue() const
|
||||
void ProxiesBoxController::refreshChecker(Item &item) {
|
||||
item.state = ItemState::Checking;
|
||||
const auto id = item.id;
|
||||
StartProxyCheck(
|
||||
MTP::StartProxyCheck(
|
||||
&_account->mtp(),
|
||||
item.data,
|
||||
Core::App().settings().proxy().tryIPv6(),
|
||||
item.checker,
|
||||
item.checkerv6,
|
||||
[=](Connection *raw, int pingTime) {
|
||||
@@ -1732,8 +1738,8 @@ void ProxiesBoxController::refreshChecker(Item &item) {
|
||||
if (item == end(_list)) {
|
||||
return;
|
||||
}
|
||||
DropProxyChecker(item->checker, item->checkerv6, raw);
|
||||
ResetProxyCheckers(item->checker, item->checkerv6);
|
||||
MTP::DropProxyChecker(item->checker, item->checkerv6, raw);
|
||||
MTP::ResetProxyCheckers(item->checker, item->checkerv6);
|
||||
if (item->state == ItemState::Checking) {
|
||||
item->state = ItemState::Available;
|
||||
item->ping = pingTime;
|
||||
@@ -1748,14 +1754,14 @@ void ProxiesBoxController::refreshChecker(Item &item) {
|
||||
if (item == end(_list)) {
|
||||
return;
|
||||
}
|
||||
DropProxyChecker(item->checker, item->checkerv6, raw);
|
||||
if (!HasProxyCheckers(item->checker, item->checkerv6)
|
||||
MTP::DropProxyChecker(item->checker, item->checkerv6, raw);
|
||||
if (!MTP::HasProxyCheckers(item->checker, item->checkerv6)
|
||||
&& item->state == ItemState::Checking) {
|
||||
item->state = ItemState::Unavailable;
|
||||
updateView(*item);
|
||||
}
|
||||
});
|
||||
if (!HasProxyCheckers(item.checker, item.checkerv6)) {
|
||||
if (!MTP::HasProxyCheckers(item.checker, item.checkerv6)) {
|
||||
item.state = ItemState::Unavailable;
|
||||
}
|
||||
}
|
||||
@@ -1854,8 +1860,8 @@ void ProxiesBoxController::setDeleted(int id, bool deleted) {
|
||||
item->deleted = deleted;
|
||||
|
||||
if (deleted) {
|
||||
auto &proxies = _settings.list();
|
||||
proxies.erase(ranges::remove(proxies, item->data), end(proxies));
|
||||
const auto removed = _settings.removeFromList(item->data);
|
||||
Assert(removed);
|
||||
|
||||
if (item->data == _settings.selected()) {
|
||||
_lastSelectedProxy = _settings.selected();
|
||||
@@ -1871,16 +1877,19 @@ void ProxiesBoxController::setDeleted(int id, bool deleted) {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
auto &proxies = _settings.list();
|
||||
if (ranges::find(proxies, item->data) == end(proxies)) {
|
||||
if (_settings.indexInList(item->data) < 0) {
|
||||
const auto &proxies = _settings.list();
|
||||
auto insertBefore = item + 1;
|
||||
while (insertBefore != end(_list) && insertBefore->deleted) {
|
||||
++insertBefore;
|
||||
}
|
||||
auto insertBeforeIt = (insertBefore == end(_list))
|
||||
? end(proxies)
|
||||
: ranges::find(proxies, insertBefore->data);
|
||||
proxies.insert(insertBeforeIt, item->data);
|
||||
const auto foundIndex = (insertBefore == end(_list))
|
||||
? int(proxies.size())
|
||||
: _settings.indexInList(insertBefore->data);
|
||||
const auto insertIndex = (foundIndex >= 0)
|
||||
? foundIndex
|
||||
: int(proxies.size());
|
||||
_settings.insertToList(insertIndex, item->data);
|
||||
}
|
||||
|
||||
if (!_settings.selected() && _lastSelectedProxy == item->data) {
|
||||
@@ -1919,8 +1928,8 @@ object_ptr<Ui::BoxContent> ProxiesBoxController::editItemBox(int id) {
|
||||
void ProxiesBoxController::replaceItemWith(
|
||||
std::vector<Item>::iterator which,
|
||||
std::vector<Item>::iterator with) {
|
||||
auto &proxies = _settings.list();
|
||||
proxies.erase(ranges::remove(proxies, which->data), end(proxies));
|
||||
const auto removed = _settings.removeFromList(which->data);
|
||||
Assert(removed);
|
||||
|
||||
_views.fire({ which->id });
|
||||
_list.erase(which);
|
||||
@@ -1939,10 +1948,8 @@ void ProxiesBoxController::replaceItemValue(
|
||||
restoreItem(which->id);
|
||||
}
|
||||
|
||||
auto &proxies = _settings.list();
|
||||
const auto i = ranges::find(proxies, which->data);
|
||||
Assert(i != end(proxies));
|
||||
*i = proxy;
|
||||
const auto replaced = _settings.replaceInList(which->data, proxy);
|
||||
Assert(replaced);
|
||||
which->data = proxy;
|
||||
refreshChecker(*which);
|
||||
|
||||
@@ -1978,8 +1985,7 @@ bool ProxiesBoxController::contains(const ProxyData &proxy) const {
|
||||
}
|
||||
|
||||
void ProxiesBoxController::addNewItem(const ProxyData &proxy) {
|
||||
auto &proxies = _settings.list();
|
||||
proxies.push_back(proxy);
|
||||
_settings.addToList(proxy);
|
||||
|
||||
_list.push_back({ ++_idCounter, proxy });
|
||||
refreshChecker(_list.back());
|
||||
@@ -2016,6 +2022,22 @@ void ProxiesBoxController::setProxyForCalls(bool enabled) {
|
||||
saveDelayed();
|
||||
}
|
||||
|
||||
void ProxiesBoxController::setProxyRotationEnabled(bool enabled) {
|
||||
if (_settings.proxyRotationEnabled() == enabled) {
|
||||
return;
|
||||
}
|
||||
_settings.setProxyRotationEnabled(enabled);
|
||||
saveDelayed();
|
||||
}
|
||||
|
||||
void ProxiesBoxController::setProxyRotationTimeout(int value) {
|
||||
if (_settings.proxyRotationTimeout() == value) {
|
||||
return;
|
||||
}
|
||||
_settings.setProxyRotationTimeout(value);
|
||||
saveDelayed();
|
||||
}
|
||||
|
||||
void ProxiesBoxController::setTryIPv6(bool enabled) {
|
||||
if (Core::App().settings().proxy().tryIPv6() == enabled) {
|
||||
return;
|
||||
@@ -2027,6 +2049,7 @@ void ProxiesBoxController::setTryIPv6(bool enabled) {
|
||||
}
|
||||
|
||||
void ProxiesBoxController::saveDelayed() {
|
||||
Core::App().proxyRotationSettingsChanged();
|
||||
_saveTimer.callOnce(kSaveSettingsDelayedTimeout);
|
||||
}
|
||||
|
||||
|
||||
@@ -85,6 +85,8 @@ public:
|
||||
object_ptr<Ui::BoxContent> addNewItemBox();
|
||||
bool setProxySettings(ProxyData::Settings value);
|
||||
void setProxyForCalls(bool enabled);
|
||||
void setProxyRotationEnabled(bool enabled);
|
||||
void setProxyRotationTimeout(int value);
|
||||
void setTryIPv6(bool enabled);
|
||||
rpl::producer<ProxyData::Settings> proxySettingsValue() const;
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "core/sandbox.h"
|
||||
#include "core/local_url_handlers.h"
|
||||
#include "core/launcher.h"
|
||||
#include "core/proxy_rotation_manager.h"
|
||||
#include "core/ui_integration.h"
|
||||
#include "chat_helpers/emoji_keywords.h"
|
||||
#include "chat_helpers/stickers_emoji_image_loader.h"
|
||||
@@ -146,6 +147,7 @@ struct Application::Private {
|
||||
base::Timer quitTimer;
|
||||
UiIntegration uiIntegration;
|
||||
Settings settings;
|
||||
std::unique_ptr<ProxyRotationManager> proxyRotation;
|
||||
};
|
||||
|
||||
Application::Application()
|
||||
@@ -173,6 +175,7 @@ Application::Application()
|
||||
, _setupEmailLock(false)
|
||||
, _autoLockTimer([=] { checkAutoLock(); }) {
|
||||
Ui::Integration::Set(&_private->uiIntegration);
|
||||
_private->proxyRotation = std::make_unique<ProxyRotationManager>();
|
||||
|
||||
_platformIntegration->init();
|
||||
|
||||
@@ -234,6 +237,7 @@ Application::~Application() {
|
||||
// Domain::finish() and there is a violation on Ensures(started()).
|
||||
closeAdditionalWindows();
|
||||
|
||||
_private->proxyRotation = nullptr;
|
||||
_domain->finish();
|
||||
|
||||
Local::finish();
|
||||
@@ -833,6 +837,17 @@ void Application::setCurrentProxy(
|
||||
refreshGlobalProxy();
|
||||
_proxyChanges.fire({ was, now });
|
||||
my.connectionTypeChangesNotify();
|
||||
proxyRotationSettingsChanged();
|
||||
}
|
||||
|
||||
void Application::proxyRotationSettingsChanged() {
|
||||
_private->proxyRotation->settingsChanged();
|
||||
}
|
||||
|
||||
void Application::checkProxyRotation(
|
||||
not_null<Main::Account*> account,
|
||||
int32 state) {
|
||||
_private->proxyRotation->handleConnectionStateChanged(account, state);
|
||||
}
|
||||
|
||||
auto Application::proxyChanges() const -> rpl::producer<ProxyChange> {
|
||||
|
||||
@@ -219,6 +219,8 @@ public:
|
||||
void setCurrentProxy(
|
||||
const MTP::ProxyData &proxy,
|
||||
MTP::ProxyData::Settings settings);
|
||||
void proxyRotationSettingsChanged();
|
||||
void checkProxyRotation(not_null<Main::Account*> account, int32 state);
|
||||
[[nodiscard]] rpl::producer<ProxyChange> proxyChanges() const;
|
||||
void badMtprotoConfigurationError();
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "storage/serialize_common.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace Core {
|
||||
namespace {
|
||||
|
||||
@@ -88,6 +90,22 @@ namespace {
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<int> NormalizeProxyRotationPreferredIndices(
|
||||
std::vector<int> indices,
|
||||
int listSize) {
|
||||
auto filtered = std::vector<int>();
|
||||
filtered.reserve(indices.size());
|
||||
for (const auto index : indices) {
|
||||
if (index < 0
|
||||
|| index >= listSize
|
||||
|| ranges::contains(filtered, index)) {
|
||||
continue;
|
||||
}
|
||||
filtered.push_back(index);
|
||||
}
|
||||
return filtered;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
SettingsProxy::SettingsProxy()
|
||||
@@ -108,7 +126,7 @@ QByteArray SettingsProxy::serialize() const {
|
||||
0,
|
||||
ranges::plus(),
|
||||
&Serialize::bytearraySize)
|
||||
+ 1 * sizeof(qint32); // _checkIpWarningShown
|
||||
+ (4 + int(_proxyRotationPreferredIndices.size())) * sizeof(qint32);
|
||||
auto stream = Serialize::ByteArrayWriter(size);
|
||||
stream
|
||||
<< qint32(_tryIPv6 ? 1 : 0)
|
||||
@@ -119,7 +137,14 @@ QByteArray SettingsProxy::serialize() const {
|
||||
for (const auto &i : serializedList) {
|
||||
stream << i;
|
||||
}
|
||||
stream << qint32(_checkIpWarningShown ? 1 : 0);
|
||||
stream
|
||||
<< qint32(_checkIpWarningShown ? 1 : 0)
|
||||
<< qint32(_proxyRotationEnabled ? 1 : 0)
|
||||
<< qint32(_proxyRotationTimeout)
|
||||
<< qint32(_proxyRotationPreferredIndices.size());
|
||||
for (const auto index : _proxyRotationPreferredIndices) {
|
||||
stream << qint32(index);
|
||||
}
|
||||
return std::move(stream).result();
|
||||
}
|
||||
|
||||
@@ -135,6 +160,7 @@ bool SettingsProxy::setFromSerialized(const QByteArray &serialized) {
|
||||
auto settings = ProxySettingsToInt(_settings);
|
||||
auto listCount = qint32(_list.size());
|
||||
auto selectedProxy = QByteArray();
|
||||
auto list = std::vector<MTP::ProxyData>();
|
||||
|
||||
if (!stream.atEnd()) {
|
||||
stream
|
||||
@@ -144,10 +170,14 @@ bool SettingsProxy::setFromSerialized(const QByteArray &serialized) {
|
||||
>> selectedProxy
|
||||
>> listCount;
|
||||
if (stream.ok()) {
|
||||
if (listCount < 0) {
|
||||
return false;
|
||||
}
|
||||
list.reserve(listCount);
|
||||
for (auto i = 0; i != listCount; ++i) {
|
||||
QByteArray data;
|
||||
stream >> data;
|
||||
_list.push_back(DeserializeProxyData(data));
|
||||
list.push_back(DeserializeProxyData(data));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -156,6 +186,30 @@ bool SettingsProxy::setFromSerialized(const QByteArray &serialized) {
|
||||
if (!stream.atEnd()) {
|
||||
stream >> checkIpWarningShown;
|
||||
}
|
||||
auto proxyRotationEnabled = qint32(_proxyRotationEnabled ? 1 : 0);
|
||||
if (!stream.atEnd()) {
|
||||
stream >> proxyRotationEnabled;
|
||||
}
|
||||
auto proxyRotationTimeout = qint32(_proxyRotationTimeout);
|
||||
if (!stream.atEnd()) {
|
||||
stream >> proxyRotationTimeout;
|
||||
}
|
||||
auto preferredCount = qint32(0);
|
||||
auto preferredIndices = std::vector<int>();
|
||||
if (!stream.atEnd()) {
|
||||
stream >> preferredCount;
|
||||
if (stream.ok()) {
|
||||
if (preferredCount < 0) {
|
||||
return false;
|
||||
}
|
||||
preferredIndices.reserve(preferredCount);
|
||||
for (auto i = 0; i != preferredCount; ++i) {
|
||||
auto index = qint32(0);
|
||||
stream >> index;
|
||||
preferredIndices.push_back(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!stream.ok()) {
|
||||
LOG(("App Error: "
|
||||
@@ -166,8 +220,12 @@ bool SettingsProxy::setFromSerialized(const QByteArray &serialized) {
|
||||
_tryIPv6 = (tryIPv6 == 1);
|
||||
_useProxyForCalls = (useProxyForCalls == 1);
|
||||
_checkIpWarningShown = (checkIpWarningShown == 1);
|
||||
_proxyRotationEnabled = (proxyRotationEnabled == 1);
|
||||
setProxyRotationTimeout(proxyRotationTimeout);
|
||||
_settings = IntToProxySettings(settings);
|
||||
_selected = DeserializeProxyData(selectedProxy);
|
||||
_list = std::move(list);
|
||||
setProxyRotationPreferredIndices(std::move(preferredIndices));
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -192,6 +250,32 @@ void SettingsProxy::setCheckIpWarningShown(bool value) {
|
||||
_checkIpWarningShown = value;
|
||||
}
|
||||
|
||||
const std::vector<int> &SettingsProxy::proxyRotationPreferredIndices() const {
|
||||
return _proxyRotationPreferredIndices;
|
||||
}
|
||||
|
||||
void SettingsProxy::setProxyRotationPreferredIndices(std::vector<int> value) {
|
||||
_proxyRotationPreferredIndices = NormalizeProxyRotationPreferredIndices(
|
||||
std::move(value),
|
||||
int(_list.size()));
|
||||
}
|
||||
|
||||
bool SettingsProxy::promoteProxyRotationPreferredIndex(int index) {
|
||||
if (index < 0 || index >= int(_list.size())) {
|
||||
return false;
|
||||
}
|
||||
auto &indices = _proxyRotationPreferredIndices;
|
||||
const auto i = ranges::find(indices, index);
|
||||
if (i == begin(indices)) {
|
||||
return false;
|
||||
} else if (i != end(indices)) {
|
||||
std::rotate(begin(indices), i, std::next(i));
|
||||
} else {
|
||||
indices.insert(begin(indices), index);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SettingsProxy::tryIPv6() const {
|
||||
return _tryIPv6;
|
||||
}
|
||||
@@ -208,6 +292,24 @@ void SettingsProxy::setUseProxyForCalls(bool value) {
|
||||
_useProxyForCalls = value;
|
||||
}
|
||||
|
||||
bool SettingsProxy::proxyRotationEnabled() const {
|
||||
return _proxyRotationEnabled;
|
||||
}
|
||||
|
||||
void SettingsProxy::setProxyRotationEnabled(bool value) {
|
||||
_proxyRotationEnabled = value;
|
||||
}
|
||||
|
||||
int SettingsProxy::proxyRotationTimeout() const {
|
||||
return _proxyRotationTimeout;
|
||||
}
|
||||
|
||||
void SettingsProxy::setProxyRotationTimeout(int value) {
|
||||
_proxyRotationTimeout = (value > 0)
|
||||
? value
|
||||
: kDefaultProxyRotationTimeout;
|
||||
}
|
||||
|
||||
MTP::ProxyData::Settings SettingsProxy::settings() const {
|
||||
return _settings;
|
||||
}
|
||||
@@ -232,6 +334,62 @@ std::vector<MTP::ProxyData> &SettingsProxy::list() {
|
||||
return _list;
|
||||
}
|
||||
|
||||
void SettingsProxy::setList(std::vector<MTP::ProxyData> value) {
|
||||
_list = std::move(value);
|
||||
_proxyRotationPreferredIndices.clear();
|
||||
}
|
||||
|
||||
void SettingsProxy::addToList(MTP::ProxyData value) {
|
||||
_list.push_back(std::move(value));
|
||||
}
|
||||
|
||||
void SettingsProxy::insertToList(int index, MTP::ProxyData value) {
|
||||
index = std::clamp(index, 0, int(_list.size()));
|
||||
for (auto &existing : _proxyRotationPreferredIndices) {
|
||||
if (existing >= index) {
|
||||
++existing;
|
||||
}
|
||||
}
|
||||
_list.insert(begin(_list) + index, std::move(value));
|
||||
}
|
||||
|
||||
bool SettingsProxy::removeFromList(const MTP::ProxyData &value) {
|
||||
const auto i = ranges::find(_list, value);
|
||||
if (i == end(_list)) {
|
||||
return false;
|
||||
}
|
||||
const auto index = int(i - begin(_list));
|
||||
_list.erase(i);
|
||||
for (auto &existing : _proxyRotationPreferredIndices) {
|
||||
if (existing > index) {
|
||||
--existing;
|
||||
}
|
||||
}
|
||||
_proxyRotationPreferredIndices.erase(
|
||||
std::remove(
|
||||
begin(_proxyRotationPreferredIndices),
|
||||
end(_proxyRotationPreferredIndices),
|
||||
index),
|
||||
end(_proxyRotationPreferredIndices));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SettingsProxy::replaceInList(
|
||||
const MTP::ProxyData &was,
|
||||
MTP::ProxyData value) {
|
||||
const auto i = ranges::find(_list, was);
|
||||
if (i == end(_list)) {
|
||||
return false;
|
||||
}
|
||||
*i = std::move(value);
|
||||
return true;
|
||||
}
|
||||
|
||||
int SettingsProxy::indexInList(const MTP::ProxyData &value) const {
|
||||
const auto i = ranges::find(_list, value);
|
||||
return (i == end(_list)) ? -1 : int(i - begin(_list));
|
||||
}
|
||||
|
||||
rpl::producer<> SettingsProxy::connectionTypeValue() const {
|
||||
return _connectionTypeChanges.events_starting_with({});
|
||||
}
|
||||
|
||||
@@ -9,10 +9,21 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "mtproto/mtproto_proxy_data.h"
|
||||
|
||||
#include <array>
|
||||
|
||||
namespace Core {
|
||||
|
||||
class SettingsProxy final {
|
||||
public:
|
||||
static constexpr auto kProxyRotationTimeouts = std::array{
|
||||
5,
|
||||
10,
|
||||
15,
|
||||
30,
|
||||
60,
|
||||
};
|
||||
static constexpr auto kDefaultProxyRotationTimeout = 10;
|
||||
|
||||
SettingsProxy();
|
||||
|
||||
[[nodiscard]] bool isEnabled() const;
|
||||
@@ -29,6 +40,12 @@ public:
|
||||
[[nodiscard]] bool useProxyForCalls() const;
|
||||
void setUseProxyForCalls(bool value);
|
||||
|
||||
[[nodiscard]] bool proxyRotationEnabled() const;
|
||||
void setProxyRotationEnabled(bool value);
|
||||
|
||||
[[nodiscard]] int proxyRotationTimeout() const;
|
||||
void setProxyRotationTimeout(int value);
|
||||
|
||||
[[nodiscard]] MTP::ProxyData::Settings settings() const;
|
||||
void setSettings(MTP::ProxyData::Settings value);
|
||||
|
||||
@@ -38,8 +55,20 @@ public:
|
||||
[[nodiscard]] bool checkIpWarningShown() const;
|
||||
void setCheckIpWarningShown(bool value);
|
||||
|
||||
[[nodiscard]] const std::vector<int> &proxyRotationPreferredIndices() const;
|
||||
void setProxyRotationPreferredIndices(std::vector<int> value);
|
||||
[[nodiscard]] bool promoteProxyRotationPreferredIndex(int index);
|
||||
|
||||
[[nodiscard]] const std::vector<MTP::ProxyData> &list() const;
|
||||
[[nodiscard]] std::vector<MTP::ProxyData> &list();
|
||||
void setList(std::vector<MTP::ProxyData> value);
|
||||
void addToList(MTP::ProxyData value);
|
||||
void insertToList(int index, MTP::ProxyData value);
|
||||
[[nodiscard]] bool removeFromList(const MTP::ProxyData &value);
|
||||
[[nodiscard]] bool replaceInList(
|
||||
const MTP::ProxyData &was,
|
||||
MTP::ProxyData value);
|
||||
[[nodiscard]] int indexInList(const MTP::ProxyData &value) const;
|
||||
|
||||
[[nodiscard]] QByteArray serialize() const;
|
||||
bool setFromSerialized(const QByteArray &serialized);
|
||||
@@ -47,14 +76,16 @@ public:
|
||||
private:
|
||||
bool _tryIPv6 = false;
|
||||
bool _useProxyForCalls = false;
|
||||
bool _proxyRotationEnabled = false;
|
||||
bool _checkIpWarningShown = false;
|
||||
int _proxyRotationTimeout = kDefaultProxyRotationTimeout;
|
||||
MTP::ProxyData::Settings _settings = MTP::ProxyData::Settings::System;
|
||||
MTP::ProxyData _selected;
|
||||
std::vector<MTP::ProxyData> _list;
|
||||
std::vector<int> _proxyRotationPreferredIndices;
|
||||
|
||||
rpl::event_stream<> _connectionTypeChanges;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Core
|
||||
|
||||
|
||||
@@ -0,0 +1,359 @@
|
||||
/*
|
||||
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 "core/proxy_rotation_manager.h"
|
||||
|
||||
#include "core/application.h"
|
||||
#include "core/core_settings.h"
|
||||
#include "main/main_account.h"
|
||||
#include "main/main_domain.h"
|
||||
#include "mtproto/facade.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace Core {
|
||||
namespace {
|
||||
|
||||
constexpr auto kProxyRotationCheckInterval = 2 * crl::time(1000);
|
||||
constexpr auto kProxyRotationCheckLifetime = 20 * crl::time(1000);
|
||||
constexpr auto kProxyRotationMaxActiveChecks = 10;
|
||||
|
||||
} // namespace
|
||||
|
||||
ProxyRotationManager::ProxyRotationManager()
|
||||
: _checkTimer([=] { runChecks(); })
|
||||
, _switchTimer([=] { switchTimerDone(); }) {
|
||||
App().domain().accountsChanges(
|
||||
) | rpl::on_next([=] {
|
||||
stopChecking();
|
||||
reevaluate();
|
||||
}, _lifetime);
|
||||
}
|
||||
|
||||
void ProxyRotationManager::settingsChanged() {
|
||||
stopChecking();
|
||||
pruneRemovedEntries();
|
||||
reevaluate();
|
||||
}
|
||||
|
||||
void ProxyRotationManager::handleConnectionStateChanged(
|
||||
not_null<Main::Account*> account,
|
||||
int32 state) {
|
||||
(void)account;
|
||||
(void)state;
|
||||
reevaluate();
|
||||
}
|
||||
|
||||
bool ProxyRotationManager::shouldObserve() const {
|
||||
const auto &settings = App().settings().proxy();
|
||||
return settings.isEnabled()
|
||||
&& settings.selected()
|
||||
&& settings.proxyRotationEnabled()
|
||||
&& (settings.list().size() > 1);
|
||||
}
|
||||
|
||||
std::vector<not_null<Main::Account*>> ProxyRotationManager::productionAccounts() const {
|
||||
auto result = std::vector<not_null<Main::Account*>>();
|
||||
for (const auto &entry : App().domain().accounts()) {
|
||||
const auto account = entry.account.get();
|
||||
if (!account->sessionExists() || account->mtp().isTestMode()) {
|
||||
continue;
|
||||
}
|
||||
result.push_back(account);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
not_null<Main::Account*> ProxyRotationManager::accountForChecks() const {
|
||||
if (App().someSessionExists()
|
||||
&& App().activeAccount().sessionExists()
|
||||
&& !App().activeAccount().mtp().isTestMode()) {
|
||||
return &App().activeAccount();
|
||||
}
|
||||
const auto accounts = productionAccounts();
|
||||
Expects(!accounts.empty());
|
||||
return accounts.front();
|
||||
}
|
||||
|
||||
auto ProxyRotationManager::find(
|
||||
const MTP::ProxyData &proxy) -> Entry* {
|
||||
const auto i = ranges::find(
|
||||
_entries,
|
||||
proxy,
|
||||
[](const Entry &entry) { return entry.proxy; });
|
||||
return (i == end(_entries)) ? nullptr : &*i;
|
||||
}
|
||||
|
||||
auto ProxyRotationManager::ensure(
|
||||
const MTP::ProxyData &proxy) -> Entry& {
|
||||
if (const auto entry = find(proxy)) {
|
||||
return *entry;
|
||||
}
|
||||
_entries.push_back({ .proxy = proxy });
|
||||
return _entries.back();
|
||||
}
|
||||
|
||||
void ProxyRotationManager::reevaluate() {
|
||||
if (!shouldObserve()) {
|
||||
stopChecking();
|
||||
return;
|
||||
}
|
||||
const auto accounts = productionAccounts();
|
||||
if (accounts.empty()) {
|
||||
stopChecking();
|
||||
return;
|
||||
}
|
||||
const auto stateProj = [](not_null<Main::Account*> account) {
|
||||
return account->mtp().dcstate();
|
||||
};
|
||||
if (ranges::contains(accounts, MTP::ConnectedState, stateProj)) {
|
||||
stopChecking();
|
||||
return;
|
||||
}
|
||||
startChecking();
|
||||
}
|
||||
|
||||
void ProxyRotationManager::startChecking() {
|
||||
if (_checking) {
|
||||
return;
|
||||
}
|
||||
_checking = true;
|
||||
_waitingToSwitch = false;
|
||||
_switchStartedAt = crl::now();
|
||||
updateProbeOrder();
|
||||
runChecks();
|
||||
const auto timeout = App().settings().proxy().proxyRotationTimeout();
|
||||
_switchTimer.callOnce(timeout * crl::time(1000));
|
||||
}
|
||||
|
||||
void ProxyRotationManager::stopChecking() {
|
||||
_checkTimer.cancel();
|
||||
_switchTimer.cancel();
|
||||
_checking = false;
|
||||
_waitingToSwitch = false;
|
||||
_switchStartedAt = 0;
|
||||
_probeOrder.clear();
|
||||
_nextCheckIndex = 0;
|
||||
clearPendingChecks();
|
||||
}
|
||||
|
||||
void ProxyRotationManager::pruneRemovedEntries() {
|
||||
const auto &settings = App().settings().proxy();
|
||||
_entries.erase(
|
||||
std::remove_if(begin(_entries), end(_entries), [&](const Entry &entry) {
|
||||
return (settings.indexInList(entry.proxy) < 0);
|
||||
}),
|
||||
end(_entries));
|
||||
}
|
||||
|
||||
void ProxyRotationManager::updateProbeOrder() {
|
||||
const auto &settings = App().settings().proxy();
|
||||
const auto currentIndex = settings.indexInList(settings.selected());
|
||||
_probeOrder.clear();
|
||||
_probeOrder.reserve(settings.list().size());
|
||||
for (const auto index : settings.proxyRotationPreferredIndices()) {
|
||||
if (index == currentIndex) {
|
||||
continue;
|
||||
}
|
||||
_probeOrder.push_back(index);
|
||||
}
|
||||
for (auto i = 0, count = int(settings.list().size()); i != count; ++i) {
|
||||
if (i == currentIndex || ranges::contains(_probeOrder, i)) {
|
||||
continue;
|
||||
}
|
||||
_probeOrder.push_back(i);
|
||||
}
|
||||
_nextCheckIndex = 0;
|
||||
}
|
||||
|
||||
void ProxyRotationManager::continueChecking(crl::time delay) {
|
||||
if (!_checking) {
|
||||
return;
|
||||
}
|
||||
if (_checkTimer.isActive()) {
|
||||
_checkTimer.cancel();
|
||||
}
|
||||
_checkTimer.callOnce(delay);
|
||||
}
|
||||
|
||||
void ProxyRotationManager::runChecks() {
|
||||
if (!_checking) {
|
||||
return;
|
||||
}
|
||||
if (!shouldObserve()) {
|
||||
stopChecking();
|
||||
return;
|
||||
}
|
||||
const auto accounts = productionAccounts();
|
||||
if (accounts.empty()
|
||||
|| ranges::contains(
|
||||
accounts,
|
||||
MTP::ConnectedState,
|
||||
[](not_null<Main::Account*> account) {
|
||||
return account->mtp().dcstate();
|
||||
})) {
|
||||
stopChecking();
|
||||
return;
|
||||
}
|
||||
pruneExpiredChecks();
|
||||
startNextCheck();
|
||||
continueChecking(kProxyRotationCheckInterval);
|
||||
}
|
||||
|
||||
void ProxyRotationManager::pruneExpiredChecks() {
|
||||
const auto now = crl::now();
|
||||
for (auto &entry : _entries) {
|
||||
if (!entry.checking
|
||||
|| (now - entry.startedAt < kProxyRotationCheckLifetime)) {
|
||||
continue;
|
||||
}
|
||||
MTP::ResetProxyCheckers(entry.v4, entry.v6);
|
||||
entry.checking = false;
|
||||
entry.startedAt = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void ProxyRotationManager::startNextCheck() {
|
||||
if (_probeOrder.empty()) {
|
||||
return;
|
||||
}
|
||||
if (ranges::count(_entries, true, &Entry::checking)
|
||||
>= kProxyRotationMaxActiveChecks) {
|
||||
return;
|
||||
}
|
||||
const auto &settings = App().settings().proxy();
|
||||
auto attemptsLeft = int(_probeOrder.size());
|
||||
while (attemptsLeft-- > 0) {
|
||||
if (_nextCheckIndex >= int(_probeOrder.size())) {
|
||||
_nextCheckIndex = 0;
|
||||
}
|
||||
const auto listIndex = _probeOrder[_nextCheckIndex++];
|
||||
if (listIndex < 0 || listIndex >= int(settings.list().size())) {
|
||||
continue;
|
||||
}
|
||||
const auto &proxy = settings.list()[listIndex];
|
||||
auto &entry = ensure(proxy);
|
||||
if (entry.checking) {
|
||||
continue;
|
||||
}
|
||||
entry.checking = true;
|
||||
entry.startedAt = crl::now();
|
||||
MTP::StartProxyCheck(
|
||||
&accountForChecks()->mtp(),
|
||||
proxy,
|
||||
settings.tryIPv6(),
|
||||
entry.v4,
|
||||
entry.v6,
|
||||
[=](MTP::details::AbstractConnection *raw, int ping) {
|
||||
checkDone(proxy, raw, ping);
|
||||
},
|
||||
[=](MTP::details::AbstractConnection *raw) {
|
||||
checkFailed(proxy, raw);
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void ProxyRotationManager::switchTimerDone() {
|
||||
if (!_checking || !shouldSwitchToAvailable()) {
|
||||
return;
|
||||
}
|
||||
_waitingToSwitch = !switchToAvailable();
|
||||
}
|
||||
|
||||
void ProxyRotationManager::clearPendingChecks() {
|
||||
for (auto &entry : _entries) {
|
||||
MTP::ResetProxyCheckers(entry.v4, entry.v6);
|
||||
entry.checking = false;
|
||||
entry.startedAt = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void ProxyRotationManager::checkDone(
|
||||
const MTP::ProxyData &proxy,
|
||||
not_null<MTP::details::AbstractConnection*> raw,
|
||||
int ping) {
|
||||
const auto entry = find(proxy);
|
||||
if (!entry
|
||||
|| !entry->checking
|
||||
|| ((entry->v4.get() != raw) && (entry->v6.get() != raw))) {
|
||||
return;
|
||||
}
|
||||
MTP::DropProxyChecker(entry->v4, entry->v6, raw);
|
||||
MTP::ResetProxyCheckers(entry->v4, entry->v6);
|
||||
entry->checking = false;
|
||||
entry->startedAt = 0;
|
||||
entry->availableAt = crl::now();
|
||||
const auto proxySettings = &App().settings().proxy();
|
||||
if (const auto index = proxySettings->indexInList(proxy); index >= 0) {
|
||||
if (proxySettings->promoteProxyRotationPreferredIndex(index)) {
|
||||
App().saveSettingsDelayed();
|
||||
}
|
||||
}
|
||||
updateProbeOrder();
|
||||
if (_waitingToSwitch && shouldSwitchToAvailable()) {
|
||||
_waitingToSwitch = !switchToAvailable();
|
||||
}
|
||||
}
|
||||
|
||||
void ProxyRotationManager::checkFailed(
|
||||
const MTP::ProxyData &proxy,
|
||||
not_null<MTP::details::AbstractConnection*> raw) {
|
||||
const auto entry = find(proxy);
|
||||
if (!entry
|
||||
|| !entry->checking
|
||||
|| ((entry->v4.get() != raw) && (entry->v6.get() != raw))) {
|
||||
return;
|
||||
}
|
||||
MTP::DropProxyChecker(entry->v4, entry->v6, raw);
|
||||
if (MTP::HasProxyCheckers(entry->v4, entry->v6)) {
|
||||
return;
|
||||
}
|
||||
entry->checking = false;
|
||||
entry->startedAt = 0;
|
||||
}
|
||||
|
||||
bool ProxyRotationManager::switchToAvailable() {
|
||||
if (!_checking) {
|
||||
return false;
|
||||
}
|
||||
const auto &settings = App().settings().proxy();
|
||||
for (const auto index : _probeOrder) {
|
||||
if (index < 0 || index >= int(settings.list().size())) {
|
||||
continue;
|
||||
}
|
||||
const auto &proxy = settings.list()[index];
|
||||
const auto entry = find(proxy);
|
||||
if (!entry || entry->checking || !entry->availableAt) {
|
||||
continue;
|
||||
}
|
||||
if (entry->availableAt < _switchStartedAt) {
|
||||
continue;
|
||||
}
|
||||
_waitingToSwitch = false;
|
||||
App().setCurrentProxy(proxy, MTP::ProxyData::Settings::Enabled);
|
||||
App().saveSettingsDelayed();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ProxyRotationManager::shouldSwitchToAvailable() const {
|
||||
if (!_checking || !shouldObserve()) {
|
||||
return false;
|
||||
}
|
||||
const auto accounts = productionAccounts();
|
||||
return !accounts.empty()
|
||||
&& !ranges::contains(
|
||||
accounts,
|
||||
MTP::ConnectedState,
|
||||
[](not_null<Main::Account*> account) {
|
||||
return account->mtp().dcstate();
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace Core
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
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
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "base/timer.h"
|
||||
#include "mtproto/proxy_check.h"
|
||||
|
||||
#include <rpl/lifetime.h>
|
||||
#include <vector>
|
||||
|
||||
namespace Main {
|
||||
class Account;
|
||||
} // namespace Main
|
||||
|
||||
namespace Core {
|
||||
|
||||
class ProxyRotationManager final {
|
||||
public:
|
||||
ProxyRotationManager();
|
||||
|
||||
void settingsChanged();
|
||||
void handleConnectionStateChanged(
|
||||
not_null<Main::Account*> account,
|
||||
int32 state);
|
||||
|
||||
private:
|
||||
struct Entry {
|
||||
MTP::ProxyData proxy;
|
||||
MTP::ProxyCheckConnection v4;
|
||||
MTP::ProxyCheckConnection v6;
|
||||
bool checking = false;
|
||||
crl::time startedAt = 0;
|
||||
crl::time availableAt = 0;
|
||||
};
|
||||
|
||||
[[nodiscard]] bool shouldObserve() const;
|
||||
[[nodiscard]] std::vector<not_null<Main::Account*>> productionAccounts() const;
|
||||
[[nodiscard]] not_null<Main::Account*> accountForChecks() const;
|
||||
[[nodiscard]] Entry *find(const MTP::ProxyData &proxy);
|
||||
[[nodiscard]] Entry &ensure(const MTP::ProxyData &proxy);
|
||||
|
||||
void reevaluate();
|
||||
void startChecking();
|
||||
void stopChecking();
|
||||
void pruneRemovedEntries();
|
||||
void updateProbeOrder();
|
||||
void continueChecking(crl::time delay);
|
||||
void runChecks();
|
||||
void pruneExpiredChecks();
|
||||
void startNextCheck();
|
||||
void switchTimerDone();
|
||||
void clearPendingChecks();
|
||||
void checkDone(
|
||||
const MTP::ProxyData &proxy,
|
||||
not_null<MTP::details::AbstractConnection*> raw,
|
||||
int ping);
|
||||
void checkFailed(
|
||||
const MTP::ProxyData &proxy,
|
||||
not_null<MTP::details::AbstractConnection*> raw);
|
||||
[[nodiscard]] bool switchToAvailable();
|
||||
[[nodiscard]] bool shouldSwitchToAvailable() const;
|
||||
|
||||
base::Timer _checkTimer;
|
||||
base::Timer _switchTimer;
|
||||
std::vector<Entry> _entries;
|
||||
std::vector<int> _probeOrder;
|
||||
int _nextCheckIndex = 0;
|
||||
bool _checking = false;
|
||||
bool _waitingToSwitch = false;
|
||||
crl::time _switchStartedAt = 0;
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Core
|
||||
@@ -462,6 +462,7 @@ void Account::startMtp(std::unique_ptr<MTP::Config> config) {
|
||||
_mtp->setStateChangedHandler([=](MTP::ShiftedDcId dc, int32 state) {
|
||||
if (dc == _mtp->mainDcId()) {
|
||||
Core::App().settings().proxy().connectionTypeChangesNotify();
|
||||
Core::App().checkProxyRotation(this, state);
|
||||
}
|
||||
});
|
||||
_mtp->setSessionResetHandler([=](MTP::ShiftedDcId shiftedDcId) {
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
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/proxy_check.h"
|
||||
|
||||
#include "mtproto/facade.h"
|
||||
#include "mtproto/mtproto_dc_options.h"
|
||||
|
||||
namespace MTP {
|
||||
|
||||
using Connection = details::AbstractConnection;
|
||||
|
||||
void ResetProxyCheckers(
|
||||
ProxyCheckConnection &v4,
|
||||
ProxyCheckConnection &v6) {
|
||||
v4 = nullptr;
|
||||
v6 = nullptr;
|
||||
}
|
||||
|
||||
void DropProxyChecker(
|
||||
ProxyCheckConnection &v4,
|
||||
ProxyCheckConnection &v6,
|
||||
not_null<Connection*> raw) {
|
||||
if (v4.get() == raw) {
|
||||
v4 = nullptr;
|
||||
} else if (v6.get() == raw) {
|
||||
v6 = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool HasProxyCheckers(
|
||||
const ProxyCheckConnection &v4,
|
||||
const ProxyCheckConnection &v6) {
|
||||
return v4 || v6;
|
||||
}
|
||||
|
||||
void StartProxyCheck(
|
||||
not_null<Instance*> mtproto,
|
||||
const ProxyData &proxy,
|
||||
bool tryIPv6,
|
||||
ProxyCheckConnection &v4,
|
||||
ProxyCheckConnection &v6,
|
||||
Fn<void(Connection *raw, int ping)> done,
|
||||
Fn<void(Connection *raw)> fail) {
|
||||
using Variants = DcOptions::Variants;
|
||||
|
||||
ResetProxyCheckers(v4, v6);
|
||||
const auto connType = (proxy.type == ProxyData::Type::Http)
|
||||
? Variants::Http
|
||||
: Variants::Tcp;
|
||||
const auto dcId = mtproto->mainDcId();
|
||||
const auto setup = [&](ProxyCheckConnection &checker, const bytes::vector &secret) {
|
||||
checker = Connection::Create(
|
||||
mtproto,
|
||||
connType,
|
||||
QThread::currentThread(),
|
||||
secret,
|
||||
proxy);
|
||||
const auto raw = checker.get();
|
||||
raw->connect(raw, &Connection::connected, [=] {
|
||||
if (done) {
|
||||
done(raw, raw->pingTime());
|
||||
}
|
||||
});
|
||||
const auto failed = [=] {
|
||||
if (fail) {
|
||||
fail(raw);
|
||||
}
|
||||
};
|
||||
raw->connect(raw, &Connection::disconnected, failed);
|
||||
raw->connect(raw, &Connection::error, failed);
|
||||
};
|
||||
if (proxy.type == ProxyData::Type::Mtproto) {
|
||||
const auto secret = proxy.secretFromMtprotoPassword();
|
||||
setup(v4, secret);
|
||||
v4->connectToServer(
|
||||
proxy.host,
|
||||
proxy.port,
|
||||
secret,
|
||||
dcId,
|
||||
false);
|
||||
return;
|
||||
}
|
||||
const auto options = mtproto->dcOptions().lookup(
|
||||
dcId,
|
||||
DcType::Regular,
|
||||
true);
|
||||
const auto tryConnect = [&](ProxyCheckConnection &checker, Variants::Address address) {
|
||||
const auto &list = options.data[address][connType];
|
||||
if (list.empty() || ((address == Variants::IPv6) && !tryIPv6)) {
|
||||
checker = nullptr;
|
||||
return;
|
||||
}
|
||||
const auto &endpoint = list.front();
|
||||
setup(checker, endpoint.secret);
|
||||
checker->connectToServer(
|
||||
QString::fromStdString(endpoint.ip),
|
||||
endpoint.port,
|
||||
endpoint.secret,
|
||||
dcId,
|
||||
false);
|
||||
};
|
||||
tryConnect(v4, Variants::IPv4);
|
||||
tryConnect(v6, Variants::IPv6);
|
||||
}
|
||||
|
||||
} // namespace MTP
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
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
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "mtproto/connection_abstract.h"
|
||||
|
||||
namespace MTP {
|
||||
|
||||
using ProxyCheckConnection = details::ConnectionPointer;
|
||||
|
||||
void ResetProxyCheckers(
|
||||
ProxyCheckConnection &v4,
|
||||
ProxyCheckConnection &v6);
|
||||
void DropProxyChecker(
|
||||
ProxyCheckConnection &v4,
|
||||
ProxyCheckConnection &v6,
|
||||
not_null<details::AbstractConnection*> raw);
|
||||
[[nodiscard]] bool HasProxyCheckers(
|
||||
const ProxyCheckConnection &v4,
|
||||
const ProxyCheckConnection &v6);
|
||||
void StartProxyCheck(
|
||||
not_null<Instance*> mtproto,
|
||||
const ProxyData &proxy,
|
||||
bool tryIPv6,
|
||||
ProxyCheckConnection &v4,
|
||||
ProxyCheckConnection &v6,
|
||||
Fn<void(details::AbstractConnection *raw, int ping)> done,
|
||||
Fn<void(details::AbstractConnection *raw)> fail);
|
||||
|
||||
} // namespace MTP
|
||||
@@ -487,9 +487,9 @@ bool ReadSetting(
|
||||
proxySettings.setSettings(proxy
|
||||
? MTP::ProxyData::Settings::Enabled
|
||||
: MTP::ProxyData::Settings::System);
|
||||
proxySettings.list() = proxy
|
||||
? std::vector<MTP::ProxyData>{ 1, proxy }
|
||||
: std::vector<MTP::ProxyData>{};
|
||||
proxySettings.setList(proxy
|
||||
? std::vector<MTP::ProxyData>{ proxy }
|
||||
: std::vector<MTP::ProxyData>{});
|
||||
Core::App().refreshGlobalProxy();
|
||||
context.legacyRead = true;
|
||||
} break;
|
||||
@@ -562,16 +562,16 @@ bool ReadSetting(
|
||||
if (!CheckStreamStatus(stream)) {
|
||||
return false;
|
||||
}
|
||||
proxySettings.list() = list;
|
||||
proxySettings.setList(std::move(list));
|
||||
if (connectionType == dbictProxiesListOld) {
|
||||
settings = static_cast<qint32>(
|
||||
(index > 0 && index <= list.size()
|
||||
(index > 0 && index <= proxySettings.list().size()
|
||||
? MTP::ProxyData::Settings::Enabled
|
||||
: MTP::ProxyData::Settings::System));
|
||||
index = std::abs(index);
|
||||
}
|
||||
proxySettings.setSelected((index > 0 && index <= list.size())
|
||||
? list[index - 1]
|
||||
proxySettings.setSelected((index > 0 && index <= proxySettings.list().size())
|
||||
? proxySettings.list()[index - 1]
|
||||
: MTP::ProxyData());
|
||||
|
||||
const auto unchecked = static_cast<MTP::ProxyData::Settings>(settings);
|
||||
@@ -596,14 +596,14 @@ bool ReadSetting(
|
||||
return false;
|
||||
}
|
||||
if (proxy) {
|
||||
proxySettings.list() = { 1, proxy };
|
||||
proxySettings.setList({ proxy });
|
||||
proxySettings.setSelected(proxy);
|
||||
proxySettings.setSettings((connectionType == dbictTcpProxy
|
||||
|| connectionType == dbictHttpProxy)
|
||||
? MTP::ProxyData::Settings::Enabled
|
||||
: MTP::ProxyData::Settings::System);
|
||||
} else {
|
||||
proxySettings.list() = {};
|
||||
proxySettings.setList({});
|
||||
proxySettings.setSelected(MTP::ProxyData());
|
||||
proxySettings.setSettings(MTP::ProxyData::Settings::System);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user