514 lines
13 KiB
C++
514 lines
13 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 "main/main_domain.h"
|
|
|
|
#include "core/application.h"
|
|
#include "core/core_settings.h"
|
|
#include "core/shortcuts.h"
|
|
#include "core/crash_reports.h"
|
|
#include "main/main_account.h"
|
|
#include "main/main_session.h"
|
|
#include "data/data_session.h"
|
|
#include "data/data_changes.h"
|
|
#include "data/data_user.h"
|
|
#include "mtproto/mtproto_config.h"
|
|
#include "mtproto/mtproto_dc_options.h"
|
|
#include "storage/storage_domain.h"
|
|
#include "storage/storage_account.h"
|
|
#include "storage/localstorage.h"
|
|
#include "export/export_settings.h"
|
|
#include "window/notifications_manager.h"
|
|
#include "window/window_controller.h"
|
|
#include "data/data_peer_values.h" // Data::AmPremiumValue.
|
|
|
|
namespace Main {
|
|
|
|
Domain::Domain(const QString &dataName)
|
|
: _dataName(dataName)
|
|
, _local(std::make_unique<Storage::Domain>(this, dataName)) {
|
|
_active.changes(
|
|
) | rpl::take(1) | rpl::start_with_next([] {
|
|
// In case we had a legacy passcoded app we start settings here.
|
|
Core::App().startSettingsAndBackground();
|
|
|
|
Core::App().notifications().createManager();
|
|
}, _lifetime);
|
|
|
|
_active.changes(
|
|
) | rpl::map([](Main::Account *account) {
|
|
return account ? account->sessionValue() : rpl::never<Session*>();
|
|
}) | rpl::flatten_latest(
|
|
) | rpl::map([](Main::Session *session) {
|
|
return session
|
|
? session->changes().peerFlagsValue(
|
|
session->user(),
|
|
Data::PeerUpdate::Flag::Username)
|
|
: rpl::never<Data::PeerUpdate>();
|
|
}) | rpl::flatten_latest(
|
|
) | rpl::start_with_next([](const Data::PeerUpdate &update) {
|
|
CrashReports::SetAnnotation("Username", update.peer->userName());
|
|
}, _lifetime);
|
|
}
|
|
|
|
Domain::~Domain() = default;
|
|
|
|
bool Domain::started() const {
|
|
return !_accounts.empty();
|
|
}
|
|
|
|
Storage::StartResult Domain::start(const QByteArray &passcode) {
|
|
Expects(!started());
|
|
|
|
const auto result = _local->start(passcode);
|
|
if (result == Storage::StartResult::Success) {
|
|
activateAfterStarting();
|
|
crl::on_main(&Core::App(), [=] { suggestExportIfNeeded(); });
|
|
} else {
|
|
Assert(!started());
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void Domain::finish() {
|
|
_accountToActivate = -1;
|
|
_active.reset(nullptr);
|
|
base::take(_accounts);
|
|
}
|
|
|
|
void Domain::suggestExportIfNeeded() {
|
|
Expects(started());
|
|
|
|
for (const auto &[index, account] : _accounts) {
|
|
if (const auto session = account->maybeSession()) {
|
|
const auto settings = session->local().readExportSettings();
|
|
if (const auto availableAt = settings.availableAt) {
|
|
session->data().suggestStartExport(availableAt);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Domain::accountAddedInStorage(AccountWithIndex accountWithIndex) {
|
|
Expects(accountWithIndex.account != nullptr);
|
|
|
|
for (const auto &[index, _] : _accounts) {
|
|
if (index == accountWithIndex.index) {
|
|
Unexpected("Repeated account index.");
|
|
}
|
|
}
|
|
_accounts.push_back(std::move(accountWithIndex));
|
|
}
|
|
|
|
void Domain::activateFromStorage(int index) {
|
|
_accountToActivate = index;
|
|
}
|
|
|
|
int Domain::activeForStorage() const {
|
|
return _accountToActivate;
|
|
}
|
|
|
|
void Domain::resetWithForgottenPasscode() {
|
|
if (_accounts.empty()) {
|
|
_local->startFromScratch();
|
|
activateAfterStarting();
|
|
} else {
|
|
for (const auto &[index, account] : _accounts) {
|
|
account->logOut();
|
|
}
|
|
}
|
|
}
|
|
|
|
void Domain::activateAfterStarting() {
|
|
Expects(started());
|
|
|
|
auto toActivate = _accounts.front().account.get();
|
|
for (const auto &[index, account] : _accounts) {
|
|
if (index == _accountToActivate) {
|
|
toActivate = account.get();
|
|
}
|
|
watchSession(account.get());
|
|
}
|
|
|
|
activate(toActivate);
|
|
removePasscodeIfEmpty();
|
|
}
|
|
|
|
const std::vector<Domain::AccountWithIndex> &Domain::accounts() const {
|
|
return _accounts;
|
|
}
|
|
|
|
std::vector<not_null<Account*>> Domain::orderedAccounts() const {
|
|
const auto order = Core::App().settings().accountsOrder();
|
|
auto accounts = ranges::views::all(
|
|
_accounts
|
|
) | ranges::views::transform([](const Domain::AccountWithIndex &a) {
|
|
return not_null{ a.account.get() };
|
|
}) | ranges::to_vector;
|
|
ranges::stable_sort(accounts, [&](
|
|
not_null<Account*> a,
|
|
not_null<Account*> b) {
|
|
const auto aIt = a->sessionExists()
|
|
? ranges::find(order, a->session().uniqueId())
|
|
: end(order);
|
|
const auto bIt = b->sessionExists()
|
|
? ranges::find(order, b->session().uniqueId())
|
|
: end(order);
|
|
return aIt < bIt;
|
|
});
|
|
return accounts;
|
|
}
|
|
|
|
rpl::producer<> Domain::accountsChanges() const {
|
|
return _accountsChanges.events();
|
|
}
|
|
|
|
Account *Domain::maybeLastOrSomeAuthedAccount() {
|
|
auto result = (Account*)nullptr;
|
|
for (const auto &[index, account] : _accounts) {
|
|
if (!account->sessionExists()) {
|
|
continue;
|
|
} else if (index == _lastActiveIndex) {
|
|
return account.get();
|
|
} else if (!result) {
|
|
result = account.get();
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
int Domain::accountsAuthedCount() const {
|
|
auto result = 0;
|
|
for (const auto &[index, account] : _accounts) {
|
|
if (account->sessionExists()) {
|
|
++result;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
rpl::producer<Account*> Domain::activeValue() const {
|
|
return _active.value();
|
|
}
|
|
|
|
Account &Domain::active() const {
|
|
Expects(!_accounts.empty());
|
|
|
|
Ensures(_active.current() != nullptr);
|
|
return *_active.current();
|
|
}
|
|
|
|
rpl::producer<not_null<Account*>> Domain::activeChanges() const {
|
|
return _active.changes() | rpl::map([](Account *value) {
|
|
return not_null{ value };
|
|
});
|
|
}
|
|
|
|
rpl::producer<Session*> Domain::activeSessionChanges() const {
|
|
return _activeSessions.events();
|
|
}
|
|
|
|
rpl::producer<Session*> Domain::activeSessionValue() const {
|
|
const auto current = _accounts.empty()
|
|
? nullptr
|
|
: active().maybeSession();
|
|
return rpl::single(current) | rpl::then(_activeSessions.events());
|
|
}
|
|
|
|
int Domain::unreadBadge() const {
|
|
return _unreadBadge;
|
|
}
|
|
|
|
bool Domain::unreadBadgeMuted() const {
|
|
return _unreadBadgeMuted;
|
|
}
|
|
|
|
rpl::producer<> Domain::unreadBadgeChanges() const {
|
|
return _unreadBadgeChanges.events();
|
|
}
|
|
|
|
void Domain::notifyUnreadBadgeChanged() {
|
|
for (const auto &[index, account] : _accounts) {
|
|
if (const auto session = account->maybeSession()) {
|
|
session->data().notifyUnreadBadgeChanged();
|
|
}
|
|
}
|
|
}
|
|
|
|
void Domain::updateUnreadBadge() {
|
|
_unreadBadge = 0;
|
|
_unreadBadgeMuted = true;
|
|
for (const auto &[index, account] : _accounts) {
|
|
if (const auto session = account->maybeSession()) {
|
|
const auto data = &session->data();
|
|
_unreadBadge += data->unreadBadge();
|
|
if (!data->unreadBadgeMuted()) {
|
|
_unreadBadgeMuted = false;
|
|
}
|
|
}
|
|
}
|
|
_unreadBadgeChanges.fire({});
|
|
}
|
|
|
|
void Domain::scheduleUpdateUnreadBadge() {
|
|
if (_unreadBadgeUpdateScheduled) {
|
|
return;
|
|
}
|
|
_unreadBadgeUpdateScheduled = true;
|
|
Core::App().postponeCall(crl::guard(&Core::App(), [=] {
|
|
_unreadBadgeUpdateScheduled = false;
|
|
updateUnreadBadge();
|
|
}));
|
|
}
|
|
|
|
not_null<Main::Account*> Domain::add(MTP::Environment environment) {
|
|
Expects(started());
|
|
Expects(_accounts.size() < kPremiumMaxAccounts);
|
|
|
|
static const auto cloneConfig = [](const MTP::Config &config) {
|
|
return std::make_unique<MTP::Config>(config);
|
|
};
|
|
auto mainDcId = MTP::Instance::Fields::kNotSetMainDc;
|
|
const auto accountConfig = [&](not_null<Account*> account) {
|
|
mainDcId = account->mtp().mainDcId();
|
|
return cloneConfig(account->mtp().config());
|
|
};
|
|
auto config = [&] {
|
|
if (_active.current()->mtp().environment() == environment) {
|
|
return accountConfig(_active.current());
|
|
}
|
|
for (const auto &[index, account] : _accounts) {
|
|
if (account->mtp().environment() == environment) {
|
|
return accountConfig(account.get());
|
|
}
|
|
}
|
|
return (environment == MTP::Environment::Production)
|
|
? cloneConfig(Core::App().fallbackProductionConfig())
|
|
: std::make_unique<MTP::Config>(environment);
|
|
}();
|
|
auto index = 0;
|
|
while (ranges::contains(_accounts, index, &AccountWithIndex::index)) {
|
|
++index;
|
|
}
|
|
_accounts.push_back(AccountWithIndex{
|
|
.index = index,
|
|
.account = std::make_unique<Account>(this, _dataName, index)
|
|
});
|
|
const auto account = _accounts.back().account.get();
|
|
account->setMtpMainDcId(mainDcId);
|
|
_local->startAdded(account, std::move(config));
|
|
watchSession(account);
|
|
_accountsChanges.fire({});
|
|
|
|
auto &settings = Core::App().settings();
|
|
if (_accounts.size() == 2 && !settings.mainMenuAccountsShown()) {
|
|
settings.setMainMenuAccountsShown(true);
|
|
Core::App().saveSettingsDelayed();
|
|
}
|
|
|
|
return account;
|
|
}
|
|
|
|
void Domain::addActivated(MTP::Environment environment, bool newWindow) {
|
|
const auto added = [&](not_null<Main::Account*> account) {
|
|
if (newWindow) {
|
|
Core::App().ensureSeparateWindowForAccount(account);
|
|
} else if (const auto window = Core::App().separateWindowForAccount(
|
|
account)) {
|
|
window->activate();
|
|
} else {
|
|
activate(account);
|
|
}
|
|
};
|
|
if (accounts().size() < maxAccounts()) {
|
|
added(add(environment));
|
|
} else {
|
|
for (auto &[index, account] : accounts()) {
|
|
if (!account->sessionExists()
|
|
&& account->mtp().environment() == environment) {
|
|
added(account.get());
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Domain::watchSession(not_null<Account*> account) {
|
|
account->sessionValue(
|
|
) | rpl::filter([=](Session *session) {
|
|
return session != nullptr;
|
|
}) | rpl::start_with_next([=](Session *session) {
|
|
session->data().unreadBadgeChanges(
|
|
) | rpl::start_with_next([=] {
|
|
scheduleUpdateUnreadBadge();
|
|
}, session->lifetime());
|
|
|
|
Data::AmPremiumValue(
|
|
session
|
|
) | rpl::start_with_next([=] {
|
|
_lastMaxAccounts = maxAccounts();
|
|
}, session->lifetime());
|
|
}, account->lifetime());
|
|
|
|
account->sessionChanges(
|
|
) | rpl::filter([=](Session *session) {
|
|
return !session;
|
|
}) | rpl::start_with_next([=] {
|
|
scheduleUpdateUnreadBadge();
|
|
closeAccountWindows(account);
|
|
crl::on_main(&Core::App(), [=] {
|
|
removeRedundantAccounts();
|
|
});
|
|
}, account->lifetime());
|
|
}
|
|
|
|
void Domain::closeAccountWindows(not_null<Main::Account*> account) {
|
|
auto another = (Main::Account*)nullptr;
|
|
for (auto i = _accounts.begin(); i != _accounts.end(); ++i) {
|
|
const auto other = i->account.get();
|
|
if (other == account) {
|
|
continue;
|
|
} else if (Core::App().separateWindowForAccount(other)) {
|
|
const auto that = Core::App().separateWindowForAccount(account);
|
|
if (that) {
|
|
that->close();
|
|
}
|
|
} else if (!another
|
|
|| (other->sessionExists() && !another->sessionExists())) {
|
|
another = other;
|
|
}
|
|
}
|
|
if (another) {
|
|
activate(another);
|
|
}
|
|
}
|
|
|
|
bool Domain::removePasscodeIfEmpty() {
|
|
if (_accounts.size() != 1 || _active.current()->sessionExists()) {
|
|
return false;
|
|
}
|
|
Local::reset();
|
|
|
|
// We completely logged out, remove the passcode if it was there.
|
|
if (Core::App().passcodeLocked()) {
|
|
Core::App().unlockPasscode();
|
|
}
|
|
if (!_local->hasLocalPasscode()) {
|
|
return false;
|
|
}
|
|
_local->setPasscode(QByteArray());
|
|
return true;
|
|
}
|
|
|
|
void Domain::removeRedundantAccounts() {
|
|
Expects(started());
|
|
|
|
const auto was = _accounts.size();
|
|
for (auto i = _accounts.begin(); i != _accounts.end();) {
|
|
if (Core::App().separateWindowForAccount(i->account.get())
|
|
|| i->account->sessionExists()) {
|
|
++i;
|
|
continue;
|
|
}
|
|
checkForLastProductionConfig(i->account.get());
|
|
i = _accounts.erase(i);
|
|
}
|
|
|
|
if (!removePasscodeIfEmpty() && _accounts.size() != was) {
|
|
scheduleWriteAccounts();
|
|
_accountsChanges.fire({});
|
|
}
|
|
}
|
|
|
|
void Domain::checkForLastProductionConfig(
|
|
not_null<Main::Account*> account) {
|
|
const auto mtp = &account->mtp();
|
|
if (mtp->environment() != MTP::Environment::Production) {
|
|
return;
|
|
}
|
|
for (const auto &[index, other] : _accounts) {
|
|
if (other.get() != account
|
|
&& other->mtp().environment() == MTP::Environment::Production) {
|
|
return;
|
|
}
|
|
}
|
|
Core::App().refreshFallbackProductionConfig(mtp->config());
|
|
}
|
|
|
|
void Domain::maybeActivate(not_null<Main::Account*> account) {
|
|
if (Core::App().separateWindowForAccount(account)) {
|
|
activate(account);
|
|
} else {
|
|
Core::App().preventOrInvoke(crl::guard(account, [=] {
|
|
activate(account);
|
|
}));
|
|
}
|
|
}
|
|
|
|
void Domain::activate(not_null<Main::Account*> account) {
|
|
if (const auto window = Core::App().separateWindowForAccount(account)) {
|
|
window->activate();
|
|
}
|
|
if (_active.current() == account.get()) {
|
|
return;
|
|
}
|
|
const auto i = ranges::find(_accounts, account.get(), [](
|
|
const AccountWithIndex &value) {
|
|
return value.account.get();
|
|
});
|
|
Assert(i != end(_accounts));
|
|
const auto changed = (_accountToActivate != i->index);
|
|
auto wasAuthed = false;
|
|
|
|
_activeLifetime.destroy();
|
|
if (_active.current()) {
|
|
_lastActiveIndex = _accountToActivate;
|
|
wasAuthed = _active.current()->sessionExists();
|
|
}
|
|
_accountToActivate = i->index;
|
|
_active = account.get();
|
|
_active.current()->sessionValue(
|
|
) | rpl::start_to_stream(_activeSessions, _activeLifetime);
|
|
|
|
if (changed) {
|
|
if (wasAuthed) {
|
|
scheduleWriteAccounts();
|
|
} else {
|
|
crl::on_main(&Core::App(), [=] {
|
|
removeRedundantAccounts();
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
void Domain::scheduleWriteAccounts() {
|
|
if (_writeAccountsScheduled) {
|
|
return;
|
|
}
|
|
_writeAccountsScheduled = true;
|
|
crl::on_main(&Core::App(), [=] {
|
|
_writeAccountsScheduled = false;
|
|
_local->writeAccounts();
|
|
});
|
|
}
|
|
|
|
int Domain::maxAccounts() const {
|
|
const auto premiumCount = ranges::count_if(accounts(), [](
|
|
const Main::Domain::AccountWithIndex &d) {
|
|
return d.account->sessionExists()
|
|
&& (d.account->session().premium()
|
|
|| d.account->session().isTestMode());
|
|
});
|
|
return std::min(int(premiumCount) + kMaxAccounts, kPremiumMaxAccounts);
|
|
}
|
|
|
|
rpl::producer<int> Domain::maxAccountsChanges() const {
|
|
return _lastMaxAccounts.changes();
|
|
}
|
|
|
|
} // namespace Main
|