Allow several accounts in Core::App.

This commit is contained in:
John Preston 2020-06-15 20:25:02 +04:00
parent 815e26eea5
commit 6fc5e22882
36 changed files with 834 additions and 267 deletions

View file

@ -668,6 +668,8 @@ PRIVATE
lang/lang_values.h
main/main_account.cpp
main/main_account.h
main/main_accounts.cpp
main/main_accounts.h
main/main_app_config.cpp
main/main_app_config.h
main/main_session.cpp
@ -912,6 +914,8 @@ PRIVATE
storage/serialize_document.h
storage/storage_account.cpp
storage/storage_account.h
storage/storage_accounts.cpp
storage/storage_accounts.h
storage/storage_cloud_blob.cpp
storage/storage_cloud_blob.h
storage/storage_facade.cpp

View file

@ -14,8 +14,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "mainwindow.h"
#include "apiwrap.h"
#include "main/main_session.h"
#include "main/main_accounts.h"
#include "core/application.h"
#include "storage/storage_account.h"
#include "storage/storage_accounts.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/input_fields.h"
#include "ui/widgets/labels.h"
@ -451,7 +452,7 @@ void PasscodeBox::save(bool force) {
return;
}
if (_session->local().checkPasscode(old.toUtf8())) {
if (Core::App().accounts().local().checkPasscode(old.toUtf8())) {
cSetPasscodeBadTries(0);
if (_turningOff) pwd = conf = QString();
} else {
@ -519,7 +520,7 @@ void PasscodeBox::save(bool force) {
closeReplacedBy();
const auto weak = Ui::MakeWeak(this);
cSetPasscodeBadTries(0);
_session->local().setPasscode(pwd.toUtf8());
Core::App().accounts().local().setPasscode(pwd.toUtf8());
Core::App().localPasscodeChanged();
if (weak) {
closeBox();

View file

@ -315,7 +315,7 @@ Panel::Panel(not_null<Call*> call)
_cancel->setDuration(st::callPanelDuration);
setMouseTracking(true);
setWindowIcon(Window::CreateIcon(&_user->account()));
setWindowIcon(Window::CreateIcon(&_user->session()));
initControls();
initLayout();
showAndActivate();

View file

@ -13,7 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "core/application.h"
#include "base/platform/base_platform_info.h"
#include "ui/emoji_config.h"
#include "main/main_account.h"
#include "main/main_accounts.h"
#include "main/main_session.h"
#include "apiwrap.h"
@ -516,7 +516,7 @@ void EmojiKeywords::langPackRefreshed() {
}
void EmojiKeywords::handleSessionChanges() {
Core::App().activeAccount().sessionValue( // #TODO multi someSessionValue
Core::App().accounts().activeSessionValue( // #TODO multi someSessionValue
) | rpl::map([](Main::Session *session) {
return session ? &session->api() : nullptr;
}) | rpl::start_with_next([=](ApiWrap *api) {

View file

@ -21,13 +21,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "core/launcher.h"
#include "core/ui_integration.h"
#include "chat_helpers/emoji_keywords.h"
#include "storage/localstorage.h"
#include "base/platform/base_platform_info.h"
#include "platform/platform_specific.h"
#include "mainwindow.h"
#include "dialogs/dialogs_entry.h"
#include "history/history.h"
#include "main/main_session.h"
#include "apiwrap.h"
#include "api/api_updates.h"
#include "calls/calls_instance.h"
@ -35,10 +33,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lang/lang_translator.h"
#include "lang/lang_cloud_manager.h"
#include "lang/lang_hardcoded.h"
#include "storage/storage_databases.h"
#include "mainwidget.h"
#include "core/file_utilities.h"
#include "main/main_account.h"
#include "main/main_accounts.h"
#include "main/main_session.h"
#include "media/view/media_view_overlay_widget.h"
#include "mtproto/dc_options.h"
#include "mtproto/mtp_instance.h"
@ -56,7 +55,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/emoji_config.h"
#include "ui/effects/animations.h"
#include "storage/serialize_common.h"
#include "storage/storage_account.h"
#include "storage/storage_accounts.h"
#include "storage/storage_databases.h"
#include "storage/localstorage.h"
#include "window/window_session_controller.h"
#include "window/window_controller.h"
#include "base/qthelp_regex.h"
@ -95,7 +96,7 @@ Application::Application(not_null<Launcher*> launcher)
, _databases(std::make_unique<Storage::Databases>())
, _animationsManager(std::make_unique<Ui::Animations::Manager>())
, _dcOptions(std::make_unique<MTP::DcOptions>())
, _account(std::make_unique<Main::Account>(cDataFile()))
, _accounts(std::make_unique<Main::Accounts>(cDataFile()))
, _langpack(std::make_unique<Lang::Instance>())
, _langCloudManager(std::make_unique<Lang::CloudManager>(langpack()))
, _emojiKeywords(std::make_unique<ChatHelpers::EmojiKeywords>())
@ -113,22 +114,22 @@ Application::Application(not_null<Launcher*> launcher)
_shouldLockAt = 0;
}, _lifetime);
activeAccount().sessionChanges( // #TODO multi activeSessionValue
accounts().activeSessionChanges(
) | rpl::start_with_next([=](Main::Session *session) {
if (_mediaView) {
hideMediaView();
_mediaView->clearData();
}
if (session && !UpdaterDisabled()) { // #TODO multi someSessionValue
UpdateChecker().setMtproto(session);
}
}, _lifetime);
activeAccount().mtpChanges(
accounts().activeValue(
) | rpl::map([=](Main::Account *account) {
return account ? account->mtpValue() : rpl::never<MTP::Instance*>();
}) | rpl::flatten_latest(
) | rpl::filter([=](MTP::Instance *instance) {
return instance != nullptr;
}) | rpl::start_with_next([=] {
if (_window) {
// Global::DesktopNotify is used in updateTrayMenu.
// This should be called when user settings are read.
// Right now after they are read the startMtp() is called.
_window->widget()->updateTrayMenu();
@ -137,7 +138,11 @@ Application::Application(not_null<Launcher*> launcher)
}
Application::~Application() {
// Depend on activeWindow() for now :(
Shortcuts::Finish();
_window.reset();
if (_mediaView) {
_mediaView->clearData();
_mediaView = nullptr;
@ -224,7 +229,7 @@ void Application::run() {
// Create mime database, so it won't be slow later.
QMimeDatabase().mimeTypeForName(qsl("text/plain"));
_window = std::make_unique<Window::Controller>(&activeAccount());
_window = std::make_unique<Window::Controller>();
QCoreApplication::instance()->installEventFilter(this);
connect(
@ -235,24 +240,19 @@ void Application::run() {
DEBUG_LOG(("Application Info: window created..."));
// Depend on activeWindow() for now :(
startShortcuts();
App::initMedia();
const auto state = activeAccount().local().start(QByteArray());
const auto state = accounts().start(QByteArray());
if (state == Storage::StartResult::IncorrectPasscode) {
Global::SetLocalPasscode(true);
Global::RefLocalPasscodeChanged().notify();
lockByPasscode();
DEBUG_LOG(("Application Info: passcode needed..."));
} else {
DEBUG_LOG(("Application Info: local map read..."));
activeAccount().startMtp();
DEBUG_LOG(("Application Info: MTP started..."));
if (activeAccount().sessionExists()) {
_window->setupMain();
} else {
_window->setupIntro();
}
_window->showAccount(&activeAccount());
}
_window->widget()->show();
@ -552,6 +552,10 @@ void Application::writeInstallBetaVersionsSetting() {
_launcher->writeInstallBetaVersionsSetting();
}
Main::Account &Application::activeAccount() const {
return _accounts->active();
}
bool Application::exportPreventsQuit() {
if (!activeAccount().sessionExists()
|| !activeAccount().session().data().exportInProgress()) {
@ -564,25 +568,28 @@ bool Application::exportPreventsQuit() {
}
int Application::unreadBadge() const {
return activeAccount().sessionExists()
return (accounts().started() && activeAccount().sessionExists())
? activeAccount().session().data().unreadBadge()
: 0;
}
bool Application::unreadBadgeMuted() const {
return activeAccount().sessionExists()
return (accounts().started() && activeAccount().sessionExists())
? activeAccount().session().data().unreadBadgeMuted()
: false;
}
bool Application::offerLegacyLangPackSwitch() const {
// #TODO multi we offer only if we were upgraded from an old authed app.
return activeAccount().sessionExists();
return (accounts().list().size() == 1) && activeAccount().sessionExists();
}
bool Application::canApplyLangPackWithoutRestart() const {
// #TODO multi we can't if at least one account is authorized.
return !activeAccount().sessionExists();
for (const auto &[index, account] : accounts().list()) {
if (account->sessionExists()) {
return false;
}
}
return true;
}
void Application::setInternalLinkDomain(const QString &domain) const {
@ -674,10 +681,6 @@ void Application::lockByPasscode() {
void Application::unlockPasscode() {
clearPasscodeLock();
if (!activeAccount().mtp()) {
// We unlocked initial passcode, so we just start mtproto.
activeAccount().startMtp();
}
if (_window) {
_window->clearPasscodeLock();
}
@ -720,10 +723,20 @@ void Application::lockByTerms(const Window::TermsLock &data) {
}
}
bool Application::someSessionExists() const {
const auto &list = _accounts->list();
for (const auto &[index, account] : list) {
if (account->sessionExists()) {
return true;
}
}
return false;
}
void Application::checkAutoLock() {
if (!Global::LocalPasscode()
|| passcodeLocked()
|| !_account->sessionExists()) {
|| !someSessionExists()) {
_shouldLockAt = 0;
_autoLockTimer.cancel();
return;
@ -949,6 +962,13 @@ void Application::quitDelayed() {
void Application::startShortcuts() {
Shortcuts::Start();
_accounts->activeSessionChanges(
) | rpl::start_with_next([=](Main::Session *session) {
const auto support = session && session->supportMode();
Shortcuts::ToggleSupportShortcuts(support);
Platform::SetApplicationIcon(Window::CreateIcon(session));
}, _lifetime);
Shortcuts::Requests(
) | rpl::start_with_next([=](not_null<Shortcuts::Request*> request) {
using Command = Shortcuts::Command;

View file

@ -36,6 +36,7 @@ void quit();
} // namespace App
namespace Main {
class Accounts;
class Account;
class Session;
} // namespace Main
@ -150,10 +151,12 @@ public:
return *_databases;
}
// Account component.
[[nodiscard]] Main::Account &activeAccount() const {
return *_account;
// Accounts component.
[[nodiscard]] Main::Accounts &accounts() const {
return *_accounts;
}
[[nodiscard]] Main::Account &activeAccount() const;
[[nodiscard]] bool someSessionExists() const;
[[nodiscard]] bool exportPreventsQuit();
// Main::Session component.
@ -278,7 +281,7 @@ private:
const std::unique_ptr<Storage::Databases> _databases;
const std::unique_ptr<Ui::Animations::Manager> _animationsManager;
const std::unique_ptr<MTP::DcOptions> _dcOptions;
const std::unique_ptr<Main::Account> _account;
const std::unique_ptr<Main::Accounts> _accounts;
std::unique_ptr<Window::Controller> _window;
std::unique_ptr<Media::View::OverlayWidget> _mediaView;
const std::unique_ptr<Lang::Instance> _langpack;

View file

@ -7,9 +7,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "core/changelogs.h"
#include "storage/storage_account.h"
#include "lang/lang_keys.h"
#include "core/application.h"
#include "main/main_accounts.h"
#include "main/main_session.h"
#include "storage/storage_accounts.h"
#include "data/data_session.h"
#include "mainwindow.h"
#include "apiwrap.h"
@ -80,7 +82,9 @@ Changelogs::Changelogs(not_null<Main::Session*> session, int oldVersion)
std::unique_ptr<Changelogs> Changelogs::Create(
not_null<Main::Session*> session) {
const auto oldVersion = session->local().oldMapVersion();
auto &local = Core::App().accounts().local();
const auto oldVersion = local.oldVersion();
local.clearOldVersion();
return (oldVersion > 0 && oldVersion < AppVersion)
? std::make_unique<Changelogs>(session, oldVersion)
: nullptr;

View file

@ -248,7 +248,6 @@ void CodeWidget::codeSubmitDone(const MTPauth_Authorization &result) {
showError(rpl::single(Lang::Hard::ServerError()));
return;
}
cSetLoggedPhoneNumber(getData()->phone);
finish(data.vuser());
}, [&](const MTPDauth_authorizationSignUpRequired &data) {
if (const auto terms = data.vterms_of_service()) {

View file

@ -373,8 +373,6 @@ void QrWidget::done(const MTPauth_Authorization &authorization) {
showError(rpl::single(Lang::Hard::ServerError()));
return;
}
const auto phone = data.vuser().c_user().vphone().value_or_empty();
cSetLoggedPhoneNumber(phone);
finish(data.vuser());
}, [&](const MTPDauth_authorizationSignUpRequired &data) {
_requestId = 0;

View file

@ -13,8 +13,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "mtproto/mtp_instance.h"
#include "storage/localstorage.h"
#include "core/application.h"
#include "main/main_session.h"
#include "main/main_account.h"
#include "main/main_accounts.h"
#include "boxes/confirm_box.h"
#include "ui/wrap/padding_wrap.h"
#include "ui/widgets/labels.h"
@ -158,7 +158,10 @@ Language ParseLanguage(const MTPLangPackLanguage &data) {
CloudManager::CloudManager(Instance &langpack)
: _langpack(langpack) {
Core::App().activeAccount().mtpValue( // #TODO multi activeAccountValue
Core::App().accounts().activeValue(
) | rpl::map([=](Main::Account *account) {
return account ? account->mtpValue() : rpl::never<MTP::Instance*>();
}) | rpl::flatten_latest(
) | rpl::start_with_next([=](MTP::Instance *instance) {
if (instance) {
_api.emplace(instance);

View file

@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "core/launcher.h"
#include "core/shortcuts.h"
#include "storage/storage_account.h"
#include "storage/storage_accounts.h" // Storage::StartResult.
#include "storage/serialize_common.h"
#include "storage/localstorage.h"
#include "data/data_session.h"
@ -26,16 +27,49 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "facades.h"
namespace Main {
namespace {
Account::Account(const QString &dataName)
: _local(std::make_unique<Storage::Account>(this, dataName))
, _appConfig(std::make_unique<AppConfig>(this)) {
watchProxyChanges();
watchSessionChanges();
[[nodiscard]] QString ComposeDataString(const QString &dataName, int index) {
auto result = dataName;
result.replace('#', QString());
if (index > 0) {
result += '#' + QString::number(index + 1);
}
return result;
}
} // namespace
Account::Account(const QString &dataName, int index)
: _local(std::make_unique<Storage::Account>(
this,
ComposeDataString(dataName, index))) {
}
Account::~Account() = default;
[[nodiscard]] Storage::StartResult Account::legacyStart(
const QByteArray &passcode) {
Expects(!_appConfig);
const auto result = _local->legacyStart(passcode);
if (result == Storage::StartResult::Success) {
finishStarting();
}
return result;
}
void Account::start(std::shared_ptr<MTP::AuthKey> localKey) {
_local->start(std::move(localKey));
finishStarting();
}
void Account::finishStarting() {
_appConfig = std::make_unique<AppConfig>(this);
watchProxyChanges();
watchSessionChanges();
}
void Account::watchProxyChanges() {
using ProxyChange = Core::Application::ProxyChange;
@ -60,28 +94,17 @@ void Account::watchProxyChanges() {
void Account::watchSessionChanges() {
sessionChanges(
) | rpl::start_with_next([=] {
crl::on_main(this, [=] {
const auto phone = sessionExists()
? session().user()->phone()
: QString();
const auto support = sessionExists() && session().supportMode();
if (cLoggedPhoneNumber() != phone) {
cSetLoggedPhoneNumber(phone);
if (_mtp) {
_mtp->setUserPhone(phone);
}
Local::writeSettings();
}
if (_mtp) {
_mtp->requestConfig();
}
Shortcuts::ToggleSupportShortcuts(support);
Platform::SetApplicationIcon(Window::CreateIcon(this));
});
) | rpl::start_with_next([=](Session *session) {
if (!session && _mtp) {
_mtp->setUserPhone(QString());
}
}, _lifetime);
}
UserId Account::willHaveUserId() const {
return _sessionUserId;
}
void Account::createSession(const MTPUser &user) {
createSession(user, QByteArray(), 0, Settings());
}
@ -330,7 +353,6 @@ void Account::startMtp() {
Core::App().dcOptions(),
MTP::Instance::Mode::Normal,
std::move(config));
_mtp->setUserPhone(cLoggedPhoneNumber());
_mtp->writeKeysRequests(
) | rpl::start_with_next([=] {
local().writeMtpData();

View file

@ -13,8 +13,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Storage {
class Account;
enum class StartResult : uchar;
} // namespace Storage
namespace MTP {
class AuthKey;
} // namespace MTP
namespace Main {
class Session;
@ -23,12 +28,17 @@ class AppConfig;
class Account final : public base::has_weak_ptr {
public:
explicit Account(const QString &dataName);
Account(const QString &dataName, int index);
~Account();
Account(const Account &other) = delete;
Account &operator=(const Account &other) = delete;
[[nodiscard]] Storage::StartResult legacyStart(
const QByteArray &passcode);
void start(std::shared_ptr<MTP::AuthKey> localKey);
[[nodiscard]] UserId willHaveUserId() const;
void createSession(const MTPUser &user);
void createSession(
UserId id,
@ -41,8 +51,11 @@ public:
void forcedLogOut();
[[nodiscard]] AppConfig &appConfig() const {
Expects(_appConfig != nullptr);
return *_appConfig;
}
[[nodiscard]] Storage::Account &local() const {
return *_local;
}
@ -87,6 +100,7 @@ private:
QByteArray serialized,
int streamVersion,
Settings &&settings);
void finishStarting();
void watchProxyChanges();
void watchSessionChanges();
bool checkForUpdates(const mtpPrime *from, const mtpPrime *end);

View file

@ -0,0 +1,112 @@
/*
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_accounts.h"
#include "core/application.h"
#include "core/shortcuts.h"
#include "main/main_account.h"
#include "main/main_session.h"
#include "storage/storage_accounts.h"
#include "storage/localstorage.h"
namespace Main {
Accounts::Accounts(const QString &dataName)
: _dataName(dataName)
, _local(std::make_unique<Storage::Accounts>(this, dataName)) {
}
Accounts::~Accounts() = default;
bool Accounts::started() const {
return !_accounts.empty();
}
Storage::StartResult Accounts::start(const QByteArray &passcode) {
Expects(!started());
auto active = -1;
const auto callback = [&](int index, std::unique_ptr<Account> account) {
Expects(account != nullptr);
Expects(!_accounts.contains(index));
if (_accounts.empty()) {
active = index;
}
_accounts.emplace(index, std::move(account));
};
const auto result = _local->start(passcode, callback);
if (result == Storage::StartResult::Success) {
Assert(started());
activate(active);
for (const auto &[index, account] : _accounts) {
account->startMtp();
}
if (Local::oldSettingsVersion() < AppVersion) {
Local::writeSettings();
}
} else {
Assert(!started());
}
return result;
}
const base::flat_map<int, std::unique_ptr<Account>> &Accounts::list() const {
return _accounts;
}
rpl::producer<Account*> Accounts::activeValue() const {
return _active.value();
}
Account &Accounts::active() const {
Expects(!_accounts.empty());
Ensures(_active.current() != nullptr);
return *_active.current();
}
rpl::producer<not_null<Account*>> Accounts::activeChanges() const {
return _active.changes() | rpl::map([](Account *value) {
return not_null{ value };
});
}
rpl::producer<Session*> Accounts::activeSessionChanges() const {
return _activeSessions.events();
}
rpl::producer<Session*> Accounts::activeSessionValue() const {
const auto current = (_accounts.empty() || !active().sessionExists())
? nullptr
: &active().session();
return rpl::single(current) | rpl::then(_activeSessions.events());
}
int Accounts::add() {
auto index = 0;
while (_accounts.contains(index)) {
++index;
}
_accounts.emplace(index, std::make_unique<Account>(_dataName, index));
return index;
}
void Accounts::activate(int index) {
Expects(_accounts.contains(index));
_activeLifetime.destroy();
_activeIndex = index;
_active = _accounts.find(index)->second.get();
_active.current()->sessionValue(
) | rpl::start_to_stream(_activeSessions, _activeLifetime);
}
} // namespace Main

View file

@ -0,0 +1,61 @@
/*
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
namespace Storage {
class Accounts;
enum class StartResult : uchar;
} // namespace Storage
namespace Main {
class Account;
class Session;
class Accounts final {
public:
explicit Accounts(const QString &dataName);
~Accounts();
[[nodiscard]] bool started() const;
Storage::StartResult start(const QByteArray &passcode);
[[nodiscard]] Storage::Accounts &local() const {
return *_local;
}
[[nodiscard]] auto list() const
-> const base::flat_map<int, std::unique_ptr<Account>> &;
[[nodiscard]] rpl::producer<Account*> activeValue() const;
// Expects(started());
[[nodiscard]] Account &active() const;
[[nodiscard]] rpl::producer<not_null<Account*>> activeChanges() const;
[[nodiscard]] rpl::producer<Session*> activeSessionValue() const;
[[nodiscard]] rpl::producer<Session*> activeSessionChanges() const;
[[nodiscard]] int add();
void activate(int index);
private:
const QString _dataName;
const std::unique_ptr<Storage::Accounts> _local;
base::flat_map<int, std::unique_ptr<Account>> _accounts;
rpl::variable<Account*> _active = nullptr;
int _activeIndex = 0;
rpl::event_stream<Session*> _activeSessions;
rpl::lifetime _activeLifetime;
rpl::lifetime _lifetime;
};
} // namespace Main

View file

@ -29,7 +29,6 @@ AppConfig::AppConfig(not_null<Account*> account) : _account(account) {
_requestId = 0;
}
}, _lifetime);
refresh();
}
void AppConfig::refresh() {

View file

@ -67,6 +67,7 @@ Session::Session(
) | rpl::start_with_next([=] {
notifications().updateAll();
}, _lifetime);
subscribe(Global::RefConnectionTypeChanged(), [=] {
_api->refreshTopPromotion();
});
@ -74,6 +75,8 @@ Session::Session(
_api->requestTermsUpdate();
_api->requestFullPeer(_user);
_api->instance()->setUserPhone(_user->phone());
crl::on_main(this, [=] {
using Flag = Data::PeerUpdate::Flag;
changes().peerUpdates(
@ -83,8 +86,16 @@ Session::Session(
| Flag::Photo
| Flag::About
| Flag::PhoneNumber
) | rpl::start_with_next([=] {
) | rpl::start_with_next([=](const Data::PeerUpdate &update) {
local().writeSelf();
if (update.flags & Flag::PhoneNumber) {
const auto phone = _user->phone();
_api->instance()->setUserPhone(phone);
if (!phone.isEmpty()) {
_api->instance()->requestConfig();
}
}
}, _lifetime);
if (_settings.hadLegacyCallsPeerToPeerNobody()) {

View file

@ -94,13 +94,6 @@ MainWindow::MainWindow(not_null<Window::Controller*> controller)
setLocale(QLocale(QLocale::English, QLocale::UnitedStates));
account().sessionValue(
) | rpl::start_with_next([=](Main::Session *session) {
updateGlobalMenu();
if (!session) {
_mediaPreview.destroy();
}
}, lifetime());
subscribe(Window::Theme::Background(), [this](const Window::Theme::BackgroundUpdate &data) {
themeUpdated(data);
});
@ -202,6 +195,7 @@ void MainWindow::finishFirstShow() {
void MainWindow::clearWidgetsHook() {
destroyLayer();
_mediaPreview.destroy();
_main.destroy();
_passcodeLock.destroy();
_intro.destroy();
@ -241,20 +235,19 @@ void MainWindow::setupPasscodeLock() {
}
void MainWindow::clearPasscodeLock() {
if (!_passcodeLock) return;
if (!_passcodeLock) {
return;
}
auto bg = grabInner();
_passcodeLock.destroy();
if (_intro) {
auto bg = grabInner();
_passcodeLock.destroy();
_intro->showAnimated(bg, true);
} else if (_main) {
auto bg = grabInner();
_passcodeLock.destroy();
_main->showAnimated(bg, true);
Core::App().checkStartUrl();
} else if (account().sessionExists()) {
setupMain();
} else {
setupIntro();
}
}
@ -984,8 +977,10 @@ QImage MainWindow::iconWithCounter(int size, int count, style::color bg, style::
}
QImage img(smallIcon ? ((size == 16) ? iconbig16 : (size == 32 ? iconbig32 : iconbig64)) : ((size == 16) ? icon16 : (size == 32 ? icon32 : icon64)));
if (account().sessionExists() && account().session().supportMode()) {
Window::ConvertIconToBlack(img);
if (const auto controller = sessionController()) {
if (controller->session().supportMode()) {
Window::ConvertIconToBlack(img);
}
}
if (!count) return img;

View file

@ -23,7 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "window/window_session_controller.h"
#include "core/shortcuts.h"
#include "core/application.h"
#include "main/main_account.h" // Account::sessionValue.
#include "main/main_accounts.h" // Accounts::activeSessionValue.
#include "mainwindow.h"
#include "main/main_session.h"
#include "facades.h"
@ -114,7 +114,7 @@ Instance::Instance()
});
// While we have one Media::Player::Instance for all sessions we have to do this.
Core::App().activeAccount().sessionValue( // #TODO multi activeSessionValue
Core::App().accounts().activeSessionValue(
) | rpl::start_with_next([=](Main::Session *session) {
if (session) {
subscribe(session->calls().currentCallChanged(), [=](Calls::Call *call) {

View file

@ -46,9 +46,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "window/window_peer_menu.h"
#include "window/window_session_controller.h"
#include "window/window_controller.h"
#include "main/main_account.h" // Account::sessionValue.
#include "base/platform/base_platform_info.h"
#include "base/unixtime.h"
#include "main/main_account.h"
#include "main/main_accounts.h" // Accounts::activeSessionValue.
#include "main/main_session.h"
#include "layout.h"
#include "storage/file_download.h"
@ -303,8 +304,6 @@ OverlayWidget::OverlayWidget()
? Global::VideoVolume()
: Global::kDefaultVolume;
// #TODO multi activeSessionValue change icon on show?
setWindowIcon(Window::CreateIcon(&Core::App().activeAccount()));
setWindowTitle(qsl("Media viewer"));
const auto text = tr::lng_mediaview_saved_to(
@ -320,9 +319,15 @@ OverlayWidget::OverlayWidget()
connect(QApplication::desktop(), SIGNAL(resized(int)), this, SLOT(onScreenResized(int)));
// While we have one mediaview for all sessions we have to do this.
Core::App().activeAccount().sessionValue( // #TODO multi activeSessionValue
Core::App().accounts().activeSessionValue(
) | rpl::start_with_next([=](Main::Session *session) {
if (!isHidden()) {
close();
}
clearData();
setWindowIcon(Window::CreateIcon(session));
if (session) {
// #TODO multi
subscribe(session->downloaderTaskFinished(), [=] {
if (!isHidden()) {
updateControls();

View file

@ -51,7 +51,6 @@ bool gNoStartUpdate = false;
bool gStartToSettings = false;
uint32 gConnectionsInSession = 1;
QString gLoggedPhoneNumber;
QByteArray gLocalSalt;
int gScreenScale = style::kScaleAuto;

View file

@ -36,7 +36,6 @@ DeclareSetting(uint64, RealAlphaVersion);
DeclareSetting(QByteArray, AlphaPrivateKey);
DeclareSetting(bool, TestMode);
DeclareSetting(QString, LoggedPhoneNumber);
DeclareSetting(bool, AutoStart);
DeclareSetting(bool, StartMinimized);
DeclareSetting(bool, StartInTray);

View file

@ -21,6 +21,8 @@ namespace {
constexpr char TdfMagic[] = { 'T', 'D', 'F', '$' };
constexpr auto TdfMagicLen = int(sizeof(TdfMagic));
constexpr auto kStrongIterationsCount = 100'000;
} // namespace
QString ToFilePart(FileKey val) {
@ -86,6 +88,28 @@ bool CheckStreamStatus(QDataStream &stream) {
MTP::AuthKeyPtr CreateLocalKey(
const QByteArray &passcode,
const QByteArray &salt) {
const auto s = bytes::make_span(salt);
const auto hash = openssl::Sha512(s, bytes::make_span(passcode), s);
const auto iterationsCount = passcode.isEmpty()
? 1 // Don't slow down for no password.
: kStrongIterationsCount;
auto key = MTP::AuthKey::Data{ { gsl::byte{} } };
PKCS5_PBKDF2_HMAC(
reinterpret_cast<const char*>(hash.data()),
hash.size(),
reinterpret_cast<const unsigned char*>(s.data()),
s.size(),
iterationsCount,
EVP_sha512(),
key.size(),
reinterpret_cast<unsigned char*>(key.data()));
return std::make_shared<MTP::AuthKey>(key);
}
MTP::AuthKeyPtr CreateLegacyLocalKey(
const QByteArray &passcode,
const QByteArray &salt) {
auto key = MTP::AuthKey::Data{ { gsl::byte{} } };
const auto iterationsCount = passcode.isEmpty()
? LocalEncryptNoPwdIterCount // Don't slow down for no password.

View file

@ -23,6 +23,9 @@ void ClearKey(const FileKey &key, const QString &basePath);
[[nodiscard]] MTP::AuthKeyPtr CreateLocalKey(
const QByteArray &passcode,
const QByteArray &salt);
[[nodiscard]] MTP::AuthKeyPtr CreateLegacyLocalKey(
const QByteArray &passcode,
const QByteArray &salt);
struct FileReadDescriptor final {
~FileReadDescriptor();

View file

@ -681,12 +681,10 @@ bool ReadSetting(
cSetWindowPos(position);
} break;
case dbiLoggedPhoneNumber: {
case dbiLoggedPhoneNumber: { // deprecated
QString v;
stream >> v;
if (!CheckStreamStatus(stream)) return false;
cSetLoggedPhoneNumber(v);
} break;
case dbiMutePeer: { // deprecated
@ -737,9 +735,9 @@ bool ReadSetting(
? false
: (v == 1);
if (Window::Theme::IsNightMode()) {
Window::Theme::Background()->setTileNightValue(tile);
context.tileNight = tile;
} else {
Window::Theme::Background()->setTileDayValue(tile);
context.tileDay = tile;
}
} break;
@ -748,8 +746,8 @@ bool ReadSetting(
stream >> tileDay >> tileNight;
if (!CheckStreamStatus(stream)) return false;
Window::Theme::Background()->setTileDayValue(tileDay == 1);
Window::Theme::Background()->setTileNightValue(tileNight == 1);
context.tileDay = (tileDay == 1);
context.tileNight = (tileNight == 1);
} break;
case dbiAdaptiveForWide: {

View file

@ -51,6 +51,9 @@ struct ReadSettingsContext {
std::vector<std::shared_ptr<MTP::AuthKey>> mtpLegacyKeys;
qint32 mtpLegacyMainDcId = 0;
qint32 mtpLegacyUserId = 0;
bool tileDay = false;
bool tileNight = true;
};
[[nodiscard]] bool ReadSetting(

View file

@ -88,14 +88,6 @@ enum class WriteMapWhen {
Soon,
};
std::unique_ptr<Main::Settings> StoredSessionSettings;
Main::Settings &GetStoredSessionSettings() {
if (!StoredSessionSettings) {
StoredSessionSettings = std::make_unique<Main::Settings>();
}
return *StoredSessionSettings;
}
void applyReadContext(ReadSettingsContext &&context) {
Core::App().dcOptions()->addFromOther(std::move(context.dcOptions));
@ -169,7 +161,7 @@ void _readOldUserSettingsFields(
continue;
}
OldKey = CreateLocalKey(QByteArray(), salt);
OldKey = CreateLegacyLocalKey(QByteArray(), salt);
if (data.size() <= 16 || (data.size() & 0x0F)) {
LOG(("App Error: bad encrypted part size in old user config: %1").arg(data.size()));
@ -344,7 +336,7 @@ void start() {
LOG(("App Error: bad salt in settings file, size: %1").arg(salt.size()));
return writeSettings();
}
SettingsKey = CreateLocalKey(QByteArray(), salt);
SettingsKey = CreateLegacyLocalKey(QByteArray(), salt);
EncryptedDescriptor settings;
if (!DecryptLocal(settings, settingsEncrypted, SettingsKey)) {
@ -388,7 +380,7 @@ void writeSettings() {
if (_settingsSalt.isEmpty() || !SettingsKey) {
_settingsSalt.resize(LocalEncryptSaltSize);
memset_rand(_settingsSalt.data(), _settingsSalt.size());
SettingsKey = CreateLocalKey(QByteArray(), _settingsSalt);
SettingsKey = CreateLegacyLocalKey(QByteArray(), _settingsSalt);
}
settings.writeData(_settingsSalt);
@ -398,7 +390,6 @@ void writeSettings() {
quint32 size = 12 * (sizeof(quint32) + sizeof(qint32));
size += sizeof(quint32) + Serialize::bytearraySize(dcOptionsSerialized);
size += sizeof(quint32) + Serialize::bytearraySize(applicationSettings);
size += sizeof(quint32) + Serialize::stringSize(cLoggedPhoneNumber());
size += sizeof(quint32) + Serialize::stringSize(Global::TxtDomainString());
size += sizeof(quint32) + Serialize::stringSize(cDialogLastPath());
@ -438,7 +429,6 @@ void writeSettings() {
data.stream << quint32(dbiScalePercent) << qint32(cConfigScale());
data.stream << quint32(dbiDcOptions) << dcOptionsSerialized;
data.stream << quint32(dbiApplicationSettings) << applicationSettings;
data.stream << quint32(dbiLoggedPhoneNumber) << cLoggedPhoneNumber();
data.stream << quint32(dbiTxtDomainString) << Global::TxtDomainString();
data.stream << quint32(dbiDialogLastPath) << cDialogLastPath();
data.stream << quint32(dbiAnimationsDisabled) << qint32(anim::Disabled() ? 1 : 0);
@ -542,7 +532,6 @@ void reset() {
Window::Theme::Background()->reset();
_oldSettingsVersion = 0;
StoredSessionSettings.reset();
}
int32 oldSettingsVersion() {

View file

@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "storage/storage_account.h"
#include "storage/localstorage.h"
#include "storage/storage_accounts.h"
#include "storage/storage_encryption.h"
#include "storage/storage_clear_legacy.h"
#include "storage/cache/storage_cache_types.h"
@ -35,7 +36,6 @@ namespace {
using namespace details;
using Database = Cache::Database;
constexpr auto kLocalKeySize = MTP::AuthKey::kSize;
constexpr auto kDelayedWriteTimeout = crl::time(1000);
constexpr auto kSavedBackgroundFormat = QImage::Format_ARGB32_Premultiplied;
@ -120,14 +120,28 @@ Account::~Account() {
}
}
StartResult Account::start(const QByteArray &passcode) {
const auto result = readMap(passcode);
StartResult Account::legacyStart(const QByteArray &passcode) {
const auto result = readMapWith(MTP::AuthKeyPtr(), passcode);
if (result == ReadMapResult::Failed) {
_mapChanged = true;
writeMap();
Assert(_localKey == nullptr);
//_mapChanged = true;
//writeMap();
} else if (result == ReadMapResult::IncorrectPasscode) {
return StartResult::IncorrectPasscode;
}
clearLegacyFiles();
return StartResult::Success;
}
void Account::start(MTP::AuthKeyPtr localKey) {
Expects(localKey != nullptr);
_localKey = std::move(localKey);
readMapWith(_localKey);
clearLegacyFiles();
}
void Account::clearLegacyFiles() {
const auto weak = base::make_weak(_owner.get());
ClearLegacyFiles(_basePath, [weak, this](
FnMut<void(base::flat_set<QString>&&)> then) {
@ -135,12 +149,6 @@ StartResult Account::start(const QByteArray &passcode) {
then(collectGoodNames());
});
});
if (Local::oldSettingsVersion() < AppVersion) {
Local::writeSettings();
}
return StartResult::Success;
}
base::flat_set<QString> Account::collectGoodNames() const {
@ -184,7 +192,9 @@ base::flat_set<QString> Account::collectGoodNames() const {
return result;
}
Account::ReadMapResult Account::readMap(const QByteArray &passcode) {
Account::ReadMapResult Account::readMapWith(
MTP::AuthKeyPtr localKey,
const QByteArray &legacyPasscode) {
auto ms = crl::now();
FileReadDescriptor mapData;
@ -193,34 +203,33 @@ Account::ReadMapResult Account::readMap(const QByteArray &passcode) {
}
LOG(("App Info: reading map..."));
QByteArray salt, keyEncrypted, mapEncrypted;
mapData.stream >> salt >> keyEncrypted >> mapEncrypted;
QByteArray legacySalt, legacyKeyEncrypted, mapEncrypted;
mapData.stream >> legacySalt >> legacyKeyEncrypted >> mapEncrypted;
if (!CheckStreamStatus(mapData.stream)) {
return ReadMapResult::Failed;
}
if (!localKey) {
if (legacySalt.size() != LocalEncryptSaltSize) {
LOG(("App Error: bad salt in map file, size: %1").arg(legacySalt.size()));
return ReadMapResult::Failed;
}
auto legacyPasscodeKey = CreateLegacyLocalKey(legacyPasscode, legacySalt);
if (salt.size() != LocalEncryptSaltSize) {
LOG(("App Error: bad salt in map file, size: %1").arg(salt.size()));
return ReadMapResult::Failed;
EncryptedDescriptor keyData;
if (!DecryptLocal(keyData, legacyKeyEncrypted, legacyPasscodeKey)) {
LOG(("App Info: could not decrypt pass-protected key from map file, maybe bad password..."));
return ReadMapResult::IncorrectPasscode;
}
auto key = Serialize::read<MTP::AuthKey::Data>(keyData.stream);
if (keyData.stream.status() != QDataStream::Ok || !keyData.stream.atEnd()) {
LOG(("App Error: could not read pass-protected key from map file"));
return ReadMapResult::Failed;
}
localKey = std::make_shared<MTP::AuthKey>(key);
}
_passcodeKey = CreateLocalKey(passcode, salt);
EncryptedDescriptor keyData, map;
if (!DecryptLocal(keyData, keyEncrypted, _passcodeKey)) {
LOG(("App Info: could not decrypt pass-protected key from map file, maybe bad password..."));
return ReadMapResult::IncorrectPasscode;
}
auto key = Serialize::read<MTP::AuthKey::Data>(keyData.stream);
if (keyData.stream.status() != QDataStream::Ok || !keyData.stream.atEnd()) {
LOG(("App Error: could not read pass-protected key from map file"));
return ReadMapResult::Failed;
}
_localKey = std::make_shared<MTP::AuthKey>(key);
_passcodeKeyEncrypted = keyEncrypted;
_passcodeKeySalt = salt;
if (!DecryptLocal(map, mapEncrypted, _localKey)) {
EncryptedDescriptor map;
if (!DecryptLocal(map, mapEncrypted, localKey)) {
LOG(("App Error: could not decrypt map."));
return ReadMapResult::Failed;
}
@ -336,6 +345,8 @@ Account::ReadMapResult Account::readMap(const QByteArray &passcode) {
}
}
_localKey = std::move(localKey);
_draftsMap = draftsMap;
_draftCursorsMap = draftCursorsMap;
_draftsNotReadMap = draftsNotReadMap;
@ -355,6 +366,7 @@ Account::ReadMapResult Account::readMap(const QByteArray &passcode) {
_recentHashtagsAndBotsKey = recentHashtagsAndBotsKey;
_exportSettingsKey = exportSettingsKey;
_oldMapVersion = mapData.version;
if (_oldMapVersion < AppVersion) {
writeMapDelayed();
} else {
@ -391,6 +403,8 @@ void Account::writeMapQueued() {
}
void Account::writeMap() {
Expects(_localKey != nullptr);
_writeMapTimer.cancel();
if (!_mapChanged) {
return;
@ -402,23 +416,8 @@ void Account::writeMap() {
}
FileWriteDescriptor map(u"map"_q, _basePath);
if (_passcodeKeySalt.isEmpty() || _passcodeKeyEncrypted.isEmpty()) {
auto pass = QByteArray(kLocalKeySize, Qt::Uninitialized);
auto salt = QByteArray(LocalEncryptSaltSize, Qt::Uninitialized);
memset_rand(pass.data(), pass.size());
memset_rand(salt.data(), salt.size());
_localKey = CreateLocalKey(pass, salt);
_passcodeKeySalt.resize(LocalEncryptSaltSize);
memset_rand(_passcodeKeySalt.data(), _passcodeKeySalt.size());
_passcodeKey = CreateLocalKey(QByteArray(), _passcodeKeySalt);
EncryptedDescriptor passKeyData(kLocalKeySize);
_localKey->write(passKeyData.stream);
_passcodeKeyEncrypted = PrepareEncrypted(passKeyData, _passcodeKey);
}
map.writeData(_passcodeKeySalt);
map.writeData(_passcodeKeyEncrypted);
map.writeData(QByteArray());
map.writeData(QByteArray());
uint32 mapSize = 0;
const auto self = [&] {
@ -514,34 +513,8 @@ void Account::writeMap() {
_mapChanged = false;
}
bool Account::checkPasscode(const QByteArray &passcode) const {
Expects(!_passcodeKeySalt.isEmpty());
Expects(_passcodeKey != nullptr);
const auto checkKey = CreateLocalKey(passcode, _passcodeKeySalt);
return checkKey->equals(_passcodeKey);
}
void Account::setPasscode(const QByteArray &passcode) {
Expects(!_passcodeKeySalt.isEmpty());
Expects(_localKey != nullptr);
_passcodeKey = CreateLocalKey(passcode, _passcodeKeySalt);
EncryptedDescriptor passKeyData(kLocalKeySize);
_localKey->write(passKeyData.stream);
_passcodeKeyEncrypted = PrepareEncrypted(passKeyData, _passcodeKey);
_mapChanged = true;
writeMap();
Global::SetLocalPasscode(!passcode.isEmpty());
Global::RefLocalPasscodeChanged().notify();
}
void Account::reset() {
auto names = collectGoodNames();
_passcodeKeySalt.clear();
_draftsMap.clear();
_draftCursorsMap.clear();
_draftsNotReadMap.clear();
@ -921,6 +894,10 @@ std::unique_ptr<Main::Settings> Account::applyReadContext(
}
}
// #TODO multi
//Window::Theme::Background()->setTileDayValue(context.tileDay);
//Window::Theme::Background()->setTileNightValue(context.tileNight);
return std::move(context.sessionSettingsStorage);
}

View file

@ -41,10 +41,7 @@ class EncryptionKey;
using FileKey = quint64;
enum class StartResult : uchar {
Success,
IncorrectPasscode,
};
enum class StartResult : uchar;
struct MessageDraft {
MsgId msgId = 0;
@ -57,13 +54,15 @@ public:
Account(not_null<Main::Account*> owner, const QString &dataName);
~Account();
[[nodiscard]] StartResult start(const QByteArray &passcode);
[[nodiscard]] void start(MTP::AuthKeyPtr localKey);
[[nodiscard]] StartResult legacyStart(const QByteArray &passcode);
[[nodiscard]] int oldMapVersion() const {
return _oldMapVersion;
}
[[nodiscard]] bool checkPasscode(const QByteArray &passcode) const;
void setPasscode(const QByteArray &passcode);
MTP::AuthKeyPtr peekLegacyLocalKey() const {
return _localKey;
}
void writeSettings();
void writeMtpData();
@ -156,7 +155,10 @@ private:
[[nodiscard]] auto prepareReadSettingsContext() const
-> details::ReadSettingsContext;
[[nodiscard]] ReadMapResult readMap(const QByteArray &passcode);
ReadMapResult readMapWith(
MTP::AuthKeyPtr localKey,
const QByteArray &legacyPasscode = QByteArray());
void clearLegacyFiles();
void writeMapDelayed();
void writeMapQueued();
void writeMap();
@ -168,18 +170,6 @@ private:
std::unique_ptr<Main::Settings> readSettings();
void writeSettings(Main::Settings *stored);
bool readOldUserSettings(
bool remove,
details::ReadSettingsContext &context);
void readOldUserSettingsFields(
QIODevice *device,
qint32 &version,
details::ReadSettingsContext &context);
void readOldMtpDataFields(
QIODevice *device,
qint32 &version,
details::ReadSettingsContext &context);
bool readOldMtpData(bool remove, details::ReadSettingsContext &context);
void readMtpData();
std::unique_ptr<Main::Settings> applyReadContext(
@ -222,9 +212,6 @@ private:
const QString _databasePath;
MTP::AuthKeyPtr _localKey;
MTP::AuthKeyPtr _passcodeKey;
QByteArray _passcodeKeySalt;
QByteArray _passcodeKeyEncrypted;
base::flat_map<PeerId, FileKey> _draftsMap;
base::flat_map<PeerId, FileKey> _draftCursorsMap;

View file

@ -0,0 +1,243 @@
/*
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 "storage/storage_accounts.h"
#include "storage/details/storage_file_utilities.h"
#include "storage/serialize_common.h"
#include "main/main_accounts.h"
#include "main/main_account.h"
#include "facades.h"
namespace Storage {
namespace {
using namespace details;
constexpr auto kMaxAccounts = 3;
[[nodiscard]] QString BaseGlobalPath() {
return cWorkingDir() + qsl("tdata/");
}
[[nodiscard]] QString ComputeKeyName(const QString &dataName) {
return "key_" + dataName + (cTestMode() ? "[test]" : "");
}
[[nodiscard]] QString ComputeInfoName(const QString &dataName) {
return "info_" + dataName + (cTestMode() ? "[test]" : "");
}
} // namespace
Accounts::Accounts(not_null<Main::Accounts*> owner, const QString &dataName)
: _owner(owner)
, _dataName(dataName) {
}
Accounts::~Accounts() = default;
StartResult Accounts::start(
const QByteArray &passcode,
Fn<void(int, std::unique_ptr<Main::Account>)> callback) {
const auto modern = startModern(passcode, callback);
if (modern == StartModernResult::Success) {
if (_oldVersion < AppVersion) {
writeAccounts();
}
return StartResult::Success;
} else if (modern == StartModernResult::IncorrectPasscode) {
return StartResult::IncorrectPasscode;
} else if (modern == StartModernResult::Failed) {
startWithSingleAccount(
passcode,
std::move(callback),
std::make_unique<Main::Account>(_dataName, 0));
return StartResult::Success;
}
auto legacy = std::make_unique<Main::Account>(_dataName, 0);
const auto result = legacy->legacyStart(passcode);
if (result == StartResult::Success) {
_oldVersion = legacy->local().oldMapVersion();
startWithSingleAccount(
passcode,
std::move(callback),
std::move(legacy));
}
return result;
}
void Accounts::startWithSingleAccount(
const QByteArray &passcode,
Fn<void(int, std::unique_ptr<Main::Account>)> callback,
std::unique_ptr<Main::Account> account) {
Expects(account != nullptr);
if (auto localKey = account->local().peekLegacyLocalKey()) {
_localKey = std::move(localKey);
encryptLocalKey(passcode);
} else {
generateLocalKey();
account->start(_localKey);
}
callback(0, std::move(account));
writeAccounts();
}
void Accounts::generateLocalKey() {
Expects(_localKey == nullptr);
Expects(_passcodeKeySalt.isEmpty());
Expects(_passcodeKeyEncrypted.isEmpty());
auto pass = QByteArray(MTP::AuthKey::kSize, Qt::Uninitialized);
auto salt = QByteArray(LocalEncryptSaltSize, Qt::Uninitialized);
memset_rand(pass.data(), pass.size());
memset_rand(salt.data(), salt.size());
_localKey = CreateLocalKey(pass, salt);
encryptLocalKey(QByteArray());
}
void Accounts::encryptLocalKey(const QByteArray &passcode) {
_passcodeKeySalt.resize(LocalEncryptSaltSize);
memset_rand(_passcodeKeySalt.data(), _passcodeKeySalt.size());
_passcodeKey = CreateLocalKey(passcode, _passcodeKeySalt);
EncryptedDescriptor passKeyData(MTP::AuthKey::kSize);
_localKey->write(passKeyData.stream);
_passcodeKeyEncrypted = PrepareEncrypted(passKeyData, _passcodeKey);
}
Accounts::StartModernResult Accounts::startModern(
const QByteArray &passcode,
Fn<void(int, std::unique_ptr<Main::Account>)> callback) {
const auto name = ComputeKeyName(_dataName);
FileReadDescriptor keyData;
if (!ReadFile(keyData, name, BaseGlobalPath())) {
return StartModernResult::Empty;
}
LOG(("App Info: reading accounts info..."));
QByteArray salt, keyEncrypted, infoEncrypted;
keyData.stream >> salt >> keyEncrypted >> infoEncrypted;
if (!CheckStreamStatus(keyData.stream)) {
return StartModernResult::Failed;
}
if (salt.size() != LocalEncryptSaltSize) {
LOG(("App Error: bad salt in info file, size: %1").arg(salt.size()));
return StartModernResult::Failed;
}
_passcodeKey = CreateLocalKey(passcode, salt);
EncryptedDescriptor keyInnerData, info;
if (!DecryptLocal(keyInnerData, keyEncrypted, _passcodeKey)) {
LOG(("App Info: could not decrypt pass-protected key from info file, "
"maybe bad password..."));
return StartModernResult::IncorrectPasscode;
}
auto key = Serialize::read<MTP::AuthKey::Data>(keyInnerData.stream);
if (keyInnerData.stream.status() != QDataStream::Ok
|| !keyInnerData.stream.atEnd()) {
LOG(("App Error: could not read pass-protected key from info file"));
return StartModernResult::Failed;
}
_localKey = std::make_shared<MTP::AuthKey>(key);
_passcodeKeyEncrypted = keyEncrypted;
_passcodeKeySalt = salt;
if (!DecryptLocal(info, infoEncrypted, _localKey)) {
LOG(("App Error: could not decrypt info."));
return StartModernResult::Failed;
}
LOG(("App Info: reading encrypted info..."));
auto count = qint32();
info.stream >> count;
if (count <= 0 || count > kMaxAccounts) {
LOG(("App Error: bad accounts count: %1").arg(count));
return StartModernResult::Failed;
}
_oldVersion = keyData.version;
auto tried = base::flat_set<int>();
auto users = base::flat_set<UserId>();
for (auto i = 0; i != count; ++i) {
auto index = qint32();
info.stream >> index;
if (index >= 0
&& index < kMaxAccounts
&& tried.emplace(index).second) {
auto account = std::make_unique<Main::Account>(_dataName, index);
account->start(_localKey);
const auto userId = account->willHaveUserId();
if (!users.contains(userId)
&& (userId != 0 || (users.empty() && i + 1 == count))) {
callback(index, std::move(account));
users.emplace(userId);
}
}
}
Ensures(!users.empty());
return StartModernResult::Success;
}
void Accounts::writeAccounts() {
Expects(!_owner->list().empty());
const auto path = BaseGlobalPath();
if (!QDir().exists(path)) {
QDir().mkpath(path);
}
FileWriteDescriptor key(ComputeKeyName(_dataName), path);
key.writeData(_passcodeKeySalt);
key.writeData(_passcodeKeyEncrypted);
const auto &list = _owner->list();
auto keySize = sizeof(qint32) + sizeof(qint32) * list.size();
EncryptedDescriptor keyData(keySize);
keyData.stream << qint32(list.size());
for (const auto &[index, account] : list) {
keyData.stream << qint32(index);
}
key.writeEncrypted(keyData, _localKey);
}
bool Accounts::checkPasscode(const QByteArray &passcode) const {
Expects(!_passcodeKeySalt.isEmpty());
Expects(_passcodeKey != nullptr);
const auto checkKey = CreateLocalKey(passcode, _passcodeKeySalt);
return checkKey->equals(_passcodeKey);
}
void Accounts::setPasscode(const QByteArray &passcode) {
Expects(!_passcodeKeySalt.isEmpty());
Expects(_localKey != nullptr);
encryptLocalKey(passcode);
writeAccounts();
Global::SetLocalPasscode(!passcode.isEmpty());
Global::RefLocalPasscodeChanged().notify();
}
int Accounts::oldVersion() const {
return _oldVersion;
}
void Accounts::clearOldVersion() {
_oldVersion = 0;
}
} // namespace Storage

View file

@ -0,0 +1,73 @@
/*
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
namespace MTP {
class AuthKey;
using AuthKeyPtr = std::shared_ptr<AuthKey>;
} // namespace MTP
namespace Main {
class Account;
class Accounts;
} // namespace Main
namespace Storage {
enum class StartResult : uchar {
Success,
IncorrectPasscode,
};
class Accounts final {
public:
Accounts(not_null<Main::Accounts*> owner, const QString &dataName);
~Accounts();
[[nodiscard]] StartResult start(
const QByteArray &passcode,
Fn<void(int, std::unique_ptr<Main::Account>)> callback);
void writeAccounts();
[[nodiscard]] bool checkPasscode(const QByteArray &passcode) const;
void setPasscode(const QByteArray &passcode);
[[nodiscard]] int oldVersion() const;
void clearOldVersion();
private:
enum class StartModernResult {
Success,
IncorrectPasscode,
Failed,
Empty,
};
[[nodiscard]] StartModernResult startModern(
const QByteArray &passcode,
Fn<void(int, std::unique_ptr<Main::Account>)> callback);
void startWithSingleAccount(
const QByteArray &passcode,
Fn<void(int, std::unique_ptr<Main::Account>)> callback,
std::unique_ptr<Main::Account> account);
void generateLocalKey();
void encryptLocalKey(const QByteArray &passcode);
void writeInfo();
const not_null<Main::Accounts*> _owner;
const QString _dataName;
MTP::AuthKeyPtr _localKey;
MTP::AuthKeyPtr _passcodeKey;
QByteArray _passcodeKeySalt;
QByteArray _passcodeKeyEncrypted;
int _oldVersion = 0;
};
} // namespace Storage

View file

@ -105,18 +105,16 @@ void ConvertIconToBlack(QImage &image) {
}
}
QIcon CreateOfficialIcon(Main::Account *account) {
QIcon CreateOfficialIcon(Main::Session *session) {
auto image = Core::IsAppLaunched() ? Core::App().logo() : LoadLogo();
if (account
&& account->sessionExists()
&& account->session().supportMode()) {
if (session && session->supportMode()) {
ConvertIconToBlack(image);
}
return QIcon(App::pixmapFromImageInPlace(std::move(image)));
}
QIcon CreateIcon(Main::Account *account) {
auto result = CreateOfficialIcon(account);
QIcon CreateIcon(Main::Session *session) {
auto result = CreateOfficialIcon(session);
#if defined Q_OS_UNIX && !defined Q_OS_MAC
return QIcon::fromTheme(Platform::GetIconName(), result);
#endif
@ -295,10 +293,12 @@ bool MainWindow::computeIsActive() const {
}
void MainWindow::updateWindowIcon() {
const auto supportIcon = account().sessionExists()
&& account().session().supportMode();
const auto session = sessionController()
? &sessionController()->session()
: nullptr;
const auto supportIcon = session && session->supportMode();
if (supportIcon != _usingSupportIcon || _icon.isNull()) {
_icon = CreateIcon(&account());
_icon = CreateIcon(session);
_usingSupportIcon = supportIcon;
}
setWindowIcon(_icon);
@ -528,9 +528,7 @@ void MainWindow::updateControlsGeometry() {
void MainWindow::updateUnreadCounter() {
if (!Global::started() || App::quitting()) return;
const auto counter = account().sessionExists()
? account().session().data().unreadBadge()
: 0;
const auto counter = Core::App().unreadBadge();
_titleText = (counter > 0) ? qsl("Telegram (%1)").arg(counter) : qsl("Telegram");
unreadCounterChangedHook();

View file

@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <QtWidgets/QSystemTrayIcon>
namespace Main {
class Session;
class Account;
} // namespace Main
@ -31,7 +32,7 @@ struct TermsLock;
QImage LoadLogo();
QImage LoadLogoNoMargin();
QIcon CreateIcon(Main::Account *account = nullptr);
QIcon CreateIcon(Main::Session *session = nullptr);
void ConvertIconToBlack(QImage &image);
class MainWindow : public Ui::RpWidget, protected base::Subscriber {
@ -40,11 +41,11 @@ class MainWindow : public Ui::RpWidget, protected base::Subscriber {
public:
explicit MainWindow(not_null<Controller*> controller);
Window::Controller &controller() const {
[[nodiscard]] Window::Controller &controller() const {
return *_controller;
}
Main::Account &account() const;
Window::SessionController *sessionController() const;
[[nodiscard]] Main::Account &account() const;
[[nodiscard]] Window::SessionController *sessionController() const;
bool hideNoQuit();

View file

@ -22,7 +22,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/unixtime.h"
#include "base/crc32hash.h"
#include "data/data_session.h"
#include "main/main_account.h" // Account::sessionValue.
#include "main/main_account.h" // Account::local.
#include "main/main_accounts.h" // Accounts::activeSessionValue.
#include "ui/image/image.h"
#include "boxes/background_box.h"
#include "core/application.h"
@ -547,7 +548,7 @@ void ChatBackground::start() {
set(Data::ThemeWallPaper());
}
Core::App().activeAccount().sessionValue( // #TODO multi activeSessionValue
Core::App().accounts().activeSessionValue(
) | rpl::filter([=](Main::Session *session) {
return session != _session;
}) | rpl::start_with_next([=](Main::Session *session) {

View file

@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "core/application.h"
#include "main/main_account.h"
#include "main/main_accounts.h"
#include "main/main_session.h"
#include "ui/layers/box_content.h"
#include "ui/layers/layer_widget.h"
@ -27,11 +28,22 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Window {
Controller::Controller(not_null<Main::Account*> account)
: _account(account)
, _widget(this) {
Controller::Controller()
: _widget(this) {
_widget.init();
}
Controller::~Controller() {
// We want to delete all widgets before the _sessionController.
_widget.clearWidgets();
}
void Controller::showAccount(not_null<Main::Account*> account) {
_accountLifetime.destroy();
_account = account;
_account->sessionValue(
) | rpl::start_with_next([=](Main::Session *session) {
) | rpl::start_with_next([=](Main::Session *session) {
_sessionController = session
? std::make_unique<SessionController>(session, this)
: nullptr;
@ -47,14 +59,13 @@ Controller::Controller(not_null<Main::Account*> account)
sideBarChanged();
}
_widget.updateWindowIcon();
_widget.updateGlobalMenu();
if (session) {
setupMain();
} else {
setupIntro();
}
}, _lifetime);
_widget.init();
}
Controller::~Controller() {
// We want to delete all widgets before the _sessionController.
_widget.clearWidgets();
}
void Controller::finishFirstShow() {
@ -75,7 +86,11 @@ void Controller::setupPasscodeLock() {
}
void Controller::clearPasscodeLock() {
_widget.clearPasscodeLock();
if (!_account) {
showAccount(&Core::App().activeAccount());
} else {
_widget.clearPasscodeLock();
}
}
void Controller::setupIntro() {
@ -83,12 +98,12 @@ void Controller::setupIntro() {
}
void Controller::setupMain() {
Expects(_account->sessionExists());
Expects(_sessionController != nullptr);
_widget.setupMain();
if (const auto id = Ui::Emoji::NeedToSwitchBackToId()) {
Ui::Emoji::LoadAndSwitchTo(&_account->session(), id);
Ui::Emoji::LoadAndSwitchTo(&_sessionController->session(), id);
}
}

View file

@ -18,18 +18,22 @@ namespace Window {
class Controller final {
public:
explicit Controller(not_null<Main::Account*> account);
Controller();
~Controller();
Controller(const Controller &other) = delete;
Controller &operator=(const Controller &other) = delete;
Main::Account &account() const {
return *_account;
}
void showAccount(not_null<Main::Account*> account);
not_null<::MainWindow*> widget() {
return &_widget;
}
Main::Account &account() const {
Expects(_account != nullptr);
return *_account;
}
SessionController *sessionController() const {
return _sessionController.get();
}
@ -74,10 +78,11 @@ private:
anim::type animated);
void checkThemeEditor();
not_null<Main::Account*> _account;
Main::Account *_account = nullptr;
::MainWindow _widget;
std::unique_ptr<SessionController> _sessionController;
rpl::lifetime _accountLifetime;
rpl::lifetime _lifetime;
};

View file

@ -8,7 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "window/window_lock_widgets.h"
#include "lang/lang_keys.h"
#include "storage/storage_account.h"
#include "storage/storage_accounts.h"
#include "mainwindow.h"
#include "core/application.h"
#include "api/api_text_entities.h"
@ -22,7 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "window/window_controller.h"
#include "window/window_slide_animation.h"
#include "window/window_session_controller.h"
#include "main/main_account.h"
#include "main/main_accounts.h"
#include "facades.h"
#include "styles/style_layers.h"
#include "styles/style_boxes.h"
@ -147,11 +147,12 @@ void PasscodeLockWidget::submit() {
}
const auto passcode = _passcode->text().toUtf8();
auto &account = window()->account();
auto &local = window()->account().local();
const auto correct = account.sessionExists()
? local.checkPasscode(passcode)
: (local.start(passcode) != Storage::StartResult::IncorrectPasscode);
const auto controller = window()->sessionController();
auto &accounts = Core::App().accounts();
const auto correct = controller
? accounts.local().checkPasscode(passcode)
: (accounts.start(passcode)
!= Storage::StartResult::IncorrectPasscode);
if (!correct) {
cSetPasscodeBadTries(cPasscodeBadTries() + 1);
cSetPasscodeLastTry(crl::now());