In-memory thread-safe cache for language pack strings.

GitOrigin-RevId: 71dc10a925f1000590501c4ddfc307806e242e51
This commit is contained in:
levlam 2018-07-05 21:28:11 +03:00
parent f3aaff1e1a
commit 08db51cdce
4 changed files with 222 additions and 33 deletions

View File

@ -1661,7 +1661,7 @@ languagePackStringValue key:string value:string = LanguagePackString;
//@few_value Value for few objects @many_value Value for many objects @other_value Default value //@few_value Value for few objects @many_value Value for many objects @other_value Default value
languagePackStringPluralized key:string zero_value:string one_value:string two_value:string few_value:string many_value:string other_value:string = LanguagePackString; languagePackStringPluralized key:string zero_value:string one_value:string two_value:string few_value:string many_value:string other_value:string = LanguagePackString;
//@description A deleted language pack string @key String key //@description A deleted language pack string, the value should be taken from a built-in english language pack @key String key
languagePackStringDeleted key:string = LanguagePackString; languagePackStringDeleted key:string = LanguagePackString;

View File

@ -66,6 +66,117 @@ void LanguagePackManager::inc_generation() {
language_pack_version_ = -1; language_pack_version_ = -1;
} }
LanguagePackManager::Language *LanguagePackManager::get_language(const string &language_pack,
const string &language_code) {
std::unique_lock<std::mutex> lock(language_packs_mutex_);
auto it = language_packs_.find(language_pack);
if (it == language_packs_.end()) {
return nullptr;
}
LanguagePack *pack = it->second.get();
lock.unlock();
return get_language(pack, language_code);
}
LanguagePackManager::Language *LanguagePackManager::get_language(LanguagePack *language_pack,
const string &language_code) {
CHECK(language_pack != nullptr);
std::lock_guard<std::mutex> lock(language_pack->mutex_);
auto it = language_pack->languages_.find(language_code);
if (it == language_pack->languages_.end()) {
return nullptr;
}
return it->second.get();
}
LanguagePackManager::Language *LanguagePackManager::add_language(const string &language_pack,
const string &language_code) {
std::lock_guard<std::mutex> packs_lock(language_packs_mutex_);
auto pack_it = language_packs_.find(language_pack);
if (pack_it == language_packs_.end()) {
pack_it = language_packs_.emplace(language_pack, make_unique<LanguagePack>()).first;
}
LanguagePack *pack = pack_it->second.get();
std::lock_guard<std::mutex> languages_lock(pack->mutex_);
auto code_it = pack->languages_.find(language_code);
if (code_it == pack->languages_.end()) {
code_it = pack->languages_.emplace(language_code, make_unique<Language>()).first;
}
return code_it->second.get();
}
bool LanguagePackManager::language_has_string_unsafe(Language *language, const string &key) {
return language->ordinary_strings_.count(key) != 0 || language->pluralized_strings_.count(key) != 0 ||
language->deleted_strings_.count(key) != 0;
}
bool LanguagePackManager::language_has_strings(Language *language, const vector<string> &keys) {
if (language == nullptr) {
return false;
}
std::lock_guard<std::mutex> lock(language->mutex_);
if (keys.empty()) {
return language->version_ != -1;
}
for (auto &key : keys) {
if (!language_has_string_unsafe(language, key)) {
return false;
}
}
return true;
}
td_api::object_ptr<td_api::LanguagePackString> LanguagePackManager::get_language_pack_string_object(
const std::pair<string, string> &str) {
return td_api::make_object<td_api::languagePackStringValue>(str.first, str.second);
}
td_api::object_ptr<td_api::LanguagePackString> LanguagePackManager::get_language_pack_string_object(
const std::pair<string, PluralizedString> &str) {
return td_api::make_object<td_api::languagePackStringPluralized>(
str.first, str.second.zero_value_, str.second.one_value_, str.second.two_value_, str.second.few_value_,
str.second.many_value_, str.second.other_value_);
}
td_api::object_ptr<td_api::LanguagePackString> LanguagePackManager::get_language_pack_string_object(const string &str) {
return td_api::make_object<td_api::languagePackStringDeleted>(str);
}
td_api::object_ptr<td_api::languagePackStrings> LanguagePackManager::get_language_pack_strings_object(
Language *language, const vector<string> &keys) {
CHECK(language != nullptr);
std::lock_guard<std::mutex> lock(language->mutex_);
vector<td_api::object_ptr<td_api::LanguagePackString>> strings;
if (keys.empty()) {
for (auto &str : language->ordinary_strings_) {
strings.push_back(get_language_pack_string_object(str));
}
for (auto &str : language->pluralized_strings_) {
strings.push_back(get_language_pack_string_object(str));
}
} else {
for (auto &key : keys) {
auto ordinary_it = language->ordinary_strings_.find(key);
if (ordinary_it != language->ordinary_strings_.end()) {
strings.push_back(get_language_pack_string_object(*ordinary_it));
continue;
}
auto pluralized_it = language->pluralized_strings_.find(key);
if (pluralized_it != language->pluralized_strings_.end()) {
strings.push_back(get_language_pack_string_object(*pluralized_it));
continue;
}
LOG_IF(ERROR, language->deleted_strings_.count(key) == 0) << "Have no string for key " << key;
strings.push_back(get_language_pack_string_object(key));
}
}
return td_api::make_object<td_api::languagePackStrings>(std::move(strings));
}
void LanguagePackManager::get_languages(Promise<td_api::object_ptr<td_api::languagePack>> promise) { void LanguagePackManager::get_languages(Promise<td_api::object_ptr<td_api::languagePack>> promise) {
auto request_promise = PromiseCreator::lambda([promise = std::move(promise)](Result<NetQueryPtr> r_query) mutable { auto request_promise = PromiseCreator::lambda([promise = std::move(promise)](Result<NetQueryPtr> r_query) mutable {
auto r_result = fetch_result<telegram_api::langpack_getLanguages>(std::move(r_query)); auto r_result = fetch_result<telegram_api::langpack_getLanguages>(std::move(r_query));
@ -88,17 +199,15 @@ void LanguagePackManager::get_languages(Promise<td_api::object_ptr<td_api::langu
void LanguagePackManager::get_language_pack_strings(string language_code, vector<string> keys, void LanguagePackManager::get_language_pack_strings(string language_code, vector<string> keys,
Promise<td_api::object_ptr<td_api::languagePackStrings>> promise) { Promise<td_api::object_ptr<td_api::languagePackStrings>> promise) {
bool is_all = keys.empty(); Language *language = get_language(language_pack_, language_code);
auto result_promise = if (language_has_strings(language, keys)) {
PromiseCreator::lambda([actor_id = actor_id(this), is_all, promise = std::move(promise)]( return promise.set_value(get_language_pack_strings_object(language, keys));
Result<vector<tl_object_ptr<telegram_api::LangPackString>>> r_result) mutable { }
send_closure(actor_id, &LanguagePackManager::on_get_language_pack_strings, std::move(r_result), is_all,
std::move(promise));
});
if (is_all) { if (keys.empty()) {
auto request_promise = auto request_promise =
PromiseCreator::lambda([promise = std::move(result_promise)](Result<NetQueryPtr> r_query) mutable { PromiseCreator::lambda([actor_id = actor_id(this), language_pack = language_pack_, language_code,
promise = std::move(promise)](Result<NetQueryPtr> r_query) mutable {
auto r_result = fetch_result<telegram_api::langpack_getLangPack>(std::move(r_query)); auto r_result = fetch_result<telegram_api::langpack_getLangPack>(std::move(r_query));
if (r_result.is_error()) { if (r_result.is_error()) {
return promise.set_error(r_result.move_as_error()); return promise.set_error(r_result.move_as_error());
@ -108,19 +217,26 @@ void LanguagePackManager::get_language_pack_strings(string language_code, vector
LOG(INFO) << "Receive language pack for language " << result->lang_code_ << " from version " LOG(INFO) << "Receive language pack for language " << result->lang_code_ << " from version "
<< result->from_version_ << " with version " << result->version_ << " of size " << result->from_version_ << " with version " << result->version_ << " of size "
<< result->strings_.size(); << result->strings_.size();
promise.set_value(std::move(result->strings_)); LOG_IF(ERROR, result->lang_code_ != language_code)
<< "Receive strings for " << result->lang_code_ << " instead of " << language_code;
LOG_IF(ERROR, result->from_version_ != 0) << "Receive lang pack from version " << result->from_version_;
send_closure(actor_id, &LanguagePackManager::on_get_language_pack_strings, std::move(language_pack),
std::move(language_code), result->version_, vector<string>(), std::move(result->strings_),
std::move(promise));
}); });
send_with_promise(G()->net_query_creator().create(create_storer(telegram_api::langpack_getLangPack(language_code))), send_with_promise(G()->net_query_creator().create(create_storer(telegram_api::langpack_getLangPack(language_code))),
std::move(request_promise)); std::move(request_promise));
} else { } else {
auto request_promise = auto request_promise =
PromiseCreator::lambda([promise = std::move(result_promise)](Result<NetQueryPtr> r_query) mutable { PromiseCreator::lambda([actor_id = actor_id(this), language_pack = language_pack_, language_code, keys,
promise = std::move(promise)](Result<NetQueryPtr> r_query) mutable {
auto r_result = fetch_result<telegram_api::langpack_getStrings>(std::move(r_query)); auto r_result = fetch_result<telegram_api::langpack_getStrings>(std::move(r_query));
if (r_result.is_error()) { if (r_result.is_error()) {
return promise.set_error(r_result.move_as_error()); return promise.set_error(r_result.move_as_error());
} }
promise.set_value(r_result.move_as_ok()); send_closure(actor_id, &LanguagePackManager::on_get_language_pack_strings, std::move(language_pack),
std::move(language_code), -1, std::move(keys), r_result.move_as_ok(), std::move(promise));
}); });
send_with_promise(G()->net_query_creator().create( send_with_promise(G()->net_query_creator().create(
create_storer(telegram_api::langpack_getStrings(language_code, std::move(keys)))), create_storer(telegram_api::langpack_getStrings(language_code, std::move(keys)))),
@ -129,36 +245,57 @@ void LanguagePackManager::get_language_pack_strings(string language_code, vector
} }
void LanguagePackManager::on_get_language_pack_strings( void LanguagePackManager::on_get_language_pack_strings(
Result<vector<tl_object_ptr<telegram_api::LangPackString>>> r_result, bool ia_all, string language_pack, string language_code, int32 version, vector<string> keys,
vector<tl_object_ptr<telegram_api::LangPackString>> results,
Promise<td_api::object_ptr<td_api::languagePackStrings>> promise) { Promise<td_api::object_ptr<td_api::languagePackStrings>> promise) {
if (r_result.is_error()) { Language *language = get_language(language_pack, language_code);
return promise.set_error(r_result.move_as_error()); if (language == nullptr || (language->version_ < version || !keys.empty())) {
if (language == nullptr) {
language = add_language(language_pack, language_code);
CHECK(language != nullptr);
} }
auto result = std::lock_guard<std::mutex> lock(language->mutex_);
transform(r_result.move_as_ok(), [](const auto &string_ptr) -> tl_object_ptr<td_api::LanguagePackString> { if (language->version_ < version || !keys.empty()) {
CHECK(string_ptr != nullptr); if (language->version_ < version) {
switch (string_ptr->get_id()) { language->version_ = version;
}
for (auto &result : results) {
CHECK(result != nullptr);
switch (result->get_id()) {
case telegram_api::langPackString::ID: { case telegram_api::langPackString::ID: {
auto str = static_cast<const telegram_api::langPackString *>(string_ptr.get()); auto str = static_cast<telegram_api::langPackString *>(result.get());
return make_tl_object<td_api::languagePackStringValue>(str->key_, str->value_); language->ordinary_strings_[str->key_] = std::move(str->value_);
break;
} }
case telegram_api::langPackStringPluralized::ID: { case telegram_api::langPackStringPluralized::ID: {
auto str = static_cast<const telegram_api::langPackStringPluralized *>(string_ptr.get()); auto str = static_cast<const telegram_api::langPackStringPluralized *>(result.get());
return make_tl_object<td_api::languagePackStringPluralized>(str->key_, str->zero_value_, str->one_value_, language->pluralized_strings_[str->key_] = PluralizedString{
str->two_value_, str->few_value_, std::move(str->zero_value_), std::move(str->one_value_), std::move(str->two_value_),
str->many_value_, str->other_value_); std::move(str->few_value_), std::move(str->many_value_), std::move(str->other_value_)};
break;
} }
case telegram_api::langPackStringDeleted::ID: { case telegram_api::langPackStringDeleted::ID: {
auto str = static_cast<const telegram_api::langPackStringDeleted *>(string_ptr.get()); auto str = static_cast<const telegram_api::langPackStringDeleted *>(result.get());
return make_tl_object<td_api::languagePackStringDeleted>(str->key_); language->deleted_strings_.insert(std::move(str->key_));
break;
} }
default: default:
UNREACHABLE(); UNREACHABLE();
return nullptr; break;
}
}
for (auto &key : keys) {
if (!language_has_string_unsafe(language, key)) {
LOG(ERROR) << "Doesn't receive key " << key << " from server";
language->deleted_strings_.insert(key);
}
} }
});
promise.set_value(make_tl_object<td_api::languagePackStrings>(std::move(result))); // TODO save language
}
}
promise.set_value(get_language_pack_strings_object(language, keys));
} }
void LanguagePackManager::on_result(NetQueryPtr query) { void LanguagePackManager::on_result(NetQueryPtr query) {
@ -177,4 +314,7 @@ void LanguagePackManager::hangup() {
stop(); stop();
} }
std::mutex LanguagePackManager::language_packs_mutex_;
std::unordered_map<string, std::unique_ptr<LanguagePackManager::LanguagePack>> LanguagePackManager::language_packs_;
} // namespace td } // namespace td

View File

@ -17,6 +17,11 @@
#include "td/utils/Container.h" #include "td/utils/Container.h"
#include "td/utils/Status.h" #include "td/utils/Status.h"
#include <atomic>
#include <mutex>
#include <unordered_map>
#include <unordered_set>
namespace td { namespace td {
class LanguagePackManager : public NetQueryCallback { class LanguagePackManager : public NetQueryCallback {
@ -36,6 +41,28 @@ class LanguagePackManager : public NetQueryCallback {
Promise<td_api::object_ptr<td_api::languagePackStrings>> promise); Promise<td_api::object_ptr<td_api::languagePackStrings>> promise);
private: private:
struct PluralizedString {
string zero_value_;
string one_value_;
string two_value_;
string few_value_;
string many_value_;
string other_value_;
};
struct Language {
std::mutex mutex_; // TODO RwMutex
std::atomic<int32> version_ = -1;
std::unordered_map<string, string> ordinary_strings_;
std::unordered_map<string, PluralizedString> pluralized_strings_;
std::unordered_set<string> deleted_strings_;
};
struct LanguagePack {
std::mutex mutex_;
std::unordered_map<string, std::unique_ptr<Language>> languages_;
};
ActorShared<> parent_; ActorShared<> parent_;
string language_pack_; string language_pack_;
@ -44,9 +71,30 @@ class LanguagePackManager : public NetQueryCallback {
int32 language_pack_version_ = -1; int32 language_pack_version_ = -1;
static std::mutex language_packs_mutex_;
static std::unordered_map<string, std::unique_ptr<LanguagePack>> language_packs_;
static Language *get_language(const string &language_pack, const string &language_code);
static Language *get_language(LanguagePack *language_pack, const string &language_code);
static Language *add_language(const string &language_pack, const string &language_code);
static bool language_has_string_unsafe(Language *language, const string &key);
static bool language_has_strings(Language *language, const vector<string> &keys);
static td_api::object_ptr<td_api::LanguagePackString> get_language_pack_string_object(
const std::pair<string, string> &str);
static td_api::object_ptr<td_api::LanguagePackString> get_language_pack_string_object(
const std::pair<string, PluralizedString> &str);
static td_api::object_ptr<td_api::LanguagePackString> get_language_pack_string_object(const string &str);
static td_api::object_ptr<td_api::languagePackStrings> get_language_pack_strings_object(Language *language,
const vector<string> &keys);
void inc_generation(); void inc_generation();
void on_get_language_pack_strings(Result<vector<tl_object_ptr<telegram_api::LangPackString>>> r_result, bool ia_all, void on_get_language_pack_strings(string language_pack, string language_code, int32 version, vector<string> keys,
vector<tl_object_ptr<telegram_api::LangPackString>> results,
Promise<td_api::object_ptr<td_api::languagePackStrings>> promise); Promise<td_api::object_ptr<td_api::languagePackStrings>> promise);
void on_result(NetQueryPtr query) override; void on_result(NetQueryPtr query) override;

View File

@ -17,6 +17,7 @@
#endif #endif
namespace td { namespace td {
class RwMutex { class RwMutex {
public: public:
RwMutex() { RwMutex() {