Skip to content

Commit

Permalink
Added sequential background dictionary loader.
Browse files Browse the repository at this point in the history
 - Moved the Loader from the dictionaries manager to the spellchecker
 common space as a DictLoader.
  • Loading branch information
23rd committed Feb 24, 2020
1 parent 9daf362 commit bb8aead
Show file tree
Hide file tree
Showing 4 changed files with 239 additions and 56 deletions.
120 changes: 66 additions & 54 deletions Telegram/SourceFiles/boxes/dictionaries_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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()
Expand All @@ -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,
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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();
}
Expand Down Expand Up @@ -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;
}

Expand Down
141 changes: 140 additions & 1 deletion Telegram/SourceFiles/chat_helpers/spellchecker_common.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,19 @@ For license and copyright information please follow this link:

#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 {
Expand All @@ -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;
}

Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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() },
Expand All @@ -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(
Expand Down
Loading

0 comments on commit bb8aead

Please sign in to comment.