diff --git a/.github/workflows/build_executable.yml b/.github/workflows/build_executable.yml index feacc47..dfbc702 100644 --- a/.github/workflows/build_executable.yml +++ b/.github/workflows/build_executable.yml @@ -20,10 +20,12 @@ jobs: run: | pyinstaller --onefile --name="flatc_deserializer" --console --icon=images\flatbuffers-logo-clean.png --add-data="images:images" --add-data="localization:localization" flatc_deserializer.py pyinstaller --onefile --name="flatc_downloader" --console --icon=images\flatbuffers-downloader-logo-clean.png --add-data="images:images" --add-data="localization:localization" flatc_downloader.py + pyinstaller --onefile --name="flatc_deserializer_batch" --console --icon=images\flatbuffers-batch-logo-clean.png --add-data="images:images" --add-data="localization:localization" flatc_deserializer_batch.py - name: Move Deserializer and Downloader Executables from dist to root directory and delete dist directory run: | move dist\flatc_deserializer.exe . move dist\flatc_downloader.exe . + move dist\flatc_deserializer_batch.exe . rd /s /q dist shell: cmd - name: Create Automatic Windows Release @@ -35,4 +37,5 @@ jobs: title: Latest Build files: | flatc_deserializer.exe - flatc_downloader.exe \ No newline at end of file + flatc_downloader.exe + flatc_deserializer_batch.exe \ No newline at end of file diff --git a/flatc_deserializer.py b/flatc_deserializer.py index ebbb123..f74a25d 100644 --- a/flatc_deserializer.py +++ b/flatc_deserializer.py @@ -1,12 +1,24 @@ """ - Десериализация бинарных файлов Flatbuffers по заданной схеме c открытием диалогового окна для - поиска компилятора схемы при его отсутствии в рабочей директории. + Десериализация выбранных бинарных файлов Flatbuffers по выбранной схеме. """ import os import sys +import argparse +from i18n import t from main import prepare_app, get_flatc_path, execute_deserialize # pylint: disable=import-error if __name__ == "__main__": prepare_app("images/flatbuffers-logo-clean.png") - sys.exit(execute_deserialize(get_flatc_path(True, os.getcwd(), True))) + parser = argparse.ArgumentParser(prog=t("main.flatc_deserializer_name"), + description=t("main.flatc_deserializer_desc")) + parser.add_argument("-s", "--schema_path", type=str, default="", help=t("main.schema_file_arg")) + parser.add_argument("-b", "--binary_paths", nargs="+", default=[], + help=t("main.binary_files_arg")) + parser.add_argument("-o", "--output_path", type=str, default="", + help=t("main.output_directory_arg")) + parser.add_argument("-f", "--flatc_path", type=str, default="", help=t("main.flatc_path_arg")) + args = parser.parse_args() + sys.exit(execute_deserialize( + get_flatc_path(os.getcwd(), True) if args.flatc_path == "" else args.flatc_path, + args.schema_path, args.binary_paths, args.output_path)) diff --git a/flatc_deserializer_batch.py b/flatc_deserializer_batch.py new file mode 100644 index 0000000..4696155 --- /dev/null +++ b/flatc_deserializer_batch.py @@ -0,0 +1,27 @@ +""" + Десериализация бинарных файлов Flatbuffers в выбранной директории по всем схемам в другой + выбранной директории. +""" +import os +import sys +import argparse +from i18n import t + +from main import prepare_app, get_flatc_path, \ + execute_deserialize_batch # pylint: disable=import-error + +if __name__ == "__main__": + prepare_app("images/flatbuffers-batch-logo-clean.png") + parser = argparse.ArgumentParser(prog=t("main.flatc_deserializer_name"), + description=t("main.flatc_derializer_desc")) + parser.add_argument("-s", "--schemas_path", type=str, default="", + help=t("main.schemas_directory_arg")) + parser.add_argument("-b", "--binaries_path", type=str, default="", + help=t("main.binaries_directory_arg")) + parser.add_argument("-o", "--output_path", type=str, default="", + help=t("main.output_directory_arg")) + parser.add_argument("-f", "--flatc_path", type=str, default="", help=t("main.flatc_path_arg")) + args = parser.parse_args() + sys.exit(execute_deserialize_batch( + get_flatc_path(os.getcwd(), True) if args.flatc_path == "" else args.flatc_path, + args.schemas_path, args.binaries_path, args.output_path)) diff --git a/images/flatbuffers-batch-logo-clean.png b/images/flatbuffers-batch-logo-clean.png new file mode 100644 index 0000000..43a3096 Binary files /dev/null and b/images/flatbuffers-batch-logo-clean.png differ diff --git a/localization/en_US/flatc_download_funcs.yml b/localization/en_US/flatc_download_funcs.yml index 6e0275e..344235f 100644 --- a/localization/en_US/flatc_download_funcs.yml +++ b/localization/en_US/flatc_download_funcs.yml @@ -1,5 +1,5 @@ download_start: Retrieving Flatbuffers latest release data... download_failed: Can't find valid latest Flatbuffers release. -download_finish: "Downloaded latest Flatbuffers release to %s." +download_finish: Downloaded latest Flatbuffers release to %s. unpack_path: Unpacked %s archive to %s. archive_removed: Flatbuffers archive is removed. \ No newline at end of file diff --git a/localization/en_US/main.yml b/localization/en_US/main.yml index 4e04575..c59cdc6 100644 --- a/localization/en_US/main.yml +++ b/localization/en_US/main.yml @@ -1,6 +1,6 @@ -tkinter_flatc_select: Select scheme compiler +tkinter_flatc_select: Select schema compiler exe_filetype: Windows Executable -tkinter_fbs_select: Select scheme file +tkinter_fbs_select: Select schema file fbs_filetype: Flatbuffers scheme tkinter_binaries_select: Select binary files flatc_binary_filetype: Binary Flatbuffers file @@ -12,4 +12,15 @@ file_not_found: File %s not found. directory_not_found: Directory %s not found. file_removed: File %s is removed. flatc_already_exists: "Flabuffers schema compiler already exists in working directory: %s." -file_not_executable: File %s is not executable. \ No newline at end of file +file_not_executable: File %s is not executable. +tkinter_fbs_directory_select: Select directory with schema files +tkinter_binary_directory_select: Select directory with binary files +no_schema_files_found: No schema files found in directory %s. +schemas_directory_arg: Directory with schema files +binaries_directory_arg: Directory with binary files +output_directory_arg: Output directory for deserialized files +schema_file_arg: Schema file +binary_files_arg: Binary files +flatc_path_arg: Path to schema compiler (flatc) +flatc_deserializer_name: Flatbuffers Batch Deserializer +flatc_deserializer_desc: Program for batch deserialization of Flatbuffers binary files. \ No newline at end of file diff --git a/localization/ru_RU/flatc_download_funcs.yml b/localization/ru_RU/flatc_download_funcs.yml index d01f6e3..a324788 100644 --- a/localization/ru_RU/flatc_download_funcs.yml +++ b/localization/ru_RU/flatc_download_funcs.yml @@ -1,5 +1,5 @@ download_start: Получение данных о последней версии Flatbuffers... download_failed: Не удалось найти валидную последнюю версию Flatbuffers. -download_finish: "Последняя версия Flatbuffers была загружена в %s." +download_finish: Последняя версия Flatbuffers была загружена в %s. unpack_path: Архив %s распакован в %s. archive_removed: Архив Flatbuffers был удалён. \ No newline at end of file diff --git a/localization/ru_RU/main.yml b/localization/ru_RU/main.yml index 07ec1be..bb778a7 100644 --- a/localization/ru_RU/main.yml +++ b/localization/ru_RU/main.yml @@ -12,4 +12,15 @@ file_not_found: Файл %s не найден. directory_not_found: Директория %s не найдена. file_removed: Файл %s был удалён. flatc_already_exists: "Компилятор схемы Flatbuffers уже существует в рабочей директории: %s" -file_not_executable: Файл %s не является исполняемым. \ No newline at end of file +file_not_executable: Файл %s не является исполняемым. +tkinter_fbs_directory_select: Выбор директории с файлами схем +tkinter_binary_directory_select: Выбор директории с бинарными файлами +no_schema_files_found: Файлы схем не были найдены в директории %s. +schemas_directory_arg: Директория с файлами схем +binaries_directory_arg: Директория с бинарными файлами +output_directory_arg: Директория вывода для десериализованных файлов +schema_file_arg: Файл схемы +binary_files_arg: Бинарные файлы +flatc_path_arg: Путь к компилятору схемы (flatc) +flatc_deserializer_name: Flatbuffers Batch Deserializer +flatc_deserializer_desc: Программа для пакетной десериализации бинарных файлов Flatbuffers. \ No newline at end of file diff --git a/main.py b/main.py index 6273eda..b8c09eb 100644 --- a/main.py +++ b/main.py @@ -37,6 +37,7 @@ def prepare_app(icon_path: str): """ logging.basicConfig(stream=sys.stdout, format="%(message)s", level=logging.INFO) filterwarnings("ignore", category=DeprecationWarning) + # noinspection PyDeprecation i18n.set("locale", getdefaultlocale()[0]) # pylint: disable=deprecated-method i18n.set("fallback", "en_US") i18n.load_path.append(get_resource_path("localization")) @@ -49,20 +50,14 @@ def prepare_app(icon_path: str): root.iconphoto(True, PhotoImage(file=get_resource_path(icon_path))) -def get_flatc_path(allow_system_path: bool, root_path: str, allow_ask: bool, - suppress_ask_error: bool = True) -> str: +def get_flatc_path(root_path: str, allow_ask: bool, suppress_ask_error: bool = True) -> str: """ Получение пути к файлу компилятора Flatbuffers. - :param allow_system_path: Позволить искать файл в системных директориях Python. :param root_path: Путь к текущей рабочей директории. :param allow_ask: Позволить открыть файл через диалоговое окно, если файла нет в рабочей директории. :param suppress_ask_error: Игнорировать ошибку FileNotFoundError, если не выводится диалоговое окно для файла. :return: Путь к файлу. """ - if allow_system_path: - flatc_path = which("flatc") - if flatc_path is not None: - return os.path.abspath(flatc_path) flatc_path = which("flatc", path=root_path + os.sep) if flatc_path is not None: return os.path.abspath(flatc_path) @@ -89,7 +84,7 @@ def execute_download(root_path: str) -> int | str: :param root_path: Путь к текущей рабочей директории. :return: Код ошибки или строка об ошибке. """ - flatc_path = get_flatc_path(False, root_path, False, True) + flatc_path = get_flatc_path(root_path, False, True) if flatc_path != "": logging.info(i18n.t("main.flatc_already_exists"), flatc_path) else: @@ -97,25 +92,39 @@ def execute_download(root_path: str) -> int | str: return os.EX_OK -def execute_deserialize(flatc_path: str) -> int | str: +def execute_deserialize(flatc_path: str, schema_path: str, binary_paths: list[str], output_path: +str) -> (int | str): """ Десериализация бинарных файлов Flatbuffers по заданной схеме. :param flatc_path: Путь к файлу компилятора схемы. + :param schema_path: Путь к файлу схемы. + :param binary_paths: Список путей к бинарным файлам. + :param output_path: Путь к директории вывода для десериализованных файлов. :return: Код ошибки или строка об ошибке. """ if not os.path.isfile(flatc_path): raise FileNotFoundError(i18n.t("main.file_not_found") % flatc_path) if which(os.path.split(flatc_path)[1], path=os.path.split(flatc_path)[0]) is None: raise FileNotFoundError(i18n.t("main.file_not_executable") % flatc_path) - schema_path = askopenfilename(title=i18n.t("main.tkinter_fbs_select"), - filetypes=[(i18n.t("main.fbs_filetype"), "*.fbs")]) if schema_path == "": - return os.EX_OK + schema_path = askopenfilename(title=i18n.t("main.tkinter_fbs_select"), + filetypes=[(i18n.t("main.fbs_filetype"), "*.fbs")]) + if schema_path == "": + return os.EX_OK + elif not os.path.isfile(schema_path): + raise FileNotFoundError(i18n.t("main.file_not_found") % schema_path) schema_name = os.path.splitext(os.path.basename(schema_path))[0] - binary_paths = askopenfilenames(title=i18n.t("main.tkinter_binaries_select"), - filetypes=[ - (i18n.t("main.flatc_binary_filetype"), "*." + schema_name)]) - output_path = askdirectory(title=i18n.t("main.tkinter_output_select")) + if len(binary_paths) < 1: + binary_paths = askopenfilenames(title=i18n.t("main.tkinter_binaries_select"), filetypes=[ + (i18n.t("main.flatc_binary_filetype"), "*." + schema_name)]) + if len(binary_paths) < 1: + return os.EX_OK + else: + binary_paths = [binary_path for binary_path in binary_paths if os.path.isfile(binary_path)] + if output_path == "": + output_path = askdirectory(title=i18n.t("main.tkinter_output_select")) + elif not os.path.isdir(output_path): + raise FileNotFoundError(i18n.t("main.directory_not_found") % output_path) full_size = sum(os.stat(binary_path).st_size for binary_path in binary_paths) with logging_redirect_tqdm(): pbar = tqdm(total=full_size, position=0, unit="B", unit_scale=True, @@ -128,3 +137,93 @@ def execute_deserialize(flatc_path: str) -> int | str: pbar.set_postfix_str("") pbar.close() return os.EX_OK + + +def get_schema_paths(root_path: str) -> list[str]: + """ + Получение списка путей к файлам схем Flatbuffers внутри заданной корневой директории. + :param root_path: Путь к корневой директории. + :return: Список путей к файлам схем. + """ + schema_paths = [] + if not os.path.isdir(root_path): + return schema_paths + for subdir, _, files in os.walk(root_path): + for file in files: + file_path = os.path.abspath(os.path.join(subdir, file)) + if os.path.splitext(file_path)[1].lower() == ".fbs": + schema_paths.append(file_path) + return schema_paths + + +def get_binary_tuples(binaries_path: str, schema_paths: list[str]) -> tuple[ + list[tuple[str, str]], int]: + """ + Получение списка кортежей из двух элементов: (путь к бинарному файлу, путь к соответствующему ему файлу схемы) + :param binaries_path: Список путей к бинарным файлам. + :param schema_paths: Список путей к файлам схем. + :return: Кортеж из двух строковых элементов. + """ + binary_tuples = [] + full_size = 0 + for subdir, _, files in os.walk(binaries_path): + for file in files: + file_path = os.path.abspath(os.path.join(subdir, file)) + for schema_path in schema_paths: + if os.path.splitext(os.path.basename(schema_path))[0].casefold() == \ + os.path.splitext(file)[1][1:].casefold(): + binary_tuples.append((file_path, schema_path)) + full_size += os.stat(file_path).st_size + break + return binary_tuples, full_size + + +def execute_deserialize_batch(flatc_path: str, schemas_path: str, binaries_path: str, + output_path: str) -> (int | str): + """ + Десериализация всех файлов Flatbuffers в директории по всем схемам из другой директории. + :param flatc_path: Путь к файлу компилятора схемы. + :param schemas_path: Путь к директории с файлами схем. + :param binaries_path: Путь к директории с бинарными файлами. + :param output_path: Путь к директории вывода. + :return: Код ошибки или строка об ошибке. + """ + if not os.path.isfile(flatc_path): + raise FileNotFoundError(i18n.t("main.file_not_found") % flatc_path) + if which(os.path.split(flatc_path)[1], path=os.path.split(flatc_path)[0]) is None: + raise FileNotFoundError(i18n.t("main.file_not_executable") % flatc_path) + if schemas_path == "": + schemas_path = askdirectory(title=i18n.t("main.tkinter_fbs_directory_select")) + if schemas_path == "": + return os.EX_OK + elif not os.path.isdir(schemas_path): + raise FileNotFoundError(i18n.t("main.directory_not_found") % schemas_path) + if binaries_path == "": + binaries_path = askdirectory(title=i18n.t("main.tkinter_binary_directory_select")) + if binaries_path == "": + return os.EX_OK + elif not os.path.isdir(binaries_path): + raise FileNotFoundError(i18n.t("main.directory_not_found") % binaries_path) + if output_path == "": + output_path = askdirectory(title=i18n.t("main.tkinter_output_select")) + if output_path == "": + output_path = os.path.split(flatc_path)[0] + elif not os.path.isdir(output_path): + raise FileNotFoundError(i18n.t("main.directory_not_found") % output_path) + schema_paths = get_schema_paths(schemas_path) + if len(schema_paths) < 1: + logging.info(i18n.t("main.no_schema_files_found"), binaries_path) + return os.EX_OK + binary_tuples, full_size = get_binary_tuples(binaries_path, schema_paths) + with logging_redirect_tqdm(): + pbar = tqdm(total=full_size, position=0, unit="B", unit_scale=True, + desc=i18n.t("main.files")) + for binary_path, schema_path in binary_tuples: + pbar.set_postfix_str(binary_path) + pbar.clear() + deserialize(flatc_path, schema_path, binary_path, output_path + os.sep + + os.path.split(os.path.relpath(binary_path, binaries_path))[0]) + pbar.update(os.stat(binary_path).st_size) + pbar.set_postfix_str("") + pbar.close() + return os.EX_OK