diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..290fa4e0165b09615c6363f5c48f6c362c0bdcb7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +* +!*/ +!.gitignore +!.gitignore +!/spdlog/* +!CMakeLists.txt +!README.md +!/img/** +!/server/** +!/test_client/** +!/plugins/**/*.py diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000000000000000000000000000000000..0f4533c91553e7b0b8824b8a37ba0eeeb2809687 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "spdlog"] + path = spdlog + url = https://github.com/gabime/spdlog diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..5e909e0f977dd07cade3f14d9b01b4dee7558b8f --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,16 @@ +cmake_minimum_required(VERSION 3.22) + +project(Dict2FlashcardsQT) +set(CMAKE_CXX_STANDARD 20) + +# Copyright(c) 2019 spdlog authors Distributed under the MIT License (http://opensource.org/licenses/MIT) +cmake_minimum_required(VERSION 3.10) +project(spdlog_examples CXX) + +# Logger +add_subdirectory(spdlog) +link_libraries(spdlog::spdlog_header_only) + +add_subdirectory(server) +add_subdirectory(test_client) +add_subdirectory(python_tests) diff --git a/plugins/audios/audios/__init__.py b/plugins/audios/audios/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..5054b8606c1579a1300f4777b18c9e7aadfd8a26 --- /dev/null +++ b/plugins/audios/audios/__init__.py @@ -0,0 +1 @@ +from .audios_provider import * diff --git a/plugins/audios/audios/audios_provider.py b/plugins/audios/audios/audios_provider.py new file mode 100644 index 0000000000000000000000000000000000000000..87a1abe2f4b393606e51e69995c978f2c3343aa1 --- /dev/null +++ b/plugins/audios/audios/audios_provider.py @@ -0,0 +1,22 @@ +def get(word: str, config: dict): + return [[], []], "" + + +def load(): + return + + +def unload(): + return + + +def get_config_description(): + return {} + + +def get_default_config(): + return {} + + +def set_config(new_config: dict) -> dict: + return {} diff --git a/plugins/definitions/definitions/__init__.py b/plugins/definitions/definitions/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..2201cffa7e14be4ddd2485114abd88f5c6ad6281 --- /dev/null +++ b/plugins/definitions/definitions/__init__.py @@ -0,0 +1 @@ +from .definitions_provider import * diff --git a/plugins/definitions/definitions/definitions_provider.py b/plugins/definitions/definitions/definitions_provider.py new file mode 100644 index 0000000000000000000000000000000000000000..95b62e5c5fcdbac20486d767ba21699aed56bcc6 --- /dev/null +++ b/plugins/definitions/definitions/definitions_provider.py @@ -0,0 +1,101 @@ +import os + +from .utils import RESULT_FORMAT +from .utils import define as _define + + +def translate(definitons_data: RESULT_FORMAT): + audio_region_field = "UK_audio_links" + word_list = [] + + for word, pos_lists in definitons_data.items(): + for pos_data in pos_lists: + pos = pos_data["POS"] + pos_fields = pos_data["data"] + + for ( + definition, + definition_translation, + examples, + domain, + level, + region, + usage, + image, + alt_terms, + irreg_forms, + region_audio_links, + ) in zip( + pos_fields["definitions"], + pos_fields["definitions_translations"], + pos_fields["examples"], + pos_fields["domains"], + pos_fields["levels"], + pos_fields["regions"], + pos_fields["usages"], + pos_fields["image_links"], + pos_fields["alt_terms"], + pos_fields["irregular_forms"], + pos_fields[audio_region_field], + ): # type: ignore + current_word_dict = { + "word": word.strip(), + "special": irreg_forms + alt_terms, + "definition": f"{definition_translation}\n{definition}" + if definition_translation + else definition, + "examples": examples, + "audio_links": region_audio_links, + "image_links": [image] if image else [], + "tags": { + "domain": domain, + "region": region, + "usage": usage, + "pos": pos, + }, + } + if level: + current_word_dict["tags"]["level"] = level + + word_list.append(current_word_dict) + return word_list + + +def load(): + return + + +def unload(): + return + + +def get_config_description(): + return {} + + +def get_default_config(): + return {} + + +def set_config(new_config: dict) -> dict: + return {} + + +def get(word: str): + batch_size = yield + definitions, error = _define( + word, + ) + cards = translate(definitions) + i = 0 + while i < len(cards): + res_batch = cards[i : i + batch_size] + i += batch_size + batch_size = yield res_batch, error + return + + +if __name__ == "__main__": + a = get("test") + next(a) + qwerwq = a.send(5) diff --git a/plugins/definitions/definitions/utils.py b/plugins/definitions/definitions/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..f98854421a63400b16edd364f4d1b402fa2e0691 --- /dev/null +++ b/plugins/definitions/definitions/utils.py @@ -0,0 +1,674 @@ +import re +from enum import IntEnum, auto +from typing import Literal, Optional, TypedDict + +import bs4 +import requests + +DEFAULT_REQUESTS_HEADERS = { + "User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64)" +} +LINK_PREFIX = "https://dictionary.cambridge.org" + +DEFINITION_T = str +DEFINITION_TRANSLATION_T = str +IMAGE_LINK_T = str +LEVEL_T = str +UK_IPA_T = list[str] +UK_AUDIO_LINKS_T = list[str] +US_IPA_T = list[str] +US_AUDIO_LINKS_T = list[str] +ALT_TERMS_T = list[str] +DOMAINS_T = list[str] +EXAMPLES_T = list[str] +EXAMPLES_TRANSLATIONS_T = list[str] +IRREGULAR_FORMS_T = list[str] +LABELS_AND_CODES_T = list[str] +REGIONS_T = list[str] +USAGES_T = list[str] + +WORD_T = str +POS_T = list[str] + + +class POSFields(TypedDict): + UK_IPA: list[UK_IPA_T] + UK_audio_links: list[UK_AUDIO_LINKS_T] + US_IPA: list[US_IPA_T] + US_audio_links: list[US_AUDIO_LINKS_T] + alt_terms: list[ALT_TERMS_T] + definitions: list[DEFINITION_T] + definitions_translations: list[DEFINITION_TRANSLATION_T] + domains: list[DOMAINS_T] + examples: list[EXAMPLES_T] + examples_translations: list[EXAMPLES_TRANSLATIONS_T] + image_links: list[IMAGE_LINK_T] + irregular_forms: list[IRREGULAR_FORMS_T] + labels_and_codes: list[LABELS_AND_CODES_T] + levels: list[LEVEL_T] + regions: list[REGIONS_T] + usages: list[USAGES_T] + + +class POSData(TypedDict): + POS: POS_T + data: POSFields + + +RESULT_FORMAT = dict[WORD_T, list[POSData]] + + +BilingualVariations = Literal[ + "", + "dutch", + "french", + "german", + "indonesian", + "italian", + "japanese", + "norwegian", + "polish", + "portuguese", + "spanish", + "arabic", + "catalan", + "chinese-simplified", + "chinese-traditional", + "czech", + "danish", + "korean", + "malay", + "russian", + "thai", + "turkish", + "ukrainian", + "vietnamese", +] + + +class DictionaryVariation(IntEnum): + English = 0 + American = auto() + Business = auto() + + +def get_tags( + tags_section: Optional[bs4.Tag], +) -> tuple[LEVEL_T, LABELS_AND_CODES_T, REGIONS_T, USAGES_T, DOMAINS_T]: + def find_tag(html_tag: str, params: dict) -> str: + nonlocal tags_section + + if tags_section is None: + return "" + + found_tag = tags_section.find(html_tag, params) + if found_tag is None: + return "" + + tag_grandparent = found_tag.parent.parent.get("class") + # var - var dvar; group - inf-group dinfg + if not any("var" in x or "group" in x for x in tag_grandparent): + tag_text = found_tag.text + return tag_text + return "" + + def find_all_tags(html_tag: str, params: dict) -> list[str]: + nonlocal tags_section + + tags: list[str] = [] + if tags_section is None: + return tags + + found_tags = tags_section.find_all(html_tag, params) + for tag in found_tags: + tag_grandparent = tag.parent.parent.get("class") + # var - var dvar; group - inf-group dinfg + if not any("var" in x or "group" in x for x in tag_grandparent): + tag_text = tag.text.strip() + if tag_text: + tags.append(tag_text) + return tags + + level = find_tag("span", {"class": "epp-xref"}) + labels_and_codes = find_all_tags("span", {"class": "gram dgram"}) + region = find_all_tags("span", {"class": "region dregion"}) + usage = find_all_tags("span", {"class": "usage dusage"}) + domain = find_all_tags("span", {"class": "domain ddomain"}) + return level, labels_and_codes, region, usage, domain + + +def get_phonetics( + header_block: Optional[bs4.Tag], dictionary_index: DictionaryVariation +) -> tuple[UK_IPA_T, US_IPA_T, UK_AUDIO_LINKS_T, US_AUDIO_LINKS_T]: + uk_ipa: UK_IPA_T = [] + us_ipa: US_IPA_T = [] + uk_audio_links: UK_AUDIO_LINKS_T = [] + us_audio_links: US_AUDIO_LINKS_T = [] + if header_block is None: + return uk_ipa, us_ipa, uk_audio_links, us_audio_links + + audio_block = header_block.find_all("span", {"class": "daud"}) + for daud in audio_block: + parent_class = [item.lower() for item in daud.parent.get("class")] + audio_source = daud.find("source") + if audio_source is None: + continue + audio_source_link = audio_source.get("src") + if not audio_source_link: # None or empty + continue + + result_audio_link = f"{LINK_PREFIX}/{audio_source_link}" + if "uk" in parent_class: + uk_audio_links.append(result_audio_link) + elif "us" in parent_class: + us_audio_links.append(result_audio_link) + + if dictionary_index == DictionaryVariation.English: + ipa = header_block.find_all("span", {"class": "pron dpron"}) + + prev_ipa_parrent: str = "" + for child in ipa: + ipa_parent = child.parent.get("class") + + if ipa_parent is None: + ipa_parent = prev_ipa_parrent + else: + prev_ipa_parrent = ipa_parent + + if "uk" in ipa_parent: + uk_ipa.append(child.text) + elif "us" in ipa_parent: + us_ipa.append(child.text) + else: + # Cambridge has different ways of adding IPA to american and english dictionaries + uk_ipa = [] + us_ipa_block = header_block.find("span", {"class": "pron dpron"}) + us_ipa = [us_ipa_block.text] if us_ipa_block is not None else [] + return uk_ipa, us_ipa, uk_audio_links, us_audio_links + + +def concatenate_tags( + tag_section: Optional[bs4.Tag], + global_level: LEVEL_T, + global_labels_and_codes: LABELS_AND_CODES_T, + global_region: REGIONS_T, + global_usage: USAGES_T, + global_domain: DOMAINS_T, +) -> tuple[LEVEL_T, LABELS_AND_CODES_T, REGIONS_T, USAGES_T, DOMAINS_T]: + level, labels_and_codes, region, usage, domain = get_tags(tag_section) + + result_level = level if level else global_level + result_labels_and_codes = global_labels_and_codes + labels_and_codes + result_word_region = global_region + region + result_word_usage = global_usage + usage + result_word_domain = global_domain + domain + return ( + result_level, + result_labels_and_codes, + result_word_region, + result_word_usage, + result_word_domain, + ) + + +BLANKS_REMOVING_PATTERN = re.compile(r"(\s{2,})|(\r\n|\r|\n)+") + + +def update_word_dict( + word_dict: RESULT_FORMAT, + word: Optional[WORD_T] = None, + pos: Optional[POS_T] = None, + definition: Optional[DEFINITION_T] = None, + definition_translation: Optional[DEFINITION_TRANSLATION_T] = None, + alt_terms: Optional[ALT_TERMS_T] = None, + irregular_forms: Optional[IRREGULAR_FORMS_T] = None, + examples: Optional[EXAMPLES_T] = None, + examples_translations: Optional[EXAMPLES_TRANSLATIONS_T] = None, + level: Optional[LEVEL_T] = None, + labels_and_codes: Optional[LABELS_AND_CODES_T] = None, + regions: Optional[REGIONS_T] = None, + usages: Optional[USAGES_T] = None, + domains: Optional[DOMAINS_T] = None, + image_link: Optional[IMAGE_LINK_T] = None, + uk_ipa: Optional[UK_IPA_T] = None, + us_ipa: Optional[US_IPA_T] = None, + uk_audio_links: Optional[UK_AUDIO_LINKS_T] = None, + us_audio_links: Optional[US_AUDIO_LINKS_T] = None, +): + def remove_blanks_from_str(src: str) -> str: + return re.sub(BLANKS_REMOVING_PATTERN, " ", src.strip()) + + def remove_blanks_from_list(src: list[str]) -> list[str]: + return [remove_blanks_from_str(item) for item in src] + + word = remove_blanks_from_str(word) if word is not None else "" + pos = remove_blanks_from_list(pos) if pos is not None else [] + + if word_dict.get(word) is None: + word_dict[word] = [] + + if not word_dict[word] or word_dict[word][-1]["POS"] != pos: + word_dict[word].append( + { + "POS": pos, + "data": { + "definitions": [], + "definitions_translations": [], + "examples": [], + "examples_translations": [], + "UK_IPA": [], + "US_IPA": [], + "UK_audio_links": [], + "US_audio_links": [], + "image_links": [], + "alt_terms": [], + "irregular_forms": [], + "levels": [], + "labels_and_codes": [], + "regions": [], + "usages": [], + "domains": [], + }, + } + ) + + last_appended_data = word_dict[word][-1]["data"] + last_appended_data["definitions"].append( + remove_blanks_from_str(definition.strip(": ")) + if definition is not None + else "" + ) + last_appended_data["definitions_translations"].append( + remove_blanks_from_str(definition_translation) + if definition_translation is not None + else "" + ) + last_appended_data["levels"].append( + remove_blanks_from_str(level) if level is not None else "" + ) + last_appended_data["image_links"].append( + remove_blanks_from_str(image_link) if image_link is not None else "" + ) + last_appended_data["UK_IPA"].append( + remove_blanks_from_list(uk_ipa) if uk_ipa is not None else [] + ) + last_appended_data["US_IPA"].append( + remove_blanks_from_list(us_ipa) if us_ipa is not None else [] + ) + last_appended_data["UK_audio_links"].append( + remove_blanks_from_list(uk_audio_links) + if uk_audio_links is not None + else [] + ) + last_appended_data["US_audio_links"].append( + remove_blanks_from_list(us_audio_links) + if us_audio_links is not None + else [] + ) + last_appended_data["examples"].append( + remove_blanks_from_list(examples) if examples is not None else [] + ) + last_appended_data["examples_translations"].append( + remove_blanks_from_list(examples_translations) + if examples_translations is not None + else [] + ) + last_appended_data["alt_terms"].append( + remove_blanks_from_list(alt_terms) if alt_terms is not None else [] + ) + last_appended_data["irregular_forms"].append( + remove_blanks_from_list(irregular_forms) + if irregular_forms is not None + else [] + ) + last_appended_data["labels_and_codes"].append( + remove_blanks_from_list(labels_and_codes) + if labels_and_codes is not None + else [] + ) + last_appended_data["regions"].append( + remove_blanks_from_list(regions) if regions is not None else [] + ) + last_appended_data["usages"].append( + remove_blanks_from_list(usages) if usages is not None else [] + ) + last_appended_data["domains"].append( + remove_blanks_from_list(domains) if domains is not None else [] + ) + + +def get_irregular_forms( + word_header_block: Optional[bs4.Tag], +) -> IRREGULAR_FORMS_T: + forms: IRREGULAR_FORMS_T = [] + if word_header_block is None: + return forms + + all_irreg_forms_block = word_header_block.find( + "span", {"class": "irreg-infls dinfls"} + ) + if all_irreg_forms_block is None: + return forms + + for irreg_form_block in all_irreg_forms_block: + text = [] + for containing_tag in ( + x for x in irreg_form_block if isinstance(x, bs4.Tag) + ): + tag_class = containing_tag.get("class") + if tag_class is not None and not any( + "dpron" in x for x in tag_class + ): + text.append(containing_tag.text) + if joined_text := " ".join(text): + forms.append(joined_text) + return forms + + +def get_alt_terms(word_header_block: Optional[bs4.Tag]) -> ALT_TERMS_T: + alt_terms: ALT_TERMS_T = [] + if word_header_block is None: + return alt_terms + + var_block = word_header_block.find_all("span", {"class": "var dvar"}) + var_block.extend( + word_header_block.find_all("span", {"class": "spellvar dspellvar"}) + ) + for alt_term in var_block: + alt_terms.append(alt_term.text) + return alt_terms + + +def define( + word: str, + dictionary_index: DictionaryVariation = DictionaryVariation.English, + bilingual_vairation: BilingualVariations = "", + request_headers: Optional[dict] = None, + timeout: float = 5.0, +) -> tuple[RESULT_FORMAT, str]: + """ + dictionary_index: DictionaryVariation + | Ignored if bilingual_vairation != "" + | One of three available english dictionaries: + | * English + | * American + | * Business + | + bilingual_vairation: str + | Type of bilingual dictionary. Empty string specifies monolingual dictionary. + | Note: + | List of available bilingual dictionaries ("BilingualVariations" type) can be easily modified if needed by adding a lowercase "-"-separated name of adding dictionary to it. + | Example: English-Russian bilingual -> russian; English-Chinese (Traditional) -> chinese-traditional + | Available types: + | "dutch" + | "french" + | "german" + | "indonesian" + | "italian" + | "japanese" + | "norwegian" + | "polish" + | "portuguese" + | "spanish" + | "arabic" + | "catalan" + | "chinese-simplified" + | "chinese-traditional" + | "czech" + | "danish" + | "korean" + | "malay" + | "russian" + | "thai" + | "turkish" + | "ukrainian" + | "vietnamese" + """ + if request_headers is None: + request_headers = DEFAULT_REQUESTS_HEADERS + + if bilingual_vairation: + link = f"{LINK_PREFIX}/dictionary/english-{bilingual_vairation}/{word}" + dictionary_index = DictionaryVariation.English + else: + link = f"{LINK_PREFIX}/dictionary/english/{word}" + # will raise error if request_headers are None + try: + page = requests.get(link, headers=request_headers, timeout=timeout) + except: + return {}, "Timeout" + + word_info: RESULT_FORMAT = {} + + soup = bs4.BeautifulSoup(page.content, "html.parser") + # Only english dictionary + # word block which contains definitions for every POS_T. + primal_block = soup.find_all("div", {"class": "pr di superentry"}) + if len(primal_block) <= dictionary_index: + return {}, "" + + main_block = primal_block[dictionary_index].find_all( + "div", {"class": "pr entry-body__el"} + ) + main_block.extend( + primal_block[dictionary_index].find_all("div", {"class": "pv-block"}) + ) + main_block.extend( + primal_block[dictionary_index].find_all( + "div", {"class": "pr idiom-block"} + ) + ) + + for entity in main_block: + header_block = entity.find("span", {"class": "di-info"}) + if header_block is None: + header_block = entity.find("div", {"class": "pos-header dpos-h"}) + pos_alt_terms_list = get_alt_terms(header_block) + pos_irregular_forms_list = get_irregular_forms(header_block) + + parsed_word_block = entity.find("h2", {"class": "headword"}) + if parsed_word_block is None: + parsed_word_block = ( + header_block.find("span", {"class": "hw dhw"}) + if header_block is not None + else None + ) + header_word = ( + parsed_word_block.text if parsed_word_block is not None else "" + ) + + pos_block = ( + header_block.find_all("span", {"class": "pos dpos"}) + if header_block is not None + else [] + ) + pos = [] + i = 0 + while i < len(pos_block): + i_pos = pos_block[i].text + pos.append(i_pos) + if ( + i_pos == "phrasal verb" + ): # after "phrasal verb" goes verb that was + i += 1 # used in a construction of this phrasal verb. We skip it. + i += 1 + uk_ipa, us_ipa, uk_audio_links, us_audio_links = get_phonetics( + header_block, dictionary_index + ) + + # data gathered from the word header + ( + pos_level, + pos_labels_and_codes, + pos_regions, + pos_usages, + pos_domains, + ) = get_tags(header_block) + + for def_and_sent_block in entity.find_all( + "div", {"class": "def-block ddef_block"} + ): + definition: DEFINITION_T = "" + alt_terms: ALT_TERMS_T = [] + irregular_forms: IRREGULAR_FORMS_T = [] + current_word_level: LEVEL_T = "" + current_word_labels_and_codes: LABELS_AND_CODES_T = [] + current_word_regions: REGIONS_T = [] + current_word_usages: USAGES_T = [] + current_word_domains: DOMAINS_T = [] + + current_def_block_word = header_word + + image_section = def_and_sent_block.find("div", {"class": "dimg"}) + image_link = "" + if image_section is not None: + image_link_block = image_section.find("amp-img") + if image_link_block is not None: + image_link = LINK_PREFIX + image_link_block.get("src", "") + + # sentence examples + sentences_and_translation_block = def_and_sent_block.find( + "div", {"class": "def-body ddef_b"} + ) + definition_translation = "" + sentence_blocks = [] + if sentences_and_translation_block is not None: + definition_translation_block = ( + sentences_and_translation_block.find( + lambda tag: tag.name == "span" + and any( + class_attr == "trans" + for class_attr in tag.attrs.get("class", [""]) + ) + ) + ) + definition_translation = ( + definition_translation_block.text + if definition_translation_block is not None + else "" + ) + sentence_blocks = sentences_and_translation_block.find_all( + "div", {"class": "examp dexamp"} + ) + + examples = [] + examples_translations = [] + for item in sentence_blocks: + sent_ex = item.find("span", {"class": "eg deg"}) + sent_translation = item.find( + "span", {"class": "trans dtrans dtrans-se hdb break-cj"} + ) + examples.append(sent_ex.text if sent_ex is not None else "") + examples_translations.append( + sent_translation.text + if sent_translation is not None + else "" + ) + + found_definition_block = def_and_sent_block.find( + "div", {"class": "ddef_h"} + ) + + if found_definition_block is not None: + found_definition_string = found_definition_block.find( + "div", {"class": "def ddef_d db"} + ) + definition = ( + "" + if found_definition_string is None + else found_definition_string.text + ) + + # Gathering specific tags for every word usage + tag_section = found_definition_block.find( + "span", {"class": "def-info ddef-info"} + ) + ( + current_word_level, + current_word_labels_and_codes, + current_word_regions, + current_word_usages, + current_word_domains, + ) = concatenate_tags( + tag_section, + pos_level, + pos_labels_and_codes, + pos_regions, + pos_usages, + pos_domains, + ) + + alt_terms = get_alt_terms(found_definition_block) + irregular_forms = get_irregular_forms(found_definition_block) + + # Phrase-block check + # The reason for this is that on website there are two different tags for phrase-blocks + phrase_block = found_definition_block.find_parent( + "div", {"class": "pr phrase-block dphrase-block"} + ) + if phrase_block is None: + phrase_block = found_definition_block.find_parent( + "div", {"class": "pr phrase-block dphrase-block lmb-25"} + ) + if phrase_block is not None: + phrase_tags_section = phrase_block.find( + "span", {"class": "phrase-info dphrase-info"} + ) + if phrase_tags_section is not None: + alt_terms += get_alt_terms(phrase_tags_section) + irregular_forms += get_irregular_forms( + phrase_tags_section + ) + + ( + current_word_level, + current_word_labels_and_codes, + current_word_regions, + current_word_usages, + current_word_domains, + ) = concatenate_tags( + phrase_tags_section, + current_word_level, + current_word_labels_and_codes, + current_word_regions, + current_word_usages, + current_word_domains, + ) + current_def_block_word = phrase_block.find( + "span", {"class": "phrase-title dphrase-title"} + ).text + + update_word_dict( + word_info, + word=current_def_block_word, + pos=pos, + definition_translation=definition_translation, + definition=definition, + alt_terms=pos_alt_terms_list + alt_terms, + irregular_forms=irregular_forms + pos_irregular_forms_list, + examples=examples, + examples_translations=examples_translations, + level=current_word_level, + labels_and_codes=current_word_labels_and_codes, + regions=current_word_regions, + usages=current_word_usages, + domains=current_word_domains, + image_link=image_link, + uk_ipa=uk_ipa, + us_ipa=us_ipa, + uk_audio_links=uk_audio_links, + us_audio_links=us_audio_links, + ) + return word_info, "" + + +if __name__ == "__main__": + from pprint import pprint + + pprint( + define( + word="endowing", + dictionary_index=DictionaryVariation.English, + bilingual_vairation="", + ) + ) diff --git a/plugins/format_processors/processor/__init__.py b/plugins/format_processors/processor/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..fc04d33498df17ae6a31ea6481c67d5ce647a7b3 --- /dev/null +++ b/plugins/format_processors/processor/__init__.py @@ -0,0 +1 @@ +from .format_processor import * diff --git a/plugins/format_processors/processor/format_processor.py b/plugins/format_processors/processor/format_processor.py new file mode 100644 index 0000000000000000000000000000000000000000..82fc2e72220225842a3b8182a41c5954ecc27a73 --- /dev/null +++ b/plugins/format_processors/processor/format_processor.py @@ -0,0 +1,22 @@ +def get(): + return "" + + +def load(): + return + + +def unload(): + return + + +def get_config_description(): + return {} + + +def get_default_config(): + return {} + + +def set_config(new_config: dict) -> dict: + return {} diff --git a/plugins/images/images/__init__.py b/plugins/images/images/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..82b9edbb6af72bf84645f01490cdb8c5fe7326cd --- /dev/null +++ b/plugins/images/images/__init__.py @@ -0,0 +1 @@ +from .images_provider import * diff --git a/plugins/images/images/images_provider.py b/plugins/images/images/images_provider.py new file mode 100644 index 0000000000000000000000000000000000000000..f30187a4ee062d692d4047b3aaa14cb92f27929d --- /dev/null +++ b/plugins/images/images/images_provider.py @@ -0,0 +1,22 @@ +def get(): + return [], "" + + +def load(): + return + + +def unload(): + return + + +def get_config_description(): + return {} + + +def get_default_config(): + return {} + + +def set_config(new_config: dict) -> dict: + return {} diff --git a/plugins/sentences/sentences/__init__.py b/plugins/sentences/sentences/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..f13fae8d489281699931980440a24d34ddf93d52 --- /dev/null +++ b/plugins/sentences/sentences/__init__.py @@ -0,0 +1 @@ +from .sentences_provider import * diff --git a/plugins/sentences/sentences/sentences_provider.py b/plugins/sentences/sentences/sentences_provider.py new file mode 100644 index 0000000000000000000000000000000000000000..f2eedee4dca8eda9fd9b113721b91694bdc0b2da --- /dev/null +++ b/plugins/sentences/sentences/sentences_provider.py @@ -0,0 +1,22 @@ +def get(): + return [], "" + + +def load(): + return + + +def unload(): + return + + +def get_config_description(): + return {} + + +def get_default_config(): + return {} + + +def set_config(new_config: dict) -> dict: + return {} diff --git a/python_tests/CMakeLists.txt b/python_tests/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..a0ff604fd813b1eb6192df7b6b64b729e8239a61 --- /dev/null +++ b/python_tests/CMakeLists.txt @@ -0,0 +1,19 @@ +add_executable(python_loading main.cpp) + +find_package(Boost COMPONENTS filesystem system date_time python REQUIRED) +message("Include dirs of boost: " ${Boost_INCLUDE_DIRS}) +message("Libs of boost: " ${Boost_LIBRARIES}) + +find_package(PythonLibs REQUIRED) +message("Include dirs of Python: " ${PYTHON_INCLUDE_DIRS}) +message("Libs of Python: " ${PYTHON_LIBRARIES}) + +include_directories( + ${Boost_INCLUDE_DIRS} + ${PYTHON_INCLUDE_DIRS} +) + +target_link_libraries(python_loading + ${Boost_LIBRARIES} + ${PYTHON_LIBRARIES} +) diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..862665cc83f5dadca828cc8a500e54850248d617 --- /dev/null +++ b/server/CMakeLists.txt @@ -0,0 +1,17 @@ +find_package(nlohmann_json 3.7.0 REQUIRED) + +find_package(Boost COMPONENTS filesystem system date_time python REQUIRED) +message("Include dirs of boost: " ${Boost_INCLUDE_DIRS}) +message("Libs of boost: " ${Boost_LIBRARIES}) + +find_package(PythonLibs REQUIRED) +message("Include dirs of Python: " ${PYTHON_INCLUDE_DIRS}) +message("Libs of Python: " ${PYTHON_LIBRARIES}) + +add_subdirectory(lib) + +add_executable(server_main main.cpp) +target_link_libraries(server_main + plugin_server + plugins_provider +) diff --git a/server/lib/CMakeLists.txt b/server/lib/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..1088125a7177bcf0fc81d8ded49938f62b0bdfc4 --- /dev/null +++ b/server/lib/CMakeLists.txt @@ -0,0 +1,2 @@ +add_subdirectory(plugins) +add_subdirectory(network) diff --git a/server/lib/network/CMakeLists.txt b/server/lib/network/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..59fd31112bde34a3534ecf6d46d380f1d4c3c237 --- /dev/null +++ b/server/lib/network/CMakeLists.txt @@ -0,0 +1,3 @@ +add_subdirectory(ResponseGenerators) +add_subdirectory(Session) +add_subdirectory(Server) diff --git a/server/lib/network/ResponseGenerators/CMakeLists.txt b/server/lib/network/ResponseGenerators/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..a8651e90746af8eda77a3a5135fa608140133191 --- /dev/null +++ b/server/lib/network/ResponseGenerators/CMakeLists.txt @@ -0,0 +1,11 @@ +message(${CMAKE_CURRENT_SOURCE_DIR}) + +add_library(response_generators ResponseGenerators.cpp) +target_include_directories(response_generators + PUBLIC + ./ +) +target_link_libraries(response_generators + plugins_bundle + plugins_provider +) diff --git a/server/lib/network/ResponseGenerators/ResponseGenerators.cpp b/server/lib/network/ResponseGenerators/ResponseGenerators.cpp new file mode 100644 index 0000000000000000000000000000000000000000..97c37ed5649aa805f48a8561a23e12ebe92074be --- /dev/null +++ b/server/lib/network/ResponseGenerators/ResponseGenerators.cpp @@ -0,0 +1,326 @@ +#include "ResponseGenerators.hpp" +#include "AudiosProviderWrapper.hpp" +#include "DefinitionsProviderWrapper.hpp" +#include "FormatProcessorWrapper.hpp" +#include "ImagesProviderWrapper.hpp" +#include "PyExceptionInfo.hpp" +#include "SentencesProviderWrapper.hpp" +#include "spdlog/common.h" + +#include +#include +#include +#include +#include +#include + +using nlohmann::json; +using std::string_literals::operator""s; + +ResponseGenerator::ResponseGenerator( + std::shared_ptr plugins_provider) + : plugins_provider_(std::move(plugins_provider)) { +} + +static auto return_error(const std::string &message) -> json { + json dst; + dst["status"] = 1; + dst["message"] = message; + return dst; +} + +auto ResponseGenerator::handle(const std::string &request) -> json { + json parsed_request; + try { + parsed_request = json::parse(request); + } catch (json::exception &e) { + return return_error(e.what()); + } + if (!parsed_request.is_object()) { + return return_error("Got non-json object as a request"); + } + if (!parsed_request.contains("query_type")) { + return return_error( + "Every server request has to have \"query_type\" field"); + } + json json_query_type = parsed_request[QUERY_TYPE_FIELD]; + if (!json_query_type.is_string()) { + return return_error("\""s + QUERY_TYPE_FIELD + + "\" field is has to be a string"); + } + std::string query_type = json_query_type; + if (query_type == INIT_QUERY_TYPE) { + return handle_init(parsed_request); + } + if (query_type == GET_DEFAULT_CONFIG_QUERY_TYPE) { + return handle_get_default_config(parsed_request); + } + if (query_type == GET_CONFIG_SCHEME_QUERY_TYPE) { + return handle_get_config_scheme(parsed_request); + } + if (query_type == SET_CONFIG_QUERY_TYPE) { + return handle_set_config(parsed_request); + } + if (query_type == LIST_PLUGINS_QUERY_TYPE) { + return handle_list_plugins(parsed_request); + } + if (query_type == LOAD_NEW_PLUGINS_QUERY_TYPE) { + return handle_load_new_plugins(parsed_request); + } + if (query_type == GET_QUERY_TYPE) { + return handle_get(parsed_request); + } + if (query_type == GET_DICT_SCHEME_QUERY_TYPE) { + return handle_get_dict_scheme(parsed_request); + } + return return_error("Unknown query type: "s + query_type); +} + +auto ResponseGenerator::handle_init(const nlohmann::json &request) + -> nlohmann::json { + if (!request.contains(PLUGIN_TYPE_FIELD)) { + return return_error("\""s + PLUGIN_TYPE_FIELD + + "\" filed was not found in request"); + } + if (!request[PLUGIN_TYPE_FIELD].is_string()) { + return return_error("\""s + PLUGIN_TYPE_FIELD + + "\" field is expected to be a string"); + } + auto plugin_type = request[PLUGIN_TYPE_FIELD].get(); + + if (!request.contains(PLUGIN_NAME_FIELD)) { + return return_error("\""s + PLUGIN_NAME_FIELD + + "\" filed was not found in request"); + } + if (!request[PLUGIN_NAME_FIELD].is_string()) { + return return_error("\""s + PLUGIN_NAME_FIELD + + "\" field is expected to be a string"); + } + auto plugin_name = request[PLUGIN_NAME_FIELD].get(); + + if (plugin_type == DEFINITION_PROVIDER_PLUGIN_TYPE) { + auto requested_wrapper_option = + plugins_provider_->get_definitions_provider(plugin_name); + if (!requested_wrapper_option.has_value()) { + return return_error("\"" + plugin_name + + "\" definition provider not found"); + } + auto &wrapper_variant = requested_wrapper_option.value(); + if (std::holds_alternative(wrapper_variant)) { + auto exception_info = std::get(wrapper_variant); + + return return_error("Exception was thrown during \"" + plugin_name + + "\" definitions provider's construction:\n" + + exception_info.error_summary() + '\n' + + exception_info.stack_trace()); + } + auto wrapper = + std::move(std::get(wrapper_variant)); + plugins_bundle_.set_definitions_provider(std::move(wrapper)); + return R"({"status": 0, "message": ""})"_json; + } + if (plugin_type == SENTENCES_PROVIDER_PLUGIN_TYPE) { + auto requested_wrapper_option = + plugins_provider_->get_sentences_provider(plugin_name); + if (!requested_wrapper_option.has_value()) { + return return_error("\"" + plugin_name + + "\" sentences provider not found"); + } + auto &wrapper_variant = requested_wrapper_option.value(); + if (std::holds_alternative(wrapper_variant)) { + auto exception_info = std::get(wrapper_variant); + + return return_error("Exception was thrown during \"" + plugin_name + + "\" sentence provider's construction:\n" + + exception_info.error_summary() + '\n' + + exception_info.stack_trace()); + } + auto &wrapper = std::get(wrapper_variant); + plugins_bundle_.set_sentences_provider(std::move(wrapper)); + return R"({"status": 0, "message": ""})"_json; + } + if (plugin_type == IMAGES_PROVIDER_PLUGIN_TYPE) { + auto requested_wrapper_option = + plugins_provider_->get_images_provider(plugin_name); + if (!requested_wrapper_option.has_value()) { + return return_error("\"" + plugin_name + + "\" images provider not found"); + } + auto &wrapper_variant = requested_wrapper_option.value(); + if (std::holds_alternative(wrapper_variant)) { + auto exception_info = std::get(wrapper_variant); + + return return_error("Exception was thrown during \"" + plugin_name + + "\" images provider's construction:\n" + + exception_info.error_summary() + '\n' + + exception_info.stack_trace()); + } + auto &wrapper = std::get(wrapper_variant); + plugins_bundle_.set_images_provider(std::move(wrapper)); + return R"({"status": 0, "message": ""})"_json; + } + if (plugin_type == AUDIOS_PROVIDER_PLUGIN_TYPE) { + auto requested_wrapper_option = + plugins_provider_->get_audios_provider(plugin_name); + if (!requested_wrapper_option.has_value()) { + return return_error("\"" + plugin_name + + "\" audios provider not found"); + } + auto &wrapper_variant = requested_wrapper_option.value(); + if (std::holds_alternative(wrapper_variant)) { + auto exception_info = std::get(wrapper_variant); + + return return_error("Exception was thrown during \"" + plugin_name + + "\" audios provider's construction:\n" + + exception_info.error_summary() + '\n' + + exception_info.stack_trace()); + } + auto &wrapper = std::get(wrapper_variant); + plugins_bundle_.set_audios_provider(std::move(wrapper)); + return R"({"status": 0, "message": ""})"_json; + } + if (plugin_type == FORMAT_PROCESSOR_PLUGIN_TYPE) { + auto requested_wrapper_option = + plugins_provider_->get_format_processor(plugin_name); + if (!requested_wrapper_option.has_value()) { + return return_error("\"" + plugin_name + + "\" format processor not found"); + } + auto &wrapper_variant = requested_wrapper_option.value(); + if (std::holds_alternative(wrapper_variant)) { + auto exception_info = std::get(wrapper_variant); + + return return_error("Exception was thrown during \"" + plugin_name + + "\" format processor's construction:\n" + + exception_info.error_summary() + '\n' + + exception_info.stack_trace()); + } + auto &wrapper = std::get(wrapper_variant); + plugins_bundle_.set_format_processor(std::move(wrapper)); + return R"({"status": 0, "message": ""})"_json; + } + return return_error("Unknown plugin_type: " + plugin_type); +} + +auto ResponseGenerator::handle_get_default_config(const nlohmann::json &request) + -> nlohmann::json { + spdlog::throw_spdlog_ex("handle_get_default_config() is not implemented"); + return {}; +} + +auto ResponseGenerator::handle_get_config_scheme(const nlohmann::json &request) + -> nlohmann::json { + spdlog::throw_spdlog_ex("handle_get_config_scheme() is not implemented"); + return {}; +} + +auto ResponseGenerator::handle_set_config(const nlohmann::json &request) + -> nlohmann::json { + spdlog::throw_spdlog_ex("handle_set_config() is not implemented"); + return {}; +} + +auto ResponseGenerator::handle_list_plugins(const nlohmann::json &request) + -> nlohmann::json { + spdlog::throw_spdlog_ex("handle_list_plugins() is not implemented"); + return {}; +} + +auto ResponseGenerator::handle_load_new_plugins(const nlohmann::json &request) + -> nlohmann::json { + spdlog::throw_spdlog_ex("handle_load_new_plugins() is not implemented"); + return {}; +} + +auto ResponseGenerator::handle_get(const nlohmann::json &request) + -> nlohmann::json { + if (!request.contains(PLUGIN_TYPE_FIELD)) { + return return_error("\""s + PLUGIN_TYPE_FIELD + + "\" filed was not found in request"); + } + if (!request[PLUGIN_TYPE_FIELD].is_string()) { + return return_error("\""s + PLUGIN_TYPE_FIELD + + "\" field is expected to be a string"); + } + auto plugin_type = request[PLUGIN_TYPE_FIELD].get(); + + if (plugin_type == DEFINITION_PROVIDER_PLUGIN_TYPE) { + auto &provider_opt = plugins_bundle_.definitions_provider(); + if (!provider_opt.has_value()) { + return return_error("Cannot return anything because definitions " + "provider is not initialized"); + } + auto &provider = provider_opt.value(); + + if (!request.contains(WORD_FIELD)) { + return return_error("\""s + WORD_FIELD + + "\" filed was not found in request"); + } + if (!request[WORD_FIELD].is_string()) { + return return_error("\""s + WORD_FIELD + + "\" field is expected to be a string"); + } + auto word = request[WORD_FIELD].get(); + + if (!request.contains(FILTER_QUERY_FIELD)) { + return return_error("\""s + FILTER_QUERY_FIELD + + "\" fieled was not found in request"); + } + if (!request[FILTER_QUERY_FIELD].is_string()) { + return return_error("\""s + FILTER_QUERY_FIELD + + "\" field is expected to be a string"); + } + auto filter_query = request[FILTER_QUERY_FIELD].get(); + + if (!request.contains(BATCH_SIZE_FIELD)) { + return return_error("\""s + BATCH_SIZE_FIELD + + "\" filed was not found in request"); + } + if (!request[BATCH_SIZE_FIELD].is_number()) { + return return_error("\""s + BATCH_SIZE_FIELD + + "\" field is expected to be a number"); + } + auto batch_size = request[BATCH_SIZE_FIELD].get(); + + if (!request.contains(RESTART_FIELD)) { + return return_error("\""s + RESTART_FIELD + + "\" filed was not found in request"); + } + if (!request[RESTART_FIELD].is_boolean()) { + return return_error("\""s + RESTART_FIELD + + "\" field is expected to be a boolean"); + } + auto restart = request[RESTART_FIELD].get(); + + auto result_or_error = + provider.get(word, filter_query, batch_size, restart); + if (std::holds_alternative(result_or_error)) { + auto exception_info = std::get(result_or_error); + + return return_error("Exception was thrown during \"" + + plugins_bundle_.definitions_provider()->name() + + "\" definitions request:\n" + + exception_info.error_summary() + '\n' + + exception_info.stack_trace()); + } + auto result = + std::get(result_or_error); + return result; + } else if (plugin_type == SENTENCES_PROVIDER_PLUGIN_TYPE) { + return return_error( + "Requests to sentences provider is not implemented"); + } else if (plugin_type == IMAGES_PROVIDER_PLUGIN_TYPE) { + return return_error("Requests to images provider is not implemented"); + } else if (plugin_type == AUDIOS_PROVIDER_PLUGIN_TYPE) { + return return_error("Requests to audios provider is not implemented"); + } else if (plugin_type == FORMAT_PROCESSOR_PLUGIN_TYPE) { + return return_error("Requests to format processor is not implemented"); + } + return return_error("Unknown plugin type: "s + plugin_type); +} + +auto ResponseGenerator::handle_get_dict_scheme(const nlohmann::json &request) + -> nlohmann::json { + spdlog::throw_spdlog_ex("handle_get_dict_scheme() is not implemented"); + return {}; +} diff --git a/server/lib/network/ResponseGenerators/ResponseGenerators.hpp b/server/lib/network/ResponseGenerators/ResponseGenerators.hpp new file mode 100644 index 0000000000000000000000000000000000000000..d17cf6291306b49fc4a50cfb1d6f96ea184d5ded --- /dev/null +++ b/server/lib/network/ResponseGenerators/ResponseGenerators.hpp @@ -0,0 +1,84 @@ +#ifndef RESPONSE_GENERATORS_H +#define RESPONSE_GENERATORS_H + +#include "PluginsBundle.hpp" +#include "PluginsProvider.hpp" +#include +#include +#include + +class IResponceGenerator { + public: + virtual ~IResponceGenerator() = default; + virtual auto handle(const std::string &request) -> nlohmann::json = 0; + + private: + virtual auto handle_init(const nlohmann::json &) -> nlohmann::json = 0; + virtual auto handle_get_default_config(const nlohmann::json &) + -> nlohmann::json = 0; + virtual auto handle_get_config_scheme(const nlohmann::json &) + -> nlohmann::json = 0; + virtual auto handle_set_config(const nlohmann::json &) + -> nlohmann::json = 0; + virtual auto handle_list_plugins(const nlohmann::json &) + -> nlohmann::json = 0; + virtual auto handle_load_new_plugins(const nlohmann::json &) + -> nlohmann::json = 0; + virtual auto handle_get(const nlohmann::json &) -> nlohmann::json = 0; + virtual auto handle_get_dict_scheme(const nlohmann::json &) + -> nlohmann::json = 0; +}; + +class ResponseGenerator : public IResponceGenerator { + public: + explicit ResponseGenerator( + std::shared_ptr plugins_provider); + + auto handle(const std::string &request) -> nlohmann::json override; + + private: + static constexpr auto QUERY_TYPE_FIELD = "query_type"; + static constexpr auto PLUGIN_TYPE_FIELD = "plugin_type"; + static constexpr auto CONFIG_FIELD = "config"; + static constexpr auto PLUGIN_NAME_FIELD = "plugin_name"; + static constexpr auto FILTER_QUERY_FIELD = "filter"; + static constexpr auto WORD_FIELD = "word"; + static constexpr auto BATCH_SIZE_FIELD = "batch_size"; + static constexpr auto RESTART_FIELD = "restart"; + + static constexpr auto DEFINITION_PROVIDER_PLUGIN_TYPE = "word"; + static constexpr auto SENTENCES_PROVIDER_PLUGIN_TYPE = "sentences"; + static constexpr auto AUDIOS_PROVIDER_PLUGIN_TYPE = "audios"; + static constexpr auto IMAGES_PROVIDER_PLUGIN_TYPE = "images"; + static constexpr auto FORMAT_PROCESSOR_PLUGIN_TYPE = "format"; + + static constexpr auto INIT_QUERY_TYPE = "init"; + static constexpr auto LIST_PLUGINS_QUERY_TYPE = "list_plugins"; + static constexpr auto GET_DEFAULT_CONFIG_QUERY_TYPE = "get_default_config"; + static constexpr auto GET_CONFIG_SCHEME_QUERY_TYPE = "get_confit_scheme"; + static constexpr auto SET_CONFIG_QUERY_TYPE = "set_config"; + static constexpr auto LOAD_NEW_PLUGINS_QUERY_TYPE = "load_new_plugins"; + static constexpr auto GET_QUERY_TYPE = "get"; + static constexpr auto GET_DICT_SCHEME_QUERY_TYPE = "get_dict_scheme"; + + auto handle_init(const nlohmann::json &request) -> nlohmann::json override; + auto handle_get_default_config(const nlohmann::json &request) + -> nlohmann::json override; + auto handle_get_config_scheme(const nlohmann::json &request) + -> nlohmann::json override; + auto handle_set_config(const nlohmann::json &request) + -> nlohmann::json override; + auto handle_list_plugins(const nlohmann::json &request) + -> nlohmann::json override; + auto handle_load_new_plugins(const nlohmann::json &request) + -> nlohmann::json override; + auto handle_get(const nlohmann::json &request) -> nlohmann::json override; + auto handle_get_dict_scheme(const nlohmann::json &request) + -> nlohmann::json override; + + private: + PluginsBundle plugins_bundle_; + const std::shared_ptr plugins_provider_; +}; + +#endif // !RESPONSE_GENERATORS_H diff --git a/server/lib/network/Server/CMakeLists.txt b/server/lib/network/Server/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..9ca53d26f190e9595178e744b6afac1045b6b2c0 --- /dev/null +++ b/server/lib/network/Server/CMakeLists.txt @@ -0,0 +1,15 @@ +message(${CMAKE_CURRENT_SOURCE_DIR}) + +add_library(plugin_server Server.cpp) +target_link_libraries(plugin_server + session + ${Boost_LIBRARIES} + ${PYTHON_LIBRARIES} +) +target_include_directories(plugin_server + PUBLIC + ./ + PRIVATE + ${Boost_INCLUDE_DIRS} + ${PYTHON_INCLUDE_DIRS} +) diff --git a/server/lib/network/Server/Server.cpp b/server/lib/network/Server/Server.cpp new file mode 100644 index 0000000000000000000000000000000000000000..30438d3c37e1f41ed6d33801f68ac17d5420512a --- /dev/null +++ b/server/lib/network/Server/Server.cpp @@ -0,0 +1,43 @@ +#include "Server.hpp" +#include "ResponseGenerators.hpp" +#include "Session.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using boost::asio::ip::tcp; +using nlohmann::json; + +PluginServer::PluginServer(std::shared_ptr &&provider, + boost::asio::io_context &context, + uint16_t port) + : io_context_(context), + acceptor_(io_context_, tcp::endpoint(tcp::v4(), port)), + plugins_provider_(std::move(provider)) { + Py_Initialize(); + start_accept(); +} + +void PluginServer::start_accept() { + acceptor_.async_accept([this](boost::system::error_code ec, + tcp::socket socket) { + if (!ec) { + auto test = std::make_unique(plugins_provider_); + std::make_shared(std::move(socket), std::move(test)) + ->start(); + } + start_accept(); + }); +} diff --git a/server/lib/network/Server/Server.hpp b/server/lib/network/Server/Server.hpp new file mode 100644 index 0000000000000000000000000000000000000000..7d7efda7efd26dffc432d8236b6fda991bafa200 --- /dev/null +++ b/server/lib/network/Server/Server.hpp @@ -0,0 +1,42 @@ +#ifndef SERVER_H +#define SERVER_H + +#include "PluginsProvider.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class PluginServer { + public: + explicit PluginServer(std::shared_ptr &&provider, + boost::asio::io_context &context, + uint16_t port); + + ~PluginServer() = default; + PluginServer(const PluginServer &) = delete; + PluginServer(PluginServer &&) = delete; + auto operator=(const PluginServer &) -> PluginServer & = delete; + auto operator=(PluginServer &&) -> PluginServer & = delete; + + private: + boost::asio::io_context &io_context_; + boost::asio::ip::tcp::acceptor acceptor_; + std::shared_ptr plugins_provider_; + + void start_accept(); + + void handle_accept(const boost::system::error_code &error); +}; + +#endif // SERVER_H! diff --git a/server/lib/network/Session/CMakeLists.txt b/server/lib/network/Session/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..ab5aaa5b444b103d4fe1927089ac8f0d0c80d733 --- /dev/null +++ b/server/lib/network/Session/CMakeLists.txt @@ -0,0 +1,10 @@ +message(${CMAKE_CURRENT_SOURCE_DIR}) + +add_library(session Session.cpp) +target_include_directories(session PUBLIC + ./ +) + +target_link_libraries(session + response_generators +) diff --git a/server/lib/network/Session/Session.cpp b/server/lib/network/Session/Session.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7f2023a254b795040c6d5a75982ad707aa60e85f --- /dev/null +++ b/server/lib/network/Session/Session.cpp @@ -0,0 +1,62 @@ +#include "Session.hpp" +#include "spdlog/common.h" +#include "spdlog/spdlog.h" +#include +#include + +using nlohmann::json; + +Session::Session(boost::asio::ip::tcp::socket socket, + std::unique_ptr response_generator) + : socket_(std::move(socket)), + response_generator_(std::move(response_generator)) { +} + +void Session::start() { + spdlog::info("Started new session"); + do_read(); +} + +void Session::do_read() { + // https://stackoverflow.com/questions/3058589/boostasioasync-read-until-reads-all-data-instead-of-just-some + auto self = shared_from_this(); + boost::asio::async_read_until( + socket_, + request_buffer, + "\r\n", + [this, self](boost::system::error_code error_code, std::size_t length) { + if (error_code) { + spdlog::error("Couldn't read from user"); + return; + } + + std::stringstream ss_out; + std::copy(boost::asio::buffers_begin(request_buffer.data()), + boost::asio::buffers_begin(request_buffer.data()) + + length - 2, + std::ostream_iterator(ss_out)); + + // std::istream istrm(&request_buffer); + // std::string result; + // istrm >> result; + std::string result = ss_out.str(); + request_buffer.consume(length); + + json response = response_generator_->handle(result); + auto str_response = response.dump() + "\r\n"; + do_write(str_response); + }); +} + +void Session::do_write(std::string response) { + auto self(shared_from_this()); + boost::asio::async_write( + socket_, + boost::asio::buffer(response, response.length()), + [this, self](boost::system::error_code ec, std::size_t length) { + if (ec) { + spdlog::error("Couldn't respond to user"); + } + do_read(); + }); +} diff --git a/server/lib/network/Session/Session.hpp b/server/lib/network/Session/Session.hpp new file mode 100644 index 0000000000000000000000000000000000000000..5c797c7b2b6422fdd159e9111d1772645c0c895e --- /dev/null +++ b/server/lib/network/Session/Session.hpp @@ -0,0 +1,29 @@ +#ifndef SESSION_H +#define SESSION_H + +#include "ResponseGenerators.hpp" +#include +#include +#include +#include +#include +#include +#include + +class Session : public std::enable_shared_from_this { + public: + explicit Session(boost::asio::ip::tcp::socket socket, + std::unique_ptr response_generator); + + void start(); + + private: + boost::asio::ip::tcp::socket socket_; + boost::asio::streambuf request_buffer; + std::unique_ptr response_generator_; + + void do_read(); + void do_write(std::string response); +}; + +#endif // SESSION_H diff --git a/server/lib/plugins/CMakeLists.txt b/server/lib/plugins/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..adca43bba56a03bfa44db6f7ea8cd3d30f1d4851 --- /dev/null +++ b/server/lib/plugins/CMakeLists.txt @@ -0,0 +1,6 @@ +add_subdirectory(PyExceptionInfo) +add_subdirectory(Container) +add_subdirectory(wrappers) +add_subdirectory(PluginsBundle) +add_subdirectory(PluginsLoader) +add_subdirectory(PluginsProvider) diff --git a/server/lib/plugins/Container/CMakeLists.txt b/server/lib/plugins/Container/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..0cb674fcc00ac45603ee0a7b9adb112a1dee8fad --- /dev/null +++ b/server/lib/plugins/Container/CMakeLists.txt @@ -0,0 +1,15 @@ +message(${CMAKE_CURRENT_SOURCE_DIR}) + +add_library(containers Container.cpp) +target_include_directories(containers + PUBLIC + ./ + ${Boost_INCLUDE_DIRS} + ${PYTHON_INCLUDE_DIRS} +) + +target_link_libraries(containers + py_exception_info + ${Boost_LIBRARIES} + ${PYTHON_LIBRARIES} +) diff --git a/server/lib/plugins/Container/Container.cpp b/server/lib/plugins/Container/Container.cpp new file mode 100644 index 0000000000000000000000000000000000000000..06c99f716c6c7d2995df3598205367f7b56115b8 --- /dev/null +++ b/server/lib/plugins/Container/Container.cpp @@ -0,0 +1,53 @@ +#include "Container.hpp" +#include "spdlog/common.h" +#include "spdlog/spdlog.h" + +// https://stackoverflow.com/questions/1418015/how-to-get-python-exception-text +auto Container::build(boost::python::object &&module) + -> std::variant> { + auto plugin_container = Container(); + try { + plugin_container.plugin_namespace_ = module.attr("__dict__"); + plugin_container.load_ = module.attr("load"); + plugin_container.get_ = module.attr("get"); + plugin_container.get_config_description_ = + module.attr("get_config_description"); + plugin_container.set_config_ = module.attr("set_config"); + plugin_container.get_default_config_ = + module.attr("get_default_config"); + plugin_container.unload_ = module.attr("unload"); + } catch (const boost::python::error_already_set &) { + return PyExceptionInfo::build(plugin_container.plugin_namespace_); + } + return plugin_container; +} + +Container::Container() = default; + +auto Container::plugin_namespace() -> boost::python::object & { + return plugin_namespace_; +} + +auto Container::load() -> boost::python::object & { + return load_; +} + +auto Container::get() -> boost::python::object & { + return get_; +} + +auto Container::get_config_description() -> boost::python::object & { + return get_config_description_; +} + +auto Container::set_config() -> boost::python::object & { + return set_config_; +} + +auto Container::get_default_config() -> boost::python::object & { + return get_default_config_; +} + +auto Container::unload() -> boost::python::object & { + return unload_; +} diff --git a/server/lib/plugins/Container/Container.hpp b/server/lib/plugins/Container/Container.hpp new file mode 100644 index 0000000000000000000000000000000000000000..3cdb49c3d84034c5c51653f594e2eda25dc1cc44 --- /dev/null +++ b/server/lib/plugins/Container/Container.hpp @@ -0,0 +1,40 @@ +#ifndef CONTAINERS_H +#define CONTAINERS_H + +#include "PyExceptionInfo.hpp" +#include +#include +#include +#include +#include + +class Container { + public: + static auto build(boost::python::object &&module) + -> std::variant>; + + Container(const Container &) = default; + Container(Container &&) = default; + Container &operator=(const Container &) = default; + Container &operator=(Container &&) = default; + + [[nodiscard]] auto plugin_namespace() -> boost::python::object &; + [[nodiscard]] auto load() -> boost::python::object &; + [[nodiscard]] auto get() -> boost::python::object &; + [[nodiscard]] auto get_config_description() -> boost::python::object &; + [[nodiscard]] auto set_config() -> boost::python::object &; + [[nodiscard]] auto get_default_config() -> boost::python::object &; + [[nodiscard]] auto unload() -> boost::python::object &; + + private: + Container(); + boost::python::object plugin_namespace_; + boost::python::object load_; + boost::python::object get_; + boost::python::object get_config_description_; + boost::python::object set_config_; + boost::python::object get_default_config_; + boost::python::object unload_; +}; + +#endif // !CONTAINERS_H diff --git a/server/lib/plugins/PluginsBundle/CMakeLists.txt b/server/lib/plugins/PluginsBundle/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..0081d763f52d53548a759caa01a09dd78c6dd563 --- /dev/null +++ b/server/lib/plugins/PluginsBundle/CMakeLists.txt @@ -0,0 +1,11 @@ +message(${CMAKE_CURRENT_SOURCE_DIR}) + +add_library(plugins_bundle PluginsBundle.cpp) +target_link_libraries(plugins_bundle + definitions_provider_wrapper + images_provider_wrapper + sentences_provider_wrapper + audios_provider_wrapper + format_processor_wrapper +) +target_include_directories(plugins_bundle PUBLIC ./) diff --git a/server/lib/plugins/PluginsBundle/PluginsBundle.cpp b/server/lib/plugins/PluginsBundle/PluginsBundle.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c90c400daa65f7731eac21802b28a24fdc389f3b --- /dev/null +++ b/server/lib/plugins/PluginsBundle/PluginsBundle.cpp @@ -0,0 +1,58 @@ +#include "PluginsBundle.hpp" +#include "DefinitionsProviderWrapper.hpp" +#include "PyExceptionInfo.hpp" +#include +#include + +PluginsBundle::PluginsBundle() { +} + +auto PluginsBundle::set_definitions_provider( + DefinitionsProviderWrapper &&new_provider) -> void { + definitions_provider_ = std::move(new_provider); +} + +auto PluginsBundle::set_sentences_provider( + SentencesProviderWrapper &&new_provider) -> void { + sentences_provider_ = std::move(new_provider); +} + +auto PluginsBundle::set_images_provider(ImagesProviderWrapper &&new_provider) + -> void { + images_provider_ = std::move(new_provider); +} + +auto PluginsBundle::set_audios_provider(AudiosProviderWrapper &&new_provider) + -> void { + audios_provider_ = std::move(new_provider); +} + +auto PluginsBundle::set_format_processor(FormatProcessorWrapper &&new_provider) + -> void { + format_processor_ = std::move(new_provider); +} + +auto PluginsBundle::definitions_provider() + -> std::optional & { + return definitions_provider_; +} + +auto PluginsBundle::sentences_provider() + -> std::optional & { + return sentences_provider_; +} + +auto PluginsBundle::images_provider() + -> std::optional & { + return images_provider_; +} + +auto PluginsBundle::audios_provider() + -> std::optional & { + return audios_provider_; +} + +auto PluginsBundle::format_processor() + -> std::optional & { + return format_processor_; +} diff --git a/server/lib/plugins/PluginsBundle/PluginsBundle.hpp b/server/lib/plugins/PluginsBundle/PluginsBundle.hpp new file mode 100644 index 0000000000000000000000000000000000000000..ee60e1d9e50363291a02ccc7f8fd24372032456c --- /dev/null +++ b/server/lib/plugins/PluginsBundle/PluginsBundle.hpp @@ -0,0 +1,52 @@ +#ifndef PLUGINS_BUNDLE_H +#define PLUGINS_BUNDLE_H + +#include "AudiosProviderWrapper.hpp" +#include "DefinitionsProviderWrapper.hpp" +#include "FormatProcessorWrapper.hpp" +#include "IPluginWrapper.hpp" +#include "ImagesProviderWrapper.hpp" +#include "SentencesProviderWrapper.hpp" +#include +#include +#include +#include +#include +#include + +class PluginsBundle { + public: + PluginsBundle(); + + auto set_definitions_provider(DefinitionsProviderWrapper &&new_provider) + -> void; + + auto set_sentences_provider(SentencesProviderWrapper &&new_provider) + -> void; + + auto set_images_provider(ImagesProviderWrapper &&new_provider) -> void; + + auto set_audios_provider(AudiosProviderWrapper &&new_provider) -> void; + + auto set_format_processor(FormatProcessorWrapper &&new_provider) -> void; + + auto definitions_provider() -> std::optional &; + + auto sentences_provider() -> std::optional &; + + auto images_provider() -> std::optional &; + + auto audios_provider() -> std::optional &; + + auto format_processor() -> std::optional &; + + private: + std::optional definitions_provider_ = + std::nullopt; + std::optional sentences_provider_ = std::nullopt; + std::optional images_provider_ = std::nullopt; + std::optional audios_provider_ = std::nullopt; + std::optional format_processor_ = std::nullopt; +}; + +#endif // !PLUGINS_BUNDLE_H diff --git a/server/lib/plugins/PluginsLoader/CMakeLists.txt b/server/lib/plugins/PluginsLoader/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..88123c017a51345648351fb53b8f822ea591cbf5 --- /dev/null +++ b/server/lib/plugins/PluginsLoader/CMakeLists.txt @@ -0,0 +1,21 @@ +message(${CMAKE_CURRENT_SOURCE_DIR}) + +add_library(plugins_loader INTERFACE) +target_include_directories(plugins_loader INTERFACE + ./ + ${Boost_INCLUDE_DIRS} + ${PYTHON_INCLUDE_DIRS} +) + +target_link_libraries(plugins_loader + INTERFACE + base_plugin_wrapper + py_exception_info + # definitions_provider_wrapper + # images_provider_wrapper + # sentences_provider_wrapper + # audios_provider_wrapper + # format_processor_wrapper + ${Boost_LIBRARIES} + ${PYTHON_LIBRARIES} +) diff --git a/server/lib/plugins/PluginsLoader/PluginsLoader.hpp b/server/lib/plugins/PluginsLoader/PluginsLoader.hpp new file mode 100644 index 0000000000000000000000000000000000000000..51b7b1be5cab69a98f2c11e79a90980541cfd092 --- /dev/null +++ b/server/lib/plugins/PluginsLoader/PluginsLoader.hpp @@ -0,0 +1,118 @@ +#ifndef PLUGINS_LOADER_H +#define PLUGINS_LOADER_H + +#include "BasePluginWrapper.hpp" +#include "spdlog/common.h" +#include "spdlog/spdlog.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "PyExceptionInfo.hpp" + +template + requires is_plugin_wrapper +class IPluginsLoader { + public: + virtual ~IPluginsLoader() = default; + + virtual auto get(const std::string &plugin_name) + -> std::optional> = 0; + virtual auto load_new_plugins() -> void = 0; +}; + +// #include "DefinitionsProviderWrapper.hpp" +// using Wrapper = DefinitionsProviderWrapper; + +template + requires is_plugin_wrapper +class PluginsLoader : public IPluginsLoader { + public: + explicit PluginsLoader(std::filesystem::path &&plugins_dir) noexcept( + false) { + try { + boost::python::object sys = boost::python::import("sys"); + sys.attr("path").attr("append")(plugins_dir.c_str()); + } catch (const boost::python::error_already_set &) { + spdlog::throw_spdlog_ex("Couldn't import sys module"); + } + + std::ranges::for_each( + std::filesystem::directory_iterator(plugins_dir), + [this](const std::filesystem::path &dir_entry) { + using std::string_literals::operator""s; + + spdlog::info("Loading plugin from "s + dir_entry.string()); + if (!std::filesystem::is_directory(dir_entry)) { + return; + } + auto module_name = dir_entry.filename(); + // TODO(blackdeer): HANDLE IMPORT ERROR + auto loaded_module = boost::python::import(module_name.c_str()); + auto plugin_container = + Container::build(std::move(loaded_module)); + + if (std::holds_alternative>( + plugin_container)) { + auto info = std::get>( + plugin_container); + spdlog::info("Failed to load plugin from "s + + dir_entry.string()); + failed_containers_.emplace(std::move(module_name), + std::move(info)); + } else if (std::holds_alternative( + plugin_container)) { + auto container = std::get(plugin_container); + spdlog::info("Successfully loaded plugin from "s + + dir_entry.string()); + loaded_containers_.emplace(std::move(module_name), + std::move(container)); + } else { + spdlog::throw_spdlog_ex( + "Unknown return from a container build"); + } + }); + } + + auto get(const std::string &plugin_name) + -> std::optional> override { + using std::string_literals::operator""s; + spdlog::info(plugin_name + " was requested"); + + auto res = loaded_containers_.find(plugin_name); + if (res == loaded_containers_.end()) { + spdlog::info(plugin_name + " not found"); + return std::nullopt; + } + spdlog::info(plugin_name + " was found"); + // Выглядит ОЧЕНЬ плохо. Но что имеем + // Надо как-то сделать чтобы возвращался врапер сразу без ::build + auto wrapper_or_error = Wrapper::build(plugin_name, res->second); + if (std::holds_alternative(wrapper_or_error)) { + auto base_wrapper = + std::move(std::get(wrapper_or_error)); + auto after_cast = Wrapper(std::move(base_wrapper)); + return after_cast; + } + return std::nullopt; + } + + auto load_new_plugins() -> void override { + throw std::runtime_error("load_new_plugins() is not implemented"); + } + + private: + std::unordered_map loaded_containers_; + std::unordered_map> + failed_containers_; +}; + +#endif diff --git a/server/lib/plugins/PluginsProvider/CMakeLists.txt b/server/lib/plugins/PluginsProvider/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..3942ab61f3acc3ca78c65f7baf46823153ad20a0 --- /dev/null +++ b/server/lib/plugins/PluginsProvider/CMakeLists.txt @@ -0,0 +1,15 @@ +message(${CMAKE_CURRENT_SOURCE_DIR}) + +add_library(plugins_provider PluginsProvider.cpp) +target_include_directories(plugins_provider PUBLIC + ./ +) + +target_link_libraries(plugins_provider + plugins_loader + definitions_provider_wrapper + images_provider_wrapper + sentences_provider_wrapper + audios_provider_wrapper + format_processor_wrapper +) diff --git a/server/lib/plugins/PluginsProvider/PluginsProvider.cpp b/server/lib/plugins/PluginsProvider/PluginsProvider.cpp new file mode 100644 index 0000000000000000000000000000000000000000..05438cd6c108bfbb340d4dd1d1692a6e780238eb --- /dev/null +++ b/server/lib/plugins/PluginsProvider/PluginsProvider.cpp @@ -0,0 +1,41 @@ +#include "PluginsProvider.hpp" +#include "pylifecycle.h" +#include "spdlog/common.h" + +PluginsProvider::PluginsProvider(PluginTypesLocationsConfig &&confg) + : definitions_providers_(std::move(confg.definitions_providers_dir)), + sentences_providers_(std::move(confg.sentences_providers_dir)), + images_providers_(std::move(confg.images_providers_dir)), + audios_providers_(std::move(confg.audios_providers_dir)), + format_processors_(std::move(confg.format_processors_dir)) { +} + +auto PluginsProvider::get_definitions_provider(const std::string &name) + -> std::optional< + std::variant> { + return definitions_providers_.get(name); +} + +auto PluginsProvider::get_sentences_provider(const std::string &name) + -> std::optional> { + return sentences_providers_.get(name); +} + +auto PluginsProvider::get_audios_provider(const std::string &name) + -> std::optional> { + return audios_providers_.get(name); +} + +auto PluginsProvider::get_images_provider(const std::string &name) + -> std::optional> { + return images_providers_.get(name); +} + +auto PluginsProvider::get_format_processor(const std::string &name) + -> std::optional> { + return format_processors_.get(name); +} + +auto PluginsProvider::load_new_plugins() -> void { + spdlog::throw_spdlog_ex("load_new_plugins() is not implemented"); +} diff --git a/server/lib/plugins/PluginsProvider/PluginsProvider.hpp b/server/lib/plugins/PluginsProvider/PluginsProvider.hpp new file mode 100644 index 0000000000000000000000000000000000000000..d85c5a16c7a5646a70bd9276b9f9e345d4b57af7 --- /dev/null +++ b/server/lib/plugins/PluginsProvider/PluginsProvider.hpp @@ -0,0 +1,77 @@ +#ifndef PLUGINS_PROVIDER_H +#define PLUGINS_PROVIDER_H + +#include "AudiosProviderWrapper.hpp" +#include "DefinitionsProviderWrapper.hpp" +#include "FormatProcessorWrapper.hpp" +#include "ImagesProviderWrapper.hpp" +#include "PluginsLoader.hpp" +#include "SentencesProviderWrapper.hpp" +#include +#include +#include +#include +#include + +class IPluginsProvider { + public: + virtual ~IPluginsProvider() = default; + + virtual auto get_definitions_provider(const std::string &name) + -> std::optional< + std::variant> = 0; + + virtual auto get_sentences_provider(const std::string &name) + -> std::optional< + std::variant> = 0; + + virtual auto get_images_provider(const std::string &name) -> std::optional< + std::variant> = 0; + + virtual auto get_audios_provider(const std::string &name) -> std::optional< + std::variant> = 0; + + virtual auto get_format_processor(const std::string &name) -> std::optional< + std::variant> = 0; + + virtual auto load_new_plugins() -> void = 0; +}; + +struct PluginTypesLocationsConfig { + std::filesystem::path definitions_providers_dir; + std::filesystem::path sentences_providers_dir; + std::filesystem::path images_providers_dir; + std::filesystem::path audios_providers_dir; + std::filesystem::path format_processors_dir; +}; + +class PluginsProvider : public IPluginsProvider { + public: + explicit PluginsProvider(PluginTypesLocationsConfig &&config); + + auto get_definitions_provider(const std::string &name) -> std::optional< + std::variant> override; + + auto get_sentences_provider(const std::string &name) -> std::optional< + std::variant> override; + + auto get_images_provider(const std::string &name) -> std::optional< + std::variant> override; + + auto get_audios_provider(const std::string &name) -> std::optional< + std::variant> override; + + auto get_format_processor(const std::string &name) -> std::optional< + std::variant> override; + + auto load_new_plugins() -> void override; + + private: + PluginsLoader definitions_providers_; + PluginsLoader sentences_providers_; + PluginsLoader images_providers_; + PluginsLoader audios_providers_; + PluginsLoader format_processors_; +}; + +#endif // !PLUGINS_PROVIDER_H diff --git a/server/lib/plugins/PyExceptionInfo/CMakeLists.txt b/server/lib/plugins/PyExceptionInfo/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..d59941128c4c5ab79f7b86bfb36f576794036e2d --- /dev/null +++ b/server/lib/plugins/PyExceptionInfo/CMakeLists.txt @@ -0,0 +1,14 @@ +message(${CMAKE_CURRENT_SOURCE_DIR}) + +add_library(py_exception_info PyExceptionInfo.cpp) +target_include_directories(py_exception_info + PUBLIC + ./ + ${Boost_INCLUDE_DIRS} + ${PYTHON_INCLUDE_DIRS} +) + +target_link_libraries(py_exception_info + ${Boost_LIBRARIES} + ${PYTHON_LIBRARIES} +) diff --git a/server/lib/plugins/PyExceptionInfo/PyExceptionInfo.cpp b/server/lib/plugins/PyExceptionInfo/PyExceptionInfo.cpp new file mode 100644 index 0000000000000000000000000000000000000000..ef464befad370a2cdc6ec5cae5352de6ad22c456 --- /dev/null +++ b/server/lib/plugins/PyExceptionInfo/PyExceptionInfo.cpp @@ -0,0 +1,36 @@ +#include "PyExceptionInfo.hpp" +#include "spdlog/common.h" +#include "spdlog/spdlog.h" +#include +#include +#include +#include + +auto PyExceptionInfo::build(boost::python::object &plugin_namespace) + -> std::optional { + // Print обязатетен + // https://stackoverflow.com/a/57896281 + PyExceptionInfo info; + try { + PyErr_Print(); + + boost::python::exec("import traceback, sys", plugin_namespace); + + boost::python::object py_err = + boost::python::eval("str(sys.last_value)", plugin_namespace); + + boost::python::object py_stack_trace = boost::python::eval( + "'\\n'.join(traceback.format_exception(sys.last_type, " + "sys.last_value, sys.last_traceback))", + plugin_namespace); + info.stack_trace_ = + std::move(boost::python::extract(py_stack_trace)); + info.error_summary_ = + std::move(boost::python::extract(py_err)); + PyErr_Clear(); + } catch (const boost::python::error_already_set &) { + spdlog::error("Couldn't extract python exception info"); + return std::nullopt; + } + return info; +} diff --git a/server/lib/plugins/PyExceptionInfo/PyExceptionInfo.hpp b/server/lib/plugins/PyExceptionInfo/PyExceptionInfo.hpp new file mode 100644 index 0000000000000000000000000000000000000000..5907e89b9af1c37205f0d0fa16e9f7dd27bf2870 --- /dev/null +++ b/server/lib/plugins/PyExceptionInfo/PyExceptionInfo.hpp @@ -0,0 +1,29 @@ +#ifndef PY_EXCEPTION_INFO_H +#define PY_EXCEPTION_INFO_H + +#include +#include +#include +#include + +class PyExceptionInfo { + public: + static auto build(boost::python::object &plugin_namespace) + -> std::optional; + + [[nodiscard]] inline auto stack_trace() const -> const std::string & { + return stack_trace_; + } + + [[nodiscard]] inline auto error_summary() const -> const std::string & { + return error_summary_; + } + + private: + std::string stack_trace_; + std::string error_summary_; + + PyExceptionInfo() = default; +}; + +#endif // !PY_EXCEPTION_INFO_H diff --git a/server/lib/plugins/wrappers/AudiosProviderWrapper/AudiosProviderWrapper.cpp b/server/lib/plugins/wrappers/AudiosProviderWrapper/AudiosProviderWrapper.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a04cba3d4e8d82955b621c3fee2100138440ff02 --- /dev/null +++ b/server/lib/plugins/wrappers/AudiosProviderWrapper/AudiosProviderWrapper.cpp @@ -0,0 +1,21 @@ +#include "AudiosProviderWrapper.hpp" +#include "BasePluginWrapper.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +AudiosProviderWrapper::AudiosProviderWrapper(BasePluginWrapper &&base) + : BasePluginWrapper(std::move(base)) { +} + +auto AudiosProviderWrapper::get(const std::string &word, uint64_t batch_size) + -> std::variant { + std::vector test; + return std::make_pair(test, ""); +} diff --git a/server/lib/plugins/wrappers/AudiosProviderWrapper/AudiosProviderWrapper.hpp b/server/lib/plugins/wrappers/AudiosProviderWrapper/AudiosProviderWrapper.hpp new file mode 100644 index 0000000000000000000000000000000000000000..29071ce5e40a8fc52727716955594101bb2cd21b --- /dev/null +++ b/server/lib/plugins/wrappers/AudiosProviderWrapper/AudiosProviderWrapper.hpp @@ -0,0 +1,29 @@ +#ifndef AUDIOS_PROVIDER_WRAPPER_H +#define AUDIOS_PROVIDER_WRAPPER_H + +#include "BasePluginWrapper.hpp" +#include "PyExceptionInfo.hpp" +#include +#include +#include +#include +#include + +struct AudioInfo { + std::string audio; + std::string additional_info; +}; + +class AudiosProviderWrapper : public BasePluginWrapper { + public: + using type = std::pair, std::string>; + + explicit AudiosProviderWrapper(BasePluginWrapper &&base); + + auto get(const std::string &word, uint64_t batch_size) + -> std::variant; +}; + +static_assert(is_plugin_wrapper); + +#endif // !AUDIOS_PROVIDER_WRAPPER_H diff --git a/server/lib/plugins/wrappers/AudiosProviderWrapper/CMakeLists.txt b/server/lib/plugins/wrappers/AudiosProviderWrapper/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..83d003c6e8c36b645eb460dc86d1bec48cf4721d --- /dev/null +++ b/server/lib/plugins/wrappers/AudiosProviderWrapper/CMakeLists.txt @@ -0,0 +1,6 @@ +add_library(audios_provider_wrapper AudiosProviderWrapper.cpp) + +target_include_directories(audios_provider_wrapper PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR} +) +target_link_libraries(audios_provider_wrapper base_plugin_wrapper containers) diff --git a/server/lib/plugins/wrappers/BasePluginWrapper/BasePluginWrapper.hpp b/server/lib/plugins/wrappers/BasePluginWrapper/BasePluginWrapper.hpp new file mode 100644 index 0000000000000000000000000000000000000000..41009e5e13ce348bcc9aa1c2608a73b8c9392d95 --- /dev/null +++ b/server/lib/plugins/wrappers/BasePluginWrapper/BasePluginWrapper.hpp @@ -0,0 +1,191 @@ +#ifndef BASE_PLUGIN_WRAPPER_H +#define BASE_PLUGIN_WRAPPER_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "IPluginWrapper.hpp" +#include "PyExceptionInfo.hpp" + +// TODO: MOVE DEFINITIONS TO A SEPARATE FILE +class BasePluginWrapper : public IPluginWrapper { + public: + BasePluginWrapper(const BasePluginWrapper &) = delete; + BasePluginWrapper(BasePluginWrapper &&) = default; + auto operator=(const BasePluginWrapper &) -> BasePluginWrapper & = delete; + auto operator=(BasePluginWrapper &&) -> BasePluginWrapper & = default; + + static auto build(std::string name, Container container) + -> std::variant { + auto wrapper = BasePluginWrapper(std::move(name), std::move(container)); + auto config_or_error = wrapper.get_default_config(); + if (std::holds_alternative(config_or_error)) { + auto error = std::get(config_or_error); + return error; + } + wrapper.config_ = std::get(config_or_error); + return wrapper; + } + + ~BasePluginWrapper() override = default; + + [[nodiscard]] auto name() const -> const std::string & override { + return name_; + } + + auto load() -> std::optional override { + try { + boost::python::object plugin_load = container_.load(); + plugin_load(); + } catch (const boost::python::error_already_set &) { + return PyExceptionInfo::build(container_.plugin_namespace()) + .value(); + } + return std::nullopt; + } + + auto unload() -> std::optional override { + try { + boost::python::object plugin_unload = container_.load(); + plugin_unload(); + } catch (const boost::python::error_already_set &) { + return PyExceptionInfo::build(container_.plugin_namespace()) + .value(); + } + return std::nullopt; + } + + auto get_config_description() + -> std::variant override { + nlohmann::json config_description; + try { + boost::python::object py_json = boost::python::import("json"); + boost::python::object py_json_dumps = py_json.attr("dumps"); + + boost::python::object py_plugin_conf_description = + container_.get_config_description()(); + boost::python::object py_str_json_conf_description = + py_json_dumps(py_plugin_conf_description); + + std::string cpp_plugin_conf_description = + boost::python::extract( + py_str_json_conf_description); + config_description = + nlohmann::json::parse(cpp_plugin_conf_description); + } catch (const boost::python::error_already_set &) { + return PyExceptionInfo::build(container_.plugin_namespace()) + .value(); + } + return config_description; + } + + auto get_default_config() + -> std::variant override { + nlohmann::json default_config; + try { + boost::python::object py_json = boost::python::import("json"); + boost::python::object py_json_dumps = py_json.attr("dumps"); + + boost::python::object py_plugin_default_conf = + container_.get_default_config()(); + boost::python::object py_str_json_default_conf = + py_json_dumps(py_plugin_default_conf); + + std::string cpp_plugin_default_conf = + boost::python::extract(py_str_json_default_conf); + default_config = nlohmann::json::parse(cpp_plugin_default_conf); + } catch (const boost::python::error_already_set &) { + return PyExceptionInfo::build(container_.plugin_namespace()) + .value(); + } + return default_config; + } + + auto set_config(nlohmann::json &&new_config) + -> std::variant override { + nlohmann::json diagnostics; + try { + boost::python::object py_json = boost::python::import("json"); + boost::python::object py_json_loads = py_json.attr("loads"); + boost::python::object py_json_dumps = py_json.attr("dumps"); + + std::string new_conf_str = new_config.dump(); + boost::python::object py_new_conf = py_json_loads(py_json_loads); + boost::python::object py_conf_diagnostics = + container_.set_config()(py_new_conf); + boost::python::object py_conf_diagnostics_str = + py_json_dumps(py_conf_diagnostics); + std::string cpp_conf_diagnostics_str = + boost::python::extract(py_conf_diagnostics_str); + diagnostics = nlohmann::json::parse(cpp_conf_diagnostics_str); + return diagnostics; + } catch (const boost::python::error_already_set &) { + return PyExceptionInfo::build(container_.plugin_namespace()) + .value(); + } + return diagnostics; + } + + protected: + explicit BasePluginWrapper(std::string &&name, Container &&container) + : name_(name), container_(container) { + } + + std::string name_; + Container container_; + nlohmann::json config_; +}; + +struct ResultFilesPaths { + std::filesystem::path cards; + std::filesystem::path audios; + std::filesystem::path images; +}; + +template +concept container_constructible = + requires(T instance, std::string name, Container container) { + { + T::build(name, container) + } -> std::same_as>; + }; + +template +concept implements_wrapper_get = ( + // DefinitionsProvider, SentencesProvider, AudiosProvider, ImagesProvider + requires(T dependent_instance, + const std::string &query, + uint64_t batch_size) { + { + dependent_instance.get(query, batch_size) + } -> std::same_as>; + } || + requires(T dependent_instance, + const std::string &query, + const std::string &filter, + bool restart, + uint64_t batch_size) { + { + dependent_instance.get(query, filter, batch_size, restart) + } -> std::same_as>; + } || + // FormatProcessor + requires(T dependent_instance, ResultFilesPaths paths) { + { + dependent_instance.get(std::move(paths)) + } -> std::same_as>; + }); + +template +concept is_plugin_wrapper = + std::derived_from && container_constructible && + implements_wrapper_get; + +#endif // !BASE_PLUGIN_WRAPPER_H diff --git a/server/lib/plugins/wrappers/BasePluginWrapper/CMakeLists.txt b/server/lib/plugins/wrappers/BasePluginWrapper/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..864562a2cdacf52abdf79ef28a344d4306a1c297 --- /dev/null +++ b/server/lib/plugins/wrappers/BasePluginWrapper/CMakeLists.txt @@ -0,0 +1,5 @@ +add_library(base_plugin_wrapper INTERFACE) +target_include_directories(base_plugin_wrapper INTERFACE ./) +target_link_libraries(base_plugin_wrapper INTERFACE + plugin_wrapper_interface +) diff --git a/server/lib/plugins/wrappers/CMakeLists.txt b/server/lib/plugins/wrappers/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..ae1b46cee7c9b3bb030c583921573ccd99bc7b8f --- /dev/null +++ b/server/lib/plugins/wrappers/CMakeLists.txt @@ -0,0 +1,7 @@ +add_subdirectory(PluginWrapperInterface) +add_subdirectory(BasePluginWrapper) +add_subdirectory(AudiosProviderWrapper) +add_subdirectory(DefinitionsProviderWrapper) +add_subdirectory(FormatProcessorWrapper) +add_subdirectory(ImagesProviderWrapper) +add_subdirectory(SentencesProviderWrapper) diff --git a/server/lib/plugins/wrappers/DefinitionsProviderWrapper/CMakeLists.txt b/server/lib/plugins/wrappers/DefinitionsProviderWrapper/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..c2245c0791382e4088188056f0dab8e3b3c3ddfb --- /dev/null +++ b/server/lib/plugins/wrappers/DefinitionsProviderWrapper/CMakeLists.txt @@ -0,0 +1,7 @@ +add_library(definitions_provider_wrapper DefinitionsProviderWrapper.cpp) +target_include_directories(definitions_provider_wrapper PUBLIC ./) +target_link_libraries(definitions_provider_wrapper base_plugin_wrapper containers) + +# set(serverside_plugin_wrappers +# "${serverside_plugin_wrappers} definitions_provider_wrapper" +# PARENT_SCOPE) diff --git a/server/lib/plugins/wrappers/DefinitionsProviderWrapper/DefinitionsProviderWrapper.cpp b/server/lib/plugins/wrappers/DefinitionsProviderWrapper/DefinitionsProviderWrapper.cpp new file mode 100644 index 0000000000000000000000000000000000000000..ab8bcae1e36cd6040239862f162dab4970c362c7 --- /dev/null +++ b/server/lib/plugins/wrappers/DefinitionsProviderWrapper/DefinitionsProviderWrapper.cpp @@ -0,0 +1,85 @@ +#include "DefinitionsProviderWrapper.hpp" +#include "PyExceptionInfo.hpp" +#include "pyerrors.h" +#include "pythonrun.h" +#include +#include +#include +#include +#include +#include + +DefinitionsProviderWrapper::DefinitionsProviderWrapper(BasePluginWrapper &&base) + : BasePluginWrapper(std::move(base)) { +} + +auto DefinitionsProviderWrapper::get(const std::string &word, + const std::string &filter_query, + uint64_t batch_size, + bool restart) + -> std::variant { + if (restart) { + auto found_item = generators_.find(word); + if (found_item != generators_.end()) { + generators_.erase(found_item); + } + } + if (generators_.find(word) == generators_.end()) { + try { + boost::python::object test = container_.get()(word); + generators_[word] = test; + generators_[word]->attr("__next__")(); + } catch (const boost::python::error_already_set &) { + generators_.erase(generators_.find(word)); + return PyExceptionInfo::build(container_.plugin_namespace()) + .value(); + } + } + + try { + boost::python::object py_json = boost::python::import("json"); + boost::python::object py_json_dumps = py_json.attr("dumps"); + + boost::python::object py_res = + generators_[word]->attr("send")(batch_size); + boost::python::object py_json_res = py_json_dumps(py_res); + + std::string str_res = boost::python::extract(py_json_res); + nlohmann::json json_res = nlohmann::json::parse(str_res); + + // TODO(blackdeer): PROPER ERROR HANDLING + if (!json_res.is_array()) { + return {}; + } + auto error_message = json_res[1].get(); + auto cards = json_res[0].get>(); + return std::make_pair(cards, error_message); + } catch (boost::python::error_already_set &) { + // https://stackoverflow.com/questions/1418015/how-to-get-python-exception-text + // boost::python::handle_exception(); + + PyErr_Print(); + PyErr_Clear(); + // TODO(blackdeer): NEED TO LEARN HOW TO CATCH StopIteration() + // + // boost::python::object main_namespace = + // boost::python::import("__main__").attr("__dict__"); + // boost::python::exec("import traceback, sys", main_namespace); + // boost::python::object py_err = + // eval("str(sys.last_value)", main_namespace); + // std::string exception_type = + // boost::python::extract(py_err); + // if (exception_type == "StopIteration()") { + // PyErr_Clear(); + // return {}; + // } + // return PyExceptionInfo::build().value(); + } + std::vector empty(0); + return std::make_pair(empty, ""); +} + +auto DefinitionsProviderWrapper::get_dictionary_scheme() + -> std::variant { + return {}; +} diff --git a/server/lib/plugins/wrappers/DefinitionsProviderWrapper/DefinitionsProviderWrapper.hpp b/server/lib/plugins/wrappers/DefinitionsProviderWrapper/DefinitionsProviderWrapper.hpp new file mode 100644 index 0000000000000000000000000000000000000000..c2af818a99a6ad8b241830814faa87bc94a42796 --- /dev/null +++ b/server/lib/plugins/wrappers/DefinitionsProviderWrapper/DefinitionsProviderWrapper.hpp @@ -0,0 +1,65 @@ +#ifndef DEFINITIONS_PROVIDER_WRAPPER_H +#define DEFINITIONS_PROVIDER_WRAPPER_H + +#include +#include +#include +#include +#include +#include +#include + +#include "BasePluginWrapper.hpp" +#include "PyExceptionInfo.hpp" + +struct Card { + Card() = default; + + Card(const Card &) = default; + Card(Card &&) = default; + auto operator=(const Card &) -> Card & = default; + auto operator=(Card &&) -> Card & = default; + + public: + std::string word; + std::vector special; + std::string definition; + std::vector examples; + std::vector image_links; + std::vector audio_links; + nlohmann::json tags; + nlohmann::json other; +}; + +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Card, + word, + special, + definition, + examples, + image_links, + audio_links, + tags + /* other */); + +class DefinitionsProviderWrapper : public BasePluginWrapper { + public: + using type = std::pair, std::string>; + + explicit DefinitionsProviderWrapper(BasePluginWrapper &&base); + + auto get_dictionary_scheme() + -> std::variant; + auto get(const std::string &word, + const std::string &filter_query, + uint64_t batch_size, + bool restart) + -> std::variant; + + private: + std::unordered_map> + generators_; +}; + +static_assert(is_plugin_wrapper); + +#endif // !DEFINITIONS_PROVIDER_WRAPPER_H diff --git a/server/lib/plugins/wrappers/FormatProcessorWrapper/CMakeLists.txt b/server/lib/plugins/wrappers/FormatProcessorWrapper/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..7dcf495f9a9dac11b6eb5851fb52ee53aeb1d577 --- /dev/null +++ b/server/lib/plugins/wrappers/FormatProcessorWrapper/CMakeLists.txt @@ -0,0 +1,8 @@ +add_library(format_processor_wrapper FormatProcessorWrapper.cpp) + +target_include_directories(format_processor_wrapper PUBLIC ./) +target_link_libraries(format_processor_wrapper base_plugin_wrapper containers) + +# set(serverside_plugin_wrappers +# "${serverside_plugin_wrappers} format_processor_wrapper" +# PARENT_SCOPE) diff --git a/server/lib/plugins/wrappers/FormatProcessorWrapper/FormatProcessorWrapper.cpp b/server/lib/plugins/wrappers/FormatProcessorWrapper/FormatProcessorWrapper.cpp new file mode 100644 index 0000000000000000000000000000000000000000..5da3ca47423cb8133da6e83a33e94e900d56cf93 --- /dev/null +++ b/server/lib/plugins/wrappers/FormatProcessorWrapper/FormatProcessorWrapper.cpp @@ -0,0 +1,11 @@ +#include "FormatProcessorWrapper.hpp" +#include "BasePluginWrapper.hpp" + +FormatProcessorWrapper::FormatProcessorWrapper(BasePluginWrapper &&base) + : BasePluginWrapper(std::move(base)) { +} + +auto FormatProcessorWrapper::get(ResultFilesPaths &&paths) + -> std::variant { + return {}; +} diff --git a/server/lib/plugins/wrappers/FormatProcessorWrapper/FormatProcessorWrapper.hpp b/server/lib/plugins/wrappers/FormatProcessorWrapper/FormatProcessorWrapper.hpp new file mode 100644 index 0000000000000000000000000000000000000000..048567d321d70c084d8ca9b2d86b8414446433a2 --- /dev/null +++ b/server/lib/plugins/wrappers/FormatProcessorWrapper/FormatProcessorWrapper.hpp @@ -0,0 +1,25 @@ +#ifndef FORMAT_PROCESSOR_WRAPPER_H +#define FORMAT_PROCESSOR_WRAPPER_H + +#include +#include +#include +#include +#include + +#include "BasePluginWrapper.hpp" +#include "PyExceptionInfo.hpp" + +class FormatProcessorWrapper : public BasePluginWrapper { + public: + using type = std::string; + + explicit FormatProcessorWrapper(BasePluginWrapper &&base); + + auto get(ResultFilesPaths &&paths) + -> std::variant; +}; + +static_assert(is_plugin_wrapper); + +#endif diff --git a/server/lib/plugins/wrappers/ImagesProviderWrapper/CMakeLists.txt b/server/lib/plugins/wrappers/ImagesProviderWrapper/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..f17f6f29079d71e79920fac9001e76e714da2f9c --- /dev/null +++ b/server/lib/plugins/wrappers/ImagesProviderWrapper/CMakeLists.txt @@ -0,0 +1,7 @@ +add_library(images_provider_wrapper ImagesProviderWrapper.cpp) +target_include_directories(images_provider_wrapper PUBLIC ./) +target_link_libraries(images_provider_wrapper base_plugin_wrapper containers) +# +# set(serverside_plugin_wrappers +# "${serverside_plugin_wrappers} images_provider_wrapper" +# PARENT_SCOPE) diff --git a/server/lib/plugins/wrappers/ImagesProviderWrapper/ImagesProviderWrapper.cpp b/server/lib/plugins/wrappers/ImagesProviderWrapper/ImagesProviderWrapper.cpp new file mode 100644 index 0000000000000000000000000000000000000000..801e21001270c036624896efdd1cacda216e5a9c --- /dev/null +++ b/server/lib/plugins/wrappers/ImagesProviderWrapper/ImagesProviderWrapper.cpp @@ -0,0 +1,10 @@ +#include "ImagesProviderWrapper.hpp" + +ImagesProviderWrapper::ImagesProviderWrapper(BasePluginWrapper &&base) + : BasePluginWrapper(std::move(base)) { +} + +auto ImagesProviderWrapper::get(const std::string &word, uint64_t batch_size) + -> std::variant { + return {}; +} diff --git a/server/lib/plugins/wrappers/ImagesProviderWrapper/ImagesProviderWrapper.hpp b/server/lib/plugins/wrappers/ImagesProviderWrapper/ImagesProviderWrapper.hpp new file mode 100644 index 0000000000000000000000000000000000000000..cc1506820417f1ea51f4516b785fe735a162ab2a --- /dev/null +++ b/server/lib/plugins/wrappers/ImagesProviderWrapper/ImagesProviderWrapper.hpp @@ -0,0 +1,25 @@ +#ifndef IMAGES_PROVIDER_WRAPPER_H +#define IMAGES_PROVIDER_WRAPPER_H + +#include +#include +#include +#include +#include + +#include "BasePluginWrapper.hpp" +#include "PyExceptionInfo.hpp" + +class ImagesProviderWrapper : public BasePluginWrapper { + public: + using type = std::pair, std::string>; + + explicit ImagesProviderWrapper(BasePluginWrapper &&base); + + auto get(const std::string &word, uint64_t batch_size) + -> std::variant; +}; + +static_assert(is_plugin_wrapper); + +#endif diff --git a/server/lib/plugins/wrappers/PluginWrapperInterface/CMakeLists.txt b/server/lib/plugins/wrappers/PluginWrapperInterface/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..5259b1ad497e463a5ff49852adede43cb1981105 --- /dev/null +++ b/server/lib/plugins/wrappers/PluginWrapperInterface/CMakeLists.txt @@ -0,0 +1,7 @@ +add_library(plugin_wrapper_interface INTERFACE) +target_include_directories(plugin_wrapper_interface INTERFACE .) +target_link_libraries(plugin_wrapper_interface + INTERFACE + containers + py_exception_info +) diff --git a/server/lib/plugins/wrappers/PluginWrapperInterface/IPluginWrapper.hpp b/server/lib/plugins/wrappers/PluginWrapperInterface/IPluginWrapper.hpp new file mode 100644 index 0000000000000000000000000000000000000000..ffa2caaede6c67d7a75e7f153e91d0b74470f7a7 --- /dev/null +++ b/server/lib/plugins/wrappers/PluginWrapperInterface/IPluginWrapper.hpp @@ -0,0 +1,29 @@ +#ifndef PLUGIN_WRAPPER_INTERFACE_H +#define PLUGIN_WRAPPER_INTERFACE_H + +#include "Container.hpp" +#include "PyExceptionInfo.hpp" +#include +#include +#include +#include +#include +#include +#include + +class IPluginWrapper { + public: + virtual ~IPluginWrapper() = default; + + [[nodiscard]] virtual auto name() const -> const std::string & = 0; + virtual auto load() -> std::optional = 0; + virtual auto get_config_description() + -> std::variant = 0; + virtual auto get_default_config() + -> std::variant = 0; + virtual auto set_config(nlohmann::json &&new_config) + -> std::variant = 0; + virtual auto unload() -> std::optional = 0; +}; + +#endif // !PLUGIN_WRAPPER_INTERFACE_H diff --git a/server/lib/plugins/wrappers/SentencesProviderWrapper/CMakeLists.txt b/server/lib/plugins/wrappers/SentencesProviderWrapper/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..ed94ac94ce84839b01b847c99e0ade07e0f640bd --- /dev/null +++ b/server/lib/plugins/wrappers/SentencesProviderWrapper/CMakeLists.txt @@ -0,0 +1,7 @@ +add_library(sentences_provider_wrapper SentencesProviderWrapper.cpp) +target_include_directories(sentences_provider_wrapper PUBLIC ./) +target_link_libraries(sentences_provider_wrapper base_plugin_wrapper containers) + +# set(serverside_plugin_wrappers +# "${serverside_plugin_wrappers} sentences_provider_wrapper" +# PARENT_SCOPE) diff --git a/server/lib/plugins/wrappers/SentencesProviderWrapper/SentencesProviderWrapper.cpp b/server/lib/plugins/wrappers/SentencesProviderWrapper/SentencesProviderWrapper.cpp new file mode 100644 index 0000000000000000000000000000000000000000..cf3f35ec358364348b39f5172ccd8d10813510a7 --- /dev/null +++ b/server/lib/plugins/wrappers/SentencesProviderWrapper/SentencesProviderWrapper.cpp @@ -0,0 +1,10 @@ +#include "SentencesProviderWrapper.hpp" + +SentencesProviderWrapper::SentencesProviderWrapper(BasePluginWrapper &&base) + : BasePluginWrapper(std::move(base)) { +} + +auto SentencesProviderWrapper::get(const std::string &word, uint64_t batch_size) + -> std::variant { + return {}; +} diff --git a/server/lib/plugins/wrappers/SentencesProviderWrapper/SentencesProviderWrapper.hpp b/server/lib/plugins/wrappers/SentencesProviderWrapper/SentencesProviderWrapper.hpp new file mode 100644 index 0000000000000000000000000000000000000000..fadbbe32ac1049640e2a2c09dfd2ab0d6bea6e43 --- /dev/null +++ b/server/lib/plugins/wrappers/SentencesProviderWrapper/SentencesProviderWrapper.hpp @@ -0,0 +1,25 @@ +#ifndef SENTENCES_PROVIDER_WRAPPER_H +#define SENTENCES_PROVIDER_WRAPPER_H + +#include +#include +#include +#include +#include + +#include "BasePluginWrapper.hpp" +#include "PyExceptionInfo.hpp" + +class SentencesProviderWrapper : public BasePluginWrapper { + public: + using type = std::pair, std::string>; + + explicit SentencesProviderWrapper(BasePluginWrapper &&base); + + auto get(const std::string &word, uint64_t batch_size) + -> std::variant; +}; + +static_assert(is_plugin_wrapper); + +#endif diff --git a/server/main.cpp b/server/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..65b2d8a2c1c53edcec179e714c5bc8b07c4e9aaa --- /dev/null +++ b/server/main.cpp @@ -0,0 +1,30 @@ +#include "DefinitionsProviderWrapper.hpp" +#include "FormatProcessorWrapper.hpp" +#include "PluginsProvider.hpp" +#include "Server.hpp" +#include +#include +#include +#include + +auto main(int argc, char *argv[]) -> int { + Py_Initialize(); + auto plugins_dirs = PluginTypesLocationsConfig{ + .definitions_providers_dir = + "/home/blackdeer/projects/cpp/technopark/plugins/definitions/", + .sentences_providers_dir = + "/home/blackdeer/projects/cpp/technopark/plugins/sentences/", + .images_providers_dir = + "/home/blackdeer/projects/cpp/technopark/plugins/images/", + .audios_providers_dir = + "/home/blackdeer/projects/cpp/technopark/plugins/audios/", + .format_processors_dir = "/home/blackdeer/projects/cpp/technopark/" + "plugins/format_processors/"}; + + boost::asio::io_context io_context; + auto plugins_provider = + std::make_shared(std::move(plugins_dirs)); + constexpr uint16_t port = 8888; + auto server = PluginServer(std::move(plugins_provider), io_context, port); + io_context.run(); +} diff --git a/spdlog b/spdlog new file mode 160000 index 0000000000000000000000000000000000000000..57a9fd0841f00e92b478a07fef62636d7be612a8 --- /dev/null +++ b/spdlog @@ -0,0 +1 @@ +Subproject commit 57a9fd0841f00e92b478a07fef62636d7be612a8 diff --git a/test_client/CMakeLists.txt b/test_client/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..030346dbcf7988d319d19395560b6b7a56bc8c1c --- /dev/null +++ b/test_client/CMakeLists.txt @@ -0,0 +1,2 @@ +add_executable(test_sender sender.cpp) + diff --git a/test_client/sender.cpp b/test_client/sender.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f3d0adfbc390ad8019c1f81e410d5dab6d565be4 --- /dev/null +++ b/test_client/sender.cpp @@ -0,0 +1,112 @@ +// +// client.cpp +// ~~~~~~~~~~ +// +// Copyright (c) 2003-2020 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// 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 +#include +#include +#include + +using boost::asio::ip::tcp; + +int main(int argc, char *argv[]) { + try { + // if (argc != 2) { + // std::cerr << "Usage: client " << std::endl; + // return 1; + // } + + boost::asio::io_service ios; + + boost::asio::ip::tcp::endpoint endpoint( + boost::asio::ip::address::from_string("127.0.0.1"), 8888); + + boost::asio::ip::tcp::socket socket(ios); + + socket.connect(endpoint); + for (auto i = 0; i < 1; ++i) { + { + boost::array buf = { + "{\"query_type\": \"init\", \"plugin_type\": \"word\", " + "\"plugin_name\": \"definitions\"}\r\n"}; + boost::system::error_code error; + + // std::cout.write(buf.data(), 4); + + size_t len = socket.write_some( + boost::asio::buffer(buf, strlen(buf.data())), error); + size_t len2 = socket.read_some(boost::asio::buffer(buf), error); + std::cout.write(buf.data(), strlen(buf.data())) << std::endl; + + if (error == boost::asio::error::eof) + break; // Connection closed cleanly by peer. + else if (error) + throw boost::system::system_error( + error); // Some other error. + } + { + boost::array buf = { + R"( +{ + "query_type": "get", + "plugin_type": "word", + "filter": "", + "word": "definitions", + "batch_size": 5, + "restart": true +})" + "\r\n"}; + boost::system::error_code error; + + // std::cout.write(buf.data(), 4); + + size_t len = socket.write_some( + boost::asio::buffer(buf, strlen(buf.data())), error); + size_t len2 = socket.read_some(boost::asio::buffer(buf), error); + std::cout.write(buf.data(), strlen(buf.data())) << std::endl; + + if (error == boost::asio::error::eof) + break; // Connection closed cleanly by peer. + else if (error) + throw boost::system::system_error( + error); // Some other error. + } + { + boost::array buf = { + R"( +{ + "query_type": "get", + "plugin_type": "word", + "filter": "", + "word": "definitions", + "batch_size": 5, + "restart": false +})" + "\r\n"}; + boost::system::error_code error; + + // std::cout.write(buf.data(), 4); + + size_t len = socket.write_some( + boost::asio::buffer(buf, strlen(buf.data())), error); + size_t len2 = socket.read_some(boost::asio::buffer(buf), error); + std::cout.write(buf.data(), strlen(buf.data())) << std::endl; + + if (error == boost::asio::error::eof) + break; // Connection closed cleanly by peer. + else if (error) + throw boost::system::system_error( + error); // Some other error. + } + } + } catch (std::exception &e) { + std::cerr << e.what() << std::endl; + } + return 0; +}