Added sequential background dictionary loader.

- Moved the Loader from the dictionaries manager to the spellchecker
 common space as a DictLoader.
This commit is contained in:
23rd 2020-02-21 21:55:11 +03:00
parent 9daf362df6
commit bb8aead078
4 changed files with 239 additions and 56 deletions

View File

@ -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<void()> destroyCallback);
void destroy() override;
void unpack(const QString &path) override;
private:
Fn<void()> _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<void()> 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<Ui::MultiSelect>(
parent,
@ -188,6 +145,9 @@ auto AddButtonWithLoader(
anim::type::instant);
}, button->lifetime());
using Loader = Spellchecker::DictLoader;
using GlobalLoaderPtr = std::shared_ptr<base::unique_qptr<Loader>>;
const auto localLoader = button->lifetime()
.make_state<base::unique_qptr<Loader>>();
const auto localLoaderValues = button->lifetime()
@ -200,11 +160,45 @@ auto AddButtonWithLoader(
setLocalLoader(nullptr);
};
const auto buttonState = button->lifetime()
.make_state<rpl::variable<DictState>>();
const auto dictionaryRemoved = button->lifetime()
.make_state<rpl::event_stream<>>();
const auto dictionaryFromGlobalLoader = button->lifetime()
.make_state<rpl::event_stream<>>();
const auto globalLoader = button->lifetime()
.make_state<GlobalLoaderPtr>();
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<Ui::FlatLabel>(
button,
@ -243,21 +237,29 @@ auto AddButtonWithLoader(
buttonEnabled
) | rpl::then(
rpl::merge(
dictionaryRemoved->events(),
buttonState->value(
) | rpl::filter([](const DictState &state) {
return state.is<Failed>();
}) | 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<Failed>();
}) | 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<Loading>()) {
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;
}

View File

@ -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 <QtGui/QGuiApplication>
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<base::unique_qptr<DictLoader>>;
DictLoaderPtr BackgroundLoader;
rpl::event_stream<int> BackgroundLoaderChanged;
void SetBackgroundLoader(DictLoaderPtr loader) {
BackgroundLoader = std::move(loader);
}
void DownloadDictionaryInBackground(
not_null<Main::Session*> session,
int counter,
std::vector<int> 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<base::unique_qptr<DictLoader>>();
*sharedLoader = base::make_unique_q<DictLoader>(
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<int> GlobalLoaderChanged() {
return BackgroundLoaderChanged.events();
}
DictLoader::DictLoader(
QObject *parent,
int id,
MTP::DedicatedLoader::Location location,
const QString &folder,
int size,
Fn<void()> 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<Dict> Dictionaries() {
return kDictionaries | ranges::to_vector;
}
@ -209,6 +319,24 @@ rpl::producer<QString> ButtonManageDictsState(
);
}
std::vector<int> DefaultLanguages() {
std::vector<int> 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<Main::Session*> session) {
Spellchecker::SetPhrases({ {
{ &ph::lng_spellchecker_add, tr::lng_spellchecker_add() },
@ -231,6 +359,17 @@ void Start(not_null<Main::Session*> session) {
? session->settings().dictionariesEnabled()
: std::vector<int>());
}, 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(

View File

@ -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<Main::Session*> session);
[[nodiscard]] rpl::producer<QString> ButtonManageDictsState(
not_null<Main::Session*> session);
std::vector<int> DefaultLanguages();
class DictLoader : public Storage::CloudBlob::BlobLoader {
public:
DictLoader(
QObject *parent,
int id,
MTP::DedicatedLoader::Location location,
const QString &folder,
int size,
Fn<void()> destroyCallback);
void destroy() override;
rpl::lifetime &lifetime() {
return _lifetime;
}
private:
void unpack(const QString &path) override;
void fail() override;
Fn<void()> _destroyCallback;
rpl::lifetime _lifetime;
};
std::shared_ptr<base::unique_qptr<DictLoader>> GlobalLoader();
rpl::producer<int> GlobalLoaderChanged();
} // namespace Spellchecker
#endif // !TDESKTOP_DISABLE_SPELLCHECK

View File

@ -91,7 +91,7 @@ public:
virtual void unpack(const QString &path) = 0;
protected:
void fail();
virtual void fail();
const QString _folder;