// // Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include "td/telegram/LanguagePackManager.h" #include "td/telegram/ConfigShared.h" #include "td/telegram/Global.h" #include "td/telegram/logevent/LogEvent.h" #include "td/telegram/misc.h" #include "td/telegram/net/NetQueryDispatcher.h" #include "td/telegram/Td.h" #include "td/telegram/td_api.h" #include "td/telegram/td_api.hpp" #include "td/telegram/telegram_api.h" #include "td/db/SqliteDb.h" #include "td/db/SqliteKeyValue.h" #include "td/utils/logging.h" #include "td/utils/misc.h" #include "td/utils/Status.h" #include #include #include namespace td { struct LanguagePackManager::PluralizedString { string zero_value_; string one_value_; string two_value_; string few_value_; string many_value_; string other_value_; }; struct LanguagePackManager::Language { std::mutex mutex_; std::atomic version_{-1}; bool is_full_ = false; bool has_get_difference_query_ = false; std::unordered_map ordinary_strings_; std::unordered_map pluralized_strings_; std::unordered_set deleted_strings_; SqliteKeyValue kv_; // usages should be guarded by database_->mutex_ }; struct LanguagePackManager::LanguageInfo { string name; string native_name; }; struct LanguagePackManager::LanguagePack { std::mutex mutex_; SqliteKeyValue pack_kv_; // usages should be guarded by database_->mutex_ std::unordered_map language_infos_; std::unordered_map> languages_; }; struct LanguagePackManager::LanguageDatabase { std::mutex mutex_; string path_; SqliteDb database_; std::unordered_map> language_packs_; }; LanguagePackManager::~LanguagePackManager() = default; bool LanguagePackManager::check_language_pack_name(Slice name) { for (auto c : name) { if (c != '_' && !is_alpha(c)) { return false; } } return true; } bool LanguagePackManager::check_language_code_name(Slice name) { for (auto c : name) { if (c != '-' && !is_alpha(c)) { return false; } } return true; } bool LanguagePackManager::is_custom_language_code(Slice language_code) { return !language_code.empty() && language_code[0] == 'X'; } static Result open_database(const string &path) { TRY_RESULT(database, SqliteDb::open_with_key(path, DbKey::empty())); TRY_STATUS(database.exec("PRAGMA synchronous=NORMAL")); TRY_STATUS(database.exec("PRAGMA temp_store=MEMORY")); TRY_STATUS(database.exec("PRAGMA encoding=\"UTF-8\"")); TRY_STATUS(database.exec("PRAGMA journal_mode=WAL")); return std::move(database); } static int32 load_database_language_version(SqliteKeyValue *kv) { CHECK(kv != nullptr); if (kv->empty()) { return -1; } string str_version = kv->get("!version"); if (str_version.empty()) { return -1; } return to_integer(str_version); } LanguagePackManager::LanguageDatabase *LanguagePackManager::add_language_database(const string &path) { auto it = language_databases_.find(path); if (it != language_databases_.end()) { return it->second.get(); } SqliteDb database; if (!path.empty()) { auto r_database = open_database(path); if (r_database.is_error()) { LOG(ERROR) << "Can't open language database " << path << ": " << r_database.error(); return add_language_database(string()); } database = r_database.move_as_ok(); } it = language_databases_.emplace(path, make_unique()).first; it->second->path_ = std::move(path); it->second->database_ = std::move(database); return it->second.get(); } void LanguagePackManager::start_up() { std::lock_guard lock(language_database_mutex_); manager_count_++; language_pack_ = G()->shared_config().get_option_string("language_pack"); language_code_ = G()->shared_config().get_option_string("language_code"); CHECK(check_language_pack_name(language_pack_)); CHECK(check_language_code_name(language_code_)); database_ = add_language_database(G()->shared_config().get_option_string("language_database_path")); auto language = add_language(database_, language_pack_, language_code_); LOG(INFO) << "Use language pack \"" << language_pack_ << "\" with language \"" << language_code_ << "\" of version " << language->version_.load() << " with database \"" << database_->path_ << '"'; } void LanguagePackManager::tear_down() { std::lock_guard lock(language_database_mutex_); manager_count_--; if (manager_count_ == 0) { // can't clear language packs, because the may be accessed later using synchronous requests // LOG(INFO) << "Clear language packs"; // language_databases_.clear(); } } void LanguagePackManager::on_language_pack_changed() { auto new_language_pack = G()->shared_config().get_option_string("language_pack"); if (new_language_pack == language_pack_) { return; } language_pack_ = std::move(new_language_pack); CHECK(check_language_pack_name(language_pack_)); inc_generation(); } void LanguagePackManager::on_language_code_changed() { auto new_language_code = G()->shared_config().get_option_string("language_code"); if (new_language_code == language_code_) { return; } language_code_ = std::move(new_language_code); CHECK(check_language_code_name(language_code_)); inc_generation(); } void LanguagePackManager::on_language_pack_version_changed(int32 new_version) { if (is_custom_language_code(language_code_)) { return; } Language *language = get_language(database_, language_pack_, language_code_); int32 version = language == nullptr ? static_cast(-1) : language->version_.load(); if (version == -1) { return; } auto new_language_pack_version = new_version >= 0 ? new_version : G()->shared_config().get_option_integer("language_pack_version", -1); if (new_language_pack_version <= version) { return; } std::lock_guard lock(language->mutex_); if (language->has_get_difference_query_) { return; } language->has_get_difference_query_ = true; auto request_promise = PromiseCreator::lambda([actor_id = actor_id(this), language_pack = language_pack_, language_code = language_code_, from_version = version](Result r_query) mutable { auto r_result = fetch_result(std::move(r_query)); if (r_result.is_error()) { send_closure(actor_id, &LanguagePackManager::on_failed_get_difference, std::move(language_pack), std::move(language_code)); return; } auto result = r_result.move_as_ok(); LOG(INFO) << "Receive language pack difference for language " << result->lang_code_ << " from version " << result->from_version_ << " with version " << result->version_ << " of size " << result->strings_.size(); LOG_IF(ERROR, result->lang_code_ != language_code) << "Receive strings for " << result->lang_code_ << " instead of " << language_code; LOG_IF(ERROR, result->from_version_ != from_version) << "Receive strings from " << result->from_version_ << " instead of " << from_version; send_closure(actor_id, &LanguagePackManager::on_get_language_pack_strings, std::move(language_pack), std::move(language_code), result->version_, true, vector(), std::move(result->strings_), Promise>()); }); send_with_promise(G()->net_query_creator().create(create_storer(telegram_api::langpack_getDifference(version))), std::move(request_promise)); } void LanguagePackManager::on_update_language_pack(tl_object_ptr difference) { LOG(INFO) << "Receive update language pack difference for language " << difference->lang_code_ << " from version " << difference->from_version_ << " with version " << difference->version_ << " of size " << difference->strings_.size(); if (difference->lang_code_ != language_code_) { LOG(WARNING) << "Ignore difference for language " << difference->lang_code_; return; } Language *language = get_language(database_, language_pack_, language_code_); int32 version = language == nullptr ? static_cast(-1) : language->version_.load(); if (difference->version_ <= version) { LOG(INFO) << "Skip applying already applied updates"; return; } if (version == -1 || version < difference->from_version_) { LOG(INFO) << "Can't apply difference"; return on_language_pack_version_changed(difference->version_); } on_get_language_pack_strings(language_pack_, std::move(difference->lang_code_), difference->version_, true, vector(), std::move(difference->strings_), Promise>()); } void LanguagePackManager::inc_generation() { G()->shared_config().set_option_empty("language_pack_version"); } LanguagePackManager::Language *LanguagePackManager::get_language(LanguageDatabase *database, const string &language_pack, const string &language_code) { std::unique_lock lock(database->mutex_); auto it = database->language_packs_.find(language_pack); if (it == database->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 lock(language_pack->mutex_); auto it = language_pack->languages_.find(language_code); if (it == language_pack->languages_.end()) { return nullptr; } return it->second.get(); } static string get_database_table_name(const string &language_pack, const string &language_code) { return PSTRING() << "\"kv_" << language_pack << '_' << language_code << '"'; } LanguagePackManager::Language *LanguagePackManager::add_language(LanguageDatabase *database, const string &language_pack, const string &language_code) { std::lock_guard packs_lock(database->mutex_); auto pack_it = database->language_packs_.find(language_pack); if (pack_it == database->language_packs_.end()) { auto pack = make_unique(); if (!database->database_.empty()) { pack->pack_kv_.init_with_connection(database->database_.clone(), get_database_table_name(language_pack, "0")) .ensure(); for (auto &lang : pack->pack_kv_.get_all()) { auto names = split(lang.second, '\x00'); auto &info = pack->language_infos_[lang.first]; info.name = std::move(names.first); info.native_name = std::move(names.second); } } pack_it = database->language_packs_.emplace(language_pack, std::move(pack)).first; } LanguagePack *pack = pack_it->second.get(); std::lock_guard languages_lock(pack->mutex_); auto code_it = pack->languages_.find(language_code); if (code_it == pack->languages_.end()) { auto language = make_unique(); if (!database->database_.empty()) { language->kv_ .init_with_connection(database->database_.clone(), get_database_table_name(language_pack, language_code)) .ensure(); language->version_ = load_database_language_version(&language->kv_); } code_it = pack->languages_.emplace(language_code, std::move(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 &keys) { if (language == nullptr) { return false; } std::lock_guard lock(language->mutex_); if (language->is_full_) { return true; } if (keys.empty()) { return language->version_ != -1 && language->is_full_; } for (auto &key : keys) { if (!language_has_string_unsafe(language, key)) { return false; } } return true; } void LanguagePackManager::load_language_string_unsafe(Language *language, const string &key, string &value) { CHECK(is_valid_key(key)); if (value.empty() || value == "3") { if (!language->is_full_) { language->deleted_strings_.insert(key); } return; } if (value[0] == '1') { language->ordinary_strings_.emplace(key, value.substr(1)); return; } CHECK(value[0] == '2'); auto all = full_split(Slice(value).substr(1), '\x00'); CHECK(all.size() == 6); language->pluralized_strings_.emplace( key, PluralizedString{all[0].str(), all[1].str(), all[2].str(), all[3].str(), all[4].str(), all[5].str()}); } bool LanguagePackManager::load_language_strings(LanguageDatabase *database, Language *language, const vector &keys) { if (language == nullptr) { return false; } LOG(DEBUG) << "Begin to load a language from database"; std::lock_guard database_lock(database->mutex_); std::lock_guard language_lock(language->mutex_); if (language->is_full_) { LOG(DEBUG) << "The language was already loaded"; return true; } if (language->kv_.empty()) { LOG(DEBUG) << "The language has no database"; return false; } if (keys.empty()) { if (language->version_ == -1) { LOG(DEBUG) << "There is nothing to load"; return false; } auto all_strings = language->kv_.get_all(); for (auto &str : all_strings) { if (str.first == "!version") { CHECK(to_integer(str.second) == language->version_); continue; } if (!language_has_string_unsafe(language, str.first)) { LOG(DEBUG) << "Load string with key " << str.first << " from database"; load_language_string_unsafe(language, str.first, str.second); } } language->is_full_ = true; language->deleted_strings_.clear(); return true; } for (auto &key : keys) { if (!language_has_string_unsafe(language, key)) { auto value = language->kv_.get(key); if (value.empty()) { if (language->version_ == -1) { LOG(DEBUG) << "Have no string with key " << key << " in the database"; return false; } // have full language in the database, so this string is just deleted } LOG(DEBUG) << "Load string with key " << key << " from database"; load_language_string_unsafe(language, key, value); } } return true; } td_api::object_ptr LanguagePackManager::get_language_pack_string_object( const std::pair &str) { return td_api::make_object(str.first, str.second); } td_api::object_ptr LanguagePackManager::get_language_pack_string_object( const std::pair &str) { return td_api::make_object( 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 LanguagePackManager::get_language_pack_string_object(const string &str) { return td_api::make_object(str); } td_api::object_ptr LanguagePackManager::get_language_pack_string_object(Language *language, const string &key) { CHECK(language != nullptr); auto ordinary_it = language->ordinary_strings_.find(key); if (ordinary_it != language->ordinary_strings_.end()) { return get_language_pack_string_object(*ordinary_it); } auto pluralized_it = language->pluralized_strings_.find(key); if (pluralized_it != language->pluralized_strings_.end()) { return get_language_pack_string_object(*pluralized_it); } LOG_IF(ERROR, !language->is_full_ && language->deleted_strings_.count(key) == 0) << "Have no string for key " << key; return get_language_pack_string_object(key); } td_api::object_ptr LanguagePackManager::get_language_pack_strings_object( Language *language, const vector &keys) { CHECK(language != nullptr); std::lock_guard lock(language->mutex_); vector> 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) { strings.push_back(get_language_pack_string_object(language, key)); } } return td_api::make_object(std::move(strings)); } void LanguagePackManager::get_languages(Promise> promise) { auto results = make_tl_object(); std::lock_guard packs_lock(database_->mutex_); auto pack_it = database_->language_packs_.find(language_pack_); if (pack_it != database_->language_packs_.end()) { LanguagePack *pack = pack_it->second.get(); for (auto &info : pack->language_infos_) { results->languages_.push_back( make_tl_object(info.first, info.second.name, info.second.native_name)); } } auto request_promise = PromiseCreator::lambda( [results = std::move(results), promise = std::move(promise)](Result r_query) mutable { auto r_result = fetch_result(std::move(r_query)); if (r_result.is_error()) { return promise.set_error(r_result.move_as_error()); } auto languages = r_result.move_as_ok(); for (auto &language : languages) { results->languages_.push_back( make_tl_object(language->lang_code_, language->name_, language->native_name_)); } promise.set_value(std::move(results)); }); send_with_promise(G()->net_query_creator().create(create_storer(telegram_api::langpack_getLanguages(language_pack_))), std::move(request_promise)); } void LanguagePackManager::get_language_pack_strings(string language_code, vector keys, Promise> promise) { for (auto &key : keys) { if (!is_valid_key(key)) { return promise.set_error(Status::Error(400, "Invalid key name")); } } Language *language = get_language(database_, language_pack_, language_code); if (language_has_strings(language, keys)) { return promise.set_value(get_language_pack_strings_object(language, keys)); } if (!database_->database_.empty()) { language = add_language(database_, language_pack_, language_code); if (load_language_strings(database_, language, keys)) { return promise.set_value(get_language_pack_strings_object(language, keys)); } } if (keys.empty()) { auto request_promise = PromiseCreator::lambda([actor_id = actor_id(this), language_pack = language_pack_, language_code, promise = std::move(promise)](Result r_query) mutable { auto r_result = fetch_result(std::move(r_query)); if (r_result.is_error()) { return promise.set_error(r_result.move_as_error()); } auto result = r_result.move_as_ok(); LOG(INFO) << "Receive language pack for language " << result->lang_code_ << " from version " << result->from_version_ << " with version " << result->version_ << " of size " << result->strings_.size(); 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_, false, vector(), std::move(result->strings_), std::move(promise)); }); send_with_promise(G()->net_query_creator().create( create_storer(telegram_api::langpack_getLangPack(language_pack_, language_code))), std::move(request_promise)); } else { auto request_promise = PromiseCreator::lambda([actor_id = actor_id(this), language_pack = language_pack_, language_code, keys, promise = std::move(promise)](Result r_query) mutable { auto r_result = fetch_result(std::move(r_query)); if (r_result.is_error()) { return promise.set_error(r_result.move_as_error()); } send_closure(actor_id, &LanguagePackManager::on_get_language_pack_strings, std::move(language_pack), std::move(language_code), -1, false, std::move(keys), r_result.move_as_ok(), std::move(promise)); }); send_with_promise(G()->net_query_creator().create(create_storer( telegram_api::langpack_getStrings(language_pack_, language_code, std::move(keys)))), std::move(request_promise)); } } td_api::object_ptr LanguagePackManager::get_language_pack_string(const string &database_path, const string &language_pack, const string &language_code, const string &key) { if (!check_language_pack_name(language_pack) || language_pack.empty()) { return td_api::make_object(400, "Language pack is invalid"); } if (!check_language_code_name(language_code) || language_code.empty()) { return td_api::make_object(400, "Language code is invalid"); } if (!is_valid_key(key)) { return td_api::make_object(400, "Key is invalid"); } std::unique_lock language_databases_lock(language_database_mutex_); LanguageDatabase *database = add_language_database(database_path); CHECK(database != nullptr); language_databases_lock.unlock(); Language *language = add_language(database, language_pack, language_code); vector keys{key}; if (language_has_strings(language, keys)) { std::lock_guard lock(language->mutex_); return get_language_pack_string_object(language, key); } if (!database->database_.empty() && load_language_strings(database, language, keys)) { return get_language_pack_string_object(language, key); } return td_api::make_object(404, "Not Found"); } bool LanguagePackManager::is_valid_key(Slice key) { for (auto c : key) { if (!is_alnum(c) && c != '_' && c != '.' && c != '-') { return false; } } return !key.empty(); } void LanguagePackManager::save_strings_to_database(Language *language, int32 new_version, vector> strings) { LOG(DEBUG) << "Save to database a language with new version " << new_version; if (new_version == -1 && strings.empty()) { return; } std::lock_guard lock(database_->mutex_); auto kv = &language->kv_; if (kv->empty()) { LOG(DEBUG) << "There is no associated database key-value"; return; } auto old_version = load_database_language_version(kv); if (old_version > new_version || (old_version == new_version && strings.empty())) { LOG(DEBUG) << "Language version doesn't increased from " << old_version; return; } kv->begin_transaction().ensure(); for (auto str : strings) { if (!is_valid_key(str.first)) { LOG(ERROR) << "Have invalid key \"" << str.first << '"'; continue; } if (language->is_full_ && str.second == "3") { kv->erase(str.first); } else { kv->set(str.first, str.second); } LOG(DEBUG) << "Save language string with key " << str.first << " to database"; } if (old_version != new_version) { LOG(DEBUG) << "Set language version in database to " << new_version; kv->set("!version", to_string(new_version)); } kv->commit_transaction().ensure(); } void LanguagePackManager::on_get_language_pack_strings( string language_pack, string language_code, int32 version, bool is_diff, vector keys, vector> results, Promise> promise) { Language *language = get_language(database_, language_pack, language_code); bool is_version_changed = false; int32 new_database_version = -1; vector> database_strings; if (language == nullptr || language->version_ < version || !keys.empty()) { if (language == nullptr) { language = add_language(database_, language_pack, language_code); CHECK(language != nullptr); } std::lock_guard lock(language->mutex_); if (language->version_ < version || !keys.empty()) { vector> strings; if (language->version_ < version && !(is_diff && language->version_ == -1)) { LOG(INFO) << "Set language " << language_code << " version to " << version; language->version_ = version; new_database_version = version; is_version_changed = true; } for (auto &result : results) { CHECK(result != nullptr); switch (result->get_id()) { case telegram_api::langPackString::ID: { auto str = static_cast(result.get()); auto it = language->ordinary_strings_.find(str->key_); if (it == language->ordinary_strings_.end()) { it = language->ordinary_strings_.emplace(str->key_, std::move(str->value_)).first; } else { it->second = std::move(str->value_); } language->pluralized_strings_.erase(str->key_); language->deleted_strings_.erase(str->key_); if (is_diff) { strings.push_back(get_language_pack_string_object(*it)); } database_strings.emplace_back(str->key_, PSTRING() << '1' << it->second); break; } case telegram_api::langPackStringPluralized::ID: { auto str = static_cast(result.get()); PluralizedString value{std::move(str->zero_value_), std::move(str->one_value_), std::move(str->two_value_), std::move(str->few_value_), std::move(str->many_value_), std::move(str->other_value_)}; auto it = language->pluralized_strings_.find(str->key_); if (it == language->pluralized_strings_.end()) { it = language->pluralized_strings_.emplace(str->key_, std::move(value)).first; } else { it->second = std::move(value); } language->ordinary_strings_.erase(str->key_); language->deleted_strings_.erase(str->key_); if (is_diff) { strings.push_back(get_language_pack_string_object(*it)); } database_strings.emplace_back( str->key_, PSTRING() << '2' << it->second.zero_value_ << '\x00' << it->second.one_value_ << '\x00' << it->second.two_value_ << '\x00' << it->second.few_value_ << '\x00' << it->second.many_value_ << '\x00' << it->second.other_value_); break; } case telegram_api::langPackStringDeleted::ID: { auto str = static_cast(result.get()); language->ordinary_strings_.erase(str->key_); language->pluralized_strings_.erase(str->key_); language->deleted_strings_.insert(std::move(str->key_)); if (is_diff) { strings.push_back(get_language_pack_string_object(str->key_)); } database_strings.emplace_back(str->key_, "3"); break; } default: UNREACHABLE(); break; } } if (!language->is_full_) { 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); if (is_diff) { strings.push_back(get_language_pack_string_object(key)); } database_strings.emplace_back(key, "3"); } } } if (is_diff) { send_closure( G()->td(), &Td::send_update, td_api::make_object(language_pack, language_code, std::move(strings))); } if (keys.empty() && !is_diff) { CHECK(new_database_version >= 0); language->is_full_ = true; language->deleted_strings_.clear(); } } } save_strings_to_database(language, new_database_version, std::move(database_strings)); if (is_diff) { CHECK(language != nullptr); std::lock_guard lock(language->mutex_); if (language->has_get_difference_query_) { language->has_get_difference_query_ = false; is_version_changed = true; } } if (is_version_changed && language_pack == language_pack_ && language_code == language_code_) { send_closure_later(actor_id(this), &LanguagePackManager::on_language_pack_version_changed, -1); } if (promise) { promise.set_value(get_language_pack_strings_object(language, keys)); } } void LanguagePackManager::on_failed_get_difference(string language_pack, string language_code) { Language *language = get_language(database_, language_pack, language_code); CHECK(language != nullptr); std::lock_guard lock(language->mutex_); if (language->has_get_difference_query_) { language->has_get_difference_query_ = false; if (language_pack == language_pack_ && language_code == language_code_) { send_closure_later(actor_id(this), &LanguagePackManager::on_language_pack_version_changed, -1); } } } Result> LanguagePackManager::convert_to_telegram_api( tl_object_ptr &&str) { if (str == nullptr) { return Status::Error(400, "Language strings must not be null"); } string key; downcast_call(*str, [&key](auto &value) { key = std::move(value.key_); }); if (!is_valid_key(key)) { return Status::Error(400, "Key is invalid"); } switch (str->get_id()) { case td_api::languagePackStringValue::ID: { auto value = static_cast(str.get()); if (!clean_input_string(value->value_)) { return Status::Error(400, "Strings must be encoded in UTF-8"); } return make_tl_object(std::move(key), std::move(value->value_)); } case td_api::languagePackStringPluralized::ID: { auto value = static_cast(str.get()); if (!clean_input_string(value->zero_value_) || !clean_input_string(value->one_value_) || !clean_input_string(value->two_value_) || !clean_input_string(value->few_value_) || !clean_input_string(value->many_value_) || !clean_input_string(value->other_value_)) { return Status::Error(400, "Strings must be encoded in UTF-8"); } return make_tl_object( 31, std::move(key), std::move(value->zero_value_), std::move(value->one_value_), std::move(value->two_value_), std::move(value->few_value_), std::move(value->many_value_), std::move(value->other_value_)); } case td_api::languagePackStringDeleted::ID: // there is no reason to save deleted strings in a custom language pack to database return make_tl_object(std::move(key)); default: UNREACHABLE(); return nullptr; } } void LanguagePackManager::set_custom_language(string language_code, string language_name, string language_native_name, vector> strings, Promise &&promise) { if (!check_language_code_name(language_code)) { return promise.set_error(Status::Error(400, "Language code name must contain only letters and hyphen")); } if (!is_custom_language_code(language_code)) { return promise.set_error(Status::Error(400, "Custom language code must begin with 'X'")); } vector> server_strings; for (auto &str : strings) { auto r_result = convert_to_telegram_api(std::move(str)); if (r_result.is_error()) { return promise.set_error(r_result.move_as_error()); } server_strings.push_back(r_result.move_as_ok()); } // TODO atomic replace do_delete_language(language_code).ensure(); on_get_language_pack_strings(language_pack_, language_code, 1, false, vector(), std::move(server_strings), Auto()); std::lock_guard packs_lock(database_->mutex_); auto pack_it = database_->language_packs_.find(language_pack_); CHECK(pack_it != database_->language_packs_.end()); LanguagePack *pack = pack_it->second.get(); auto &info = pack->language_infos_[language_code]; info.name = language_name; info.native_name = language_native_name; if (!pack->pack_kv_.empty()) { pack->pack_kv_.set(language_code, PSLICE() << language_name << '\x00' << language_native_name); } promise.set_value(Unit()); } void LanguagePackManager::set_custom_language_string(string language_code, tl_object_ptr str, Promise &&promise) { if (!check_language_code_name(language_code)) { return promise.set_error(Status::Error(400, "Language code name must contain only letters and hyphen")); } if (!is_custom_language_code(language_code)) { return promise.set_error(Status::Error(400, "Custom language code must begin with 'X'")); } if (get_language(database_, language_pack_, language_code) == nullptr) { return promise.set_error(Status::Error(400, "Custom language not found")); } string key; if (str != nullptr) { downcast_call(*str, [&key](auto &value) { key = value.key_; }); } auto r_str = convert_to_telegram_api(std::move(str)); if (r_str.is_error()) { return promise.set_error(r_str.move_as_error()); } vector> server_strings; server_strings.push_back(r_str.move_as_ok()); on_get_language_pack_strings(language_pack_, language_code, 1, true, {std::move(key)}, std::move(server_strings), Auto()); promise.set_value(Unit()); } void LanguagePackManager::delete_language(string language_code, Promise &&promise) { if (!check_language_code_name(language_code)) { return promise.set_error(Status::Error(400, "Language code name is invalid")); } if (language_code.empty()) { return promise.set_error(Status::Error(400, "Language code name is empty")); } if (language_code_ == language_code) { return promise.set_error(Status::Error(400, "Currently used language can't be deleted")); } auto status = do_delete_language(language_code); if (status.is_error()) { promise.set_error(std::move(status)); } else { promise.set_value(Unit()); } } Status LanguagePackManager::do_delete_language(string language_code) { add_language(database_, language_pack_, language_code); std::lock_guard packs_lock(database_->mutex_); auto pack_it = database_->language_packs_.find(language_pack_); CHECK(pack_it != database_->language_packs_.end()); LanguagePack *pack = pack_it->second.get(); std::lock_guard languages_lock(pack->mutex_); auto code_it = pack->languages_.find(language_code); CHECK(code_it != pack->languages_.end()); auto language = code_it->second.get(); if (language->has_get_difference_query_) { return Status::Error(400, "Language can't be deleted now, try again later"); } if (!language->kv_.empty()) { language->kv_.drop().ignore(); CHECK(language->kv_.empty()); CHECK(!database_->database_.empty()); language->kv_ .init_with_connection(database_->database_.clone(), get_database_table_name(language_pack_, language_code)) .ensure(); } std::lock_guard language_lock(language->mutex_); language->version_ = -1; language->is_full_ = false; language->ordinary_strings_.clear(); language->pluralized_strings_.clear(); language->deleted_strings_.clear(); if (is_custom_language_code(language_code)) { if (!pack->pack_kv_.empty()) { pack->pack_kv_.erase(language_code); } pack->language_infos_.erase(language_code); } return Status::OK(); } void LanguagePackManager::on_result(NetQueryPtr query) { auto token = get_link_token(); container_.extract(token).set_value(std::move(query)); } void LanguagePackManager::send_with_promise(NetQueryPtr query, Promise promise) { auto id = container_.create(std::move(promise)); G()->net_query_dispatcher().dispatch_with_callback(std::move(query), actor_shared(this, id)); } void LanguagePackManager::hangup() { container_.for_each( [](auto id, Promise &promise) { promise.set_error(Status::Error(500, "Request aborted")); }); stop(); } int32 LanguagePackManager::manager_count_ = 0; std::mutex LanguagePackManager::language_database_mutex_; std::unordered_map> LanguagePackManager::language_databases_; } // namespace td