From bb8aead078f23b994dfdf702600d103f7f4cfd92 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 21 Feb 2020 21:55:11 +0300 Subject: [PATCH] Added sequential background dictionary loader. - Moved the Loader from the dictionaries manager to the spellchecker common space as a DictLoader. --- .../boxes/dictionaries_manager.cpp | 120 ++++++++------- .../chat_helpers/spellchecker_common.cpp | 141 +++++++++++++++++- .../chat_helpers/spellchecker_common.h | 32 ++++ .../SourceFiles/storage/storage_cloud_blob.h | 2 +- 4 files changed, 239 insertions(+), 56 deletions(-) diff --git a/Telegram/SourceFiles/boxes/dictionaries_manager.cpp b/Telegram/SourceFiles/boxes/dictionaries_manager.cpp index e5e99cbd3..1d8940251 100644 --- a/Telegram/SourceFiles/boxes/dictionaries_manager.cpp +++ b/Telegram/SourceFiles/boxes/dictionaries_manager.cpp @@ -44,24 +44,6 @@ constexpr auto kMaxQueryLength = 15; using QStringView = QString; #endif -class Loader : public BlobLoader { -public: - Loader( - QObject *parent, - int id, - MTP::DedicatedLoader::Location location, - const QString &folder, - int size, - Fn destroyCallback); - - void destroy() override; - void unpack(const QString &path) override; - -private: - Fn _destroyCallback; - -}; - class Inner : public Ui::RpWidget { public: Inner(QWidget *parent, Dictionaries enabledDictionaries); @@ -101,31 +83,6 @@ QString StateDescription(const DictState &state) { tr::lng_settings_manage_enabled_dictionary); } -Loader::Loader( - QObject *parent, - int id, - MTP::DedicatedLoader::Location location, - const QString &folder, - int size, - Fn destroyCallback) -: BlobLoader(parent, id, location, folder, size) -, _destroyCallback(std::move(destroyCallback)) { -} - -void Loader::unpack(const QString &path) { - Expects(_destroyCallback); - crl::async([=] { - const auto success = Spellchecker::UnpackDictionary(path, id()); - if (success) { - QFile(path).remove(); - } - crl::on_main(success ? _destroyCallback : [=] { fail(); }); - }); -} - -void Loader::destroy() { -} - auto CreateMultiSelect(QWidget *parent) { const auto result = Ui::CreateChild( parent, @@ -188,6 +145,9 @@ auto AddButtonWithLoader( anim::type::instant); }, button->lifetime()); + using Loader = Spellchecker::DictLoader; + using GlobalLoaderPtr = std::shared_ptr>; + const auto localLoader = button->lifetime() .make_state>(); const auto localLoaderValues = button->lifetime() @@ -200,11 +160,45 @@ auto AddButtonWithLoader( setLocalLoader(nullptr); }; - const auto buttonState = button->lifetime() .make_state>(); const auto dictionaryRemoved = button->lifetime() .make_state>(); + const auto dictionaryFromGlobalLoader = button->lifetime() + .make_state>(); + + const auto globalLoader = button->lifetime() + .make_state(); + + const auto rawGlobalLoaderPtr = [=]() -> Loader* { + if (!globalLoader || !*globalLoader || !*globalLoader->get()) { + return nullptr; + } + return globalLoader->get()->get(); + }; + + const auto setGlobalLoaderPtr = [=](GlobalLoaderPtr loader) { + if (localLoader->get()) { + if (loader && loader->get()) { + loader->get()->destroy(); + } + return; + } + *globalLoader = std::move(loader); + localLoaderValues->fire(rawGlobalLoaderPtr()); + if (rawGlobalLoaderPtr()) { + dictionaryFromGlobalLoader->fire({}); + } + }; + + Spellchecker::GlobalLoaderChanged( + ) | rpl::start_with_next([=](int langId) { + if (!langId && rawGlobalLoaderPtr()) { + setGlobalLoaderPtr(nullptr); + } else if (langId == id) { + setGlobalLoaderPtr(Spellchecker::GlobalLoader()); + } + }, button->lifetime()); const auto label = Ui::CreateChild( button, @@ -243,21 +237,29 @@ auto AddButtonWithLoader( buttonEnabled ) | rpl::then( rpl::merge( - dictionaryRemoved->events(), - buttonState->value( - ) | rpl::filter([](const DictState &state) { - return state.is(); - }) | rpl::map([] { - return rpl::empty_value(); + // Events to toggle on. + dictionaryFromGlobalLoader->events( + ) | rpl::map([] { + return true; + }), + // Events to toggle off. + rpl::merge( + dictionaryRemoved->events(), + buttonState->value( + ) | rpl::filter([](const DictState &state) { + return state.is(); + }) | rpl::map([] { + return rpl::empty_value(); + }) + ) | rpl::map([] { + return false; }) - ) | rpl::map([]() { - return false; - }) + ) ) ); *buttonState = localLoaderValues->events_starting_with( - localLoader->get() + rawGlobalLoaderPtr() ? rawGlobalLoaderPtr() : localLoader->get() ) | rpl::map([=](Loader *loader) { return (loader && loader->id() == id) ? loader->state() @@ -292,6 +294,10 @@ auto AddButtonWithLoader( Spellchecker::GetDownloadSize(id), crl::guard(weak, destroyLocalLoader))); } else if (!toggled && state.is()) { + if (const auto g = rawGlobalLoaderPtr()) { + g->destroy(); + return; + } if (localLoader && localLoader->get()->id() == id) { destroyLocalLoader(); } @@ -321,6 +327,12 @@ auto AddButtonWithLoader( return base::EventFilterResult::Continue; }); + if (const auto g = Spellchecker::GlobalLoader()) { + if (g.get() && g->get()->id() == id) { + setGlobalLoaderPtr(g); + } + } + return button; } diff --git a/Telegram/SourceFiles/chat_helpers/spellchecker_common.cpp b/Telegram/SourceFiles/chat_helpers/spellchecker_common.cpp index 05fd9837a..cf580ae53 100644 --- a/Telegram/SourceFiles/chat_helpers/spellchecker_common.cpp +++ b/Telegram/SourceFiles/chat_helpers/spellchecker_common.cpp @@ -9,13 +9,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #ifndef TDESKTOP_DISABLE_SPELLCHECK +#include "base/platform/base_platform_info.h" +#include "base/zlib_help.h" +#include "data/data_session.h" +#include "lang/lang_instance.h" #include "lang/lang_keys.h" #include "main/main_session.h" -#include "base/zlib_help.h" +#include "mainwidget.h" #include "spellcheck/platform/platform_spellcheck.h" #include "spellcheck/spellcheck_utils.h" #include "spellcheck/spellcheck_value.h" +#include + namespace Spellchecker { namespace { @@ -24,12 +30,20 @@ using namespace Storage::CloudBlob; constexpr auto kDictExtensions = { "dic", "aff" }; +// 31 - QLocale::English, 91 - QLocale::Portuguese. +constexpr auto kLangsForLWC = { 31, 91 }; +// 225 - QLocale::UnitesStates, 30 - QLocale::Brazil. +constexpr auto kDefaultCountries = { 225, 30 }; + // Language With Country. inline auto LWC(QLocale::Country country) { const auto l = QLocale::matchingLocales( QLocale::AnyLanguage, QLocale::AnyScript, country)[0]; + if (ranges::contains(kDefaultCountries, country)) { + return int(l.language()); + } return (l.language() * 1000) + country; } @@ -90,8 +104,104 @@ bool IsGoodPartName(const QString &name) { }) != end(kDictExtensions); } +using DictLoaderPtr = std::shared_ptr>; + +DictLoaderPtr BackgroundLoader; +rpl::event_stream BackgroundLoaderChanged; + +void SetBackgroundLoader(DictLoaderPtr loader) { + BackgroundLoader = std::move(loader); +} + +void DownloadDictionaryInBackground( + not_null session, + int counter, + std::vector langs) { + const auto id = langs[counter]; + counter++; + const auto destroyer = [=] { + // This is a temporary workaround. + const auto copyId = id; + const auto copyLangs = langs; + const auto copySession = session; + const auto copyCounter = counter; + BackgroundLoader = nullptr; + BackgroundLoaderChanged.fire(0); + + if (DictionaryExists(copyId)) { + auto dicts = copySession->settings().dictionariesEnabled(); + if (!ranges::contains(dicts, copyId)) { + dicts.push_back(copyId); + copySession->settings().setDictionariesEnabled(std::move(dicts)); + copySession->saveSettingsDelayed(); + } + } + + if (copyCounter >= copyLangs.size()) { + return; + } + DownloadDictionaryInBackground(copySession, copyCounter, copyLangs); + }; + if (DictionaryExists(id)) { + destroyer(); + return; + } + + auto sharedLoader = std::make_shared>(); + *sharedLoader = base::make_unique_q( + App::main(), + id, + GetDownloadLocation(id), + DictPathByLangId(id), + GetDownloadSize(id), + crl::guard(session, destroyer)); + SetBackgroundLoader(std::move(sharedLoader)); + BackgroundLoaderChanged.fire_copy(id); +} + } // namespace +DictLoaderPtr GlobalLoader() { + return BackgroundLoader; +} + +rpl::producer GlobalLoaderChanged() { + return BackgroundLoaderChanged.events(); +} + +DictLoader::DictLoader( + QObject *parent, + int id, + MTP::DedicatedLoader::Location location, + const QString &folder, + int size, + Fn destroyCallback) +: BlobLoader(parent, id, location, folder, size) +, _destroyCallback(std::move(destroyCallback)) { +} + +void DictLoader::unpack(const QString &path) { + Expects(_destroyCallback); + crl::async([=] { + const auto success = Spellchecker::UnpackDictionary(path, id()); + if (success) { + QFile(path).remove(); + } + crl::on_main(success ? _destroyCallback : [=] { fail(); }); + }); +} + +void DictLoader::destroy() { + Expects(_destroyCallback); + + _destroyCallback(); +} + +void DictLoader::fail() { + BlobLoader::fail(); + destroy(); +} + std::vector Dictionaries() { return kDictionaries | ranges::to_vector; } @@ -209,6 +319,24 @@ rpl::producer ButtonManageDictsState( ); } +std::vector DefaultLanguages() { + std::vector langs; + + const auto method = QGuiApplication::inputMethod(); + langs.reserve(method ? 3 : 2); + if (method) { + const auto loc = method->locale(); + const auto locLang = int(loc.language()); + langs.push_back(ranges::contains(kLangsForLWC, locLang) + ? LWC(loc.country()) + : locLang); + } + langs.push_back(QLocale(Platform::SystemLanguage()).language()); + langs.push_back(QLocale(Lang::Current().id()).language()); + + return langs; +} + void Start(not_null session) { Spellchecker::SetPhrases({ { { &ph::lng_spellchecker_add, tr::lng_spellchecker_add() }, @@ -231,6 +359,17 @@ void Start(not_null session) { ? session->settings().dictionariesEnabled() : std::vector()); }, session->lifetime()); + + session->data().contactsLoaded().changes( + ) | rpl::start_with_next([=](bool loaded) { + if (!loaded) { + return; + } + + DownloadDictionaryInBackground(session, 0, DefaultLanguages()); + }, session->lifetime()); + + } if (session->settings().spellcheckerEnabled()) { Platform::Spellchecker::UpdateLanguages( diff --git a/Telegram/SourceFiles/chat_helpers/spellchecker_common.h b/Telegram/SourceFiles/chat_helpers/spellchecker_common.h index e7c560923..203c4e047 100644 --- a/Telegram/SourceFiles/chat_helpers/spellchecker_common.h +++ b/Telegram/SourceFiles/chat_helpers/spellchecker_common.h @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #ifndef TDESKTOP_DISABLE_SPELLCHECK #include "storage/storage_cloud_blob.h" +#include "base/unique_qptr.h" namespace Main { class Session; @@ -37,6 +38,37 @@ void Start(not_null session); [[nodiscard]] rpl::producer ButtonManageDictsState( not_null session); +std::vector DefaultLanguages(); + +class DictLoader : public Storage::CloudBlob::BlobLoader { +public: + DictLoader( + QObject *parent, + int id, + MTP::DedicatedLoader::Location location, + const QString &folder, + int size, + Fn destroyCallback); + + void destroy() override; + + rpl::lifetime &lifetime() { + return _lifetime; + } + +private: + void unpack(const QString &path) override; + void fail() override; + + Fn _destroyCallback; + + rpl::lifetime _lifetime; + +}; + +std::shared_ptr> GlobalLoader(); +rpl::producer GlobalLoaderChanged(); + } // namespace Spellchecker #endif // !TDESKTOP_DISABLE_SPELLCHECK diff --git a/Telegram/SourceFiles/storage/storage_cloud_blob.h b/Telegram/SourceFiles/storage/storage_cloud_blob.h index 4a1b485bc..af6301ca0 100644 --- a/Telegram/SourceFiles/storage/storage_cloud_blob.h +++ b/Telegram/SourceFiles/storage/storage_cloud_blob.h @@ -91,7 +91,7 @@ public: virtual void unpack(const QString &path) = 0; protected: - void fail(); + virtual void fail(); const QString _folder;